> Documentation index: https://docs.dairo.app/llms.txt, a list of every page in this documentation.

# Schemas

> Every object the Dairo API sends and receives, with its fields.

The objects of the API, generated from the published OpenAPI document at [docs.dairo.app/openapi.json](https://docs.dairo.app/openapi.json). Fields marked as required are always present in responses and must be sent in requests.

## ActiveOrganization

The resolved ACTIVE organization for the request and the caller's role in it.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string or null | yes |  |
| `role` | string or null | yes |  |

## AgentBindRequest

Provide an apiKeyId and/or inboxId to bind to this agent.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `apiKeyId` | string or null | no |  |
| `inboxId` | string or null | no |  |

## AgentBindResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `agentId` | string | yes |  |
| `apiKeyId` | string or null | no |  |
| `inboxId` | string or null | no |  |
| `bound` | boolean | yes |  |

## AgentCreateRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `display` | string | yes | Human-readable agent name. (max length 120) |
| `description` | string or null | no |  |

## AgentListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `agents` | array of [AgentPassport](https://docs.dairo.app/api-reference/schemas.md#agentpassport) | yes |  |

## AgentPassport

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `agentId` | string | yes | Portable, durable public identifier (agt\_...). |
| `display` | string | yes |  |
| `description` | string or null | no |  |
| `status` | `"active"`, `"suspended"`, `"revoked"` | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

## AgentReputation

An agent's rolling reputation and circuit-breaker state.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `agentId` | string | yes |  |
| `state` | `"healthy"`, `"throttled"`, `"quarantined"`, `"review"` | yes | Circuit-breaker state. healthy sends freely; throttled sends under a reduced per-agent rate; quarantined is fail-closed (no send) until cleared; review is an operator-pinned hold. |
| `verdict` | `"allow"`, `"throttle"`, `"block"`, `"review"` | yes |  |
| `shouldSend` | boolean | yes | false only when the agent is quarantined. Poll before a batch. |
| `window` | object | yes |  |
| `rates` | object | yes |  |
| `thresholds` | object | yes |  |
| `lastReason` | string or null | no |  |
| `lastTrippedAt` | string or null | no |  |
| `lastEvaluatedAt` | string or null | no |  |
| `overrideLocked` | boolean | no |  |

## AgentReputationClearResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `agentId` | string | yes |  |
| `state` | string | yes |  |
| `overrideLocked` | boolean | no |  |
| `shouldSend` | boolean | yes |  |
| `reputation` | object or null | no |  |

## AgentReputationListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `window` | object | no |  |
| `thresholds` | object | no |  |
| `agents` | array of object | yes |  |

## AgentResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `agent` | [AgentPassport](https://docs.dairo.app/api-reference/schemas.md#agentpassport) | yes |  |

## ApiKey

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"api_key"` | yes |  |
| `id` | string (uuid) | yes |  |
| `name` | string | yes |  |
| `prefix` | string | yes |  |
| `scopes` | array of string | yes |  |
| `status` | `"active"`, `"revoked"` | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `lastUsedAt` | string (date-time) or null | no |  |
| `allowedIps` | array of string or null | no | The key's IP allowlist (IPv4/IPv6 addresses or CIDR ranges), or null when the key authenticates from any IP. |
| `inboxIds` | array of string (uuid) or null | no | The inbox ids (inboxes.id) this key may send as. Empty or null means the key is unrestricted and may send as any inbox in the project — the default, and what every key minted before this field existed has. |
| `environment` | `"live"`, `"test"` | no |  |

## ApiKeyListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `apiKeys` | array of [ApiKey](https://docs.dairo.app/api-reference/schemas.md#apikey) | yes |  |

## AttachmentDownloadUrlResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `attachment` | [MessageAttachment](https://docs.dairo.app/api-reference/schemas.md#messageattachment) | yes |  |
| `downloadUrl` | string (uri) | yes |  |
| `expiresInSeconds` | integer | yes | (minimum 1, maximum 604800) |
| `shareUrl` | string (uri) | yes | Human branded Dairo share page URL for the attachment. |

## Audience

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `name` | string | yes |  |
| `description` | string or null | no |  |
| `status` | string | yes |  |
| `memberCount` | integer or null | no |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

## AudienceCreateRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes | (min length 1) |
| `description` | string | no |  |

## AudienceDeleteResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `deleted` | boolean | yes |  |
| `id` | string (uuid) | yes |  |

## AudienceDetailResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `list` | [Audience](https://docs.dairo.app/api-reference/schemas.md#audience) | yes |  |
| `members` | array of [AudienceMember](https://docs.dairo.app/api-reference/schemas.md#audiencemember) | yes |  |

## AudienceListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `audiences` | array of [Audience](https://docs.dairo.app/api-reference/schemas.md#audience) | yes |  |

## AudienceMember

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `audienceId` | string (uuid) | yes |  |
| `email` | string (email) | yes |  |
| `contactId` | string (uuid) or null | no | Set when this member was added as a contact (§12); the email is derived from the contact's primary email handle. Null for a raw-email member. |
| `name` | string or null | no |  |
| `metadata` | object | yes |  |
| `status` | string | yes |  |
| `source` | string | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

## AudienceMembersRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `members` | array of object | yes | (min items 1, max items 2000) |

## AudienceMembersResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `audienceId` | string (uuid) | yes |  |
| `imported` | integer | yes |  |

## AudienceResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `list` | [Audience](https://docs.dairo.app/api-reference/schemas.md#audience) | yes |  |

## AuditExportManifest

Signed manifest committing to the export window bounds, rowCount, and chain head.

Type: [SignedArtifact](https://docs.dairo.app/api-reference/schemas.md#signedartifact) and object.

## AuditExportResponse

A hash-chained audit-ledger export plus its signed verification manifest.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `rows` | array of [AuditExportRow](https://docs.dairo.app/api-reference/schemas.md#auditexportrow) | yes |  |
| `manifest` | [AuditExportManifest](https://docs.dairo.app/api-reference/schemas.md#auditexportmanifest) | yes |  |
| `pagination` | object | no | Keyset pagination for draining history past the 5000-row page cap. |

## AuditExportRow

One hash-chained audit row in an export.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `userId` | string (uuid) | yes |  |
| `action` | string | yes |  |
| `resourceType` | string or null | no |  |
| `resourceId` | string or null | no |  |
| `metadata` | object | no |  |
| `actor` | string or null | no |  |
| `ip` | string or null | no |  |
| `agentId` | string or null | no |  |
| `chainSeq` | integer or null | yes | 1-based contiguous per-user sequence; a gap means a deleted row. |
| `prevHash` | string or null | yes | hex sha256 of the predecessor rowHash ('' for the genesis row). |
| `rowHash` | string or null | yes | hex sha256(prevHash \|\| canonical(row)). |
| `createdAt` | string (date-time) | yes |  |

## AuditLogEvent

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `action` | string | yes | The audited action, e.g. api_key.created, domain.deleted, webhook.created. |
| `resourceType` | string or null | no | The kind of resource the action targeted, e.g. api_key, domain, inbox, webhook. |
| `resourceId` | string or null | no | Identifier of the affected resource, when one applies. |
| `actor` | string or null | no | Who performed the action: an API key id/prefix, a user id, or a system actor. |
| `ip` | string or null | no | Source IP address the action originated from, when captured. |
| `metadata` | object or null | no | Free-form, action-specific context captured with the event. Null when no metadata was recorded. |
| `createdAt` | string (date-time) | yes | When the event was recorded (UTC). |

## AuditLogListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `logs` | array of [AuditLogEvent](https://docs.dairo.app/api-reference/schemas.md#auditlogevent) | yes |  |
| `pagination` | [Pagination](https://docs.dairo.app/api-reference/schemas.md#pagination) | yes |  |

## AvailablePhoneNumber

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"phone_number.available"` | yes |  |
| `phoneNumber` | string | yes | E.164. Partially masked (e.g. `+16313------`) when `purchasable` is false. |
| `countryCode` | string | yes |  |
| `numberType` | string | yes |  |
| `locality` | string or null | no |  |
| `region` | string or null | no |  |
| `capabilities` | array of string | no |  |
| `monthlyCostUsd` | number or null | no |  |
| `setupCostUsd` | number or null | no |  |
| `purchasable` | boolean | yes | False when the provider account has not completed identity verification; the number is masked and cannot be bought yet. |

## AvailablePhoneNumberListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"list"` | yes |  |
| `data` | array of [AvailablePhoneNumber](https://docs.dairo.app/api-reference/schemas.md#availablephonenumber) | yes |  |

## BatchDeleteBucketObjectsRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `objectIds` | array of string (uuid) | yes | The storage object ids to delete (1-1000). (min items 1, max items 1000) |

## BatchDeleteMessagesRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `messageIds` | array of string (uuid) | yes | The message ids to delete (1-1000). (min items 1, max items 1000) |

## BatchDeleteResult

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"batch_delete_result"` | yes |  |
| `deleted` | array of string | yes | The ids that were successfully deleted. |
| `failed` | array of object | yes | Per-id failures; never aborts the rest of the batch. |

## BatchRecipient

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `to` | [PostalAddress](https://docs.dairo.app/api-reference/schemas.md#postaladdress) | yes |  |
| `templateData` | object | no | Per-recipient values that fill the template's {{placeholders}} for this letter. |

## Broadcast

The 202 accept envelope for a list broadcast enqueued for asynchronous fan-out.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"broadcast"` | yes |  |
| `id` | string | yes | The server-minted broadcast id (bcast\_...). |
| `audienceId` | string (uuid) | yes |  |
| `status` | `"queued"` | yes |  |

## BroadcastRequest

The body of an audience broadcast. Every active member of the audience receives its own email from inboxId, which must be an email inbox. Per-recipient fields (to, cc, bcc, contactId, channel, sendAt, inReplyTo, references, replyToMessageId, dryRun) and the Telegram-only buttons and telegram are rejected with 400: members are the recipients, one message each, so none of them can apply.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `inboxId` | string | yes |  |
| `subject` | string | no | (default `""`) |
| `text` | string | no |  |
| `html` | string | no |  |
| `react` | [ReactRenderInput](https://docs.dairo.app/api-reference/schemas.md#reactrenderinput) | no |  |
| `template` | [MessageTemplateRef](https://docs.dairo.app/api-reference/schemas.md#messagetemplateref) | no |  |
| `attachments` | array of [SendMessageAttachment](https://docs.dairo.app/api-reference/schemas.md#sendmessageattachment) | no | Outbound send attachments. 'attachment' and safe 'auto' sends are inline; current inline API limit is 10 files, 8 MiB per file, 8 MiB total decoded bytes. 'link' is explicit and must be backed by a pre-created Dairo share link placed deliberately in text/html by the caller. (max items 10) |
| `idempotencyKey` | string | no | Per-broadcast idempotency base: each member's message is keyed {idempotencyKey}:{address}, so re-posting the same broadcast never re-sends to a member already accepted. Omitted, the base is derived from the org, the audience and the body. (max length 128) |
| `replyTo` | string (email) | no | Optional single Reply-To address, set verbatim as the MIME Reply-To header. |
| `headers` | map of string | no | Optional custom MIME headers ({name: value}). Spoofable/provenance headers are dropped server-side; malformed names or CR/LF are rejected. |
| `tags` | map of string | no | Optional SES message tags ({name: value}), validated against SES tag constraints. |
| `ignoreComplaints` | boolean | no | Override complaint and hard-bounce suppression for every member that previously complained or bounced. (default `false`) |

## Bucket

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"bucket"` | yes |  |
| `id` | string | yes | Bucket identifier (buk\_\*). |
| `name` | string | yes |  |
| `displayName` | string or null | no |  |
| `description` | string or null | no |  |
| `isDefault` | boolean | yes |  |
| `status` | `"active"`, `"archived"` | yes |  |
| `usedBytes` | integer | yes | (minimum 0) |
| `objectCount` | integer | yes | (minimum 0) |
| `metadata` | object or null | no |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

## BucketListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"list"` | yes |  |
| `data` | array of [Bucket](https://docs.dairo.app/api-reference/schemas.md#bucket) | yes |  |
| `pagination` | [Pagination](https://docs.dairo.app/api-reference/schemas.md#pagination) | yes |  |

## BucketObject

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"bucket_object"` | yes |  |
| `id` | string (uuid) | yes |  |
| `filename` | string | yes |  |
| `contentType` | string or null | no |  |
| `bytes` | integer | yes | (minimum 0) |
| `scanStatus` | `"pending"`, `"clean"`, `"infected"`, `"failed"`, `"skipped"` | yes | Async malware-scan verdict (W2.2). Objects start 'pending' (owner-only, not publicly served) and become 'clean' or 'infected'/'failed' once the scanner runs. Public links (/d/, /s/) and native attach require 'clean' (or trusted 'skipped'). |
| `createdAt` | string (date-time) | yes |  |

## BucketObjectListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"list"` | yes |  |
| `data` | array of [BucketObject](https://docs.dairo.app/api-reference/schemas.md#bucketobject) | yes |  |
| `pagination` | [Pagination](https://docs.dairo.app/api-reference/schemas.md#pagination) | yes |  |

## Budget

A fail-closed spend/send budget for an account, key, or agent scope.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `scope` | `"account"`, `"key"`, `"agent"` | yes |  |
| `scopeId` | string or null | no | null for the account scope; the api_key id for key/agent scopes. |
| `enabled` | boolean | yes |  |
| `limits` | [BudgetLimits](https://docs.dairo.app/api-reference/schemas.md#budgetlimits) | yes |  |
| `usage` | [BudgetUsage](https://docs.dairo.app/api-reference/schemas.md#budgetusage) | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

## BudgetLimits

Budget limits. At least one limit (maxSendsPerDay, maxNewRecipientsPerHour, maxSpendPerDayCents, or hardStopOnComplaint=true) is required. maxSpendPerDayCents is allowed on the account budget only.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `maxSendsPerDay` | integer or null | no | Recipient-denominated sends allowed in a rolling 24h window. (minimum 0, maximum 1000000) |
| `maxNewRecipientsPerHour` | integer or null | no | Recipients addressed in a rolling 1h window. V1 counts every recipient on each send (a conservative over-count); true distinct-recipient dedup is V2. (minimum 0, maximum 1000000) |
| `hardStopOnComplaint` | boolean or null | no | When true, every send under this budget fails closed once the account has any recorded complaint. |
| `maxSpendPerDayCents` | integer or null | no | Account budgets only: the daily money cap in USD cents. A paid hold (AI generation, phone call, letter) that would take the last 24 hours of spend past it is rejected with spend_cap_reached; 0 blocks every paid hold. (minimum 0, maximum 1000000) |

## BudgetListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `budgets` | array of [Budget](https://docs.dairo.app/api-reference/schemas.md#budget) | yes |  |

## BudgetResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `budget` | [Budget](https://docs.dairo.app/api-reference/schemas.md#budget) | yes |  |

## BudgetSetRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `scope` | `"account"`, `"key"`, `"agent"` | yes |  |
| `scopeId` | string or null | no | Required for key/agent scopes (an api_key id you own); omit for the account scope. |
| `enabled` | boolean | no | (default `true`) |
| `limits` | [BudgetLimits](https://docs.dairo.app/api-reference/schemas.md#budgetlimits) | yes |  |

## BudgetUsage

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `sendsLast24h` | integer | yes | Recipient-denominated sends recorded in the last 24h for this scope. |
| `newRecipientsLastHour` | integer | yes | Recipients addressed in the last hour for this scope. |
| `accountHasComplaint` | boolean | yes | Whether the account has any recorded complaint (drives hardStopOnComplaint). |

## CancelOutboundMessageResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `message` | [OutboundMessage](https://docs.dairo.app/api-reference/schemas.md#outboundmessage) | yes |  |

## Contact

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `projectId` | string (uuid) | no |  |
| `userId` | string (uuid) | no |  |
| `alias` | string or null | no | @-referenceable handle, unique per project. |
| `displayName` | string | yes |  |
| `kind` | `"person"`, `"agent"`, `"self"` | yes |  |
| `isSelf` | boolean | yes | The project self contact (the "@me" recipient). |
| `info` | string or null | no | Free-text notes. |
| `metadata` | object | no |  |
| `avatarUrl` | string or null | no |  |
| `handles` | array of [ContactHandle](https://docs.dairo.app/api-reference/schemas.md#contacthandle) | no | Present on the detail view (GET /v1/contacts/{id} and /v1/contacts/me). |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

## ContactCreateRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `displayName` | string | yes | (min length 1, max length 200) |
| `alias` | string | no |  |
| `kind` | `"person"`, `"agent"`, `"self"` | no |  |
| `info` | string | no | (max length 10000) |
| `avatarUrl` | string | no |  |
| `metadata` | object | no |  |
| `handles` | array of [ContactHandleInput](https://docs.dairo.app/api-reference/schemas.md#contacthandleinput) | no |  |

## ContactDetailResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `contact` | [Contact](https://docs.dairo.app/api-reference/schemas.md#contact) | yes |  |

## ContactHandle

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `contactId` | string (uuid) | yes |  |
| `projectId` | string (uuid) | no |  |
| `userId` | string (uuid) | no |  |
| `channel` | string | yes | Delivery channel of this handle (email/a2a/telegram/postal/…). |
| `value` | string | yes | The channel address (email address, chat id, agent id, or for a postal handle the one-line formatted address). |
| `label` | string or null | no |  |
| `isPrimary` | boolean | yes |  |
| `verified` | boolean | yes |  |
| `metadata` | object | no | Structured extras. For a postal handle this carries the structured address: { name, street, line2?, postalCode, city, state?, country } (see PostalAddress). |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

## ContactHandleInput

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `channel` | string | yes | email / a2a / telegram / postal / … |
| `value` | string | yes | The channel address. For a postal handle this is the one-line formatted address (for display); the structured address goes in metadata. (max length 500) |
| `label` | string | no |  |
| `isPrimary` | boolean | no | (default `false`) |
| `metadata` | object | no | Structured extras. REQUIRED for a postal handle: { name, street, line2?, postalCode, city, state?, country } (see PostalAddress). name/street/postalCode/city/country are required; country is an ISO-3166 alpha-2 code or a country name. |

## ContactHandleResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `contactHandle` | [ContactHandle](https://docs.dairo.app/api-reference/schemas.md#contacthandle) | yes |  |

## ContactListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"list"` | yes |  |
| `data` | array of [Contact](https://docs.dairo.app/api-reference/schemas.md#contact) | yes |  |
| `pagination` | [Pagination](https://docs.dairo.app/api-reference/schemas.md#pagination) | yes |  |

## ContactUpdateRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `displayName` | string | no | (min length 1, max length 200) |
| `alias` | string or null | no |  |
| `kind` | `"person"`, `"agent"`, `"self"` | no |  |
| `info` | string or null | no | (max length 10000) |
| `avatarUrl` | string or null | no |  |
| `metadata` | object | no |  |

## CreateApiKeyRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes |  |
| `scopes` | array of `"account:read"`, `"account:write"`, `"admin"`, `"agents:read"`, `"agents:write"`, `"audiences:read"`, `"audiences:write"`, `"buckets:read"`, `"buckets:write"`, `"budgets:read"`, `"budgets:write"`, `"compliance:read"`, `"compliance:write"`, `"contacts:read"`, `"contacts:write"`, `"domains:read"`, `"domains:write"`, `"events:read"`, `"events:write"`, `"inboxes:read"`, `"inboxes:write"`, `"keys:read"`, `"keys:write"`, `"letters"`, `"letters:read"`, `"letters:send"`, `"messages"`, `"messages:read"`, `"messages:send"`, `"phone"`, `"phone:call"`, `"phone:read"`, `"phone:write"`, `"templates:read"`, `"templates:write"`, `"webhooks:read"`, `"webhooks:write"` | yes | (min items 1) |
| `allowedIps` | array of string or null | no | Optional IPv4/IPv6 addresses or CIDR ranges (up to 50) the key may authenticate from. Deny-by-default: a correct secret from an unlisted IP is rejected with 403. Omit or null for any-IP. |
| `environment` | `"test"`, `"live"`, `null` | no | Cosmetic LABEL only; picks the secret prefix (`dairo_test_…`/`dairo_live_…`). It does NOT create a sandbox: a `test` key sends real mail, really delivered and really billed. Defaults to `live`. (default `"live"`) |
| `projectId` | string or null | no | Optional organization to bind the key to. Defaults to the caller's active project; you must be a member of the one you name. |
| `inboxIds` | array of string (uuid) or null | no | Optional per-inbox restriction: the inbox ids (inboxes.id, up to 100) this key may send as. Enforced server-side on every send, so it binds a raw POST /v1/messages exactly as much as it binds the CLI. Omit, null, or \[\] for unrestricted (any inbox). A key that is itself restricted may only grant a subset of its own inboxes; a child that requests none inherits the parent's restriction rather than escaping it. |

## CreateApiKeyResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"api_key"` | yes |  |
| `id` | string (uuid) | yes |  |
| `name` | string | yes |  |
| `prefix` | string | yes |  |
| `scopes` | array of string | yes |  |
| `status` | `"active"`, `"revoked"` | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `lastUsedAt` | string (date-time) or null | no |  |
| `allowedIps` | array of string or null | no | The key's IP allowlist (IPv4/IPv6 addresses or CIDR ranges), or null when the key authenticates from any IP. |
| `inboxIds` | array of string (uuid) or null | no | The inbox ids (inboxes.id) this key may send as. Empty or null means the key is unrestricted and may send as any inbox in the project — the default, and what every key minted before this field existed has. |
| `environment` | `"live"`, `"test"` | no |  |
| `secret` | string | yes | One-time API key secret. Store it now; it is never shown again. |
| `secretShownOnce` | `true` | yes |  |

## CreateBucketObjectRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `filename` | string | yes | (min length 1, max length 1024) |
| `contentType` | string | yes | (min length 1, max length 256) |
| `expectedBytes` | integer or null | no | Optional client-declared size; the true size is verified on finalize. (minimum 0) |
| `metadata` | object or null | no |  |

## CreateBucketRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes | Unique bucket name (case-insensitive per account). (min length 1, max length 128) |
| `displayName` | string or null | no | (max length 256) |
| `description` | string or null | no | (max length 1024) |
| `metadata` | object or null | no |  |

## CreateDomainRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `domain` | string | yes |  |

## CreateInboxRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `username` | string | yes |  |
| `localPart` | string | no |  |
| `domain` | string | yes |  |
| `agent` | string | no | (default `"Inbox Agent"`) |
| `mode` | `"receive_send"`, `"send_only"`, `"receive_only"` | no | (default `"receive_send"`) |

## CreateLetterBatchRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `templateId` | string | yes | The stored letter template rendered once per recipient. |
| `recipients` | array of [BatchRecipient](https://docs.dairo.app/api-reference/schemas.md#batchrecipient) | yes | (min items 1, max items 1000) |
| `from` | [PostalAddress](https://docs.dairo.app/api-reference/schemas.md#postaladdress) or null | no |  |
| `print` | [LetterPrintOptions](https://docs.dairo.app/api-reference/schemas.md#letterprintoptions) | no |  |
| `delivery` | [LetterDelivery](https://docs.dairo.app/api-reference/schemas.md#letterdelivery) | no |  |
| `paymentSlip` | `"qr"`, `"sepaDe"`, `"sepaAt"` or null | no | Optional payment-slip overlay applied to every letter. |
| `autoSend` | boolean | no | (default `true`) |
| `notifications` | boolean | no | (default `true`) |
| `metadata` | object | no |  |

## CreateLetterRequest

Exactly one document source must be provided: `pdfBase64`, `file`, or `templateId`. Validate any letter first with `POST /v1/letters/verify`; `GET /v1/letters/requirements` has the full layout spec and compliant starter templates.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `pdfBase64` | string or null | no | Base64-encoded PDF (alternative to file). |
| `file` | [LetterFileRef](https://docs.dairo.app/api-reference/schemas.md#letterfileref) or null | no |  |
| `fileName` | string | yes |  |
| `to` | [PostalAddress](https://docs.dairo.app/api-reference/schemas.md#postaladdress) | yes |  |
| `from` | [PostalAddress](https://docs.dairo.app/api-reference/schemas.md#postaladdress) or null | no |  |
| `print` | [LetterPrintOptions](https://docs.dairo.app/api-reference/schemas.md#letterprintoptions) | no |  |
| `delivery` | [LetterDelivery](https://docs.dairo.app/api-reference/schemas.md#letterdelivery) | no |  |
| `autoSend` | boolean | no | (default `true`) |
| `metadata` | object | no |  |
| `templateId` | string or null | no | A stored letter template id. Dairo renders the template (filled with `templateData`) and composites the `to` recipient into the address window — the third document source, mutually exclusive with `pdfBase64`/`file`. Required for the structured `payment` object. |
| `templateData` | object or null | no | Values for the template's `{{placeholder}}` variables. Ignored unless `templateId` is set. |
| `dryRun` | boolean | no | When true the letter is validated, rendered, and priced exactly like a real send but is NEVER created, mailed, or charged — the response is a non-persisted `status:"preview"` object. The per-request no-send rehearsal. (default `false`) |
| `notifications` | boolean | no | Email the account owner a status update at notable delivery milestones. (default `true`) |
| `paymentSlip` | `"qr"`, `"sepaDe"`, `"sepaAt"`, `null` | no | Bring-your-own-slip flag for a `pdfBase64`/`file` letter whose PDF already contains the slip artwork: `qr` (Swiss QR-bill), `sepaDe`, or `sepaAt`. Omit/null for a normal letter. To have Dairo GENERATE the slip use the structured `payment` object (template letters only). |
| `payment` | [LetterPayment](https://docs.dairo.app/api-reference/schemas.md#letterpayment) or null | no | Structured payment object — Dairo GENERATES the payment slip (Swiss QR-bill / SEPA Zahlschein + GiroCode) and composites it full-width at the page bottom. Honored ONLY with `templateId`; pairing it with `pdfBase64`/`file` is a 400. |

## CreateLetterTemplateRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes |  |
| `html` | string | yes | The branded letter HTML. Use {{placeholders}} where values vary per recipient and leave the recipient address window blank. |
| `variables` | array of string or [LetterTemplateVariable](https://docs.dairo.app/api-reference/schemas.md#lettertemplatevariable) or object | no | Declared template variables. Preferred: \[{name, type?, example?, label?}\]. Also accepted: an array of names, or an object {name: type}. Stored and returned in the preferred shape. |
| `status` | [LetterTemplateStatus](https://docs.dairo.app/api-reference/schemas.md#lettertemplatestatus) | no |  |

## CreateShareLinkRequest

Options for a secure share link over a stored object.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `password` | string | no | Optional password the recipient must enter on the /s/ page: 8-256 characters, used exactly as given (never trimmed) and without a NUL character. An empty string means no password. (min length 8, max length 256) |
| `maxUses` | integer | no | Optional use cap. 1 = one-time (the /d/ download returns 410 Gone after the first use). (minimum 1) |
| `expiresAt` | string (date-time) | no | Optional absolute expiry (RFC3339). Must be in the future. |

## CreateWebhookRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string (uri) | yes |  |
| `events` | array of `"message.received"`, `"message.quarantined"`, `"message.sent"`, `"message.delivered"`, `"message.bounced"`, `"message.complained"`, `"message.delayed"`, `"budget.tripped"`, `"compliance.erasure.completed"`, `"subprocessor.changed"`, `"object.scanned"`, `"share_link.opened"`, `"letter.status_changed"`, `"call.status_changed"`, `"domain.verified"` | yes | (min items 1) |
| `label` | string | no | Optional human label for the endpoint. Surfaced read-only and used as the primary match field by prefix-scoped test cleanup. |

## CreateWebhookResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"webhook"` | yes |  |
| `id` | string (uuid) | yes |  |
| `url` | string (uri) | yes |  |
| `events` | array of `"message.received"`, `"message.quarantined"`, `"message.sent"`, `"message.delivered"`, `"message.bounced"`, `"message.complained"`, `"message.delayed"`, `"budget.tripped"`, `"compliance.erasure.completed"`, `"subprocessor.changed"`, `"object.scanned"`, `"share_link.opened"`, `"letter.status_changed"`, `"call.status_changed"`, `"domain.verified"` | yes |  |
| `label` | string or null | no | Optional human label for the endpoint, or null when unlabeled. |
| `status` | `"active"`, `"paused"` | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `lastDeliveryAt` | string (date-time) or null | no | Timestamp of the most recent successful webhook delivery, or null before the first successful delivery. |
| `secondarySecretExpiresAt` | string (date-time) or null | no | End of the dual-secret window after a rotation, or null. |
| `secondarySecretActive` | boolean | no | Whether deliveries are still signed with the previous secret too. |
| `consecutiveFailures` | integer | no |  |
| `pausedReason` | string or null | no | auto:delivery_failures when the circuit breaker paused the endpoint, else null. |
| `autoPaused` | boolean | no |  |
| `autoPausedAt` | string (date-time) or null | no |  |
| `signingSecret` | string | yes | One-time signing secret (whsec\_...). Store it now; it is never shown again. |
| `secretShownOnce` | `true` | yes |  |

## DeleteResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `deleted` | boolean | yes |  |

## DeletionCertificate

A court-usable, Ed25519-signed receipt enumerating exactly what was destroyed.

Type: [SignedArtifact](https://docs.dairo.app/api-reference/schemas.md#signedartifact) and object.

## DnsRecord

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | `"CNAME"`, `"MX"`, `"TXT"` | yes |  |
| `host` | string | yes |  |
| `value` | string | yes |  |
| `priority` | integer or null | no |  |
| `status` | `"pending"`, `"configured"` | yes |  |

## Domain

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"domain"` | yes |  |
| `id` | string (uuid) | yes |  |
| `domain` | string | yes |  |
| `status` | `"pending"`, `"verified"`, `"failed"` | yes |  |
| `verifiedAt` | string (date-time) or null | no |  |
| `region` | string | yes |  |
| `records` | array of [DnsRecord](https://docs.dairo.app/api-reference/schemas.md#dnsrecord) | yes |  |
| `type` | `"custom"`, `"included"`, `"system"` | no |  |
| `isIncluded` | boolean | no |  |
| `isDeletable` | boolean | no |  |
| `isSystem` | boolean | no | Only on the shared dairo.app system domain. |
| `isReadOnly` | boolean | no | Only on the shared dairo.app system domain. |
| `receiving` | `"active"`, `"pending"` | no | Only on the verify response: whether the domain receives mail (it does exactly when verified). |

## DomainListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"list"` | yes |  |
| `data` | array of [Domain](https://docs.dairo.app/api-reference/schemas.md#domain) | yes |  |
| `pagination` | [Pagination](https://docs.dairo.app/api-reference/schemas.md#pagination) | yes |  |

## EditTelegramMessageRequest

Edit a Telegram message you sent: exactly one of text or html, plus an optional replacement inline keyboard.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `text` | string | no | New plain-text body. Mutually exclusive with html. |
| `html` | string | no | New rich-HTML body. Mutually exclusive with text. |
| `buttons` | array of array of [TelegramButton](https://docs.dairo.app/api-reference/schemas.md#telegrambutton) | no | Optional replacement inline keyboard (rows of buttons). Pass \[\] to clear the keyboard. Callback buttons show a feedback toast on tap (default: ✓ + the button label; set alert:true for a modal). |

## EraseRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | `"subject"`, `"inbox"` | yes | subject: erase all stored mail to or from subjectHandle. inbox: purge everything in inboxId. |
| `subjectHandle` | string (email) | no | Required for type subject: the data subject's plain email address. |
| `inboxId` | string (uuid) | no | Required for type inbox: the inbox to purge. |
| `idempotencyKey` | string | no | Optional caller-supplied key; a retry with the same key returns the same job, a reuse for another target is a 409. |

## ErasureJob

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `kind` | `"erase_subject"`, `"purge_inbox"` | yes |  |
| `subjectHandle` | string or null | no |  |
| `inboxId` | string or null | no |  |
| `status` | `"pending"`, `"running"`, `"completed"`, `"failed"` | yes |  |
| `deleted` | [ErasureJobDeletedCounts](https://docs.dairo.app/api-reference/schemas.md#erasurejobdeletedcounts) | yes |  |
| `certificate` | [DeletionCertificate](https://docs.dairo.app/api-reference/schemas.md#deletioncertificate) or null | no |  |
| `certificateSig` | string or null | no |  |
| `error` | string or null | no |  |
| `createdAt` | string (date-time) | yes |  |
| `startedAt` | string (date-time) or null | no |  |
| `completedAt` | string (date-time) or null | no |  |

## ErasureJobAccepted

Acknowledgement that an erasure job was enqueued.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `jobId` | string (uuid) | yes |  |
| `kind` | `"erase_subject"`, `"purge_inbox"` | yes |  |
| `status` | `"pending"`, `"running"`, `"completed"`, `"failed"` | yes |  |
| `createdAt` | string (date-time) | yes |  |

## ErasureJobDeletedCounts

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `messages` | integer | no |  |
| `threads` | integer | no |  |
| `attachments` | integer | no |  |
| `s3Objects` | integer | no |  |
| `ledgerRowsRedacted` | integer | no |  |

## ErasureJobResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `job` | [ErasureJob](https://docs.dairo.app/api-reference/schemas.md#erasurejob) | yes |  |

## Error

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `message` | string | yes |  |
| `code` | string | no |  |
| `type` | string or null | no |  |
| `param` | string or null | no |  |

## ErrorResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `error` | [Error](https://docs.dairo.app/api-reference/schemas.md#error) | yes |  |

## EventLedgerPage

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"list"` | yes | Always "list" — the unified list envelope discriminator. |
| `data` | array of [LedgerEvent](https://docs.dairo.app/api-reference/schemas.md#ledgerevent) | yes | The ledger events for this page, each with seq/partitionKey join keys. (This is the array named `data`, not `events`.) |
| `pagination` | [Pagination](https://docs.dairo.app/api-reference/schemas.md#pagination) | yes |  |
| `gaps` | array of [LedgerGap](https://docs.dairo.app/api-reference/schemas.md#ledgergap) | yes | Per-partition seq gaps detected within this page's window. An empty array means no gap was detected in the returned window — detection is window-local (only the seq range present on this page), not whole-history. |

## EventReplayBound

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `eventId` | string | yes |  |
| `seq` | integer (int64) | yes |  |
| `partitionKey` | string | yes |  |
| `createdAt` | string (date-time) | yes |  |

## EventReplayRequest

Selects a ledger slice to re-deliver. Provide exactly one lower bound: since (an opaque cursor from GET /v1/events), sinceSeq (+inboxId, replay one partition from a seq), or sinceTimestamp (RFC3339).

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `since` | string | no | Opaque keyset cursor (from GET /v1/events) to replay forward from. |
| `sinceSeq` | integer (int64) | no | Replay one partition starting at this seq (inclusive). Requires inboxId. |
| `inboxId` | string (uuid) | no | Scope the replay to one inbox partition (required with sinceSeq; an optional filter otherwise). |
| `sinceTimestamp` | string (date-time) | no | Replay events created at or after this RFC3339 timestamp. |
| `until` | string (date-time) | no | Optional upper bound (events created at or before this RFC3339 timestamp). |
| `types` | array of string | no | Optional event-type filter, e.g. \["email.bounced"\]. |
| `webhookId` | string | no | Optional: replay only to this one webhook subscription; otherwise to every matching active subscription. |
| `maxEvents` | integer | no | Hard cap on the slice size. A slice that exceeds the cap is rejected with 400 asking the caller to page with until/maxEvents. (minimum 1, maximum 5000, default `1000`) |

## EventReplayResult

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `replayed` | integer | yes | Number of events re-delivered to at least one matching subscription. |
| `skipped` | integer | yes | Number of events in the slice with no matching active subscription. |
| `from` | [EventReplayBound](https://docs.dairo.app/api-reference/schemas.md#eventreplaybound) or null | no |  |
| `to` | [EventReplayBound](https://docs.dairo.app/api-reference/schemas.md#eventreplaybound) or null | no |  |

## Inbox

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"inbox"` | yes |  |
| `id` | string (uuid) | yes |  |
| `address` | string (email) or null | yes |  |
| `localPart` | string or null | yes |  |
| `domain` | string or null | yes |  |
| `domainStatus` | `"pending"`, `"verified"`, `"failed"`, `null` | no |  |
| `agent` | string | no |  |
| `mode` | `"receive_send"`, `"send_only"`, `"receive_only"` | yes |  |
| `status` | `"active"`, `"paused"`, `"deleted"` | yes |  |
| `createdAt` | string (date-time) or null | no |  |
| `lastMessageAt` | string (date-time) or null | no |  |
| `channel` | `"email"`, `"a2a"` | yes | The channel this message transited: "email" (external, over SES) or "a2a" (internal Dairo-to-Dairo, which skips SES). Reserved future values (sms, whatsapp, …) are not yet wired. |

## InboxListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `inboxes` | array of [Inbox](https://docs.dairo.app/api-reference/schemas.md#inbox) | yes |  |

## InboxSchemaContract

JSON-Schema-lite extraction contract: a flat map of field name to declaration. Identical shape to a template variables schema. '{}' (or omitted) means no contract (passthrough).

Type: map of object.

## InboxSchemaDeleteResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `deleted` | boolean | yes |  |
| `inboxId` | string (uuid) | yes |  |

## InboxSchemaResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `inboxId` | string (uuid) | yes |  |
| `schema` | [InboxSchemaContract](https://docs.dairo.app/api-reference/schemas.md#inboxschemacontract) | yes |  |
| `onValidationError` | `"quarantine"`, `"passthrough"` | yes |  |
| `extractionHint` | string or null | no |  |
| `createdAt` | string (date-time) | no |  |
| `updatedAt` | string (date-time) | no |  |

## LedgerEvent

One durable, ordered row of the reliability-spine event ledger. Mirrors the webhook event for the same source event (same eventId/type/data) and adds the monotonic per-partition seq plus the loop-wide idempotencyKey.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `eventId` | string | yes | The evt\_`<uuid>` id; identical to the X-Dairo-Event-Id of the original webhook delivery, so consumer-side dedupe is exact across live delivery and replay. |
| `type` | string | yes | Event type, e.g. email.sent, email.delivered, email.bounced, email.complained, message.received. |
| `seq` | integer (int64) | yes | Monotonic, 1-based, contiguous sequence number within the partition. A gap in seq is a lost event (surfaced in gaps\[\]). |
| `partitionKey` | string | yes | The ordering domain for seq, e.g. inbox:`<uuid>`. |
| `inboxId` | string (uuid) or null | no |  |
| `threadId` | string (uuid) or null | no |  |
| `idempotencyKey` | string or null | no | Loop-wide idempotency key threaded from the originating send onto every status event and the matched inbound reply. Null when the event did not originate from an idempotent send. |
| `outboundMessageId` | string (uuid) or null | no |  |
| `messageId` | string (uuid) or null | no |  |
| `providerMessageId` | string or null | no |  |
| `occurredAt` | string (date-time) or null | no |  |
| `createdAt` | string (date-time) | yes |  |
| `data` | object | no | The original webhook event data payload (metadata-first, body-free). |

## LedgerGap

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `partitionKey` | string | yes | The partition (ordering domain) the gap was detected in, e.g. inbox:`<uuid>`. |
| `missingSeq` | array of integer (int64) | yes | Sequence numbers expected-but-absent within the returned window for this partition. A non-empty list means one or more events were lost; replay to recover. |

## Letter

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"letter"` | yes |  |
| `id` | string | yes |  |
| `status` | [LetterStatus](https://docs.dairo.app/api-reference/schemas.md#letterstatus) | yes |  |
| `fileName` | string or null | no |  |
| `pageCount` | integer or null | no |  |
| `to` | [PostalAddress](https://docs.dairo.app/api-reference/schemas.md#postaladdress) | no |  |
| `from` | [PostalAddress](https://docs.dairo.app/api-reference/schemas.md#postaladdress) or null | no |  |
| `print` | [LetterPrintOptions](https://docs.dairo.app/api-reference/schemas.md#letterprintoptions) | no |  |
| `delivery` | [LetterDelivery](https://docs.dairo.app/api-reference/schemas.md#letterdelivery) | no |  |
| `paperTypes` | array of [LetterPaperType](https://docs.dairo.app/api-reference/schemas.md#letterpapertype) | no |  |
| `autoSend` | boolean | no |  |
| `price` | [LetterMoney](https://docs.dairo.app/api-reference/schemas.md#lettermoney) or null | no |  |
| `trackingNumber` | string or null | no |  |
| `metadata` | object | no |  |
| `lastEventType` | string or null | no | The `type` of the newest event on the letter's timeline (e.g. `processing`, `canceled`); null until the letter has an event. |
| `lastEventAt` | string (date-time) or null | no | When the newest timeline event occurred; null until the letter has an event. |
| `submittedAt` | string (date-time) or null | no |  |
| `canceledAt` | string (date-time) or null | no |  |
| `error` | string or null | no |  |
| `createdAt` | string (date-time) or null | no |  |
| `updatedAt` | string (date-time) or null | no |  |
| `events` | array of [LetterEvent](https://docs.dairo.app/api-reference/schemas.md#letterevent) | no |  |
| `paymentSlip` | `"qr"`, `"sepaDe"`, `"sepaAt"`, `null` | no | The chosen payment-slip overlay, or null for a normal letter. |
| `dryRun` | boolean | no | True for a validate/price-only letter that is never mailed or charged. |
| `providerMode` | `"test"`, `"live"` | no | `test`: the letter is validated and priced but never printed, mailed, or charged (a dry run, or the account's provider mode is test). `live`: it will be printed and posted. |
| `notifications` | boolean | no | Owner milestone-email preference for this letter. |
| `payment` | [LetterPayment](https://docs.dairo.app/api-reference/schemas.md#letterpayment) or null | no | The structured payment object the slip was generated from (owner records), or null. |
| `undeliverableReason` | string or null | no | Human-readable reason when the letter reached `undeliverable`; null otherwise. |
| `batchId` | string or null | no | The owning batch id for a batch-member letter; null for a single send. Pairs with the `?batchId=` list filter. |

## LetterAddressPlacement

Type: `"left"`, `"right"`.

## LetterBatch

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"letter_batch"` | yes |  |
| `id` | string | yes |  |
| `templateId` | string or null | no |  |
| `status` | string | yes |  |
| `total` | integer | yes |  |
| `completed` | integer | no | Member letters in a terminal state (delivered, undeliverable, canceled, failed). |
| `statusCounts` | map of integer | no | Live count of member letters keyed by their letter status. |
| `createdAt` | string (date-time) or null | no |  |
| `updatedAt` | string (date-time) or null | no |  |

## LetterBatchCreateResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"letter_batch"` | yes |  |
| `id` | string | yes |  |
| `total` | integer | yes | Number of letters created (one per recipient). |
| `status` | string | yes | The batch status; queued on create. |

## LetterDelivery

Type: `"economy"`, `"priority"`, `"registered"`.

## LetterEvent

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"letter_event"` | yes |  |
| `eventId` | string | yes |  |
| `letterId` | string or null | no |  |
| `type` | string | yes |  |
| `code` | string or null | no |  |
| `description` | string or null | no |  |
| `producer` | string or null | no |  |
| `location` | string or null | no |  |
| `reason` | string or null | no | Human-readable reason, when the event carries one (an undeliverable reason, or why a created letter is held, e.g. code `test_mode`). |
| `hasImage` | boolean | no |  |
| `occurredAt` | string (date-time) or null | no |  |
| `recordedAt` | string (date-time) or null | no |  |

## LetterEventList

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"list"` | yes |  |
| `data` | array of [LetterEvent](https://docs.dairo.app/api-reference/schemas.md#letterevent) | yes |  |
| `pagination` | [Pagination](https://docs.dairo.app/api-reference/schemas.md#pagination) | yes |  |

## LetterFileRef

Reuse a PDF already stored in Dairo as the letter — no re-upload. Provide EXACTLY ONE of `attachmentId` (an email attachment) or `objectId` (a storage object from the buckets API). Providing both, or neither, is a 400.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `attachmentId` | string | no | An email attachment id to reuse as the letter PDF. |
| `messageId` | string or null | no | Optional message id the attachment belongs to (informational). |
| `objectId` | string | no | A storage object id (from POST /v1/buckets/{bucketId}/objects upload + finalize) to reuse as the letter PDF. Must have passed its malware scan (clean/skipped). |

## LetterList

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"list"` | yes |  |
| `data` | array of [Letter](https://docs.dairo.app/api-reference/schemas.md#letter) | yes |  |
| `pagination` | [Pagination](https://docs.dairo.app/api-reference/schemas.md#pagination) | yes |  |

## LetterMoney

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `currency` | string | yes | Upper-case ISO 4217 code, e.g. EUR. |
| `amount` | number | yes |  |
| `walletAmount` | number | no | The price in the credits-wallet currency, at the same conversion rate the charge uses (rounded to the cent). |
| `walletCurrency` | string | no | The credits-wallet currency (USD). |
| `walletRate` | number | no | The conservative rate from the letter currency (EUR) to the wallet currency used for `walletAmount` (`walletAmount` = `amount` × `walletRate`, rounded to the cent). |

## LetterPaperType

Type: `"standard"`, `"qr"`, `"sepa_at"`, `"sepa_de"`.

## LetterPayment

A generated payment slip: `qr` = Swiss QR-bill (CHF), `sepaDe`/`sepaAt` = SEPA Zahlschein + GiroCode (EUR). The debtor defaults to the letter recipient.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | `"qr"`, `"sepaDe"`, `"sepaAt"` | yes |  |
| `creditor` | object | yes | The payee (account holder). |
| `amount` | number | yes | Amount > 0, at most 2 decimals. |
| `currency` | `"CHF"`, `"EUR"`, `null` | no | Must match `type` (qr => CHF, sepa\* => EUR); omitted = inferred. |
| `reference` | string or null | no | Structured payment reference. |
| `message` | string or null | no | Unstructured remittance info (Verwendungszweck). |
| `debtor` | [LetterPaymentParty](https://docs.dairo.app/api-reference/schemas.md#letterpaymentparty) or null | no |  |

## LetterPaymentParty

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no |  |
| `street` | string or null | no |  |
| `houseNumber` | string or null | no |  |
| `postalCode` | string or null | no |  |
| `city` | string or null | no |  |
| `country` | string or null | no | ISO 3166-1 alpha-2. |

## LetterPrice

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"letter_price"` | yes |  |
| `currency` | string | yes | Upper-case ISO 4217 code, e.g. EUR. |
| `amount` | number | yes |  |
| `walletAmount` | number | no | The price in the credits-wallet currency, at the same conversion rate the charge uses (rounded to the cent). |
| `walletCurrency` | string | no | The credits-wallet currency (USD). |
| `walletRate` | number | no | The conservative rate from the letter currency (EUR) to the wallet currency used for `walletAmount` (`walletAmount` = `amount` × `walletRate`, rounded to the cent). |
| `country` | string | no |  |
| `pageCount` | integer | no |  |
| `delivery` | [LetterDelivery](https://docs.dairo.app/api-reference/schemas.md#letterdelivery) | no |  |
| `print` | [LetterPrintOptions](https://docs.dairo.app/api-reference/schemas.md#letterprintoptions) | no |  |
| `breakdown` | object | no |  |

## LetterPriceRequest

Give the destination (`country` or `to.country`) and one page source: `pageCount`, `pdfBase64`, `file`, or `templateId` (rendered with `templateData` and `to` to count pages). First match wins in that order.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `country` | string | no | ISO 3166-1 alpha-2 country code. Optional when `to.country` is given. |
| `pageCount` | integer or null | no | (minimum 1) |
| `pdfBase64` | string or null | no |  |
| `print` | [LetterPrintOptions](https://docs.dairo.app/api-reference/schemas.md#letterprintoptions) | no |  |
| `delivery` | [LetterDelivery](https://docs.dairo.app/api-reference/schemas.md#letterdelivery) | no |  |
| `paperTypes` | array of [LetterPaperType](https://docs.dairo.app/api-reference/schemas.md#letterpapertype) | no |  |
| `file` | [LetterFileRef](https://docs.dairo.app/api-reference/schemas.md#letterfileref) | no |  |
| `templateId` | string or null | no | A stored letter template; rendered to count pages. |
| `templateData` | object or null | no | Values for the template's placeholders. |
| `to` | [PostalAddress](https://docs.dairo.app/api-reference/schemas.md#postaladdress) or null | no | Recipient. Its country prices the letter when `country` is absent; a template render composites it (a sample recipient is used when absent). |

## LetterPrintMode

Type: `"color"`, `"grayscale"`.

## LetterPrintOptions

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `mode` | [LetterPrintMode](https://docs.dairo.app/api-reference/schemas.md#letterprintmode) | no |  |
| `sides` | [LetterSides](https://docs.dairo.app/api-reference/schemas.md#lettersides) | no |  |
| `addressPlacement` | [LetterAddressPlacement](https://docs.dairo.app/api-reference/schemas.md#letteraddressplacement) | no |  |

## LetterRequirements

The machine-readable letter compliance spec: exact A4 address-window and keep-out coordinates (mm), PDF rules, the verify→price→dryRun→send workflow, and ready-made compliant starter templates (`starterTemplates[].html` can be POSTed to `/v1/letters/templates` verbatim).

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"letter_requirements"` | no |  |
| `version` | string | no |  |
| `printMode` | `"test"`, `"live"` | no | The account's letter print mode, the same as `letters.printMode` on `GET /v1/whoami`. `test` letters are never printed. |
| `printModeNote` | string | no |  |
| `testMode` | object | no | `endState`: what happens to a letter created in test mode (it stays `processing`, is never printed or charged, does not expire; cancel it to close it out). |
| `destinations` | object | no | Accepted recipient countries: `supportedCountries` is `"any"` (every assigned ISO 3166-1 alpha-2 code) or the configured list; `printCentreCountry` is where letters are posted (domestic); `registered` states the domestic-only rule. |
| `summary` | string | no |  |
| `pageSetup` | object | no |  |
| `addressWindow` | object | no |  |
| `keepOuts` | object | no |  |
| `bodyLayout` | object | no |  |
| `pdfRules` | array of string | no |  |
| `workflow` | array of object | no |  |
| `starterTemplates` | array of object | no |  |
| `endpoints` | object | no |  |
| `agentHint` | string | no |  |
| `mcpTools` | map of string | no | Which MCP tool carries each letters action (prepare_letters, list_letters, send_letters). |

## LetterSides

Type: `"simplex"`, `"duplex"`.

## LetterStatus

Type: `"draft"`, `"queued"`, `"processing"`, `"printable"`, `"submitted"`, `"in_transit"`, `"delivered"`, `"undeliverable"`, `"canceled"`, `"failed"`.

## LetterTemplate

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"letter_template"` | yes |  |
| `id` | string | yes |  |
| `name` | string | yes |  |
| `html` | string | no | The branded letter HTML with {{placeholders}} and a blank recipient address window. Omitted from list items. |
| `variables` | array of [LetterTemplateVariable](https://docs.dairo.app/api-reference/schemas.md#lettertemplatevariable) | no | Declared template variables, always normalized to \[{name, type?, example?, label?}\] (legacy string\[\] and {name: type} rows are converted on read). |
| `status` | [LetterTemplateStatus](https://docs.dairo.app/api-reference/schemas.md#lettertemplatestatus) | yes |  |
| `createdAt` | string (date-time) or null | no |  |
| `updatedAt` | string (date-time) or null | no |  |

## LetterTemplateList

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"list"` | yes |  |
| `data` | array of [LetterTemplate](https://docs.dairo.app/api-reference/schemas.md#lettertemplate) | yes |  |
| `pagination` | [Pagination](https://docs.dairo.app/api-reference/schemas.md#pagination) | yes |  |

## LetterTemplatePreview

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"letter_template_preview"` | yes |  |
| `templateId` | string | yes |  |
| `addressPlacement` | [LetterAddressPlacement](https://docs.dairo.app/api-reference/schemas.md#letteraddressplacement) | yes |  |
| `pdfBase64` | string | no | The rendered PDF, base64. Present only for `output: inline`. |
| `pdfUrl` | string (uri) or null | no | `output: url` only: a presigned download URL for the PDF (null when storage is unavailable). |
| `pdfUrlExpiresInSeconds` | integer or null | no | `output: url` only: seconds until `pdfUrl` expires (900). |
| `pageCount` | integer | no | `output: url` only. |
| `pageSize` | object or null | no | `output: url` only: the rendered page size from the layout check. |
| `valid` | boolean or null | no | `output: url` only: the layout-check verdict (null when the check was unavailable). |
| `summary` | any | no | `output: url` only: the layout-check summary (null when unavailable). |
| `failedChecks` | array of string | no | `output: url` only: ids of the layout checks that failed (empty when none failed or the check was unavailable). |
| `checks` | array of object | no | `output: url` only: every layout check as `{id, status, message}`, the same list `POST /v1/letters/verify` returns without the `expected`/`actual` detail. |
| `sizeBytes` | integer | no | `output: url` only: PDF size in bytes. |
| `agentHint` | string | no | `output: url` only, when storage was unavailable. |

## LetterTemplateStatus

Type: `"active"`, `"archived"`.

## LetterTemplateVariable

One declared template placeholder.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes | The placeholder name used as {{name}}. |
| `type` | string | no | Optional value type hint, e.g. string, number, date. |
| `example` | string | no | Optional sample value (used by previews). |
| `label` | string | no | Optional human label. |

## LetterVerification

The structured layout verdict for a letter PDF.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"letter_verification"` | yes |  |
| `valid` | boolean | yes | True when NO check failed. Warnings may still be present. |
| `pageCount` | integer | no |  |
| `pageSize` | object or null | no | Page size in mm. A page within 1 mm of A4 is reported as exactly 210 x 297 with format A4. |
| `measuredPageSize` | object or null | no | The raw measured page size, present when `pageSize` was reported as nominal A4 (a page within 1 mm of 210 x 297 mm, e.g. Chromium's 596 pt width). |
| `addressPlacement` | `"left"`, `"right"`, `null` | no | The window where the recipient address was detected, or null when none was found. |
| `addressLines` | array of string | no | The text lines detected inside the address window, top to bottom. |
| `checks` | array of [LetterVerificationCheck](https://docs.dairo.app/api-reference/schemas.md#letterverificationcheck) | yes |  |
| `summary` | string | no |  |
| `agentHint` | string | no | The recommended next step. |

## LetterVerificationCheck

One layout check verdict. `fail` blocks a compliant send; `warn` is a risk to review; `skipped` means the check was not applicable.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Stable check id, e.g. `page_size`, `address_window`, `address_match`, `franking_zone`, `edge_clearance`, `fonts_embedded`. |
| `status` | `"pass"`, `"warn"`, `"fail"`, `"skipped"` | yes |  |
| `message` | string | yes | Agent-readable finding, with expected geometry in mm where relevant. |
| `expected` | any | no | Machine-usable expected value, when applicable. |
| `actual` | any | no | Machine-usable observed value, when applicable. |

## LetterVerifyRequest

Exactly one document source must be provided: `pdfBase64`, `file`, or `templateId` (with `templateData` + `to` — the render composites the recipient). Passing `to` is strongly recommended: it enables the printed-address match check.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `pdfBase64` | string or null | no | Base64-encoded PDF to verify (max 4 MB for verification). |
| `file` | [LetterFileRef](https://docs.dairo.app/api-reference/schemas.md#letterfileref) or null | no |  |
| `templateId` | string or null | no | Verify a stored template's render. Requires `to`. |
| `templateData` | object or null | no |  |
| `to` | [PostalAddress](https://docs.dairo.app/api-reference/schemas.md#postaladdress) or null | no | The intended recipient — enables the printed-address match check. Required with `templateId`. |
| `from` | [PostalAddress](https://docs.dairo.app/api-reference/schemas.md#postaladdress) or null | no | Optional return address (template path only). |
| `print` | [LetterPrintOptions](https://docs.dairo.app/api-reference/schemas.md#letterprintoptions) | no |  |
| `maxPages` | integer or null | no | Optional page-count ceiling enforced as a fail check. (minimum 1) |
| `delivery` | [LetterDelivery](https://docs.dairo.app/api-reference/schemas.md#letterdelivery) or null | no | Pass the SAME delivery class you will send with. `registered` letters use a DIFFERENT address geometry — the carrier prints its registered-mail label into y=50–67mm, so the recipient block must sit in y=67–90mm — and are verified against it. Registered delivery is domestic only (the recipient must be in the print centre country, DE); with `to` in another country verify adds a failing `registered_domestic` check, and price/create reject it with 422, param `delivery`. |
| `payment` | [LetterPayment](https://docs.dairo.app/api-reference/schemas.md#letterpayment) or null | no | The structured payment object the create will carry. Template letters are rendered WITH the generated slip so the verified PDF is the exact PDF the create would mail. Template-path only (mirrors the create's rule). |

## Message

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `inboxId` | string (uuid) | yes |  |
| `threadId` | string (uuid) or null | yes |  |
| `direction` | `"inbound"`, `"outbound"` | yes |  |
| `status` | string | yes |  |
| `from` | [MessageAddress](https://docs.dairo.app/api-reference/schemas.md#messageaddress) | yes |  |
| `to` | array of string | yes |  |
| `cc` | array of string | yes |  |
| `bcc` | array of string | yes |  |
| `subject` | string | yes |  |
| `textPreview` | string | yes |  |
| `hasHtml` | boolean | yes |  |
| `hasAttachments` | boolean | yes |  |
| `receivedAt` | string (date-time) | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `textBody` | string or null | no |  |
| `htmlBody` | string or null | no |  |
| `attachments` | array of [MessageAttachment](https://docs.dairo.app/api-reference/schemas.md#messageattachment) | no |  |
| `structured` | object or null | no | Schema-Inbox extraction result for this message: the extracted object (validated data, or the raw candidate on passthrough/quarantine). Null when no schema was configured or extraction did not run. |
| `extractionStatus` | `"skipped"`, `"validated"`, `"quarantined"`, `"passthrough"`, `"error"`, `null` | no | Outcome of the Schema-Inbox extraction stage for this message. Null when extraction did not run. |
| `channel` | `"email"`, `"a2a"` | yes | The channel this message transited: "email" (external, over SES) or "a2a" (internal Dairo-to-Dairo, which skips SES). Reserved future values (sms, whatsapp, …) are not yet wired. |
| `channelMetadata` | object or null | no | Channel-specific metadata bag (per-channel extension point). Inbound email: {sesMessageId, spfVerdict, dkimVerdict, dmarcVerdict, …}. A2A: {receiptId, provenance, provenanceVerified}. A folded outbound send: {providerMessageId, provider, lastEventType, lastEventAt, bouncedAt, complainedAt}. |

## MessageActionResponse

The result of a Telegram message action (edit/react/unreact).

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | string | no |  |
| `id` | string | no | The message id acted on. |
| `channel` | string | no |  |
| `edited` | boolean | no |  |
| `editedAt` | string | no |  |
| `reacted` | boolean | no |  |
| `emoji` | string | no |  |
| `big` | boolean | no |  |

## MessageAddress

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `address` | string | yes |  |
| `name` | string or null | no |  |

## MessageAttachment

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `filename` | string or null | no |  |
| `contentType` | string or null | no |  |
| `sizeBytes` | integer or null | no | (minimum 0) |
| `contentId` | string or null | no |  |
| `createdAt` | string (date-time) or null | no |  |
| `messageId` | string (uuid) | no |  |
| `disposition` | `"attachment"`, `"inline"`, `null` | no |  |

## MessageListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `messages` | array of [Message](https://docs.dairo.app/api-reference/schemas.md#message) | yes |  |
| `pagination` | [Pagination](https://docs.dairo.app/api-reference/schemas.md#pagination) | yes |  |

## MessageResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `message` | [Message](https://docs.dairo.app/api-reference/schemas.md#message) | yes |  |

## MessageTemplateRef

Send by referencing a stored template instead of an inline body. Mutually exclusive with text, html, and react: combining a template with any inline body is a 400. The referenced version is resolved and pinned at request time; supplied variables are validated against that version's variable schema (a bad variable is a synchronous 400). When the version declares a subject template it is used unless an explicit subject is provided on the request.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Template UUID or slug, owned by the authenticated account. |
| `version` | integer (int64) | no | Explicit version to pin. Omit to resolve and pin the template's current default version at request time. An archived template can only be sent with an explicit version. (minimum 1) |
| `variables` | object | no | Values for the template's declared variables. Validated against the pinned version's variable schema (type, enum, maxLength, url/email format, required, defaults). Serialized size limit: 32 KiB. |

## NotificationPreference

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `category` | `"account"`, `"billing"`, `"usage"`, `"security"`, `"product"` | yes | The notification category this preference applies to. |
| `emailEnabled` | boolean | yes | Whether the account receives email notifications in this category. Defaults to true when no preference has been set. |

## NotificationPreferencesResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"notification_preferences"` | yes |  |
| `preferences` | array of [NotificationPreference](https://docs.dairo.app/api-reference/schemas.md#notificationpreference) | yes | One entry per notification category, with its resolved emailEnabled flag. |

## NotificationPreferencesUpdateRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `preferences` | object | yes | A partial map of category -> emailEnabled. At least one known category is required; unknown category keys are rejected. |

## Organization

An organization (project) the account belongs to, with the caller's role.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string or null | yes |  |
| `name` | string | no |  |
| `slug` | string | no |  |
| `vanitySlug` | string or null | no |  |
| `role` | `"owner"`, `"admin"`, `"member"`, `null` | yes |  |

## OutboundEvent

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `eventId` | string | yes |  |
| `messageId` | string or null | no |  |
| `providerMessageId` | string or null | no |  |
| `type` | string | yes | SES event type, e.g. Delivery, Bounce, Complaint. |
| `recipient` | string or null | no |  |
| `occurredAt` | string or null | no |  |
| `subject` | string or null | no |  |
| `from` | string or null | no |  |
| `to` | array of string or null | no |  |
| `status` | string or null | no |  |
| `bounceType` | string or null | no |  |
| `bounceSubType` | string or null | no |  |
| `bounceAction` | string or null | no |  |
| `bounceStatus` | string or null | no |  |
| `diagnosticCode` | string or null | no |  |
| `complaintFeedbackType` | string or null | no |  |
| `complaintUserAgent` | string or null | no |  |
| `complaintSubType` | string or null | no |  |
| `agentHint` | string or null | no |  |

## OutboundEventListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `events` | array of [OutboundEvent](https://docs.dairo.app/api-reference/schemas.md#outboundevent) | yes |  |

## OutboundMessage

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |
| `from` | string or null | no |  |
| `to` | array of string or null | no |  |
| `subject` | string or null | no |  |
| `status` | string or null | no |  |
| `sentAt` | string or null | no |  |
| `error` | string or null | no |  |
| `providerMessageId` | string or null | no |  |
| `lastEventType` | string or null | no |  |
| `lastEventAt` | string or null | no |  |
| `bouncedAt` | string or null | no |  |
| `complainedAt` | string or null | no |  |
| `createdAt` | string or null | no |  |
| `scheduledAt` | string (date-time) or null | no | When the email is scheduled to send, for status scheduled. Null for immediate sends. |
| `canceledAt` | string (date-time) or null | no | When a scheduled email was canceled, for status canceled. Null otherwise. |

## Pagination

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `nextCursor` | string or null | yes |  |

## PatchInboxRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `agent` | string | no | The inbox agent's display name. |
| `mode` | `"receive_send"`, `"send_only"`, `"receive_only"` | no |  |

## PatchWebhookRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string (uri) | no |  |
| `events` | array of `"message.received"`, `"message.quarantined"`, `"message.sent"`, `"message.delivered"`, `"message.bounced"`, `"message.complained"`, `"message.delayed"`, `"budget.tripped"`, `"compliance.erasure.completed"`, `"subprocessor.changed"`, `"object.scanned"`, `"share_link.opened"`, `"letter.status_changed"`, `"call.status_changed"`, `"domain.verified"` | no |  |

## PhoneCall

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"phone_call"` | yes |  |
| `id` | string (uuid) | yes |  |
| `direction` | `"outbound"`, `"inbound"` | yes | `outbound` for calls placed with POST /v1/phone/calls, `inbound` for calls answered by a number's inbound agent. |
| `status` | `"queued"`, `"initiating"`, `"ringing"`, `"in_progress"`, `"completed"`, `"failed"`, `"no_answer"`, `"busy"`, `"canceled"` | yes | Terminal: completed, failed, no_answer, busy, canceled. |
| `fromNumber` | string | yes |  |
| `toNumber` | string | yes |  |
| `instructions` | string or null | no |  |
| `greeting` | string or null | no |  |
| `voice` | string or null | no | The Dairo Live voice id. |
| `model` | string or null | no | Always `dairo-live`. |
| `language` | string or null | no |  |
| `maxDurationSeconds` | integer or null | no |  |
| `voicemailMessage` | string or null | no |  |
| `queuedAt` | string (date-time) or null | no |  |
| `startedAt` | string (date-time) or null | no |  |
| `answeredAt` | string (date-time) or null | no |  |
| `endedAt` | string (date-time) or null | no |  |
| `durationSeconds` | integer or null | no |  |
| `hangupCause` | string or null | no |  |
| `answeredBy` | string or null | no | `human` or `machine`, from answering-machine detection. |
| `summary` | string or null | no |  |
| `notes` | array of [PhoneCallNote](https://docs.dairo.app/api-reference/schemas.md#phonecallnote) or null | no | Inbound calls: the caller's structured notes (tasks, notes, reminders, messages). |
| `costUsd` | number or null | no | Billed per started minute: the $0.09/min Dairo Live rate plus the destination's telephony rate. Calls that never connect are free. |
| `error` | string or null | no |  |
| `metadata` | object | no |  |
| `idempotencyKey` | string or null | no |  |
| `createdAt` | string (date-time) | yes |  |

## PhoneCallCreateRequest

Calls are always recorded. To follow a call's progress subscribe a webhook to the `call.status_changed` event. The agent hangs up by itself when the person says goodbye.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `to` | string | yes | Destination, E.164. A real phone rings here. |
| `from` | string | yes | Caller id, E.164. MUST be a number this account owns with status `active`. |
| `instructions` | string | yes | What the agent should accomplish. `{{name}}` placeholders are filled from `variables` when the call is created; unknown placeholders stay as written. (max length 8000) |
| `greeting` | string | no | Opening line spoken when the callee answers. Supports `{{name}}` placeholders filled from `variables`. |
| `voice` | `"marin"`, `"cedar"`, `"alloy"`, `"ash"`, `"ballad"`, `"coral"`, `"echo"`, `"sage"`, `"shimmer"`, `"verse"`, `"quartz"`, `"ripple"`, `"vesper"`, `"willow"`, `"stone"`, `"gleam"`, `"meridian"`, `"bossa"`, `"tempo"`, `"beacon"`, `"delta"`, `"cinder"` | no | A Dairo Live voice id. `marin` (default), `cedar`, `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer` and `verse` are multilingual; `quartz`, `ripple` (Australian English), `vesper` (British English), `willow`, `stone` (Irish English), `gleam`, `meridian` (North American English), `bossa`, `tempo` (Brazilian Portuguese), `beacon` (Filipino English), `delta`, `cinder` (Southern U.S. English). Any other value is a 400. Browse them with GET /v1/phone/voices. (default `"marin"`) |
| `model` | `"dairo-live"` | no | The call model. Phone calls run on Dairo Live; `dairo-live` is the only accepted value (any other value is a 400). (default `"dairo-live"`) |
| `language` | string | no | ISO language code the agent speaks (e.g. `en`, `de`). |
| `maxDurationSeconds` | integer | no | Hard cap on call length in seconds (30-780). (minimum 30, maximum 780, default `600`) |
| `variables` | object | no | Values for `{{name}}` placeholders in `instructions` and `greeting`, filled in at create time. Placeholders without a value stay as written. |
| `voicemailMessage` | string | no | Message the agent leaves when the call reaches a voicemail. Without it the agent hangs up silently on a voicemail. (max length 500) |
| `metadata` | object | no |  |
| `idempotencyKey` | string | no | A retried request returns the original call rather than dialing the human twice. |

## PhoneCallListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"list"` | yes |  |
| `data` | array of [PhoneCall](https://docs.dairo.app/api-reference/schemas.md#phonecall) | yes |  |

## PhoneCallNote

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `kind` | `"task"`, `"note"`, `"reminder"`, `"message"` | yes |  |
| `text` | string | yes |  |
| `due` | string or null | yes | When it is due, as the caller said it (e.g. "tomorrow"), or null. |

## PhoneCallRecording

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"phone_call.recording"` | yes |  |
| `callId` | string (uuid) | yes |  |
| `objectId` | string (uuid) | yes | A Dairo storage object; fetch it through the storage API. |

## PhoneCallTranscript

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"phone_call.transcript"` | yes |  |
| `callId` | string (uuid) | yes |  |
| `turns` | array of [PhoneCallTranscriptTurn](https://docs.dairo.app/api-reference/schemas.md#phonecalltranscriptturn) | yes |  |

## PhoneCallTranscriptTurn

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `role` | `"agent"`, `"caller"`, `"tool"` | yes |  |
| `content` | string | yes |  |
| `timestamp` | string (date-time) or null | no |  |

## PhoneNumber

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"phone_number"` | yes |  |
| `id` | string (uuid) | yes |  |
| `phoneNumber` | string | yes | E.164. |
| `countryCode` | string | yes |  |
| `numberType` | `"local"`, `"toll_free"`, `"national"`, `"mobile"` | yes |  |
| `capabilities` | array of string | no |  |
| `status` | `"pending"`, `"active"`, `"releasing"`, `"released"`, `"failed"` | yes | `pending` means the provider reserved the number but regulatory documents are still outstanding (common for German local numbers). It is not an error. |
| `inboxId` | string (uuid) or null | no | When set, every inbound call to this number (summary, the caller's notes and the transcript) lands in that inbox as a `channel: "voice"` message. |
| `agentId` | string (uuid) or null | no |  |
| `inboundInstructions` | string or null | no | Who the inbound agent is and what the line is for. null = the default assistant that takes messages and notes. (max length 8000) |
| `inboundGreeting` | string or null | no | Opening line for inbound calls. null = a short default greeting (by first name for a known contact). (max length 500) |
| `inboundVoice` | `"marin"`, `"cedar"`, `"alloy"`, `"ash"`, `"ballad"`, `"coral"`, `"echo"`, `"sage"`, `"shimmer"`, `"verse"`, `"quartz"`, `"ripple"`, `"vesper"`, `"willow"`, `"stone"`, `"gleam"`, `"meridian"`, `"bossa"`, `"tempo"`, `"beacon"`, `"delta"`, `"cinder"`, `null` | no | Dairo Live voice id for the inbound agent. |
| `inboundLanguage` | string or null | no | ISO language code the inbound agent speaks. |
| `monthlyCostUsd` | number or null | no |  |
| `setupCostUsd` | number or null | no |  |
| `requirements` | object | no | Outstanding regulatory requirements, when `status` is `pending`. |
| `metadata` | object | no |  |
| `purchasedAt` | string (date-time) or null | no |  |
| `releasedAt` | string (date-time) or null | no |  |
| `createdAt` | string (date-time) | yes |  |

## PhoneNumberBuyRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `phoneNumber` | string | yes | E.164, exactly as returned by the availability search. |

## PhoneNumberListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"list"` | yes |  |
| `data` | array of [PhoneNumber](https://docs.dairo.app/api-reference/schemas.md#phonenumber) | yes |  |

## PhoneNumberReleaseRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `confirm` | `true` | yes | Releasing is irreversible: the number returns to the public pool. |

## PhoneNumberUpdateRequest

At least one property is required. An explicit null unbinds (inboxId, agentId) or resets an inbound\* field to its default.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `inboxId` | string (uuid) or null | no |  |
| `agentId` | string (uuid) or null | no |  |
| `inboundInstructions` | string or null | no | Who the inbound agent is and what the line is for. null = the default assistant that takes messages and notes. (max length 8000) |
| `inboundGreeting` | string or null | no | Opening line for inbound calls. null = a short default greeting (by first name for a known contact). (max length 500) |
| `inboundVoice` | `"marin"`, `"cedar"`, `"alloy"`, `"ash"`, `"ballad"`, `"coral"`, `"echo"`, `"sage"`, `"shimmer"`, `"verse"`, `"quartz"`, `"ripple"`, `"vesper"`, `"willow"`, `"stone"`, `"gleam"`, `"meridian"`, `"bossa"`, `"tempo"`, `"beacon"`, `"delta"`, `"cinder"`, `null` | no | Dairo Live voice id for the inbound agent. |
| `inboundLanguage` | string or null | no | ISO language code the inbound agent speaks. |
| `metadata` | object | no |  |

## PhoneVoice

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `voiceId` | `"marin"`, `"cedar"`, `"alloy"`, `"ash"`, `"ballad"`, `"coral"`, `"echo"`, `"sage"`, `"shimmer"`, `"verse"`, `"quartz"`, `"ripple"`, `"vesper"`, `"willow"`, `"stone"`, `"gleam"`, `"meridian"`, `"bossa"`, `"tempo"`, `"beacon"`, `"delta"`, `"cinder"` | yes | Pass as `voice` on POST /v1/phone/calls or `inboundVoice` on a number. |
| `name` | string | yes |  |
| `description` | string or null | no |  |
| `language` | `"multilingual"`, `"en"`, `"pt"` | yes |  |
| `gender` | string or null | no |  |
| `accent` | string or null | no |  |

## PostalAddress

A postal address (letter `to` / `from`). Requires `country` plus `street` (with `houseNumber`) or `poBox`; a violation is a 422 whose `param` names the path, e.g. `from.street`.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string or null | no |  |
| `company` | string or null | no |  |
| `street` | string or null | no | Street name. Required unless `poBox` is given. |
| `houseNumber` | string or null | no | House number; give it with `street`. |
| `poBox` | string or null | no | PO box. Required unless `street` is given. |
| `addressLine2` | string or null | no |  |
| `postalCode` | string or null | no |  |
| `city` | string or null | no |  |
| `country` | string | yes | ISO 3166-1 alpha-2 country code. |

## PresignedDownload

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `downloadUrl` | string (uri) | yes | Presigned S3 GET URL. |
| `expiresInSeconds` | integer | yes | (minimum 1, maximum 604800) |

## PresignedUpload

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `objectId` | string (uuid) | yes |  |
| `uploadUrl` | string (uri) | yes | Presigned S3 PUT URL. |
| `method` | `"PUT"` | yes |  |
| `headers` | map of string | yes | Headers that MUST be sent with the PUT, exactly as given: the stored Content-Type, the declared type as x-amz-meta-content-type and the filename as x-amz-meta-filename (finalize records both). |
| `expiresInSeconds` | integer | yes | (minimum 1, maximum 604800) |

## PreviewLetterTemplateRequest

All fields optional; the proof renders with a sample recipient and, by default, left address placement.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `templateData` | object | no | Values that fill the template's {{placeholders}} for the preview. |
| `addressPlacement` | [LetterAddressPlacement](https://docs.dairo.app/api-reference/schemas.md#letteraddressplacement) | no |  |
| `output` | `"inline"`, `"url"` | no | `inline` (default): the PDF as `pdfBase64`. `url`: the PDF is stored and a 15-minute `pdfUrl` is returned with `pageCount`, `pageSize`, `valid`, `summary`, `failedChecks` and `checks` from the layout check, and no base64. (default `"inline"`) |

## ReactRenderInput

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `source` | string | yes | React Email TSX/JSX component source. Rendered asynchronously by Dairo in an isolated renderer with allowlisted React Email imports. (max length 65536) |
| `props` | object | no | JSON props passed to the default exported email component. Serialized size limit: 32 KiB. |

## ReactTelegramMessageRequest

Set the bot's emoji reaction on a Telegram message.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `emoji` | string | yes | A single Telegram-allowed reaction emoji (e.g. 👍 👎 ❤ 🔥). |
| `big` | boolean | no | Play the big-reaction animation. (default `false`) |

## Recipients

Type: string (email) or array of string (email).

## RegisterVerificationWaitRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `timeoutSec` | integer | yes | How long the wait stays open, 30..=1800 seconds. expiresAt = now + timeoutSec. (minimum 30, maximum 1800) |
| `fromHint` | string or null | no | Optional sender to accept, case-insensitive: a full address (matched exactly) or a domain (matches that domain and its subdomains, e.g. 'github.com' matches '`noreply@mail.github.com`'). Never a substring match. Anything that is not an address or a domain is a 400. A hinted wait resolves only from an authenticated sender: an email whose From domain passed DMARC, or an A2A message from a Dairo inbox on a verified domain or a Dairo address. Null/absent accepts any sender except an email that failed its From domain's DMARC policy. (max length 255) |
| `instruction` | string or null | no | Natural-language instruction for the AI matcher: which email to wait for and what to extract (e.g. 'wait for the Telnyx password-reset email and return the reset link'). An EU-resident model evaluates each inbound email against it and, on a match, resolves the wait with the extracted value in `result`. Absent defaults to extracting any verification/OTP/confirmation code. (max length 2000) |
| `idempotencyKey` | string or null | no | Optional idempotency key; a retried register with the same key returns the same wait. (max length 255) |

## ResidencyResponse

Machine-checkable residency posture + subprocessor list.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | string | yes |  |
| `primaryRegion` | string | yes |  |
| `dataResidency` | string | yes |  |
| `noUsEgress` | boolean | yes |  |
| `controllerParent` | string | no |  |
| `cloudActExposed` | boolean | no |  |
| `enforcement` | object | no |  |
| `subprocessors` | array of [Subprocessor](https://docs.dairo.app/api-reference/schemas.md#subprocessor) | yes |  |
| `subprocessorListVersion` | integer | yes |  |
| `subprocessorListUpdatedAt` | string (date-time) or null | no |  |
| `checkedAt` | string (date-time) | yes |  |

## SendMessageAttachment

An outbound attachment. Provide EITHER inline bytes (contentBase64) OR a storage reference (objectId) — exactly one, never both. Inline attachments require filename and are capped at 8 MiB decoded (API Gateway JSON/base64 envelope). A storage-reference attachment names a Dairo storage object you own (the objectId returned by a bucket upload's finalize, or any owned storage object); Dairo fetches its bytes server-side and native-attaches up to 24 MiB, deriving filename and contentType from the object unless you override them here. Over 24 MiB the send fails with a 413 (create a share link and place it in the body yourself; Dairo never auto-edits the body).

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `filename` | string | no | Required for an inline (contentBase64) attachment. Optional for a storage-reference (objectId) attachment, where it overrides the object's stored filename; when omitted the object's filename is used. (min length 1, max length 120) |
| `contentType` | string | no | Optional. For a storage-reference (objectId) attachment, overrides the object's stored content type; when omitted the object's content type is used. (default `"application/octet-stream"`) |
| `contentBase64` | string | no | Base64-encoded inline attachment bytes. Mutually exclusive with objectId. Dairo inline attachment delivery is capped at 8 MiB decoded total to stay below API Gateway JSON/base64 envelope limits and SES v2 40 MB message limits. For larger files, upload to a Dairo bucket and reference it via objectId (native-attached up to 24 MiB). |
| `objectId` | string | no | Storage-reference attachment: the id of a Dairo storage object you own. Mutually exclusive with contentBase64. Dairo resolves the object with an ownership check, fetches its bytes server-side, and native-attaches up to 24 MiB; over that the send fails with a 413 (no share link is auto-added). filename and contentType are derived from the object unless overridden here. |
| `delivery` | `"attachment"`, `"link"`, `"auto"` | no | Explicit delivery mode. 'attachment' sends inline as a normal email attachment. 'auto' sends inline only when safely below inline limits; otherwise callers should use an explicit share-link flow. 'link' never mutates the email body automatically and this send endpoint does not create standalone file links; create/get a Dairo share link first and place it in text/html yourself. (default `"attachment"`) |

## SendMessageRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `inboxId` | string | yes |  |
| `to` | [Recipients](https://docs.dairo.app/api-reference/schemas.md#recipients) | yes |  |
| `cc` | [Recipients](https://docs.dairo.app/api-reference/schemas.md#recipients) | no |  |
| `bcc` | [Recipients](https://docs.dairo.app/api-reference/schemas.md#recipients) | no |  |
| `subject` | string | no | (default `""`) |
| `text` | string | no |  |
| `html` | string | no |  |
| `idempotencyKey` | string | no | Body-level idempotency key kept for current API compatibility. Prefer Idempotency-Key header for new clients. (max length 128) |
| `attachments` | array of [SendMessageAttachment](https://docs.dairo.app/api-reference/schemas.md#sendmessageattachment) | no | Outbound send attachments. 'attachment' and safe 'auto' sends are inline; current inline API limit is 10 files, 8 MiB per file, 8 MiB total decoded bytes. 'link' is explicit and must be backed by a pre-created Dairo share link placed deliberately in text/html by the caller. (max items 10) |
| `react` | [ReactRenderInput](https://docs.dairo.app/api-reference/schemas.md#reactrenderinput) | no |  |
| `template` | [MessageTemplateRef](https://docs.dairo.app/api-reference/schemas.md#messagetemplateref) | no |  |
| `sendAt` | string (date-time) or null | no | RFC 3339 timestamp WITH an explicit timezone offset to schedule the send for a future time (max 30 days ahead). Omit to send immediately. A scheduled email starts with status scheduled and can be stopped via POST /v1/messages/{messageId}/cancel before it fires. |
| `channel` | `"email"`, `"a2a"` | no | Optional explicit delivery channel. "email" forces SES even when every recipient is a Dairo inbox; "a2a" requires every recipient to be a Dairo inbox (400 otherwise) and cannot combine with sendAt. Absent = auto (a2a when all recipients are internal, else email). Unknown values are a 400. |
| `replyTo` | string (email) | no | Optional single Reply-To address, set verbatim as the MIME Reply-To header. |
| `inReplyTo` | string | no | Optional RFC 5322 Message-ID this message replies to, with or without angle brackets (normalized to `<id@host>`). Stamped as the In-Reply-To header and used to place the sent copy in the parent's thread. Takes precedence over a custom In-Reply-To in headers. (max length 250) |
| `references` | array of string | no | Optional References chain of RFC 5322 Message-IDs (normalized like inReplyTo, deduplicated). Defaults to \[inReplyTo\] when omitted and inReplyTo is set. Takes precedence over a custom References in headers. (max items 32) |
| `replyToMessageId` | string (uuid) | no | Optional Dairo message id (from the messages API) to reply to. Resolved server-side into inReplyTo, references (the parent's References plus its Message-ID) and the parent thread. 404 when the message is not visible to the caller; 400 when it disagrees with an explicit inReplyTo. |
| `headers` | map of string | no | Optional custom MIME headers ({name: value}). Spoofable/provenance headers are dropped server-side; malformed names or CR/LF are rejected. |
| `tags` | map of string | no | Optional SES message tags ({name: value}), validated against SES tag constraints. |
| `ignoreComplaints` | boolean | no | Override complaint suppression for recipients that previously complained. (default `false`) |
| `contactId` | string | no | Optional contact reference: the contact's PRIMARY handle for the sending inbox's channel is added to `to`, so a send needs no raw address. Accepts a bare contact id, or a `contact:<id>` / `@alias` / `@me` ref. Recipients in to/cc/bcc may ALSO be contact references (`@alias`, `contact:<id>`, `@me`); raw addresses pass through unchanged. |
| `buttons` | array of array of [TelegramButton](https://docs.dairo.app/api-reference/schemas.md#telegrambutton) | no | Telegram inline keyboard (Telegram-channel inbox only): rows of {text, url\|callback} buttons. A URL button opens a link; a callback button surfaces the tap back to you as an inbound button_tap event and shows a feedback toast on tap (default: ✓ + the button label; set alert:true for a modal). |
| `dryRun` | boolean | no | Per-request dry run, at parity with the letters API's dryRun. When true the request is validated and the sending inbox resolved exactly as a real send would be, then a non-persisted MessagePreview (status "preview") is returned: nothing is created, queued, handed to the provider, metered, or billed. Absent or false is a normal send. Test mode is scoped to the REQUEST, not to the API key — an API key's `environment` label has no effect on delivery. (default `false`) |

## SendMessageResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `status` | `"sent"`, `"scheduled"`, `"failed"`, `"queued"` | yes | A single immediate send (POST /v1/messages without sendAt) is performed synchronously and returns 'sent' (with providerMessageId) or 'failed' — it is never accepted as 'queued' only to fail later on something knowable at submit time (oversized attachment, unverified sender, etc.). A scheduled send (sendAt in the future) returns 'scheduled'. 'queued' is returned ONLY for a bulk list broadcast (POST /v1/audiences/{id}/send), whose batches are delivered asynchronously by the worker fleet. |
| `providerMessageId` | string or null | no |  |
| `error` | string or null | no | Public-safe failure code/message. Provider/internal text is stored internally and never returned to API clients. |
| `warnings` | array of [SendMessageWarning](https://docs.dairo.app/api-reference/schemas.md#sendmessagewarning) | yes | Warning-only delivery guidance. Complaint suppressions do not block sending; agents should surface these warnings before further contact. |
| `channel` | `"email"`, `"a2a"` | no | The channel this send transited: "email" (SES) or "a2a" (internal Dairo-to-Dairo, delivered as a receipt, providerMessageId 'a2a\_…'). |
| `channelMetadata` | object or null | no | Channel-specific delivery metadata. For a2a: {receiptId, provenance, provenanceVerified}. For email: provider send metadata. |
| `scheduledAt` | string (date-time) or null | no | When a scheduled send will fire (RFC3339); present only when status is "scheduled". |

## SendMessageWarning

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `recipient` | string (email) | yes |  |
| `reason` | `"complaint"` | yes |  |
| `message` | string | yes |  |
| `sourceMessageId` | string (uuid) or null | no | Dairo emailId that originally produced the complaint warning, when known. |
| `providerMessageId` | string or null | no | Provider correlation ID for the complaint event, when known. |
| `complaintFeedbackType` | string or null | no |  |
| `complaintUserAgent` | string or null | no |  |
| `lastEventAt` | string (date-time) or null | no |  |

## SetInboxSchemaRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `schema` | [InboxSchemaContract](https://docs.dairo.app/api-reference/schemas.md#inboxschemacontract) | no |  |
| `onValidationError` | `"quarantine"`, `"passthrough"` | no | Behaviour when an extracted object fails validation. 'quarantine' (default) holds the message out of message.received and emits message.quarantined; 'passthrough' delivers it with the error attached. (default `"quarantine"`) |
| `extractionHint` | string or null | no | Optional free-text hint passed to the extractor as prompt context. Never executed. (max length 1000) |

## ShareLink

A secure (stateful) share link over a stored object.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | string | no |  |
| `id` | string | no | Share-link id (shr\_\*). |
| `objectId` | string (uuid) | no |  |
| `shareUrl` | string | no | Branded /s/ share page URL (returned only at create time). |
| `downloadUrl` | string | no | Branded /d/ download URL (returned only at create time). |
| `hasPassword` | boolean | no |  |
| `maxUses` | integer or null | no |  |
| `oneTime` | boolean | no |  |
| `usesCount` | integer | no |  |
| `expiresAt` | string (date-time) or null | no |  |
| `revoked` | boolean | no |  |
| `createdAt` | string (date-time) | no |  |

## ShareLinkListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | string | no |  |
| `data` | array of [ShareLink](https://docs.dairo.app/api-reference/schemas.md#sharelink) | no |  |

## ShareLinkOpensResponse

Open analytics for one secure share link.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | string | no |  |
| `shareLinkId` | string | no |  |
| `objectId` | string (uuid) | no |  |
| `summary` | object | no |  |
| `data` | array of object | no |  |

## SignedArtifact

A JSON object signed with the org Ed25519 key. The signature commits to the canonical form of every field except alg/kid/signature; a verifier reconstructs those bytes and checks the signature with the public key from JWKS.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `alg` | string or null | no | EdDSA, or null when no signing key is configured. |
| `kid` | string or null | no | Key id whose public half is published in JWKS. |
| `signature` | string or null | no | base64url (no-pad) Ed25519 signature, or null when unsigned. |

## SlackApp

A tenant Slack app (managed or BYO). Secret-free - the sealed OAuth client/signing secrets are never returned.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Dairo record id. |
| `appId` | string | yes | The Slack app id (e.g. A012ABCD0A0). |
| `clientId` | string | yes | The OAuth client id (public; it appears in the install URL). |
| `name` | string or null | no | Display name the app was created with (null for a BYO import). |
| `origin` | `"managed"`, `"byo"` | yes | 'managed' (Dairo minted it via the App Manifest APIs) or 'byo' (imported credentials). |
| `contextMode` | `"mentions"`, `"ambient"` | yes | 'mentions' (@mentions + DMs only) or 'ambient' (also channel message events). |
| `ambientMode` | `"off"`, `"store"` | no | 'off' (drop ambient chatter) or 'store' (persist it silently, no message.received). Present on list reads; absent on the create response. |
| `status` | string | no | 'active' \| 'deleted'. Present on list reads; absent on the create response. |
| `createdAt` | string | no | ISO 8601 creation timestamp. Present on list reads; absent on the create response. |

## SlackAppAmbientResponse

A Slack app's ambient state after a patch.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `appId` | string | yes |  |
| `contextMode` | `"mentions"`, `"ambient"` | yes |  |
| `ambientMode` | `"off"`, `"store"` | yes |  |

## SlackAppCreateRequest

Create a managed or BYO Slack app. Provide origin='managed' with a configToken, or origin='byo' with the four credential fields.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `origin` | `"managed"`, `"byo"` | yes |  |
| `contextMode` | `"mentions"`, `"ambient"` | no | Default 'mentions'. 'ambient' also subscribes the app to channel message events. |
| `name` | string | no | managed: display name for the app/bot (optional). |
| `configToken` | string | no | managed only: your Slack app-configuration access token (xoxe...). Used once to mint the app, then dropped - never stored. |
| `appId` | string | no | byo only: the existing Slack app id. |
| `clientId` | string | no | byo only: the OAuth client id. |
| `clientSecret` | string | no | byo only: the OAuth client secret (sealed at rest). |
| `signingSecret` | string | no | byo only: the signing secret (sealed at rest). |

## SlackAppDeleteResponse

Confirmation of a soft-deleted Slack app.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `appId` | string | yes |  |
| `status` | string | yes | 'deleted'. |

## SlackAppListResponse

The account's tenant Slack apps.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `apps` | array of [SlackApp](https://docs.dairo.app/api-reference/schemas.md#slackapp) | yes |  |

## SlackAppPatchRequest

The only mutable field on a Slack app.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ambientMode` | `"off"`, `"store"` | yes | 'store' requires a context_mode='ambient' app. |

## SlackManifestTemplateResponse

A pasteable Slack app manifest for the self-setup flow.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `manifest` | object | yes | The Slack app manifest JSON to paste into Slack's 'Create app from manifest' flow. |

## SlackOauthStartResponse

The signed Slack OAuth install URL to present as an "Add to Slack" button.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `url` | string | yes | [https://slack.com/oauth/v2/authorize](https://slack.com/oauth/v2/authorize) URL carrying the app's bot scopes and a signed, single-use state (10-minute expiry). |

## Subprocessor

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes |  |
| `role` | string | yes |  |
| `region` | string | yes |  |
| `country` | string or null | no |  |
| `url` | string or null | no |  |
| `status` | `"active"`, `"removed"` | yes |  |

## TelegramButton

A Telegram inline-keyboard button: a text label plus exactly one of url (a http/https/tg link) or callback (an opaque action string surfaced back to you as an inbound button_tap event). A callback button shows a feedback toast on tap (default: ✓ + the button label); set alert:true for a modal. feedback/alert are ignored on a url button.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `text` | string | no | Button label (1-64 chars). |
| `url` | string | no | A `http://`, `https://`, or `tg://` link opened on tap. Mutually exclusive with callback. |
| `callback` | string | no | An opaque action string. On tap it is surfaced back to you as an inbound button_tap message + a message.button.tapped event. Mutually exclusive with url. |
| `feedback` | string | no | Callback-button only: the toast text shown to the tapping user (answerCallbackQuery text, max 200 chars). Defaults to '✓ `<button label>`' when omitted, so every tap gives visible feedback. Ignored on a url button. |
| `alert` | boolean | no | Callback-button only: when true, the feedback is shown as a modal dialog (show_alert) instead of a transient toast. Ignored on a url button. |

## TelegramVoice

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `slug` | string | yes | Stable handle; use as telegram.voice.voiceId. |
| `name` | string | yes |  |
| `description` | string or null | no |  |
| `bestLanguage` | string or null | no |  |
| `languages` | array of string | no |  |
| `gender` | string or null | no |  |
| `tags` | array of string | no |  |
| `featured` | boolean | yes |  |
| `popularity` | integer | no |  |

## Template

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `slug` | string | yes |  |
| `name` | string | yes |  |
| `description` | string or null | no |  |
| `currentVersion` | integer or null | no |  |
| `status` | string | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `updatedAt` | string (date-time) | yes |  |

## TemplateCreateRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `slug` | string | yes |  |
| `name` | string | yes | (min length 1) |
| `description` | string | no |  |
| `source` | string | yes | React Email TSX source for version 1. |
| `subject` | string | no |  |
| `variables` | [TemplateVariableSchema](https://docs.dairo.app/api-reference/schemas.md#templatevariableschema) | no |  |
| `notes` | string | no |  |

## TemplateDeleteResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `deleted` | boolean | yes |  |
| `id` | string (uuid) | yes |  |

## TemplateDetailResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `template` | [Template](https://docs.dairo.app/api-reference/schemas.md#template) | yes |  |
| `version` | [TemplateVersionDetail](https://docs.dairo.app/api-reference/schemas.md#templateversiondetail) | yes |  |

## TemplateListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `templates` | array of [Template](https://docs.dairo.app/api-reference/schemas.md#template) | yes |  |

## TemplatePatchRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no | (min length 1) |
| `description` | string or null | no |  |
| `currentVersion` | integer | no | (minimum 1) |

## TemplateResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `template` | [Template](https://docs.dairo.app/api-reference/schemas.md#template) | yes |  |

## TemplateVariableSchema

JSON-Schema-lite variable contract: a flat map of variable name to declaration.

Type: map of object.

## TemplateVersion

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `version` | integer | yes |  |
| `subject` | string or null | no |  |
| `variables` | object | yes |  |
| `notes` | string or null | no |  |
| `createdAt` | string (date-time) | yes |  |

## TemplateVersionDetail

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `version` | integer | yes |  |
| `source` | string | yes |  |
| `subject` | string or null | no |  |
| `variables` | object | yes |  |
| `notes` | string or null | no |  |
| `createdAt` | string (date-time) | yes |  |

## TemplateVersionDetailResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `templateId` | string (uuid) | yes |  |
| `version` | [TemplateVersionDetail](https://docs.dairo.app/api-reference/schemas.md#templateversiondetail) | yes |  |

## TemplateVersionListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `templateId` | string (uuid) | yes |  |
| `versions` | array of [TemplateVersion](https://docs.dairo.app/api-reference/schemas.md#templateversion) | yes |  |

## TemplateVersionRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `source` | string | yes | React Email TSX source for the new version. |
| `subject` | string | no |  |
| `variables` | [TemplateVariableSchema](https://docs.dairo.app/api-reference/schemas.md#templatevariableschema) | no |  |
| `promote` | boolean | no | Advance currentVersion to this version. Defaults to true. |
| `notes` | string | no |  |

## TemplateVersionResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `template` | [Template](https://docs.dairo.app/api-reference/schemas.md#template) | yes |  |
| `version` | [TemplateVersion](https://docs.dairo.app/api-reference/schemas.md#templateversion) | yes |  |
| `warnings` | array of string | yes |  |

## Thread

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | yes |  |
| `inboxId` | string (uuid) | yes |  |
| `subject` | string | yes |  |
| `messageCount` | integer | yes | (minimum 0) |
| `lastMessageAt` | string (date-time) or null | yes |  |
| `updatedAt` | string (date-time) | yes |  |
| `status` | string | yes |  |
| `lastMessagePreview` | string or null | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `channel` | `"email"`, `"a2a"` | yes | The channel this message transited: "email" (external, over SES) or "a2a" (internal Dairo-to-Dairo, which skips SES). Reserved future values (sms, whatsapp, …) are not yet wired. |

## ThreadListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `threads` | array of [Thread](https://docs.dairo.app/api-reference/schemas.md#thread) | yes |  |
| `pagination` | [Pagination](https://docs.dairo.app/api-reference/schemas.md#pagination) | yes |  |

## ThreadResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"thread"` | no |  |
| `id` | string (uuid) | yes |  |
| `inboxId` | string (uuid) | yes |  |
| `subject` | string | yes |  |
| `messageCount` | integer | yes | (minimum 0) |
| `lastMessageAt` | string (date-time) or null | yes |  |
| `updatedAt` | string (date-time) | yes |  |
| `status` | string | yes |  |
| `lastMessagePreview` | string or null | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `channel` | `"email"`, `"a2a"` | yes | The channel this message transited: "email" (external, over SES) or "a2a" (internal Dairo-to-Dairo, which skips SES). Reserved future values (sms, whatsapp, …) are not yet wired. |
| `messages` | array of [Message](https://docs.dairo.app/api-reference/schemas.md#message) | yes |  |

## UpdateApiKeyRequest

At least one of `name`, `allowedIps` or `inboxIds` is required. `scopes` is deliberately absent: scopes are immutable after mint, and revoking is DELETE on this same path. Sending either is a 400 rather than a silently-ignored no-op.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no | New display name. Omit to leave it unchanged; `null` is rejected with 400 rather than silently ignored. |
| `allowedIps` | array of string | no | Replaces the whole allowlist; it never merges. Omit the field to leave the current allowlist unchanged, or pass an empty array to clear it so the key may authenticate from any IP. Entries are IPv4/IPv6 addresses or CIDR ranges (up to 50); deny-by-default, so a correct secret from an unlisted IP is rejected with 403. Explicit `null` is rejected with 400 — unlike on create it does not mean `any IP` here, and it used to be a silently-ignored no-op. |
| `inboxIds` | array of string (uuid) | no | Replaces the whole per-inbox restriction; it never merges. Omit to leave it unchanged, or pass an empty array to clear it so the key may send as any inbox. A restricted key may only ever narrow to a subset of its own inboxes — it cannot unpin itself or a sibling. Explicit `null` is rejected with 400: unlike on create it does not mean `unrestricted` here, it would change nothing. |

## UpdateBucketRequest

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `displayName` | string or null | no | (max length 256) |
| `description` | string or null | no | (max length 1024) |
| `metadata` | object or null | no |  |

## UpdateLetterTemplateRequest

Partial update. Provide at least one of name, html, variables, or status.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | no |  |
| `html` | string | no |  |
| `variables` | array of string or [LetterTemplateVariable](https://docs.dairo.app/api-reference/schemas.md#lettertemplatevariable) or object | no | Declared template variables. Preferred: \[{name, type?, example?, label?}\]. Also accepted: an array of names, or an object {name: type}. Stored and returned in the preferred shape. |
| `status` | [LetterTemplateStatus](https://docs.dairo.app/api-reference/schemas.md#lettertemplatestatus) | no |  |

## VerificationWait

A durable await_verification wait. status is pending until it resolves (a code was extracted from an inbound message) or expires (deadline passed / canceled). Resolution outputs (result, from, messageId, resolvedAt) are present only once resolved.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `waitId` | string (uuid) | yes |  |
| `inboxId` | string (uuid) | yes |  |
| `agentId` | string (uuid) or null | no | The Agent Passport that registered this wait, when the API key was agent-bound. |
| `status` | `"pending"`, `"resolved"`, `"expired"` | yes |  |
| `fromHint` | string or null | no |  |
| `instruction` | string or null | no | The natural-language matcher instruction this wait was registered with. |
| `result` | string or null | no | The value the AI matcher extracted per the instruction (present once resolved) — a code, a link, or a quoted line. |
| `from` | string or null | no | The From address of the message that resolved the wait. |
| `messageId` | string (uuid) or null | no | The inbound message that resolved the wait. |
| `idempotencyKey` | string or null | no |  |
| `expiresAt` | string (date-time) | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `resolvedAt` | string (date-time) or null | no |  |
| `expiredAt` | string (date-time) or null | no |  |

## VerificationWaitListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `waits` | array of [VerificationWait](https://docs.dairo.app/api-reference/schemas.md#verificationwait) | yes |  |

## Webhook

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `object` | `"webhook"` | yes |  |
| `id` | string (uuid) | yes |  |
| `url` | string (uri) | yes |  |
| `events` | array of `"message.received"`, `"message.quarantined"`, `"message.sent"`, `"message.delivered"`, `"message.bounced"`, `"message.complained"`, `"message.delayed"`, `"budget.tripped"`, `"compliance.erasure.completed"`, `"subprocessor.changed"`, `"object.scanned"`, `"share_link.opened"`, `"letter.status_changed"`, `"call.status_changed"`, `"domain.verified"` | yes |  |
| `label` | string or null | no | Optional human label for the endpoint, or null when unlabeled. |
| `status` | `"active"`, `"paused"` | yes |  |
| `createdAt` | string (date-time) | yes |  |
| `lastDeliveryAt` | string (date-time) or null | no | Timestamp of the most recent successful webhook delivery, or null before the first successful delivery. |
| `secondarySecretExpiresAt` | string (date-time) or null | no | End of the dual-secret window after a rotation, or null. |
| `secondarySecretActive` | boolean | no | Whether deliveries are still signed with the previous secret too. |
| `consecutiveFailures` | integer | no |  |
| `pausedReason` | string or null | no | auto:delivery_failures when the circuit breaker paused the endpoint, else null. |
| `autoPaused` | boolean | no |  |
| `autoPausedAt` | string (date-time) or null | no |  |

## WebhookListResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `webhooks` | array of [Webhook](https://docs.dairo.app/api-reference/schemas.md#webhook) | yes |  |

## WhoamiApiKey

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes |  |
| `scopes` | array of string | yes |  |

## WhoamiResponse

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `userId` | string | yes |  |
| `projectId` | string or null | no |  |
| `apiKey` | [WhoamiApiKey](https://docs.dairo.app/api-reference/schemas.md#whoamiapikey) | yes |  |
| `plan` | string | yes |  |
| `limits` | object | yes |  |
| `usage` | object | yes |  |
| `period` | object | yes |  |
| `notes` | object | yes |  |
| `storage` | [WhoamiStorage](https://docs.dairo.app/api-reference/schemas.md#whoamistorage) | yes |  |
| `organization` | [ActiveOrganization](https://docs.dairo.app/api-reference/schemas.md#activeorganization) | no |  |
| `organizations` | array of [Organization](https://docs.dairo.app/api-reference/schemas.md#organization) | no | Every organization the account is a member of (the org switcher list). |
| `letters` | object | no | Physical-mail status for this account, so you know before sending whether a letter will really be printed. |

## WhoamiStorage

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `usedBytes` | integer (int64) | yes |  |
| `limitBytes` | integer (int64) | yes |  |
| `remainingBytes` | integer (int64) | yes |  |
| `breakdown` | [WhoamiStorageBreakdown](https://docs.dairo.app/api-reference/schemas.md#whoamistoragebreakdown) | yes |  |

## WhoamiStorageBreakdown

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `mailBodyBytes` | integer (int64) | yes |  |
| `attachmentBytes` | integer (int64) | yes |  |
| `fileBytes` | integer (int64) | yes |  |
| `expiringFileBytes` | integer (int64) | yes |  |
| `activeFileObjects` | integer (int64) | yes |  |

## Related topics

- [API reference](https://docs.dairo.app/api-reference/introduction.md)
- Previous: [API reference](https://docs.dairo.app/api-reference/introduction.md)
- Next: [List letter templates](https://docs.dairo.app/api-reference/dairo-api/letters/list-letter-templates.md)
