//#region src/core/http.d.ts /** Transport metadata exposed to callers via `.withResponse()`. */ interface BirdResponse { status: number; headers: Headers; /** Correlation ID — the `X-Request-Id` header. */ requestId: string; } /** Per-request lifecycle inputs, supplied by the resource method. */ interface RequestLifecycleOptions { /** HTTP method — decides idempotency-key generation and retry safety. */ method: string; /** Caller-supplied idempotency key; auto-generated for mutations if absent. */ idempotencyKey?: string; /** Caller cancellation. */ signal?: AbortSignal; /** Per-attempt timeout (ms). Overrides the client default. */ timeout?: number; /** Max retry attempts. Overrides the client default. */ maxRetries?: number; } /** The shape a generated hey-api SDK call resolves to. */ interface FetchOutcome { data?: T; error?: unknown; /** Present whenever the HTTP round-trip completed; absent only on a rejected call. */ response?: Response; } /** Context handed to the call thunk on each attempt. */ interface AttemptContext { signal: AbortSignal; idempotencyKey?: string; } interface CoreDefaults { /** Per-attempt timeout (ms). */ timeout: number; /** Max retry attempts. */ maxRetries: number; /** * Extra credentials some operations require on top of the API key, keyed by the * security scheme that names them. A generated method names the schemes its * operation declares; the core resolves them, so a credential reaches only * those operations and never an unrelated request. */ credentials?: Record; } declare class BirdHTTPClient { private readonly defaults; constructor(defaults: CoreDefaults); /** * Resolve the credential headers an operation's security schemes require. * Throws before the request when one is unconfigured, so a caller gets a named * error instead of a 401. */ credentialHeaders(schemes: string[] | undefined, override?: Record): Record; /** * Run a generated hey-api SDK call through the request lifecycle. * * @param call Invokes the SDK function; receives the per-attempt signal and * the idempotency key to set as a header. * @returns the parsed body plus transport metadata. * @throws a `BirdError` subclass on terminal failure; the native * `AbortError` if the caller's signal aborts. */ request(call: (ctx: AttemptContext) => Promise>, options: RequestLifecycleOptions): Promise<{ data: T; response: BirdResponse; }>; } //#endregion //#region src/errors.d.ts /** Root of the hierarchy. Catch this to catch anything the SDK throws. */ declare class BirdError extends Error { constructor(message: string); } /** Network-level failure with no HTTP response (DNS, refused, socket hangup). */ declare class BirdConnectionError extends BirdError { constructor(message: string); } /** A single attempt exceeded its timeout. Retryable. */ declare class BirdTimeoutError extends BirdError { readonly timeoutMs: number; constructor(message: string, timeoutMs: number); } /** A webhook payload failed signature verification (bad signature, stale timestamp, malformed headers). */ declare class BirdWebhookVerificationError extends BirdError { constructor(message: string); } /** One per-field validation failure (the `details` array on a 422). */ interface ErrorDetail { /** Dotted field path, e.g. `to[0].email`, `subject`, `.`. */ param: string; /** What is wrong with this field. */ message: string; } /** * One recovery step the server suggests. Read `kind` before `operation`: only an * `operation` step carries one. */ interface NextAction { /** * What to do about this step: `operation` calls the operation named in * `operation` and reads again, `external` acts somewhere this API does not * reach, `wait` reads again later, `terminal` means nothing resolves this so * stop retrying. A value this SDK version does not know is display-only: show * `description` and offer no action. */ kind: string; /** Short human-readable label for the step, suitable for display. */ description: string; /** operationId to call. Present only when `kind` is `operation`. */ operation?: string; /** * Parameters that address `operation`, by name — every parameter the call * needs, so it can be made from this step alone. A request body, when the * operation takes one, is described by the operation and never appears here. */ params?: Record; /** * A URL to open. Present only when `kind` is `external`, and only when the step * has one; an external step with nothing to open is normal. */ url?: string; } /** One verification requirement blocking the action, with the flow that resolves it. */ /** Constructor fields shared by every API error, mapped from the wire body. */ interface BirdAPIErrorFields { statusCode: number; /** Opaque, stable error code (`E#####`). */ code: string; /** Coarse category — the value callers branch on. */ type: string; /** Human-readable slug for logs. Paired with `code`, never replaces it. */ errorName: string; message: string; /** Stable link to the docs page for this code. */ docUrl: string; /** Correlation ID — also the `X-Request-Id` response header. */ requestId: string; /** Offending field, when applicable. */ param?: string; /** Verbatim code from a downstream system (SMTP reply, payment decline). */ vendorCode?: string; /** Human recovery line for this error, when a recovery is known. */ remediation?: string; /** Recovery steps for this error, in the order to take them. */ next?: NextAction[]; } /** The server returned an error body. Base for every `type`-specific class. */ declare class BirdAPIError extends BirdError { readonly statusCode: number; readonly code: string; readonly type: string; readonly errorName: string; readonly docUrl: string; readonly requestId: string; readonly param?: string; readonly vendorCode?: string; readonly remediation?: string; readonly next?: NextAction[]; constructor(fields: BirdAPIErrorFields); } /** 401 — authentication failed or missing. */ declare class BirdAuthError extends BirdAPIError { constructor(fields: BirdAPIErrorFields); } /** 403 — authenticated but not allowed. */ declare class BirdPermissionError extends BirdAPIError { constructor(fields: BirdAPIErrorFields); } /** 404 — resource does not exist. */ declare class BirdNotFoundError extends BirdAPIError { constructor(fields: BirdAPIErrorFields); } /** 409 — semantic conflict (e.g. a unique value already taken). */ declare class BirdConflictError extends BirdAPIError { constructor(fields: BirdAPIErrorFields); } /** 400 — malformed request. */ declare class BirdBadRequestError extends BirdAPIError { constructor(fields: BirdAPIErrorFields); } /** 402 — billing/balance problem. */ declare class BirdBillingError extends BirdAPIError { constructor(fields: BirdAPIErrorFields); } /** 412/428 — a precondition was not met. */ declare class BirdPreconditionError extends BirdAPIError { constructor(fields: BirdAPIErrorFields); } /** 413 — request body too large. */ declare class BirdPayloadTooLargeError extends BirdAPIError { constructor(fields: BirdAPIErrorFields); } /** 500 — unexpected server error. */ declare class BirdInternalError extends BirdAPIError { constructor(fields: BirdAPIErrorFields); } /** 501 — endpoint not implemented. */ declare class BirdNotImplementedError extends BirdAPIError { constructor(fields: BirdAPIErrorFields); } /** 421 — request reached the wrong region. */ declare class BirdMisdirectedError extends BirdAPIError { constructor(fields: BirdAPIErrorFields); } /** 503 — service temporarily unavailable. */ declare class BirdServiceUnavailableError extends BirdAPIError { constructor(fields: BirdAPIErrorFields); } /** 422 — field validation failed; `details` carries the per-field errors. */ declare class BirdValidationError extends BirdAPIError { readonly details: ErrorDetail[]; constructor(fields: BirdAPIErrorFields & { details: ErrorDetail[]; }); } /** 429 — rate limited; `retryAfter` is the server-advised wait in seconds. */ declare class BirdRateLimitError extends BirdAPIError { readonly retryAfter?: number; constructor(fields: BirdAPIErrorFields & { retryAfter?: number; }); } //#endregion //#region src/core/result.d.ts /** Per-request overrides accepted by every resource method. */ interface RequestOptions { /** * Per-call override for the extra credentials an operation requires, keyed by * security scheme (`{ RealtimeKey: "…", RealtimeSecret: "…" }`). Overrides the * client config for this call, so one client can address several apps. */ credentials?: Record; /** Idempotency key; auto-generated for mutations if omitted, reused on retry. */ idempotencyKey?: string; /** Caller cancellation. Rejects with the native `AbortError`. */ signal?: AbortSignal; /** Per-attempt timeout (ms). Overrides the client default. */ timeout?: number; /** Max retry attempts. Overrides the client default. */ maxRetries?: number; /** Extra headers for this request. SDK-internal headers win on conflict. */ headers?: Record; } /** * The result of `.safe()` — the value or the error, never thrown. On success * `data` and the `response` envelope are present and `error` is `null`. On * failure `error` is a `BirdError` you can `instanceof`-narrow, and `data`/ * `response` are `null` — the metadata you need (status, request id) is on the * error itself. A caller-initiated abort is not a Bird failure and still throws * (the native `AbortError`). */ type SafeResult = { data: T; error: null; response: BirdResponse; } | { data: null; error: BirdError; response: null; }; /** Single-result return: `await` for the value, `.withResponse()` for metadata. */ interface APIPromise extends Promise { withResponse(): Promise<{ data: T; response: BirdResponse; }>; /** Resolve to `{ data, error }` instead of throwing. */ safe(): Promise>; } /** One cursor-paginated page — the wire envelope shape (snake), verbatim. */ interface CursorPage { data: T[]; /** Pass back as `starting_after` to advance. Null at the end. */ next_cursor: string | null; /** Pass back as `ending_before` to step back. Null at the start. */ prev_cursor: string | null; /** Refresh anchor; pass as `ending_before` later for items since this page. */ refresh_cursor: string | null; /** Total across all pages — only when `include_total=true` was passed. */ total?: number | null; } /** * List return (R1): `await` resolves the first page; `for await` walks every * item across all pages, fetching subsequent pages lazily. */ interface PaginatedPromise extends Promise>, AsyncIterable { withResponse(): Promise<{ data: CursorPage; response: BirdResponse; }>; /** Resolve the first page as `{ data, error }` instead of throwing. */ safe(): Promise>>; } //#endregion //#region src/generated/types.gen.d.ts /** * Webhook delivery body. `type` identifies the event variant, `timestamp` is when the event occurred, and `data` contains the event-specific payload. See the [webhooks guide](/docs/guides/webhooks) for signature verification. * */ type WebhookEvent = ({ type: "domain.failed"; } & EventDomainFailed) | ({ type: "domain.verified"; } & EventDomainVerified) | ({ type: "email.accepted"; } & EventEmailAccepted) | ({ type: "email.bounced"; } & EventEmailBounced) | ({ type: "email.canceled"; } & EventEmailCanceled) | ({ type: "email.clicked"; } & EventEmailClicked) | ({ type: "email.complained"; } & EventEmailComplained) | ({ type: "email.deferred"; } & EventEmailDeferred) | ({ type: "email.delivered"; } & EventEmailDelivered) | ({ type: "email.list_unsubscribed"; } & EventEmailListUnsubscribed) | ({ type: "email.opened"; } & EventEmailOpened) | ({ type: "email.out_of_band_bounce"; } & EventEmailOutOfBandBounce) | ({ type: "email.processed"; } & EventEmailProcessed) | ({ type: "email.received"; } & EventEmailReceived) | ({ type: "email.rejected"; } & EventEmailRejected) | ({ type: "email.scheduled"; } & EventEmailScheduled) | ({ type: "email.unsubscribed"; } & EventEmailUnsubscribed) | ({ type: "email_mailbox.message_delivered"; } & EventEmailMailboxMessageDelivered) | ({ type: "email_mailbox.message_failed"; } & EventEmailMailboxMessageFailed) | ({ type: "email_mailbox.message_received"; } & EventEmailMailboxMessageReceived) | ({ type: "email_mailbox.message_sent"; } & EventEmailMailboxMessageSent) | ({ type: "email_mailbox.suspended"; } & EventEmailMailboxSuspended) | ({ type: "email_mailbox.thread_created"; } & EventEmailMailboxThreadCreated) | ({ type: "email_suppression.created"; } & EventEmailSuppressionCreated) | ({ type: "sms.accepted"; } & EventSmsAccepted) | ({ type: "sms.delivered"; } & EventSmsDelivered) | ({ type: "sms.expired"; } & EventSmsExpired) | ({ type: "sms.failed"; } & EventSmsFailed) | ({ type: "sms.received"; } & EventSmsReceived) | ({ type: "sms.rejected"; } & EventSmsRejected) | ({ type: "sms.sent"; } & EventSmsSent) | ({ type: "sms.undelivered"; } & EventSmsUndelivered) | ({ type: "verify.attempt.delivered"; } & EventVerifyAttemptDelivered) | ({ type: "verify.attempt.sent"; } & EventVerifyAttemptSent) | ({ type: "verify.attempt.undelivered"; } & EventVerifyAttemptUndelivered) | ({ type: "verify.verification.created"; } & EventVerifyVerificationCreated) | ({ type: "verify.verification.failed"; } & EventVerifyVerificationFailed) | ({ type: "verify.verification.verified"; } & EventVerifyVerificationVerified) | ({ type: "voice_call.answered"; } & EventVoiceCallAnswered) | ({ type: "voice_call.ended"; } & EventVoiceCallEnded) | ({ type: "voice_call.initiated"; } & EventVoiceCallInitiated) | ({ type: "whatsapp.accepted"; } & EventWhatsAppAccepted) | ({ type: "whatsapp.delivered"; } & EventWhatsAppDelivered) | ({ type: "whatsapp.failed"; } & EventWhatsAppFailed) | ({ type: "whatsapp.read"; } & EventWhatsAppRead) | ({ type: "whatsapp.received"; } & EventWhatsAppReceived) | ({ type: "whatsapp.rejected"; } & EventWhatsAppRejected) | ({ type: "whatsapp.sent"; } & EventWhatsAppSent); type NextAction$1 = { /** * What you do about this step. * * - `operation`: call the operation named in `operation`, then * read again. * - `external`: act somewhere this API does not reach, then read * again. * - `wait`: nothing is asked of you, so read again later. * - `terminal`: nothing you do resolves this, so stop retrying. * * Tolerate a value you do not recognize: show the `description` and * offer no action. * */ kind: string; /** * A short, human-readable label for the step, suitable for display. */ description: string; /** * The operationId to call. Present only when `kind` is `operation`. The operation's own schema says how to call it; this says only which one, and what to address it with. * */ operation?: string; /** * The parameters that address the operation, by name: `{"sender_id": "…"}` for an operation on `/v1/sms/senders/{sender_id}/requirements`. A parameter the operation takes in its query string is given the same way, so an operation addressed as `?subject_id=` carries `{"subject_id": "…"}`. Every parameter the call needs is here, whether its value came from the thing you were acting on or is fixed for this step, so you can make the call from this object alone. Present only when `kind` is `operation` and the operation names a subject. A request body, when the operation takes one, is described by the operation's own schema and never appears here. * */ params?: { [key: string]: string; }; /** * A URL to open. Present only when `kind` is `external`, and only when the step has one. An external step whose `description` says to go and do something with no URL to open is normal. * */ url?: string; }; type WorkspaceId = string; /** * ISO 3166-1 alpha-2 country code. */ type CountryCode = string; type Timestamps = { readonly created_at: string; readonly updated_at: string; }; type RealtimeAppId = string; /** * The event name clients bind to. Application event names are free-form; the `bird:` and `bird_internal:` prefixes are reserved for the protocol and rejected. */ type RealtimeEventName = string; /** * A Realtime channel name. Only letters, digits, and _ - = @ , . ; Prefix with `private-` or `presence-` for authenticated channels, or `private-encrypted-` for channels whose payloads are end-to-end encrypted with a key only you hold. */ type RealtimeChannelName = string; /** * Arbitrary JSON payload delivered as the event data: an object, array, or scalar. Cap: 10 KB serialized. */ type RealtimeEventData = unknown; /** * Exclude this connection from delivery, to avoid echoing a change back to the client that triggered it. The value is the client's connection id, assigned when its connection is established. */ type RealtimeExcludeConnectionId = string; /** * A per-channel attribute to include in the response. `member_count` is presence-channels only; `connection_count` requires the app's connection-counting flag. */ type RealtimeChannelInclude = "member_count" | "connection_count"; /** * A Realtime publish: delivers one event to one or more channels of the app. Listing several channels fans the event out to all of them (broadcast) in a single call. * */ type RealtimePublish = { event: RealtimeEventName; /** * The channels to deliver the event to (up to 100 per call). Prefix with `private-` or `presence-` for authenticated channels. A `private-encrypted-` channel must be the only channel in its publish: each encrypted channel has its own key, so a fan-out would hand the other channels unreadable ciphertext. * */ channels: Array; data?: RealtimeEventData; exclude_connection_id?: RealtimeExcludeConnectionId; /** * Per-channel attributes to return alongside the publish, reflecting each channel's state at publish time. `member_count` is available only for presence channels. `connection_count` requires the app's connection-counting flag. Requesting attributes counts as one additional message toward usage. */ include?: Array; }; /** * Per-channel counts, present only when requested via `include` and applicable. */ type RealtimeChannelCounts = { /** * Distinct members (presence channels only; requires `include=member_count`). */ member_count?: number; /** * Connections currently subscribed to this channel (requires `include=connection_count` and the app's connection-counting flag). Channel-scoped: distinct from the app-wide peak connections metric. */ connection_count?: number; }; type RealtimeChannelListItem = RealtimeChannelCounts & { name: RealtimeChannelName; }; /** * The result of a Realtime publish. The event was accepted and fanned out to the requested channels; delivery to connected clients is asynchronous. * */ type RealtimePublishResult = { /** * Per-channel attributes at publish time, present only when the request asked for them via `include`; one item per distinct target channel, sorted by name. */ readonly data?: Array; }; /** * A single event published to one channel as part of a batch. */ type RealtimeBatchEvent = { event: RealtimeEventName; channel: RealtimeChannelName; data?: RealtimeEventData; exclude_connection_id?: RealtimeExcludeConnectionId; /** * Attributes of this event's channel to return alongside the publish (same semantics and validation errors as on the channel endpoints). Requesting attributes counts as one additional message toward usage. */ include?: Array; }; /** * A batch of events, each delivered to a single channel, in one request. */ type RealtimeBatchPublish = { /** * Up to 10 events per batch. */ events: Array; }; type RealtimeBatchPublishResultItem = RealtimeChannelCounts & { channel: RealtimeChannelName; }; /** * The result of a Realtime batch publish. The events were accepted for delivery; delivery to connected clients is asynchronous. * */ type RealtimeBatchPublishResult = { /** * Per-event channel attributes at publish time, present only when at least one event asked for them via `include`. Positional: one item per event, in request order. */ readonly data?: Array; }; /** * The app's occupied channels. The Realtime service does not paginate this listing, so all occupied channels are returned in one response. */ type RealtimeChannelsList = { /** * The occupied channels, sorted by name. */ data: Array; }; type RealtimeChannelInfo = RealtimeChannelCounts & { /** * Whether at least one client is subscribed. */ occupied: boolean; }; /** * An app-defined member ID for your application's end user, assigned when your auth server authorizes them. Use up to 128 URL-safe characters because member IDs appear directly in API request paths. The value can include `+ : @ . _ -`, but not `/ ? # %` or whitespace. */ type RealtimeMemberId = string; /** * A member present on a presence channel. */ type RealtimeChannelMember = { member_id: RealtimeMemberId; }; /** * The members present on a presence channel. */ type RealtimeChannelMembers = { members: Array; }; /** * An event addressed to one member rather than to a channel. Every connection that member currently holds receives it; if they hold none, the event is dropped. */ type RealtimeMemberPublish = { event: RealtimeEventName; data?: RealtimeEventData; }; /** * Aggregate delivery status of an email, derived from its recipients' states. * * In flight: * * - `scheduled`: The message is queued to send at a future time and has not been dispatched yet. * - `accepted`: The initial status of an immediate send. The message is queued for its recipients. * - `processed`: Delivery is underway, so at least one recipient's message is on its way out and none has failed. * - `deferred`: At least one recipient's mailbox provider asked for a retry, and delivery attempts continue. * * Final: * * - `delivered`: Every recipient's mail server accepted the message. * - `bounced`: Every recipient permanently failed (bounced or was rejected). * - `rejected`: Every recipient was rejected before a delivery attempt (for example, all recipients were suppressed). * - `partial_failure`: Some recipients permanently failed while others were delivered or are still in flight. * - `canceled`: A scheduled message was canceled before it was sent. * * `complained` takes precedence over every other status: at least one recipient reported * the message as spam, regardless of what happened to the rest. * */ type EmailMessageStatus = "scheduled" | "accepted" | "processed" | "deferred" | "delivered" | "partial_failure" | "bounced" | "complained" | "rejected" | "canceled"; /** * Content classification, which controls suppression policy: * * - `marketing`: Blocks on all suppression reasons. * - `transactional`: Allows delivery through complaint and unsubscribe suppressions, for receipts, password resets, and similar operational mail. * */ type EmailMessageCategory = "marketing" | "transactional"; type EmailId = string; /** * An email address with an optional display name. */ type EmailAddress = { /** * Email address. */ email: string; /** * Display name shown alongside the address in mail clients. */ name?: string; }; /** * A language tag in BCP-47 form, for example `en` or `pt-BR`. */ type LanguageTag = string; type EmailTemplateId = string; type EmailTemplateVersionId = string; /** * Structured key/value label attached to a message. Surfaces in list filters, the event log, and webhook payloads. Use tags for low-cardinality filtering dimensions (category, experiment ID, template ID). For arbitrary per-send context that does not need to be filterable, use `metadata`. * * The send request defines the tag-count limit. Tag names are unique within a send; supplying the same name twice is rejected. * */ type Tag = { /** * Tag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters. * */ name: string; /** * Tag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters. * */ value: string; }; type EmailAttachmentId = string; /** * Attachment metadata returned on API reads. Download the file during its retention window with `GET /v1/email/messages/{message_id}/attachments/{attachment_id}`. * */ type EmailAttachmentRef = { /** * Attachment ID, stable per email send. */ readonly id?: EmailAttachmentId; /** * Filename as shown to the recipient. */ filename: string; /** * Resolved MIME type at send time. */ content_type?: string; /** * Decoded size in bytes. */ size: number; /** * True when the attachment was sent inline via a `content_id` reference in the HTML body, false for regular file attachments. * */ inline?: boolean; /** * The Content-ID set at send time, when the attachment was inline. */ content_id?: string | null; }; type EmailMessage = { /** * Message ID. */ readonly id: EmailId; /** * Sender address. `name` is present when a display name was provided on the send. */ from: EmailAddress; /** * Primary recipients. Length is the recipient count. Use the broadcasts endpoint for audience-targeted sends. Each entry's `name` is present when a display name was provided on the send. */ to: Array; /** * CC recipients. */ cc?: Array; /** * BCC recipients. */ bcc?: Array; /** * The subject line as delivered. For a send that used a template, the stored subject is the template's, so this reports it with the send's `parameters` substituted in, which is what the recipient saw. * */ subject: string; category: EmailMessageCategory; /** * Reply-To addresses, if set on the send. Empty/null when no Reply-To was provided. */ reply_to?: Array | null; readonly status: EmailMessageStatus; /** * How many recipients are in the `accepted` state, meaning we have the message and are getting ready to deliver it. */ readonly accepted_count: number; /** * How many recipients the message has been prepared for and queued for delivery. */ readonly processed_count: number; /** * How many recipients' messages were accepted by their mail server. */ readonly delivered_count: number; /** * Number of recipients that resulted in a permanent delivery failure. */ readonly bounced_count: number; /** * Number of recipients that reported spam. */ readonly complained_count: number; /** * Number of recipients in transient delivery deferral. Their mail server asked for a retry, and delivery attempts continue. */ readonly deferred_count: number; /** * Number of recipients rejected before delivery. Read the per-recipient `rejection_reason` field on `GET /v1/email/messages/{message_id}/recipients` for the specific cause. * */ readonly rejected_count: number; /** * Time between the send being accepted and the message being prepared for delivery, in milliseconds, for the fastest recipient. Null until the first recipient reaches `processed`. * */ readonly processing_latency_ms?: number | null; /** * Time between the message being processed and the receiving mail server accepting it, in milliseconds, for the fastest delivered recipient. Null until the first recipient is delivered. * */ readonly delivery_latency_ms?: number | null; /** * End-to-end accept → delivered time for the fastest delivered recipient, in milliseconds. Null until the first recipient is delivered. * */ readonly total_latency_ms?: number | null; /** * Total open events across all recipients. */ readonly open_count: number; /** * Total click events across all recipients. */ readonly click_count: number; /** * The template language this send asked for, in canonical form (`pt-BR` for a request of `pt-br`). Null when the send named no language (it took the template's default) or used no template at all. Compare it with `resolved_language`: when they differ, the language you asked for was not available and the template's `on_missing_language` policy chose the one shown there instead. * */ readonly requested_language?: LanguageTag | null; /** * The template language this send was actually delivered in, in canonical form. Null when the send used no template. A non-null value with a null `requested_language` means the send named no language and took the template's default. * */ readonly resolved_language?: LanguageTag | null; /** * The template this send rendered from, or null for a send that supplied its content inline. * */ readonly template_id?: EmailTemplateId | null; /** * The exact template version this send rendered from, or null for an inline send. A template's live version changes every time you submit it, so this is what identifies the wording that was actually delivered, together with `resolved_language`. * */ readonly template_version_id?: EmailTemplateVersionId | null; /** * Labels on this message, each one a `name` and a `value`, that you can filter and search messages by. Use tags for anything you want to find messages by later, and `metadata` for data you only want handed back to you. */ tags?: Array; /** * Any JSON you kept on the message. We store it and hand it back in webhook payloads, and that is all it does. If you want to search or filter by it, use `tags` instead. */ metadata?: { [key: string]: unknown; }; /** * The substitution values this send supplied, whether inline or from a template, or null if none were supplied. They are the values applied to `subject` and to the bodies the content endpoint returns, kept so you can see what produced the delivered copy and not only the result. * */ readonly parameters?: { [key: string]: unknown; } | null; /** * Attachment metadata for the send. Empty when no attachments were included. Raw content is not echoed. When content storage is enabled, download an attachment by its `id` via the message's attachment endpoint. */ attachments?: Array; /** * Whether open tracking is enabled for this send. */ track_opens: boolean; /** * Whether click tracking is enabled for this send. */ track_clicks: boolean; /** * When the send request was accepted. */ readonly created_at: string; /** * Thread this message belongs to, or null when the message is not part of one. */ readonly thread_id?: string | null; /** * The message this one is a reply to, if any. */ readonly in_reply_to_message_id?: EmailId | null; /** * When all recipients reached a terminal delivered state, or null if not yet fully delivered. */ readonly delivered_at?: string | null; /** * When this message is scheduled to send, for a send created with a future send time. Null for an immediate send. Stays set after the scheduled send fires. */ readonly scheduled_at?: string | null; }; /** * A sender or recipient address. Accepts a plain email string (`jane@acme.com`), an RFC 5322 mailbox string with an embedded display name (`Jane Doe `), or an object carrying the address and an optional display name. All forms can be mixed freely within one request. Responses always return the object form. * */ type EmailAddressInput = string | EmailAddress; /** * A template's slug: what you send it by, for example `welcome-email`. You choose it when you create the template, and it cannot be changed afterwards. It can contain lowercase letters, numbers, hyphens, and underscores, has to start and end with a letter or a number, and can be up to 63 characters long. * */ type TemplateSlug = string; type EmailTemplateSend = unknown & { /** * The template to send, by its id. */ id?: EmailTemplateId; /** * The template to send, by its slug handle. A workspace template (for example `welcome-email`) or a built-in `system` template (for example `bird_welcome`). */ slug?: TemplateSlug; /** * Which of the template's languages to send. Omit it to send the template's default language, unless the template sets `language_source_required`, in which case a send naming no language is rejected. When the template does not have the language you ask for, its own `on_missing_language` setting decides whether the closest available language is sent instead or the send is rejected. * */ language?: LanguageTag; /** * Values for the template's variables, keyed by the variable name. A variable name is a single word. * * Every variable in the template's `variables` list needs a value. A send * that omits one is rejected. Languages can use different variables, and a * value unused by the selected language is ignored. * * The API supplies values under the reserved `bird` key, so a send that sets * it is rejected. `parameters` is capped at 16 KB once serialized. * */ parameters?: { [key: string]: unknown; }; }; /** * A file attached to an email. Put the base64-encoded bytes in `content` and the * recipient-facing name in `filename`. To show an image inline, set `content_id` * and reference it from the HTML body with ``. * * The generated message is limited to 20 MB across the HTML body, text body, * attachments, and inline images after base64 and MIME encoding. Keep raw * attachment content at or below 15 MB to leave room for encoding and the body. * * Batch sends apply the same 20 MB limit to each message and to the full request * body. Executable and script content types are rejected. * */ type EmailAttachment = { /** * The name the recipient sees on the attachment. */ filename: string; /** * Base64-encoded file bytes. The encoded value and MIME wrapping count toward the 20 MB message limit. */ content: string; /** * The file's MIME type. If omitted, the API infers it from the extension in `filename`. The API rejects executable and script types based on this value. */ content_type?: string; /** * An RFC 2392 Content-ID for an inline file. Reference it from the HTML body with ``. Omit it to send a downloadable attachment. */ content_id?: string; }; type EmailMessageSendRequest = { /** * Sender address, as a plain email string, an RFC 5322 mailbox string (`Jane `), or an object with an optional display name. Must be from a verified domain in this workspace. */ from: EmailAddressInput; /** * Primary recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane `), or an object with an optional display name. */ to: Array; /** * CC recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane `), or an object with an optional display name. */ cc?: Array; /** * BCC recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane `), or an object with an optional display name. */ bcc?: Array; /** * Message subject line. Required for inline sends. Omit it when sending a `template` (the template supplies the subject). */ subject?: string; /** * HTML body. At least one of html or text must be provided. */ html?: string; /** * Plain-text body. At least one of html or text must be provided. */ text?: string; /** * Reply-To addresses, each a plain email string, an RFC 5322 mailbox string, or an object with an optional display name. RFC 5322 allows multiple. Every recipient reply hits all listed addresses, so 1-2 is typical. The 25 cap exists to prevent header sizes that some receiving mail servers reject. * */ reply_to?: Array; /** * Custom email headers as key-value pairs (for example `References`, `In-Reply-To`, or your own `X-*` headers). Reserved headers are rejected with a `422`. Set the message's addressing and subject through the dedicated fields: `from`, `to`, `cc`, `bcc`, `reply_to`, and `subject`. The API automatically generates `Content-Type`, `Content-Transfer-Encoding`, `DKIM-Signature`, `Received`, and `Return-Path`. You cannot override these generated headers. `List-Unsubscribe` and `List-Unsubscribe-Post` are honored as-is on `transactional` sends. Marketing sends receive a compliant unsubscribe header, so supplying either one is rejected with a `422`. Header values may not contain carriage-return or line-feed characters. Up to 25 headers per send, each value up to 998 characters. * */ headers?: { [key: string]: string; }; /** * Structured `{name, value}` labels for **filtering and analytics**. Tags become first-class query dimensions: * * - Filter the list endpoint by tag name. * - Slice analytics rollups by tag. * - Surface in webhook payloads. * * Cap: 20 tags per send. Use tags for low-cardinality dimensions (`category`, `experiment_variant`, `template_id`). For arbitrary structured context that you do not need as a filter dimension, use `metadata` instead. * */ tags?: Array; /** * Arbitrary JSON object returned on API reads and included in webhook payloads. You can query its paths in analytics, such as `metadata.order_id`, but it is not a dashboard filter. The serialized object is limited to 2 KB. Use metadata for per-send context such as order IDs, customer references, and structured event data. For low-cardinality filterable labels, use `tags` instead. * */ metadata?: { [key: string]: unknown; }; /** * Parameter values used to personalize inline content. A parameter is a single word, and a token in the subject or body (for example `{{ animal }}`) is replaced with the value of that name at send time. Shared across all recipients of this send. A token with no matching key renders empty. Cap: 16 KB serialized. When sending a stored `template`, put the values in `template.parameters` instead. * */ parameters?: { [key: string]: unknown; }; /** * Send a stored template instead of inline content. When set, omit `subject`, `html` and `text`, because the template supplies them. Personalize with `template.parameters`. A template send goes out immediately: `template` and `scheduled_at` are mutually exclusive, and combining them is rejected with a `422`. * */ template?: EmailTemplateSend; /** * Whether to track open events for this message. */ track_opens?: boolean; /** * Whether to track click events for this message. */ track_clicks?: boolean; /** * ID of the IP pool to send from (`ipp_` prefix), or `ipp_shared` to route through the shared pool explicitly. Omit to use your organization's default pool. An unknown pool, or a pool with no dedicated IPs available to send from, is rejected with a `422`. * */ ip_pool_id?: string; /** * Content classification, which controls suppression policy: * * - `marketing`: Blocks on all suppression reasons. * - `transactional`: Allows delivery through complaint and unsubscribe suppressions, for receipts, password resets, and similar operational mail. * * When you send with `template` and omit this field, the message takes the template's own classification, so a template created as `transactional` sends as transactional. Set this field to classify a single send differently from its template. It always takes precedence. A send with no template and no category defaults to `marketing`. * */ category?: EmailMessageCategory; /** * Files to attach, up to 20 per message. A message can be at most 20 MB once it has been generated, and we refuse a send that would go over. That figure covers the HTML body, the text body and every attachment and inline image, all measured after base64 encoding, which adds roughly a third. So 15 MB of raw files already accounts for most of the budget, and the body competes for the same space. A batch send is held to the same 20 MB per message, and the whole request body is capped at 20 MB as well. * */ attachments?: Array; /** * Schedule the message to send at a future time instead of immediately. Must be at least 30 seconds and at most 30 days ahead. Outside that range the request is rejected with `422`. The message returns with status `accepted` and shows as `scheduled` on reads until it sends. Cancel it before then with the message cancel endpoint. Scheduled sends count against your plan's monthly scheduled-email allowance. Exceeding it is rejected with a `422`. A scheduled message has inline content: `scheduled_at` and `template` are mutually exclusive, and combining them is rejected with a `422`. This field is accepted only on a single send. Batch items reject it. * */ scheduled_at?: string; }; /** * Batch of email message send requests. All items are validated before any are queued. Attachments are allowed on individual messages. Each message must stay within the 20 MB estimated generated message-size cap. The serialized JSON request body for the batch has a hard 20 MB cap. * */ type EmailMessageBatchRequest = Array; type EmailMessageBatchItem = { /** * Message ID assigned to this batch item. */ readonly id: EmailId; /** * Initial status of this message in the batch. */ readonly status: "accepted"; /** * Resolved category for this batch item. */ category: "marketing" | "transactional"; /** * The template language this item asked for, in canonical form. Null when the item named no language or used no template. Every item in a batch resolves its own template reference, so this and `resolved_language` can differ from item to item. * */ readonly requested_language?: LanguageTag | null; /** * The template language this item was actually delivered in, in canonical form. Null when the item used no template. A value here differing from `requested_language` means the template did not have the language asked for and its `on_missing_language` policy chose this one. * */ readonly resolved_language?: LanguageTag | null; /** * The template this item rendered from, or null for an item that supplied its content inline. * */ readonly template_id?: EmailTemplateId | null; /** * The exact template version this item rendered from, or null for an inline item. Record it if you need to reproduce what was sent: a template's live version changes every time you submit it. * */ readonly template_version_id?: EmailTemplateVersionId | null; }; type EmailMessageBatchResponse = { /** * One entry per message in the batch, in submission order. */ data: Array; }; type RecipientId = string; /** * Envelope position of a recipient on an outbound email event. */ type RecipientRole = "to" | "cc" | "bcc"; type AudienceId = string; /** * Which identifier a contact has on file, `email` for an email address or `phone_number` for a phone number. */ type ContactIdentifierFilter = "email" | "phone_number"; type ContactId = string; /** * A compact reference to an audience, carrying its ID and display name. */ type AudienceRef = { /** * ID of the referenced audience. */ readonly id: AudienceId; /** * The audience's display name. */ name: string; }; type Contact = { /** * ID of the contact, accepted by every operation that takes a `contact_id`. */ readonly id: ContactId; /** * The contact's email address, in its stored form, trimmed and lowercased before uniqueness is checked. Unique within the workspace. `null` when the contact has no email address. */ email: string | null; /** * The contact's phone number in normalized international form: a leading `+` and four to 15 digits. We normalize formatting but do not verify the number against numbering-plan metadata. The number is unique within the workspace. Because carriers recycle disconnected numbers, use `external_id` as the durable key for your own records. `null` when the contact has no phone number. */ phone_number: string | null; /** * The contact's first name. Available in broadcast templates as `bird.contact.first_name`. */ first_name?: string | null; /** * The contact's last name. Available in broadcast templates as `bird.contact.last_name`. */ last_name?: string | null; /** * Your own identifier for this contact, such as a user ID in your system. Unique within the workspace when set. */ external_id?: string | null; /** * Custom property values for this contact, available in broadcast templates as `bird.contact.`. Each key is a property created via the contact properties API, and each value is a string, number, boolean, or RFC 3339 datetime matching the property's declared type (strings up to `500` characters). Total size is capped at 2 KB serialized. Values stored under a property that was later archived remain readable here. * */ data?: { [key: string]: unknown; }; /** * The audiences this contact belongs to, most-recently-joined first. Only present when listing contacts; omitted from every other contact operation. */ readonly audiences?: Array; } & Timestamps; type ContactCreateRequest = { /** * The contact's email address. Trimmed and lowercased before it is stored and checked for uniqueness. Unique within the workspace. Supply an email address, a phone number, or both. */ email?: string; /** * The contact's phone number in E.164 format, including the leading `+` and country code. Spaces and punctuation are accepted and stripped; the number is stored in its canonical form, which may differ from what you send, and is unique within the workspace. An empty string is treated as if the field were omitted. Supply an email address, a phone number, or both. */ phone_number?: string; /** * The contact's first name. */ first_name?: string; /** * The contact's last name. */ last_name?: string; /** * Your own identifier for this contact, such as a user ID in your system. Unique within the workspace when set. */ external_id?: string; /** * Custom property values for this contact. Each key must be an active contact property. Each value must match the property's declared type: string, number, boolean, or RFC 3339 datetime. Strings can contain up to `500` characters, and a `null` value is ignored. Unregistered or archived keys return a validation error. The serialized data is limited to 2 KB. */ data?: { [key: string]: unknown; }; }; /** * A contact identifier a batch entry can be matched on. */ type ContactMatchKey = "email" | "phone_number" | "external_id"; type ContactUpsertRequest = { /** * Contacts to create or update, matched automatically against every identifier an entry supplies. Existing contacts are updated with the fields each entry supplies; omitted fields keep their stored values, so an entry can set fields but never clear them. Unmatched entries create contacts. */ contacts: Array; /** * Audiences every contact in this request is added to. Contacts that are already members are left in place. Every listed audience must exist, or the whole request fails with a validation error and nothing is written. */ audience_ids?: Array; /** * Optional field used to match every entry to an existing contact. Every entry must include this field when set. When omitted, each entry is matched against all identifiers it supplies. No match creates a contact, one match updates it, and identifiers that match multiple contacts return an error naming each contact. */ match_on?: ContactMatchKey; /** * How a supplied `data` object is applied to an existing contact. The default `merge` mode adds the supplied keys to the contact's stored custom values. A key with a `null` value deletes that key. The `replace` mode overwrites the whole stored `data` map with the supplied map. In both modes a contact that omits `data` keeps its stored values unchanged, so an import that touches one attribute never wipes the others. * */ data_mode?: "merge" | "replace"; }; /** * The identifiers a batch entry supplied, in the normalized form used for matching. A field is `null` when the entry did not include it. These values identify the request entry and do not represent the contact's current state. */ type ContactUpsertEntry = { /** * Email address this entry carried, trimmed and lowercased. `null` when the entry carried none. */ email: string | null; /** * Phone number this entry carried, in its normalized international form. `null` when the entry carried none. A row rejected for an invalid phone echoes the value as sent, trimmed, since no normalized form exists. */ phone_number: string | null; /** * Your own identifier for this entry, when the entry supplied one. */ external_id: string | null; }; /** * Which identifier matched a batch entry to an existing contact. `null` when the entry created a new contact. */ type ContactMatchedOn = "email" | "phone_number" | "external_id" | null; type ContactUpsertError = { /** * Machine-readable error category for this entry, such as `validation_error` or `conflict_error`, in the same vocabulary as the top-level error `type`. New categories may be added over time, so treat unrecognized values as a generic failure. */ type: string; /** * Specific error code for this entry, from the same catalog as the top-level error `code`. `E04058` means the entry matched two contacts and requires review. `E04055` means the phone number belongs to another contact and you must retry with different data. Both are `conflict_error` errors; the code distinguishes them. */ code: string; /** * Human-readable explanation of why this entry failed. */ message: string; }; type ContactUpsertResultItem = { entry: ContactUpsertEntry; /** * Which identifier matched this entry to an existing contact. `null` when the entry created a new contact. */ matched_on: ContactMatchedOn; /** * What happened to this contact. * * - `created`: a new contact was created for the address. * - `updated`: an existing contact with the address was updated. * - `failed`: the entry was rejected and `error` explains why. A failed entry * does not affect the other entries in the request. * */ status: "created" | "updated" | "failed"; /** * ID of the created or updated contact. Absent when the entry failed. */ contact_id?: ContactId; /** * Why this entry failed. Absent for successful entries. */ error?: ContactUpsertError; }; type ContactUpsertResult = { /** * One entry per contact in the request, in submission order. */ data: Array; }; type ContactUpdateRequest = { /** * New email address for the contact. Trimmed and lowercased before it is stored and checked for uniqueness. Must not be in use by another contact in the workspace. Omit to keep the current address; set to `null` to remove it, as long as the contact keeps at least one identifier. */ email?: string | null; /** * New phone number for the contact, in E.164 format with the leading `+` and country code. Spaces and punctuation are accepted and stripped. Stored in its canonical form, which may differ from what you send, and unique within the workspace. Omit to keep the current number; set to `null` to remove it, as long as the contact keeps at least one identifier. An empty string behaves as `null`. */ phone_number?: string | null; /** * The contact's first name. Set to `null` to clear. */ first_name?: string | null; /** * The contact's last name. Set to `null` to clear. */ last_name?: string | null; /** * Your own identifier for this contact. Unique within the workspace when set. Set to `null` to clear. */ external_id?: string | null; /** * Custom property values to merge into the contact's existing data. Supplied keys are set, keys with a `null` value are removed, and omitted keys remain unchanged. Each key must be an active contact property. Each value must match the property's declared type: string, number, boolean, or RFC 3339 datetime. Strings can contain up to `500` characters. An unregistered or archived key returns a validation error. The serialized result is limited to 2 KB. */ data?: { [key: string]: unknown; }; }; type Audience = { /** * ID of the audience, accepted by every operation that takes an `audience_id`. */ readonly id: AudienceId; /** * Display name for the audience. */ name: string; /** * Longer description of who this audience is. */ description?: string | null; /** * How the audience's recipients are determined. `static` is an explicit member list you manage by adding and removing contacts. */ type: "static"; } & Timestamps; type ContactPropertyId = string; /** * The value type every contact must use for a property. Cannot be changed after creation. * * `datetime` values are RFC 3339 timestamps with an explicit offset. Examples include `2024-01-15T09:30:00Z` and `2024-01-15T11:30:00+02:00`. A bare date or a time with no offset is rejected. The value is normalized to UTC with second precision on write, so `2024-01-15T11:30:00+02:00` is stored and returned as `2024-01-15T09:30:00Z`, and any fractional seconds are dropped. * */ type ContactPropertyType = "string" | "number" | "boolean" | "datetime"; type ContactProperty = { /** * ID of the property, accepted by every operation that takes a `property_id`. */ readonly id: ContactPropertyId; /** * The property key, used as the key in contact data and as the attribute in the `bird.contact.` broadcast template variable. Lowercase letters, digits, and underscores, starting with a letter. Cannot be changed after creation. */ key: string; type: ContactPropertyType; /** * Default used when a contact has no value for this property and the template does not supply an inline fallback. A string, number, boolean, or RFC 3339 datetime matching the declared type (strings up to `500` characters), or `null` when no fallback is set. */ fallback_value?: unknown; /** * Whether the property is archived. An archived property is rejected in new contact writes and stops rendering in templates, but every value already stored on contacts is preserved. Reactivate it with unarchive. */ readonly archived?: boolean; } & Timestamps; type ContactPropertyCreateRequest = { /** * The property key, used as the key in contact data and as the attribute in the `bird.contact.` broadcast template variable. Lowercase letters, digits, and underscores, starting with a letter. Cannot be changed after creation. */ key: string; type: ContactPropertyType; /** * Default used when a contact has no value for this property and the template does not supply an inline fallback. A string, number, boolean, or RFC 3339 datetime matching the declared type (strings up to `500` characters), or `null` for no fallback; a value of another type returns a validation error. */ fallback_value?: unknown; }; type ContactPropertyUpdateRequest = { /** * Default used when a contact has no value for this property and the template does not supply an inline fallback. A string, number, boolean, or RFC 3339 datetime matching the declared type (strings up to `500` characters); a value of another type returns a validation error. Set to `null` to remove the fallback. */ fallback_value?: unknown; }; type AudienceCreateRequest = { /** * Display name for the audience. */ name: string; /** * Longer description of who this audience is. */ description?: string; /** * How the audience's recipients are determined. `static` is an explicit member list you manage by adding and removing contacts. */ type?: "static"; }; type AudienceUpdateRequest = { /** * New display name for the audience. Omit to keep the current name. The name cannot be cleared, and a whitespace-only value returns a validation error. */ name?: string; /** * Longer description of who this audience is. Set to null to clear. */ description?: string | null; }; type AudienceMember = { contact: Contact; /** * When this contact joined the audience. Members are listed in join order, most recent first. */ readonly joined_at: string; /** * The audiences this contact belongs to, including the one being listed, most-recently-joined first. */ readonly audiences?: Array; }; type AudienceContactsAddRequest = { /** * Contacts to add to the audience. Adding a contact that is already a member has no effect and keeps its original join time. Duplicate IDs in the list are collapsed. If any ID does not exist in the workspace, the whole request fails with a validation error and no contacts are added. */ contact_ids: Array; }; type AudienceContactsRemoveRequest = { /** * Contacts to remove from the audience. Removing a contact that is not a member has no effect. Duplicate IDs in the list are collapsed. If any ID does not exist in the workspace, the whole request fails with a validation error and no memberships are removed. */ contact_ids: Array; }; /** * Whether a message was sent from the workspace (`outbound`) or received by it (`inbound`). */ type MessageDirection = "outbound" | "inbound"; /** * Content classification. Tells Bird and carriers why you're sending; per-country compliance rules (opt-out policy, quiet hours) key on it as they roll out. */ type SmsMessageCategory = "transactional" | "marketing" | "authentication" | "service"; type SmsMessageId = string; /** * Delivery status: * * - `scheduled`: Queued for a future send time. * - `accepted`: Accepted and awaiting carrier handoff. * - `sent`: Handed to the carrier and awaiting a delivery receipt. * - `delivered`: Confirmed as delivered. * - `undelivered`: Temporarily unreachable. * - `failed`: Permanently failed. * - `rejected`: Refused before carrier handoff. * - `canceled`: Canceled before a scheduled send. * - `expired`: Reached its validity limit without a final receipt. * - `received`: Received as an inbound message. * */ type SmsMessageStatus = "scheduled" | "accepted" | "sent" | "delivered" | "undelivered" | "failed" | "rejected" | "canceled" | "expired" | "received"; /** * Segment breakdown for the message body. Segment count drives billing. */ type SmsSegments = { /** * Number of segments the body is split into. Each segment is a billable unit. */ readonly count: number; /** * Encoding used for the body. The `GSM_7BIT` encoding fits 160 characters in one segment, or 153 per part in a multi-segment message. The `UCS2` encoding applies when the body contains a character outside the GSM 03.38 alphabet, including emoji, CJK, and some accented characters. It fits 70 characters in one segment, or 67 per part in a multi-segment message. * */ readonly encoding: "GSM_7BIT" | "UCS2"; /** * Character count of the body under the selected encoding. */ readonly characters: number; }; /** * ISO 4217 three-letter currency code. */ type CurrencyCode = string; /** * What was charged for a message, split into the components that make it up. `null` until at least one component has been priced. * */ type MessageCost = { /** * Total charged, as a decimal string: the sum of the components below. Net of tax, which applies to your wallet balance rather than to an individual charge. * */ readonly amount: string; /** * ISO 4217 currency code. Every component is denominated in this currency. */ readonly currency_code: CurrencyCode; /** * What we charged to carry the message, as a decimal string. `null` when this component was not priced; `"0.00000"` when it priced at zero. * */ readonly transaction_amount: string | null; /** * Third-party fees we pass on, as a decimal string, such as US 10DLC carrier surcharges. `null` when this component was not priced; `"0.00000"` when it priced at zero. * */ readonly passthrough_amount: string | null; } | null; /** * The settings Bird applied to this message. Every option is reported, whether you set it on the send or took the default that was in force at the time. * */ type SmsMessageEffectiveOptions = { /** * Whether Bird replaced characters outside the GSM-7 alphabet in this message's body with their closest equivalent before sending it. When `true`, `text` is the body as sent and `segments` describes that body. * */ smart_encoding: boolean; }; /** * Standardized failure reason: * * - `invalid_destination`: The number is unassigned, ported out, or malformed. * - `unreachable`: The handset is off or outside coverage. * - `blocked_by_carrier`: The carrier filtered the message. * - `blocked_by_recipient`: The recipient device blocked the sender. * - `landline_unreachable`: The destination is a landline that does not accept SMS. * - `content_rejected`: The carrier rejected the content. * - `sender_unregistered`: The sender is not registered for the destination. * - `recipient_opted_out`: The recipient is on a suppression list. * - `provider_unavailable`: The provider remained unavailable after retries. * - `insufficient_balance`: The workspace wallet could not fund the send. * - `unknown`: The failure could not be classified. * * This is an open enum. Accept unrecognized values. * */ type SmsErrorCode = "invalid_destination" | "unreachable" | "blocked_by_carrier" | "blocked_by_recipient" | "landline_unreachable" | "content_rejected" | "sender_unregistered" | "recipient_opted_out" | "provider_unavailable" | "insufficient_balance" | "unknown" | (string & {}); /** * Failure detail for a message that could not be delivered or was rejected. */ type SmsError = { code: SmsErrorCode; /** * Human-readable explanation of the failure. */ description: string; /** * Raw provider-supplied error code, finer-grained than the `code` that normalizes it. Not a Bird-defined value, so quote it to support when asking why a message failed. Null when the provider sent none, including any failure decided before one was reached. */ carrier_error_code?: string | null; /** * When the failure occurred. */ occurred_at: string; } | null; type SmsMessage = { /** * ID of the message, assigned when the send is accepted. Pass it as `message_id` to the get-message endpoint. * */ readonly id: SmsMessageId; /** * Whether the message was sent from a Bird sender (`outbound`) or received from a subscriber (`inbound`). */ readonly direction: "outbound" | "inbound"; readonly status: SmsMessageStatus; /** * Where the message went. On an outbound message this is the recipient's phone number in E.164 format; on an inbound one it is your own number that received it. * */ to: string; /** * Where the message came from. On an outbound message this is the sender you sent it from: an E.164 number, an alphanumeric sender ID, or a short code. On an inbound message, this is the phone number that sent it to you. * */ from: string; /** * The message body. Every message carries body text, attachments, or both, so this is absent only on a received message that carried attachments and no text. For a template send, this is the rendered text after parameter substitution. When `category` is `authentication` (a message carrying a one-time code), this is `**REDACTED**`: the code still reaches the recipient, but the API does not retain it for later reads. * */ text?: string; /** * Content classification supplied on the send. Null for inbound messages. */ category?: SmsMessageCategory | null; /** * Segment breakdown for the body. */ segments: SmsSegments; /** * What the message cost, split into Bird's charge and any third-party fees passed through. Null until the message has been priced. */ cost?: MessageCost; /** * Structured `{name, value}` filter labels applied to this message. */ tags?: Array; /** * Arbitrary JSON metadata stored on the message and echoed in webhook payloads. */ metadata?: { [key: string]: unknown; }; /** * Settings Bird applied to this message, with any option you omitted filled in with the default that was in force when you sent it. Absent on inbound messages, and on outbound messages sent before Bird began recording these settings. * */ readonly options?: SmsMessageEffectiveOptions; /** * How long, in seconds, Bird keeps trying to deliver before the message transitions to `expired`. */ readonly validity_period?: number; /** * Carrier that handled the message. Absent until a delivery receipt identifies it, and on a received message the carrier reports it only where a carrier fee applies. */ readonly carrier?: string; /** * Mobile country code and mobile network code of the carrier. Absent until the carrier is identified. */ readonly mcc_mnc?: string; /** * Failure detail on a message that failed, was rejected, was not delivered, or expired. Absent otherwise. */ last_error?: SmsError; /** * When the message was accepted (outbound) or received (inbound). */ readonly created_at: string; /** * When the message was handed to the carrier. Null until then. */ readonly sent_at?: string | null; /** * When delivery was confirmed. Null until then. */ readonly delivered_at?: string | null; }; /** * Settings that change how Bird processes this message. Each option applies to this send only; omit one to use its default. * */ type SmsSendOptions = { /** * Replace characters outside the GSM-7 alphabet with their closest GSM-7 equivalent before sending: typically curly quotes, dashes, ellipses, fullwidth forms, and non-breaking spaces. * * One such character forces the whole body into `UCS2`, which more than halves the characters that fit in a segment, so replacing them often lowers the segment count and the cost. * * Disabled by default, because it alters the body you composed. The replacement is all-or-nothing: a body that still holds a character outside the alphabet afterwards, such as an emoji or a non-Latin script, is sent exactly as you supplied it. Read the message back to see what was applied: `text` is the body as sent. * */ smart_encoding?: boolean; /** * Preview feature: link click tracking. Defaults to `false`. Currently unavailable; setting this to `true` returns `422 SMSUnsupportedFeature`. */ track_clicks?: boolean; /** * Preview feature: per-segment price ceiling. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`. */ max_price_per_segment?: number; }; type SmsTemplateId = string; type SmsTemplateSend = unknown & { /** * The template to send, by its id. */ id?: SmsTemplateId; /** * The template to send, by its slug handle (for example `bird_otp_verification`). Browse the available templates and their variables with the templates endpoint. * */ slug?: TemplateSlug; /** * Deprecated: use `slug` instead. Resolved as a slug first, and only if that finds nothing, matched against the template's display name. * * * @deprecated */ name?: string; /** * Which of the template's languages to send. Omit it to send the template's default language, unless the template sets `language_source_required`, in which case a send naming no language is rejected. When the template does not carry the language you ask for, its own `on_missing_language` setting decides whether the closest available language is sent instead or the send is rejected. * */ language?: LanguageTag; /** * Values for the template's variables, keyed by variable name. The accepted keys and their formats are fixed per template (the template's `variables` on the templates endpoint). A missing required variable, an undeclared key, a value that does not match its variable's format, or a serialized payload over 16 KB each return a `422`. * */ parameters?: { [key: string]: unknown; }; }; type SmsMessageSendRequest = unknown & { /** * Recipient phone number in E.164 format (for example `+14155550100`). One recipient per message. The number is stored and returned in canonical E.164; a recipient that cannot be routed returns a `422` `SMSInvalidRecipient`. * */ to: string; /** * Sender to send from. Use an E.164 number such as `+15557654321`, or a short code of 5-6 digits. You can also use an alphanumeric sender ID of 1-11 letters, digits, spaces, dashes, or underscores. It must contain at least one letter, for example `MyBrand`. A numeric sender must be a number your workspace owns; an alphanumeric sender is accepted where the destination country permits one. Required on a free-text send: omitting it returns a `422` `SMSNoEligibleSender`. Not accepted alongside `template`, which selects its sender automatically. * */ from?: string; /** * Free-text message body. Required unless `template` is supplied (the two are mutually exclusive). At least 1 character, up to a 12-segment cap (roughly 1836 GSM-7 or 804 UCS-2 characters). Bird does not truncate; a body exceeding 12 segments is rejected with a 422. The cap applies to segments because GSM-7 and UCS-2 encodings differ in characters per segment. * */ text?: string; /** * Content classification. Tells Bird and carriers why you're sending; per-country compliance rules (opt-out policy, quiet hours) key on it as they roll out. Required on a free-text send; omit it on a template send, where the category is derived from the template. * */ category?: SmsMessageCategory; /** * Preview feature: how long, in seconds (60-172800), Bird keeps trying to deliver before the message transitions to `expired`. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`. * */ validity_period?: number; /** * Structured `{name, value}` labels for filtering and analytics. Tags become first-class query dimensions: filter the list endpoint by tag name, slice analytics by tag, and surface in webhook payloads. Maximum 20 tags per send. Use tags for low-cardinality dimensions (`category`, `experiment_variant`). For arbitrary structured context you do not need as a filter dimension, use `metadata` instead. * */ tags?: Array; /** * Arbitrary JSON object stored on the message, returned on API reads, and echoed in webhook payloads. Maximum 2 KB serialized. Use metadata for per-send context like internal IDs and foreign keys. For low-cardinality filterable labels, use `tags` instead. * */ metadata?: { [key: string]: unknown; }; /** * What Bird does to this message on its way out, such as `smart_encoding`. The message being relayed stays at the top level: its recipient, sender, content, and the delivery instructions the carrier acts on. * */ options?: SmsSendOptions; /** * Preview feature: multimedia (MMS) attachments. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`. */ media_urls?: Array; /** * Preview feature: sender selection from a messaging profile pool. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`. */ messaging_profile_id?: string; /** * Preview feature: send-later scheduling. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`. */ scheduled_at?: string; /** * Send using a stored template instead of free text. Mutually exclusive with `text`; the message category is derived from the template, so `from`, `category`, and `media_urls` are not accepted alongside it. * */ template?: SmsTemplateSend; /** * Preview feature: broadcast correlation. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`. */ broadcast_id?: string; /** * Preview feature: campaign correlation for analytics. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`. */ campaign_id?: string; /** * Preview feature: audience-targeted sends. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`. */ audience_id?: string; /** * Preview feature: contact-targeted sends. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`. */ contact_id?: string; /** * Preview feature: topic-gated sends. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`. */ topic_id?: string; /** * Preview feature: per-recipient substitution for batch sends. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`. */ personalization?: { [key: string]: unknown; }; }; /** * Batch of SMS message send requests. All items are validated before any are queued. */ type SmsMessageBatchRequest = Array; /** * Aggregate result for an SMS batch. */ type SmsBatchSummary = { /** * Number of messages accepted in the batch. Acceptance is all-or-nothing, so this equals the number of messages submitted. * */ accepted_count: number; }; type SmsMessageBatchResponse = { /** * One entry per message in the batch, in submission order. */ data: Array; /** * Aggregate result for the batch. */ summary: SmsBatchSummary; }; type SmsEvent = { /** * Unique identifier for this event, stable across repeated fetches of the message. */ readonly id: string; /** * Lifecycle event type. The `sms.accepted` event means the API accepted the request. The `sms.sent` event means the message reached the carrier. The `sms.delivered` event confirms delivery. The `sms.undelivered`, `sms.failed`, and `sms.expired` events describe delivery failures. The `sms.rejected` event means the message was refused before carrier handoff. This is an open enum. Accept unrecognized values. * */ readonly type: string; /** * When this event occurred. */ readonly occurred_at: string; /** * Carrier that handled the message. Present on `sms.sent` and `sms.delivered` once identified, absent otherwise. */ readonly carrier?: string; /** * Mobile country code and mobile network code of the carrier. Present on `sms.sent` and `sms.delivered` once identified, absent otherwise. */ readonly mcc_mnc?: string; /** * Failure detail. Present only on `sms.failed`, `sms.undelivered`, `sms.rejected`, and `sms.expired` events. */ error?: SmsError; }; type SmsEventList = { /** * Timeline events for this SMS message, in chronological order. The bounded timeline is returned in full and is not paginated. */ data: Array; }; /** * Whether the template is one of our built-in templates (`system`) or one your workspace created (`workspace`). Every SMS template is `system`. * */ type TemplateScope = "system" | "workspace"; /** * Where the template stands as a whole. The same five states on every channel. * * - `draft`: nothing has ever gone live. * - `pending`: nothing is live and at least one language is in review. * - `active`: at least one language is live, so something can be sent. * - `rejected`: it was reviewed and every language was refused. * - `inactive`: nothing is live and nothing is in review, so content was withdrawn or was blocked before anything went live. * * This summary answers whether the template is usable at all. A template with * one language live is `active` even while another is still drafted or refused. * Read `languages` to determine the state of each language and its reason. * * Which of the five a template can reach follows its channel's review model. A * channel whose content a third party reviews reaches all five; one whose * content goes live on publish moves between `draft`, `active` and `inactive`. * * Open enum: treat a value you do not recognize as a new one rather than as * an error. * */ type TemplateStatus$1 = "draft" | "pending" | "active" | "rejected" | "inactive" | (string & {}); /** * A single variable slot a template fills in from the values supplied when sending. The same shape on email, SMS and WhatsApp, so reading what a template needs works the same way whichever channel you are sending on. * */ type TemplateVariable = { /** * The key this slot is filled by. On email and SMS it is the key you set in the send's `parameters` object. On WhatsApp it is the `name` you repeat on the matching parameter inside `components`, or, for a template whose placeholders are positional, the position itself as `1`, `2` and so on. * */ readonly key: string; /** * The value type this slot accepts. SMS templates use the typed slots (`code`, `amount` and the rest), each of which rejects a value that does not match its `constraint`. Email and WhatsApp templates use `text`, which accepts any value. Open enum: treat an unrecognized value as a future type rather than an error. * */ readonly type: string; /** * Whether the send must supply this variable. Omitting a required value returns `422` on email, SMS, and WhatsApp sends. * */ readonly required: boolean; /** * A plain-language description of what values this variable accepts. */ readonly constraint: string; /** * Whether this slot's value is kept out of durable storage. A sensitive slot's rendered value never appears in message content read back through the API: a stand-in placeholder is stored instead. * */ readonly sensitive?: boolean; }; /** * Status of one template language on channels without third-party review. * * - `draft`: it has never been published. * - `live`: it is available to sends. * - `superseded`: a later version replaced it. * * Treat an unknown value as not sendable. * */ type TemplateLanguageStatus$1 = "draft" | "live" | "superseded" | (string & {}); /** * One language's state on a template: whether it is live for sends. Content is not here; the template carries the body of its default language, and a send resolves the rest. * */ type SmsTemplateLanguageState = { status: TemplateLanguageStatus$1; }; /** * What a send or a preview does when it asks for a language the template * cannot serve. * * `fallback` serves the closest match instead. It tries a broader form of the * same language first, so a request for `pt-BR` can be served by a stocked * `pt`, and then the template's default language. A send never fails because a * language is missing. * * `fail` rejects the send rather than serving a different language, for content * where sending the wrong language is worse than not sending at all. It matches * the requested tag or a broader form of it and refuses a sibling variant, so * `pt-BR` is never served by `pt-PT`. A send that names no language still uses * the default language. * * The default is per channel and stated on each channel's own field, because * what a wrong-language send costs differs. Where every language is separately * reviewed and separately priced, falling back silently would send content the * recipient did not expect at a rate the sender did not choose. * */ type TemplateOnMissingLanguage = "fallback" | "fail"; type SmsTemplateVersionId = string; /** * A message template: one identity holding a copy of the message per language, resolved to one at send. It declares the variable slots a send fills in, so the parts that change travel with the request and the wording does not. * */ type SmsTemplate = { /** * Unique identifier for the template. */ readonly id: SmsTemplateId; /** * The template's permanent handle. Pass it (or the id) as the template reference when sending. Handles beginning with `bird_` are reserved for our built-in templates. * */ readonly slug: TemplateSlug; /** * The template's display name, shown wherever the template is listed. Nothing resolves through it, so it is safe to show wherever a human reads the template. * */ readonly name: string; /** * What the template is for. Null when unset. */ readonly description: string | null; scope: TemplateScope; status: TemplateStatus$1; /** * Content classification applied to messages sent from this template. */ readonly category: SmsMessageCategory; /** * The template body in its default language, shown for preview. Variable placeholders appear inline (for example `{{ code }}`). Name a `language` on the send to have another one served. * */ readonly body: string; /** * The typed slots this template fills in from the values you supply in `parameters` when sending. Every language of a template declares the same slots, so this list holds for whichever one a send resolves to. * */ readonly variables: Array; /** * The language a send uses when it names none, and the last resort when `on_missing_language` is `fallback` and the language asked for is not available. * */ readonly default_language: LanguageTag; /** * The languages a send can resolve right now, as BCP-47 tags. The set may shrink for reasons other than editing, so read it rather than assuming it matches what you last saw. * */ readonly available_languages: Array; /** * Where each of the template's languages stands, keyed by BCP-47 language tag. Content is not here: `body` previews the default language, and a send resolves the one it needs. * */ readonly languages: { [key: string]: SmsTemplateLanguageState; }; /** * What a send does when it asks for a language this template does not carry. Defaults to `fallback` on SMS. * */ readonly on_missing_language: TemplateOnMissingLanguage; /** * Whether a send has to name a language. When true, a send that names none is rejected instead of being served the default language. * */ readonly language_source_required: boolean; /** * The current editable draft version, or null for a built-in `system` template, which has no draft. * */ readonly draft_version_id: SmsTemplateVersionId | null; /** * The version a send resolves to, or null for a built-in `system` template, which Bird ships ready to send rather than versioning. * */ readonly live_version_id: SmsTemplateVersionId | null; /** * Deprecated: use `live_version_id` instead, which carries the same value. * * * @deprecated */ readonly published_version_id: SmsTemplateVersionId | null; /** * The draft's revision counter. Null for a built-in `system` template, which is unversioned. * */ readonly revision: number | null; /** * When this template was last submitted. Null for a built-in `system` template, which is already available to send. * */ readonly last_submitted_at: string | null; /** * When the template was created. Null for a built-in `system` template, which Bird ships rather than stores. * */ readonly created_at: string | null; /** * When the template was last modified. Null for a built-in `system` template, which Bird ships rather than stores. * */ readonly updated_at: string | null; }; type SmsTemplateList = { /** * The templates available to your workspace. The catalog is returned in full and is not paginated. */ data: Array; }; type SmsSuppressionReasonFilter = "keyword_stop" | "carrier_opted_out" | "manual"; type SmsSuppressionId = string; /** * Reason this sender cannot message the subscriber: * * - `keyword_stop`: The subscriber sent a stop keyword. A start keyword clears it. * - `carrier_opted_out`: The carrier reported the opt-out. * - `manual`: Your workspace added the suppression through the API or dashboard. * * This is an open enum. Accept unrecognized values. * */ type SmsSuppressionReason = "keyword_stop" | "carrier_opted_out" | "manual" | (string & {}); /** * How the opt-out reached us: * * - `keyword`: an inbound message from the subscriber. * - `dlr_event`: a carrier delivery report. * - `api_key`: an API call. * - `user`: someone acting in the dashboard. * * Kept beside the reason because one reason can arrive by more than one route. * * This list grows over time, so treat an unknown value as informational rather than rejecting the * record. * */ type SmsSuppressionOrigin = "keyword" | "dlr_event" | "api_key" | "user" | (string & {}); /** * Message categories blocked for this sender and subscriber. `all` blocks every category, including authentication and transactional messages. `non_transactional` blocks marketing messages only. Responses currently use `all`. Treat unrecognized values as blocking every category. * */ type SmsSuppressionCoverage = "all" | "non_transactional" | (string & {}); /** * What ended it: * * - `keyword_start`: the subscriber texting a start keyword to the same sender. * - `api_key`: an API call. * - `user`: someone acting in the dashboard. * - `carrier_cleared`: the carrier reporting its own opt-out cleared. * * This list grows over time, so treat an unknown value as informational rather than rejecting the * record. * */ type SmsSuppressionEndReason = "keyword_start" | "api_key" | "user" | "carrier_cleared" | (string & {}); /** * One period during which a sender's messages to a subscriber are stopped: when it started, what started it, and what ended it. A subscriber who opts out, opts back in, and opts out again has three of these on record rather than one current state, and each keeps its own dates once it ends. The list returns the periods in force; fetch one by ID to read an ended one. * */ type SmsSuppression = { readonly id: SmsSuppressionId; /** * The subscriber, in E.164 format. */ destination: string; /** * The sender this stops. A suppression covers one sender, so your other senders still reach this subscriber. Opting out of one of your programs does not opt out of the others. * */ originator: string; readonly reason: SmsSuppressionReason; readonly origin: SmsSuppressionOrigin; readonly applies_to: SmsSuppressionCoverage; /** * Whether this is stopping messages right now. Always true in a list, which carries only the suppressions in force; false when you fetch one by ID that has since ended, which is also when `ended_at` is set. * */ readonly blocking: boolean; /** * The inbound message the subscriber opted out with, or the outbound message whose delivery report reported the opt-out. Null when neither applies. * */ readonly source_sms_id?: SmsMessageId | null; /** * When the subscriber opted out, as reported by whoever reported it. This is what orders one subscriber's history, and it can be earlier than `created_at` when a message reached us late. * */ readonly effective_at: string; /** * When this stopped applying. Null while it is still stopping messages. */ readonly ended_at?: string | null; /** * What ended it. Null while it is still stopping messages. */ readonly ended_reason?: SmsSuppressionEndReason | null; /** * When the subscriber opted back in, as reported. Null while it is still stopping messages. */ readonly ended_effective_at?: string | null; /** * The inbound message the subscriber opted back in with, when there was one. Null while it is still stopping messages, and when something other than a start keyword ended it. * */ readonly source_end_sms_id?: SmsMessageId | null; /** * When we recorded it. */ readonly created_at: string; /** * When we last recorded the subscriber opting out of this sender. Later than `created_at` when they texted a stop keyword again while already suppressed, which adds no new record but does earn another confirmation reply. * */ readonly last_asserted_at: string; }; /** * Stops one sender's messages to one subscriber. Both ends are required, because a suppression covers a sender-and-subscriber pair rather than a subscriber alone. * */ type SmsSuppressionCreate = { /** * The subscriber to stop messaging, in E.164 format. */ destination: string; /** * The sender to stop. Your other senders keep reaching this subscriber, so stopping every one of them means one call per sender. * */ originator: string; }; /** * Action taken when an inbound message matches the rule. `stop` unsubscribes the sender, `start` resubscribes them, `help` sends your support information, and `custom` sends the reply you configured. Built-in compliance rules fix the operation for `stop`, `start`, and `help`. This is an open enum. Accept unrecognized values. * */ type SmsKeywordOperation = "stop" | "start" | "help" | "custom" | (string & {}); /** * Whether the rule is one of Bird's defaults (`system`) or one your workspace created (`workspace`). A `workspace` rule takes precedence over Bird's default for the same country, so it is how you replace a reply without losing the keywords Bird ships. * */ type SmsKeywordRuleScope = "system" | "workspace"; /** * Identifier of a keyword rule. An `sks_` id is one of Bird's defaults, which you can read but not change; an `skw_` id is a rule your workspace created. * */ type SmsKeywordRuleId = string; type SmsKeywordRule = { id: SmsKeywordRuleId; scope: SmsKeywordRuleScope; operation: SmsKeywordOperation; /** * The country the rule applies in, as an ISO 3166-1 alpha-2 code. A rule for `NL` covers messages received on your Dutch numbers, and messages from a subscriber whose own number is Dutch whichever of your numbers they text. Rules for the country a message arrives in always outrank rules for the country its sender is in; within each, your rule wins over Bird's keywords for that country. `number` confines a rule to one number. Null means the rule applies worldwide, which is allowed for `custom` operations only. * */ country?: string | null; /** * The language this rule covers, in countries where Bird ships keywords in more than one. Canada has separate English and French rules, so a Canadian rule names which one it replaces and the other keeps Bird's reply. Null in countries with a single set. * */ language?: string | null; /** * Narrows the rule to one of your numbers in E.164 format, instead of every number you hold in the country. Null means it applies to all of them. * */ number?: string | null; /** * The keywords this rule adds. For one of Bird's defaults this is the full set Bird ships. For a rule you created it is only what you added on top. It never restates or removes Bird's keywords, so `effective_keywords` is what actually matches. * */ keywords: Array; /** * Every keyword that matches this rule: Bird's keywords for the same operation, country and language, plus the ones you added. This is what an inbound message is compared against. Keywords Bird adds later join it without you changing anything. * */ readonly effective_keywords: Array; /** * The message sent back when one of the keywords matches, except on a `confirm` rule, which never sends one. Null when the auto-reply is switched off, which `reply_disabled_at` distinguishes from a rule that has not been given one. * */ reply?: string | null; /** * Text appended to your reply that you cannot change: the rates and opt-out wording carriers require on a help response. Your reply is sent in front of it, and both count against the length a single message allows. Null when the operation carries none. * */ readonly reply_suffix?: string | null; /** * When the auto-reply for this rule was switched off, or null if it is on. Switching it off records that you send this reply from your own system, which is what Bird points to if a carrier asks why no reply went out. * */ readonly reply_disabled_at?: string | null; /** * Whether what this operation does is fixed. When true you can change the reply but not the behavior. An opt-out keyword always unsubscribes the sender, whichever rule matched it, because carriers and regulators require it. * */ readonly mandatory: boolean; /** * When the rule was created. */ readonly created_at: string; /** * When the rule was last changed. On one of Bird's defaults this is when Bird last changed the keywords or the reply for that country. */ readonly updated_at: string; }; type SmsKeywordRuleList = { /** * The keyword rules that apply to your workspace, Bird's defaults included. Ordered most specific first, so the first rule whose keywords match an inbound message is the one that runs. The set is small and returned in full; this list is not paginated. * */ data: Array; }; type SmsKeywordRuleCreate = { operation: SmsKeywordOperation; /** * The country this rule applies in, as an ISO 3166-1 alpha-2 code. It matches a message two ways: one received on any of your numbers in this country, and one sent by a subscriber whose own number is in it, wherever they text you. Rules for the country a message arrives in always outrank rules for the country its sender is in; within each, your rule wins over Bird's keywords for that country. To confine a rule to one of your numbers, set `number` instead. Required for `stop`, `start` and `help`, because those replace what Bird ships for one country and a worldwide rule would replace every country's. Omit it only for `custom`, which then applies everywhere you send. Derived from `number` when you supply an E.164 number and leave this out; a short code carries no country, so a rule for one must name it. * */ country?: string | null; /** * Which language this rule replaces, in countries where Bird ships keywords in more than one. Required there and rejected elsewhere. Listing the country's rules shows whether it applies and which languages are available. * */ language?: string | null; /** * Narrows the rule to one number you hold, in E.164 format or as a short code. Omit to cover every number you hold in the country. The number must be one of yours and able to receive messages. * */ number?: string | null; /** * Extra keywords to match, on top of the ones Bird already ships for this operation and country. Omit to keep Bird's keywords and change only the reply, including keywords Bird adds later. You cannot remove one of Bird's keywords, and a keyword Bird has bound to another operation cannot be reused here. Required for `custom`, which inherits none. * */ keywords?: Array; /** * The message to send back when a keyword matches, except on a `confirm` rule, which never sends one whatever this is set to. Set it to null together with `confirmed_self_managed` to send nothing at all. * */ reply?: string | null; /** * Set this with `reply: null` to confirm you send this reply from your own system, which switches Bird's auto-reply off for the rule. Required to send no reply, and rejected when a reply is given, so the two can never disagree. * */ confirmed_self_managed?: boolean; }; /** * Changes the reply and the added keywords. What a rule applies to (its operation, country, language and number) is fixed once created: those decide which inbound messages reach it, so changing one would make it a different rule. Delete it and create the one you want. * */ type SmsKeywordRuleUpdate = { /** * Replaces the extra keywords this rule matches, on top of the ones Bird ships. Send an empty array to keep Bird's keywords only. Omit to leave the current ones unchanged. * */ keywords?: Array; /** * Replaces the message sent back when a keyword matches, except on a `confirm` rule, which never sends one whatever this is set to. Set it to null together with `confirmed_self_managed` to switch the auto-reply off. Omit to leave it unchanged. * */ reply?: string | null; /** * Set this with `reply: null` to confirm you send this reply from your own system. Required to switch the auto-reply off, and rejected when a reply is given. * */ confirmed_self_managed?: boolean; }; /** * Set to `previous_period` to also return the same figures for the immediately preceding window of equal length, plus the change between the two, so you can show "+X% vs last period" without a second request. * */ type StatsComparePeriod = "previous_period"; /** * The window the server actually computed against. The summary serves two window grains: calendar days (bounds are YYYY-MM-DD) and hours (bounds are RFC 3339 instants on the hour). The grain of `from` and `to` mirrors the grain of the request's bounds. * */ type SmsStatsSummaryPeriod = { /** * Inclusive start of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary. */ readonly from: string; /** * Inclusive end of the window, as a calendar day (`YYYY-MM-DD`) or an RFC 3339 hour boundary. */ readonly to: string; /** * Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable. * */ readonly data_as_of?: string | null; }; /** * SMS lifecycle counts and rates for a whole period or breakdown. Counts use the message send time, so a later delivery stays attributed to the bucket in which the message was sent. Rates are null when their denominator is zero. * */ type SmsDeliveryStats = { /** * Distinct messages accepted for sending after admission checks. This is the denominator for `delivery_rate` and `failure_rate`. */ readonly accepted: number; /** * Distinct messages handed off to the carrier for delivery. */ readonly sent: number; /** * Distinct messages the carrier confirmed as delivered to the handset. */ readonly delivered: number; /** * Distinct messages the carrier reported as not delivered. */ readonly undelivered: number; /** * Distinct messages that failed during sending. */ readonly failed: number; /** * Distinct messages rejected before any send attempt, for example by sending policy or a message-generation failure. */ readonly rejected: number; /** * Distinct messages that could not be delivered within their validity window and expired. */ readonly expired: number; /** * Share of accepted messages that were delivered, computed as `delivered / accepted`. Null when no messages were accepted in scope. * */ readonly delivery_rate: number | null; /** * Share of accepted messages that ultimately failed, computed as `(undelivered + failed + expired) / accepted`. Null when no messages were accepted in scope. * */ readonly failure_rate: number | null; }; /** * Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family. All three are null when no qualifying event contributed a measurement. * */ type SmsLatencyQuantiles = { /** * Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement. */ readonly p50_ms: number | null; /** * 95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement. */ readonly p95_ms: number | null; /** * 99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement. */ readonly p99_ms: number | null; }; /** * Latency percentiles in milliseconds for the requested scope: * * - `processing`: From acceptance to carrier handoff. * - `delivery`: From carrier handoff to delivery confirmation. * - `total`: From acceptance to delivery confirmation. * * Each family is omitted when no qualifying event contributes a measurement. * Individual percentiles can also be null. * */ type SmsLatencyStats = { processing?: SmsLatencyQuantiles; delivery?: SmsLatencyQuantiles; total?: SmsLatencyQuantiles; }; /** * Changes from the previous period. A `*_pct_change` value is the signed relative change `(current - previous) / previous` and is null when the previous count is zero. A `*_rate_pp` value is the signed difference between rate fractions and is null when either rate is undefined. * */ type SmsStatsComparisonDelta = { /** * Relative change in accepted messages (`delivery.accepted`) versus the previous period, as a signed fraction. Null when the previous period accepted none. */ readonly accepted_pct_change: number | null; /** * Relative change in sent messages (`delivery.sent`) versus the previous period, as a signed fraction. Null when the previous period had none. */ readonly sent_pct_change: number | null; /** * Relative change in delivered messages (`delivery.delivered`) versus the previous period, as a signed fraction. Null when the previous period delivered none. */ readonly delivered_pct_change: number | null; /** * Relative change in undelivered messages (`delivery.undelivered`) versus the previous period, as a signed fraction. Null when the previous period had none. */ readonly undelivered_pct_change: number | null; /** * Relative change in failed messages (`delivery.failed`) versus the previous period, as a signed fraction. Null when the previous period had none. */ readonly failed_pct_change: number | null; /** * Relative change in rejected messages (`delivery.rejected`) versus the previous period, as a signed fraction. Null when the previous period had none. */ readonly rejected_pct_change: number | null; /** * Relative change in expired messages (`delivery.expired`) versus the previous period, as a signed fraction. Null when the previous period had none. */ readonly expired_pct_change: number | null; /** * Signed difference between this period's and the previous period's delivery rate, both fractions in [0,1] (multiply by 100 for percentage points). Null when either period's delivery rate is undefined. */ readonly delivery_rate_pp: number | null; /** * Signed difference between the current and previous failure-rate fractions. Multiply by 100 for percentage points. The value can fall outside `[-1, 1]` because a message can contribute to more than one failure outcome and high-volume counts are approximate. Null when either rate is undefined. * */ readonly failure_rate_pp: number | null; }; /** * The same statistics for the equal-length, inclusive period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request. * */ type SmsStatsComparison = { /** * Equal-length window ending immediately before the requested start. */ period: SmsStatsSummaryPeriod; readonly delivery: SmsDeliveryStats; readonly latency: SmsLatencyStats; readonly delta: SmsStatsComparisonDelta; }; /** * Single-row aggregate across the full requested period, covering SMS lifecycle counts plus the derived delivery and failure rates, and latency percentiles. Use this endpoint for KPI tiles and reporting; the daily and hourly endpoints carry the same counts per bucket. * * Every count is a sum of per-bucket counts across the window. Latency percentiles are computed across the whole period rather than summed per bucket. Rates are null when their denominator is zero. * */ type SmsStatsSummary = { /** * The window the response covers (echoed back from the request, day or hour grain), plus `data_as_of`, the freshness boundary the data is current to. */ period: SmsStatsSummaryPeriod; readonly delivery: SmsDeliveryStats; readonly latency: SmsLatencyStats; readonly comparison?: SmsStatsComparison; }; /** * The bucket grain of the series, either `day` or `hour`. */ type StatsGrain = "day" | "hour"; /** * The window and bucket grain the response covers, echoed from the request, plus the freshness boundary the data is current to. * */ type SmsStatsSeriesPeriod = { /** * Inclusive start of the window. A calendar day (YYYY-MM-DD) on the day grain, an RFC 3339 instant rounded to the hour on the hour grain. */ readonly from: string; /** * Inclusive end of the window. A calendar day (YYYY-MM-DD) on the day grain, an RFC 3339 instant rounded to the hour on the hour grain. */ readonly to: string; readonly grain: StatsGrain; /** * Latest time reflected in the statistics. More recent events might not be included yet. Null when the freshness boundary is unavailable. * */ readonly data_as_of?: string | null; }; /** * SMS lifecycle counts for a time bucket. Counts use the message send time, so a message accepted on Monday and delivered on Tuesday counts in Monday's bucket. Rates are available only for whole periods and breakdowns. * */ type SmsDeliveryCounts = { /** * Distinct messages accepted for sending after admission checks. */ readonly accepted: number; /** * Distinct messages handed off to the carrier for delivery. */ readonly sent: number; /** * Distinct messages the carrier confirmed as delivered to the handset. */ readonly delivered: number; /** * Distinct messages the carrier reported as not delivered. */ readonly undelivered: number; /** * Distinct messages that failed during sending. */ readonly failed: number; /** * Distinct messages rejected before any send attempt, for example by sending policy or a message-generation failure. */ readonly rejected: number; /** * Distinct messages that could not be delivered within their validity window and expired. */ readonly expired: number; }; /** * SMS lifecycle counts for one time bucket (a calendar day or hour), bucketed by send time. Every count in a bucket describes the messages accepted in it, regardless of when their later events arrived. Per-bucket values include counts only. The summary and breakdown endpoints report rates and latency as whole-window aggregates. * */ type SmsStatsPoint = { /** * The day (YYYY-MM-DD) or hour (RFC 3339, on the hour) this point covers, matching the period's grain. */ readonly bucket: string; readonly delivery: SmsDeliveryCounts; }; /** * Time-series stats payload. `period` echoes the range and bucket grain the server computed against; `data` is one row per bucket in chronological order. * */ type SmsStatsResponse = { period: SmsStatsSeriesPeriod; /** * One row per day or hour in chronological order. Buckets with no activity contain zero counts. */ readonly data: Array; }; /** * Metric to rank breakdown rows by, applied descending. Shared by the volume breakdowns whose rows carry the full delivery and latency block (originators, countries, categories, carriers). Any lifecycle count or derived rate may be used; rows whose rate is undefined (zero denominator) sort last. * */ type SmsStatsSortMetric = "accepted" | "sent" | "delivered" | "undelivered" | "failed" | "rejected" | "expired" | "delivery_rate" | "failure_rate"; /** * Bucket grain for a stats trend series. */ type StatsTrendGrain = "daily" | "hourly"; /** * Aggregate delivery and latency stats for a single originator (the sender address messages were sent from) over the requested period. */ type SmsOriginatorStatsPoint = { /** * Sender address this row aggregates, either an alphanumeric sender ID or a phone number. Matches the message `from` value. */ readonly originator: string; readonly delivery: SmsDeliveryStats; readonly latency: SmsLatencyStats; /** * Per-bucket lifecycle counts for this originator, using `trend_grain`. Includes only buckets with activity. Present when `include_trend=true`. */ readonly trend?: Array; }; /** * Per-originator breakdown for the requested period, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200). */ type SmsStatsByOriginatorResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: SmsStatsSummaryPeriod; /** * Originator breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no messages were sent in the period. */ readonly data: Array; /** * Total number of distinct originators with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Aggregate delivery and latency stats for a single destination country over the requested period. */ type SmsCountryStatsPoint = { /** * The destination country this row aggregates, as an ISO 3166-1 alpha-2 code. */ readonly country: string; readonly delivery: SmsDeliveryStats; readonly latency: SmsLatencyStats; /** * Per-bucket lifecycle counts for this country, using `trend_grain`. Includes only buckets with activity. Present when `include_trend=true`. */ readonly trend?: Array; }; /** * Per-country breakdown for the requested period, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200). */ type SmsStatsByCountryResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: SmsStatsSummaryPeriod; /** * Country breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no messages were sent in the period. */ readonly data: Array; /** * Total number of distinct destination countries with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Aggregate delivery and latency stats for a single message category over the requested period. */ type SmsCategoryStatsPoint = { /** * The category this row aggregates, as set at send time. `transactional` is one-to-one messaging triggered by a user action; `marketing` is bulk sending. New categories may be added over time. */ readonly category: string; readonly delivery: SmsDeliveryStats; readonly latency: SmsLatencyStats; /** * Per-bucket lifecycle counts for this category, using `trend_grain`. Includes only buckets with activity. Present when `include_trend=true`. */ readonly trend?: Array; }; /** * Per-category breakdown for the requested period, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200). */ type SmsStatsByCategoryResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: SmsStatsSummaryPeriod; /** * Category breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no messages were sent in the period. */ readonly data: Array; /** * Total number of distinct categories with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Metric to rank breakdown rows by, applied descending. Shared by the breakdowns whose rows carry lifecycle counts only, with no derived rates to sort on. * */ type SmsStatsLifecycleSortMetric = "accepted" | "sent" | "delivered" | "undelivered" | "failed" | "rejected" | "expired"; /** * Delivery and latency statistics for one standardized failure reason over the requested period. */ type SmsErrorCodeStatsPoint = { /** * Standardized failure reason this row aggregates. Matches the `error_code` message-list filter. */ readonly error_code: SmsErrorCode; readonly delivery: SmsDeliveryStats; readonly latency: SmsLatencyStats; /** * Per-bucket lifecycle counts for this error code, using `trend_grain`. Includes only buckets with activity. Present when `include_trend=true`. */ readonly trend?: Array; }; /** * Per-error-code breakdown for the requested period, ranked by the `sort` metric (default `failed`) descending and capped at the requested `limit` (default 50, max 200). */ type SmsStatsByErrorCodeResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: SmsStatsSummaryPeriod; /** * Error-code breakdown rows, ranked by the `sort` metric (default `failed`) descending. Empty when no delivery failures occurred in the period. */ readonly data: Array; /** * Total number of distinct error codes with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Aggregate delivery and latency stats for a single delivery carrier over the requested period. */ type SmsCarrierStatsPoint = { /** * The delivery carrier this row aggregates, as resolved for the destination handset. */ readonly carrier: string; readonly delivery: SmsDeliveryStats; readonly latency: SmsLatencyStats; /** * Per-bucket lifecycle counts for this carrier, using `trend_grain`. Includes only buckets with activity. Present when `include_trend=true`. */ readonly trend?: Array; }; /** * Per-carrier breakdown for the requested period, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200). */ type SmsStatsByCarrierResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: SmsStatsSummaryPeriod; /** * Carrier breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no messages were sent in the period. */ readonly data: Array; /** * Total number of distinct carriers with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Aggregate delivery and latency stats for a single tag (`name:value`) over the requested period. */ type SmsTagStatsPoint = { /** * The tag this row aggregates, in `name:value` form. Each distinct name-and-value pair is its own row, and a message carrying several tags is counted once under each of them, so rows do not sum to the period total. * */ readonly tag: string; readonly delivery: SmsDeliveryStats; readonly latency: SmsLatencyStats; /** * Per-bucket lifecycle-count series for this tag over the window, bucketed by `trend_grain`. Sparse, so only buckets with activity are present rather than zero-filled, unlike the daily and hourly series. Present only when `include_trend=true`. * */ readonly trend?: Array; }; /** * Per-tag breakdown for the requested period, ranked by the `sort` metric (default `accepted`) descending and capped at the requested `limit` (default 50, max 200). */ type SmsStatsByTagResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: SmsStatsSummaryPeriod; /** * Tag breakdown rows, ranked by the `sort` metric (default `accepted`) descending. Empty when no tagged messages were sent in the period. */ readonly data: Array; /** * Total number of distinct tags with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * The number of messages that ended the requested period in a single lifecycle status. This transposes the lifecycle counts into one row per status, so it carries no rates or latency. */ type SmsStatusStatsPoint = { /** * The lifecycle status this row counts. These are successive lifecycle stages. The `accepted` status was admitted for sending, `sent` was handed to the carrier, and `delivered` was confirmed by the carrier. The `undelivered`, `failed`, and `expired` statuses are failure outcomes. The `rejected` status was refused before a send attempt. Counted outcomes are a subset of the full message status vocabulary. The pre-send `scheduled`, cancellation `canceled`, and inbound-only `received` statuses are not send outcomes, so they never appear here. * */ readonly status: "accepted" | "sent" | "delivered" | "undelivered" | "failed" | "rejected" | "expired"; /** * Distinct messages that reached this lifecycle status in the period, attributed to the message's send time rather than the event's own. */ readonly count: number; }; /** * Lifecycle-status breakdown for the requested period, ordered by message count descending. Statuses with no activity are omitted. */ type SmsStatsByStatusResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: SmsStatsSummaryPeriod; /** * Status breakdown rows, one per lifecycle status with activity, ordered by count descending. Empty when no messages had activity in the period. */ readonly data: Array; /** * Number of distinct lifecycle statuses with activity in the period (at most seven). Equal to the number of rows returned, since this breakdown is never capped. */ readonly total: number; }; /** * The change from the preceding period to the requested one. The `received_pct_change` field is a signed relative change, computed as `(current - previous) / previous`. A value of `0.5` means 50% higher, and `-0.2` means 20% lower. The field is null when the previous period received none. * */ type SmsInboundStatsComparisonDelta = { /** * Relative change in received messages versus the previous period, as a signed fraction. Null when the previous period received none. */ readonly received_pct_change: number | null; }; /** * The received-message count for the equal-length, inclusive period ending immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request. * */ type SmsInboundStatsComparison = { /** * Equal-length window ending immediately before the requested start. */ period: SmsStatsSummaryPeriod; /** * Distinct messages received in the preceding period. */ readonly received: number; readonly delta: SmsInboundStatsComparisonDelta; }; /** * Total received messages over the requested period. * */ type SmsInboundStatsSummaryResponse = { period: SmsStatsSummaryPeriod; /** * Distinct messages received in the period, counted by the time the carrier received them. */ readonly received: number; readonly comparison?: SmsInboundStatsComparison; }; /** * One time bucket of received-message counts, attributed by arrival time. */ type SmsInboundStatsPoint = { /** * Start of the bucket this row covers, as a calendar day (YYYY-MM-DD) for the daily series or an hour boundary (RFC 3339) for the hourly one. */ readonly bucket: string; /** * Distinct messages received in this bucket, counted by the time the carrier received them. */ readonly received: number; }; /** * Received-message time series. `period` echoes the range and bucket grain the server computed against; `data` is one row per bucket in chronological order. * */ type SmsInboundStatsResponse = { period: SmsStatsSeriesPeriod; /** * One row per bucket (day or hour, per the grain) in the period, in chronological order. Buckets with no activity are included with a count of zero, so the series charts continuously without client-side gap handling. */ readonly data: Array; }; /** * Received-message volume for one country. * */ type SmsInboundCountryStatsPoint = { /** * The country of the Bird number the messages arrived on, as an ISO 3166-1 alpha-2 code. This identifies where the message was received. It does not identify the sender's country. */ readonly country: string; /** * Distinct messages received on numbers in this country during the period. */ readonly received: number; }; /** * Received-message volume broken down by country, ranked by volume. * */ type SmsInboundStatsByCountryResponse = { period: SmsStatsSummaryPeriod; /** * One row per country with activity in the period, most messages first, capped at the requested `limit`. A country with no messages in the period is absent rather than zero-filled, because unlike a time bucket it is not part of a continuous axis. * */ readonly data: Array; /** * Total number of distinct countries the messages arrived in with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more. */ readonly total: number; }; /** * Received-message volume for one mobile operator. * */ type SmsInboundOperatorStatsPoint = { /** * Mobile country code and mobile network code of the network the sending subscriber is on. The breakdown keys on this rather than on an operator name because the carrier reports a name only where a surcharge applies, which would leave most of the world in one unnamed bucket. */ readonly mcc_mnc: string; /** * Distinct messages received from senders on this operator during the period. */ readonly received: number; }; /** * Received-message volume broken down by operator, ranked by volume. * */ type SmsInboundStatsByOperatorResponse = { period: SmsStatsSummaryPeriod; /** * One row per operator with activity in the period, most messages first, capped at the requested `limit`. An operator with no messages in the period is absent rather than zero-filled, because unlike a time bucket it is not part of a continuous axis. * */ readonly data: Array; /** * Total number of distinct sending operators with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more. */ readonly total: number; }; /** * Received-message volume for one of your numbers. * */ type SmsInboundNumberStatsPoint = { /** * The Bird number the messages arrived on, in E.164, or the short code they were sent to. This is the same value the message resource exposes as `to`. */ readonly number: string; /** * Distinct messages received on this number during the period. */ readonly received: number; }; /** * Received-message volume broken down by number, ranked by volume. * */ type SmsInboundStatsByNumberResponse = { period: SmsStatsSummaryPeriod; /** * One row per number with activity in the period, most messages first, capped at the requested `limit`. A number with no messages in the period is absent rather than zero-filled, because unlike a time bucket it is not part of a continuous axis. * */ readonly data: Array; /** * Total number of distinct numbers with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped; raise `limit` (up to 200) or narrow the window to see more. */ readonly total: number; }; /** * Identifier of a number allocated to your workspace, as returned in the id field of `GET /v1/numbers`. */ type AllocatedNumberId = string; /** * An intelligence property you can add to a base phone number lookup. * * - `classification`: the property resolves `line_type` to its precise * allocated service (premium rate, satellite, machine-to-machine, * payphone) where the base lookup only distinguishes broad categories. * - `porting`: the property returns when the number last moved network and * its full porting record. * - `presence`: the property reports whether the number is currently live * on the network. * - `roaming`: the property reports whether it is roaming and on which * network. * - `sim_swap`: the property returns when its SIM last changed. * - `score`: the property returns a credibility score from 0 to 100. * * Each property you request is billed separately, and only when it is * delivered. * */ type LookupProperty = "classification" | "porting" | "presence" | "roaming" | "sim_swap" | "score"; type PhoneNumberLookupRequest = { /** * The phone number to look up, in international format: the country calling code, then the national number. The leading `+` is optional, and `00` works in its place, so `+31612345678`, `31612345678` and `0031612345678` are all the same number. A number written for dialling inside one country, with no country code, is rejected rather than guessed at. */ phone_number: string; /** * Properties to add to the base lookup. Omit this field or send an empty * array to request only the base lookup. * * Each delivered property is billed in addition to the base lookup. A * property that could not be answered is returned with its status and is * not billed. * */ type?: Array; }; /** * The network a number belongs to. */ type LookupNetworkInfo = { /** * The carrier's name, absent when the carrier could not be identified. */ readonly carrier_name?: string | null; /** * The mobile country code, absent for a network that has none or could not be identified. */ readonly mcc?: string | null; /** * The mobile network code, absent for a network that has none or could not be identified. */ readonly mnc?: string | null; }; /** * A notable characteristic of a number. `ported` means the number has moved * from the network that issued it to another one, so `network_info` and * `original_network_info` name different carriers. * * Open enum: more flags may be added over time, so treat an unrecognized value * as a future flag rather than an error. * */ type LookupFlag$1 = "ported" | (string & {}); /** * What kind of line the number is, as reported by the carrier platform. * * This is included in every base lookup, regardless of which properties you * request. * * - `unknown`: the carrier platform holds no classification for the * range. * - `other`: it holds a classification with no equivalent here. * - `m2m`: a range reserved for machine-to-machine traffic, belonging to no * individual subscriber. * * For the allocated service of the range at finer precision, request the * `classification` property. That answers from a different source, with its own * wider vocabulary, and is reported separately so you can always tell the two * apart. * */ type LookupLineType = "mobile" | "fixed_line" | "voip" | "toll_free" | "premium_rate" | "satellite" | "pager" | "payphone" | "m2m" | "service" | "other" | "unknown"; /** * How a requested property resolved. * * - `ok`: the property was answered and its value is in the response. * - `unavailable`: no answer arrived, so the property adds nothing: its * block is `null`, or for `classification`, `line_type` retains the value * from the base lookup. The property is not billed. * - `inconclusive`: an answer arrived but does not resolve the * property, either because the number is outside the coverage of the data * behind it or because the source returned a value this property does not * report. It is a real answer rather than a missing one, and it is not * billed either. * * Open enum: further statuses may be added over time, so treat an unrecognized * value as a future one rather than an error. Only `ok` carries a value and only * `ok` is billed, so branching on `ok` and treating everything else as "not * answered" stays correct however the vocabulary grows. * */ type LookupPropertyStatus$1 = "ok" | "unavailable" | "inconclusive" | (string & {}); /** * The allocated service of the number's range, at the precision the * intelligence source publishes it. * * This is a finer vocabulary than `line_type`, which reports what the carrier * platform alone can tell. Some values have no carrier equivalent. For example, * a number the carrier calls `service` may be classified as `premium_rate`, * `shared_cost`, `universal_access`, or a voicemail platform. Where the fields * overlap, they remain independent results rather than one refining the other. * * Three values in particular have no carrier-side concept at all: * * - `m2m` is a range reserved for machine-to-machine traffic, belonging to no * individual subscriber. * - `national_rate` is a non-geographic landline number charged above the * local rate. * - `fixed_line_or_mobile` is a range a country allocates so a number can port * between the two. * */ type LookupClassificationValue = "mobile" | "fixed_line" | "fixed_line_or_mobile" | "voip" | "toll_free" | "premium_rate" | "shared_cost" | "local_rate" | "national_rate" | "personal_number" | "universal_access" | "satellite" | "pager" | "payphone" | "m2m" | "isp" | "vpn" | "voice_mail" | "calling_cards" | "short_codes" | "service" | "other"; /** * The allocated service of the number's range. Returned when you request the * `classification` property. * * This sits beside `line_type` rather than replacing it, so you can always see * which source answered. The `line_type` property is included in the base lookup * and comes from the carrier platform. The `classification` property is * separately billed and comes from an intelligence source. * */ type LookupClassification = { readonly status: LookupPropertyStatus$1; /** * The allocated service of the range. Present only when `status` is `ok`. */ readonly value?: LookupClassificationValue; }; /** * Whether the number is live on its network right now. Returned when you * request the `presence` property. * * The result reflects a real-time query to the network where the number is * registered. * */ type LookupPresence = { readonly status: LookupPropertyStatus$1; /** * Whether the number is registered on a network and able to receive traffic. A `false` value means the network answered and reported the number as currently unreachable. This differs from the API being unable to find out. Present only when `status` is `ok`. * */ readonly reachable?: boolean; }; /** * Whether the number is roaming, and on which network. Returned when you request the `roaming` property. */ type LookupRoaming = { readonly status: LookupPropertyStatus$1; /** * Whether the number is currently roaming outside its home network. Present only when `status` is `ok`. */ readonly is_roaming?: boolean; /** * The mobile country code of the visited network. Absent when the number is not roaming or the visited network is not reported. */ readonly mcc?: string | null; /** * The mobile network code of the visited network. Absent when the number is not roaming or the visited network is not reported. */ readonly mnc?: string | null; }; /** * When the number's SIM last changed. Returned when you request the `sim_swap` property. */ type LookupSimSwap = { readonly status: LookupPropertyStatus$1; /** * When the SIM was last changed. Absent when only a recency band is known. */ readonly last_swapped_at?: string | null; /** * The lower bound, in days, of how long ago the SIM was last changed. Networks that do not release an exact date report a band instead; absent when no lower bound is known. */ readonly min_days?: number | null; /** * The upper bound, in days, of how long ago the SIM was last changed. Absent when no upper bound is known; with a lower bound present, that means the change was at least `min_days` ago. */ readonly max_days?: number | null; }; /** * One recorded move of a number between networks. */ type LookupPortingEvent = { /** * When the move was recorded, `null` when the record carries no date. */ readonly occurred_at: string | null; /** * What the record describes, as the number's registry reports it. Registries use their own short codes rather than a shared vocabulary, so treat this as a label to display rather than a value to branch on. */ readonly action: string | null; }; /** * Whether the number has ever moved network, when it last did, and its full porting record. Returned when you request the `porting` property. The base lookup only reports whether the number has ever ported, through the `ported` flag. * */ type LookupPorting = { readonly status: LookupPropertyStatus$1; /** * Whether the number has ever moved network. `false` is a positive finding rather than a lack of one: the registry was consulted and holds no move for this number. Present only when `status` is `ok`. * */ readonly ported?: boolean; /** * When the number last moved network. Absent when it has never ported or when no date is on record. */ readonly last_ported_at?: string | null; /** * Whether `last_ported_at` is an approximation. Some registries record the period of a move without its exact day. */ readonly last_ported_at_is_approximate?: boolean; /** * Every move on record, oldest first. Absent when the number has never ported or when its registry publishes no history. */ readonly history?: Array; }; /** * A credibility score for the number. Returned when you request the `score` property. */ type LookupScore = { readonly status: LookupPropertyStatus$1; /** * Credibility from 0 (low) to 100 (high). A low score means the number looks less credible than a typical subscriber line in the same range. Treat it as one signal instead of a verdict. It is a composite and is not derivable from the other properties. Present only when `status` is `ok`. * */ readonly value?: number; }; /** * Information about a phone number. * * The number, its flags, and its line type are included in the base lookup and * are always present. The country and two networks are present when identified. * Each property you request through `type` is returned in a block with the same * name and its own `status`. A property you did not request is absent. A * property that could not be answered is present with only its `status`. Each * requested property is billed only when its status is `ok`. * * Fields with no value are omitted rather than returned as `null`. * */ type PhoneNumberLookup = { /** * The number that was looked up, in E.164 format. */ readonly phone_number: string; /** * The ISO 3166-1 alpha-2 country of the number. Absent when the number belongs to no single country, as a non-geographic range does. */ readonly country_code?: CountryCode | null; /** * The network that serves the number today. Absent when no network could be identified. */ readonly network_info?: LookupNetworkInfo | null; /** * The network that issued the number's range. It differs from `network_info` when the number has been ported. Absent when the issuing network could not be identified. */ readonly original_network_info?: LookupNetworkInfo | null; /** * Notable characteristics of the number. Empty when none apply. */ readonly flags: Array; readonly line_type: LookupLineType; /** * The allocated service of the number's range. Absent unless you requested the `classification` property. */ readonly classification?: LookupClassification; /** * Whether the number is live on its network. Absent unless you requested the `presence` property. */ readonly presence?: LookupPresence; /** * Whether the number is roaming. Absent unless you requested the `roaming` property. */ readonly roaming?: LookupRoaming; /** * When the number's SIM last changed. Absent unless you requested the `sim_swap` property. */ readonly sim_swap?: LookupSimSwap; /** * The number's porting record. Absent unless you requested the `porting` property. */ readonly porting?: LookupPorting; /** * The number's credibility score. Absent unless you requested the `score` property. */ readonly score?: LookupScore; }; type EmailLookupRequest = { /** * The email address to look up. Send it exactly as you hold it. The part before the `@` is case-sensitive, so the API does not lowercase it. A display-name form such as `Aisha ` is rejected rather than unwrapped. * */ email: string; }; /** * The verdict on the address, and the one field to decide on. * * - `valid`: the address exists and accepts mail. * - `neutral`: it could not be confirmed either way, usually because the * receiving domain answers every recipient the same. * - `risky`: it probably accepts mail but is likelier than most to * bounce or complain. Examples include role, disposable, and low-reputation * addresses. * - `undeliverable`: it does not accept mail, and `reason` says why. * - `typo`: the address looks misspelled, and `did_you_mean` contains * the correction. * * Open enum: further verdicts may be added over time, so treat an unrecognized * value as a future one rather than an error. Branch on the values you know and * fall back on `delivery_confidence`, which is always present and always * comparable. * */ type EmailLookupResult$1 = "valid" | "neutral" | "risky" | "undeliverable" | "typo" | (string & {}); /** * A notable characteristic of an email address. * * - `role`: it addresses a function rather than a person (`support@`, * `info@`). Replies and consent are therefore ambiguous, and complaints are * more likely. * - `disposable`: it belongs to a throwaway-address provider and * typically stops existing. * - `free_provider`: it belongs to a consumer mailbox provider such as * Gmail or Outlook.com. This is ordinary for consumer mail and a signal when * you expected a business address. * * Open enum: more flags may be added over time, so treat an unrecognized value * as a future flag rather than an error. * */ type EmailLookupFlag$1 = "role" | "disposable" | "free_provider" | (string & {}); /** * Why an address cannot receive mail. * * - `invalid_syntax`: the address is malformed. * - `invalid_domain`: the domain does not accept mail. * - `invalid_recipient`: the domain accepts mail but this mailbox does * not exist. * * Open enum: further reasons may be added over time, so treat an unrecognized * value as a future one rather than an error. `result` is what to branch on; this * field explains it. * */ type EmailLookupReason$1 = "invalid_syntax" | "invalid_domain" | "invalid_recipient" | (string & {}); /** * Assessment of whether an email address accepts mail, the confidence and reason for that * assessment, and a suggested correction when the address appears misspelled. * * `result` is the field to decide on; `delivery_confidence` grades it, and * `flags` describes the address itself rather than its deliverability, so a * perfectly valid address can still have `role` or `disposable`. * * Fields without resolved values are omitted rather than sent as null. Every * field present in the response was resolved. * */ type EmailLookup = { /** * The address that was looked up, exactly as you sent it. */ readonly email: string; /** * Whether the address is well-formed and its domain is set up to receive mail at all. It says nothing about the mailbox itself, so a `valid` domain with no such mailbox is `true` here and `undeliverable` in `result`. */ readonly valid: boolean; readonly result: EmailLookupResult$1; /** * How likely mail to this address is to be delivered, from 0 (certain not to be) to 100 (certain to be). Read it alongside `result` rather than instead of it, because the same score can sit under `neutral` or `risky` for different reasons. */ readonly delivery_confidence: number; /** * Notable characteristics of the address. Empty when none apply. */ readonly flags: Array; /** * Why the address cannot receive mail. Absent unless `result` is `undeliverable`. */ readonly reason?: EmailLookupReason$1; /** * The address this one looks like a misspelling of. Absent unless a correction was found, which in practice means `result` is `typo`. Offer it to whoever typed the original rather than sending to it unasked, because it is a guess and the address they meant may be neither one. */ readonly did_you_mean?: string; }; type VerificationId = string; /** * Why a verification session reached its final state without succeeding: `attempts_exhausted` (too many incorrect passcodes), `ttl_elapsed` (the time window elapsed before a correct passcode), or `undeliverable` (no planned channel could deliver a passcode, so the recipient never had one to submit). Open enum: new reasons may be added over time, so treat any unrecognized value as a future reason rather than an error. */ type VerificationTerminalReason$1 = "attempts_exhausted" | "ttl_elapsed" | "undeliverable" | (string & {}); /** * The recipient to verify. Provide an `email`, a `phone_number`, or both; at least one is required. The addresses also identify the verification: a check must supply exactly the set used on the create call, so a verification created with both addresses is not found by either one alone. * */ type VerificationTo = { /** * The recipient's email address. Case does not matter; the address is lowercased before use. */ email?: string; /** * The recipient's phone number in E.164 format, with the leading `+` and country code (for example `+15551234567`). A number in any other format is rejected as an invalid recipient (`422`). */ phone_number?: string; }; /** * The channel a passcode is delivered over. Open enum: new channels may be added over time, so treat any unrecognized value as a future channel rather than an error. */ type VerificationChannel$1 = "email" | "sms" | "whatsapp" | "telegram" | (string & {}); type VerificationChannelEntry = { channel: VerificationChannel$1; }; /** * Why a passcode send did not deliver: * * - `carrier_rejected`: The SMS carrier rejected the send. * - `hard_bounce`: The email permanently bounced. * - `soft_bounce`: The email temporarily bounced, such as when a mailbox is full. * - `undelivered`: The channel reported a generic delivery failure. * - `channel_unavailable`: The channel could not be used, so the verification * moved to the next channel. * - `channel_disabled`: Sending on the channel is temporarily disabled, so the * verification moved to the next channel. * - `delivery_timeout`: No delivery confirmation arrived before the channel's * timeout, so the verification moved to the next channel. * - `not_billable`: The send could not be charged, so it was never handed to the * channel. Usually the workspace balance is too low to cover it. Topping up * the balance is what clears this. * * New reasons may be added over time. Treat unrecognized values as reasons added * later rather than errors. */ type VerificationAttemptFailureReason$1 = "carrier_rejected" | "hard_bounce" | "soft_bounce" | "undelivered" | "channel_unavailable" | "channel_disabled" | "delivery_timeout" | "not_billable" | (string & {}); /** * Meta's content classification for a template. * * - `authentication`: delivers one-time passcodes. * - `utility`: delivers transaction-triggered updates (receipts, order status). * - `marketing`: carries promotional content. * * The category determines the sender number and price. This is an open enum. * Accept unrecognized values. * */ type WhatsAppTemplateCategory$1 = "authentication" | "utility" | "marketing" | (string & {}); type Verification = { readonly id: VerificationId; /** * The verification's current state: * * - `pending`: Awaiting a correct passcode. * - `verified`: A correct passcode was submitted. * - `failed`: The verification cannot be completed. Either too many * incorrect passcodes were submitted, or no planned channel could * deliver one. Read `reason` to tell those apart. * - `expired`: The validity window elapsed before a correct passcode. * - `canceled`: The verification was canceled before completion. * - `blocked`: A fraud or abuse control stopped the verification. */ readonly status: "pending" | "verified" | "failed" | "expired" | "canceled" | "blocked"; /** * Why the verification reached its final state, or `null` while `pending` and once `verified`. See the enum for the values it can take. */ readonly reason?: VerificationTerminalReason$1 | null; readonly to: VerificationTo; /** * The channels this verification uses to deliver the passcode, in attempt order: the first entry is tried first and later entries are fallbacks. An email recipient is verified over email; a phone recipient is verified over the phone channels enabled for its destination country, in the order that country's configuration sets. */ readonly channels: Array; /** * The channel the most recent passcode was sent on, or `null` before the first send. Open enum; new channels may be added over time, so treat any unrecognized value as a future channel rather than an error. */ readonly last_channel?: string | null; /** * The key/value pairs attached when the verification was created. */ readonly metadata?: { [key: string]: unknown; }; /** * When the verification expires if no correct passcode is submitted first. After this time its status reports `expired`. */ readonly expires_at: string; /** * When the verification was completed, or `null` if it is not yet verified. */ readonly verified_at?: string | null; } & Timestamps; /** * Per-request overrides applied to this verification only. */ type VerificationOptions = { /** * Passcode length for this verification. Omit to use the configured length. */ code_length?: number; /** * Reorder or narrow the delivery channels for this request. List channel names in the order to try them; a channel you omit is not used for this request, and a channel not already enabled for the recipient is ignored. A list that leaves no usable channel fails the request with `422`. Omit the field to use the configured order. */ channels?: Array; }; type VerificationCreateRequest = { to: VerificationTo; options?: VerificationOptions; /** * Optional key/value pairs to attach to the verification, for example a correlation id. Returned on the verification. */ metadata?: { [key: string]: unknown; }; }; type VerificationCheckRequest = { to: VerificationTo; /** * The passcode the recipient received. Passcodes are numeric; submit the digits exactly as delivered. An incorrect value is a normal `200` outcome with `success: false`. It does not return an error. */ code: string; }; type VerificationCheckResult = { /** * Whether the submitted passcode verified this verification. `true` means the passcode was correct and the verification is now complete; `false` means it did not verify, and `reason` says why. A verification that has already reached a final state is no longer checkable and returns `404`. */ readonly success: boolean; /** * Why the check did not succeed: * * - `incorrect_code`: The passcode was wrong and attempts remain. * - `expired`: The validity window elapsed. * - `attempts_exhausted`: Too many incorrect attempts were submitted. * * `null` when `success` is `true`. Treat unrecognized values as reasons added * later. */ readonly reason?: string | null; verification: Verification; /** * The number of check attempts left while the verification is still pending, or `null` once it has reached a final state. */ readonly attempts_remaining?: number | null; }; type VerificationNextChannelRequest = { to: VerificationTo; }; type DomainId = string; /** * Delivery status: * * - `accepted`: Accepted and queued for sending. * - `sent`: Handed to the WhatsApp network. * - `delivered`: Confirmed as delivered to the recipient's device. * - `failed`: Permanently failed. * - `rejected`: Refused before sending and not charged. * - `received`: Received as an inbound message. * - `scheduled`: Reserved and not returned. * - `canceled`: Reserved and not returned. * * Read receipts appear in `read_at` and `whatsapp.read` events. * */ type WhatsAppMessageStatus = "scheduled" | "accepted" | "sent" | "delivered" | "failed" | "rejected" | "canceled" | "received"; type WhatsAppMessageId = string; /** * Sender or recipient of a WhatsApp message: a phone number, a business-scoped user ID, or both. */ type WhatsAppAddress = { /** * Phone number in E.164 format, when known. */ phone_number?: string; /** * Business-scoped user ID, Meta's identifier for the WhatsApp user. Present only on the WhatsApp-user side of the message. * */ bsuid?: string; }; /** * The kind of value a template parameter carries, which follows the block it fills. The `text` type is a plain string substituted into a placeholder. This includes a coupon button's code, which the recipient copies from the button. The `image`, `video`, `gif`, and `document` types carry a media header's file in `url`. Each matches its header's `format`. The `location` type fills a location header and carries a point on the map. Open enum: more kinds may be added over time. * */ type WhatsAppTemplateParameterType$1 = "text" | "image" | "video" | "gif" | "document" | "location" | (string & {}); /** * A free-form location to send: a point on the map the recipient can open in their maps app. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead. * */ type WhatsAppLocationSend = { /** * Latitude in decimal degrees. */ latitude: number; /** * Longitude in decimal degrees. */ longitude: number; /** * Name of the place, shown above the address. */ name?: string; /** * Street address of the place. Shown only when `name` is also set. */ address?: string; }; type WhatsAppMessageTemplateComponentParameter = { /** * The kind of value this parameter carries, which decides which of the fields below to send. */ type: WhatsAppTemplateParameterType$1; /** * The value substituted into the placeholder, as a plain string. Send it on a `text` parameter. */ text?: string; /** * Public `https` URL of the file a media header shows. Send it on an `image`, `video`, `gif` or `document` parameter. WhatsApp fetches it at send time, so it must still be reachable then, the same way a free-form media message's `url` must. * */ url?: string; /** * The point on the map a location header opens. Send it on a `location` parameter. */ location?: WhatsAppLocationSend; /** * Required when the template declares named parameters: the placeholder this value fills (for example `first_name`), matching exactly one of the names the template declares. Name every parameter in that case; order does not matter once names are supplied. Omit this field for a positional template, which takes its values in `{{n}}` order instead. Sending the wrong set of names, or leaving one out that the template requires, returns a `422` `WhatsAppTemplateParameterMismatch`. * */ name?: string; }; /** * The values that fill one block of one carousel card. */ type WhatsAppMessageTemplateCardComponent = { /** * Which part of the card this fills in. * * - `header`: the card's image or video. * - `body`: its text. * - `button`: a button's variable. * */ type: string; /** * The values that fill this part's placeholders, in placeholder order. */ parameters?: Array; }; /** * The values that fill one card of a carousel. Cards fill in the order the template was approved with, so send one entry per card and keep them in that order. * */ type WhatsAppMessageTemplateCard = { /** * The values that fill this card's blocks. */ components: Array; }; type WhatsAppMessageTemplateComponent = { /** * Which part of the template this fills in. * * - `body`: the main text. * - `button`: a button's variable. * - `header`: the header's text, media or location. * - `carousel`: the cards. * */ type: string; /** * The values that fill this part's placeholders. A positional template takes them in `{{n}}` placeholder order; a template with named parameters requires each parameter's `name` to match one the template declares, and order then carries no meaning. Send it on every part except `carousel`, which carries its values on `cards`. * */ parameters?: Array; /** * The values that fill each card of a carousel. Send it only on a `carousel` part. A carousel sends exactly the number of cards its template was approved with, so every card needs an entry. * */ cards?: Array; }; /** * The template a message was sent from. On reads `slug`, `language`, `category`, and `components` are always present; `components` is an empty array for an authentication template (the filled-in values, for example a verification code, are never returned). * */ type WhatsAppMessageTemplate = { /** * The template's stable handle (for example `bird_otp`). */ readonly slug: TemplateSlug; /** * Content classification applied to messages sent from this template. */ readonly category: WhatsAppTemplateCategory$1; /** * The canonical BCP-47 tag of the template variant that was sent. */ readonly language: LanguageTag; /** * The values that filled the template's placeholders. Empty for an authentication template, whose content is never returned. * */ readonly components: Array; }; /** * Text content of a WhatsApp message. */ type WhatsAppText = { /** * The message text. */ body: string; }; type WhatsAppFileId = string; /** * Fields shared by every media content object on a WhatsApp message. */ type WhatsAppMedia = { /** * ID of the stored file, to pass as `media_id` when fetching it. Absent on an outbound message, whose file we never stored. * */ readonly id?: WhatsAppFileId; /** * Where to fetch the media. On an inbound message this is a Bird URL you fetch with your API key; it is absent when the file could not be retrieved from WhatsApp. It stays populated after the stored bytes expire, and the link returns `410` from then on. On an outbound message it is the URL the sender supplied, whose availability is the sender's to guarantee. * */ url?: string; /** * Media type WhatsApp reported for the file, for example `image/jpeg`. Absent on outbound messages. * */ mime_type?: string; }; /** * Image content of a WhatsApp message. * */ type WhatsAppImage = WhatsAppMedia & { /** * Text shown beneath the image. Absent when the sender wrote none. */ caption?: string; }; /** * Video content of a WhatsApp message. * */ type WhatsAppVideo = WhatsAppMedia & { /** * Text shown beneath the video. Absent when the sender wrote none. */ caption?: string; }; /** * Audio content of a WhatsApp message. * */ type WhatsAppAudio = WhatsAppMedia & { /** * Whether this is a voice note rather than an attached audio file. A voice note auto-downloads in the WhatsApp client and can be transcribed for the recipient. * */ voice?: boolean; }; /** * Sticker content of a WhatsApp message. * */ type WhatsAppSticker = WhatsAppMedia & { /** * Whether the sticker is animated. Absent on an outbound message. * */ animated?: boolean; }; /** * Document content of a WhatsApp message. * */ type WhatsAppDocument = WhatsAppMedia & { /** * Text shown beneath the document. Absent when the sender wrote none. */ caption?: string; /** * The sender's own name for the file. */ filename?: string; }; /** * Location content of a WhatsApp message: a point on the map the recipient can open in their maps app. * */ type WhatsAppLocation = { /** * Latitude in decimal degrees. */ latitude?: number; /** * Longitude in decimal degrees. */ longitude?: number; /** * Name of the place. Absent when the sender shared a plain pin. */ name?: string; /** * Street address of the place. Shown only when `name` is also set. */ address?: string; /** * Link to the place, which WhatsApp includes mainly for business locations. Present on an inbound message when the sender's client supplied one, and absent on a message you sent, since sending a location does not support this field. * */ url?: string; }; /** * A message whose content we do not model, named so it is visible in the message log rather than arriving empty. Inbound only. * */ type WhatsAppUnsupported = { /** * The WhatsApp content type we did not model. `unsupported` is not a placeholder here: WhatsApp reports its own `unsupported` type for a message its own clients cannot render, and that arrives as this value. Open enum: WhatsApp adds content types over time, so treat an unrecognized value as a future type rather than an error. * */ type: string; }; /** * Standardized failure reason: * * - `insufficient_balance`: The workspace wallet could not fund the send. * - `price_not_found`: No price was configured for the destination and template. * - `internal_error`: An unexpected service failure occurred. * - `undeliverable`: The recipient could not be reached. * - `service_window_expired`: The 24-hour service window closed; send a template. * - `rate_limited`: The send was throttled. * - `recipient_suppressed`: The recipient is on the workspace suppression list. * - `media_rejected`: WhatsApp could not fetch the media URL, or refused the file it found there; `description` carries its reason. * * This is an open enum. Accept unrecognized values. * */ type WhatsAppErrorCode$1 = "insufficient_balance" | "price_not_found" | "internal_error" | "undeliverable" | "service_window_expired" | "rate_limited" | "recipient_suppressed" | "media_rejected" | (string & {}); /** * Failure detail for a message that could not be delivered or was rejected. */ type WhatsAppError = { code: WhatsAppErrorCode$1; /** * Human-readable explanation of the failure. */ readonly description: string; /** * Raw error code from the WhatsApp Cloud API, when available, for low-level debugging. */ readonly meta_error_code?: string | null; /** * When the failure occurred. */ readonly occurred_at: string; } | null; type WhatsAppMessage = { /** * ID of the message, assigned when the send is accepted. Pass it as `message_id` to the get-message and list-events endpoints. * */ readonly id: WhatsAppMessageId; /** * Whether the message was sent by the business (`outbound`) or received from the contact (`inbound`). */ readonly direction: "outbound" | "inbound"; /** * Sender of the message. On outbound messages, the business number it was sent from; on inbound, the WhatsApp contact. */ readonly from: WhatsAppAddress; /** * Recipient of the message. On outbound messages, the WhatsApp contact; on inbound, the business number. */ readonly to: WhatsAppAddress; /** * The template the message was sent from. For authentication templates the filled-in values are not returned. */ readonly template?: WhatsAppMessageTemplate; /** * Text the message carried. */ readonly text?: WhatsAppText; /** * Image the message carried. */ readonly image?: WhatsAppImage; /** * Video the message carried. */ readonly video?: WhatsAppVideo; /** * Audio the message carried. */ readonly audio?: WhatsAppAudio; /** * Sticker the message carried. */ readonly sticker?: WhatsAppSticker; /** * Document the message carried. */ readonly document?: WhatsAppDocument; /** * Location the message carried. */ readonly location?: WhatsAppLocation; /** * Set when the contact sent content we do not model, naming the WhatsApp content type so the message is not silently empty. Inbound only. * */ readonly unsupported?: WhatsAppUnsupported; readonly status: WhatsAppMessageStatus; /** * Failure detail for a message that did not reach the recipient. Present only when the message failed. */ last_error?: WhatsAppError; /** * When the message was accepted for delivery. */ readonly created_at: string; /** * When the message was handed to the WhatsApp network. Null until then. */ readonly sent_at?: string | null; /** * When delivery was confirmed. Null until then. */ readonly delivered_at?: string | null; /** * When the message was read by the recipient. Null until then. */ readonly read_at?: string | null; /** * What the message cost, split into Bird's charge and any third-party fees passed through. Null on an inbound message, which is never priced, on an outbound message that has not been priced yet, and on one rejected before pricing. The rate depends on the message category and the recipient's country. */ readonly cost?: MessageCost; /** * Structured `{name, value}` filter labels applied to this message. */ tags?: Array; /** * Arbitrary JSON metadata stored on the message. */ metadata?: { [key: string]: unknown; }; }; type WhatsAppTemplateId = string; type WhatsAppTemplateSend = unknown & { /** * The template to send, by its id. */ id?: WhatsAppTemplateId; /** * The template to send, by its slug handle (for example `bird_otp`). */ slug?: TemplateSlug; /** * Which of the template's languages to send, as a BCP-47 tag (for example `en` or `pt-BR`); Meta's underscore form (`pt_BR`) is accepted and normalized. Omit it to send the template's default language, unless the template sets `language_source_required`, in which case a send naming no language is rejected. When the template does not carry the language you ask for, its own `on_missing_language` setting decides whether the closest available language is sent instead or the send is rejected. The accepted message echoes the canonical BCP-47 form of the language it resolved to. * */ language?: LanguageTag; /** * The values that fill the template's placeholders: one entry per content block that has placeholders, each carrying its `parameters`. A positional template takes its parameters in `{{n}}` order; a template with named parameters requires each parameter's `name` to match one the template declares. Either way, sending parameters that do not match what the template declares returns a `422` `WhatsAppTemplateParameterMismatch`. * */ components?: Array; }; /** * Free-form text to send. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead. * */ type WhatsAppTextSend = { /** * The message text. The WhatsApp client turns any URL it contains into a clickable link. * */ body: string; /** * Whether the WhatsApp client renders a preview of the first URL in `body`. A URL must begin with `http://` or `https://`, only the first one is previewed, and the client falls back to a plain link when it cannot fetch a preview. Not returned when the message is read back, because WhatsApp does not report whether a preview rendered. * */ preview_url?: boolean; }; /** * A free-form image to send, with an optional caption. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead. * */ type WhatsAppImageSend = { /** * Public `https` URL of the image. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. JPEG and PNG only, up to 5 MB. * */ url: string; /** * Text shown beneath the image. */ caption?: string; }; /** * A free-form video to send, with an optional caption. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead. * */ type WhatsAppVideoSend = { /** * Public `https` URL of the video. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. MP4 with H.264 video and AAC audio, up to 16 MB. * */ url: string; /** * Text shown beneath the video. */ caption?: string; }; /** * Free-form audio to send, either as a voice note or as a basic audio file. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead. * */ type WhatsAppAudioSend = { /** * Public `https` URL of the audio file. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. AAC, AMR, MP3, M4A and OGG (OPUS codec, mono) are supported, up to 16 MB. * */ url: string; /** * Whether to send this as a voice note rather than a basic audio message. A voice note auto-downloads, shows the sender's profile picture, and can be transcribed for the recipient. It requires an `.ogg` file encoded with the OPUS codec; any other format makes transcription fail. Leave it false for an ordinary audio attachment. * */ voice?: boolean; }; /** * A free-form sticker to send. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead. * */ type WhatsAppStickerSend = { /** * Public `https` URL of the sticker. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. WebP only: up to 100 KB for a static sticker and 500 KB for an animated one. A sticker carries no caption. * */ url: string; }; /** * A free-form document to send, with an optional caption and filename. Deliverable only inside an open 24-hour customer service window; outside one, send a template instead. * */ type WhatsAppDocumentSend = { /** * Public `https` URL of the document. WhatsApp fetches it at send time, so it must still be reachable then: a signed URL has to outlive the send. We do not store or proxy the file. WhatsApp caches a fetched URL for 10 minutes and re-serves that copy for an identical URL sent again within the window; vary the URL to force a re-fetch. Up to 100 MB. PDF, Word, Excel, PowerPoint and plain text render reliably in the WhatsApp client; other file types are transmitted but WhatsApp does not support them. * */ url: string; /** * Text shown beneath the document. */ caption?: string; /** * Name the recipient sees, including the extension. WhatsApp derives one from the URL when you omit it. * */ filename?: string; }; /** * A WhatsApp message to send. Carry exactly one kind of content: a request with none returns a `422` `WhatsAppContentRequired`, and one carrying more than one returns a `422` `WhatsAppContentAmbiguous`. The schema does not express that constraint, because which combinations are available depends on the content types your workspace can send. * */ type WhatsAppMessageSendRequest = { /** * The message recipient: a phone number in E.164 format (for example `+31612345678`), or the recipient's business-scoped user ID (for example `US.13491208655302741918`), which addresses a WhatsApp user whose phone number you do not have. A value that is neither returns a `422` `WhatsAppInvalidRecipient`. One-time-passcode templates require a phone number and return a `422` `WhatsAppRecipientNotSupportedForTemplate` when sent to a business-scoped user ID. * */ to: string; /** * The business phone number to send from, in E.164 format. Omit it for a Bird-managed template, which selects its own number from its category: setting it there returns a `422` `WhatsAppSenderNotAllowed`. Every other send, whether free-form content of any kind or a template your workspace authored, requires it, and the number must be one this workspace owns. Omitting it returns a `422` `WhatsAppSenderRequired`, and naming a number this workspace cannot send from returns a `422` `WhatsAppSenderNotFound`. Naming a number this workspace owns but that sits on a different WhatsApp Business Account than an authored template returns a `422` `WhatsAppSenderWABAMismatch`. A number this workspace holds but has not finished connecting returns a `422` `WhatsAppSenderNotConnected`. * */ from?: string; /** * The template to send. A Bird-managed template selects the sender number from the template's category, so `from` must be omitted. A template is the only content deliverable outside a customer service window. * */ template?: WhatsAppTemplateSend; /** * Free-form text to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`. * */ text?: WhatsAppTextSend; /** * A free-form image to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`. * */ image?: WhatsAppImageSend; /** * A free-form video to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`. * */ video?: WhatsAppVideoSend; /** * Free-form audio to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`. * */ audio?: WhatsAppAudioSend; /** * A free-form sticker to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`. * */ sticker?: WhatsAppStickerSend; /** * A free-form document to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`. * */ document?: WhatsAppDocumentSend; /** * A free-form location to send instead of a template. Deliverable only inside an open 24-hour customer service window, which the contact opens by messaging or calling you and resets each time they do it again. We do not track the window, so a send outside one is accepted and then fails, with `service_window_expired` on the message's `last_error`. * */ location?: WhatsAppLocationSend; /** * Structured `{name, value}` labels for filtering. Tags become first-class query dimensions: filter the list endpoint by tag name. Maximum 20 tags per send. Use tags for low-cardinality dimensions (`category`, `experiment_variant`). For arbitrary structured context you do not need as a filter dimension, use `metadata` instead. * */ tags?: Array; /** * Arbitrary JSON object stored on the message and returned on API reads. Maximum 2 KB serialized. Use metadata for per-send context like internal IDs and foreign keys. For low-cardinality filterable labels, use `tags` instead. * */ metadata?: { [key: string]: unknown; }; }; /** * Message timeline event type: * * - `whatsapp.accepted`: The API accepted the request. * - `whatsapp.sent`: The message reached the WhatsApp network. * - `whatsapp.delivered`: Delivery to the recipient's device was confirmed. * - `whatsapp.read`: The recipient opened the message. * - `whatsapp.failed`: Delivery failed permanently. * - `whatsapp.rejected`: The message was refused before sending and not charged. * - `whatsapp.received`: An inbound message arrived from the contact. * * This is an open enum. Accept unrecognized values. * */ type WhatsAppEventType$1 = "whatsapp.accepted" | "whatsapp.delivered" | "whatsapp.failed" | "whatsapp.read" | "whatsapp.received" | "whatsapp.rejected" | "whatsapp.sent" | (string & {}); type WhatsAppEventId = string; type WhatsAppEvent = { /** * ID of the event, unique within the message's timeline. */ readonly id: WhatsAppEventId; readonly type: WhatsAppEventType$1; /** * When this event occurred. */ readonly occurred_at: string; /** * Failure detail. Present only on `whatsapp.failed` and `whatsapp.rejected` events. */ error?: WhatsAppError; }; type WhatsAppEventList = { /** * Timeline events for this WhatsApp message, in chronological order. The timeline is bounded and returned in full; this list is not paginated. */ data: Array; }; /** * The window and bucket grain the response covers, echoed from the request, plus the freshness boundary the data is current to. * */ type EmailStatsSeriesPeriod = { /** * Inclusive start of the window. A calendar day (YYYY-MM-DD, in the requested `timezone`) on the day grain. On the hour grain, an RFC 3339 UTC instant marking the start of the first hour bucket, which falls on a local hour boundary when `timezone` is set. */ readonly from: string; /** * Inclusive end of the window. A calendar day (YYYY-MM-DD, in the requested `timezone`) on the day grain. On the hour grain, an RFC 3339 UTC instant marking the start of the last hour bucket, which falls on a local hour boundary when `timezone` is set. */ readonly to: string; readonly grain: StatsGrain; /** * The instant the statistics in this response are current to: events recorded up to roughly this time are reflected, while more recent events may not be yet. Statistics are served from a rolling aggregation that refreshes every few seconds, so a response reflects data from up to a few seconds ago. Use this field to label data freshness rather than assuming the numbers are to-the-second. Null when the freshness boundary is not being reported. * */ readonly data_as_of?: string | null; }; /** * Breakdown of `bounced` by failure type, with each rate as a fraction of `bounced`. Counts are distinct bounced recipients of that type; the five types approximately partition `bounced`, so the five rates sum to roughly 1.0 when `bounced` is non-zero. * */ type EmailBounceStatsWithRates = { /** * Distinct recipients with a permanent delivery failure (invalid address or non-existent domain). */ readonly hard: number; /** * Distinct recipients with a transient delivery failure (mailbox full or server temporarily unavailable). */ readonly soft: number; /** * Distinct recipients refused by a policy at the receiving end, such as relaying denied or a blocklisted domain. */ readonly admin: number; /** * Distinct recipients bounced because the receiving mail server blocked the sending IP for reputation reasons. */ readonly block: number; /** * Distinct recipients bounced where the receiving server's response did not allow precise classification. */ readonly undetermined: number; /** * Fraction of bounced recipients that hard bounced, computed as `hard / bounced`. Null when `bounced` is zero. * */ readonly hard_rate: number | null; /** * Fraction of bounced recipients that soft bounced, computed as `soft / bounced`. Null when `bounced` is zero. * */ readonly soft_rate: number | null; /** * Fraction of bounced recipients that admin bounced, computed as `admin / bounced`. Null when `bounced` is zero. * */ readonly admin_rate: number | null; /** * Fraction of bounced recipients that block bounced, computed as `block / bounced`. Null when `bounced` is zero. * */ readonly block_rate: number | null; /** * Fraction of bounced recipients with undetermined classification, computed as `undetermined / bounced`. Null when `bounced` is zero. * */ readonly undetermined_rate: number | null; }; /** * Delivery counts and rates for the scope of the containing row (a time bucket, a breakdown dimension, or the whole period). Every count is the number of distinct recipients that reached the named lifecycle stage in scope. On the period summary, each count is the sum of the per-bucket distinct counts. Event time determines attribution; send time does not. A recipient delivered on Monday counts in Monday's row. A recipient who bounced and then succeeded on a retry can appear in both `bounced` and `delivered`. Very large counts are close estimates rather than exact tallies. * * These counts are successive lifecycle stages, so a recipient can appear in more than one: * * - `rejected`: Happens before any send attempt, from suppression, policy, or a generation failure. * - `deferred`: A temporary in-flight delay that is still being retried. * - `bounced`: A delivery failure, with its own hard, soft, admin, block, and undetermined sub-types. * - `complained`: Post-delivery spam feedback. * * Each rate is a fraction in the range 0 to 1 and is null when its denominator is zero. `accepted` is reported only where it can be attributed (time buckets and the period summary). Breakdown rows omit it. * */ type EmailDeliveryStats = { /** * Distinct recipients accepted for delivery after suppression filtering. Reported on time buckets and the period summary. Breakdown rows leave it out, because their rollups do not have it. */ readonly accepted?: number; /** * Distinct recipients whose message was processed and handed off for delivery. */ readonly processed: number; /** * Distinct recipients whose message the receiving mail server accepted. */ readonly delivered: number; /** * Distinct recipients whose delivery failed. This is approximately the sum of the five `bounces.*` sub-counts (hard, soft, admin, block, undetermined). The two totals are worked out independently, so they can differ slightly. * */ readonly bounced: number; readonly bounces: EmailBounceStatsWithRates; /** * Distinct recipients who reported the message as spam via a feedback loop. */ readonly complained: number; /** * Distinct recipients whose delivery the receiving server temporarily delayed and is still being retried. * */ readonly deferred: number; /** * Distinct recipients rejected before any delivery attempt. Includes recipients on the workspace suppression list, transmissions that could not be completed, message-generation failures, and recipients refused by sending policy. The per-recipient `rejection_reason` field on `GET /v1/email/messages/{message_id}/recipients` surfaces the specific cause. * */ readonly rejected: number; /** * Out-of-band bounce events: distinct failure notifications received after the receiving server had initially confirmed delivery. The count represents deduplicated events rather than unique recipients. * */ readonly oob_bounces: number; /** * Recipients who remain delivered after all bounce signals resolve, computed as `delivered - oob_bounces`. Use this as the base for engagement-rate denominators. Clamped to 0 when `oob_bounces` exceeds `delivered`. */ readonly effective_delivered: number; /** * Total recipients in this scope who did not receive the message, computed as `bounced + oob_bounces`. */ readonly all_bounces: number; /** * Share of this scope's delivery attempts that resulted in an out-of-band bounce, computed as `oob_bounces / (delivered + bounced)`. Null when there were no attempts. */ readonly oob_rate: number | null; /** * Share of this scope's delivery attempts that remained delivered after all bounce signals, computed as `effective_delivered / (delivered + bounced)`. Null when there were no attempts. * */ readonly delivery_rate: number | null; /** * Share of this scope's delivery attempts that ultimately failed (inband or out-of-band), computed as `all_bounces / (delivered + bounced)`. Because `oob_bounces` counts events rather than recipients, `all_bounces` can exceed the attempt count. The rate is clamped to 1. Null when there were no attempts. * */ readonly bounce_rate: number | null; /** * Spam complaints in this scope relative to effectively delivered recipients, computed as `complained / effective_delivered`. Complaints are attributed by event time, so a scope can record more of them than it effectively delivered, pushing the rate above 1. Null when `effective_delivered` is zero. * */ readonly complaint_rate: number | null; }; /** * Engagement counts and rates for the scope of the containing row (a time bucket, a breakdown dimension, or the whole period). `opens`, `opens_non_prefetched` and `clicks` count distinct engagement events (deduplicated occurrences). The `unique_*` fields count distinct recipients. `unsubscribes` counts distinct unsubscribe events. An event counts in the time bucket when it occurs, even if the message was sent in an earlier bucket. Counts are deduplicated with a scalable approximate counting method, so very large counts are close estimates rather than exact tallies. Each rate divides the counts in this scope and is null when its denominator is zero. * */ type EmailEngagementStats = { /** * Distinct open events, counting repeat opens from the same recipient and opens auto-fetched by inbox privacy features (such as Apple Mail Privacy Protection and the Gmail image proxy). * */ readonly opens: number; /** * Distinct open events excluding those auto-fetched by inbox privacy features. Same event-counting semantics as `opens` (repeat opens from the same recipient count separately), with prefetched opens removed. * */ readonly opens_non_prefetched: number; /** * Distinct recipients who opened at least once, including opens auto-fetched by inbox privacy features. */ readonly unique_opens: number; /** * Distinct recipients who opened at least once, excluding opens auto-fetched by inbox privacy features. This is the numerator used for open rate, so iOS-heavy audiences (Apple Mail Privacy Protection and similar) do not inflate it. * */ readonly unique_opens_non_prefetched: number; /** * Distinct click events, counting repeat clicks from the same recipient. */ readonly clicks: number; /** * Distinct recipients who clicked at least once. */ readonly unique_clicks: number; /** * Distinct unsubscribe events, recorded via the list-unsubscribe header or the footer link. */ readonly unsubscribes: number; /** * Distinct non-prefetched openers relative to effectively delivered recipients in the same scope, computed as `unique_opens_non_prefetched / delivery.effective_delivered`; on rows without an `effective_delivered` field (the mailbox-provider breakdowns) the denominator equals `delivery.delivered`. The numerator excludes opens auto-fetched by inbox privacy features. Opens are attributed by event time, so engagement earned by earlier deliveries can push the rate above 1. Null when the denominator is zero. * */ readonly open_rate: number | null; /** * Distinct clickers relative to effectively delivered recipients in the same scope, computed as `unique_clicks / delivery.effective_delivered` (`delivery.delivered` on rows without an `effective_delivered` field). Clicks are attributed by event time, so engagement earned by earlier deliveries can push the rate above 1. Null when the denominator is zero. * */ readonly click_rate: number | null; /** * Unsubscribe events relative to effectively delivered recipients in the same scope, computed as `unsubscribes / delivery.effective_delivered` (`delivery.delivered` on rows without an `effective_delivered` field). Unsubscribes are attributed by event time, so the rate can exceed 1. Null when the denominator is zero. * */ readonly unsubscribe_rate: number | null; }; /** * Approximate p50, p95, and p99 latency percentiles in milliseconds for one latency family over the bucket. All three are null when no qualifying event contributed a measurement. * */ type EmailLatencyQuantiles = { /** * Median (50th percentile) latency in milliseconds. Null when no qualifying event contributed a measurement. */ readonly p50_ms: number | null; /** * 95th percentile latency in milliseconds. Null when no qualifying event contributed a measurement. */ readonly p95_ms: number | null; /** * 99th percentile latency in milliseconds. Null when no qualifying event contributed a measurement. */ readonly p99_ms: number | null; }; /** * Latency percentiles (p50, p95, p99) in milliseconds for the bucket. On the summary endpoint these are computed across the whole period rather than per bucket. Three families are reported: * * - `processing`: Time from accepting the send to handing the message off for delivery. Measured per processed recipient; null when no recipient in the bucket has reached the processed stage. * - `delivery`: Time from handoff to the receiving mail server accepting the message, dominated by recipient-side delivery behavior. Measured per delivered recipient; null when no deliveries occurred in the bucket. * - `total`: End-to-end time from accepting the send to delivery, and the number most worth watching against your own delivery targets. Measured per delivered recipient; null when no deliveries occurred in the bucket. * * Each family is reported independently. A family is omitted when no qualifying * event contributed a latency measurement in the bucket. This also applies when * the workspace has not recorded latency for that stage yet. The `processing` * family can therefore be present while `delivery` and `total` are absent. A * client must handle a missing family, and a null p50/p95/p99 within a present * family, by rendering a placeholder rather than assuming a number. * */ type EmailLatencyStats = { processing?: EmailLatencyQuantiles; delivery?: EmailLatencyQuantiles; total?: EmailLatencyQuantiles; }; /** * Aggregate stats for one time bucket (a calendar day or hour, per the requested grain, in the requested `timezone` or UTC by default), bucketed by event time. Buckets with no activity are included with zero counts and null latency percentiles, so the series charts continuously without client-side gap handling. * */ type EmailStatsPoint = { /** * The day (YYYY-MM-DD, in the requested `timezone`) or hour this point covers, matching the period's grain. An hour bucket is an RFC 3339 UTC instant marking the start of the hour. It falls on a local hour boundary when `timezone` is set, which is on the UTC hour only for whole-hour offsets. */ readonly bucket: string; /** * Distinct email messages accepted in this bucket, counted at the message level (one per accepted send regardless of how many recipients it addresses). Every other metric in `delivery` and `engagement` is recipient-level or event-level. * */ readonly sends_accepted: number; readonly delivery: EmailDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailLatencyStats; }; /** * Time-series stats payload. `period` echoes the range and bucket grain actually computed against. `data` is one row per bucket in chronological order. * */ type EmailStatsResponse = { period: EmailStatsSeriesPeriod; /** * One row per bucket (day or hour, per the grain) in the period, in chronological order. Buckets with no activity are included with zero counts. */ readonly data: Array; }; /** * Metric to rank breakdown rows by, applied descending. Shared by the breakdowns whose rows have the full delivery, engagement, and latency block: tags, sending domains, categories, recipient domains, templates, and broadcasts. Any count or rate can be used. A row whose rate is undefined, because its denominator was zero, sorts last. A bounce sub-type is nested under `bounces` in each row, so its sort name reflects that, for example `bounces.hard` and `bounces.hard_rate`. `oob_bounces` is distinct from `bounced`: it counts out-of-band bounces, failure notifications that arrive after delivery was already confirmed. * */ type EmailStatsSortMetric = "processed" | "delivered" | "bounced" | "complained" | "deferred" | "rejected" | "oob_bounces" | "bounces.hard" | "bounces.soft" | "bounces.admin" | "bounces.block" | "bounces.undetermined" | "opens" | "opens_non_prefetched" | "unique_opens" | "unique_opens_non_prefetched" | "clicks" | "unique_clicks" | "unsubscribes" | "delivery_rate" | "bounce_rate" | "complaint_rate" | "open_rate" | "click_rate" | "unsubscribe_rate" | "bounces.hard_rate" | "bounces.soft_rate" | "bounces.admin_rate" | "bounces.block_rate" | "bounces.undetermined_rate"; /** * The date range this response was actually computed against. Echoed back so clients can render the period without tracking it themselves and so cached responses can be keyed by what was queried. * */ type EmailStatsPeriod = { /** * Inclusive start date the response covers (YYYY-MM-DD). */ readonly from: string; /** * Inclusive end date the response covers (YYYY-MM-DD). */ readonly to: string; /** * The instant the statistics in this response are current to: events recorded up to roughly this time are reflected, while more recent events may not be yet. Statistics are served from a rolling aggregation that refreshes every few seconds, so a response reflects data from up to a few seconds ago. Use this field to label data freshness (for example "as of 14:03") rather than assuming the numbers are to-the-second. Null when the freshness boundary is not being reported. * */ readonly data_as_of?: string | null; }; /** * One point in a breakdown row's trend series: the headline delivery and engagement rates for that row's dimension value over a single day or hour. Returned only when `include_trend=true`. The bucket grain (day or hour) follows the `trend_grain` parameter. Counts and rates are approximate at scale. * */ type EmailStatsSeriesPoint = { /** * The day (YYYY-MM-DD) or hour (ISO 8601, on the hour) this point covers, matching the requested `trend_grain`. */ readonly bucket: string; /** * Delivered recipients in this bucket. */ readonly delivered: number; /** * Bounced recipients in this bucket. */ readonly bounced: number; /** * Delivery rate for this bucket, as a fraction. Null when nothing was delivered or bounced. */ readonly delivery_rate: number | null; /** * Bounce rate for this bucket, as a fraction. Null when nothing was delivered or bounced. */ readonly bounce_rate: number | null; /** * Complaint rate for this bucket, as a fraction. Event-time attribution can push it above 1 when complaints outrun the bucket's deliveries. Null when nothing was delivered in the bucket. On a sending-IP row complaints are not attributed to the IP, so this reads 0 in buckets that had deliveries and null in buckets that had none. */ readonly complaint_rate: number | null; /** * Open rate for this bucket, as a fraction. Event-time attribution can push it above 1 when opens outrun the bucket's deliveries. Null when nothing was delivered in the bucket. On a sending-IP row engagement is not attributed to the IP, so this reads 0 in buckets that had deliveries and null in buckets that had none. */ readonly open_rate: number | null; /** * Click rate for this bucket, as a fraction. Event-time attribution can push it above 1 when clicks outrun the bucket's deliveries. Null when nothing was delivered in the bucket. On a sending-IP row engagement is not attributed to the IP, so this reads 0 in buckets that had deliveries and null in buckets that had none. */ readonly click_rate: number | null; }; /** * Aggregate delivery and engagement stats for a single tag name-and-value pair over the requested period. */ type EmailTagStatsPoint = { /** * The tag this row aggregates, formatted as `name:value` from the tag set at send time (for example `campaign:welcome-series`). Each distinct name-and-value pair is its own row. * */ readonly tag: string; readonly delivery: EmailDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailLatencyStats; /** * Per-bucket rate series for this tag over the window. Present only when `include_trend=true`. */ readonly trend?: Array; }; /** * Per-tag breakdown for the requested period, ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsTagsResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Tag breakdown rows, ranked by the `sort` metric (default `processed`) descending. Empty when no tagged sends occurred in the period. */ readonly data: Array; /** * Total number of distinct tags (name and value pairs) with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * The window this response was actually computed against. The summary serves two window grains: calendar days (bounds are YYYY-MM-DD) and hours (bounds are RFC 3339 instants). The grain of `from` and `to` mirrors the grain of the request's bounds. Days and hour boundaries follow the requested `timezone` (UTC when omitted). * */ type EmailStatsSummaryPeriod = { /** * Inclusive start of the window the response covers. A calendar day (YYYY-MM-DD, in the requested `timezone`) for day windows. For hour windows, an RFC 3339 UTC instant marking the start of the first hour, which falls on a local hour boundary when `timezone` is set. */ readonly from: string; /** * Inclusive end of the window the response covers. A calendar day (YYYY-MM-DD, in the requested `timezone`) for day windows. For hour windows, an RFC 3339 UTC instant marking the start of the last hour, which falls on a local hour boundary when `timezone` is set. */ readonly to: string; /** * The instant the statistics in this response are current to: events recorded up to roughly this time are reflected, while more recent events may not be yet. Statistics are served from a rolling aggregation that refreshes every few seconds, so a response reflects data from up to a few seconds ago. Use this field to label data freshness (for example "as of 14:03") rather than assuming the numbers are to-the-second. Null when the freshness boundary is not being reported. * */ readonly data_as_of?: string | null; }; /** * The change in each headline metric from the preceding period to the requested one. A `*_pct_change` field is a signed relative change in a count, computed as `(current - previous) / previous`, so `0.5` means 50% higher and `-0.2` means 20% lower. It is null when the previous period's count was zero because a relative change cannot be computed. A `*_rate_pp` field is the signed difference between the two periods' rate values. Each value is expressed as a fraction. A value of `0.012` means the rate rose by 1.2 percentage points. A value of `-0.003` means it fell by 0.3 points. The field is null when either period's rate is undefined, because its denominator was zero. `delivery_rate_pp` and `bounce_rate_pp` range from `-1` to `1`, because the rates behind them cannot exceed 1. The engagement deltas have no fixed bound, because events are counted when they arrive rather than when the message was sent, which can push their rate above 1. * */ type EmailStatsComparisonDelta = { /** * Relative change in accepted messages (the `sends_accepted` count) versus the previous period, as a signed fraction. Null when the previous period accepted none. */ readonly sends_accepted_pct_change: number | null; /** * Relative change in effectively delivered recipients (`delivery.effective_delivered`, the delivery-rate numerator) versus the previous period, as a signed fraction. Null when the previous period effectively delivered none. */ readonly delivered_pct_change: number | null; /** * Relative change in total bounces including out-of-band (`delivery.all_bounces`, the bounce-rate numerator) versus the previous period, as a signed fraction. Null when the previous period had none. */ readonly bounced_pct_change: number | null; /** * Relative change in spam complaints (`delivery.complained`) versus the previous period, as a signed fraction. Null when the previous period had none. */ readonly complained_pct_change: number | null; /** * Relative change in unique non-prefetched opens (`engagement.unique_opens_non_prefetched`, the same count the open rate uses) versus the previous period, as a signed fraction. Null when the previous period had none. */ readonly opened_pct_change: number | null; /** * Signed difference between this period's and the previous period's delivery rate, both fractions in [0,1] (multiply by 100 for percentage points). Null when either period's delivery rate is undefined. */ readonly delivery_rate_pp: number | null; /** * Signed difference between this period's and the previous period's open rate, both fractions (multiply by 100 for percentage points). Null when either period's open rate is undefined. */ readonly open_rate_pp: number | null; /** * Signed difference between this period's and the previous period's click rate, both fractions (multiply by 100 for percentage points). Null when either period's click rate is undefined. */ readonly click_rate_pp: number | null; /** * Signed difference between this period's and the previous period's bounce rate, both fractions in [0,1] (multiply by 100 for percentage points). Null when either period's bounce rate is undefined. */ readonly bounce_rate_pp: number | null; /** * Signed difference between this period's and the previous period's complaint rate, both fractions (multiply by 100 for percentage points). Null when either period's complaint rate is undefined. */ readonly complaint_rate_pp: number | null; /** * Signed difference between this period's and the previous period's unsubscribe rate, both fractions (multiply by 100 for percentage points). Null when either period's unsubscribe rate is undefined. */ readonly unsubscribe_rate_pp: number | null; }; /** * The same statistics for the equal-length, inclusive period ending the day immediately before the requested start, together with the change between the two periods. Present only when `compare=previous_period` is requested. The change is already computed, so a percentage difference needs no second request. * */ type EmailStatsComparison = { /** * The preceding window these comparison figures cover, the equal-length window ending immediately before the requested start (the prior day for day windows, the prior hour for hour windows). For a request covering 2026-05-01 to 2026-05-31, this is 2026-03-31 to 2026-04-30, both inclusive. */ period: EmailStatsSummaryPeriod; /** * Distinct email messages accepted in the preceding period, counted at the message level. */ readonly sends_accepted: number; readonly delivery: EmailDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailLatencyStats; readonly delta: EmailStatsComparisonDelta; }; /** * A single row that aggregates delivery and engagement counts, plus derived * rates, across the whole requested period. Use this endpoint for KPI * tiles, campaign reporting, and anywhere you need a rate with a meaningful * denominator. The daily and hourly endpoints report the same rates, but * per bucket, each one dividing that bucket's own counts. * * Every count is a sum of per-bucket counts across the window (per day for * day windows, per hour for hour windows). A recipient, or a message, that * is active in two buckets contributes to each of them, so it is counted * twice in the period total. This matches how most mailbox providers report * their own numbers. The effect to plan for is that the total is a sum of * per-bucket activity rather than a count of distinct recipients or messages * across the whole period. Latency percentiles work differently: they are computed * once across the whole period rather than summed from the buckets. A rate * is null when its denominator is zero. * */ type EmailStatsSummary = { /** * The window the response covers (echoed back from the request, day or hour grain), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsSummaryPeriod; /** * Distinct email messages accepted, counted at the message level (one per accepted send regardless of recipient count) and summed per bucket across the period. This field counts messages. `delivery.accepted` counts recipients, so the two values are not comparable (a single message to 500 recipients is 1 here and up to 500 there). */ readonly sends_accepted: number; readonly delivery: EmailDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailLatencyStats; readonly comparison?: EmailStatsComparison; }; type IpPoolId = string; /** * Delivery counts and rates for messages attributed to a single sending IP. Per-IP results omit `accepted` and `processed` counts. The sending IP becomes known only after a message is delivered, bounced, deferred, or bounced late. Those earlier lifecycle states cannot be attributed to a specific IP. Spam complaints and out-of-band bounce notifications also lack per-IP attribution on this breakdown. The `complained` and `oob_bounces` fields therefore read 0. Their rates read 0 when the denominator is non-zero and null when it is zero. The `effective_delivered` field equals `delivered`, and `all_bounces` equals `bounced`. * */ type EmailSendingIpDeliveryStats = { /** * Distinct recipients whose message the receiving mail server accepted. */ readonly delivered: number; /** * Distinct recipients whose delivery failed. This is approximately the sum of the five `bounces.*` sub-counts (hard, soft, admin, block, undetermined). The two are computed independently, so they can differ slightly. */ readonly bounced: number; /** * Distinct recipients who reported the message as spam. Complaints are not attributed to a sending IP, so this reads 0 on this breakdown. Read complaint counts from the summary or time-series statistics instead. */ readonly complained: number; /** * Distinct recipients in transient delivery deferral that is still being retried. */ readonly deferred: number; /** * Out-of-band bounce events: failure notifications received after the receiving server had initially confirmed delivery. Not attributed to a sending IP on this breakdown, so this reads 0. Workspace-wide out-of-band counts are on the summary and time-series statistics. * */ readonly oob_bounces: number; /** * Recipients on this IP who remain delivered after all bounce signals resolve, computed as `delivered - oob_bounces`. Clamped to 0 when `oob_bounces` exceeds `delivered`. */ readonly effective_delivered: number; /** * Total recipients on this IP who did not receive the message, computed as `bounced + oob_bounces`. */ readonly all_bounces: number; /** * Share of this IP's delivery attempts that resulted in an out-of-band bounce, computed as `oob_bounces / (delivered + bounced)`. Null when `delivered + bounced` is zero (no attempts). */ readonly oob_rate: number | null; readonly bounces: EmailBounceStatsWithRates; /** * Share of this IP's delivery attempts that remained delivered after all bounce signals, computed as `effective_delivered / (delivered + bounced)`. Null when `delivered + bounced` is zero. * */ readonly delivery_rate: number | null; /** * Share of this IP's delivery attempts that ultimately failed (inband or out-of-band), computed as `all_bounces / (delivered + bounced)`. Null when `delivered + bounced` is zero (no attempts). * */ readonly bounce_rate: number | null; /** * Share of effectively delivered recipients on this IP who reported the message as spam, computed as `complained / effective_delivered`. Null when `effective_delivered` is zero. * */ readonly complaint_rate: number | null; }; /** * Latency percentiles (p50, p95, p99) in milliseconds for the messages in this breakdown row, for breakdowns whose dimension is known only from delivery onward (sending IP, mailbox provider). * * - `delivery`: Time from handing the message off to the receiving mail server accepting it. Null when no deliveries occurred for this row in the period. * - `total`: End-to-end time from accepting the send to delivery. Null when no deliveries occurred for this row in the period. * * These breakdowns have no `processing` latency family. A message's row identifies which sending IP carried it or which mailbox provider received it. This becomes known only after the receiving mail server reports a delivery, bounce, deferral, or late bounce. The accept-to-processed phase ends before that attribution is known, preventing row-level processing latency. Use `GET /v1/email/stats/daily` for processing-latency percentiles across the whole workspace. * */ type EmailDeliveryLatencyStats = { delivery: EmailLatencyQuantiles; total: EmailLatencyQuantiles; }; /** * Delivery and latency stats for messages sent from a single IP address over the requested period. Per-IP attribution begins only after a message is processed: we learn which IP a message used only from its delivery, bounce, deferral, and late-bounce events. Acceptance and processing events do not contribute to per-IP attribution. As a result, per-IP rows omit the `accepted` and `processed` counts and the `processing` latency family. Those fields never appear on a per-IP row. They are not returned as null. * */ type EmailSendingIpStatsPoint = { /** * The IP address used to send messages aggregated in this row. */ readonly sending_ip: string; /** * The dedicated IP pool this address sent through, or null when the messages went through the shared pool. Recorded when each message was sent, so it reflects the pool used at send time even if the IP has since moved between pools or been released. * */ readonly ip_pool_id?: IpPoolId | null; readonly delivery: EmailSendingIpDeliveryStats; readonly latency: EmailDeliveryLatencyStats; /** * Per-bucket delivery-rate series for this IP over the window. Present only when `include_trend=true`. Engagement is not attributed to a sending IP, so each point's open and click rates read 0 in buckets with deliveries and null in buckets without. */ readonly trend?: Array; }; /** * Per-sending-IP breakdown for the requested period, ranked by the `sort` metric (default `delivered`) descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsBySendingIpResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Sending-IP breakdown rows, ranked by the `sort` metric (default `delivered`) descending. Empty when no per-IP-attributable activity (delivery, bounce, deferral, or late bounce) occurred in the period. */ readonly data: Array; /** * Total number of distinct sending IP addresses with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Aggregate delivery, engagement, and latency stats for messages sent from a single sending domain over the requested period. */ type EmailSendingDomainStatsPoint = { /** * The sending domain (the portion of the `From` address after the `@`), normalized to lowercase. */ readonly sending_domain: string; readonly delivery: EmailDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailLatencyStats; /** * Per-bucket rate series for this sending domain over the window. Present only when `include_trend=true`. */ readonly trend?: Array; }; /** * Per-sending-domain breakdown for the requested period, ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsBySendingDomainResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Sending-domain breakdown rows, ranked by the `sort` metric (default `processed`) descending. Empty when no eligible activity occurred in the period. */ readonly data: Array; /** * Total number of distinct sending domains with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Aggregate delivery and engagement stats for a single category over the requested period. */ type EmailCategoryStatsPoint = { /** * The category this row aggregates, as set at send time. `transactional` is one-to-one mail triggered by a user action. `marketing` is bulk sending. New categories may be added over time. */ readonly category: string; readonly delivery: EmailDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailLatencyStats; /** * Per-bucket rate series for this category over the window. Present only when `include_trend=true`. */ readonly trend?: Array; }; /** * Per-category breakdown for the requested period, ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsByCategoryResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Category breakdown rows, ranked by the `sort` metric (default `processed`) descending. Empty when no sends occurred in the period. */ readonly data: Array; /** * Total number of distinct categories with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Metric to rank rows by, applied descending. Shared by every breakdown whose attribution begins at delivery, so `processed`, `rejected`, and `oob_bounces` are not part of those rows and are not sortable. Any count or rate on the row can be used; rows whose rate is undefined (zero denominator) sort last. Bounce sub-types use their nested location in each row, for example `bounces.hard` and `bounces.hard_rate`. * */ type EmailMailboxProviderSortMetric = "delivered" | "bounced" | "complained" | "deferred" | "bounces.hard" | "bounces.soft" | "bounces.admin" | "bounces.block" | "bounces.undetermined" | "opens" | "opens_non_prefetched" | "unique_opens" | "unique_opens_non_prefetched" | "clicks" | "unique_clicks" | "unsubscribes" | "delivery_rate" | "bounce_rate" | "complaint_rate" | "open_rate" | "click_rate" | "unsubscribe_rate" | "bounces.hard_rate" | "bounces.soft_rate" | "bounces.admin_rate" | "bounces.block_rate" | "bounces.undetermined_rate"; /** * Delivery counts and rates for messages attributed to a single recipient mailbox provider. Per-provider results do not include `accepted` or `processed` counts, because we only learn the recipient's mailbox provider once the receiving mail server reports delivery, a bounce, a deferral, or a late bounce. Earlier lifecycle states (accepted, processed) cannot be attributed to a specific provider. * */ type EmailMailboxProviderDeliveryStats = { /** * Distinct recipients whose message the receiving mail server accepted. */ readonly delivered: number; /** * Distinct recipients whose delivery failed. Approximately the sum of the five `bounces.*` sub-counts (hard, soft, admin, block, undetermined); the totals are computed independently so they may differ slightly at the approximation error. */ readonly bounced: number; /** * Distinct recipients who reported the message as spam. */ readonly complained: number; /** * Distinct recipients in transient delivery deferral that is still being retried. */ readonly deferred: number; readonly bounces: EmailBounceStatsWithRates; /** * Share of attempted recipients on this mailbox provider that were delivered, computed as `delivered / (delivered + bounced)`. Null when `delivered + bounced` is zero (no attempts). * */ readonly delivery_rate: number | null; /** * Share of attempted recipients on this mailbox provider that bounced, computed as `bounced / (delivered + bounced)`. Null when `delivered + bounced` is zero (no attempts). * */ readonly bounce_rate: number | null; /** * Share of delivered recipients on this mailbox provider who reported the message as spam, computed as `complained / delivered`. Null when `delivered` is zero. * */ readonly complaint_rate: number | null; }; /** * Delivery, engagement, and deliverability stats for messages grouped by a single recipient mailbox provider (`gmail`, `microsoft`, `yahoo`, `apple`, ...) over the requested period. We learn a recipient's mailbox provider from the receiving mail server. Per-provider rows therefore cover the delivery stage onward. They omit the `accepted` and `processed` counts and the `processing` latency family. These fields are absent rather than null. Engagement (opens and clicks, and their rates) is included because those events happen after delivery, once the mailbox provider is already known. * */ type EmailMailboxProviderStatsPoint = { /** * The recipient mailbox provider this row aggregates, as a lowercase classifier such as `gmail`, `yahoo`, `microsoft`, or `apple`. New classifiers may be added over time. */ readonly mailbox_provider: string; readonly delivery: EmailMailboxProviderDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailDeliveryLatencyStats; /** * Per-bucket rate series for this mailbox provider over the window. Present only when `include_trend=true`. */ readonly trend?: Array; }; /** * Per-mailbox-provider breakdown for the requested period, ranked by the `sort` metric (default `delivered`) descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsByMailboxProviderResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Mailbox-provider breakdown rows, ranked by the `sort` metric (default `delivered`) descending. Empty when no eligible activity occurred in the period. */ readonly data: Array; /** * Total number of distinct mailbox providers with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Delivery, engagement, and deliverability stats for messages grouped by a single mailbox provider and provider region pair over the requested period, for example `gmail` in `NA` or `microsoft` in `EU`. The provider region is the regional pod the receiving mail system reports for the recipient's provider; pairing it with the provider disambiguates a region label that several providers share. Like the mailbox-provider breakdown, rows cover the delivery stage onward: the `accepted` and `processed` counts and the `processing` latency family are omitted (a provider region cannot be attributed before delivery). * */ type EmailMailboxProviderRegionStatsPoint = { /** * The recipient mailbox provider this row aggregates, as a lowercase classifier such as `gmail`, `yahoo`, `microsoft`, or `apple`. */ readonly mailbox_provider: string; /** * The provider region this row aggregates, as reported by the receiving mail system (for example `NA`, `EU`, `APAC`). The set is open and provider-specific. */ readonly mailbox_provider_region: string; readonly delivery: EmailMailboxProviderDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailDeliveryLatencyStats; /** * Per-bucket rate series for this provider region over the window. Present only when `include_trend=true`. */ readonly trend?: Array; }; /** * Per-(mailbox provider, provider region) breakdown for the requested period, ranked by the `sort` metric (default `delivered`) descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsByMailboxProviderRegionResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Provider-region breakdown rows, ranked by the `sort` metric (default `delivered`) descending. Empty when no deliveries occurred in the period. */ readonly data: Array; /** * Total number of distinct mailbox provider and region pairs with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Aggregate delivery, engagement, and latency stats for messages sent to a single recipient mailbox domain over the requested period. */ type EmailRecipientDomainStatsPoint = { /** * The recipient mailbox domain this row aggregates (the part of the recipient address after the `@`), normalized to lowercase. */ readonly recipient_domain: string; readonly delivery: EmailDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailLatencyStats; /** * Per-bucket rate series for this recipient domain over the window. Present only when `include_trend=true`. */ readonly trend?: Array; }; /** * Per-recipient-domain breakdown for the requested period, ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsByRecipientDomainResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Recipient-domain breakdown rows, ranked by the `sort` metric (default `processed`) descending. Empty when no eligible activity occurred in the period. */ readonly data: Array; /** * Total number of distinct recipient domains with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Delivery, engagement, and latency numbers for every message sent with one template over the requested period. */ type EmailTemplateStatsPoint = { /** * The template this row is about, using the same `id` the email template endpoints return. Only messages sent with a template appear in this breakdown at all. If the template was deleted after it was used to send, this row still appears, keyed by that same `id`. * */ readonly template_id: EmailTemplateId; readonly delivery: EmailDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailLatencyStats; /** * A short series of this template's delivery and engagement rates, one point per time bucket over the window. Only present when you set `include_trend=true` on the request. * */ readonly trend?: Array; }; /** * Per-template breakdown for the requested period, ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsByTemplateResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Template breakdown rows, ranked by the `sort` metric (default `processed`) descending. Empty when no messages were sent with a template in the period. */ readonly data: Array; /** * Total number of distinct templates with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Metric to rank rows by, applied descending. Shared by the engagement-only breakdowns (locations, email clients), which report open and click counts but no rates because delivery events provide no per-dimension denominator. * */ type EmailEngagementSortMetric = "opens" | "opens_non_prefetched" | "unique_opens" | "unique_opens_non_prefetched" | "clicks" | "unique_clicks"; /** * Open and click counts for a breakdown row whose dimension is resolved from engagement events only. `opens`, `opens_non_prefetched`, and `clicks` count each event. The same recipient opening or clicking more than once counts each time. The `unique_*` fields count distinct recipients instead, so a recipient who opened five times only counts once there. Rates and unsubscribe counts are not included here. A per-dimension delivered count is unavailable as a rate's denominator, and an unsubscribe event has none of the information this breakdown is grouped by, so it cannot be placed on a row. * */ type EmailEngagementCounts = { /** * Distinct open events, counting repeat opens from the same recipient and opens auto-fetched by inbox privacy features (such as Apple Mail Privacy Protection and the Gmail image proxy). * */ readonly opens: number; /** * Distinct open events excluding those auto-fetched by inbox privacy features. Same event-counting semantics as `opens`, with prefetched opens removed. * */ readonly opens_non_prefetched: number; /** * Distinct recipients who opened at least once, including opens auto-fetched by inbox privacy features. */ readonly unique_opens: number; /** * Distinct recipients who opened at least once, excluding opens auto-fetched by inbox privacy features. */ readonly unique_opens_non_prefetched: number; /** * Distinct click events, counting repeat clicks from the same recipient. */ readonly clicks: number; /** * Distinct recipients who clicked at least once. */ readonly unique_clicks: number; }; /** * Open and click counts for messages engaged with from a single location over the requested period. Location is resolved from open and click events only, so this breakdown reports engagement activity: opens, clicks, and the recipients behind them. It has no delivery counts and no open or click rates, because the receiving mail server reports delivery without a recipient location, preventing a per-location delivered denominator and rates. Each row always includes all three of `country`, `region`, and `city`; the levels below the requested `group_by` are null. * */ type EmailLocationStatsPoint = { /** * The country this row aggregates, as a two-letter country code (ISO 3166-1 alpha-2) resolved from the open or click event. Always present. */ readonly country: string; /** * The region (state or province) within the country. Populated when `group_by` is `region` or `city`; null at coarser groupings. */ readonly region: string | null; /** * The city within the region. Populated when `group_by` is `city`; null at coarser groupings. */ readonly city: string | null; readonly engagement: EmailEngagementCounts; }; /** * Per-location engagement breakdown for the requested period, grouped at the requested `group_by` granularity, ranked by the `sort` metric (default `unique_opens`) descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsByLocationResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Location breakdown rows, ranked by the `sort` metric (default `unique_opens`) descending. Empty when no opens or clicks with a resolved location occurred in the period. */ readonly data: Array; /** * Total number of distinct locations at the requested `group_by` level with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Engagement counts for messages opened or clicked from a single email client, operating system, or device type over the requested period. The reading environment is resolved from open and click events only, so this breakdown reports engagement activity: opens, clicks, and the recipients behind them. It has no delivery counts and no open or click rates, because the receiving mail server reports delivery without a client or device, preventing a per-client delivered denominator and rates. Exactly one of `email_client`, `os`, and `device_type` is populated, selected by the request's `group_by`. The other two are null. Inbox-privacy prefetching also affects the detected client. As with open counts, `opens_non_prefetched` excludes opens auto-fetched by an inbox privacy feature. It includes opens caused by a person opening the message. * */ type EmailClientStatsPoint = { /** * The mail client this row aggregates (for example `Gmail`, `Apple Mail`, `Outlook`). Populated only when `group_by=email_client`. Null otherwise. */ readonly email_client: string | null; /** * The operating system this row aggregates (for example `iOS`, `Android`, `Windows`, `macOS`). Populated only when `group_by=os`. Null otherwise. */ readonly os: string | null; /** * The device type this row aggregates (for example `mobile`, `desktop`, `tablet`). Populated only when `group_by=device_type`. Null otherwise. */ readonly device_type: string | null; readonly engagement: EmailEngagementCounts; }; /** * Per-client engagement breakdown for the requested period, grouped by the requested `group_by` facet, ranked by the `sort` metric (default `unique_opens`) descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsByClientResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Client breakdown rows, ranked by the `sort` metric (default `unique_opens`) descending. Empty when no opens or clicks with a detected client occurred in the period. */ readonly data: Array; /** * Total number of distinct values of the requested `group_by` facet with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Breakdown of `bounced` by failure type. Each field counts distinct bounced recipients of that type in this row's scope; the five types approximately partition `bounced`. * */ type EmailBounceStats = { /** * Distinct recipients with a permanent delivery failure (invalid address or non-existent domain). The address is automatically added to the suppression list. * */ readonly hard: number; /** * Distinct recipients with a transient delivery failure (mailbox full or server temporarily unavailable). Delivery was retried. * */ readonly soft: number; /** * Distinct recipients refused by a policy at the receiving end, such as relaying denied or a blocklisted domain. Fix these by changing your content or sender configuration. Cleaning the recipient list does not usually help. * */ readonly admin: number; /** * Distinct recipients bounced because the receiving mail server blocked the sending IP for reputation reasons (mail block, spam block, spam content). Triage usually focuses on IP reputation and sending volume. * */ readonly block: number; /** * Distinct recipients bounced where the receiving server's response did not allow precise classification. * */ readonly undetermined: number; }; /** * Bounce counts for a single SMTP status code over the requested period, with the per-type breakdown. This is a deliverability-debugging view keyed on what the receiving mail server returned, so it only reports the failure side: bounced recipients, and their `hard`, `soft`, `admin`, `block`, and `undetermined` split. It has no delivered, open, or rate fields. * */ type EmailBounceCodeStatsPoint = { /** * The SMTP error code the receiving mail server returned for these bounces, as reported by that server (for example `5.1.1` for an unknown recipient, `4.2.2` for a full mailbox). The form varies by server, and the set of codes is open. */ readonly smtp_error_code: string; /** * Distinct recipients whose delivery failed with this SMTP status code, approximately equal to the sum of the five `bounces.*` sub-counts. The two are computed independently, so they can differ slightly because of approximation. */ readonly bounced: number; readonly bounces: EmailBounceStats; }; /** * Per-SMTP-code bounce breakdown for the requested period, ranked by the `sort` metric (default `bounced`) descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsByBounceCodeResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Bounce-code breakdown rows, ranked by the `sort` metric (default `bounced`) descending. Empty when no bounces occurred in the period. */ readonly data: Array; /** * Total number of distinct SMTP error codes with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Complaint counts for a single feedback-loop complaint type over the requested period. A complaint type is recorded only on spam-complaint events, so this breakdown reports the complained count for each type and nothing else. A complaint event has no delivery or engagement information attached to it, so no other count applies. * */ type EmailComplaintTypeStatsPoint = { /** * The complaint classification reported by the mailbox provider's feedback loop, in the abuse-reporting-format vocabulary (for example `abuse`, `fraud`, `virus`, `other`). The set is open. */ readonly feedback_type: string; /** * Distinct recipients who reported a message as spam with this complaint type at any point in the period. */ readonly complained: number; }; /** * Per-complaint-type breakdown for the requested period, ranked by `complained` descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsByComplaintTypeResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Complaint-type breakdown rows, ranked by `complained` descending. Empty when no complaints occurred in the period. */ readonly data: Array; /** * Total number of distinct feedback types with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Delivery, engagement and latency figures for one broadcast's messages over the period you asked for. */ type EmailBroadcastStatsPoint = { /** * The broadcast this row covers, the same ID the broadcast endpoints return. Only mail sent as part of a broadcast has a broadcast ID, so one-off and transactional sends do not appear in this breakdown at all. */ readonly broadcast_id: string; readonly delivery: EmailDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailLatencyStats; }; /** * Per-broadcast breakdown for the requested period, ranked by the `sort` metric (default `processed`) descending and capped at the requested `limit` (default 50, max 200). */ type EmailStatsByBroadcastResponse = { /** * The date range the response covers (echoed back from the request), plus `data_as_of`, the freshness boundary the data is current to. */ period: EmailStatsPeriod; /** * Broadcast breakdown rows, ranked by the `sort` metric (default `processed`) descending. Empty when no broadcast messages were active in the period. */ readonly data: Array; /** * Total number of distinct broadcasts with activity in the period, regardless of `limit`. When it exceeds the number of rows returned, the ranking was capped. Raise `limit` (up to 200) or narrow the window to see more. * */ readonly total: number; }; /** * Per-domain behavior toggles. Changes apply immediately to new sends. * */ type DomainSettings = { /** * Rewrite links in HTML email through your tracking domain to record clicks. You can enable this before your tracking domain has verified; it begins working once verification completes. A tracking domain must be configured; enabling it without one returns `409`. * */ click_tracking?: boolean; /** * Insert a tracking pixel in HTML email to record opens. You can enable this before your tracking domain has verified: it begins working once verification completes. A tracking domain must be configured; enabling it without one returns `409`. * */ open_tracking?: boolean; }; /** * Active DKIM signing configuration for the domain. */ type DomainDkim = { /** * How the DKIM public key is published in your DNS. `txt`: you publish the key as a TXT record. `delegated`: you publish a single CNAME and we host and rotate the key. * */ readonly mode: "txt" | "delegated"; /** * DKIM selector used to sign mail from this domain. */ readonly selector: string; /** * RSA key size in bits. */ readonly key_size: number; }; /** * A staged configuration change awaiting DNS verification. The currently active configuration keeps serving until the staged one verifies, at which point it is promoted automatically. Submitting another change for the same capability replaces the staged value. * */ type DomainCapabilityPending = { /** * Hostname the capability uses after the staged change verifies. */ readonly domain: string; /** * Verification status of the staged change. * * - `pending`: the DNS records have not been detected yet. * - `failed`: the records resolved with wrong values; correct them * or submit a different change. * - `temporary_failure`: the DNS lookup failed transiently and is * queued for retry. * */ readonly status: "pending" | "failed" | "temporary_failure"; }; type DomainCapability = { /** * Capability verification status. * * - `pending`: verification has not run, or is currently running. * - `verified`: all DNS records for this capability resolved with the * expected values. * - `warning`: a record for this capability verified before and a recent * check no longer matches, but it is still within the grace period. * Sending is not yet affected; fix it before the grace period ends. * - `failed`: DNS records resolved but at least one value is wrong. * Update your DNS to recover. * - `temporary_failure`: DNS lookup failed transiently. Verification retries * automatically; do not change DNS records unless they are incorrect. * - `not_configured`: the capability is not set up on this domain * (for example, no tracking domain configured). * */ readonly status: "pending" | "verified" | "warning" | "failed" | "temporary_failure" | "not_configured"; /** * Hostname this capability is configured with: the return-path domain, the tracking domain, or the domain where the DMARC policy was found. `null` when not applicable or not configured. * */ readonly domain?: string | null; pending?: DomainCapabilityPending; /** * Machine-readable reason code for a failed capability status. Only set when * `status` is `failed`. Use this to display a specific message to users rather * than a generic failure message. * * - `tracking_domain_in_use`: the link tracking subdomain is already claimed * by another organization. * */ readonly reason?: string | null; }; type DomainCapabilities = { /** * Overall authorization to send from this domain. Verified when the DKIM record, the return-path CNAME, and a DMARC policy are all in place. Required for live sends. * */ sending: DomainCapability; /** * Return-path (bounce) CNAME verification. The return-path domain receives bounce and complaint notifications and is what mailbox providers check for SPF: no separate SPF record is needed. * */ return_path: DomainCapability; /** * DMARC policy check. Satisfied by any valid DMARC record covering the sending domain: on the domain itself or on its registered (organizational) domain; `domain` reports where the policy was found. A minimal policy of `p=none` is sufficient. * */ dmarc: DomainCapability; /** * Branded open/click tracking domain. `not_configured` until a tracking domain is set. Tracked links are served over HTTPS once the CNAME verifies. * */ tracking: DomainCapability; /** * Inbound mail receiving. `not_configured` until receiving is enabled on this domain (see `DomainUpdate.inbound`), then `pending` while the published MX records are checked, and `verified` once they resolve to us. The MX records to publish are always listed under `dns_records` (`purpose: inbound_mx`) as a regional reference, even while this is `not_configured`: enabling is what actually starts delivery. * */ inbound?: DomainCapability; }; type DnsRecord = { /** * The DNS record type to publish, determined by `purpose`. * * - `TXT`: used for the `dkim` and `dmarc` purposes. * - `CNAME`: used for the `return_path` and `tracking` purposes. * - `MX`: used for the `inbound_mx` purpose. * */ type: "TXT" | "CNAME" | "MX"; /** * The record name: the part you enter in your DNS provider's `Name` or `Host` field, relative to the DNS zone the record belongs in (your registered domain). For a sending domain `mail.acme.com` the DKIM record name is `bird1._domainkey.mail`, entered in the `acme.com` zone. `@` for records at the zone apex. * */ name: string; /** * The fully qualified hostname for this record (for example, `bird1._domainkey.mail.acme.com`). * */ host: string; /** * The value to publish, as entered in your DNS provider's `Value` or `Content` field. For `TXT`, enter the full record content. For `CNAME`, enter the target hostname. For `MX`, enter the priority followed by the mail server hostname. * */ value: string; /** * What this record is for. * * - `dkim`: signs outbound mail and proves domain ownership. * - `return_path`: identifies the return-path (bounce) CNAME for sending. * - `tracking`: identifies the optional branded open/click tracking CNAME. * - `inbound_mx`: identifies the MX record routing mail to us for receiving. * Always present wherever inbound is available, as a regional reference, * regardless of whether receiving is enabled; publishing it does not * enable receiving on its own: see `DomainUpdate.inbound`. It is * `optional` until receiving is enabled, and publishing it before then * is destructive: on a domain at the zone apex it replaces the MX * records that carry the domain's existing mail. * - `dmarc`: identifies the advisory DMARC policy record. * */ purpose: "dkim" | "return_path" | "tracking" | "inbound_mx" | "dmarc"; /** * Lifecycle state of this record. * * - `active`: the record backs the domain's current configuration. * - `pending`: the record belongs to a staged configuration change; * publish it to complete the change. * - `deprecated`: the record belonged to a previous configuration. * Keep it in DNS until `safe_to_remove` is `true`; in-flight mail and * previously sent tracked links may still resolve through it. * */ readonly state: "active" | "pending" | "deprecated"; /** * Whether this record can be skipped. An optional record enables extra functionality (branded tracking, or receiving) rather than sending, so publish one only when you want what it enables. The `inbound_mx` records are optional until you enable receiving on the domain, and publishing one before then changes where mail to the domain is delivered. * */ readonly optional: boolean; /** * Verification status of this record's most recent DNS check. * * - `pending`: the record has not verified yet; publish it (or correct it) * and it verifies on the next check. * - `verified`: the most recent check matched the expected value. * - `warning`: the record verified before and a recent check no longer * matched, but it is still within the grace period. Sending is not yet * affected; fix the record before the grace period ends to avoid it * being blocked. * - `failed`: the record verified before but later checks kept failing * past the grace period; the configuration has regressed and needs * attention. * */ readonly status: "pending" | "verified" | "warning" | "failed"; /** * Human-readable detail for a failed check on this record: what was found in DNS and why it did not match. `null` when the record is verified or not yet checked. * */ readonly error?: string | null; /** * Only set on `deprecated` records: `true` once the record is no longer referenced by in-flight mail or live tracked links and can be deleted from your DNS. `null` on `active` and `pending` records. * */ readonly safe_to_remove?: boolean | null; }; type Domain = { readonly id: DomainId; readonly workspace_id: WorkspaceId; /** * The sending domain name. Set at creation and immutable. */ readonly domain: string; /** * The DNS provider hosting this domain's nameservers, so you know which provider's dashboard to manage the required DNS records in. Returns `other` when the provider has not been detected or is not recognized. * */ readonly vendor: "other" | "cloudflare" | "route53" | "godaddy" | "namecheap" | "google" | "azure" | "digitalocean" | "squarespace"; /** * Domain ownership verification, proven by the DKIM record. Readiness to * send or track is reported separately per capability under * `capabilities.*.status`. * * - `pending`: the DKIM record has not been published yet. * - `verified`: the DKIM record is in place; ownership is confirmed. * - `failed`: a DKIM record exists but does not match the expected * value (for example a stale record from an earlier setup), or a * previously verified record was removed. Correct the record to * recover. * - `temporary_failure`: DNS resolution failed transiently, such as from a * timeout or unreachable nameserver. Verification retries automatically; * do not change the DNS records unless they are incorrect. * - `rejected`: the domain was refused for policy reasons and cannot be * used for sending. Contact support if you believe this is an error. * */ readonly status: "pending" | "verified" | "failed" | "temporary_failure" | "rejected"; settings: DomainSettings; /** * What to do next about this domain, given the state it is in. Each entry names one action and says * why it is worth taking, so you can act on this response without working out the order * yourself. Present on reads that compute it: an empty list means there is nothing to do, * and the field is absent entirely on responses that do not report next actions. * * This answers whether you own the domain, which is what `status` reports. What each * capability still needs before it can send or receive is reported separately under * `capabilities`, so an empty list here does not on its own mean the domain is ready. * */ readonly next?: Array; readonly dkim: DomainDkim; capabilities: DomainCapabilities; /** * The domain's DNS records and their individual verification state, returned in full on both the list and single-domain responses. This is the complete set to publish across DKIM, return-path, DMARC, tracking, and inbound; records for a staged change carry `state: pending`. Inbound MX records are always included as a regional reference, even while receiving is off and `capabilities.inbound.status` is `not_configured`. Their presence alone does not mean receiving is enabled; see `DomainUpdate.inbound`. * */ readonly dns_records: Array; /** * When we last checked this domain's DNS records, whether or not the outcome changed. Updated on every verification: your manual refresh and the periodic automatic re-checks alike. `null` if the domain has never been checked. * */ readonly last_checked_at?: string | null; /** * When the domain's ownership was confirmed: the moment `status` became `verified` via the DKIM record. Unchanged by later re-checks while it stays verified. `null` if the domain has never been verified. * */ readonly verified_at?: string | null; /** * When the domain was added. */ readonly created_at: string; /** * When the domain's configuration was last changed (such as a settings or return-path change). Verification re-checks do not change this; see `last_checked_at` and `verified_at` for verification timing. * */ readonly updated_at: string; }; /** * Return-path (bounce) domain configuration. The return-path domain receives bounce and complaint notifications for mail sent from this domain and is what mailbox providers check for SPF. Provide only the name part; we add the sending domain automatically. * */ type DomainReturnPathConfig = { /** * Name part to use for the return-path domain. For example, `send` on `mail.acme.com` becomes `send.mail.acme.com`. Defaults to `send` when omitted at creation. * */ name: string; }; /** * Tracking domain configuration for branded open and click tracking URLs. Provide only the name part; we add the sending domain automatically. A domain created with no tracking configuration defaults to `links`. Tracked links are served over HTTPS after the tracking record verifies. * */ type DomainTrackingConfig = { /** * Name part to use for branded open and click tracking URLs. For example, `links` on `mail.acme.com` becomes `links.mail.acme.com`. * */ name: string; }; /** * DKIM signing configuration. */ type DomainDkimConfig = { /** * How the DKIM public key is published in your DNS. * * - `txt` (default): you publish the DKIM public key as a TXT record. Key * rotation requires updating the record. * - `delegated`: you publish a CNAME that points to a DKIM key we host and * rotate. This mode is unavailable for new configurations; supplying it * returns `422`. * */ mode?: "txt" | "delegated"; }; type DomainCreate = { /** * The domain you send from: the domain of your `from` addresses. Use a dedicated subdomain (for example, `mail.acme.com`) rather than your registered domain so sending reputation stays separate from other services on the domain. * */ domain: string; return_path?: DomainReturnPathConfig; tracking?: DomainTrackingConfig; dkim?: DomainDkimConfig; settings?: DomainSettings; }; /** * Inbound (receiving) configuration. Enable inbound to receive email addressed to this domain. We return MX records to publish. After they verify, mail to any local-part at this domain is delivered as an inbound message and triggers the `email.received` webhook. Use a dedicated subdomain, such as `inbound.acme.com`, because using your apex domain would capture your corporate mail. * */ type DomainInboundConfig = { /** * Set `true` to enable receiving on this domain, `false` to disable it. Disabling tears receiving down and removes the MX records from `dns_records`; this is immediate in the normal case, and if a step needs retrying the capability clears as soon as teardown finishes. * */ enabled: boolean; }; /** * Partial update. `settings` changes apply immediately. Changes to `return_path`, `tracking`, or `dkim` on a verified capability are staged. The current configuration keeps serving until the new DNS records verify. The change is then promoted automatically and the old records are marked `deprecated`. The staged value is visible under `capabilities.*.pending` and can be replaced by submitting another change. * */ type DomainUpdate = { settings?: DomainSettings; /** * Change the return-path name part. Cannot be removed: the return-path is required for sending. * */ return_path?: DomainReturnPathConfig; /** * Set or change the tracking name part, or remove tracking by passing `null`. Removal requires `click_tracking` and `open_tracking` to be disabled first, and returns `409` otherwise. After removal, links in previously sent email keep resolving while the tracking records are reported as `deprecated`. * */ tracking?: DomainTrackingConfig | null; /** * Change how the DKIM key is published. The current key keeps signing until the new configuration verifies, so mail is never sent unsigned during the transition. * */ dkim?: DomainDkimConfig; /** * Enable or disable receiving on this domain. Enabling claims the domain for inbound and moves `capabilities.inbound.status` from `not_configured` to `pending`, then `verified` once the MX records resolve to us. The MX records to publish are always present under `dns_records` (`purpose: inbound_mx`) as a regional reference. Their presence does not mean receiving is enabled; enable the domain whenever `capabilities.inbound.status` is `not_configured`. Enabling requires the domain's DKIM to be verified first. A fresh enable on a domain whose DKIM is not verified returns `422` with `E05019` and claims nothing. A domain already receiving inbound for another organization returns `422` with `E05018`. * */ inbound?: DomainInboundConfig; }; type SuppressionId = string; type Actor = { /** * Actor identifier. */ id: string; /** * Who or what performed the action: `user` for a member's own session, `oauth_token` for a token issued to a caller on a member's behalf, `api_key` for a workspace API key, `system` for our own automation, `sso` for an organization's SSO connection, and `service_account` for a workspace's connected Integration acting with no member behind it. Open enum: new actor types may be added over time, so treat any unrecognized value as a future type rather than an error. */ type: string; /** * The label the actor is shown under: typically a member's name or email address, or the API key's name. Null when it could not be resolved. * */ readonly display_name?: string | null; }; type InboundAddressId = string; type InboundEmailMessageId = string; type MailboxId = string; /** * The principal that owns the mailbox. Always the workspace. */ type MailboxOwner = { /** * Owner principal type. */ readonly type: "workspace"; /** * Owner principal ID. */ readonly id: WorkspaceId; }; /** * A durable mailbox identity for an agent. A mailbox owns an email address, groups mail into threads, applies receive policy, and remembers message metadata and extracted text for its retention tier. The original rendered source of each message remains available for 30 days. * */ type Mailbox = { /** * Mailbox ID. */ readonly id: MailboxId; /** * The mailbox's email address. Immutable once created. */ readonly address: string; /** * Display name used as the sender name on mail from this mailbox. `null` when unset. */ display_name: string | null; /** * Default `Reply-To` address stamped on mail sent from this mailbox. `null` when unset. */ default_reply_to: string | null; /** * Which inbound mail the mailbox accepts: * * - `open`: Accepts everything not blocked by a rule. * - `replies_only`: Accepts only replies to messages this mailbox has * sent. A reply must match a message the mailbox sent. Landing in an * existing thread by itself does not count. * - `allowlist`: Accepts only senders matching an allow rule. Replies to * prior outbound mail are always admitted unless blocked. * - `drop`: Stores nothing. * */ receive_policy: "open" | "replies_only" | "allowlist" | "drop"; /** * Lifecycle state. Suspended mailboxes stop emitting events. Inbound mail is retained as blocked. */ readonly state: "active" | "suspended"; /** * The channel this mailbox receives on. Always `email`. */ readonly channel: "email"; readonly owner: MailboxOwner; /** * The underlying inbound address that receives this mailbox's mail. */ readonly inbound_address_id: InboundAddressId; /** * How long the mailbox remembers message metadata and extracted text. Original rendered source (HTML, raw message, attachments) is always available for 30 days regardless of tier. */ retention_tier: "30d" | "90d" | "1y"; /** * Number of retained messages across all threads. */ readonly message_count: number; /** * Number of retained threads. */ readonly thread_count: number; /** * Number of threads with unread messages in this mailbox, excluding trash. `null` on create/update responses. * */ readonly unread_thread_count?: number | null; /** * Your own key/value data attached to the mailbox. Up to 2 KB. Keys starting with `__bird` are reserved. */ metadata: { [key: string]: unknown; }; /** * Whether we generated the local part of the address. `false` means a custom handle was chosen at creation. On the shared `inbox.ai` domain a custom handle counts against your plan's custom-handle allowance. */ readonly local_part_generated?: boolean; /** * When the mailbox was created. */ readonly created_at: string; /** * When the mailbox was last updated. */ readonly updated_at: string; /** * When the mailbox was deleted, or `null` if it is active. A deleted mailbox stops receiving mail immediately but can be restored for 30 days, after which it and its remembered messages are permanently removed. */ readonly deleted_at?: string | null; }; /** * Parameters for creating a mailbox. */ type MailboxCreate = { /** * The local part of the mailbox address (the part before `@`). Letters, digits, dots, underscores, and hyphens. Stored lowercase. On the shared `inbox.ai` domain, separators must sit between letters or digits. Leading, trailing, and repeated separators are not allowed. Reserved names such as `postmaster` and `abuse` are unavailable. Choosing your own local part uses one of your plan's custom-handle allowance slots; generated addresses remain available. Omit this field to generate a random local part. */ local_part?: string; /** * The domain the address lives under. Defaults to `inbox.ai`, our shared mailbox domain. Creating a mailbox claims the shared address for your organization on a first-come, first-served basis. The address remains reserved to your organization after the mailbox is deleted. You can instead use one of your own domains enabled for receiving email. */ domain?: string; /** * Display name used as the sender name on mail from this mailbox. */ display_name?: string; /** * Default `Reply-To` address stamped on mail sent from this mailbox. */ default_reply_to?: string; /** * Which inbound mail the mailbox accepts: * * - `open`: Accepts everything not blocked by a rule. * - `replies_only`: Accepts only replies to messages this mailbox has * sent. A reply must match a message the mailbox sent. Landing in an * existing thread by itself does not count. * - `allowlist`: Accepts only senders matching an allow rule. * - `drop`: Stores nothing. * */ receive_policy?: "open" | "replies_only" | "allowlist" | "drop"; /** * How long the mailbox remembers message metadata and extracted text. Original rendered source is always available for 30 days regardless of tier. */ retention_tier?: "30d"; /** * Your own key/value data to attach to the mailbox. Up to 2 KB. Keys starting with `__bird` are reserved. */ metadata?: { [key: string]: unknown; }; }; /** * Fields to update on a mailbox. Omitted fields are unchanged. Fields set to `null` are cleared. The address and domain are immutable. */ type MailboxUpdate = { /** * Display name used as the sender name on mail from this mailbox. `null` clears it. */ display_name?: string | null; /** * Default `Reply-To` address stamped on mail sent from this mailbox. `null` clears it. */ default_reply_to?: string | null; /** * Which inbound mail the mailbox accepts: * * - `open`: Accepts everything not blocked by a rule. * - `replies_only`: Accepts only replies to messages this mailbox has * sent. A reply must match a message the mailbox sent. Landing in an * existing thread by itself does not count. * - `allowlist`: Accepts only senders matching an allow rule. * - `drop`: Stores nothing. * */ receive_policy?: "open" | "replies_only" | "allowlist" | "drop"; /** * How long the mailbox remembers message metadata and extracted text. Lowering the tier deletes remembered messages older than the new horizon, and requires `confirm=true` when that would happen. */ retention_tier?: "30d"; /** * Replaces the mailbox's key/value data. Up to 2 KB. Keys starting with `__bird` are reserved. */ metadata?: { [key: string]: unknown; }; }; /** * Single-row aggregate of the mailbox's email activity across the full requested period. Counts are sums of per-bucket counts across the window. Latency percentiles are computed across the whole period rather than summed per bucket. Rates are `null` when their denominator is zero. * */ type MailboxStatsSummary = { /** * Distinct email messages the mailbox sent that were accepted, counted at the message level and summed per bucket across the period. */ readonly sends_accepted: number; readonly delivery: EmailDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailLatencyStats; /** * Distinct emails the mailbox received, summed per bucket across the period. */ readonly received: number; }; /** * Per-mailbox email activity for one time bucket, bucketed by event time. Sent-mail metrics use the same delivery, engagement, and latency breakdowns as the email stats endpoints. `received` counts mail that arrived at the mailbox. Buckets with no activity are included with zero counts and `null` latency percentiles. * */ type MailboxStatsPoint = { /** * The day (`YYYY-MM-DD`) or instant (RFC 3339, on the bucket boundary) this point covers, matching the period's grain. */ readonly bucket: string; /** * Distinct email messages the mailbox sent that were accepted in this bucket, counted at the message level (one per accepted send regardless of how many recipients it addresses). Every other sent-mail metric in `delivery` and `engagement` is recipient-level or event-level. * */ readonly sends_accepted: number; readonly delivery: EmailDeliveryStats; readonly engagement: EmailEngagementStats; readonly latency: EmailLatencyStats; /** * Distinct emails the mailbox received in this bucket. */ readonly received: number; }; /** * A mailbox's sent and received email statistics: a period-wide summary plus a bucketed time series. `period` echoes the range and grain actually used. `data` is one row per bucket in chronological order. * */ type MailboxStatsResponse = { period: EmailStatsSeriesPeriod; summary: MailboxStatsSummary; /** * One row per bucket in the period, in chronological order. Buckets with no activity are included with zero counts. */ readonly data: Array; }; type ReceiveRuleId = string; /** * An allow or block entry on a mailbox, evaluated when inbound mail arrives. Matching is against the message's envelope sender; domain entries also match subdomains. A given entry can be allow or block, never both. * */ type ReceiveRule = { /** * Identifies this rule for deletion. There is no update operation. */ readonly id: ReceiveRuleId; /** * The mailbox the rule applies to. */ readonly mailbox_id: MailboxId; /** * What the rule does when it matches. Block rules always win: over allow rules and over the reply admission on allowlist mailboxes. */ readonly action: "allow" | "block"; /** * The sender address or domain the rule matches. Domains also match their subdomains. */ readonly entry: string; /** * Whether the entry is a full address or a domain. */ readonly entry_type: "address" | "domain"; /** * Your own note about why the rule exists. `null` when unset. */ readonly note: string | null; /** * When the rule was created. */ readonly created_at: string; }; /** * Parameters for adding a receive rule to a mailbox. */ type ReceiveRuleCreate = { /** * What the rule does when it matches. Block rules always win. To flip an entry's action, delete the existing rule and re-create it. */ action: "allow" | "block"; /** * The sender address (`alice@example.com`) or domain (`example.com`) to match. Domains also match their subdomains. Stored lowercase. */ entry: string; /** * Your own note about why the rule exists. */ note?: string; }; type ThreadId = string; /** * Matched search fragments for a thread, one array per field the query matched, with the matched terms wrapped in `**`. A field is present only when the query matched it, so the keys that are present tell you which fields produced the hit. Returned only on thread search results. * */ type EmailThreadHighlights = { /** * Matched fragments from the conversation's subject. */ subject?: Array; /** * Matched fragments from a message's body text. */ text?: Array; }; /** * A conversation in a mailbox. It groups every message in both directions, the mail the mailbox received and the replies it sent, and it holds the conversation's read state, labels, and participant list. A message is retained until it is trashed or ages past the mailbox's retention tier. Only retained messages count toward the totals below. * */ type EmailThread = { /** * Thread ID. */ readonly id: ThreadId; /** * Mailbox this conversation belongs to. */ readonly mailbox_id: MailboxId; /** * Channel this conversation lives on. Always `email`. */ readonly channel: string; /** * Contact linked to this conversation, or null when none is linked. */ contact_id: ContactId | null; /** * Subject of the conversation, taken from its first message. Null when that message had no subject. */ readonly subject: string | null; /** * Addresses that appear on the retained messages in this conversation, including the mailbox's own address. */ readonly participants: Array; /** * Number of retained messages in this conversation, both directions. */ readonly message_count: number; /** * Number of retained received messages that are still unread. Spam and blocked mail is not counted. */ readonly unread_count: number; /** * When the most recent retained message in this conversation was received or sent. */ readonly last_message_at: string; /** * Direction of the most recent message: `inbound` for a received message, `outbound` for a sent one. */ readonly last_direction: "inbound" | "outbound"; /** * Labels on this conversation. Exactly one system placement label is always present, set by the message that started the conversation: * * - `inbox`: The conversation is in the inbox. * - `archive`: The conversation was filed away and is done for now. * - `spam`: The conversation's opening message failed sender authentication. * - `blocked`: The conversation's opening message was rejected by the mailbox's receive policy or rules. * * Move a conversation by updating its labels. Add `spam` to file it as spam, add `archive` to clean it out of the inbox, and add `inbox`, or remove `spam`, `blocked`, or `archive`, to bring it back. An archived conversation returns to the inbox by itself when a new message arrives. Custom labels share the same list, and a conversation has at most 20 labels in total. * */ labels: Array; /** * When the thread was created. */ readonly created_at: string; /** * When the thread last changed. */ readonly updated_at: string; /** * Matched search fragments, keyed by the field that matched. Returned only by thread search. Omitted when listing threads. * */ readonly highlights?: EmailThreadHighlights; }; /** * Label changes to apply. Labels in `add` are applied and labels in `remove` are taken off; other labels are left untouched. Adding a label that is already present, or removing one that is not, has no effect. System labels express state changes. On a conversation, adding `spam` files it as spam. Adding `archive` files it away without deleting it. Adding `inbox`, or removing `spam`, `blocked`, or `archive`, returns it to the inbox. Removing `unread` marks all retained received messages as read in one call. On a message, adding or removing `unread` flips read state. Adding or removing `trash` moves it to or out of the trash. The API rejects changes that contradict this model. A request cannot add more than one placement label. It cannot add `blocked`, because blocking a sender is a receive-rule decision. Removing `inbox` requires adding a destination. A conversation cannot add `trash` or `unread`; removing `unread` is the mark-all-read shortcut, and `trash` uses the `DELETE` verb. A message cannot use placement labels; move its conversation instead. A sent message cannot use `unread`. Custom labels are 1-64 characters with no commas, control characters, or leading or trailing whitespace. System label names and a small reserved set (`all`, `archived`, `deleted`, `draft`, `drafts`, `flagged`, `important`, `junk`, `muted`, `none`, `outbox`, `pinned`, `read`, `scheduled`, `snoozed`, `starred`) cannot be used as custom labels, in any casing. A conversation or message has at most 20 labels, system labels included. * */ type EmailLabelsUpdate = { /** * Labels to apply. */ add?: Array; /** * Labels to take off. */ remove?: Array; }; /** * Changes to apply to a thread. Omitted fields are left unchanged. */ type EmailThreadUpdateRequest = { labels?: EmailLabelsUpdate; /** * Contact to link this conversation to, or null to unlink the current contact. */ contact_id?: ContactId | null; }; /** * One recipient's terminal delivery outcome on a sent conversation message, recorded once the outcome becomes known. * */ type EmailThreadMessageRecipient = { /** * Recipient address. */ readonly address: string; /** * Terminal outcome: `delivered`, or `failed` (bounce or provider rejection). */ readonly status: "delivered" | "failed"; }; /** * Attachment metadata on a conversation message. The metadata stays readable for the mailbox's retention tier. The attachment bytes are downloadable for 30 days after the message occurred. * */ type EmailThreadMessageAttachment = { /** * Attachment ID, used to download the attachment bytes. */ readonly id: string; /** * Original filename, or null when the attachment had none. */ readonly filename: string | null; /** * MIME content type, or null when it could not be determined. */ readonly content_type: string | null; /** * Attachment size in bytes. */ readonly size: number; }; /** * Link to the message's entry in the received-message or sent-message log, which has delivery analytics such as per-recipient events. Log entries expire 30 days after the message occurred. * */ type EmailThreadMessageSource = { /** * API path of the log entry for this message. */ readonly resource: string; /** * When the log entry (and the message's original rendered source) expires. */ readonly available_until: string; }; /** * A message in a mailbox conversation, either direction. Message metadata and extracted text stay readable for the mailbox's retention tier. The original rendered source (HTML body, raw MIME, attachment bytes) is available through the body, raw, and attachment endpoints for 30 days after the message occurred. * */ type EmailThreadMessage = { /** * Message ID. Received messages have a `rem_` ID, sent messages an `em_` ID: the same IDs used by the received-message and sent-message logs. * */ readonly id: string; /** * Which way the message went. `inbound` means you received it, `outbound` means you sent it. */ readonly direction: "inbound" | "outbound"; /** * Channel this message lives on. Always `email`. */ readonly channel: string; /** * Conversation this message belongs to. */ readonly thread_id: ThreadId; /** * Sender address. */ readonly from: string; /** * Recipient addresses on the To line. */ readonly to: Array; /** * Recipient addresses on the Cc line. Empty when the message had none. */ readonly cc: Array; /** * Address the message was actually delivered to, when it differs from the mailbox address (for example mail routed in from another address). Null for sent messages and for mail addressed directly to the mailbox. * */ readonly delivered_to: string | null; /** * Message subject. Null when the message had no subject. */ readonly subject: string | null; /** * Short plain-text preview of the message body. */ readonly preview: string | null; /** * Plain-text content of the message with quoted history stripped. Readable for the mailbox's full retention tier, in both directions. Always present when fetching a single message. On list endpoints it is included only when the request sets `include=extracted_text`. Null when no text could be extracted. * */ readonly extracted_text?: string | null; /** * Labels on this message. A received message always has exactly one placement label: * * - `inbox`: Accepted mail. * - `archive`: The message's conversation was filed away. * - `spam`: The message failed sender authentication. * - `blocked`: The message was rejected by the mailbox's receive policy or rules. * * A received message also has `unread` until it is read. `trash` marks a message in the trash, in either direction. Custom labels share the same list, and a message has at most 20 labels in total. * */ labels: Array; /** * Aggregate delivery status of a sent message: * * - `accepted`: Accepted for sending. * - `sent`: Handed off to the provider. * - `delivered`: All attempted recipients delivered. * - `failed`: Terminal failure. * * Null for received messages. * */ readonly status: string | null; /** * Terminal per-recipient delivery outcomes of a sent message, filled in as each one becomes known and kept for the mailbox's full retention tier. Null for received messages and before any recipient reaches a terminal state. Per-recipient event detail lives on the sent-message log (`source`) for 30 days. * */ readonly recipients: Array | null; /** * Whether the sender of a received message was authenticated. * * - `pass`: the sender's identity was verified. * - `fail`: it was checked and did not verify. * - `unknown`: no verdict could be determined, so do not treat the * sender as verified. * * Null for sent messages. This field is readable for the mailbox's full * retention tier, so the verdict is still available after the 30-day * received-message log has expired. * */ readonly authentication: "pass" | "fail" | "unknown" | null; /** * Whether SPF passed for the sender of a received message. Null for sent messages and when no verdict is available. This field is kept for the mailbox's retention tier. * */ readonly spf_pass: boolean | null; /** * Whether DKIM passed for the sender of a received message. Null for sent messages and when no verdict is available. This field is kept for the mailbox's retention tier. * */ readonly dkim_pass: boolean | null; /** * Whether DMARC passed for the sender of a received message. Null for sent messages and when no verdict is available. This field is kept for the mailbox's retention tier. * */ readonly dmarc_pass: boolean | null; /** * Scheduled permanent-deletion time. This is the end of the mailbox's retention tier, moved to no more than 30 days in the future while the message is in the trash. Restore a trashed message before then with `PATCH {"labels": {"remove": ["trash"]}}`. * */ readonly purge_at: string; /** * Number of attachments on the message. */ readonly attachment_count: number; /** * Attachment metadata (filename, content type, size). Stays readable for the mailbox's retention tier even after the attachment bytes themselves have expired. * */ readonly attachment_manifest: Array; /** * RFC 5322 References header entries used to thread the conversation. */ readonly reference_ids: Array; /** * Contact linked to this message, or null when none is linked. */ contact_id: ContactId | null; readonly source: EmailThreadMessageSource; /** * When the message was received or accepted for sending. */ readonly occurred_at: string; }; /** * The original rendered body of a conversation message. Available for 30 days after the message occurred. After that, the endpoint returns `410 Gone`, but the message's extracted text stays readable on the message itself. * */ type EmailThreadMessageBody = { /** * The HTML body of the message, or null when the message had no HTML part. */ html: string | null; /** * The plain-text body of the message, or null when the message had no text part. */ text: string | null; }; /** * The attachments on a conversation message. */ type EmailThreadMessageAttachmentList = { data: Array; }; /** * A reply to a conversation message. Recipients are derived from the message being replied to: its Reply-To address when present, otherwise its From address. Set `reply_all` to also include the original To and Cc recipients (minus the mailbox's own address). The subject and threading headers are set automatically. At least one of `html` or `text` must be provided. * */ type EmailThreadMessageReplyRequest = { /** * HTML body of the reply. At least one of html or text must be provided. */ html?: string; /** * Plain-text body of the reply. At least one of html or text must be provided. */ text?: string; /** * Also send the reply to the original To and Cc recipients, minus the mailbox's own address. */ reply_all?: boolean; /** * Structured `{name, value}` labels for filtering and analytics on the sent-message log. Cap: 20 tags per send. * */ tags?: Array; /** * Arbitrary JSON object stored on the send and echoed in webhook payloads. Cap: 2 KB serialized. * */ metadata?: { [key: string]: unknown; }; category?: EmailMessageCategory; /** * File attachments to include with the reply. The send is rejected when the estimated generated message size exceeds 20 MB (bodies plus all attachments after base64 encoding). Keep total raw attachment content at or below 15 MB for reliable headroom. Attachment metadata stays on the message's `attachment_manifest`, and the bytes are downloadable for 30 days. * */ attachments?: Array; }; /** * A new message sent from a mailbox, starting a new conversation. Mirrors the plain send request without `from`, because the mailbox is who the message comes from, and without `scheduled_at`, because a mailbox sends immediately. We set the RFC 5322 Message-ID so replies thread back into this conversation. At least one of `html` or `text` must be provided. * */ type EmailMailboxComposeRequest = { /** * Primary recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane `), or an object with an optional display name. */ to: Array; /** * CC recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane `), or an object with an optional display name. */ cc?: Array; /** * BCC recipients. Each entry is a plain email string, an RFC 5322 mailbox string (`Jane `), or an object with an optional display name. */ bcc?: Array; /** * Message subject line. */ subject: string; /** * HTML body. At least one of html or text must be provided. */ html?: string; /** * Plain-text body. At least one of html or text must be provided. */ text?: string; /** * Reply-To addresses. When omitted, the mailbox's `default_reply_to` applies (replies then come back to the mailbox itself). * */ reply_to?: Array; /** * File attachments. The send is rejected when the estimated generated message size exceeds 20 MB (bodies plus all attachments after base64 encoding). Keep total raw attachment content at or below 15 MB for reliable headroom. Attachment metadata stays on the message's `attachment_manifest`, and the bytes are downloadable for 30 days. * */ attachments?: Array; /** * Structured `{name, value}` labels for filtering and analytics on the sent-message log. Cap: 20 tags per send. * */ tags?: Array; /** * Arbitrary JSON object stored on the send and echoed in webhook payloads. Cap: 2 KB serialized. * */ metadata?: { [key: string]: unknown; }; category?: EmailMessageCategory; }; /** * One label available in a mailbox. */ type EmailMailboxLabel = { /** * The label name, as it appears on conversations and messages. */ readonly name: string; /** * `system` labels are the built-in placements a message can be in: * * - Inbox. * - Archive. * - Spam. * - Blocked. * - Sent. * - Trash. * - Unread. * * `custom` labels are the workspace's own tags. */ readonly type: "system" | "custom"; }; /** * The labels available in a mailbox. */ type EmailMailboxLabelList = { data: Array; }; /** * Payload of the domain.failed event. */ type EventDomainFailedData = { /** * The sending domain resource whose verification failed. */ domain_id: DomainId; /** * The sending domain hostname. */ domain: string; /** * The workspace the domain is assigned to. */ workspace_id: WorkspaceId; /** * Why verification failed, when a specific reason is available (for example, the DKIM record was not found at the expected selector). */ failure_reason?: string | null; }; /** * A sending domain failed DNS verification. */ type EventDomainFailed = { /** * Event type. */ type: "domain.failed"; /** * When the event occurred. */ timestamp: string; data: EventDomainFailedData; }; /** * Payload of the domain.verified event. */ type EventDomainVerifiedData = { /** * The sending domain resource that verified. */ domain_id: DomainId; /** * The sending domain hostname. */ domain: string; /** * The workspace the domain is assigned to. */ workspace_id: WorkspaceId; }; /** * A sending domain completed DNS verification successfully. */ type EventDomainVerified = { /** * Event type. */ type: "domain.verified"; /** * When the event occurred. */ timestamp: string; data: EventDomainVerifiedData; }; /** * Identity fields shared by every email lifecycle event payload. */ type EventEmailBase = { /** * ID of the email send. */ email_id: EmailId; /** * ID of the recipient. */ recipient_id: RecipientId; /** * ID of the workspace that owns this event. */ workspace_id: WorkspaceId; /** * Recipient address as it appeared on the envelope. */ recipient: string; /** * Envelope position of the recipient. */ recipient_role: RecipientRole; /** * Tags provided on the send request, echoed on every event for the send so you can route and correlate without an extra lookup. Null when the send carried no tags. * */ tags: Array | null; /** * The metadata object provided on the send request, echoed on every event for the send so you can correlate events with your own records. Null when the send carried no metadata. * */ metadata: { [key: string]: unknown; } | null; }; /** * Payload of the email.accepted event. */ type EventEmailAcceptedData = EventEmailBase; /** * The API accepted the email send and is preparing it for delivery. Fires once per requested recipient. */ type EventEmailAccepted = { /** * Event type. */ type: "email.accepted"; /** * Time the API accepted the send. */ timestamp: string; data: EventEmailAcceptedData; }; /** * Bounce classification. * * - `hard`: A permanent failure, such as an invalid address or a domain that does not exist. * - `soft`: A transient failure, such as a full mailbox or a server that is temporarily unavailable. * - `block`: The receiving mail server refused the sending IP on reputation grounds. * - `admin`: An administrative refusal, such as relaying denied or a blocklisted domain. * - `undetermined`: The receiving server's response was ambiguous. * */ type EmailBounceType = "hard" | "soft" | "undetermined" | "admin" | "block"; /** * Payload of the email.bounced event. */ type EventEmailBouncedData = EventEmailBase & { bounce_type: EmailBounceType; /** * Numeric bounce classification for fine-grained deliverability triage, or null when the receiving server's response could not be classified. Lets you distinguish, for example, a DNS failure from a spam block when both would be `bounce_type: soft` or `bounce_type: block`. * */ bounce_class: number | null; /** * SMTP reply code returned by the receiving mail server, or null when none was provided. */ bounce_code: string | null; /** * Human-readable reason the receiving mail server gave for the bounce, or null when none was provided. */ bounce_description: string | null; /** * The IP address used to send this message, or null when it is not known. */ sending_ip: string | null; }; /** * An outbound email permanently failed at the recipient's mail server. Fires once per recipient. */ type EventEmailBounced = { /** * Event type. */ type: "email.bounced"; /** * Time the bounce was recorded. */ timestamp: string; data: EventEmailBouncedData; }; /** * Identity fields shared by the message-level email lifecycle events (scheduled, canceled), which are not tied to a single recipient. */ type EventEmailMessageBase = { /** * ID of the email send. */ email_id: EmailId; /** * ID of the workspace that owns this event. */ workspace_id: WorkspaceId; /** * Tags provided on the send request, echoed on the event so you can route and correlate without an extra lookup. Null when the send carried no tags. * */ tags: Array | null; /** * The metadata object provided on the send request, echoed on the event so you can correlate events with your own records. Null when the send carried no metadata. * */ metadata: { [key: string]: unknown; } | null; }; /** * Payload of the email.canceled event. */ type EventEmailCanceledData = EventEmailMessageBase; /** * A scheduled send was canceled before it fired. Fires once for each message regardless of its recipient count. */ type EventEmailCanceled = { /** * Event type. */ type: "email.canceled"; /** * Time the scheduled send was canceled. */ timestamp: string; data: EventEmailCanceledData; }; /** * Payload of the email.clicked event. */ type EventEmailClickedData = EventEmailBase & { /** * The URL the recipient clicked. */ url: string; /** * IP address of the client that clicked the link, or null when it is not known. */ ip_address: string | null; /** * User-agent string of the client that clicked the link, or null when it is not known. */ user_agent: string | null; }; /** * The recipient clicked a tracked link in the email. May fire more than once per recipient. */ type EventEmailClicked = { /** * Event type. */ type: "email.clicked"; /** * Time the click was recorded. */ timestamp: string; data: EventEmailClickedData; }; /** * Payload of the email.complained event. */ type EventEmailComplainedData = EventEmailBase & { /** * The kind of feedback the mailbox provider reported (such as `abuse` or `fraud`), or null when the provider did not specify one. */ feedback_type: string | null; }; /** * The recipient marked the email as spam through their mailbox provider's feedback loop. Fires once per recipient. */ type EventEmailComplained = { /** * Event type. */ type: "email.complained"; /** * Time the complaint was recorded. */ timestamp: string; data: EventEmailComplainedData; }; /** * Payload of the email.deferred event. */ type EventEmailDeferredData = EventEmailBase & { bounce_type: EmailBounceType; /** * Numeric bounce classification for fine-grained deliverability triage, or null when the receiving server's response could not be classified. Distinguishes, for example, a greylisting deferral from a full mailbox. * */ bounce_class: number | null; /** * Human-readable reason the receiving mail server gave for the deferral, or null when none was provided. */ defer_reason: string | null; /** * The IP address used to send this message, or null when it is not known. */ sending_ip: string | null; }; /** * The recipient's mail server temporarily refused the email. Delivery remains pending and is retried. May fire more than once per recipient. */ type EventEmailDeferred = { /** * Event type. */ type: "email.deferred"; /** * Time the deferral was recorded. */ timestamp: string; data: EventEmailDeferredData; }; /** * Payload of the email.delivered event. */ type EventEmailDeliveredData = EventEmailBase; /** * An outbound email reached the recipient's mail server and was accepted. */ type EventEmailDelivered = { /** * Event type. */ type: "email.delivered"; /** * Time the recipient's mail server accepted the message. */ timestamp: string; data: EventEmailDeliveredData; }; /** * Payload of the email.list_unsubscribed event. */ type EventEmailListUnsubscribedData = EventEmailBase; /** * Recipient unsubscribed via the RFC 8058 one-click List-Unsubscribe mechanism. Fires once per recipient. */ type EventEmailListUnsubscribed = { /** * Event type. */ type: "email.list_unsubscribed"; /** * Time the unsubscribe was recorded. */ timestamp: string; data: EventEmailListUnsubscribedData; }; /** * Payload of the email.opened event. */ type EventEmailOpenedData = EventEmailBase & { /** * IP address of the client that opened the email, or null when it is not known. */ ip_address: string | null; /** * User-agent string of the client that opened the email, or null when it is not known. */ user_agent: string | null; }; /** * The recipient opened the email (the tracking pixel was loaded). May fire more than once per recipient. */ type EventEmailOpened = { /** * Event type. */ type: "email.opened"; /** * Time the open was recorded. */ timestamp: string; data: EventEmailOpenedData; }; /** * Payload of the email.out_of_band_bounce event. */ type EventEmailOutOfBandBounceData = EventEmailBase & { bounce_type: EmailBounceType; /** * Numeric bounce classification for fine-grained deliverability triage, or null when the receiving server's response could not be classified. * */ bounce_class: number | null; /** * SMTP reply code returned by the receiving mail server, or null when none was provided. */ bounce_code: string | null; /** * Human-readable reason the receiving mail server gave for the bounce, or null when none was provided. */ bounce_description: string | null; /** * The IP address used to send this message, or null when it is not known. */ sending_ip: string | null; }; /** * A bounce notification arrived after the message had already been accepted for delivery. Fires once per recipient. */ type EventEmailOutOfBandBounce = { /** * Event type. */ type: "email.out_of_band_bounce"; /** * Time the bounce notification was recorded. */ timestamp: string; data: EventEmailOutOfBandBounceData; }; /** * Payload of the email.processed event. */ type EventEmailProcessedData = EventEmailBase; /** * The API prepared the message for delivery to the recipient's mail server. Fires once per recipient. */ type EventEmailProcessed = { /** * Event type. */ type: "email.processed"; /** * Time the message was prepared for delivery. */ timestamp: string; data: EventEmailProcessedData; }; /** * Payload of the email.received event. */ type EventEmailReceivedData = { /** * ID of the received email. Fetch its parsed metadata with `GET /v1/email/inbound-messages/{id}`, and its content from that message's `/body`, `/raw`, and `/attachments` sub-resources. */ inbound_message_id: InboundEmailMessageId; /** * ID of the workspace that owns this event. */ workspace_id: WorkspaceId; /** * RFC 5322 Message-ID header from the sender, or null when the sender did not include one. */ message_id: string | null; /** * Envelope-from address. */ from: string; /** * Recipient addresses the message was sent to. */ to: Array; /** * Subject line as received, or null when the message had no subject. */ subject: string | null; /** * `In-Reply-To` header containing the `Message-ID` this message replies to, or null when it is not a reply. */ in_reply_to?: string | null; /** * Whether the sender of the received message was authenticated. * * - `pass`: the sender's identity was verified. * - `fail`: it was checked and did not verify. * - `unknown`: no verdict is available, so do not treat the sender * as verified. * */ authentication?: "pass" | "fail" | "unknown" | null; /** * Whether SPF passed for the sender, or null when the result did not carry an SPF verdict. */ spf_pass?: boolean | null; /** * Whether DKIM passed for the sender, or null when the result did not carry a DKIM verdict. */ dkim_pass?: boolean | null; /** * Whether DMARC passed for the sender, or null when the result did not carry a DMARC verdict. */ dmarc_pass?: boolean | null; /** * Spam score carried on the received message, or null when it carries no score. */ spam_score?: number | null; }; /** * The API received and parsed an inbound email. The payload carries the message's identifiers, sender and recipients, subject, threading reference, and authentication results, which is enough to route and triage without a fetch. Fetch content separately. Get the parsed body with `GET /v1/email/inbound-messages/{id}/body`. Get the original MIME with `GET /v1/email/inbound-messages/{id}/raw`. Get attachment bytes with `GET /v1/email/inbound-messages/{id}/attachments/{attachment_id}`. */ type EventEmailReceived = { /** * Event type. */ type: "email.received"; /** * When the API received the message. */ timestamp: string; data: EventEmailReceivedData; }; /** * Why an email was rejected before delivery. * * - `recipient_suppressed`: The recipient is on the workspace suppression list, so * delivery was never attempted. * - `transmission_failed`: The message could not be transmitted for delivery. * - `generation_failure`: The message could not be built for delivery (template or * content issue). * - `policy_rejection`: The message was refused by sending policy. * - `domain_unverified`: The sending domain was not verified. * - `quota_exceeded`: The organization's send quota was reached. * - `recipient_not_allowed`: A recipient was not permitted for this send (for shared * onboarding-domain sends, recipients must be verified workspace members). * */ type EmailRejectionReason = "recipient_suppressed" | "transmission_failed" | "generation_failure" | "policy_rejection" | "domain_unverified" | "quota_exceeded" | "recipient_not_allowed"; /** * Payload of the email.rejected event. */ type EventEmailRejectedData = EventEmailBase & { rejection_reason: EmailRejectionReason; }; /** * The API rejected the email before delivery because of suppression, transmission failure, content, or policy. Fires once per recipient. */ type EventEmailRejected = { /** * Event type. */ type: "email.rejected"; /** * Time the rejection was recorded. */ timestamp: string; data: EventEmailRejectedData; }; /** * Payload of the email.scheduled event. */ type EventEmailScheduledData = EventEmailMessageBase & { /** * When the message is scheduled to send. */ scheduled_at: string; }; /** * The API accepted an email scheduled for a future time. Fires once per message when the schedule is created. */ type EventEmailScheduled = { /** * Event type. */ type: "email.scheduled"; /** * Time the send was scheduled. */ timestamp: string; data: EventEmailScheduledData; }; /** * Payload of the email.unsubscribed event. */ type EventEmailUnsubscribedData = EventEmailBase; /** * Recipient unsubscribed by clicking a tracked unsubscribe link in the email. Fires once per recipient. */ type EventEmailUnsubscribed = { /** * Event type. */ type: "email.unsubscribed"; /** * Time the unsubscribe was recorded. */ timestamp: string; data: EventEmailUnsubscribedData; }; /** * Payload of the email_mailbox.message_delivered event. */ type EventEmailMailboxMessageDeliveredData = { /** * ID of the delivered message. Per-recipient `email.*` events use this value as `email_id`. Use it to deduplicate events when you subscribe to both event families. */ message_id: EmailId; /** * ID of the mailbox the message was sent from. */ mailbox_id: MailboxId; /** * ID of the thread the message belongs to. */ thread_id: ThreadId; }; /** * Every recipient of a mailbox message reached a delivered state. This event fires once per message. The same send also emits one `email.delivered` event for each recipient. Choose one event family for each automation and deduplicate mailbox events by `message_id`. */ type EventEmailMailboxMessageDelivered = { /** * Event type. */ type: "email_mailbox.message_delivered"; /** * When the event occurred. */ timestamp: string; data: EventEmailMailboxMessageDeliveredData; }; /** * Payload of the email_mailbox.message_failed event. */ type EventEmailMailboxMessageFailedData = { /** * ID of the failed message. Per-recipient `email.*` events use this value as `email_id`. Use it to deduplicate events when you subscribe to both event families. */ message_id: EmailId; /** * ID of the mailbox the message was sent from. */ mailbox_id: MailboxId; /** * ID of the thread the message belongs to. */ thread_id: ThreadId; /** * Why the message reached a terminal delivery failure. */ reason: string; }; /** * A mailbox message reached a terminal delivery failure. This event fires once per message. The same send also emits per-recipient `email.*` events. Choose one event family for each automation and deduplicate mailbox events by `message_id`. */ type EventEmailMailboxMessageFailed = { /** * Event type. */ type: "email_mailbox.message_failed"; /** * When the event occurred. */ timestamp: string; data: EventEmailMailboxMessageFailedData; }; /** * Identifiers, threading details, authentication results, and extracted text for a received mailbox message. The thread-message endpoints provide the original source during its 30-day retention window. */ type EventEmailMailboxMessageReceivedData = { /** * ID of the received message. The corresponding `email.received` event uses this value as `inbound_message_id`. Use it to deduplicate events when you subscribe to both event families. */ message_id: InboundEmailMessageId; /** * ID of the mailbox that received the message. */ mailbox_id: MailboxId; /** * ID of the thread the message was filed into. */ thread_id: ThreadId; /** * ID (ein_…) of the explicit inbound route that matched, or null when the message was delivered by the virtual exact-address route. */ route_id?: string | null; /** * Envelope-from address. */ from: string; /** * Recipient addresses the message was sent to. */ to: Array; /** * Subject line as received, or null when the message had no subject. */ subject: string | null; /** * Plain-text body with quoted history removed, capped at 64 KB. See `truncated_text` to check whether the value was truncated. Null when extraction produces no text. */ extracted_text?: string | null; /** * True when `extracted_text` was truncated to the 64 KB cap. Fetch the full text through the thread-member endpoint. */ truncated_text?: boolean; /** * Number of attachments on the message. Attachment content remains available during the 30-day original-source retention window. */ attachment_count: number; /** * Whether the sender of the received message was authenticated. * * - `pass`: the sender's identity was verified. * - `fail`: it was checked and did not verify. * - `unknown`: no verdict is available, so do not treat the sender * as verified. * */ authentication?: "pass" | "fail" | "unknown" | null; /** * Whether SPF passed for the sender, or null when no verdict was computable. */ spf_pass?: boolean | null; /** * Whether DKIM passed for the sender, or null when no verdict was computable. */ dkim_pass?: boolean | null; /** * Whether DMARC passed for the sender, or null when no verdict was computable. */ dmarc_pass?: boolean | null; }; /** * An email arrived in a mailbox. The same message also emits an `email.received` event. The two events can arrive in either order. Choose one event family for each automation and deduplicate mailbox events by `message_id`. */ type EventEmailMailboxMessageReceived = { /** * Event type. */ type: "email_mailbox.message_received"; /** * When the event occurred. */ timestamp: string; data: EventEmailMailboxMessageReceivedData; }; /** * Payload of the email_mailbox.message_sent event. */ type EventEmailMailboxMessageSentData = { /** * ID of the sent message. Per-recipient `email.*` events use this value as `email_id`. Use it to deduplicate events when you subscribe to both event families. */ message_id: EmailId; /** * ID of the mailbox the message was sent from. */ mailbox_id: MailboxId; /** * ID of the thread the message belongs to. */ thread_id: ThreadId; }; /** * A mailbox message was handed off for delivery. This event fires once per message. The same send also emits per-recipient `email.*` events. Choose one event family for each automation and deduplicate mailbox events by `message_id`. */ type EventEmailMailboxMessageSent = { /** * Event type. */ type: "email_mailbox.message_sent"; /** * When the event occurred. */ timestamp: string; data: EventEmailMailboxMessageSentData; }; /** * Payload of the email_mailbox.suspended event. */ type EventEmailMailboxSuspendedData = { /** * ID of the suspended mailbox. */ mailbox_id: MailboxId; /** * Why the mailbox was suspended. */ reason: string; }; /** * This mailbox-suspension event is reserved and is not currently emitted. */ type EventEmailMailboxSuspended = { /** * Event type. */ type: "email_mailbox.suspended"; /** * When the event occurred. */ timestamp: string; data: EventEmailMailboxSuspendedData; }; /** * Payload of the email_mailbox.thread_created event. */ type EventEmailMailboxThreadCreatedData = { /** * ID of the thread. */ thread_id: ThreadId; /** * ID of the mailbox the thread was created in. */ mailbox_id: MailboxId; /** * Subject of the first message in the thread, or null when it had none. */ subject: string | null; /** * Which direction created the thread. */ initiated_by: "inbound" | "outbound"; }; /** * A new thread was created in a mailbox, from either direction. */ type EventEmailMailboxThreadCreated = { /** * Event type. */ type: "email_mailbox.thread_created"; /** * When the event occurred. */ timestamp: string; data: EventEmailMailboxThreadCreatedData; }; /** * Payload of the email_suppression.created event. */ type EventEmailSuppressionCreatedData = { /** * The suppression entry that was created. */ suppression_id: SuppressionId; /** * The recipient address that was added to the suppression list. */ email: string; /** * Why the address was suppressed. New values may be added over time; treat unknown values as informational. * */ reason: string; /** * The workspace the suppression belongs to. */ workspace_id: WorkspaceId; }; /** * An email address was added to the workspace's suppression list (manually, via complaint, or via hard bounce). */ type EventEmailSuppressionCreated = { /** * Event type. */ type: "email_suppression.created"; /** * When the event occurred. */ timestamp: string; data: EventEmailSuppressionCreatedData; }; /** * Identity fields shared by every SMS lifecycle event payload. */ type EventSmsBase = { /** * ID of the SMS message. */ sms_id: SmsMessageId; /** * ID of the workspace that owns this event. */ workspace_id: WorkspaceId; /** * Where the message went. On an outbound message this is the recipient's phone number in E.164 format; on an inbound one it is your own number that received it. * */ to: string; /** * Where the message came from. On an outbound message this is the sender you sent it from: an E.164 number, an alphanumeric sender ID, or a short code. On an inbound one it is the phone number that sent it to you. * */ from: string; /** * Tags provided on the send request, echoed on every event for the message so you can route and correlate without an extra lookup. Null when the message carried no tags. * */ tags: Array | null; /** * The metadata object provided on the send request, echoed on every event for the message so you can correlate events with your own records. Null when the message carried no metadata. * */ metadata: { [key: string]: unknown; } | null; /** * Message cost as of this event, split into the platform charge and any * third-party fees passed through. Null on an event that priced nothing. * * Components are named so you can merge them per component rather than replacing the * object: webhook delivery is not ordered, so an older event arriving late would * otherwise overwrite a newer figure. Take the latest `occurred_at` you have seen for * each component. `amount` is the sum of the components in this payload and does not * represent a settled total. * */ cost?: MessageCost; }; /** * Payload of the sms.accepted event. */ type EventSmsAcceptedData = EventSmsBase & { /** * Segment breakdown used to calculate the message charge. */ segments: SmsSegments; }; /** * The API accepted the SMS send request and queued it for processing. */ type EventSmsAccepted = { /** * Event type. */ type: "sms.accepted"; /** * Time the API accepted the request. */ timestamp: string; data: EventSmsAcceptedData; }; /** * Payload of the sms.delivered event. */ type EventSmsDeliveredData = EventSmsBase & { /** * Carrier that delivered the message. Absent when the carrier does not report one. */ carrier?: string; /** * Mobile country code and mobile network code of the carrier. Absent when the carrier does not report one. */ mcc_mnc?: string; }; /** * The carrier confirmed delivery of the message to the recipient handset. */ type EventSmsDelivered = { /** * Event type. */ type: "sms.delivered"; /** * Time the carrier confirmed delivery. */ timestamp: string; data: EventSmsDeliveredData; }; /** * Payload of the sms.expired event. */ type EventSmsExpiredData = EventSmsBase & { /** * Why the message was still undelivered when its validity period elapsed. Typically `unreachable`, the handset having stayed off or out of coverage for the whole window. */ error: SmsError; }; /** * The message's validity period elapsed before it could be delivered. */ type EventSmsExpired = { /** * Event type. */ type: "sms.expired"; /** * Time the message expired. */ timestamp: string; data: EventSmsExpiredData; }; /** * Payload of the sms.failed event. */ type EventSmsFailedData = EventSmsBase & { /** * Why the message terminally failed. */ error: SmsError; }; /** * Message delivery failed permanently. */ type EventSmsFailed = { /** * Event type. */ type: "sms.failed"; /** * Time the failure was recorded. */ timestamp: string; data: EventSmsFailedData; }; /** * Always `sms.received` for this event. */ type SmsReceivedEventType = "sms.received"; /** * Payload of the sms.received event. */ type EventSmsReceivedData = EventSmsBase & unknown & { /** * The message body, so you can act on it without a follow-up read. Absent when the message carried only attachments and no text of its own. * */ text?: string; /** * Segment breakdown of the received body. */ segments: SmsSegments; /** * Carrier the message came in over. Absent where the carrier does not report one. */ carrier?: string; /** * Mobile country code and mobile network code of the carrier. Absent when not known. */ mcc_mnc?: string; /** * Subject line. Absent when the message carried none. */ subject?: string; }; /** * A message was received on one of your numbers. */ type EventSmsReceived = { type: SmsReceivedEventType; /** * Time the sender sent the message. */ timestamp: string; data: EventSmsReceivedData; }; /** * Payload of the sms.rejected event. */ type EventSmsRejectedData = EventSmsBase & { /** * Why the message was rejected before reaching the carrier. */ error: SmsError; }; /** * The API rejected the message before sending it to the carrier because of an invalid destination, suppression, or content or policy restriction. */ type EventSmsRejected = { /** * Event type. */ type: "sms.rejected"; /** * Time the rejection was recorded. */ timestamp: string; data: EventSmsRejectedData; }; /** * Payload of the sms.sent event. */ type EventSmsSentData = EventSmsBase & { /** * Carrier that handled the message. Absent when the carrier does not report one. */ carrier?: string; /** * Mobile country code and mobile network code of the carrier. Absent when the carrier does not report one. */ mcc_mnc?: string; }; /** * The API handed the message to the carrier for delivery. */ type EventSmsSent = { /** * Event type. */ type: "sms.sent"; /** * Time the message was handed to the carrier. */ timestamp: string; data: EventSmsSentData; }; /** * Payload of the sms.undelivered event. */ type EventSmsUndeliveredData = EventSmsBase & { /** * Why the message was not delivered. */ error: SmsError; }; /** * The carrier reported a non-permanent failure to deliver the message. */ type EventSmsUndelivered = { /** * Event type. */ type: "sms.undelivered"; /** * Time the non-delivery was recorded. */ timestamp: string; data: EventSmsUndeliveredData; }; /** * Identity fields shared by every Verify lifecycle event payload. */ type EventVerifyBase = { /** * ID of the verification session. */ verification_id: VerificationId; /** * ID of the workspace that owns this event. */ workspace_id: WorkspaceId; /** * The recipient identity of the verification session (email address, phone number, or both), echoed on every event so you can correlate without an extra lookup. An individual attempt reports the single address it was dispatched to in its own `address` field. */ to: VerificationTo; /** * The metadata object provided when the verification was created, echoed on every event for the session so you can correlate events with your own records. Null when the verification carried no metadata. * */ metadata: { [key: string]: unknown; } | null; }; /** * Payload of the verify.attempt.delivered event. */ type EventVerifyAttemptDeliveredData = EventVerifyBase & { /** * The channel this attempt was sent on. */ channel: VerificationChannel$1; /** * The single address this attempt was dispatched to, an E.164 phone number or an email address. */ address: string; /** * Carrier that delivered the message, when the carrier network reports it. Always null for email, WhatsApp, and Telegram. */ carrier: string | null; /** * Mobile country code and mobile network code of the delivering carrier, when reported. Always null for email, WhatsApp, and Telegram. */ mcc_mnc: string | null; /** * Time delivery was confirmed. */ delivered_at: string; }; /** * The channel confirmed delivery of a one-time passcode to the recipient. */ type EventVerifyAttemptDelivered = { /** * Event type. */ type: "verify.attempt.delivered"; /** * Time delivery was confirmed. */ timestamp: string; data: EventVerifyAttemptDeliveredData; }; /** * Payload of the verify.attempt.sent event. */ type EventVerifyAttemptSentData = EventVerifyBase & { /** * The channel this attempt was sent on. */ channel: VerificationChannel$1; /** * The single address this attempt was dispatched to, an E.164 phone number or an email address. */ address: string; /** * The sender the passcode was sent from: a phone number, alphanumeric sender ID, short code, or email address. Null when the channel exposes no sender. */ from: string | null; /** * Time the passcode was dispatched. */ sent_at: string; }; /** * A one-time passcode was dispatched to the recipient on a channel. */ type EventVerifyAttemptSent = { /** * Event type. */ type: "verify.attempt.sent"; /** * Time the passcode was dispatched. */ timestamp: string; data: EventVerifyAttemptSentData; }; /** * Payload of the verify.attempt.undelivered event. */ type EventVerifyAttemptUndeliveredData = EventVerifyBase & { /** * The channel this attempt was sent on. */ channel: VerificationChannel$1; /** * The single address this attempt was dispatched to, an E.164 phone number or an email address. */ address: string; /** * Why the attempt failed to reach the recipient. */ reason: VerificationAttemptFailureReason$1; /** * Diagnostic text describing the failure, for display only. Null when none was reported. */ error: string | null; /** * Time the failure was recorded. */ failed_at: string; }; /** * A one-time passcode failed to deliver to the recipient. */ type EventVerifyAttemptUndelivered = { /** * Event type. */ type: "verify.attempt.undelivered"; /** * Time the failure was recorded. */ timestamp: string; data: EventVerifyAttemptUndeliveredData; }; /** * Payload of the verify.verification.created event. */ type EventVerifyVerificationCreatedData = EventVerifyBase & { /** * The first channel of the verification's resolved channel plan. */ channel: VerificationChannel$1; /** * The verification's state at creation, always `pending`. Open enum for forward compatibility. */ status: string; /** * Time the verification session was created. */ created_at: string; }; /** * A verification session was created and its first one-time passcode is being sent. */ type EventVerifyVerificationCreated = { /** * Event type. */ type: "verify.verification.created"; /** * Time the verification session was created. */ timestamp: string; data: EventVerifyVerificationCreatedData; }; /** * Always `verify.verification.failed` for this event. */ type VerifyVerificationFailedEventType = "verify.verification.failed"; /** * Payload of the verify.verification.failed event. */ type EventVerifyVerificationFailedData = EventVerifyBase & { /** * The verification's state, always `failed`. Open enum for forward compatibility. */ status: string; /** * Why the verification ended. Always `undeliverable` on this event: no planned channel delivered a passcode. */ reason: VerificationTerminalReason$1; /** * The last channel the verification tried, the one whose failure left it with nowhere else to go. Null when no channel was attributed. */ channel: VerificationChannel$1 | null; /** * Why that last send did not deliver. This is the actionable half of the event: `not_billable` means the workspace balance could not cover the send, while the delivery reasons point at the recipient or the channel. */ last_attempt_reason: VerificationAttemptFailureReason$1; /** * Time the verification was resolved. */ failed_at: string; }; /** * The verification ended without the recipient receiving a passcode: every planned channel reported that its send would not arrive. */ type EventVerifyVerificationFailed = { type: VerifyVerificationFailedEventType; /** * Time the verification was resolved. */ timestamp: string; data: EventVerifyVerificationFailedData; }; /** * Payload of the verify.verification.verified event. */ type EventVerifyVerificationVerifiedData = EventVerifyBase & { /** * The verification's state, always `verified`. Open enum for forward compatibility. */ status: string; /** * The channel whose passcode the recipient confirmed, the channel that converted. Null when the verification was resolved without attributing a channel. */ channel: VerificationChannel$1 | null; /** * Time the verification was verified. */ verified_at: string; }; /** * The verification was successfully resolved: the recipient confirmed the correct code. */ type EventVerifyVerificationVerified = { /** * Event type. */ type: "verify.verification.verified"; /** * Time the verification was verified. */ timestamp: string; data: EventVerifyVerificationVerifiedData; }; type VoiceCallId = string; type VoiceSessionId = string; /** * Whether the call originated from your PBX (outbound) or arrived from a remote party (inbound). */ type VoiceCallDirection = "inbound" | "outbound"; /** * Identity fields shared by every voice call lifecycle event payload. */ type EventVoiceBase = { /** * ID of the call record. */ call_id: VoiceCallId; /** * Session identifier shared across all legs of a multi-party or transferred call. Use this to correlate related call records. Null when session correlation is not available for the call. */ session_id?: VoiceSessionId | null; /** * ID of the workspace that owns this event. */ workspace_id: WorkspaceId; direction: VoiceCallDirection; /** * Calling party number in E.164 format. */ from: string; /** * Called party number in E.164 format. */ to: string; }; /** * Payload of the voice_call.answered event. */ type EventVoiceCallAnsweredData = EventVoiceBase; /** * The called party answered and media began flowing. */ type EventVoiceCallAnswered = { /** * Event type. */ type: "voice_call.answered"; /** * Time the call was answered. */ timestamp: string; data: EventVoiceCallAnsweredData; }; /** * Call status. * * A call that has ended carries one of: * * - `answered` means it connected and the far end picked up. * - `no_answer` means nobody picked up before the call timed out. * - `rejected` means it was refused rather than attempted. Either we turned it * away before dialing a carrier, in which case `rejection_reason` names the * check it failed where there was one, or the far end declined it. * - `failed` means it was attempted and did not work, and `sip_response_code` * is what came back. * - `unknown` means the outcome could not be determined. Contact support with * the call `id` if you see one. * * An active call carries `ringing` before it is picked up and `in_progress` * afterward. The call list's `status` filter takes any mix of the two sets. * * `busy` and `canceled` are reserved for incoming calls delivered to your own * numbers: `busy` for a called party that rejected the call as busy, `canceled` * for a caller who hung up before it was picked up. Neither is emitted yet and * both outcomes are reported as `failed` today. * */ type VoiceCallStatus = "answered" | "no_answer" | "busy" | "canceled" | "failed" | "rejected" | "unknown" | "ringing" | "in_progress"; /** * Payload of the voice_call.ended event. */ type EventVoiceCallEndedData = EventVoiceBase & { status: VoiceCallStatus; /** * Final SIP response code received from the carrier. Null when no SIP response was received, for example on timeout or DNS failure. */ sip_response_code: number | null; /** * Total call duration in milliseconds, measured from the first SIP `INVITE` to the `BYE` or final response. */ duration_ms: number; /** * Billable duration in milliseconds, measured from answer to call end. Zero for unanswered calls. */ billable_ms: number; }; /** * The call ended after either party hung up or call setup failed. */ type EventVoiceCallEnded = { /** * Event type. */ type: "voice_call.ended"; /** * Time either party hung up or call setup failed. */ timestamp: string; data: EventVoiceCallEndedData; }; /** * Payload of the voice_call.initiated event. */ type EventVoiceCallInitiatedData = EventVoiceBase; /** * Call routing began. */ type EventVoiceCallInitiated = { /** * Event type. */ type: "voice_call.initiated"; /** * Time the call was initiated. */ timestamp: string; data: EventVoiceCallInitiatedData; }; /** * Identity fields shared by every WhatsApp lifecycle event payload. */ type EventWhatsAppBase = { /** * ID of the WhatsApp message. */ whatsapp_id: WhatsAppMessageId; /** * ID of the workspace that owns this event. */ workspace_id: WorkspaceId; /** * Whether the message was sent by the business (`outbound`) or received from the contact (`inbound`). */ direction: "outbound" | "inbound"; /** * Sender of the message. On outbound messages, the business number it was sent from; on inbound, the WhatsApp contact. */ from: WhatsAppAddress; /** * Recipient of the message. On outbound messages, the WhatsApp contact; on inbound, the business number. */ to: WhatsAppAddress; /** * Tags provided on the send request, echoed on every event for the message. Null when the message carried no tags. * */ tags: Array | null; /** * The metadata object provided on the send request, echoed on every event for the message. Null when the message carried no metadata. * */ metadata: { [key: string]: unknown; } | null; }; /** * Payload of the whatsapp.accepted event. */ type EventWhatsAppAcceptedData = EventWhatsAppBase; /** * The API accepted and charged the send request. */ type EventWhatsAppAccepted = { /** * Event type. */ type: "whatsapp.accepted"; /** * Time the API accepted and charged the send request. */ timestamp: string; data: EventWhatsAppAcceptedData; }; /** * Payload of the whatsapp.delivered event. */ type EventWhatsAppDeliveredData = EventWhatsAppBase; /** * The message was delivered to the recipient's device. */ type EventWhatsAppDelivered = { /** * Event type. */ type: "whatsapp.delivered"; /** * Time the message was delivered to the recipient's device. */ timestamp: string; data: EventWhatsAppDeliveredData; }; /** * Payload of the whatsapp.failed event. */ type EventWhatsAppFailedData = EventWhatsAppBase & { /** * Why the message terminally failed. */ error: WhatsAppError; }; /** * Message delivery failed permanently. */ type EventWhatsAppFailed = { /** * Event type. */ type: "whatsapp.failed"; /** * Time the failure was recorded. */ timestamp: string; data: EventWhatsAppFailedData; }; /** * Payload of the whatsapp.read event. */ type EventWhatsAppReadData = EventWhatsAppBase; /** * The recipient read the message. */ type EventWhatsAppRead = { /** * Event type. */ type: "whatsapp.read"; /** * Time the recipient read the message. */ timestamp: string; data: EventWhatsAppReadData; }; /** * Event type. */ type WhatsAppReceivedEventType = "whatsapp.received"; /** * Payload of the whatsapp.received event. Carries the message's content so a subscriber can act on it without reading the message back. * */ type EventWhatsAppReceivedData = EventWhatsAppBase & { /** * Text the contact sent. */ text?: WhatsAppText; /** * Image the contact sent. */ image?: WhatsAppImage; /** * Video the contact sent. */ video?: WhatsAppVideo; /** * Audio the contact sent. */ audio?: WhatsAppAudio; /** * Sticker the contact sent. */ sticker?: WhatsAppSticker; /** * Document the contact sent. */ document?: WhatsAppDocument; /** * Location the contact sent. */ location?: WhatsAppLocation; /** * Set when the contact sent content the API does not model, naming the WhatsApp content type. * */ unsupported?: WhatsAppUnsupported; }; /** * A contact sent the business a WhatsApp message. */ type EventWhatsAppReceived = { type: WhatsAppReceivedEventType; /** * Time the contact sent the message, as reported by WhatsApp. */ timestamp: string; data: EventWhatsAppReceivedData; }; /** * Payload of the whatsapp.rejected event. */ type EventWhatsAppRejectedData = EventWhatsAppBase & { /** * Why the message was rejected before sending. */ error: WhatsAppError; }; /** * The API rejected the message before sending it to WhatsApp because the recipient is on the workspace suppression list, the wallet has insufficient balance, or the destination is unpriced. The message is not sent or charged. */ type EventWhatsAppRejected = { /** * Event type. */ type: "whatsapp.rejected"; /** * Time the rejection was recorded. */ timestamp: string; data: EventWhatsAppRejectedData; }; /** * Payload of the whatsapp.sent event. */ type EventWhatsAppSentData = EventWhatsAppBase; /** * The API handed the message to Meta for delivery. */ type EventWhatsAppSent = { /** * Event type. */ type: "whatsapp.sent"; /** * Time the API handed the message to Meta for delivery. */ timestamp: string; data: EventWhatsAppSentData; }; /** * Physical type of a phone number. New number types may be added over time, so treat unrecognized values as supported types rather than errors. */ type NumberType$1 = "mobile" | "local" | "national" | "short_code" | "short_code_fteu" | "toll_free" | (string & {}); /** * Channel capability supported by a phone number. New capabilities may be added over time, so treat unrecognized values as supported capabilities rather than errors. */ type NumberCapability$1 = "sms" | "mms" | "voice" | (string & {}); /** * Where this number stands with the ownership paperwork its country requires before it may carry traffic. Present only for a number whose country requires any, so its absence means no paperwork was ever asked for and this number is unconditionally usable. Absent as well when the requirement cannot be established right now, since reporting either answer would state something about your paperwork that has not been checked. * */ type NumberOwnership = { /** * Whether the paperwork is accepted. Read `next` for what advances it while this is false. Whether sending is currently refused is reported by `blocked_at` instead: a number bought before its country asked for anything is unsatisfied and still usable until a review says otherwise. * */ readonly satisfied: boolean; /** * When the number stopped being able to carry traffic, and null while it can. Always null when `satisfied` is true, but null does not imply it: a number whose country began asking after you bought it is usable with its paperwork still outstanding. A number can also arrive blocked, and one that was usable can be blocked again if its approval is withdrawn. * */ readonly blocked_at?: string | null; /** * What you do about it, in the order to do it. Empty only when `satisfied` is true, so while anything is outstanding there is always at least one step. When what you already sent is being reviewed and nothing is needed from you, that step has kind `wait` and says so. Re-read it after each call rather than caching the first list you saw. * */ readonly next: Array; }; type Number = { /** * Identifier of this allocated number. Pass it as `number_id` to read this number, or to release it when kind is dedicated. */ readonly id: AllocatedNumberId; /** * How this number is allocated. `dedicated` is allocated to this workspace alone and billed as a subscription. `shared` is a shortcode allocated to several workspaces at once and managed by us. */ readonly kind: "dedicated" | "shared"; /** * Phone number in E.164 format. */ readonly number: string; readonly country_code: CountryCode; /** * Physical type of this phone number. */ readonly number_type: NumberType$1; /** * Channel capabilities supported by this number. */ readonly capabilities: Array; /** * Whether this number can carry traffic. * * - `active` means this number is allocated to your workspace and usable. * - `pending_compliance` means this number is allocated to your workspace and billed, * but it cannot carry traffic until the ownership paperwork its country requires is * accepted. Read `ownership.next` for what advances it, and re-read later if * `ownership` is momentarily `null`. * - `released` means this number is no longer allocated to your workspace. * * An allocated number is not always enough to send from it: some destination * countries also require an approved registration for the sender. * */ readonly status: "active" | "pending_compliance" | "released"; /** * When this number was allocated to your workspace. */ readonly allocated_at: string; /** * When this number was released. `null` while it is still allocated to your workspace. */ readonly released_at?: string | null; /** * Where this number stands with the ownership paperwork its country requires. `null` when the country requires none, which is the usual case: a number with no `ownership` object is usable as soon as it is allocated. Also `null` when that standing cannot be established right now; `status` still reads `pending_compliance` while the number is blocked, so re-read this field rather than caching its absence. We manage the paperwork for shared short codes, so this field is always `null` for them. * */ readonly ownership?: NumberOwnership | null; }; type AvailableNumber = { /** * Phone number in E.164 format. */ number: string; country_code: CountryCode; /** * Physical type of this phone number. */ number_type: NumberType$1; /** * Channel capabilities supported by this number. */ capabilities: Array; }; /** * Lifecycle state of a number purchase order: * * - `charging`: Securing funds. * - `ordering`: Placing the order with the carrier. * - `pending`: The carrier accepted the order and is provisioning the number. * - `completed`: Your workspace owns the number. * - `failed`: The purchase did not complete. * * A setup fee already charged is non-refundable. Contact support about a failed * order. */ type NumbersOrderStatus$1 = "charging" | "ordering" | "pending" | "completed" | "failed" | (string & {}); type NumbersOrderId = string; type NumbersDedicatedAllocationId = string; type NumbersOrder = { /** * Identifier of this purchase order. */ readonly id: NumbersOrderId; /** * The number being acquired, in E.164 format. */ readonly number: string; readonly country_code: CountryCode; /** * Physical type of the number being acquired. */ readonly number_type: NumberType$1; readonly status: NumbersOrderStatus$1; /** * Identifier of the number this order produced, set when `status` is `completed`. Pass it as `number_id` to `GET /v1/numbers/{number_id}` or `DELETE /v1/numbers/{number_id}`. `null` until the order completes. * */ readonly number_id?: NumbersDedicatedAllocationId | null; /** * Human-readable reason the purchase failed. `null` unless status is failed. An order can fail some time after it was created, so `updated_at` tells you when the failure was recorded rather than when the order was placed. * */ readonly failure_reason?: string | null; /** * When the purchase completed and the number became owned (status completed). `null` for orders still in progress or failed. * */ readonly completed_at?: string | null; readonly created_at: string; readonly updated_at: string; }; type NumbersOrderCreate = { /** * The number to acquire, in E.164 format, as returned by `GET /v1/numbers/available`. */ number: string; }; type SipTrunkId = string; /** * Why we refused the call before dialing a carrier. Every refusal is signalled * to your PBX as `503`, so `sip_response_code` alone cannot tell these causes * apart. This field is where the cause lives. * * Most of them you can fix yourself: * * - `source_not_allowed`: The call came from an IP address that is not in the * trunk's allowed-address list. Add the address your PBX sends from. * - `caller_id_not_verified`: The number in the `From` header is not a verified * caller ID for this workspace. Verify it, or present a number you have * already verified. * - `destination_not_enabled`: You have not turned on calling to this * destination country. Enable it in your voice destination settings. * - `insufficient_balance`: Your wallet did not cover the call. Top up, or turn * on automatic top-ups. * - `daily_spend_exceeded`: The call would have passed your organization's daily * voice spend limit. The limit resets at the start of the next UTC day. * - `concurrent_calls_exceeded`: You already have as many calls in progress as * your account allows. Wait for one to end, or ask support to raise the limit. * - `calls_per_second_exceeded`: You placed calls faster than your account * allows. Slow the rate you dial at, then retry. * * For all other reasons, contact support and provide the call `id`: * * - `routing_not_configured`: No dial plan is attached to this trunk yet. * Expected on a new trunk. * - `no_route_found`: A dial plan is attached, but no rule in it covers this * destination. * - `destination_blocked`: The destination is blocked by our routing * configuration. * - `call_not_permitted`: The call could not be priced for your account. * */ type VoiceCallRejectionReason = "source_not_allowed" | "caller_id_not_verified" | "routing_not_configured" | "no_route_found" | "destination_blocked" | "destination_not_enabled" | "insufficient_balance" | "daily_spend_exceeded" | "concurrent_calls_exceeded" | "calls_per_second_exceeded" | "call_not_permitted"; type VoiceMediaQuality = { /** * Mean opinion score, the single number for how the call sounded, from 1 (unintelligible) to 5 (as good as being in the same room). Anything at or above 4.0 is what most people would call a clear line, and below 3.5 is where callers start asking each other to repeat themselves. The three other fields are the impairments that move it. * */ readonly mos: number; /** * Variation in the arrival time of the audio packets, in milliseconds. Audio arriving unevenly is heard as choppiness even when no packets are lost at all. */ readonly jitter_ms: number; /** * Percentage of audio packets that never arrived. Heard as brief gaps or clipped words, and the impairment that degrades a call fastest. */ readonly packet_loss_pct: number; /** * Round-trip time between the two ends, in milliseconds. It does not distort the audio. Above roughly 300 ms, the two parties start talking over each other. */ readonly round_trip_time_ms: number; }; /** * What was charged for a call, split into the components that make it up. * */ type VoiceCallCost = { /** * Total charged, as a decimal string: the sum of the components below. Net of tax, which applies to your wallet balance rather than to an individual charge. * */ readonly amount: string; /** * ISO 4217 currency code. Every component is denominated in this currency. */ readonly currency_code: CurrencyCode; /** * What we charged to carry the call to the destination network, as a decimal string. `null` until this component is priced. * */ readonly outbound_amount: string | null; /** * What we charged to receive the call from the originating network, as a decimal string. Only a call that arrived at your number can carry it. `null` until this component is priced. * */ readonly inbound_amount: string | null; /** * What we charged for handling the call itself, as a decimal string. A call is charged for handling once, however many legs it has, so only one leg's record carries it. `null` until this component is priced. * */ readonly call_handling_amount: string | null; }; type VoiceCall = { /** * Unique identifier for this call record. */ readonly id: VoiceCallId; /** * Session identifier shared across all legs of a multi-party or transferred call. Use this to correlate related call records. `null` when session correlation is not available for the call. */ readonly session_id?: VoiceSessionId | null; readonly workspace_id: WorkspaceId; readonly direction: VoiceCallDirection; /** * Calling party number in E.164 format. */ readonly from: string; /** * Called party number in E.164 format. */ readonly to: string; /** * Who placed the call: the API key whose credentials it used, the integration acting for the workspace, or the user who placed it from a browser or the CLI. Absent when the call was admitted only by its source IP address, or when no actor was recorded. */ readonly actor?: Actor; /** * Identifier of the SIP trunk that originated this call. `null` when no trunk is associated. */ readonly sip_trunk_id?: SipTrunkId | null; readonly status: VoiceCallStatus; /** * Final SIP response code received from the carrier. `null` when no SIP response was received, for example on timeout or DNS failure. */ readonly sip_response_code?: number | null; /** * Why we refused the call before dialing a carrier. Absent when the call connected or failed at the carrier; see `sip_response_code` for the carrier response. */ readonly rejection_reason?: VoiceCallRejectionReason; /** * When the call was initiated. */ readonly started_at: string; /** * When the call was answered (`200` OK received). `null` for unanswered calls. */ readonly answered_at?: string | null; /** * When the call ended (BYE or final non-2xx response). `null` for calls that ended abnormally without a recorded end event. */ readonly ended_at?: string | null; /** * Total call duration in milliseconds, measured from the first INVITE to the BYE or final response. `null` while the call is still in progress and has no final duration yet. */ readonly duration_ms?: number | null; /** * Post-dial delay in milliseconds: how long the caller heard nothing between dialing and the phone starting to ring at the other end. High values are what callers experience as the call `not going through`. Absent when the call never rang, either because it failed first or because the carrier answered it immediately. * */ readonly pdd_ms?: number; /** * Billable duration in milliseconds, measured from answer to call end. Zero for unanswered calls, and `null` while the call is still in progress. */ readonly billable_ms?: number | null; /** * How the audio sounded, as opposed to whether the call connected. Absent when the call carried no audio, or when the far end reported nothing to measure from. */ media_quality?: VoiceMediaQuality; /** * What the call cost, net of tax, at full precision, split into the components that make it up. Absent until the call has been rated; unanswered or unpriced calls have no cost. */ cost?: VoiceCallCost; }; type PublishRealtimeAppEventData = { body: RealtimePublish; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * ID of the Realtime app (`rap_` prefix), as returned when the app was created. */ realtime_app_id: RealtimeAppId; }; query?: never; url: "/v1/realtime/apps/{realtime_app_id}/events"; }; type PublishRealtimeAppBatchData = { body: RealtimeBatchPublish; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * ID of the Realtime app (`rap_` prefix), as returned when the app was created. */ realtime_app_id: RealtimeAppId; }; query?: never; url: "/v1/realtime/apps/{realtime_app_id}/batch-events"; }; type ListRealtimeAppChannelsData = { body?: never; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; }; path: { /** * ID of the Realtime app (`rap_` prefix), as returned when the app was created. */ realtime_app_id: RealtimeAppId; }; query?: { /** * Only channels whose name starts with this prefix (for example, `presence-`). */ prefix?: string; /** * Per-channel attributes to include. Repeatable. Requesting `member_count` without a presence-channel `prefix`, or `connection_count` when the app's connection-counting flag is off, returns a validation error (400). */ include?: Array; }; url: "/v1/realtime/apps/{realtime_app_id}/channels"; }; type GetRealtimeAppChannelData = { body?: never; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; }; path: { /** * ID of the Realtime app (`rap_` prefix), as returned when the app was created. */ realtime_app_id: RealtimeAppId; /** * Name of the Realtime channel to retrieve. */ channel_name: RealtimeChannelName; }; query?: { /** * Attributes to include. Repeatable. Requesting `member_count` for a non-presence channel, or `connection_count` when the app's connection-counting flag is off, returns a validation error (400). */ include?: Array; }; url: "/v1/realtime/apps/{realtime_app_id}/channels/{channel_name}"; }; type SendRealtimeAppMemberEventData = { body: RealtimeMemberPublish; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * ID of the Realtime app (`rap_` prefix), as returned when the app was created. */ realtime_app_id: RealtimeAppId; /** * The member to deliver the event to. */ member_id: RealtimeMemberId; }; query?: never; url: "/v1/realtime/apps/{realtime_app_id}/members/{member_id}/events"; }; type ListEmailMessagesData = { body?: never; path?: never; query?: { /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; /** * Limits the response to resources created at or after this timestamp. Combine it with `created_before` to select a time window. Use an RFC 3339 timestamp with a timezone offset. */ created_after?: string; /** * Limits the response to resources created before this timestamp. Combine it with `created_after` to select a time window. Use an RFC 3339 timestamp with a timezone offset. */ created_before?: string; /** * Filter by aggregate delivery status. */ status?: EmailMessageStatus; /** * Filter by tag. Accepts `name` to match any message carrying that tag name, or `name:value` to match a specific tag pair (for example `category:welcome`). Repeat the parameter to add more tags. A message must match every tag listed to be returned. * */ tag?: Array; /** * Filter by category. */ category?: EmailMessageCategory; /** * Filter by recipient address. Exact match against any `to`/`cc`/`bcc` recipient on the message. The address is normalized to lowercase before comparison. * */ to?: string; /** * Filter by sender address. Exact match against the message `from` field. The address is normalized to lowercase before comparison. * */ from?: string; }; url: "/v1/email/messages"; }; type ListContactsData = { body?: never; path?: never; query?: { /** * Return the contact with exactly this email address (case-insensitive). Email is unique within a workspace, so this matches at most one contact. An empty value is a validation error, never an unfiltered page. */ email?: string; /** * Return the contacts with exactly this phone number in international E.164 form. Repeat the parameter to match any of up to 50 numbers. Set `limit` to at least the number of values you pass. The default `limit` is 25, and a page cut short by it looks exactly like numbers that matched nothing. Different identifier parameters still combine with AND, so `phone_number=a&phone_number=b&email=c` asks for a contact whose phone number is `a` or `b` and whose email is `c`. Encode the leading plus sign as `%2B` (an unencoded `+` arrives as a space and is rejected). Phone numbers are unique within a workspace, so each value matches at most one contact. Non-canonical forms of the same number match the contact they canonicalize to; a value that is not a phone number shape, or an empty value, is a validation error, never an unfiltered page. */ phone_number?: Array; /** * Return the contact with exactly this external_id (your own identifier for the contact). Unique within a workspace, so this matches at most one contact. An empty value is a validation error, never an unfiltered page. */ external_id?: string; /** * Case-insensitive substring match against the contact's email address, first name, last name, or phone number. Phone matching is over the digits of the international form, so a full pasted number, a formatted number, or trailing digits all match; a national form with a leading trunk zero does not. */ q?: string; /** * Filter to contacts that have a specific identifier on file. */ identifier?: ContactIdentifierFilter; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; /** * When true, the response includes a `total` field with the total number of items matching the request's filters across all pages. */ include_total?: boolean; }; url: "/v1/contacts"; }; type CreateContactData = { body: ContactCreateRequest; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/contacts"; }; type CreateContactBatchData = { body: ContactUpsertRequest; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/contacts/batch"; }; type UpdateContactData = { body: ContactUpdateRequest; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * ID of the contact to update. */ contact_id: ContactId; }; query?: never; url: "/v1/contacts/{contact_id}"; }; type ListContactPropertiesData = { body?: never; path?: never; query?: { /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; }; url: "/v1/contact-properties"; }; type CreateContactPropertyData = { body: ContactPropertyCreateRequest; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/contact-properties"; }; type UpdateContactPropertyData = { body: ContactPropertyUpdateRequest; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * ID of the contact property to update. */ property_id: ContactPropertyId; }; query?: never; url: "/v1/contact-properties/{property_id}"; }; type ListAudiencesData = { body?: never; path?: never; query?: { /** * Case-insensitive substring match against the audience's name. */ q?: string; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; }; url: "/v1/audiences"; }; type CreateAudienceData = { body: AudienceCreateRequest; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/audiences"; }; type UpdateAudienceData = { body: AudienceUpdateRequest; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * ID of the audience to update. */ audience_id: AudienceId; }; query?: never; url: "/v1/audiences/{audience_id}"; }; type ListAudienceContactsData = { body?: never; path: { /** * ID of the audience whose contacts to list. */ audience_id: AudienceId; }; query?: { /** * Case-insensitive substring match against a contact's email address or the digits in its international phone number. */ q?: string; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; }; url: "/v1/audiences/{audience_id}/contacts"; }; type AssignAudienceContactsData = { body: AudienceContactsAddRequest; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * ID of the audience to add contacts to. */ audience_id: AudienceId; }; query?: never; url: "/v1/audiences/{audience_id}/contacts"; }; type UnassignAudienceContactsData = { body: AudienceContactsRemoveRequest; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * ID of the audience to remove contacts from. */ audience_id: AudienceId; }; query?: never; url: "/v1/audiences/{audience_id}/contacts/remove"; }; type ListSmsMessagesData = { body?: never; path?: never; query?: { /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; /** * Limits the response to resources created at or after this timestamp. Combine it with `created_before` to select a time window. Use an RFC 3339 timestamp with a timezone offset. */ created_after?: string; /** * Limits the response to resources created before this timestamp. Combine it with `created_after` to select a time window. Use an RFC 3339 timestamp with a timezone offset. */ created_before?: string; /** * Filter by direction. Omit for both. */ direction?: MessageDirection; /** * Keep only messages whose current `status` matches; repeat the parameter to match any of several. One of `scheduled`, `accepted`, `sent`, `delivered`, `undelivered`, `failed`, `rejected`, `canceled`, `expired`, or `received`. * */ status?: Array; /** * Keep only messages whose failure reason (`last_error.code`) matches; repeat the parameter to match any of several. One of `invalid_destination`, `unreachable`, `blocked_by_carrier`, `blocked_by_recipient`, `landline_unreachable`, `content_rejected`, `sender_unregistered`, `recipient_opted_out`, `provider_unavailable`, `insufficient_balance`, or `unknown`. * */ error_code?: Array; /** * Filter by category. */ category?: SmsMessageCategory; /** * Filter by recipient phone number (E.164 exact match). */ to?: string; /** * Filter by sender (E.164, alphanumeric, or short code; exact match). */ from?: string; /** * Filter by tag. Accepts `name` to match any message carrying that tag name, or `name:value` to match a specific tag pair (for example `category:welcome`). Repeat the parameter to add more tags. A message must match every tag listed to be returned. * */ tag?: Array; }; url: "/v1/sms/messages"; }; type ListSmsMessageEventsData = { body?: never; path: { /** * ID of the SMS message (`sms_` prefix), as returned when the message was accepted. */ message_id: SmsMessageId; }; query?: { /** * Filter by event type, such as `sms.delivered` or `sms.failed`. */ type?: string; }; url: "/v1/sms/messages/{message_id}/events"; }; type ListSmsTemplatesData = { body?: never; path?: never; query?: { /** * Keep only templates of this scope. Every SMS template is `system`, so `workspace` matches none. Omit for all. * */ scope?: TemplateScope; /** * Keep only templates whose `category` matches. Omit for all categories. */ category?: SmsMessageCategory; /** * Keep only templates available in this language, as a BCP-47 tag. Matches the template's `available_languages` entries exactly, with no fallback. * */ language?: LanguageTag; }; url: "/v1/sms/templates"; }; type ListSmsSuppressionsData = { body?: never; path?: never; query?: { /** * Return only suppressions for this exact subscriber number in E.164 form. Prefix matching is unsupported. * */ destination?: string; /** * Return only suppressions covering this sender. */ originator?: string; /** * Return only suppressions with this reason: * * - `keyword_stop`: The subscriber texted a stop keyword to the sender. * - `carrier_opted_out`: Their carrier reported the opt-out. * - `manual`: Added through the API or dashboard. * */ reason?: SmsSuppressionReasonFilter; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; }; url: "/v1/sms/suppressions"; }; type CreateSmsSuppressionData = { body: SmsSuppressionCreate; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/sms/suppressions"; }; type ListSmsKeywordRulesData = { body?: never; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; }; path?: never; query?: { /** * Keep only rules that apply in this country, as an ISO 3166-1 alpha-2 code. Omit for every country the default catalog covers, plus your own rules. * */ country?: string; /** * Keep only the rules that apply to this number of yours, in E.164 format or as a short code, ordered the way they are applied to an inbound message. * */ number?: string; /** * The country a sender is messaging from, as an ISO 3166-1 alpha-2 code. Use it with `number` to see what someone in that country gets, which can differ from what a local sender gets. Ignored without `number`. * */ from_country?: string; /** * Keep only rules for this operation. Omit for all of them. */ operation?: SmsKeywordOperation; /** * Keep only default rules (`system`) or only the rules you created (`workspace`). Omit for both. * */ scope?: SmsKeywordRuleScope; }; url: "/v1/sms/keyword-rules"; }; type CreateSmsKeywordRuleData = { body: SmsKeywordRuleCreate; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/sms/keyword-rules"; }; type UpdateSmsKeywordRuleData = { body: SmsKeywordRuleUpdate; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * ID of the default or workspace keyword rule, as returned by the list operation. */ id: SmsKeywordRuleId; }; query?: never; url: "/v1/sms/keyword-rules/{id}"; }; type GetSmsStatsSummaryData = { body?: never; path?: never; query?: { /** * Inclusive start of the window: a calendar day (YYYY-MM-DD) or an RFC 3339 instant rounded down to the hour. The `timezone` parameter makes a calendar day local and rounds an instant down to the local hour. Omit `timezone` to use UTC. When `timezone` is set, a numeric UTC offset such as `+05:45` is rejected; use a calendar day or a `Z` (UTC) instant. This value must use the same form as `to`. When omitted, it defaults to 30 days before `to` for day windows or 168 hours (7 days) before `to` for hour windows. * */ from?: string; /** * Inclusive end of the window: a calendar day (YYYY-MM-DD) or an RFC 3339 instant rounded down to the hour. The `timezone` parameter makes a calendar day local and rounds an instant down to the local hour. Omit `timezone` to use UTC. When `timezone` is set, a numeric UTC offset is rejected; use a calendar day or a `Z` (UTC) instant. This value must use the same form as `from`. When omitted, it defaults to today for day windows or the current hour for hour windows in that timezone. Day windows may not exceed 365 days; hour windows may not exceed 720 hours (30 days). * */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Restrict the statistics to a single originator (the sender address messages were sent from). Mutually exclusive with the other dimension filters (`country`, `category`, `carrier`); only one may be set per request. Matches the message `from`. * */ originator?: string; /** * Restrict the statistics to a single destination country, as an ISO 3166-1 alpha-2 code. Mutually exclusive with the other dimension filters (`originator`, `category`, `carrier`); only one may be set per request. * */ country?: string; /** * Restrict the statistics to a single category. Mutually exclusive with the other dimension filters (`originator`, `country`, `carrier`); only one may be set per request. * */ category?: string; /** * Restrict the statistics to a single delivery carrier. Mutually exclusive with the other dimension filters (`originator`, `country`, `category`); only one may be set per request. * */ carrier?: string; /** * Set to `previous_period` to also include the same statistics for the immediately preceding window of equal length, plus the change between the two, so you can show "+X% vs last period" without a second request. * */ compare?: StatsComparePeriod; }; url: "/v1/sms/stats/summary"; }; type GetSmsStatsDailyData = { body?: never; path?: never; query?: { /** * Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted. */ from?: string; /** * End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Restrict the statistics to a single originator (the sender address messages were sent from). Mutually exclusive with the other dimension filters (`country`, `category`, `carrier`); only one may be set per request. Matches the message `from`. * */ originator?: string; /** * Restrict the statistics to a single destination country, as an ISO 3166-1 alpha-2 code. Mutually exclusive with the other dimension filters (`originator`, `category`, `carrier`); only one may be set per request. * */ country?: string; /** * Restrict the statistics to a single category. Mutually exclusive with the other dimension filters (`originator`, `country`, `carrier`); only one may be set per request. * */ category?: string; /** * Restrict the statistics to a single delivery carrier. Mutually exclusive with the other dimension filters (`originator`, `country`, `category`); only one may be set per request. * */ carrier?: string; }; url: "/v1/sms/stats/daily"; }; type GetSmsStatsHourlyData = { body?: never; path?: never; query?: { /** * Start of the window (ISO 8601 instant), rounded down to the start of its hour and included. The boundary uses the local hour when `timezone` is set and the UTC hour otherwise. When `timezone` is set, a numeric UTC offset such as `+05:45` is rejected; use a `Z` (UTC) instant. Defaults to 7 days before `to` when omitted. */ from?: string; /** * End of the window (ISO 8601 instant), rounded down to the start of its hour and included. The boundary uses the local hour when `timezone` is set and the UTC hour otherwise, so both bounds are inclusive. When `timezone` is set, a numeric UTC offset is rejected; use a `Z` (UTC) instant. Defaults to the current hour when omitted. The window may not exceed 30 days (720 hours). */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Restrict the statistics to a single originator (the sender address messages were sent from). Mutually exclusive with the other dimension filters (`country`, `category`, `carrier`); only one may be set per request. Matches the message `from`. * */ originator?: string; /** * Restrict the statistics to a single destination country, as an ISO 3166-1 alpha-2 code. Mutually exclusive with the other dimension filters (`originator`, `category`, `carrier`); only one may be set per request. * */ country?: string; /** * Restrict the statistics to a single category. Mutually exclusive with the other dimension filters (`originator`, `country`, `carrier`); only one may be set per request. * */ category?: string; /** * Restrict the statistics to a single delivery carrier. Mutually exclusive with the other dimension filters (`originator`, `country`, `category`); only one may be set per request. * */ carrier?: string; }; url: "/v1/sms/stats/hourly"; }; type GetSmsStatsByOriginatorData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Metric to rank rows by, applied descending. Any lifecycle count or derived rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `accepted`. * */ sort?: SmsStatsSortMetric; /** * Maximum number of originator rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/sms/stats/originators"; }; type GetSmsStatsByCountryData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Metric to rank rows by, applied descending. Any lifecycle count or derived rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `accepted`. * */ sort?: SmsStatsSortMetric; /** * Maximum number of country rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/sms/stats/countries"; }; type GetSmsStatsByCategoryData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Metric to rank rows by, applied descending. Any lifecycle count or derived rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `accepted`. * */ sort?: SmsStatsSortMetric; /** * Maximum number of category rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/sms/stats/categories"; }; type GetSmsStatsByErrorCodeData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Metric to rank rows by, applied descending. Defaults to `failed`. Only lifecycle counts are sortable; this breakdown has no rates. * */ sort?: SmsStatsLifecycleSortMetric; /** * Maximum number of error-code rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/sms/stats/error-codes"; }; type GetSmsStatsByCarrierData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Metric to rank rows by, applied descending. Any lifecycle count or derived rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `accepted`. * */ sort?: SmsStatsSortMetric; /** * Maximum number of carrier rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/sms/stats/carriers"; }; type GetSmsStatsByTagData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to keep the window within the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Metric to rank rows by, applied descending. Any lifecycle count or derived rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `accepted`. * */ sort?: SmsStatsSortMetric; /** * Maximum number of tag rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also carries a `trend` array: a short per-bucket lifecycle-count series for that row over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns 422. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/sms/stats/tags"; }; type GetSmsStatsByStatusData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted. */ from?: string; /** * End date (inclusive) in YYYY-MM-DD, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; }; url: "/v1/sms/stats/statuses"; }; type GetSmsInboundStatsSummaryData = { body?: never; path?: never; query?: { /** * Inclusive start of the window, either a calendar day (YYYY-MM-DD) or an RFC 3339 instant rounded down to the hour. The form you use selects the grain the total is resolved at. Interpreted in `timezone`, or in UTC when `timezone` is omitted. Must use the same form as `to`. A numeric UTC offset (for example `+05:45`) is rejected when `timezone` is set; pass a calendar day or a `Z` instant instead. Defaults to 30 days before `to` for day windows, or 168 hours before `to` for hour windows. * */ from?: string; /** * Inclusive end of the window, in the same form as `from`. Defaults to today, or the current hour for an hour window. A day window may not exceed 365 days and an hour window 720 hours. * */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Set to `previous_period` to include the received-message count for the immediately preceding window of equal length. The response also includes the change between the two, so you can show "+X% vs last period" without a second request. * */ compare?: StatsComparePeriod; }; url: "/v1/sms/stats/inbound/summary"; }; type GetSmsInboundStatsDailyData = { body?: never; path?: never; query?: { /** * Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted. */ from?: string; /** * End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; }; url: "/v1/sms/stats/inbound/daily"; }; type GetSmsInboundStatsHourlyData = { body?: never; path?: never; query?: { /** * Start of the window (inclusive), an RFC 3339 instant truncated to the hour. Defaults to 7 days (168 hours) before `to` when omitted. */ from?: string; /** * End of the window (inclusive), an RFC 3339 instant truncated to the hour. Defaults to the current hour when omitted. Window may not exceed 720 hours. A numeric UTC offset (for example `+05:45`) is rejected when `timezone` is set; pass a calendar day or a `Z` instant instead. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; }; url: "/v1/sms/stats/inbound/hourly"; }; type GetSmsInboundStatsByCountryData = { body?: never; path?: never; query?: { /** * Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted. */ from?: string; /** * End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Maximum rows to return, ranked by volume. Defaults to 50; the maximum is 200, and asking for more returns 422 rather than silently returning fewer. */ limit?: number; }; url: "/v1/sms/stats/inbound/countries"; }; type GetSmsInboundStatsByOperatorData = { body?: never; path?: never; query?: { /** * Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted. */ from?: string; /** * End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Maximum rows to return, ranked by volume. Defaults to 50; the maximum is 200, and asking for more returns 422 rather than silently returning fewer. */ limit?: number; }; url: "/v1/sms/stats/inbound/operators"; }; type GetSmsInboundStatsByNumberData = { body?: never; path?: never; query?: { /** * Start date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted. */ from?: string; /** * End date (inclusive), YYYY-MM-DD. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Maximum rows to return, ranked by volume. Defaults to 50; the maximum is 200, and asking for more returns 422 rather than silently returning fewer. */ limit?: number; }; url: "/v1/sms/stats/inbound/numbers"; }; type CreatePhoneNumberLookupData = { body: PhoneNumberLookupRequest; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/lookup/phone-number"; }; type CreateEmailLookupData = { body: EmailLookupRequest; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/lookup/email"; }; type CreateVerificationData = { body: VerificationCreateRequest; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/verify/verifications"; }; type CreateVerificationCheckData = { body: VerificationCheckRequest; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/verify/verifications/check"; }; type CreateVerificationNextChannelData = { body: VerificationNextChannelRequest; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/verify/verifications/next-channel"; }; type ListWhatsAppMessagesData = { body?: never; path?: never; query?: { /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; /** * Limits the response to resources created at or after this timestamp. Combine it with `created_before` to select a time window. Use an RFC 3339 timestamp with a timezone offset. */ created_after?: string; /** * Limits the response to resources created before this timestamp. Combine it with `created_after` to select a time window. Use an RFC 3339 timestamp with a timezone offset. */ created_before?: string; /** * Filter by status. Repeat the parameter to match any of several statuses. */ status?: Array; /** * Filter by whether the business sent the message (`outbound`) or received it from the contact (`inbound`). * */ direction?: MessageDirection; /** * Filter by contact phone number (E.164 exact match). */ phone_number?: string; /** * Filter by business-scoped user ID (Meta identifier). */ bsuid?: string; /** * Filter by category. */ category?: WhatsAppTemplateCategory$1; /** * Filter by tag. Accepts `name` to match any message carrying that tag name, or `name:value` to match a specific tag pair (for example `category:welcome`). Repeat the parameter to add more tags. A message must match every tag listed to be returned. * */ tag?: Array; }; url: "/v1/whatsapp/messages"; }; type ListWhatsAppMessageEventsData = { body?: never; path: { /** * ID of the message, as returned in the send response's `id` field. */ message_id: WhatsAppMessageId; }; query?: { /** * Keep only events of this exact type (for example `whatsapp.delivered` or `whatsapp.failed`). Omit for the full timeline. * */ type?: WhatsAppEventType$1; }; url: "/v1/whatsapp/messages/{message_id}/events"; }; type GetEmailStatsDailyData = { body?: never; path?: never; query?: { /** * Start date (inclusive), `YYYY-MM-DD`. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted. */ from?: string; /** * End date (inclusive), `YYYY-MM-DD`. Interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Restrict the statistics to a single category: `transactional` or `marketing`. Mutually exclusive with the other dimension filters; only one may be set per request. * */ category?: string; /** * Restrict the statistics to a single sending domain (the part of the From address after @). Mutually exclusive with the other dimension filters; only one may be set per request. */ sending_domain?: string; /** * Restrict the statistics to a single tag. Use `name` to match any value of a tag, or `name:value` for a specific pair (for example `campaign:spring_launch`). Mutually exclusive with the other dimension filters; only one may be set per request. * */ tag?: string; /** * Restrict the statistics to a single sending IP. Mutually exclusive with the other dimension filters; only one may be set per request. A sending IP is assigned only after a message reaches delivery, so this filter reports delivery-side metrics only. Accepted, processed, rejected, complaint, and engagement counts are `0`, and processing latency is `null`. Complaint, open, and click rates are `0` when deliveries exist and `null` otherwise. * */ sending_ip?: string; /** * Restrict the statistics to a single recipient mailbox domain (the part of the recipient address after the `@`, for example `gmail.com`). Mutually exclusive with the other dimension filters; only one may be set per request. * */ recipient_domain?: string; /** * Restricts the statistics to one template, identified by its ID (`emt_…`) or name. This parameter is mutually exclusive with other dimension filters. * */ template?: string; }; url: "/v1/email/stats/daily"; }; type GetEmailStatsHourlyData = { body?: never; path?: never; query?: { /** * Start of the window (ISO 8601 instant). Rounded down to the start of its hour (the local hour when `timezone` is set, otherwise the UTC hour), and that hour is included. When `timezone` is set, a numeric UTC offset here (for example `+05:45`) is rejected; use a `Z` (UTC) instant. Defaults to 7 days before `to` when omitted. */ from?: string; /** * End of the window (ISO 8601 instant). Rounded down to the start of its hour (the local hour when `timezone` is set, otherwise the UTC hour), and that hour is included (both bounds inclusive). When `timezone` is set, a numeric UTC offset here is rejected; use a `Z` (UTC) instant. Defaults to the current hour when omitted. Window may not exceed 30 days (720 hours). */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Restrict the statistics to a single category: `transactional` or `marketing`. Mutually exclusive with the other dimension filters; only one may be set per request. * */ category?: string; /** * Restrict the statistics to a single sending domain (the part of the From address after @). Mutually exclusive with the other dimension filters; only one may be set per request. */ sending_domain?: string; /** * Restrict the statistics to a single tag. Use `name` to match any value of a tag, or `name:value` for a specific pair (for example `campaign:spring_launch`). Mutually exclusive with the other dimension filters; only one may be set per request. * */ tag?: string; /** * Restrict the statistics to a single sending IP. Mutually exclusive with the other dimension filters; only one may be set per request. A sending IP is assigned only after a message reaches delivery, so this filter reports delivery-side metrics only. Accepted, processed, rejected, complaint, and engagement counts are `0`, and processing latency is `null`. Complaint, open, and click rates are `0` when deliveries exist and `null` otherwise. * */ sending_ip?: string; /** * Restrict the statistics to a single recipient mailbox domain (the part of the recipient address after the `@`, for example `gmail.com`). Mutually exclusive with the other dimension filters; only one may be set per request. * */ recipient_domain?: string; /** * Restricts the statistics to one template, identified by its ID (`emt_…`) or name. This parameter is mutually exclusive with other dimension filters. * */ template?: string; }; url: "/v1/email/stats/hourly"; }; type GetEmailStatsByTagData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter. */ category?: string; /** * Metric to rank rows by, applied descending. Any count or rate in the response can be used. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `processed`. * */ sort?: EmailStatsSortMetric; /** * Maximum number of tag rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also gets a `trend` array: a short per-bucket series showing that tag's delivery and engagement rates over the window. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/email/stats/tags"; }; type GetEmailStatsSummaryData = { body?: never; path?: never; query?: { /** * Inclusive start of the window: a calendar day (`YYYY-MM-DD`) or an RFC 3339 instant (rounded down to the hour). Interpreted in `timezone` (a calendar day names a local day; an instant is rounded down to the local hour), or in UTC when `timezone` is omitted. A numeric UTC offset (for example `+05:45`) is rejected when `timezone` is set; use a calendar day or a `Z` (UTC) instant. Must use the same form as `to`. Defaults to 30 days before `to` for day windows, or 168 hours (7 days) before `to` for hour windows, when omitted. * */ from?: string; /** * Inclusive end of the window: a calendar day (`YYYY-MM-DD`) or an RFC 3339 instant (rounded down to the hour). Interpreted in `timezone` (a calendar day names a local day; an instant is rounded down to the local hour), or in UTC when `timezone` is omitted. A numeric UTC offset is rejected when `timezone` is set; use a calendar day or a `Z` (UTC) instant. Must use the same form as `from`. Defaults to today for day windows, or the current hour for hour windows, in that timezone, when omitted. Day windows may not exceed 365 days; hour windows may not exceed 720 hours (30 days). * */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Restrict the statistics to a single category: `transactional` or `marketing`. Mutually exclusive with the other dimension filters; only one may be set per request. * */ category?: string; /** * Restrict the statistics to a single sending domain (the part of the From address after @). Mutually exclusive with the other dimension filters; only one may be set per request. */ sending_domain?: string; /** * Restrict the statistics to a single tag. Use `name` to match any value of a tag, or `name:value` for a specific pair (for example `campaign:spring_launch`). Mutually exclusive with the other dimension filters; only one may be set per request. * */ tag?: string; /** * Restrict the statistics to a single sending IP. Mutually exclusive with the other dimension filters; only one may be set per request. A sending IP is assigned only after a message reaches delivery, so this filter reports delivery-side metrics only. Accepted, processed, rejected, complaint, and engagement counts are `0`, and processing latency is `null`. Complaint, open, and click rates are `0` when deliveries exist and `null` otherwise. * */ sending_ip?: string; /** * Restrict the statistics to a single recipient mailbox domain (the part of the recipient address after the `@`, for example `gmail.com`). Mutually exclusive with the other dimension filters; only one may be set per request. * */ recipient_domain?: string; /** * Restricts the statistics to one template, identified by its ID (`emt_…`) or name. This parameter is mutually exclusive with other dimension filters. * */ template?: string; /** * Set to `previous_period` to also include the same statistics for the immediately preceding window of equal length, plus the change between the two, so you can show "+X% vs last period" without a second request. * */ compare?: "previous_period"; }; url: "/v1/email/stats/summary"; }; type GetEmailStatsBySendingIpData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter. */ category?: string; /** * Metric to rank IPs by, applied descending. Sorting by `bounces.block` puts the IPs whose reputation is most likely degraded at the top. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `delivered`. A sending IP has no engagement, so engagement metrics aren't sortable here, and neither are `processed`, `rejected`, or `oob_bounces`. * */ sort?: "delivered" | "bounced" | "complained" | "deferred" | "bounces.hard" | "bounces.soft" | "bounces.admin" | "bounces.block" | "bounces.undetermined" | "delivery_rate" | "bounce_rate" | "complaint_rate" | "bounces.hard_rate" | "bounces.soft_rate" | "bounces.admin_rate" | "bounces.block_rate" | "bounces.undetermined_rate"; /** * Maximum number of IP rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also gets a `trend` array: a short per-bucket series showing that IP's delivery rates over the window. A trend point's open and click rates read `0` in a bucket that had deliveries and `null` in one that had none, because a sending IP has no engagement data. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/email/stats/sending-ips"; }; type GetEmailStatsBySendingDomainData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter. */ category?: string; /** * Metric to rank rows by, applied descending. Any count or rate in the response can be used. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `processed`. * */ sort?: EmailStatsSortMetric; /** * Maximum number of domain rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also gets a `trend` array: a short per-bucket series showing that domain's delivery and engagement rates over the window. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/email/stats/sending-domains"; }; type GetEmailStatsByCategoryData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Metric to rank rows by, applied descending. Any count or rate in the response can be used. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `processed`. * */ sort?: EmailStatsSortMetric; /** * Maximum number of category rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also gets a `trend` array: a short per-bucket series showing that category's delivery and engagement rates over the window. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/email/stats/categories"; }; type GetEmailStatsByMailboxProviderData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Not supported on breakdown endpoints; supplying it returns `422`. To compare categories use `GET /v1/email/stats/categories`; the summary, daily, and hourly statistics accept `category` as a filter. */ category?: string; /** * Metric to rank rows by, applied descending. Any count or rate in the response can be used. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `delivered`. `processed`, `rejected`, and `oob_bounces` are not part of this breakdown's rows, so they are not sortable here. * */ sort?: EmailMailboxProviderSortMetric; /** * Maximum number of mailbox-provider rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also gets a `trend` array: a short per-bucket series showing that provider's delivery and engagement rates over the window. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/email/stats/mailbox-providers"; }; type GetEmailStatsByMailboxProviderRegionData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter. */ category?: string; /** * Metric to rank rows by, applied descending. Any count or rate in the response can be used. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `delivered`. `processed`, `rejected`, and `oob_bounces` are not part of this breakdown's rows, so they are not sortable here. * */ sort?: EmailMailboxProviderSortMetric; /** * Maximum number of provider-region rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also gets a `trend` array: a short per-bucket series showing that provider region's delivery and engagement rates over the window. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/email/stats/mailbox-provider-regions"; }; type GetEmailStatsByRecipientDomainData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). It defaults to 30 days before `to` when you leave it out. When `include_trend=true` and `trend_grain=hourly`, that default tightens to 29 days before `to` instead, so the defaulted window still fits inside the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter. */ category?: string; /** * Metric to rank rows by, applied descending. Any count or rate in the response can be used. A row whose rate is undefined because its denominator is zero sorts last. It defaults to `processed`. * */ sort?: EmailStatsSortMetric; /** * Maximum number of recipient-domain rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also gets a `trend` array: a short per-bucket series showing that recipient domain's delivery and engagement rates over the window. This only works when `limit` is 50 or fewer and the window is at most 90 days for `trend_grain=daily` or 720 hours for `trend_grain=hourly`. Ask for more and you get a `422`. When you leave `from` out and use `trend_grain=hourly`, the default window tightens to 29 days before `to` (720 hours total), so a request built entirely from defaults always fits inside the cap. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/email/stats/recipient-domains"; }; type GetEmailStatsByTemplateData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted; with `include_trend=true` and `trend_grain=hourly` the default tightens to 29 days before `to`, keeping the defaulted window within the 720-hour trend cap. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Not supported on breakdown endpoints; supplying it returns `422`. To compare categories use `GET /v1/email/stats/categories`; the summary, daily, and hourly statistics accept `category` as a filter. */ category?: string; /** * Metric to rank rows by, applied descending. Any count or rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `processed`. * */ sort?: EmailStatsSortMetric; /** * Maximum number of template rows to return, ranked by the `sort` field descending. */ limit?: number; /** * When true, each row also has a `trend` array: a short per-bucket series of that template's delivery and engagement rates over the window. Returned only when `limit` is 50 or fewer and the window is at most 90 days (trend_grain=daily) or 720 hours (trend_grain=hourly); a larger request returns `422`. When `from` is omitted and `trend_grain=hourly`, the default start tightens to 29 days before `to`, keeping the window inside 720 hours, so a request built entirely from defaults always fits the cap. * */ include_trend?: boolean; /** * Bucket grain for the `trend` series. Has no effect unless `include_trend=true`. */ trend_grain?: StatsTrendGrain; }; url: "/v1/email/stats/templates"; }; type GetEmailStatsByLocationData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Not supported on breakdown endpoints; supplying it returns `422`. To compare categories use `GET /v1/email/stats/categories`; the summary, daily, and hourly statistics accept `category` as a filter. */ category?: string; /** * Location granularity for each row. `country` (default) groups by country; `region` groups by region within country; `city` groups by city within region. Each row reports the location hierarchy down to the chosen level. * */ group_by?: "country" | "region" | "city"; /** * Metric to rank rows by, applied descending. It defaults to `unique_opens`. Only engagement counts are sortable. This breakdown has no rates. * */ sort?: EmailEngagementSortMetric; /** * Maximum number of location rows to return, ranked by the `sort` field descending. */ limit?: number; }; url: "/v1/email/stats/locations"; }; type GetEmailStatsByClientData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Not supported on breakdown endpoints; supplying it returns `422`. To compare categories use `GET /v1/email/stats/categories`; the summary, daily, and hourly statistics accept `category` as a filter. */ category?: string; /** * Which reading-environment facet to group rows by. `email_client` (default) groups by mail client; `os` groups by operating system; `device_type` groups by device type. Each row populates the chosen facet and leaves the other two `null`. * */ group_by?: "email_client" | "os" | "device_type"; /** * Metric to rank rows by, applied descending. It defaults to `unique_opens`. Only engagement counts are sortable. This breakdown has no rates. * */ sort?: EmailEngagementSortMetric; /** * Maximum number of client rows to return, ranked by the `sort` field descending. */ limit?: number; }; url: "/v1/email/stats/clients"; }; type GetEmailStatsByBounceCodeData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter. */ category?: string; /** * Metric to rank rows by, applied descending. It defaults to `bounced`. Only the bounce counts are sortable here, because this breakdown has no rate fields. * */ sort?: "bounced" | "bounces.hard" | "bounces.soft" | "bounces.admin" | "bounces.block" | "bounces.undetermined"; /** * Maximum number of bounce-code rows to return, ranked by the `sort` field descending. */ limit?: number; }; url: "/v1/email/stats/bounce-codes"; }; type GetEmailStatsByComplaintTypeData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to 30 days before `to` when omitted. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, interpreted as a calendar day in `timezone` (a UTC day when `timezone` is omitted). Defaults to today in that timezone when omitted. Window may not exceed 365 days. */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Not supported on breakdown endpoints. Supplying it returns `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter. */ category?: string; /** * Metric to rank rows by, applied descending. It defaults to `complained`, the only sortable metric for this breakdown. * */ sort?: "complained"; /** * Maximum number of complaint-type rows to return, ranked by `complained` descending. */ limit?: number; }; url: "/v1/email/stats/complaint-types"; }; type GetEmailStatsByBroadcastData = { body?: never; path?: never; query?: { /** * Start date (inclusive) in `YYYY-MM-DD`, UTC. Defaults to 30 days before `to` when omitted. */ from?: string; /** * End date (inclusive) in `YYYY-MM-DD`, UTC. Defaults to today (UTC) when omitted. Window may not exceed 365 days. */ to?: string; /** * Not supported on breakdown endpoints. Supplying it returns a `422`. To compare categories, use `GET /v1/email/stats/categories`. The summary, daily, and hourly statistics accept `category` as a filter. */ category?: string; /** * Metric to rank rows by, applied descending. Any count or rate in the response may be used; rows whose rate is undefined (zero denominator) sort last. Defaults to `processed`. * */ sort?: EmailStatsSortMetric; /** * Maximum number of broadcast rows to return, ranked by the `sort` field descending. */ limit?: number; }; url: "/v1/email/stats/broadcasts"; }; type ListDomainsData = { body?: never; path?: never; query?: { /** * Substring match against the domain name (case-insensitive). */ name?: string; /** * Field to sort by. Defaults to `created_at`. */ sort?: "created_at" | "name"; /** * Sort direction. Defaults to `desc`, which sorts from newest to oldest or largest to smallest, depending on the selected sort field. * */ order?: "asc" | "desc"; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; /** * When true, the response includes a `total` field with the total number of items matching the request's filters across all pages. */ include_total?: boolean; }; url: "/v1/email/domains"; }; type CreateDomainData = { body: DomainCreate; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/email/domains"; }; type UpdateDomainData = { body: DomainUpdate; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * ID of the domain to update. */ domain_id: DomainId; }; query?: never; url: "/v1/email/domains/{domain_id}"; }; type ListMailboxesData = { body?: never; path?: never; query?: { /** * Filter to the mailbox with exactly this address. */ address?: string; /** * Case-insensitive search matching the mailbox's address or display name (substring). */ q?: string; /** * Return only `active` or `suspended` mailboxes. Use `include_deleted` for restorable deleted mailboxes. */ state?: "active" | "suspended"; /** * Filter to mailboxes whose address is on this domain. */ domain?: string; /** * Include mailboxes deleted within their 30-day restore window. Defaults to false, so only active and suspended mailboxes are returned. A deleted mailbox has `deleted_at` set. */ include_deleted?: boolean; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; }; url: "/v1/email/mailboxes"; }; type CreateMailboxData = { body: MailboxCreate; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/email/mailboxes"; }; type UpdateMailboxData = { body: MailboxUpdate; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * Mailbox identifier. Starts with `mbx_`. */ mailbox_id: MailboxId; }; query?: { /** * Set to `true` when lowering `retention_tier` would delete remembered messages older than the new cutoff. The request is rejected without it in that case. */ confirm?: boolean; }; url: "/v1/email/mailboxes/{mailbox_id}"; }; type GetMailboxStatsData = { body?: never; path: { /** * Mailbox identifier. Starts with `mbx_`. */ mailbox_id: MailboxId; }; query?: { /** * Inclusive start of the window: a calendar day (`YYYY-MM-DD`, `day` granularity only) or an RFC 3339 instant rounded down to the hour (`hour` granularity only). Interpreted in `timezone`, or in UTC when `timezone` is omitted. A numeric UTC offset is rejected when `timezone` is set; use a calendar day or a `Z` (UTC) instant. Must use the same form as `to`. Defaults to 30 days before `to` at `day` granularity and 7 days before `to` at `hour`, when omitted. * */ from?: string; /** * Inclusive end of the window: a calendar day (`YYYY-MM-DD`, `day` granularity only) or an RFC 3339 instant rounded down to the hour (`hour` granularity only). Interpreted in `timezone`, or in UTC when `timezone` is omitted. A numeric UTC offset is rejected when `timezone` is set; use a calendar day or a `Z` (UTC) instant. Must use the same form as `from`. Defaults to today (day) or the current hour (hour) in that timezone when omitted. Window may not exceed 365 days at `day` or 30 days at `hour` granularity. * */ to?: string; /** * IANA timezone identifier used to group statistics, for example `Asia/Kathmandu`. The default is UTC. Day and hour boundaries, including the default window when `from` and `to` are omitted, follow this timezone. When this parameter is set, pass `from` and `to` as calendar days or `Z` instants instead of timestamps with explicit UTC offsets. * */ timezone?: string; /** * Granularity of the series: `day` (default) or `hour`. Echoed back as `period.grain`. * */ granularity?: "day" | "hour"; }; url: "/v1/email/mailboxes/{mailbox_id}/stats"; }; type ListMailboxReceiveRulesData = { body?: never; path: { /** * Mailbox identifier. Starts with `mbx_`. */ mailbox_id: MailboxId; }; query?: { /** * Return only `allow` or `block` rules; omit to return both actions. */ action?: "allow" | "block"; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; }; url: "/v1/email/mailboxes/{mailbox_id}/receive-rules"; }; type CreateMailboxReceiveRuleData = { body: ReceiveRuleCreate; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * Mailbox identifier. Starts with `mbx_`. */ mailbox_id: MailboxId; }; query?: never; url: "/v1/email/mailboxes/{mailbox_id}/receive-rules"; }; type ListEmailThreadsData = { body?: never; path?: never; query?: { /** * Filter to conversations in a specific mailbox. */ mailbox_id?: MailboxId; /** * Filter to conversations linked to a specific contact. */ contact_id?: ContactId; /** * Filter to conversations that have this label. Repeat the parameter to ask for more than one: only conversations that have every label you list are returned. * * A placement label picks a folder: `inbox`, `archive`, `spam`, or `blocked`. A custom label matches a conversation in any folder. Leave this out and you get the inbox. */ label?: Array; /** * When `true`, only conversations with unread messages are returned. This filters on the conversation's unread state, so you can combine it with `label`, for example to get unread conversations in the archive. The `unread` label itself lives on individual messages; this filter uses the conversation's aggregate unread state. */ has_unread?: boolean; /** * Conversations involving this address, matching the sender or any recipient. The match is case-insensitive and matches on any part of the address, so a fragment works as well as the whole address. */ participant?: string; /** * Conversations whose subject contains this text (case-insensitive). */ subject?: string; /** * Filter to conversations whose most recent message is at or after this time. Use the response cursors for pagination. */ after?: string; /** * Filter to conversations whose most recent message is at or before this time. Use the response cursors for pagination. */ before?: string; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; }; url: "/v1/email/threads"; }; type DeleteEmailThreadData = { body?: never; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * Thread identifier. Starts with `thr_`. */ thread_id: ThreadId; }; query?: { /** * Permanently delete the conversation and its messages immediately instead of moving them to the trash. */ permanent?: boolean; }; url: "/v1/email/threads/{thread_id}"; }; type UpdateEmailThreadData = { body: EmailThreadUpdateRequest; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * Thread identifier. Starts with `thr_`. */ thread_id: ThreadId; }; query?: never; url: "/v1/email/threads/{thread_id}"; }; type ListEmailThreadMessagesData = { body?: never; path: { /** * Thread identifier. Starts with `thr_`. */ thread_id: ThreadId; }; query?: { /** * Filter to received (`inbound`) or sent (`outbound`) messages. */ direction?: MessageDirection; /** * Filter to messages that have this label. `trash` lists trashed messages. Any other label, whether that is `archive`, `spam`, `blocked`, `unread` or one of your own, lists the messages that have it and are not in the trash. When omitted, every message that is not trashed is returned, whichever folder the conversation is in. * */ label?: string; /** * Set to `extracted_text` to inline each message's extracted plain text. */ include?: "extracted_text"; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; }; url: "/v1/email/threads/{thread_id}/messages"; }; type ReplyEmailThreadMessageData = { body: EmailThreadMessageReplyRequest; headers?: { /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path: { /** * Thread identifier. Starts with `thr_`. */ thread_id: ThreadId; /** * Message ID (`rem_` for a received message, `em_` for a sent one). */ message_id: string; }; query?: never; url: "/v1/email/threads/{thread_id}/messages/{message_id}/reply"; }; type ListWorkspaceNumbersData = { body?: never; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; }; path?: never; query?: { /** * Return only the number matching these digits. Give a full number with its country code, however your own records spell it: `+12025550188`, `12025550188`, `0012025550188`, and `+1 202 555 0188` all resolve to the same number. Spacing and punctuation are fine once a leading `+` or `00` marks the country code, or when `country_code` names the country; a grouped spelling without either is refused rather than guessed at, and a national spelling (bare digits without the country code) matches only when `country_code` names the country. A short code is matched on its bare digits instead, and since the same short code can be allocated in more than one country, pass `country_code` alongside it to name which one. This filter narrows the list like the others rather than replacing them, so a country or capability filter still applies. To match a range of numbers rather than one, use `prefix`. */ number?: string; /** * Filter by the country a number belongs to, as an ISO 3166-1 alpha-2 code. */ country_code?: string; /** * Return only allocated numbers of this physical type after applying the country and prefix filters. */ number_type?: NumberType$1; /** * Return only numbers that start with these digits, matched right after the country dial code: with `country_code=US`, `prefix=212` returns the +1 212 area-code numbers allocated to you. Digits only, and `country_code` is required alongside it, since the digits are national ones. Leave out the country dial code and any national dialing prefix such as a leading 0. Short codes never match a prefix search. */ prefix?: string; /** * Filter by channel capability. Repeat the parameter to require several at once: `capabilities=sms&capabilities=voice` returns only numbers that support both. */ capabilities?: Array; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; }; url: "/v1/numbers"; }; type ListAvailableNumbersData = { body?: never; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; }; path?: never; query: { /** * ISO 3166-1 alpha-2 country code to search in. */ country_code: string; /** * Return only numbers of this physical type after applying the country and prefix filters. */ number_type?: NumberType$1; /** * Return only numbers that start with these digits, matched right after the country dial code: with `country_code=US`, `prefix=212` matches +1 212 area-code numbers and `prefix=833` matches 833 toll-free numbers. Digits only. Leave out the country dial code and any national dialing prefix such as a leading 0. Short codes never match a prefix search. */ prefix?: string; /** * Filter by channel capability. Repeat the parameter to require several at once: `capabilities=sms&capabilities=voice` returns only numbers that support both. */ capabilities?: Array; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; }; url: "/v1/numbers/available"; }; type ListNumbersOrdersData = { body?: never; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; }; path?: never; query?: { /** * Return only orders with status `charging`, `ordering`, `pending`, `completed`, or `failed`. */ status?: NumbersOrderStatus$1; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; }; url: "/v1/numbers/orders"; }; type CreateNumbersOrderData = { body: NumbersOrderCreate; headers?: { /** * Workspace context for the request. Required for dashboard authentication; API-key requests derive the workspace from the key. */ "X-Workspace-Id"?: string; /** * Client-supplied deduplication key. When present, the original response is replayed for any duplicate request with the same key, within the idempotency window (3 hours by default). * * Two distinct 409 errors signal misuse: * * - `request_in_progress` (E01004): The same key is currently being * processed by a concurrent request. Wait briefly and retry. The lock expires within 30 seconds. * - `idempotency_key_reuse` (E01005): The same key has already completed * against a different request body or method. Generate a new key. * * Recommended key format is `/` (for example `welcome-user/usr_abc123`). * */ "Idempotency-Key"?: string; }; path?: never; query?: never; url: "/v1/numbers/orders"; }; type ListVoiceCallsData = { body?: never; path?: never; query?: { /** * Return only calls in this direction. */ direction?: VoiceCallDirection; /** * Return only calls with one of these statuses, comma-separated. * In-flight and final statuses may be combined freely. * */ status?: Array; /** * Return only calls belonging to this session, which is how the legs of one multi-party or transferred call are correlated. */ session_id?: VoiceSessionId; /** * Return only calls carried by this SIP trunk. */ sip_trunk_id?: SipTrunkId; /** * Return only calls placed from this calling party number, matched as a whole number rather than as a fragment. Give it in international form: `+14155551234`, `14155551234`, and `0014155551234` all select the same calls. A number given without a country code is read as an international one, so give the country code to be sure of what you are matching. Use `number` instead to match part of a number, or either side of the call. * */ from?: string; /** * Return only calls placed to this called party number, matched as a whole number rather than as a fragment. Give it in international form: `+16505559876`, `16505559876`, and `0016505559876` all select the same calls. A number given without a country code is read as an international one, so give the country code to be sure of what you are matching. Use `number` instead to match part of a number, or either side of the call. * */ to?: string; /** * Return only calls where the calling or called number contains this value. Matches a partial number, so a country or area-code prefix returns every call to or from it. Combines with `from`/`to`, which match one side exactly. */ number?: string; /** * Return only calls that started at or after this instant, inclusive. RFC 3339 timestamp. */ started_after?: string; /** * Return only calls that started at or before this instant, inclusive. RFC 3339 timestamp. */ started_before?: string; /** * Maximum number of items to return per page. */ limit?: number; /** * Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order. */ starting_after?: string; /** * Cursor from the `prev_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. */ ending_before?: string; }; url: "/v1/voice/calls"; }; //#endregion //#region src/generated/core/auth.gen.d.ts type AuthToken = string | undefined; interface Auth { /** * Which part of the request do we use to send the auth? * * @default 'header' */ in?: "header" | "query" | "cookie"; /** * A unique identifier for the security scheme. * * Defined only when there are multiple security schemes whose `Auth` * shape would otherwise be identical. */ key?: string; /** * Header or query parameter name. * * @default 'Authorization' */ name?: string; scheme?: "basic" | "bearer"; type: "apiKey" | "http"; } //#endregion //#region src/generated/core/pathSerializer.gen.d.ts interface SerializerOptions { /** * @default true */ explode: boolean; style: T; } type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited"; type ObjectStyle = "form" | "deepObject"; //#endregion //#region src/generated/core/bodySerializer.gen.d.ts type QuerySerializer = (query: Record) => string; type BodySerializer = (body: unknown) => unknown; type QuerySerializerOptionsObject = { allowReserved?: boolean; array?: Partial>; object?: Partial>; }; type QuerySerializerOptions = QuerySerializerOptionsObject & { /** * Per-parameter serialization overrides. When provided, these settings * override the global array/object settings for specific parameter names. */ parameters?: Record; }; //#endregion //#region src/generated/core/types.gen.d.ts type HttpMethod = "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace"; type Client$1 = { /** * Returns the final request URL. */ buildUrl: BuildUrlFn; getConfig: () => Config; request: RequestFn; setConfig: (config: Config) => Config; } & { [K in HttpMethod]: MethodFn; } & ([SseFn] extends [never] ? { sse?: never; } : { sse: { [K in HttpMethod]: SseFn; }; }); interface Config$1 { /** * Auth token or a function returning auth token. The resolved value will be * added to the request payload as defined by its `security` array. */ auth?: ((auth: Auth) => Promise | AuthToken) | AuthToken; /** * A function for serializing request body parameter. By default, * {@link JSON.stringify()} will be used. */ bodySerializer?: BodySerializer | null; /** * An object containing any HTTP headers that you want to pre-populate your * `Headers` object with. * * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} */ headers?: RequestInit["headers"] | Record; /** * The request method. * * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} */ method?: Uppercase; /** * A function for serializing request query parameters. By default, arrays * will be exploded in form style, objects will be exploded in deepObject * style, and reserved characters are percent-encoded. * * This method will have no effect if the native `paramsSerializer()` Axios * API function is used. * * {@link https://swagger.io/docs/specification/serialization/#query View examples} */ querySerializer?: QuerySerializer | QuerySerializerOptions; /** * A function validating request data. This is useful if you want to ensure * the request conforms to the desired shape, so it can be safely sent to * the server. */ requestValidator?: (data: unknown) => Promise; /** * A function transforming response data before it's returned. This is useful * for post-processing data, e.g., converting ISO strings into Date objects. */ responseTransformer?: (data: unknown) => Promise; /** * A function validating response data. This is useful if you want to ensure * the response conforms to the desired shape, so it can be safely passed to * the transformers and returned to the user. */ responseValidator?: (data: unknown) => Promise; } //#endregion //#region src/generated/core/serverSentEvents.gen.d.ts type ServerSentEventsOptions = Omit & Pick & { /** * Fetch API implementation. You can use this option to provide a custom * fetch instance. * * @default globalThis.fetch */ fetch?: typeof fetch; /** * Implementing clients can call request interceptors inside this hook. */ onRequest?: (url: string, init: RequestInit) => Promise; /** * Callback invoked when a network or parsing error occurs during streaming. * * This option applies only if the endpoint returns a stream of events. * * @param error The error that occurred. */ onSseError?: (error: unknown) => void; /** * Callback invoked when an event is streamed from the server. * * This option applies only if the endpoint returns a stream of events. * * @param event Event streamed from the server. * @returns Nothing (void). */ onSseEvent?: (event: StreamEvent) => void; serializedBody?: RequestInit["body"]; /** * Default retry delay in milliseconds. * * This option applies only if the endpoint returns a stream of events. * * @default 3000 */ sseDefaultRetryDelay?: number; /** * Maximum number of retry attempts before giving up. */ sseMaxRetryAttempts?: number; /** * Maximum retry delay in milliseconds. * * Applies only when exponential backoff is used. * * This option applies only if the endpoint returns a stream of events. * * @default 30000 */ sseMaxRetryDelay?: number; /** * Optional sleep function for retry backoff. * * Defaults to using `setTimeout`. */ sseSleepFn?: (ms: number) => Promise; url: string; }; interface StreamEvent { data: TData; event?: string; id?: string; retry?: number; } type ServerSentEventsResult = { stream: AsyncGenerator ? TData[keyof TData] : TData, TReturn, TNext>; }; //#endregion //#region src/generated/client/utils.gen.d.ts type ErrInterceptor = (error: Err, /** response may be undefined due to a network error where no response object is produced */ response: Res | undefined, /** request may be undefined, because error may be from building the request object itself */ request: Req | undefined, options: Options) => Err | Promise; type ReqInterceptor = (request: Req, options: Options) => Req | Promise; type ResInterceptor = (response: Res, request: Req, options: Options) => Res | Promise; declare class Interceptors { fns: Array; clear(): void; eject(id: number | Interceptor): void; exists(id: number | Interceptor): boolean; getInterceptorIndex(id: number | Interceptor): number; update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false; use(fn: Interceptor): number; } interface Middleware { error: Interceptors>; request: Interceptors>; response: Interceptors>; } //#endregion //#region src/generated/client/types.gen.d.ts type ResponseStyle = "data" | "fields"; interface Config extends Omit, Config$1 { /** * Base URL for all requests made by this client. */ baseUrl?: T["baseUrl"]; /** * Fetch API implementation. You can use this option to provide a custom * fetch instance. * * @default globalThis.fetch */ fetch?: typeof fetch; /** * Please don't use the Fetch client for Next.js applications. The `next` * options won't have any effect. * * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. */ next?: never; /** * Return the response data parsed in a specified format. By default, `auto` * will infer the appropriate method from the `Content-Type` response header. * You can override this behavior with any of the {@link Body} methods. * Select `stream` if you don't want to parse response data at all. * * @default 'auto' */ parseAs?: "arrayBuffer" | "auto" | "blob" | "formData" | "json" | "stream" | "text"; /** * Should we return only data or multiple fields (data, error, response, etc.)? * * @default 'fields' */ responseStyle?: ResponseStyle; /** * Throw an error instead of returning it in the response? * * @default false */ throwOnError?: T["throwOnError"]; } interface RequestOptions$1 extends Config<{ responseStyle: TResponseStyle; throwOnError: ThrowOnError; }>, Pick, "onRequest" | "onSseError" | "onSseEvent" | "sseDefaultRetryDelay" | "sseMaxRetryAttempts" | "sseMaxRetryDelay"> { /** * Any body that you want to add to your request. * * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} */ body?: unknown; path?: Record; query?: Record; /** * Security mechanism(s) to use for the request. */ security?: ReadonlyArray; url: Url; } interface ResolvedRequestOptions extends RequestOptions$1 { headers: Headers; serializedBody?: string; } type RequestResult = ThrowOnError extends true ? Promise ? TData[keyof TData] : TData : { data: TData extends Record ? TData[keyof TData] : TData; request: Request; response: Response; }> : Promise ? TData[keyof TData] : TData) | undefined : ({ data: TData extends Record ? TData[keyof TData] : TData; error: undefined; } | { data: undefined; error: TError extends Record ? TError[keyof TError] : TError; }) & { /** request may be undefined, because error may be from building the request object itself */ request?: Request; /** response may be undefined, because error may be from building the request object itself or from a network error */ response?: Response; }>; interface ClientOptions { baseUrl?: string; responseStyle?: ResponseStyle; throwOnError?: boolean; } type MethodFn = (options: Omit, "method">) => RequestResult; type SseFn = (options: Omit, "method">) => Promise>; type RequestFn = (options: Omit, "method"> & Pick>, "method">) => RequestResult; type BuildUrlFn = ; query?: Record; url: string; }>(options: TData & Options) => string; type Client = Client$1 & { interceptors: Middleware; }; interface TDataShape { body?: unknown; headers?: unknown; path?: unknown; query?: unknown; url: string; } type OmitKeys = Pick>; type Options = OmitKeys, "body" | "path" | "query" | "url"> & ([TData] extends [never] ? unknown : Omit); //#endregion //#region src/resources/base.d.ts /** Resolved per-attempt inputs handed to the hey-api SDK call. */ interface CallContext { signal: AbortSignal; /** Merged headers: caller `headers` plus the resolved `Idempotency-Key`. */ headers: Record; } declare abstract class Resource { protected readonly core: BirdHTTPClient; protected readonly client: Client; constructor(core: BirdHTTPClient, client: Client); /** Run a single typed call through the lifecycle. */ protected call(method: string, options: RequestOptions | undefined, invoke: (ctx: CallContext) => Promise>, schemes?: string[]): APIPromise; /** Run a cursor-paginated list through the lifecycle (each page retried independently). */ protected paginated(method: string, options: RequestOptions | undefined, invoke: (ctx: CallContext, cursor: string | undefined) => Promise>>, schemes?: string[]): PaginatedPromise; } //#endregion //#region src/resources/email.gen.d.ts type EmailListQuery$1 = NonNullable; declare class EmailResourceBase extends Resource { /** * Fetch one email message by `id`, with aggregate delivery status and per-state recipient counts. The message body (`html`, `text`) is not returned. Per-recipient delivery statuses and the event log are separate sub-resources: `GET /v1/email/messages/{message_id}/recipients` and `GET /v1/email/messages/{message_id}/events`. * * @example * const msg = await bird.email.get("em_abc123"); * msg.status; // "accepted" | "processed" | "delivered" | "bounced" | … * msg.delivered_count; * msg.bounced_count; */ get(messageId: string, options?: RequestOptions): APIPromise; /** * List sent email messages, newest first, as a cursor page (`{data, next_cursor, …}`). Pass `next_cursor` back as `starting_after` to fetch the next page. Filter by creation time with the half-open range `created_after` (inclusive) and `created_before` (exclusive). For a single UTC day, `created_after` is that day at 00:00:00Z and `created_before` is the next day at 00:00:00Z. * * @example * for await (const message of bird.email.list({ status: "bounced" })) { * console.log(message.id); * } */ list(query?: EmailListQuery$1, options?: RequestOptions): PaginatedPromise; /** * Cancel a scheduled email before it sends. Only works while the message's `status` is still `scheduled`. Once it starts sending, or was already canceled, the call returns a conflict error. Canceling does not return consumed scheduled-send quota. * * @example * await bird.email.cancel("em_abc123"); */ cancel(messageId: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/emailDefaults.d.ts /** * Channel-level defaults set at client construction. Field names mirror the * send params (so they read as pre-filled fields). Any field set here becomes * optional in `send` and is filled when omitted (per-send value wins). */ type EmailChannelDefaults = Partial>; type PartialBy = Omit & Partial>; /** Keys with a configured default. These keys are optional in `send`. */ type DefaultedKeys = D extends object ? Extract : never; /** `send` params with defaulted fields made optional. */ type EmailSend = PartialBy>; /** `sendBatch` params — every item relaxed the same way `send` is. */ type EmailSendBatch = Array>; //#endregion //#region src/resources/emailStats.gen.d.ts type EmailStatsSummaryQuery = NonNullable; type EmailStatsDailyQuery = NonNullable; type EmailStatsHourlyQuery = NonNullable; type EmailStatsByTagQuery = NonNullable; type EmailStatsByCategoryQuery = NonNullable; type EmailStatsBySendingIpQuery = NonNullable; type EmailStatsBySendingDomainQuery = NonNullable; type EmailStatsByRecipientDomainQuery = NonNullable; type EmailStatsByMailboxProviderQuery = NonNullable; type EmailStatsByMailboxProviderRegionQuery = NonNullable; type EmailStatsByTemplateQuery = NonNullable; type EmailStatsByLocationQuery = NonNullable; type EmailStatsByClientQuery = NonNullable; type EmailStatsByBounceCodeQuery = NonNullable; type EmailStatsByComplaintTypeQuery = NonNullable; type EmailStatsByBroadcastQuery = NonNullable; declare class EmailStatsResource extends Resource { /** * Aggregate email KPIs for one period: sends, delivered, bounces, complaints, opens, clicks, their rates, and latency percentiles. The `from` and `to` values are both `YYYY-MM-DD` days or both RFC 3339 instants (hour grain). Add `compare=previous_period` for deltas versus the prior window. For a per-day or per-hour series use `email.stats.daily` or `email.stats.hourly`. * * @example Summary for a month * const s = await bird.email.stats.summary({ from: "2026-05-01", to: "2026-05-31" }); * console.log(s.sends_accepted, s.delivery.delivered); */ summary(query?: EmailStatsSummaryQuery, options?: RequestOptions): APIPromise; /** * Per-day email stats series (counts, rates, latency percentiles), gap-filled with zero rows, max 365 days. At most one filter of `category`, `sending_domain`, `tag`, `sending_ip`, `recipient_domain`, `template`. For hour resolution use `email.stats.hourly`; for one aggregate row use `email.stats.summary`. * * @example * const series = await bird.email.stats.daily({ from: "2026-05-01", to: "2026-05-31" }); * for (const row of series.data) console.log(row.bucket, row.delivery.delivered); */ daily(query?: EmailStatsDailyQuery, options?: RequestOptions): APIPromise; /** * Per-hour email stats series, gap-filled with zero rows, max 720 hours (30 days). Takes the same single-dimension filters as `email.stats.daily`; for longer ranges use `email.stats.daily`, for one aggregate row use `email.stats.summary`. * * @example * const series = await bird.email.stats.hourly({ from: "2026-05-01", to: "2026-05-02" }); * for (const row of series.data) console.log(row.bucket, row.delivery.delivered); */ hourly(query?: EmailStatsHourlyQuery, options?: RequestOptions): APIPromise; /** * Email delivery and engagement stats grouped by tag, one row per `name:value` pair set at send time. Rows are ranked by `sort`, `processed` by default. Set `include_trend=true` to add a per-bucket rate series to each row. * * @example Top 10 tags by delivered * const { data } = await bird.email.stats.byTag({ * from: "2026-05-01", * to: "2026-05-31", * sort: "delivered", * limit: 10, * }); * for (const row of data) console.log(row.tag, row.delivery.delivered); */ byTag(query?: EmailStatsByTagQuery, options?: RequestOptions): APIPromise; /** * Email delivery and engagement stats grouped by category, meaning `transactional` compared with `marketing`. Rows are ranked by `sort`, `processed` by default. Set `include_trend=true` to add a per-bucket rate series to each row. * * @example * const { data } = await bird.email.stats.byCategory({ from: "2026-05-01", to: "2026-05-31" }); * for (const row of data) console.log(row.category, row.delivery.delivered); */ byCategory(query?: EmailStatsByCategoryQuery, options?: RequestOptions): APIPromise; /** * Delivery and bounce stats grouped by sending IP, with deferral counts alongside them. `sort=bounces.block` surfaces reputation-damaged IPs first. Engagement, accepted, and processed counts aren't available per IP, and complaint and out-of-band bounce counts always read `0` here. For workspace-wide figures, use `email.stats.daily`. * * @example * const { data } = await bird.email.stats.bySendingIp({ * from: "2026-05-01", * to: "2026-05-31", * sort: "bounces.block", * limit: 20, * }); * for (const row of data) console.log(row.sending_ip, row.delivery.delivered); */ bySendingIp(query?: EmailStatsBySendingIpQuery, options?: RequestOptions): APIPromise; /** * Email delivery and engagement stats grouped by sending (`From`) domain, so you can compare deliverability across your workspace's verified domains. For per-IP reputation instead, use `email.stats.by_sending_ip`. * * @example * const { data } = await bird.email.stats.bySendingDomain({ * from: "2026-05-01", * to: "2026-05-31", * sort: "delivery_rate", * limit: 25, * }); * for (const row of data) console.log(row.sending_domain, row.delivery.delivery_rate); */ bySendingDomain(query?: EmailStatsBySendingDomainQuery, options?: RequestOptions): APIPromise; /** * Email delivery and engagement stats grouped by exact recipient mailbox domain, for example `gmail.com`. Finer-grained than `email.stats.by_mailbox_provider`, which buckets domains into providers. * * @example * const { data } = await bird.email.stats.byRecipientDomain({ * from: "2026-05-01", * to: "2026-05-31", * sort: "bounce_rate", * limit: 25, * }); * for (const row of data) console.log(row.recipient_domain, row.delivery.bounce_rate); */ byRecipientDomain(query?: EmailStatsByRecipientDomainQuery, options?: RequestOptions): APIPromise; /** * Email delivery and engagement stats grouped by recipient mailbox provider, for example `gmail`, `microsoft`, or `yahoo`. It covers the delivery stage onward and omits accepted or processed counts. For a per-region split within a provider, use `email.stats.by_mailbox_provider_region`; for exact destination domains instead, use `email.stats.by_recipient_domain`. * * @example * const { data } = await bird.email.stats.byMailboxProvider({ * from: "2026-05-01", * to: "2026-05-31", * limit: 25, * }); * for (const row of data) console.log(row.mailbox_provider, row.delivery.delivered); */ byMailboxProvider(query?: EmailStatsByMailboxProviderQuery, options?: RequestOptions): APIPromise; /** * Email delivery and engagement stats grouped by a mailbox provider and provider region pair, for example `gmail` in `NA`. It covers the delivery stage onward and omits accepted or processed counts. For the provider-level view without the region split, use `email.stats.by_mailbox_provider`. * * @example * const { data } = await bird.email.stats.byMailboxProviderRegion({ * from: "2026-05-01", * to: "2026-05-31", * limit: 25, * }); * for (const row of data) console.log(row.mailbox_provider, row.mailbox_provider_region, row.delivery.delivered); */ byMailboxProviderRegion(query?: EmailStatsByMailboxProviderRegionQuery, options?: RequestOptions): APIPromise; /** * Email delivery and engagement stats grouped by the template used at send time, keyed by template id (`emt_…`); only templated sends appear. A single template's trend over time comes from `email.stats.daily` with its `template` filter. * * @example * const { data } = await bird.email.stats.byTemplate({ * from: "2026-05-01", * to: "2026-05-31", * sort: "open_rate", * limit: 25, * }); * for (const row of data) console.log(row.template_id, row.engagement.open_rate); */ byTemplate(query?: EmailStatsByTemplateQuery, options?: RequestOptions): APIPromise; /** * Opens and clicks grouped by country, region, or city, whichever you choose with `group_by`. It only has engagement counts, no delivery counts or rates. For engagement grouped by mail client or device instead, use `email.stats.by_client`. * * @example * const { data } = await bird.email.stats.byLocation({ * from: "2026-05-01", * to: "2026-05-31", * limit: 25, * }); * for (const row of data) console.log(row.country, row.engagement.unique_opens); */ byLocation(query?: EmailStatsByLocationQuery, options?: RequestOptions): APIPromise; /** * Opens and clicks grouped by mail client, operating system, or device type, whichever you choose with `group_by`. It only has engagement counts, no delivery counts or rates. For engagement grouped by geography instead, use `email.stats.by_location`. * * @example * const { data } = await bird.email.stats.byClient({ * from: "2026-05-01", * to: "2026-05-31", * limit: 25, * }); * for (const row of data) console.log(row.email_client, row.engagement.unique_opens); */ byClient(query?: EmailStatsByClientQuery, options?: RequestOptions): APIPromise; /** * Bounce counts grouped by the SMTP error code the receiving mail server returned. Each row also breaks the bounce down into its hard, soft, admin, block, and undetermined split. It omits delivered, open, and click counts because a bounce code only appears on a bounce event. For bounces broken down by destination instead, use `email.stats.by_recipient_domain` or `email.stats.by_mailbox_provider`. * * @example * const { data } = await bird.email.stats.byBounceCode({ * from: "2026-05-01", * to: "2026-05-31", * sort: "bounced", * limit: 25, * }); * for (const row of data) console.log(row.smtp_error_code, row.bounced); */ byBounceCode(query?: EmailStatsByBounceCodeQuery, options?: RequestOptions): APIPromise; /** * Spam-complaint counts grouped by the feedback-loop complaint type, for example `abuse`, `fraud`, or `virus`. This complaint-only breakdown omits delivery and engagement counts. For complaints broken down by destination instead, use `email.stats.by_mailbox_provider` or `email.stats.by_recipient_domain`. * * @example * const { data } = await bird.email.stats.byComplaintType({ from: "2026-05-01", to: "2026-05-31" }); * for (const row of data) console.log(row.feedback_type, row.complained); */ byComplaintType(query?: EmailStatsByComplaintTypeQuery, options?: RequestOptions): APIPromise; /** * Email delivery and engagement stats grouped by broadcast. Only broadcast sends appear. Reflects roughly the last 30 days of activity. * * @example * const { data } = await bird.email.stats.byBroadcast({ * from: "2026-05-01", * to: "2026-05-31", * sort: "click_rate", * limit: 25, * }); * for (const row of data) console.log(row.broadcast_id, row.engagement.click_rate); */ byBroadcast(query?: EmailStatsByBroadcastQuery, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/emailMailboxes.gen.d.ts type EmailMailboxesListQuery = NonNullable; type EmailMailboxesCreateParams = NonNullable; type EmailMailboxesUpdateParams = NonNullable; type EmailMailboxesUpdateQuery = NonNullable; type EmailMailboxesStatsQuery = NonNullable; declare class EmailMailboxesResourceBase extends Resource { /** * List the workspace's mailboxes as a cursor page, newest first. Search addresses and display names with q, or filter by exact address, state, or domain. * * @example List mailboxes * for await (const mailbox of bird.email.mailboxes.list()) { * console.log(mailbox.address); * } */ list(query?: EmailMailboxesListQuery, options?: RequestOptions): PaginatedPromise; /** * Create a mailbox: a durable agent identity that owns an email address, groups mail into conversations, and remembers conversations for its retention tier. * * @example Create a mailbox * const mailbox = await bird.email.mailboxes.create({ display_name: "Support" }); * console.log(mailbox.address); // "abc123@inbox.ai" */ create(params?: EmailMailboxesCreateParams, options?: RequestOptions): APIPromise; /** * Read one mailbox by ID. A mailbox deleted within its 30-day restore window is still returned, with `deleted_at` set. Once that window closes it is gone and this returns `404`. * * @example Get a mailbox * const mailbox = await bird.email.mailboxes.get("mbx_01abc"); * console.log(mailbox.state); // "active" */ get(mailboxId: string, options?: RequestOptions): APIPromise; /** * Update a mailbox's display name, reply-to, receive policy, retention tier, IP pool, or metadata. Lowering the retention tier requires `confirm=true` when it would delete remembered messages older than the new cutoff. * * @example Change a mailbox's receive policy * const mailbox = await bird.email.mailboxes.update("mbx_01abc", { * receive_policy: "open", * }); * console.log(mailbox.id, mailbox.receive_policy); */ update(mailboxId: string, params?: EmailMailboxesUpdateParams, query?: EmailMailboxesUpdateQuery, options?: RequestOptions): APIPromise; /** * Delete a mailbox. The address stops receiving immediately and is quarantined. The mailbox and its remembered messages stay restorable for 30 days through the restore endpoint, then are permanently deleted. * * @example Delete a mailbox * await bird.email.mailboxes.delete("mbx_01abc"); */ delete(mailboxId: string, options?: RequestOptions): APIPromise; /** * Restore a mailbox deleted less than 30 days ago: the address starts receiving again and the remembered messages are back. Past the window the mailbox is permanently deleted and returns `404`. A mailbox that is not deleted returns `409`. * * @example Restore a deleted mailbox * const mailbox = await bird.email.mailboxes.restore("mbx_01abc"); * console.log(mailbox.deleted_at); // null */ restore(mailboxId: string, options?: RequestOptions): APIPromise; /** * Resume a suspended mailbox so it can send and receive again and its conversations become visible. Fails if your plan does not have room for another active mailbox (or another custom inbox.ai handle). Delete an active mailbox or upgrade first. A mailbox that is not suspended returns `409`. * * @example Resume a suspended mailbox * const mailbox = await bird.email.mailboxes.resume("mbx_01abc"); * console.log(mailbox.state); // "active" */ resume(mailboxId: string, options?: RequestOptions): APIPromise; /** * Read a mailbox's sent and received email statistics over a window: a period summary plus a bucketed series. Rows are bucketed by event time rather than send time, so engagement that arrived during the period for messages sent earlier is counted here. Both window bounds must use the same form, calendar days or RFC 3339 instants, matching the granularity. * * @example Get mailbox stats * const stats = await bird.email.mailboxes.stats("mbx_01abc"); * console.log(stats.summary?.sends_accepted); */ stats(mailboxId: string, query?: EmailMailboxesStatsQuery, options?: RequestOptions): APIPromise; /** * List the labels available in a mailbox: the built-in system labels (inbox, archive, spam, blocked, sent, trash, unread) plus every custom label in use. * * @example List a mailbox's labels * const labels = await bird.email.mailboxes.labels("mbx_01abc"); * console.log(labels.data.map((label) => label.name)); */ labels(mailboxId: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/emailMailboxesMessages.d.ts /** Parameters for sending a new message from a mailbox. */ type EmailMailboxesMessagesCreateParams = EmailMailboxComposeRequest; declare class EmailMailboxesMessagesResource extends Resource { #private; constructor(core: ConstructorParameters[0], client: ConstructorParameters[1], defaults?: EmailChannelDefaults); /** * Send a new email from this mailbox, starting a new conversation. * * @example Send from a mailbox * const msg = await bird.email.mailboxes.messages.create("mbx_01abc", { * to: ["customer@example.com"], * subject: "Hello", * text: "Hi there!", * }); */ create(mailboxId: string, params: EmailMailboxesMessagesCreateParams, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/emailMailboxesReceiveRules.gen.d.ts type EmailMailboxesReceiveRulesListQuery = NonNullable; type EmailMailboxesReceiveRulesCreateParams = NonNullable; declare class EmailMailboxesReceiveRulesResource extends Resource { /** * List a mailbox's allow/block receive rules as a cursor page, oldest first. Filter by action. * * @example List a mailbox's receive rules * for await (const rule of bird.email.mailboxes.receiveRules.list("mbx_01abc")) { * console.log(rule.action, rule.entry); * } */ list(mailboxId: string, query?: EmailMailboxesReceiveRulesListQuery, options?: RequestOptions): PaginatedPromise; /** * Add an allow or block rule for a sender address or domain to a mailbox. Block always wins. Up to 200 rules per mailbox. * * @example Block a domain * const rule = await bird.email.mailboxes.receiveRules.create("mbx_01abc", { * action: "block", * entry: "spam.example.com", * }); * console.log(rule.id); */ create(mailboxId: string, params: EmailMailboxesReceiveRulesCreateParams, options?: RequestOptions): APIPromise; /** * Remove a receive rule from a mailbox. Rules have no update operation, so a rule's allow or block action cannot be changed after it is created. * * @example Delete a rule * await bird.email.mailboxes.receiveRules.delete("mbx_01abc", "erl_01xyz"); */ delete(mailboxId: string, ruleId: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/emailMailboxes.d.ts declare class EmailMailboxesResource extends EmailMailboxesResourceBase { /** Messages sent from the mailbox's own address — `bird.email.mailboxes.messages.create(...)`. */ readonly messages: EmailMailboxesMessagesResource; /** Per-sender allow/block rules — `bird.email.mailboxes.receiveRules.create(...)`, `.list(...)`, `.delete(...)`. */ readonly receiveRules: EmailMailboxesReceiveRulesResource; constructor(core: ConstructorParameters[0], client: ConstructorParameters[1], defaults?: EmailChannelDefaults); } //#endregion //#region src/resources/emailThreads.gen.d.ts type EmailThreadsListQuery = NonNullable; type EmailThreadsUpdateParams = NonNullable; type EmailThreadsDeleteQuery = NonNullable; declare class EmailThreadsResourceBase extends Resource { /** * List mailbox conversations as a cursor page, most recently active first. `label` selects the view: inbox (default), archive, spam, blocked, or a custom label. Filter by mailbox, contact, participant address, or subject substring. * * @example List conversation threads * for await (const thread of bird.email.threads.list({ mailbox_id: "mbx_01abc" })) { * console.log(thread.id, thread.subject); * } */ list(query?: EmailThreadsListQuery, options?: RequestOptions): PaginatedPromise; /** * Get one conversation: participants, counts, labels, read state. Fetch its messages with the thread messages endpoint. * * @example Get a thread * const thread = await bird.email.threads.get("thr_01abc"); * console.log(thread.subject); */ get(threadId: string, options?: RequestOptions): APIPromise; /** * Add or remove labels on a conversation, or link and unlink a contact. Adding `spam` files it as spam, `archive` clears it out of the inbox, and `inbox` brings it back. * * @example Apply label changes to a thread * const thread = await bird.email.threads.update("thr_01abc", { * labels: { add: ["archive"] }, * }); * console.log(thread.id); */ update(threadId: string, params?: EmailThreadsUpdateParams, options?: RequestOptions): APIPromise; /** * Move a conversation and all its messages to trash (purged after 30 days), or delete permanently with `?permanent=true`. * * @example Delete a thread * await bird.email.threads.delete("thr_01abc", { permanent: true }); */ delete(threadId: string, query?: EmailThreadsDeleteQuery, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/emailThreadsMessages.gen.d.ts type EmailThreadsMessagesListQuery = NonNullable; type EmailThreadsMessagesReplyParams = NonNullable; declare class EmailThreadsMessagesResource extends Resource { /** * List the messages in a conversation newest first, both directions. Page older messages with `starting_after`, and pass `include=extracted_text` to inline each message's extracted plain text. * * @example List a thread's messages * for await (const msg of bird.email.threads.messages.list("thr_01abc")) { * console.log(msg.id, msg.direction); * } */ list(threadId: string, query?: EmailThreadsMessagesListQuery, options?: RequestOptions): PaginatedPromise; /** * Get one conversation message with its extracted plain text, readable for the mailbox's full retention tier without MIME parsing. * * @example Get a message * const msg = await bird.email.threads.messages.get("thr_01abc", "rem_01xyz"); * console.log(msg.direction); // "inbound" */ get(threadId: string, messageId: string, options?: RequestOptions): APIPromise; /** * Get the original rendered HTML and plain-text body of a conversation message. Available for 30 days. After that, use the message's extracted_text. * * @example Get a message body * const body = await bird.email.threads.messages.body("thr_01abc", "rem_01xyz"); * console.log(body.text); */ body(threadId: string, messageId: string, options?: RequestOptions): APIPromise; /** * Reply to a specific conversation message from the mailbox's own address. To reply to a conversation, target its newest received message. Recipients, subject, and threading headers are derived automatically. * * @example Reply to a message * const reply = await bird.email.threads.messages.reply("thr_01abc", "rem_01xyz", { * text: "Thanks for reaching out!", * }); * console.log(reply.id); */ reply(threadId: string, messageId: string, params?: EmailThreadsMessagesReplyParams, options?: RequestOptions): APIPromise; /** * List the attachments on a conversation message. Bytes are downloadable for 30 days, and the metadata stays readable afterward on the message's attachment_manifest. * * @example List a message's attachments * const atts = await bird.email.threads.messages.attachments("thr_01abc", "rem_01xyz"); * console.log(atts.data.map((a) => a.filename)); */ attachments(threadId: string, messageId: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/emailThreads.d.ts declare class EmailThreadsResource extends EmailThreadsResourceBase { /** Messages in a conversation — `bird.email.threads.messages.list(...)`, `.reply(...)`, … */ readonly messages: EmailThreadsMessagesResource; constructor(...args: ConstructorParameters); } //#endregion //#region src/resources/email.d.ts /** Body for `bird.email.send`. */ type EmailSendParams = EmailMessageSendRequest; /** Body for `bird.email.sendBatch`. Contains send params validated as a unit. */ type EmailSendBatchParams = EmailMessageBatchRequest; /** Result of `bird.email.sendBatch`. Contains one accepted item per submitted message. */ type EmailSendBatchResult = EmailMessageBatchResponse; /** Filters and cursor params for `bird.email.list`. */ type EmailListQuery = NonNullable; declare class EmailResource extends EmailResourceBase { #private; /** Email statistics — `bird.email.stats.summary(...)`, `.daily(...)`, `.byTag(...)`, … */ readonly stats: EmailStatsResource; /** Durable agent mailboxes — `bird.email.mailboxes.list(...)`, `.create(...)`, … */ readonly mailboxes: EmailMailboxesResource; /** Conversations across every mailbox — `bird.email.threads.list(...)`, `.get(...)`, … */ readonly threads: EmailThreadsResource; constructor(core: ConstructorParameters[0], client: ConstructorParameters[1], defaults?: D); /** * Send an email message. Resolves once the message is accepted for delivery * (the API's 202). Throws on failure — a 422 (unverified sender, all * recipients suppressed, validation) is a `BirdValidationError`. Fields set as * channel defaults may be omitted (per-send value wins). * * @example Send a message * const msg = await bird.email.send({ * from: { email: "onboarding@messagebird.dev", name: "Bird" }, * to: ["delivered@messagebird.dev"], * subject: "Hello from Bird", * html: "

My first Bird email.

", * }); * console.log(msg.id, msg.status); // "em_…", "accepted" * * @example Send a published template instead of inline content * const msg = await bird.email.send({ * from: { email: "onboarding@messagebird.dev", name: "Bird" }, * to: ["delivered@messagebird.dev"], * category: "transactional", * template: { * slug: "welcome-email", * parameters: { first_name: "Jane" }, * }, * }); * console.log(msg.id, msg.status); * * @example Sending to the sandbox bounce address, which hard-bounces every time * const msg = await bird.email.send({ * from: { email: "onboarding@messagebird.dev", name: "Bird" }, * to: ["bounce+signup-flow@messagebird.dev"], * subject: "Sandbox bounce test", * html: "

This message will hard-bounce.

", * tags: [{ name: "flow", value: "signup" }], * metadata: { test_run: "docs-capture-1" }, * }); * console.log(msg.id, msg.status); // "em_…", "accepted" * * @example A richer send — cc/bcc, reply-to, tags, metadata, click-tracking off, and an idempotency key (safe to retry; the server dedupes) * await bird.email.send( * { * from: "hello@acme.com", * to: ["a@example.com", "b@example.com"], * cc: ["manager@example.com"], * reply_to: ["support@acme.com"], * subject: "Your March invoice", * html: "

Attached.

", * tags: [{ name: "category", value: "billing" }], * metadata: { invoice_id: "inv_123" }, * track_clicks: false, * }, * { idempotencyKey: "invoice-march/cust_1" }, * ); * * @example Branch on the typed error hierarchy * import { BirdRateLimitError, BirdValidationError, BirdAPIError } from "@messagebird/sdk"; * * try { * await bird.email.send({ * from: { email: "onboarding@messagebird.dev", name: "Bird" }, * to: ["delivered@messagebird.dev"], * subject: "Hello from Bird", * html: "

My first Bird email.

", * }); * } catch (err) { * if (err instanceof BirdRateLimitError) console.log(`rate limited; retry in ${err.retryAfter}s`); * else if (err instanceof BirdValidationError) console.error(err.details); * else if (err instanceof BirdAPIError) console.error(err.code, err.requestId); * else throw err; * } * * @example Errors as values with `.safe()` * const { data, error } = await bird.email * .send({ * from: { email: "onboarding@messagebird.dev", name: "Bird" }, * to: ["delivered@messagebird.dev"], * subject: "Hello from Bird", * html: "

My first Bird email.

", * }) * .safe(); * if (error) console.error(error.message); * else console.log(data.id); */ send(params: EmailSend, options?: RequestOptions): APIPromise; /** * Send a batch of up to 100 independent email messages in one request. The * batch is validated as a unit — if any item fails validation (unverified * sender, all recipients suppressed, field-level errors) the whole batch is * rejected with a `BirdValidationError` and nothing is queued. Resolves with * one accepted item per submitted message, in submission order, once the batch * is accepted (the API's 202). Channel defaults are applied per item, so a * field set as a default may be omitted from every item (per-item value wins). * * @example Send a batch of messages * const batch = await bird.email.sendBatch([ * { * from: { email: "onboarding@messagebird.dev", name: "Bird" }, * to: ["alice@example.com"], * subject: "Your receipt", * html: "

Thanks, Alice.

", * }, * { * from: { email: "onboarding@messagebird.dev", name: "Bird" }, * to: ["bob@example.com"], * subject: "Your receipt", * html: "

Thanks, Bob.

", * }, * ]); * for (const item of batch.data) console.log(item.id, item.status); */ sendBatch(params: EmailSendBatch, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/audiences.gen.d.ts type AudienceListQuery = NonNullable; type AudienceCreateParams = NonNullable; type AudienceUpdateParams = NonNullable; type AudienceListContactsQuery = NonNullable; type AudienceAddContactsParams = NonNullable; type AudienceRemoveContactsParams = NonNullable; declare class AudiencesResource extends Resource { /** * List the workspace's audiences as a cursor page, newest first. Filter by name substring with `q`. * * @example Iterate every audience, or take one page * for await (const audience of bird.audiences.list()) { * console.log(audience.id, audience.name); * } */ list(query?: AudienceListQuery, options?: RequestOptions): PaginatedPromise; /** * Get a single audience by ID: name, description, and type. Members are listed separately with `audiences.list_contacts`. * * @example Fetch an audience by id * const audience = await bird.audiences.get("adn_01krdgeqcxet5s7t44vh8rt9mg"); * console.log(audience.name); */ get(audienceId: string, options?: RequestOptions): APIPromise; /** * Create an audience in the workspace. New audiences start empty; add contacts with `audiences.add_contacts` or `contacts.batch`. Only static audiences can be created today. * * @example Create an audience * const audience = await bird.audiences.create({ name: "Newsletter subscribers" }); * console.log(audience.id); // "adn_…" */ create(params: AudienceCreateParams, options?: RequestOptions): APIPromise; /** * Update an audience's name or description. Omitted fields are unchanged; a `null` description clears it. * * @example Rename an audience * await bird.audiences.update("adn_01krdgeqcxet5s7t44vh8rt9mg", { name: "Renamed" }); */ update(audienceId: string, params?: AudienceUpdateParams, options?: RequestOptions): APIPromise; /** * Delete an audience and its memberships; contacts themselves are not deleted. Fails while a broadcast targeting the audience is scheduled, accepted, sending, or canceling. * * @example Delete an audience by id * await bird.audiences.delete("adn_01krdgeqcxet5s7t44vh8rt9mg"); */ delete(audienceId: string, options?: RequestOptions): APIPromise; /** * List the contacts in a static audience by ID, as a cursor page ordered by when each contact joined (most recent first). Each entry pairs the contact with its join time. * * @example Iterate an audience's members * for await (const member of bird.audiences.listContacts("adn_01krdgeqcxet5s7t44vh8rt9mg")) { * console.log(member.contact.id, member.joined_at); * } */ listContacts(audienceId: string, query?: AudienceListContactsQuery, options?: RequestOptions): PaginatedPromise; /** * Add up to 1,000 existing contacts to a static audience by ID. Fails entirely if any contact ID does not exist. To add contacts you have not created yet, use `contacts.batch` with `audience_ids` instead: it matches or creates each contact by email address and assigns it to the audience in one call. * * @example Add contacts to an audience * await bird.audiences.addContacts("adn_01krdgeqcxet5s7t44vh8rt9mg", { * contact_ids: ["con_01krdgeqcxet5s7t44vh8rt9mg"], * }); */ addContacts(audienceId: string, params: AudienceAddContactsParams, options?: RequestOptions): APIPromise; /** * Remove up to 1,000 contacts from a static audience by ID. Fails entirely if any contact ID does not exist; contacts are not deleted. * * @example Remove contacts from an audience * await bird.audiences.removeContacts("adn_01krdgeqcxet5s7t44vh8rt9mg", { * contact_ids: ["con_01krdgeqcxet5s7t44vh8rt9mg"], * }); */ removeContacts(audienceId: string, params: AudienceRemoveContactsParams, options?: RequestOptions): APIPromise; /** * Remove one contact's membership from an audience. The contact itself is not deleted and stays a member of any other audiences. * * @example Remove one contact's membership * await bird.audiences.removeContact( * "adn_01krdgeqcxet5s7t44vh8rt9mg", * "con_01krdgeqcxet5s7t44vh8rt9mg", * ); */ removeContact(audienceId: string, contactId: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/domains.gen.d.ts type DomainListQuery = NonNullable; type DomainCreateParams = NonNullable; type DomainUpdateParams = NonNullable; declare class DomainsResource extends Resource { /** * List the workspace's sending domains with their verification status, as a cursor page. * * @example Iterate every sending domain * for await (const domain of bird.domains.list()) { * console.log(domain.id, domain.status); * } */ list(query?: DomainListQuery, options?: RequestOptions): PaginatedPromise; /** * Fetch one sending domain: verification status and the DNS records with their individual verification states. * * @example Fetch a sending domain by id * const domain = await bird.domains.get("dom_01krdgeqcxet5s7t44vh8rt9mg"); * console.log(domain.domain); */ get(domainId: string, options?: RequestOptions): APIPromise; /** * Register a new sending domain and get the DNS records to publish. Verification is a second step: the records go live at the DNS provider, then email_domains_verify confirms them. Propagation takes minutes to hours, so the first verify often still reports unverified and a later one succeeds. * * @example Register a sending domain * const domain = await bird.domains.create({ domain: "mail.acme.com" }); * console.log(domain.id, domain.status); // "dom_…", "pending" */ create(params: DomainCreateParams, options?: RequestOptions): APIPromise; /** * Trigger a DNS verification check for a sending domain and return the refreshed domain with per-record results. Safe to repeat while waiting for DNS propagation. * * @example Re-run the DNS verification check * const domain = await bird.domains.verify("dom_01krdgeqcxet5s7t44vh8rt9mg"); * console.log(domain.status); // "verified" once DNS is in place */ verify(domainId: string, options?: RequestOptions): APIPromise; /** * Update a sending domain's tracking and inbound configuration. Tracking: click_tracking and open_tracking apply immediately to new sends, and the tracking domain can be set, changed, or removed (the name part only, and the sending domain is appended for you). Enabling either toggle with no tracking domain configured returns 409, and removing the tracking domain while either toggle is still on also returns 409. Tracking-domain changes on a verified domain are staged behind DNS verification, so the current config keeps serving until the new records verify. Inbound receiving: inbound.enabled starts or stops receiving mail for the domain. Enabling requires the domain's DKIM to be verified first (a fresh enable on an unverified domain returns 422), and a domain already receiving inbound for another organization returns 422. The MX records to publish are always listed in dns_records regardless, marked optional until inbound.enabled is set, so receiving starts only once you set it even when those records are already published. Publishing them earlier is not free: on a domain at the zone apex they replace the MX records carrying its existing mail, changing where that mail is delivered. * * @example Enable tracking on a domain * await bird.domains.update("dom_01krdgeqcxet5s7t44vh8rt9mg", { * settings: { click_tracking: true, open_tracking: true }, * tracking: { name: "links" }, * }); */ update(domainId: string, params?: DomainUpdateParams, options?: RequestOptions): APIPromise; /** * Delete a sending domain by ID. Revokes its sender authorization: new sends from the domain are rejected afterward, while historical statistics and events for past sends are preserved. Destructive. * * @example Delete a sending domain by id * await bird.domains.delete("dom_01krdgeqcxet5s7t44vh8rt9mg"); */ delete(domainId: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/contactProperties.gen.d.ts type ContactPropertyListQuery = NonNullable; type ContactPropertyCreateParams = NonNullable; type ContactPropertyUpdateParams = NonNullable; declare class ContactPropertiesResource extends Resource { /** * List the workspace's contact properties as a cursor page, newest first. Archived properties are included, marked by their archived flag. * * @example Iterate every contact property, or take one page * for await (const prop of bird.contactProperties.list()) { * console.log(prop.key, prop.type); * } * const page = await bird.contactProperties.list({ limit: 50 }); // page.data, page.next_cursor */ list(query?: ContactPropertyListQuery, options?: RequestOptions): PaginatedPromise; /** * Get a single contact property by ID: key, type, fallback value, and archived state. * * @example Fetch a contact property by id * const prop = await bird.contactProperties.get("cp_01krdgeqcxet5s7t44vh8rt9mg"); * console.log(prop.key, prop.type); */ get(propertyId: string, options?: RequestOptions): APIPromise; /** * Define a custom contact property (key + value type) that becomes available in contact data and as a broadcast template variable. The key and type cannot change after creation; a workspace holds at most 200 properties, archived included. * * @example Define a custom property * const prop = await bird.contactProperties.create({ key: "plan", type: "string" }); * console.log(prop.id); // "cp_…" */ create(params: ContactPropertyCreateParams, options?: RequestOptions): APIPromise; /** * Update a contact property's fallback value. Only the fallback value can change; the key and type are fixed at creation, so a different key or type needs a new property. * * @example Change a property's fallback value * await bird.contactProperties.update("cp_01krdgeqcxet5s7t44vh8rt9mg", { fallback_value: "free" }); */ update(propertyId: string, params?: ContactPropertyUpdateParams, options?: RequestOptions): APIPromise; /** * Archive a contact property: the key is rejected in new contact writes and stops rendering in templates, while stored values remain readable. The key stays reserved and counts toward the 200-property limit; reverse with `contact_properties.unarchive`. * * @example Archive a property, retiring the field without deleting its data * const prop = await bird.contactProperties.archive("cp_01krdgeqcxet5s7t44vh8rt9mg"); * console.log(prop.key, prop.archived); */ archive(propertyId: string, options?: RequestOptions): APIPromise; /** * Reactivate an archived contact property so its key is accepted in contact writes and renders in templates again. Fails with a conflict if the property is not archived. * * @example Restore an archived property * await bird.contactProperties.unarchive("cp_01krdgeqcxet5s7t44vh8rt9mg"); */ unarchive(propertyId: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/contacts.gen.d.ts type ContactListQuery = NonNullable; type ContactCreateParams = NonNullable; type ContactUpdateParams = NonNullable; type ContactBatchParams = NonNullable; declare class ContactsResource extends Resource { /** * List the workspace's contacts as a cursor page, newest first. Look one up by exact email, phone_number, or external_id, repeating phone_number to resolve up to 50 numbers in one call (raise limit to match), or search by email, name, or phone substring. Pass include_total for a total count. * * @example Iterate every contact, or take one page * for await (const contact of bird.contacts.list({ q: "acme.com" })) { * console.log(contact.id, contact.email); * } * const page = await bird.contacts.list({ limit: 50 }); // page.data, page.next_cursor */ list(query?: ContactListQuery, options?: RequestOptions): PaginatedPromise; /** * Get a single contact by ID. Look up an ID by exact email, phone_number, or external_id with `contacts.list`. * * @example Fetch a contact by id * const contact = await bird.contacts.get("con_01krdgeqcxet5s7t44vh8rt9mg"); * console.log(contact.email, contact.first_name); */ get(contactId: string, options?: RequestOptions): APIPromise; /** * Create a contact identified by an email address, an E.164 phone number, or both. Fails with a conflict if the email, phone_number, or external_id is already used by another contact. For bulk import or create-or-update semantics use `contacts.batch`. * * @example Create a contact * const contact = await bird.contacts.create({ * email: "jane@acme.com", * first_name: "Jane", * }); * console.log(contact.id); // "con_…" */ create(params?: ContactCreateParams, options?: RequestOptions): APIPromise; /** * Update a contact's name, `external_id`, email, `phone_number`, or custom data. Only supplied fields change; custom data keys are merged, with `null` removing a key. A contact keeps at least one identifier: clearing both email and `phone_number` is rejected. * * @example Change a contact's fields * const contact = await bird.contacts.update("con_01krdgeqcxet5s7t44vh8rt9mg", { * first_name: "Jane", * }); * console.log(contact.first_name); */ update(contactId: string, params?: ContactUpdateParams, options?: RequestOptions): APIPromise; /** * Delete a contact and remove it from every audience it belongs to. Suppression records for the address are unaffected. * * @example Delete a contact by id * await bird.contacts.delete("con_01krdgeqcxet5s7t44vh8rt9mg"); */ delete(contactId: string, options?: RequestOptions): APIPromise; /** * Create or update up to 1,000 contacts in one request. Match each entry against every supplied identifier (`email`, `phone_number`, and `external_id`), or set `match_on` to use one identifier. Optionally add all successful contacts to up to 10 audiences. Results follow submission order. * * @example Create or update many contacts at once, matched by the identifiers each entry carries * const result = await bird.contacts.batch({ * contacts: [{ email: "jane@acme.com", first_name: "Jane" }], * }); * for (const item of result.data) { * console.log(item.entry.email, item.status); * } */ batch(params: ContactBatchParams, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/sms.gen.d.ts type SmsListQuery = NonNullable; type SmsListEventsQuery = NonNullable; declare class SmsResourceBase extends Resource { /** * Get one SMS message by ID: its current delivery status, segment breakdown, cost, and failure detail if it failed. * * @example Read a message back * const msg = await bird.sms.get("sms_abc123"); * msg.status; // "accepted" | "delivered" | … */ get(messageId: string, options?: RequestOptions): APIPromise; /** * List SMS messages, newest first, as a cursor page (`data`, `next_cursor`). Pass `next_cursor` back as `starting_after` to fetch the next page. Filter by direction, status, category, recipient, sender, or tag. * * @example Iterate outbound messages * for await (const msg of bird.sms.list({ direction: "outbound" })) { * console.log(msg.id, msg.status); * } */ list(query?: SmsListQuery, options?: RequestOptions): PaginatedPromise; /** * The lifecycle event timeline for one SMS, oldest first: what happened to it and when. Filter with `type` (for example `sms.delivered`) to keep one kind of event. Use `sms.get` for the message's current state and `sms.list` to find its ID. * * @example Read one message's lifecycle timeline * const events = await bird.sms.listEvents("sms_abc123"); * for (const event of events.data ?? []) { * console.log(event.type, event.occurred_at); * } */ listEvents(messageId: string, query?: SmsListEventsQuery, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/smsStats.gen.d.ts type SmsStatsSummaryQuery = NonNullable; type SmsStatsDailyQuery = NonNullable; type SmsStatsHourlyQuery = NonNullable; type SmsStatsByCountryQuery = NonNullable; type SmsStatsByCarrierQuery = NonNullable; type SmsStatsByCategoryQuery = NonNullable; type SmsStatsByOriginatorQuery = NonNullable; type SmsStatsByStatusQuery = NonNullable; type SmsStatsByErrorCodeQuery = NonNullable; type SmsStatsByTagQuery = NonNullable; declare class SmsStatsResourceBase extends Resource { /** * Aggregate SMS KPIs for one period: accepted, sent, delivered, undelivered, failed, rejected and expired counts, the derived delivery and failure rates, and latency percentiles. The `from` and `to` values are both YYYY-MM-DD days or both RFC 3339 instants (hour grain). Add `compare=previous_period` for deltas against the preceding window. For a per-day or per-hour series use `sms.stats.daily` or `sms.stats.hourly`. * * @example Aggregate KPIs for a window * const summary = await bird.sms.stats.summary({ * from: "2026-05-01", // both calendar days for a day window, or * to: "2026-05-31", // both RFC 3339 instants for an hour window * }); * console.log(summary.delivery, summary.latency); */ summary(query?: SmsStatsSummaryQuery, options?: RequestOptions): APIPromise; /** * One row of SMS lifecycle counts per calendar day, for charts and trend lines. The window is at most 365 days; set `timezone` to get local calendar days instead of UTC. Rates and latency percentiles are whole-window figures, so read those from `sms.stats.summary`. * * @example One row per calendar day * const stats = await bird.sms.stats.daily({ from: "2026-05-01", to: "2026-05-31" }); * for (const point of stats.data ?? []) { * console.log(point.bucket, point.delivery); * } */ daily(query?: SmsStatsDailyQuery, options?: RequestOptions): APIPromise; /** * One row of SMS lifecycle counts per hour, for inspecting send rate and deliverability inside a single day. The window is at most 30 days (720 rows) and both bounds round down to the hour. For longer ranges use `sms.stats.daily`. * * @example One row per hour, up to 30 days * const stats = await bird.sms.stats.hourly({ * from: "2026-05-30T00:00:00Z", * to: "2026-05-31T00:00:00Z", * }); * for (const point of stats.data ?? []) { * console.log(point.bucket, point.delivery); * } */ hourly(query?: SmsStatsHourlyQuery, options?: RequestOptions): APIPromise; /** * SMS delivery and latency stats grouped by destination country, ranked by the `sort` metric (default `accepted`) and capped by `limit` (default 50, maximum 200). Use it to find where delivery is worst before drilling into `sms.stats.by_error_code`. * * @example Find where delivery is worst * const stats = await bird.sms.stats.byCountry({ * from: "2026-05-01", * to: "2026-05-31", * sort: "delivery_rate", * }); * for (const row of stats.data ?? []) { * console.log(row.country, row.delivery); * } */ byCountry(query?: SmsStatsByCountryQuery, options?: RequestOptions): APIPromise; /** * SMS delivery and latency stats grouped by the carrier that handled the message, ranked by the `sort` metric (default `accepted`) and capped by `limit` (default 50, maximum 200). Use it to compare delivery performance across carriers. * * @example Compare delivery across carriers * const stats = await bird.sms.stats.byCarrier({ from: "2026-05-01", to: "2026-05-31" }); * for (const row of stats.data ?? []) { * console.log(row.carrier, row.delivery); * } */ byCarrier(query?: SmsStatsByCarrierQuery, options?: RequestOptions): APIPromise; /** * SMS delivery and latency stats grouped by the category you sent under, ranked by the `sort` metric (default `accepted`) and capped by `limit` (default 50, maximum 200). * * @example Split a window by category * const stats = await bird.sms.stats.byCategory({ from: "2026-05-01", to: "2026-05-31" }); * for (const row of stats.data ?? []) { * console.log(row.category, row.delivery); * } */ byCategory(query?: SmsStatsByCategoryQuery, options?: RequestOptions): APIPromise; /** * SMS delivery and latency stats grouped by originator, the sender address messages went out from, ranked by the `sort` metric (default `accepted`) and capped by `limit` (default 50, maximum 200). Use it to compare how your senders perform. * * @example Compare how each sender performs * const stats = await bird.sms.stats.byOriginator({ from: "2026-05-01", to: "2026-05-31" }); * for (const row of stats.data ?? []) { * console.log(row.originator, row.delivery); * } */ byOriginator(query?: SmsStatsByOriginatorQuery, options?: RequestOptions): APIPromise; /** * How many messages ended the period in each lifecycle status: accepted, sent, delivered, undelivered, failed, rejected, expired, ordered by count. The "where did my messages end up" view, suitable for a status-distribution chart. * * @example Where the window's messages ended up * const stats = await bird.sms.stats.byStatus({ from: "2026-05-01", to: "2026-05-31" }); * for (const row of stats.data ?? []) { * console.log(row.status, row.count); * } */ byStatus(query?: SmsStatsByStatusQuery, options?: RequestOptions): APIPromise; /** * SMS stats grouped by our normalized failure reason, which answers which reasons are driving your failures. The grouping key is the same value as the `error_code` filter on `sms.list`, so a row joins straight to the messages behind it. Ranked by the `sort` metric (default `failed`) and capped by `limit`. * * @example Which reasons drive failures * const stats = await bird.sms.stats.byErrorCode({ from: "2026-05-01", to: "2026-05-31" }); * for (const row of stats.data ?? []) { * // The same value as the error_code filter on bird.sms.list. * console.log(row.error_code, row.delivery); * } */ byErrorCode(query?: SmsStatsByErrorCodeQuery, options?: RequestOptions): APIPromise; /** * SMS delivery and latency stats grouped by tag (`name:value`), ranked by the `sort` metric (default `accepted`) and capped by `limit` (default 50, maximum 200). Only tagged messages appear, and one carrying several tags counts once under each, so rows do not sum to the period total. * * @example Compare campaigns and segments * const stats = await bird.sms.stats.byTag({ from: "2026-05-01", to: "2026-05-31" }); * for (const row of stats.data ?? []) { * // A message carrying several tags counts once under each, so rows do not sum * // to the period total. * console.log(row.tag, row.delivery); * } */ byTag(query?: SmsStatsByTagQuery, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/smsStatsInbound.gen.d.ts type SmsStatsInboundSummaryQuery = NonNullable; type SmsStatsInboundDailyQuery = NonNullable; type SmsStatsInboundHourlyQuery = NonNullable; type SmsStatsInboundByCountryQuery = NonNullable; type SmsStatsInboundByOperatorQuery = NonNullable; type SmsStatsInboundByNumberQuery = NonNullable; declare class SmsStatsInboundResource extends Resource { /** * Total messages your numbers received over a period. For a breakdown use `sms.stats.inbound.by_country`, `sms.stats.inbound.by_operator`, or `sms.stats.inbound.by_number`. * * @example Total messages received * const summary = await bird.sms.stats.inbound.summary({ from: "2026-05-01", to: "2026-05-31" }); * console.log(summary.received); */ summary(query?: SmsStatsInboundSummaryQuery, options?: RequestOptions): APIPromise; /** * Messages your numbers received, one row per calendar day. Set `timezone` to get local calendar days instead of UTC. * * @example Received messages per day * const stats = await bird.sms.stats.inbound.daily({ from: "2026-05-01", to: "2026-05-31" }); * for (const point of stats.data ?? []) { * console.log(point.bucket, point.received); * } */ daily(query?: SmsStatsInboundDailyQuery, options?: RequestOptions): APIPromise; /** * Messages your numbers received, one row per hour, for inspecting inbound volume inside a single day. * * @example Received messages per hour * const stats = await bird.sms.stats.inbound.hourly({ * from: "2026-05-30T00:00:00Z", * to: "2026-05-31T00:00:00Z", * }); * for (const point of stats.data ?? []) { * console.log(point.bucket, point.received); * } */ hourly(query?: SmsStatsInboundHourlyQuery, options?: RequestOptions): APIPromise; /** * Messages your numbers received, grouped by the country of the receiving number. * * @example Where senders messaged from * const stats = await bird.sms.stats.inbound.byCountry({ from: "2026-05-01", to: "2026-05-31" }); * for (const row of stats.data ?? []) { * console.log(row.country, row.received); * } */ byCountry(query?: SmsStatsInboundByCountryQuery, options?: RequestOptions): APIPromise; /** * Messages your numbers received, grouped by the sender's mobile operator. Messages whose operator the carrier did not report are excluded, so these rows can sum to less than `sms.stats.inbound.summary` for the same period. * * @example Received messages per operator * const stats = await bird.sms.stats.inbound.byOperator({ from: "2026-05-01", to: "2026-05-31" }); * for (const row of stats.data ?? []) { * // Messages whose operator the carrier did not report are excluded, so these * // rows can sum to less than the inbound summary for the same period. * console.log(row.mcc_mnc, row.received); * } */ byOperator(query?: SmsStatsInboundByOperatorQuery, options?: RequestOptions): APIPromise; /** * How many messages each of your numbers received, which is the view that shows whether a campaign's reply traffic is landing on the number you expect. * * @example Which number took the traffic * const stats = await bird.sms.stats.inbound.byNumber({ from: "2026-05-01", to: "2026-05-31" }); * for (const row of stats.data ?? []) { * console.log(row.number, row.received); * } */ byNumber(query?: SmsStatsInboundByNumberQuery, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/smsStats.d.ts declare class SmsStatsResource extends SmsStatsResourceBase { /** Received-message statistics — `bird.sms.stats.inbound.summary(...)`, `.byNumber(...)`, … */ readonly inbound: SmsStatsInboundResource; constructor(core: ConstructorParameters[0], client: ConstructorParameters[1]); } //#endregion //#region src/resources/sms.d.ts /** * Body for `bird.sms.send`. Supply either `text` (with `category` and `from`) * or `template`. */ type SmsSendParams = SmsMessageSendRequest; /** Body for `bird.sms.sendBatch`. Contains up to 100 sends. */ type SmsSendBatchParams = SmsMessageBatchRequest; /** Result of `bird.sms.sendBatch`. */ type SmsSendBatchResult = SmsMessageBatchResponse; /** Filters and cursor params for `bird.sms.list`. */ declare class SmsResource extends SmsResourceBase { /** SMS statistics: `bird.sms.stats.summary(...)`, `.daily(...)`, `.inbound.byNumber(...)`, … */ readonly stats: SmsStatsResource; constructor(core: ConstructorParameters[0], client: ConstructorParameters[1]); /** * Send one SMS to a single recipient. Supply either `text` (with a `category` and `from`) * or a stored `template` (by `id` or `slug`, with its `parameters`). The API * accepts the message for delivery. Read it back with `get` for the latest status. * * @example Send free text * const msg = await bird.sms.send({ * from: "+15557654321", * to: "+14155550100", * text: "Your verification code is 123456.", * category: "authentication", * }); * console.log(msg.id, msg.status); * * @example Send by template * await bird.sms.send({ * to: "+14155550100", * template: { slug: "bird_otp_verification", parameters: { code: "123456" } }, * }); */ send(params: SmsSendParams, options?: RequestOptions): APIPromise; /** * Send up to 100 independent SMS messages in one call. Each item is a full send * (free text or template); all items are validated before any are queued. * * @example * const result = await bird.sms.sendBatch([ * { * from: "+15557654321", * to: "+15551111111", * text: "Hi Alice!", * category: "marketing", * }, * { * from: "+15557654321", * to: "+15552222222", * text: "Hi Bob!", * category: "marketing", * }, * ]); */ sendBatch(params: SmsSendBatchParams, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/smsKeywordRules.gen.d.ts type SmsKeywordRulesListQuery = NonNullable; type SmsKeywordRulesCreateParams = NonNullable; type SmsKeywordRulesUpdateParams = NonNullable; declare class SmsKeywordRulesResource extends Resource { /** * List the default and workspace keyword rules that apply to inbound messages, most specific first. Filter by `country`, `number`, `operation`, or `scope`. Pass `number` to see one number's rules in evaluation order. Default coverage varies by country. */ list(query?: SmsKeywordRulesListQuery, options?: RequestOptions): APIPromise; /** * Read one default or workspace keyword rule. Its ID prefix identifies which kind: a workspace rule can be changed with `sms_keyword_rules.update`, a Bird default cannot. */ get(id: string, options?: RequestOptions): APIPromise; /** * Replace the default opt-out, opt-in, or help reply for one country, or add a `custom` keyword. A workspace rule takes precedence over the default for that country. Opt-out and opt-in keywords cannot be assigned to another operation. */ create(params: SmsKeywordRulesCreateParams, options?: RequestOptions): APIPromise; /** * Change one of your own keyword rules: its reply, its extra keywords, or its self-managed attestation. Bird's default rules cannot be updated; create your own for that country instead with `sms_keyword_rules.create`. Omitting `keywords` leaves the set alone, while sending an empty list clears your additions back to Bird's. */ update(id: string, params?: SmsKeywordRulesUpdateParams, options?: RequestOptions): APIPromise; /** * Delete one of your own keyword rules, which restores Bird's default for that country and operation. Bird's own rules cannot be deleted. */ delete(id: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/smsSuppressions.gen.d.ts type SmsSuppressionsListQuery = NonNullable; type SmsSuppressionsAddParams = NonNullable; declare class SmsSuppressionsResource extends Resource { /** * List the workspace's SMS suppressions (sender-and-subscriber pairs blocked from delivery) as a cursor page. */ list(query?: SmsSuppressionsListQuery, options?: RequestOptions): PaginatedPromise; /** * Read one SMS suppression: the sender and subscriber it covers, why messages are stopped, what it blocks, and whether it is still in force. To check whether you may message someone, filter `sms_suppressions.list` by their number instead. */ get(suppressionId: string, options?: RequestOptions): APIPromise; /** * Stop one of your senders from messaging one subscriber. Covers that sender only; your other senders keep reaching them. */ add(params: SmsSuppressionsAddParams, options?: RequestOptions): APIPromise; /** * End a manual SMS suppression, letting that sender message that subscriber again. Only reason `manual` can be ended this way: a subscriber's own stop keyword and a carrier's opt-out are refused. */ remove(suppressionId: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/smsTemplates.gen.d.ts type SmsTemplateListQuery = NonNullable; declare class SmsTemplatesResource extends Resource { /** * List the SMS templates available to your workspace, including our built-in templates. Filter by scope, category, or language. The catalog is small and returned in full; this list is not paginated. Use `sms_templates.get` to read one template's variables before sending with it. * * @example List the built-in templates * const { data } = await bird.smsTemplates.list({ scope: "system" }); * for (const tpl of data) console.log(tpl.id, tpl.slug); */ list(query?: SmsTemplateListQuery, options?: RequestOptions): APIPromise; /** * Get one SMS template by its slug or ID, including its body and the variables it expects. Fetch it before `sms.send` to see which parameter keys a template send requires. * * @example Read one template by slug or id * const tpl = await bird.smsTemplates.get("bird_otp_verification"); * console.log(tpl.body, tpl.variables); */ get(templateRef: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/whatsapp.gen.d.ts type WhatsappListQuery = NonNullable; type WhatsappListEventsQuery = NonNullable; declare class WhatsappResourceBase extends Resource { /** * Get one WhatsApp message by id: current delivery status, sent/delivered/read timestamps, the one content it was built from (a template, or free-form text, image, video, audio, sticker, document or location), and failure detail if it failed. For the per-event timeline use whatsapp_list_events. * * @example Read a message back * const msg = await bird.whatsapp.get("wa_abc123"); * msg.status; // "accepted" | "delivered" | … */ get(messageId: string, options?: RequestOptions): APIPromise; /** * List WhatsApp messages, newest first, as a cursor page ({data, next_cursor, …}). Each message carries the one content it was built from: a template, or free-form text, image, video, audio, sticker, document or location. Pass next_cursor back as starting_after to fetch the next page. Filter by direction, status, contact phone number, bsuid, template category, or tag. Use whatsapp_get for one message's current state. * * @example Iterate delivered messages * for await (const msg of bird.whatsapp.list({ status: ["delivered"] })) { * console.log(msg.id, msg.status); * } */ list(query?: WhatsappListQuery, options?: RequestOptions): PaginatedPromise; /** * Get one WhatsApp message's delivery timeline, oldest first: whatsapp.accepted, whatsapp.sent, whatsapp.delivered, whatsapp.read, and whatsapp.failed events, with failure detail on failed events. Not paginated; an unknown message ID returns `404`. Use `whatsapp.get` for the condensed current status. * * @example Read one message's delivery timeline * const { data } = await bird.whatsapp.listEvents("wa_abc123"); * for (const event of data) console.log(event.type, event.occurred_at); */ listEvents(messageId: string, query?: WhatsappListEventsQuery, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/whatsapp.d.ts /** Body for `bird.whatsapp.send` — a template send, or one free-form content arm. */ type WhatsappSendParams = WhatsAppMessageSendRequest; declare class WhatsappResource extends WhatsappResourceBase { /** * Send one message, carrying exactly one kind of content: a template, or * free-form `text`, `image`, `video`, `audio`, `sticker`, `document` or * `location`. Every send but a Bird-managed template needs `from`, a number * this workspace owns. The result is `accepted`, not yet delivered — read it * back with `get` to confirm. * * @example * const msg = await bird.whatsapp.send({ * to: "+15551234567", * template: { * slug: "bird_otp", * components: [{ type: "body", parameters: [{ type: "text", text: "123456" }] }], * }, * }); * console.log(msg.id, msg.status); */ send(params: WhatsappSendParams, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/voice.gen.d.ts type VoiceListQuery = NonNullable; declare class VoiceResource extends Resource { /** * List the workspace's calls, newest first. Filter to `ringing`/`in_progress` for the calls in progress right now, to final statuses for completed records, or to any mix of the two. Use `from`/`to` for one known party number in international form, and `number` to search either side by fragment. These are per-call records and do not include aggregate rates or totals. Use `voice.get` to follow one call to settlement. * * @example Iterate the calls happening right now * for await (const call of bird.voice.list({ status: ["ringing", "in_progress"] })) { * console.log(call.id, call.status); * } */ list(query?: VoiceListQuery, options?: RequestOptions): PaginatedPromise; /** * Fetch one call by ID, at any point in its lifecycle. A call still ringing or connected carries no economics yet: `duration_ms`, `billable_ms`, `ended_at`, and `cost` are null until it ends, and the same ID then returns the settled record. Poll here to watch one known call; use `voice.list` to find calls in the first place. When a call was refused, `rejection_reason` names the gate that turned it away. * * @example Read one call back * const call = await bird.voice.get("vcl_01k0p3v9wera3v6q6xw3e9y2mh"); * // A call still ringing or connected carries no economics yet. * call.status; // "answered" | "no_answer" | "ringing" | … */ get(callId: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/verifyVerifications.gen.d.ts type VerifyVerificationsCreateParams = NonNullable; type VerifyVerificationsCheckParams = NonNullable; type VerifyVerificationsNextChannelParams = NonNullable; declare class VerifyVerificationsResource extends Resource { /** * Start a verification and send a one-time passcode to the email address, phone number, or both in `to`. Delivery uses one planned channel at a time and fails over when necessary. Calling again for the same recipient reuses the verification in progress and sends after the resend cooldown. The passcode is never returned; submit the recipient's code with `verify.verifications.check`. SMS, WhatsApp, and Telegram delivery draw on the workspace's balance. * * @example Start a verification over SMS * const verification = await bird.verify.verifications.create({ * to: { phone_number: "+15551234567" }, * }); * console.log(verification.id, verification.status); */ create(params: VerifyVerificationsCreateParams, options?: RequestOptions): APIPromise; /** * Check a passcode a recipient submitted. Identify the verification by the same `to` recipient used to start it; no verification ID is needed. A wrong or expired code returns HTTP 200 with `success: false` and a `reason` (for example `incorrect_code` or `expired`). A verification that has already reached a final state is no longer checkable and returns 404, as does a missing verification; malformed input or rate limiting is also an error status. * * @example Check a submitted passcode * const result = await bird.verify.verifications.check({ * to: { phone_number: "+15551234567" }, * code: "123456", * }); * console.log(result.success); */ check(params: VerifyVerificationsCheckParams, options?: RequestOptions): APIPromise; /** * Advance an in-progress verification to its next channel and send a fresh passcode. Identify it with the same `to` recipient used to create it; no verification ID is required. This bypasses the resend cooldown, and earlier passcodes remain valid. `last_channel` identifies the most recent completed send. `422 NoNextChannel` means the plan is exhausted; create the verification again to resend on the current channel. * * @example Send the code again on the next channel * const verification = await bird.verify.verifications.nextChannel({ * to: { phone_number: "+15551234567" }, * }); * console.log(verification.last_channel); */ nextChannel(params: VerifyVerificationsNextChannelParams, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/verify.d.ts /** The Verify product namespace — holds the `verifications` collection. */ declare class VerifyResource { readonly verifications: VerifyVerificationsResource; constructor(...args: ConstructorParameters); } //#endregion //#region src/resources/webhooks.d.ts /** A verified webhook event, discriminated on `type`. */ type BirdWebhookEvent = WebhookEvent; /** Inbound request headers, as a `Headers` object or a plain record. */ type WebhookHeaders = Headers | Record; /** Client-level webhooks config (`new BirdClient({ webhooks: { secret } })`). */ interface WebhookOptions { /** Signing secret used by `unwrap`; a per-call `secret` overrides it. */ secret?: string; } declare class WebhooksResource { #private; constructor(config?: WebhookOptions); /** * Verify a webhook delivery and return the typed event. * * **Pass the raw request body**, exactly as received — do NOT parse it first. * The Standard Webhooks signature is computed over the raw bytes, so parsing * and re-serializing before verifying is the classic webhook bug. * * The secret comes from `webhooks.secret` on the client; pass `{ secret }` to * override per call. Throws {@link BirdWebhookVerificationError} on a bad * signature, a stale timestamp, or missing/malformed headers. Unknown event * types are returned as-is (handle them in a `default` case) so a newer server * event can't break an older SDK. * * @example One call verifies the signature and returns the typed event * // Pass the RAW request body; set the secret via new BirdClient({ webhooks: { secret } }). * const event = bird.webhooks.unwrap(rawBody, headers); * console.log(event.type); // discriminated union: narrow on event.type * * @example Verify and dispatch: pass the raw request body, never the parsed JSON * // new BirdClient({ apiKey, webhooks: { secret } }) * try { * const event = bird.webhooks.unwrap(rawBody, req.headers); * switch (event.type) { * case "email.delivered": * markDelivered(event.data.email_id, event.data.recipient); // narrowed by event.type * break; * case "email.bounced": * case "email.complained": * suppress(event.data.recipient); * break; * default: // unknown future event types — an older SDK won't break on a new one * } * } catch (err) { * if (err instanceof BirdWebhookVerificationError) { * // reject with 400 — bad signature, stale timestamp, or missing/malformed headers * } else throw err; * } */ unwrap(payload: string, headers: WebhookHeaders, options?: WebhookOptions): BirdWebhookEvent; } //#endregion //#region src/resources/realtime.gen.d.ts type RealtimePublishParams = NonNullable; type RealtimePublishBatchParams = NonNullable; declare class RealtimeResourceBase extends Resource { /** * @example Broadcast an event to a channel * const result = await bird.realtime.publish("rap_01krdgeqcxet5s7t44vh8rt9mg", { * event: "order.updated", * channels: ["orders", "presence-lobby"], * data: { order_id: "ord_123", status: "shipped" }, * }); * console.log(result.data?.length); // one entry per channel */ publish(realtimeAppId: string, params: RealtimePublishParams, options?: RequestOptions): APIPromise; /** * @example Publish two events in one call * await bird.realtime.publishBatch("rap_01krdgeqcxet5s7t44vh8rt9mg", { * events: [ * { event: "order.created", channel: "orders", data: { id: 1 } }, * { event: "order.updated", channel: "orders", data: { id: 2 } }, * ], * }); */ publishBatch(realtimeAppId: string, params: RealtimePublishBatchParams, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/realtimeChannels.gen.d.ts type RealtimeChannelListQuery = NonNullable; type RealtimeChannelGetQuery = NonNullable; declare class RealtimeChannelsResource extends Resource { /** * @example List the occupied presence channels with their member counts * const { data } = await bird.realtime.channels.list("rap_01krdgeqcxet5s7t44vh8rt9mg", { * prefix: "presence-", * include: ["member_count"], * }); * for (const channel of data) console.log(channel.name, channel.member_count); */ list(realtimeAppId: string, query?: RealtimeChannelListQuery, options?: RequestOptions): APIPromise; /** * @example Check whether anyone is in a channel * const channel = await bird.realtime.channels.get("rap_01krdgeqcxet5s7t44vh8rt9mg", "presence-lobby", { * include: ["member_count"], * }); * console.log(channel.occupied, channel.member_count); */ get(realtimeAppId: string, channelName: string, query?: RealtimeChannelGetQuery, options?: RequestOptions): APIPromise; /** * @example Who is in the lobby * const { members } = await bird.realtime.channels.members("rap_01krdgeqcxet5s7t44vh8rt9mg", "presence-lobby"); * for (const member of members) console.log(member.member_id); */ members(realtimeAppId: string, channelName: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/realtimeMembers.gen.d.ts type RealtimeMemberSendParams = NonNullable; declare class RealtimeMembersResource extends Resource { /** * @example Notify one person wherever they are signed in * await bird.realtime.members.send("rap_01krdgeqcxet5s7t44vh8rt9mg", "user_42", { * event: "order-shipped", * data: { order_id: "ord_123" }, * }); */ send(realtimeAppId: string, memberId: string, params: RealtimeMemberSendParams, options?: RequestOptions): APIPromise; /** * @example Kick a member off every connection * await bird.realtime.members.disconnect("rap_01krdgeqcxet5s7t44vh8rt9mg", "user_42"); */ disconnect(realtimeAppId: string, memberId: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/realtime.d.ts /** * Realtime app credentials — `new BirdClient({ realtime: { key, secret } })`. * They come from the app's credentials (shown once at creation) and must belong * to the calling workspace. */ interface RealtimeOptions { /** The Realtime app key, sent as `X-Realtime-Key`. */ key?: string; /** The Realtime app secret, sent as `X-Realtime-Secret`. */ secret?: string; /** * The end-to-end encryption master key for `private-encrypted-` channels: * 32 random bytes, base64-encoded. Yours alone — it is used locally to * encrypt publishes and derive each channel's `shared_secret`, and is never * sent to Bird. Losing it makes rotating to a new one the only recovery. */ encryptionMasterKey?: string; } /** * What `authorizeChannel` returns: the JSON your auth endpoint sends back to * the browser client, field names already on the wire spelling. */ interface ChannelAuthorization { /** `:` signature the edge verifies. */ auth: string; /** Echo of the signed member data (presence channels). */ member_data?: string; /** The channel's decryption key, base64 (encrypted channels). */ shared_secret?: string; } /** * `bird.realtime` — publish events to a Realtime app's channels and inspect its * live state. Reached as `bird.realtime.*`. */ declare class RealtimeResource extends RealtimeResourceBase { #private; /** Channel state — `bird.realtime.channels.list(...)`, `.get(...)`, `.members(...)`. */ readonly channels: RealtimeChannelsResource; /** Members — `bird.realtime.members.send(...)`, `.disconnect(...)`. */ readonly members: RealtimeMembersResource; constructor(core: ConstructorParameters[0], client: ConstructorParameters[1], options?: RealtimeOptions); /** * Publish, with end-to-end encryption when the channel asks for it: a * `private-encrypted-` channel's payload is sealed locally under the * configured master key before the request leaves the process. One channel * per encrypted publish — each channel derives its own key, so a fan-out * would deliver ciphertext other channels' subscribers cannot open. * * @example Publish to an encrypted channel * // Client config: realtime: { key, secret, encryptionMasterKey } * await bird.realtime.publish("rap_01krdgeqcxet5s7t44vh8rt9mg", { * event: "order.updated", * channels: ["private-encrypted-orders"], * data: { order_id: "ord_123", status: "shipped" }, * }); */ publish(realtimeAppId: string, params: RealtimePublishParams, options?: RequestOptions): APIPromise; /** * Publish a batch, sealing each event addressed to a `private-encrypted-` * channel under that channel's derived key (batch events carry one channel * each, so items encrypt independently). */ publishBatch(realtimeAppId: string, params: RealtimePublishBatchParams, options?: RequestOptions): APIPromise; /** * Sign a channel subscription for the browser client — the body your auth * endpoint returns. Runs locally (no request): the signature is * `HMAC-SHA256(secret, ":[:]")`, * prefixed with the app key. For a presence channel pass `memberData`, the * exact JSON string carrying `member_id` (and optionally `member_info`) — * it is signed and echoed byte-identical. For a `private-encrypted-` * channel the response also carries the channel's `shared_secret`, derived * from the configured encryption master key. * * @example An Express auth endpoint * app.post("/bird/auth", async (req, res) => { * const { connection_id, channel_name } = req.body; * if (!mayJoin(req.session.user, channel_name)) return res.sendStatus(403); * res.json( * await bird.realtime.authorizeChannel({ * connectionId: connection_id, * channelName: channel_name, * }), * ); * }); */ authorizeChannel(params: { /** The subscribing connection's id, as POSTed by the client. */ connectionId: string; /** The channel being subscribed, as POSTed by the client. */ channelName: string; /** Presence channels: the member-identity JSON string to sign and echo. */ memberData?: string; }): Promise; } //#endregion //#region src/resources/lookup.gen.d.ts type LookupPhoneNumberParams = NonNullable; type LookupEmailParams = NonNullable; declare class LookupResource extends Resource { /** * Create a lookup for a phone number's networks, porting state, country, and line type. Pass `type` to request separately billed `classification`, `porting`, `presence`, `roaming`, `sim_swap`, or `score` blocks. Each block reports its own status, and only blocks with an `ok` status add a charge; the lookup does not contact the number. * * @example Look up a number, buying two extra blocks * const answer = await bird.lookup.phoneNumber({ * phone_number: "+31612345678", * type: ["classification", "score"], * }); * console.log(answer.country_code, answer.line_type); * // Only a block whose status is ok carries a value, and only that one is billed. * if (answer.score?.status === "ok") console.log(answer.score.value); */ phoneNumber(params: LookupPhoneNumberParams, options?: RequestOptions): APIPromise; /** * Create a deliverability lookup for one email address. Returns `result`, `delivery_confidence`, address `flags`, an undeliverable `reason`, and `did_you_mean` when a correction is available. Treat unknown `result` and `reason` values as valid additions and use `delivery_confidence` as the fallback; each completed lookup incurs the same charge. * * @example Check whether an address is worth sending to * const answer = await bird.lookup.email({ email: "aisha.khan@example.com" }); * // result is an open vocabulary; delivery_confidence is always comparable. * console.log(answer.result, answer.delivery_confidence); */ email(params: LookupEmailParams, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/numbers.gen.d.ts type NumbersListQuery = NonNullable; declare class NumbersResourceBase extends Resource { /** * Pages the numbers allocated to the workspace, dedicated and shared alike. Narrows on country, type, prefix and capability, so one number is reached without walking every page. * * @example List the numbers allocated to you * for await (const allocated of bird.numbers.list({ country_code: "GB" })) { * // kind tells a number you bought from one Bird manages for several workspaces. * console.log(allocated.number, allocated.kind, allocated.status); * } */ list(query?: NumbersListQuery, options?: RequestOptions): PaginatedPromise; /** * Reads one allocated number by the id `numbers.list` returns. Carries its status and, where a country demands ownership paperwork, what is still outstanding on it. * * @example Read one number allocated to you * const allocated = await bird.numbers.get("nda_01krdgeqcxet5s7t44vh8rt9mg"); * // A country that asks for ownership paperwork answers here; most answer null. * console.log(allocated.status, allocated.ownership ?? "no paperwork required"); */ get(numberId: string, options?: RequestOptions): APIPromise; /** * Gives a dedicated number back and stops its monthly charge. Irreversible: the number leaves the workspace and the channels built on it stop sending. A shared number cannot be released. * * @example Give a dedicated number back * // Releasing stops the monthly charge and the number stops working for you. * // Only a dedicated number can be released; a shared one answers E14002. * await bird.numbers.release("nda_01krdgeqcxet5s7t44vh8rt9mg"); */ release(numberId: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/numbersAvailable.gen.d.ts type NumbersAvailableListQuery = NonNullable; declare class NumbersAvailableResource extends Resource { /** * Searches one country's numbers on sale. Our own inventory answers first and pages; the last page can carry a live carrier snapshot, so a number seen here may be gone by the time it is ordered. * * @example Find a number to buy in one country * // The search is always country-scoped, so country_code is required. * const page = await bird.numbers.available.list({ * country_code: "GB", * capabilities: ["sms", "voice"], * }); * for (const candidate of page.data) { * console.log(candidate.number, candidate.number_type); * } */ list(query: NumbersAvailableListQuery, options?: RequestOptions): PaginatedPromise; /** * Re-checks one number from `numbers.available.list` against the carrier, so a stale search result is caught before it is ordered. * * @example Check one number is still for sale * // A number a carrier supplies is only on sale while the carrier still has it, * // so a 404 here means someone else took it. * const candidate = await bird.numbers.available.get("+447700900201"); * console.log(candidate.country_code, candidate.capabilities); */ get(number: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/numbersOrders.gen.d.ts type NumbersOrdersCreateParams = NonNullable; type NumbersOrdersListQuery = NonNullable; declare class NumbersOrdersResource extends Resource { /** * Buys a number and starts its monthly charge. Most orders settle inline; one waiting on a carrier comes back pending and is followed with `numbers.orders.get`. A setup fee already taken is not refunded if the order then fails. * * @example Buy a number * const order = await bird.numbers.orders.create({ number: "+447700900201" }); * // Most orders finish inside the request. One that has to wait on a carrier * // comes back without a number_id. Poll it until it is completed or failed. * if (order.status === "completed") { * console.log("allocated as", order.number_id); * } else { * console.log("still", order.status, "; poll", order.id); * } */ create(params: NumbersOrdersCreateParams, options?: RequestOptions): APIPromise; /** * Pages the workspace's purchase attempts, newest first, filtered by status. An order outlives its attempt, so a failure stays readable with the reason it carried. * * @example Find the purchases that did not complete * const page = await bird.numbers.orders.list({ status: "failed" }); * for (const order of page.data) { * console.log(order.number, order.failure_reason ?? ""); * } */ list(query?: NumbersOrdersListQuery, options?: RequestOptions): PaginatedPromise; /** * Reads one order's current state, and the number it produced once completed. This is the poll for an order that came back pending. * * @example Poll an order that did not finish inline * const order = await bird.numbers.orders.get("nor_01krdgeqcxet5s7t44vh8rt9mg"); * // failure_reason says what went wrong, and only ever on a failed order. * console.log(order.status, order.failure_reason ?? ""); */ get(orderId: string, options?: RequestOptions): APIPromise; } //#endregion //#region src/resources/numbers.d.ts declare class NumbersResource extends NumbersResourceBase { /** Numbers on sale — `bird.numbers.available.list(...)`, `.get(...)`. */ readonly available: NumbersAvailableResource; /** Purchases — `bird.numbers.orders.create(...)`, `.list(...)`, `.get(...)`. */ readonly orders: NumbersOrdersResource; constructor(core: ConstructorParameters[0], client: ConstructorParameters[1]); } //#endregion //#region src/client.d.ts interface BirdClientOptions { apiKey: string; /** Explicit base URL; overrides region resolution. For local/self-hosted use. */ baseUrl?: string; /** Region override (e.g. `"eu1"`); the API key prefix is used by default. */ region?: string; /** Per-attempt timeout in ms. Default 60_000. */ timeout?: number; /** Max retry attempts on retryable failures (429, 5xx, network). Default 2. */ maxRetries?: number; /** Custom fetch for testing, proxying, or edge-runtime adapters. Default global fetch. */ fetch?: typeof fetch; /** Headers added to every request. SDK-internal headers win on conflict. */ defaultHeaders?: Record; /** * Email channel defaults. Any field set here may be omitted in * `bird.email.send` (the type enforces this); the per-send value wins. */ email?: EmailChannelDefaults; /** Webhook config. `secret` is the default used by `bird.webhooks.unwrap`. */ webhooks?: WebhookOptions; /** * Realtime app credentials. Every `bird.realtime.*` call authenticates to the * Realtime edge with this key/secret pair; a call's options can override it. */ realtime?: RealtimeOptions; } /** A raw request for the `bird.request` escape hatch. */ interface BirdRequest { method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; /** * Absolute path on the API host, e.g. `/v1/email/domains`; must start * with a single `/`. */ path: string; query?: Record; /** JSON request body. */ body?: unknown; headers?: Record; } type EmailDefaultsOf = O extends { email: infer E extends EmailChannelDefaults; } ? E : undefined; /** * The Bird API client. Construct it with an API key. The region comes from the * key's prefix (`bk_{region}_…`). Pass `baseUrl` or `region` to override it. * * @example Construct and send * const bird = new BirdClient({ apiKey: process.env.BIRD_API_KEY! }); * const msg = await bird.email.send({ * from: "hello@acme.com", * to: ["customer@example.com"], * subject: "Welcome aboard", * html: "

Hi there 👋

", * }); * console.log(msg.id); * * @example Set channel defaults once; a per-send value always wins * const bird = new BirdClient({ * apiKey: process.env.BIRD_API_KEY!, * email: { from: "hello@acme.com", category: "transactional" }, * }); * // `from` and `category` are filled from the defaults; both stay optional in `send`. * await bird.email.send({ to: ["customer@example.com"], subject: "Hi", html: "

hi

" }); * * @example All client options * const bird = new BirdClient({ * apiKey: process.env.BIRD_API_KEY!, * region: "eu1", // optional; overrides the region from the key prefix * baseUrl: "http://localhost:8080", // optional; overrides region (local or self-hosted) * timeout: 60_000, // per-attempt timeout in ms (default 60_000) * maxRetries: 2, // retry budget for transient failures (default 2) * }); */ declare class BirdClient { #private; protected readonly core: BirdHTTPClient; /** Email channel: `bird.email.send(...)`, `.get(...)`, `.list(...)`. */ readonly email: EmailResource>; /** SMS channel: `bird.sms.send(...)`, `.get(...)`, `.list(...)`. */ readonly sms: SmsResource; /** SMS templates: `bird.smsTemplates.list(...)`, `.get(...)`. */ readonly smsTemplates: SmsTemplatesResource; /** SMS suppressions: `bird.smsSuppressions.list(...)`, `.add(...)`, `.remove(...)`. */ readonly smsSuppressions: SmsSuppressionsResource; /** SMS keyword rules: `bird.smsKeywordRules.list(...)`, `.create(...)`, … */ readonly smsKeywordRules: SmsKeywordRulesResource; /** WhatsApp channel: `bird.whatsapp.send(...)`, `.get(...)`, `.list(...)`, `.listEvents(...)`. */ readonly whatsapp: WhatsappResource; /** Voice call log: `bird.voice.list(...)`, `.get(...)`. Your SIP equipment places calls, so this is a read surface. */ readonly voice: VoiceResource; /** Verify: `bird.verify.verifications.create(...)`, `.check(...)`. */ readonly verify: VerifyResource; /** Contacts: `bird.contacts.create(...)`, `.list(...)`, `.get(...)`, `.batch(...)`, … */ readonly contacts: ContactsResource; /** Audiences: `bird.audiences.create(...)`, `.list(...)`, `.addContacts(...)`, … */ readonly audiences: AudiencesResource; /** Contact properties: `bird.contactProperties.create(...)`, `.list(...)`, `.archive(...)`, … */ readonly contactProperties: ContactPropertiesResource; /** Sending domains: `bird.domains.create(...)`, `.list(...)`, `.verify(...)`, … */ readonly domains: DomainsResource; /** Recipient intelligence: `bird.lookup.email(...)`, `.phoneNumber(...)`. Every answer is billed. */ readonly lookup: LookupResource; /** Numbers: `bird.numbers.available.list(...)`, `.orders.create(...)`, `.list(...)`, `.release(...)`. */ readonly numbers: NumbersResource; /** Webhooks: `bird.webhooks.unwrap(payload, headers)` verifies an inbound delivery. */ readonly webhooks: WebhooksResource; /** Realtime: `bird.realtime.publish(...)`, `.channels.list(...)`, `.members.disconnect(...)`, … */ readonly realtime: RealtimeResource; constructor(options: O); /** * Escape hatch for endpoints the typed resources don't cover. Runs the full * lifecycle (auth, retries, idempotency, error mapping); you supply the * response type. Prefer a typed resource method where one exists. * * @throws {TypeError} if `req.path` does not start with exactly one `/` or * resolves to a different origin than the configured Bird API base URL. * * @example Reach an endpoint outside the curated surface. Supply the response type * type Suppressions = { data: Array<{ recipient: string }> }; * const suppressions = await bird.request({ method: "GET", path: "/v1/email/suppressions" }); * console.log(suppressions.data.length); */ request(req: BirdRequest, options?: RequestOptions): APIPromise; } //#endregion //#region src/region.d.ts /** Extracts the region code from a `bk_{region}_{token}` key, or undefined. */ declare function regionFromApiKey(apiKey: string): string | undefined; declare function baseUrlForRegion(region: string): string; //#endregion //#region src/event-types.gen.d.ts /** * Webhook event types known at this SDK version. The wire value is an open * string: a value added by a newer server is returned by `unwrap` unchanged, * so switch on these with a `default` branch. */ declare const WebhookEventType: { readonly DomainFailed: "domain.failed"; readonly DomainVerified: "domain.verified"; readonly EmailAccepted: "email.accepted"; readonly EmailBounced: "email.bounced"; readonly EmailCanceled: "email.canceled"; readonly EmailClicked: "email.clicked"; readonly EmailComplained: "email.complained"; readonly EmailDeferred: "email.deferred"; readonly EmailDelivered: "email.delivered"; readonly EmailListUnsubscribed: "email.list_unsubscribed"; readonly EmailMailboxMessageDelivered: "email_mailbox.message_delivered"; readonly EmailMailboxMessageFailed: "email_mailbox.message_failed"; readonly EmailMailboxMessageReceived: "email_mailbox.message_received"; readonly EmailMailboxMessageSent: "email_mailbox.message_sent"; readonly EmailMailboxSuspended: "email_mailbox.suspended"; readonly EmailMailboxThreadCreated: "email_mailbox.thread_created"; readonly EmailOpened: "email.opened"; readonly EmailOutOfBandBounce: "email.out_of_band_bounce"; readonly EmailProcessed: "email.processed"; readonly EmailReceived: "email.received"; readonly EmailRejected: "email.rejected"; readonly EmailScheduled: "email.scheduled"; readonly EmailSuppressionCreated: "email_suppression.created"; readonly EmailUnsubscribed: "email.unsubscribed"; readonly SmsAccepted: "sms.accepted"; readonly SmsDelivered: "sms.delivered"; readonly SmsExpired: "sms.expired"; readonly SmsFailed: "sms.failed"; readonly SmsReceived: "sms.received"; readonly SmsRejected: "sms.rejected"; readonly SmsSent: "sms.sent"; readonly SmsUndelivered: "sms.undelivered"; readonly VerifyAttemptDelivered: "verify.attempt.delivered"; readonly VerifyAttemptSent: "verify.attempt.sent"; readonly VerifyAttemptUndelivered: "verify.attempt.undelivered"; readonly VerifyVerificationCreated: "verify.verification.created"; readonly VerifyVerificationFailed: "verify.verification.failed"; readonly VerifyVerificationVerified: "verify.verification.verified"; readonly VoiceCallAnswered: "voice_call.answered"; readonly VoiceCallEnded: "voice_call.ended"; readonly VoiceCallInitiated: "voice_call.initiated"; readonly WhatsappAccepted: "whatsapp.accepted"; readonly WhatsappDelivered: "whatsapp.delivered"; readonly WhatsappFailed: "whatsapp.failed"; readonly WhatsappRead: "whatsapp.read"; readonly WhatsappReceived: "whatsapp.received"; readonly WhatsappRejected: "whatsapp.rejected"; readonly WhatsappSent: "whatsapp.sent"; }; /** A known webhook event type value. */ type WebhookEventTypeValue = (typeof WebhookEventType)[keyof typeof WebhookEventType]; //#endregion //#region src/open-enums.gen.d.ts /** * Values of EmailEventType known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const EmailEventType: { readonly EmailAccepted: "email.accepted"; readonly EmailBounced: "email.bounced"; readonly EmailCanceled: "email.canceled"; readonly EmailClicked: "email.clicked"; readonly EmailComplained: "email.complained"; readonly EmailDeferred: "email.deferred"; readonly EmailDelivered: "email.delivered"; readonly EmailListUnsubscribed: "email.list_unsubscribed"; readonly EmailOpened: "email.opened"; readonly EmailOutOfBandBounce: "email.out_of_band_bounce"; readonly EmailProcessed: "email.processed"; readonly EmailRejected: "email.rejected"; readonly EmailScheduled: "email.scheduled"; readonly EmailUnsubscribed: "email.unsubscribed"; }; /** A known EmailEventType value. */ type EmailEventTypeValue = (typeof EmailEventType)[keyof typeof EmailEventType]; /** * Values of EmailLookupFlag known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const EmailLookupFlag: { readonly Disposable: "disposable"; readonly FreeProvider: "free_provider"; readonly Role: "role"; }; /** A known EmailLookupFlag value. */ type EmailLookupFlagValue = (typeof EmailLookupFlag)[keyof typeof EmailLookupFlag]; /** * Values of EmailLookupReason known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const EmailLookupReason: { readonly InvalidDomain: "invalid_domain"; readonly InvalidRecipient: "invalid_recipient"; readonly InvalidSyntax: "invalid_syntax"; }; /** A known EmailLookupReason value. */ type EmailLookupReasonValue = (typeof EmailLookupReason)[keyof typeof EmailLookupReason]; /** * Values of EmailLookupResult known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const EmailLookupResult: { readonly Neutral: "neutral"; readonly Risky: "risky"; readonly Typo: "typo"; readonly Undeliverable: "undeliverable"; readonly Valid: "valid"; }; /** A known EmailLookupResult value. */ type EmailLookupResultValue = (typeof EmailLookupResult)[keyof typeof EmailLookupResult]; /** * Values of LookupFlag known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const LookupFlag: { readonly Ported: "ported"; }; /** A known LookupFlag value. */ type LookupFlagValue = (typeof LookupFlag)[keyof typeof LookupFlag]; /** * Values of LookupPropertyStatus known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const LookupPropertyStatus: { readonly Inconclusive: "inconclusive"; readonly Ok: "ok"; readonly Unavailable: "unavailable"; }; /** A known LookupPropertyStatus value. */ type LookupPropertyStatusValue = (typeof LookupPropertyStatus)[keyof typeof LookupPropertyStatus]; /** * Values of NumberCapability known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const NumberCapability: { readonly Mms: "mms"; readonly Sms: "sms"; readonly Voice: "voice"; }; /** A known NumberCapability value. */ type NumberCapabilityValue = (typeof NumberCapability)[keyof typeof NumberCapability]; /** * Values of NumberType known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const NumberType: { readonly Local: "local"; readonly Mobile: "mobile"; readonly National: "national"; readonly ShortCode: "short_code"; readonly ShortCodeFteu: "short_code_fteu"; readonly TollFree: "toll_free"; }; /** A known NumberType value. */ type NumberTypeValue = (typeof NumberType)[keyof typeof NumberType]; /** * Values of NumbersOrderStatus known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const NumbersOrderStatus: { readonly Charging: "charging"; readonly Completed: "completed"; readonly Failed: "failed"; readonly Ordering: "ordering"; readonly Pending: "pending"; }; /** A known NumbersOrderStatus value. */ type NumbersOrderStatusValue = (typeof NumbersOrderStatus)[keyof typeof NumbersOrderStatus]; /** * Values of SMSErrorCode known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const SMSErrorCode: { readonly BlockedByCarrier: "blocked_by_carrier"; readonly BlockedByRecipient: "blocked_by_recipient"; readonly ContentRejected: "content_rejected"; readonly InsufficientBalance: "insufficient_balance"; readonly InvalidDestination: "invalid_destination"; readonly LandlineUnreachable: "landline_unreachable"; readonly ProviderUnavailable: "provider_unavailable"; readonly RecipientOptedOut: "recipient_opted_out"; readonly SenderUnregistered: "sender_unregistered"; readonly Unknown: "unknown"; readonly Unreachable: "unreachable"; }; /** A known SMSErrorCode value. */ type SMSErrorCodeValue = (typeof SMSErrorCode)[keyof typeof SMSErrorCode]; /** * Values of SMSKeywordOperation known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const SMSKeywordOperation: { readonly Custom: "custom"; readonly Help: "help"; readonly Start: "start"; readonly Stop: "stop"; }; /** A known SMSKeywordOperation value. */ type SMSKeywordOperationValue = (typeof SMSKeywordOperation)[keyof typeof SMSKeywordOperation]; /** * Values of SMSSuppressionCoverage known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const SMSSuppressionCoverage: { readonly All: "all"; readonly NonTransactional: "non_transactional"; }; /** A known SMSSuppressionCoverage value. */ type SMSSuppressionCoverageValue = (typeof SMSSuppressionCoverage)[keyof typeof SMSSuppressionCoverage]; /** * Values of SMSSuppressionEndReason known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const SMSSuppressionEndReason: { readonly ApiKey: "api_key"; readonly CarrierCleared: "carrier_cleared"; readonly KeywordStart: "keyword_start"; readonly User: "user"; }; /** A known SMSSuppressionEndReason value. */ type SMSSuppressionEndReasonValue = (typeof SMSSuppressionEndReason)[keyof typeof SMSSuppressionEndReason]; /** * Values of SMSSuppressionOrigin known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const SMSSuppressionOrigin: { readonly ApiKey: "api_key"; readonly DlrEvent: "dlr_event"; readonly Keyword: "keyword"; readonly User: "user"; }; /** A known SMSSuppressionOrigin value. */ type SMSSuppressionOriginValue = (typeof SMSSuppressionOrigin)[keyof typeof SMSSuppressionOrigin]; /** * Values of SMSSuppressionReason known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const SMSSuppressionReason: { readonly CarrierOptedOut: "carrier_opted_out"; readonly KeywordStop: "keyword_stop"; readonly Manual: "manual"; }; /** A known SMSSuppressionReason value. */ type SMSSuppressionReasonValue = (typeof SMSSuppressionReason)[keyof typeof SMSSuppressionReason]; /** * Values of TemplateLanguageStatus known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const TemplateLanguageStatus: { readonly Draft: "draft"; readonly Live: "live"; readonly Superseded: "superseded"; }; /** A known TemplateLanguageStatus value. */ type TemplateLanguageStatusValue = (typeof TemplateLanguageStatus)[keyof typeof TemplateLanguageStatus]; /** * Values of TemplateStatus known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const TemplateStatus: { readonly Active: "active"; readonly Draft: "draft"; readonly Inactive: "inactive"; readonly Pending: "pending"; readonly Rejected: "rejected"; }; /** A known TemplateStatus value. */ type TemplateStatusValue = (typeof TemplateStatus)[keyof typeof TemplateStatus]; /** * Values of VerificationAttemptFailureReason known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const VerificationAttemptFailureReason: { readonly CarrierRejected: "carrier_rejected"; readonly ChannelDisabled: "channel_disabled"; readonly ChannelUnavailable: "channel_unavailable"; readonly DeliveryTimeout: "delivery_timeout"; readonly HardBounce: "hard_bounce"; readonly NotBillable: "not_billable"; readonly SoftBounce: "soft_bounce"; readonly Undelivered: "undelivered"; }; /** A known VerificationAttemptFailureReason value. */ type VerificationAttemptFailureReasonValue = (typeof VerificationAttemptFailureReason)[keyof typeof VerificationAttemptFailureReason]; /** * Values of VerificationChannel known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const VerificationChannel: { readonly Email: "email"; readonly Sms: "sms"; readonly Telegram: "telegram"; readonly Whatsapp: "whatsapp"; }; /** A known VerificationChannel value. */ type VerificationChannelValue = (typeof VerificationChannel)[keyof typeof VerificationChannel]; /** * Values of VerificationTerminalReason known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const VerificationTerminalReason: { readonly AttemptsExhausted: "attempts_exhausted"; readonly TtlElapsed: "ttl_elapsed"; readonly Undeliverable: "undeliverable"; }; /** A known VerificationTerminalReason value. */ type VerificationTerminalReasonValue = (typeof VerificationTerminalReason)[keyof typeof VerificationTerminalReason]; /** * Values of WhatsAppErrorCode known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const WhatsAppErrorCode: { readonly InsufficientBalance: "insufficient_balance"; readonly InternalError: "internal_error"; readonly MediaRejected: "media_rejected"; readonly PriceNotFound: "price_not_found"; readonly RateLimited: "rate_limited"; readonly RecipientSuppressed: "recipient_suppressed"; readonly ServiceWindowExpired: "service_window_expired"; readonly Undeliverable: "undeliverable"; }; /** A known WhatsAppErrorCode value. */ type WhatsAppErrorCodeValue = (typeof WhatsAppErrorCode)[keyof typeof WhatsAppErrorCode]; /** * Values of WhatsAppEventType known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const WhatsAppEventType: { readonly WhatsappAccepted: "whatsapp.accepted"; readonly WhatsappDelivered: "whatsapp.delivered"; readonly WhatsappFailed: "whatsapp.failed"; readonly WhatsappRead: "whatsapp.read"; readonly WhatsappReceived: "whatsapp.received"; readonly WhatsappRejected: "whatsapp.rejected"; readonly WhatsappSent: "whatsapp.sent"; }; /** A known WhatsAppEventType value. */ type WhatsAppEventTypeValue = (typeof WhatsAppEventType)[keyof typeof WhatsAppEventType]; /** * Values of WhatsAppTemplateCategory known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const WhatsAppTemplateCategory: { readonly Authentication: "authentication"; readonly Marketing: "marketing"; readonly Utility: "utility"; }; /** A known WhatsAppTemplateCategory value. */ type WhatsAppTemplateCategoryValue = (typeof WhatsAppTemplateCategory)[keyof typeof WhatsAppTemplateCategory]; /** * Values of WhatsAppTemplateParameterType known at this SDK version. The wire value is an open * string: a value added by a newer server deserializes unchanged, so switch on * these with a `default` branch rather than treating the set as closed. */ declare const WhatsAppTemplateParameterType: { readonly Document: "document"; readonly Gif: "gif"; readonly Image: "image"; readonly Location: "location"; readonly Text: "text"; readonly Video: "video"; }; /** A known WhatsAppTemplateParameterType value. */ type WhatsAppTemplateParameterTypeValue = (typeof WhatsAppTemplateParameterType)[keyof typeof WhatsAppTemplateParameterType]; //#endregion export { type APIPromise, type Audience, type AudienceAddContactsParams, type AudienceCreateParams, type AudienceListContactsQuery, type AudienceListQuery, type AudienceMember, type AudienceRemoveContactsParams, type AudienceUpdateParams, BirdAPIError, BirdAuthError, BirdBadRequestError, BirdBillingError, BirdClient, type BirdClientOptions, BirdConflictError, BirdConnectionError, BirdError, BirdInternalError, BirdMisdirectedError, BirdNotFoundError, BirdNotImplementedError, BirdPayloadTooLargeError, BirdPermissionError, BirdPreconditionError, BirdRateLimitError, type BirdRequest, type BirdResponse, BirdServiceUnavailableError, BirdTimeoutError, BirdValidationError, type BirdWebhookEvent, BirdWebhookVerificationError, type ChannelAuthorization, type Contact, type ContactBatchParams, type ContactCreateParams, type ContactListQuery, type ContactProperty, type ContactPropertyCreateParams, type ContactPropertyListQuery, type ContactPropertyUpdateParams, type ContactUpdateParams, type ContactUpsertResult, type CursorPage, type DnsRecord, type Domain, type DomainCapabilities, type DomainCreateParams, type DomainDkim, type DomainListQuery, type DomainUpdateParams, type EmailChannelDefaults, EmailEventType, type EmailEventTypeValue, type EmailListQuery, type EmailLookup, EmailLookupFlag, type EmailLookupFlagValue, EmailLookupReason, type EmailLookupReasonValue, EmailLookupResult, type EmailLookupResultValue, type EmailMailboxLabelList, type EmailMailboxesCreateParams, type EmailMailboxesListQuery, type EmailMailboxesMessagesCreateParams, type EmailMailboxesReceiveRulesCreateParams, type EmailMailboxesReceiveRulesListQuery, type EmailMailboxesStatsQuery, type EmailMailboxesUpdateParams, type EmailMailboxesUpdateQuery, type EmailMessage, type EmailSendBatchParams, type EmailSendBatchResult, type EmailSendParams, type EmailStatsByBounceCodeQuery, type EmailStatsByBounceCodeResponse, type EmailStatsByBroadcastQuery, type EmailStatsByBroadcastResponse, type EmailStatsByCategoryQuery, type EmailStatsByCategoryResponse, type EmailStatsByClientQuery, type EmailStatsByClientResponse, type EmailStatsByComplaintTypeQuery, type EmailStatsByComplaintTypeResponse, type EmailStatsByLocationQuery, type EmailStatsByLocationResponse, type EmailStatsByMailboxProviderQuery, type EmailStatsByMailboxProviderRegionQuery, type EmailStatsByMailboxProviderRegionResponse, type EmailStatsByMailboxProviderResponse, type EmailStatsByRecipientDomainQuery, type EmailStatsByRecipientDomainResponse, type EmailStatsBySendingDomainQuery, type EmailStatsBySendingDomainResponse, type EmailStatsBySendingIpQuery, type EmailStatsBySendingIpResponse, type EmailStatsByTagQuery, type EmailStatsByTemplateQuery, type EmailStatsByTemplateResponse, type EmailStatsDailyQuery, type EmailStatsHourlyQuery, type EmailStatsResponse, type EmailStatsSummary, type EmailStatsSummaryQuery, type EmailStatsTagsResponse, type EmailThread, type EmailThreadMessage, type EmailThreadMessageAttachmentList, type EmailThreadMessageBody, type EmailThreadsDeleteQuery, type EmailThreadsListQuery, type EmailThreadsMessagesListQuery, type EmailThreadsMessagesReplyParams, type EmailThreadsUpdateParams, type ErrorDetail, type LookupEmailParams, LookupFlag, type LookupFlagValue, type LookupPhoneNumberParams, LookupPropertyStatus, type LookupPropertyStatusValue, type Mailbox, type MailboxStatsResponse, type NextAction, NumberCapability, type NumberCapabilityValue, NumberType, type NumberTypeValue, NumbersOrderStatus, type NumbersOrderStatusValue, type PaginatedPromise, type PhoneNumberLookup, type RealtimeBatchPublishResult, type RealtimeChannelGetQuery, type RealtimeChannelInclude, type RealtimeChannelInfo, type RealtimeChannelListItem, type RealtimeChannelListQuery, type RealtimeChannelMember, type RealtimeChannelMembers, type RealtimeChannelsList, type RealtimeOptions, type RealtimePublishBatchParams, type RealtimePublishParams, type RealtimePublishResult, type ReceiveRule, type RequestOptions, SMSErrorCode, type SMSErrorCodeValue, SMSKeywordOperation, type SMSKeywordOperationValue, SMSSuppressionCoverage, type SMSSuppressionCoverageValue, SMSSuppressionEndReason, type SMSSuppressionEndReasonValue, SMSSuppressionOrigin, type SMSSuppressionOriginValue, SMSSuppressionReason, type SMSSuppressionReasonValue, type SafeResult, type SmsEventList, type SmsInboundStatsByCountryResponse, type SmsInboundStatsByNumberResponse, type SmsInboundStatsByOperatorResponse, type SmsInboundStatsResponse, type SmsInboundStatsSummaryResponse, type SmsKeywordRule, type SmsKeywordRuleList, type SmsKeywordRulesCreateParams, type SmsKeywordRulesListQuery, type SmsKeywordRulesUpdateParams, type SmsListEventsQuery, type SmsListQuery, type SmsMessage, type SmsSendBatchParams, type SmsSendBatchResult, type SmsSendParams, type SmsStatsByCarrierQuery, type SmsStatsByCarrierResponse, type SmsStatsByCategoryQuery, type SmsStatsByCategoryResponse, type SmsStatsByCountryQuery, type SmsStatsByCountryResponse, type SmsStatsByErrorCodeQuery, type SmsStatsByErrorCodeResponse, type SmsStatsByOriginatorQuery, type SmsStatsByOriginatorResponse, type SmsStatsByStatusQuery, type SmsStatsByStatusResponse, type SmsStatsDailyQuery, type SmsStatsHourlyQuery, type SmsStatsInboundByCountryQuery, type SmsStatsInboundByNumberQuery, type SmsStatsInboundByOperatorQuery, type SmsStatsInboundDailyQuery, type SmsStatsInboundHourlyQuery, type SmsStatsInboundSummaryQuery, type SmsStatsResponse, type SmsStatsSummary, type SmsStatsSummaryQuery, type SmsSuppression, type SmsSuppressionsAddParams, type SmsSuppressionsListQuery, type SmsTemplate, type SmsTemplateList, type SmsTemplateListQuery, TemplateLanguageStatus, type TemplateLanguageStatusValue, TemplateStatus, type TemplateStatusValue, type Verification, VerificationAttemptFailureReason, type VerificationAttemptFailureReasonValue, VerificationChannel, type VerificationChannelValue, type VerificationCheckResult, VerificationTerminalReason, type VerificationTerminalReasonValue, type VerifyVerificationsCheckParams, type VerifyVerificationsCreateParams, type VerifyVerificationsNextChannelParams, WebhookEventType, type WebhookEventTypeValue, type WebhookHeaders, type WebhookOptions, WhatsAppErrorCode, type WhatsAppErrorCodeValue, type WhatsAppEventList, WhatsAppEventType, type WhatsAppEventTypeValue, type WhatsAppMessage, WhatsAppTemplateCategory, type WhatsAppTemplateCategoryValue, WhatsAppTemplateParameterType, type WhatsAppTemplateParameterTypeValue, type WhatsappListEventsQuery, type WhatsappListQuery, type WhatsappSendParams, baseUrlForRegion, regionFromApiKey }; //# sourceMappingURL=index.d.mts.map