{"openapi":"3.1.0","info":{"title":"REST API","version":"1.0.0"},"paths":{"/api/v1/auth/signup":{"post":{"operationId":"signup","summary":"Request a sign-up code","description":"Sends a one-time code to the given email and returns a `verificationId` to complete sign-up. No authentication required.","tags":["authentication"],"responses":{"200":{"description":"Code sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Authentication_signup_Response_200"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"The address to send the code to. Addresses that reach the same mailbox are treated as one account: a `+tag` is dropped from the local part, dots are ignored in a `gmail.com` local part, and `googlemail.com` is read as `gmail.com`. So `dev.user+dial@gmail.com` signs in to the account owned by `devuser@gmail.com` rather than creating a second one, and the account is created under that canonical address."}},"required":["email"]}}}}}},"/api/v1/auth/verify":{"post":{"operationId":"verify-signup","summary":"Verify a sign-up code","description":"Verifies the one-time code for a `verificationId`. This endpoint has **two\npossible outcomes**, and you must branch on the response body:\n\n- **Signing in to an existing account** — the account and API key are\n  returned immediately. **The API key is returned only once — store it\n  immediately.**\n- **Registering a new account** — the email is now verified, but an account\n  is *not* created yet. The response carries a `registrationId` instead.\n  Registering requires a verified phone number: continue with\n  [Register a phone number](#operation/registerNumber), then\n  [Verify a phone number](#operation/verifyNumber), which creates the\n  account and returns the same shape as the sign-in outcome.\n\nIn short: **if the response contains `registrationId`, the flow is not\nfinished.** A new number's inbound voice-agent prompt is always the default\nat creation; change it afterwards with\n[Update a phone number](#operation/updateNumber).\n","tags":["authentication"],"responses":{"200":{"description":"Verified. Either the account (sign-in) or a `registrationId` (registration still needs a verified phone number).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Authentication_verifySignup_Response_200"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many attempts. Wait before retrying. During registration this also covers the cap on verification codes sent to a single phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"verificationId":{"type":"string"},"code":{"type":"string","description":"The 6-digit code from the email."}},"required":["verificationId","code"]}}}}}},"/api/v1/auth/register-number":{"post":{"operationId":"register-number","summary":"Register a phone number","description":"Sends a one-time code by SMS to the phone number that will own the new\naccount. Call this with the `registrationId` returned by\n[Verify a sign-up code](#operation/verifySignup).\n\nThe number must be a valid phone number in E.164 form, and it must not\nalready be registered to another account — **a verified number is bound to\none account permanently.** A Dial number cannot be used to register a Dial\naccount.\n\nCalling this again with the same number re-sends the code. Calling it with\na different number replaces the pending one, so a mistyped number is\nrecoverable until it is verified.\n","tags":["authentication"],"responses":{"200":{"description":"Code sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Authentication_registerNumber_Response_200"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state. During registration this means the registration is unknown or already complete, its email is not verified yet, no phone number has been registered against it, or the phone number is already registered to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many attempts. Wait before retrying. During registration this also covers the cap on verification codes sent to a single phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"registrationId":{"type":"string","description":"From the verify-sign-up response."},"phoneNumber":{"type":"string","description":"The owner's phone number, E.164 preferred."}},"required":["registrationId","phoneNumber"]}}}}}},"/api/v1/auth/verify-number":{"post":{"operationId":"verify-number","summary":"Verify a phone number","description":"Submits the SMS code from [Register a phone number](#operation/registerNumber).\nOn success the account is created, an API key is issued, and a first phone\nnumber is provisioned — the response is identical to the sign-in outcome of\n[Verify a sign-up code](#operation/verifySignup). **The API key is returned\nonly once — store it immediately.**\n\nIf provisioning fails you receive a `502` and may retry this call with the\nsame `registrationId`; the phone number stays verified, so no new code is\nneeded.\n\nNote that `phoneNumber` in the response is the **Dial** number provisioned\nfor the account, not the number you just verified.\n","tags":["authentication"],"responses":{"200":{"description":"Verified, account created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthenticatedAccount"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The code is wrong, expired, or already used. Request a new one with [Register a phone number](#operation/registerNumber).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The request conflicts with the current state. During registration this means the registration is unknown or already complete, its email is not verified yet, no phone number has been registered against it, or the phone number is already registered to another account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many attempts. Wait before retrying. During registration this also covers the cap on verification codes sent to a single phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"registrationId":{"type":"string"},"code":{"type":"string","description":"The 6-digit code from the SMS."}},"required":["registrationId","code"]}}}}}},"/api/v1/account":{"get":{"operationId":"get-account","summary":"Get the current account","tags":["account"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The authenticated account and its API keys (key values are previewed, not full).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"delete-account","summary":"Delete the current account","description":"Permanently deletes the account and releases every phone number. This cannot be undone.","tags":["account"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account_deleteAccount_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update-account","summary":"Update the current account","tags":["account"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"maxCallDurationSeconds":{"type":["integer","null"],"minimum":60,"maximum":3600,"description":"Account-wide default cap on call length, in seconds, for every inbound and outbound call on the account. The effective cap on a call is the first one set in priority order — the per-call cap, then the number's cap, then this account cap — falling back to the global default when none is set. A higher-priority level overrides a lower one (it can raise or lower the limit), so this account cap applies only when neither the call nor the number sets one. Must be between 60 and 3600 seconds (1 minute to 1 hour); a value outside that range returns 400. Send null to clear it (falls back to the global default). Free accounts — accounts that have never added credit and never subscribed — are capped at 300 seconds (5 minutes), reported as `limits.maxCallDurationSeconds` on the account: a higher value sent here returns 400, and a cap inherited from another level is clamped to 300. When a cap is active, Dial warns ~30–40 seconds before the hard cut so the agent can wrap up naturally (managed and LLM self-hosted: an injected spoken prompt; audio self-hosted: a `duration_warning` protocol frame)."}}}}}}}},"/api/v1/usage":{"get":{"operationId":"get-usage","summary":"Get account usage","description":"Activity analytics for the account over a selected window, derived from real message and call records: headline metrics with per-day spark series and deltas, a daily activity series, voice-call duration stats, the most active numbers, and fixed-window breakdown totals.\n","tags":["usage"],"parameters":[{"name":"days","in":"query","description":"The analytics window. One of 7, 30, or 90. Defaults to 30; any other value falls back to 30.","required":false,"schema":{"$ref":"#/components/schemas/ApiV1UsageGetParametersDays","default":30}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Usage analytics for the selected window.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Usage"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing":{"get":{"operationId":"get-billing","summary":"Get billing status","description":"Returns the account's credit-wallet balance, the current subscription (if any), each phone number's billing mode (PAYG or FIXED), and recent usage.","tags":["billing"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Billing status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/checkout/credits":{"post":{"operationId":"create-credits-checkout","summary":"Start a credit top-up checkout","description":"Returns an embedded Stripe Checkout `clientSecret` for a pay-as-you-go credit top-up (amount entered by the customer in Checkout). The wallet is credited when the resulting invoice is paid. The customer may optionally save the card for future use; the first saved card becomes the default payment method automatically. Rejected with `409` while a subscription is active — subscription accounts don't draw on the wallet. Not available during impersonation.","tags":["billing"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Embedded Checkout client secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing_createCreditsCheckout_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A subscription is active, so credit top-ups are disabled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"returnUrl":{"type":"string","format":"uri","description":"Where Checkout returns after completion. Defaults to the dashboard billing page."}}}}}}}},"/api/v1/billing/checkout/subscription":{"post":{"operationId":"create-subscription-checkout","summary":"Start a subscription checkout","description":"Returns an embedded Stripe Checkout `clientSecret` to subscribe the account for the given `interval`. Quantity is set server-side to the account's current phone-number count (one unit per number) — Dial doesn't subscribe a subset. Coverage begins when the invoice is paid. Not available during impersonation.\n\nAccounts holding an iMessage number can't subscribe — iMessage is pay-as-you-go only, so the request is rejected with `403`. Release the iMessage number first.","tags":["billing"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Embedded Checkout client secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing_createSubscriptionCheckout_Response_200"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The account holds an iMessage number, which is pay-as-you-go only and can't be covered by a subscription. Release it before subscribing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"interval":{"$ref":"#/components/schemas/ApiV1BillingCheckoutSubscriptionPostRequestBodyContentApplicationJsonSchemaInterval"},"returnUrl":{"type":"string","format":"uri","description":"Where Checkout returns after completion. Defaults to the dashboard billing page."}},"required":["interval"]}}}}}},"/api/v1/billing/subscription/cancel":{"post":{"operationId":"cancel-subscription","summary":"Cancel the subscription at period end","description":"Schedules the subscription to cancel at the end of the current period. Coverage already paid for stays until `periodEnd`, then the account reverts to pay-as-you-go (no refund or proration). Reversible with Resume before the period ends. Not available during impersonation.","tags":["billing"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Cancellation scheduled.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing_cancelSubscription_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"No active subscription to cancel.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/subscription/resume":{"post":{"operationId":"resume-subscription","summary":"Resume a subscription scheduled to cancel","description":"Clears a scheduled cancellation (the inverse of Cancel), so the subscription renews normally at `periodEnd`. Only meaningful while `cancelAtPeriodEnd` is true. Not available during impersonation.","tags":["billing"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Cancellation cleared; the subscription will renew.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing_resumeSubscription_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"No active subscription to resume.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/subscription/change":{"post":{"operationId":"change-subscription-interval","summary":"Change the subscription billing interval","description":"Switches the subscription billing interval (e.g. upgrade monthly → annual). Prorated and invoiced immediately; the new-period entitlement lands when that invoice is paid. Clears any scheduled cancellation. No-op if already on the target interval. Not available during impersonation.","tags":["billing"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Interval change applied (or already on the target interval).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing_changeSubscriptionInterval_Response_200"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"interval":{"$ref":"#/components/schemas/ApiV1BillingSubscriptionChangePostRequestBodyContentApplicationJsonSchemaInterval"},"promotionCode":{"type":"string","description":"Optional customer-facing promotion code (e.g. `LAUNCH20`) to apply to the subscription. Resolved to its Stripe promotion code and handed to Stripe, which validates it and applies the discount per the underlying coupon's own scope and duration. An invalid, expired, or inapplicable code is rejected with `400`. Preview the discounted amount first with Preview a subscription change."},"prorationDate":{"type":"integer","description":"Optional Unix timestamp (seconds) from a prior preview's `prorationDate`. When supplied, the proration is calculated as of that instant so the charged amount matches the previewed quote exactly. Omit to prorate as of now."}},"required":["interval"]}}}}}},"/api/v1/billing/subscription/preview":{"post":{"operationId":"preview-subscription-change","summary":"Preview a subscription change","description":"Computes the prorated amount that a subscription change would invoice **today**, without making any change or charge. Use it to show the customer what they'll pay before confirming an interval switch, a number addition, or a coupon application. The amounts come straight from Stripe's invoice preview — the same engine that bills the actual change.\n\nProvide the target state: `interval` and/or `quantity` (each defaults to the subscription's current value when omitted), and an optional `promotionCode`. An invalid, expired, or inapplicable code is rejected with `400`. Requires an active subscription.","tags":["billing"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The previewed charge for the proposed change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing_previewSubscriptionChange_Response_200"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"interval":{"$ref":"#/components/schemas/ApiV1BillingSubscriptionPreviewPostRequestBodyContentApplicationJsonSchemaInterval","description":"Target billing interval. Defaults to the current interval."},"quantity":{"type":"integer","minimum":1,"description":"Target number count (one unit per phone number). Defaults to the current quantity."},"promotionCode":{"type":"string","description":"Optional customer-facing promotion code; the preview reflects the discount it produces."}}}}}}}},"/api/v1/billing/payment-methods/setup":{"post":{"operationId":"create-payment-method-setup","summary":"Start a checkout to save a card","description":"Returns an embedded Stripe Checkout `clientSecret` in setup mode. The customer enters card details in Checkout; on completion the card is saved to the account's Stripe customer for top-ups and subscription billing, and appears in `paymentMethods` on GET /api/v1/billing. Not available during impersonation.","tags":["billing"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Embedded Checkout client secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing_createPaymentMethodSetup_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"returnUrl":{"type":"string","format":"uri","description":"Where Checkout returns after completion. Defaults to the dashboard billing page."}}}}}}}},"/api/v1/billing/payment-methods/{id}":{"delete":{"operationId":"remove-payment-method","summary":"Remove a saved card","description":"Detaches a saved card from the account's Stripe customer. If it was the default and other cards remain, the next card on file becomes the default. Not available during impersonation.","tags":["billing"],"parameters":[{"name":"id","in":"path","description":"Stripe PaymentMethod id (pm_…).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Card removed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing_removePaymentMethod_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such card on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/payment-methods/{id}/default":{"post":{"operationId":"set-default-payment-method","summary":"Set the default card","description":"Makes a saved card the account's default payment method for invoices (top-ups) and subscription renewals. Not available during impersonation.","tags":["billing"],"parameters":[{"name":"id","in":"path","description":"Stripe PaymentMethod id (pm_…).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Default updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing_setDefaultPaymentMethod_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such card on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/invoices/{invoiceId}":{"get":{"operationId":"get-invoice","summary":"Download an invoice","description":"Returns the Stripe-hosted invoice links for one of the account's invoices — Stripe is the source of truth for invoices. Use the `invoiceId` from a deposit or the subscription in GET /api/v1/billing. `invoicePdf` is a direct PDF download; `hostedInvoiceUrl` is Stripe's hosted invoice page. Returns 404 if the invoice doesn't belong to the account.","tags":["billing"],"parameters":[{"name":"invoiceId","in":"path","description":"Stripe invoice id (in_…), as returned in GET /api/v1/billing.","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Stripe-hosted invoice links.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing_getInvoice_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such invoice on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/activity":{"get":{"operationId":"list-billing-activity","summary":"List billing activity","description":"The unified billing activity ledger — usage (calls, SMS, number ownership), wallet credits, and subscription payments — merged newest-first and cursor-paginated. Each item has a `type` that selects which fields are present. Pagination is Stripe-style: pass `limit` (max page size) and `starting_after` set to the `occurredAt` of the last item you received to fetch the next page (only items strictly older are returned). `hasMore` indicates whether further pages exist.","tags":["billing"],"parameters":[{"name":"filter","in":"query","description":"Scope the feed: `all`, `usage` only, or `payment-credits` (subscription payments + wallet credits).","required":false,"schema":{"$ref":"#/components/schemas/ApiV1BillingActivityGetParametersFilter","default":"all"}},{"name":"limit","in":"query","description":"Max items per page (clamped to 1–100).","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"starting_after","in":"query","description":"Exclusive cursor — an exact ISO timestamp (the `occurredAt` of the last item from the previous page). Only items strictly older are returned.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"One page of activity, newest-first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Billing_listBillingActivity_Response_200"}}}},"400":{"description":"Invalid `limit` or `starting_after`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/numbers":{"get":{"operationId":"list-numbers","summary":"List phone numbers","tags":["phoneNumbers"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"All phone numbers on the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Phone numbers_listNumbers_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"provision-number","summary":"Provision a phone number","description":"Provisions a new phone number. `inboundInstruction` is optional — it becomes the new number's inbound voice-agent prompt, and a default greeting is used if you omit it. Not idempotent — there is no idempotency key, so retrying a failed request can provision an additional number. Confirm via List phone numbers before retrying.\n\n**Billing.** Pay-as-you-go accounts are charged the first ownership month from their credit balance immediately, and the request is rejected with `402` when the balance can't cover it. On a **flat-rate subscription** the number is added to the subscription instead — the quantity goes up by one and Stripe invoices the prorated difference. The one exception: a subscription always bills at least one number, so if you hold **no** numbers (you released them all) the first number you provision back adds **no charge** — the quantity is already there. Either way the new number is covered by the subscription the moment this call returns.\n\nInclude `imessage` in `capabilities` to provision an iMessage number. iMessage provisioning is **asynchronous**: the response is `202` with the number in `setupStatus: provisioning`. Poll List phone numbers until `setupStatus` is `ready` before sending from it or calling from it. iMessage numbers are available on **pay-as-you-go only** — an account on a flat-rate subscription is rejected with `403`; switch to pay-as-you-go to add one.\n\n`explicitProgrammaticConsent` is **required** on every provisioning request (standard and iMessage): a short, human-readable attestation that the account holder has explicitly consented to provisioning this number programmatically (e.g. `\"User modal confirmation\"`). Requests without it are rejected with `400`.","tags":["phoneNumbers"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Number provisioned (synchronous — SMS/call numbers).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Phone numbers_provisionNumber_Response_201"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Insufficient Dial credit. The number is pay-as-you-go and the account's credit balance is at or below zero. Add credit or start a subscription. (error code `insufficient_credit`.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"iMessage numbers aren't available for this account — either the account isn't eligible for iMessage yet, or it's on a flat-rate subscription (iMessage is pay-as-you-go only).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"explicitProgrammaticConsent":{"type":"string","minLength":1,"maxLength":2000,"description":"Required. A short, human-readable attestation that the account holder explicitly consented to provisioning this number programmatically. Stored on the number for the provisioning audit trail. Example values: `\"User modal confirmation\"` (from the dashboard) or your own consent record reference."},"capabilities":{"type":"array","items":{"$ref":"#/components/schemas/ApiV1NumbersPostRequestBodyContentApplicationJsonSchemaCapabilitiesItems"},"default":["sms","call"],"description":"What the number should be able to do. Two combinations are accepted: `[\"sms\", \"call\"]` (the default) for a standard SMS/call number, or `[\"sms\", \"call\", \"imessage\"]` for an iMessage number (provisioned asynchronously — see the note above). `areaCode` and `promotionCode` are ignored for iMessage numbers."},"inboundInstruction":{"type":"string","description":"System prompt for the AI voice agent on inbound calls to this number. Optional — a default greeting is used if omitted. Update later with Update a phone number."},"inboundVoiceGender":{"$ref":"#/components/schemas/ApiV1NumbersPostRequestBodyContentApplicationJsonSchemaInboundVoiceGender","description":"Voice gender for the AI agent on inbound calls to this number. Optional — when omitted, the voice is female (the default for every language). Pass `male` to override. Update later with Update a phone number."},"inboundLanguage":{"type":"string","description":"BCP-47 language tag for the AI voice agent on inbound calls to this number. Optional — when omitted, Dial detects the language from the caller's country prefix on each call and the agent handles both that language and `en-US`. Pass an explicit tag to pin every inbound call to a single language. Update later with Update a phone number."},"areaCode":{"type":"string","description":"Preferred US area code. Optional — when omitted, any available US number is provisioned. Only US numbers can be provisioned at this time."},"promotionCode":{"type":"string","description":"Optional customer-facing promotion code (e.g. `LAUNCH20`). Only applies to **subscription** accounts, where adding a number bumps the subscription quantity and bills a prorated amount — the code is handed to Stripe and discounts that charge per the underlying coupon's own scope and duration. Ignored for pay-as-you-go accounts. An invalid, expired, or inapplicable code is rejected with `400`. Still applied to the subscription when the purchase raises no charge at all (the free first number described above), so it discounts the following invoices instead."}},"required":["explicitProgrammaticConsent"]}}}}}},"/api/v1/numbers/{id}":{"get":{"operationId":"get-number","summary":"Get a phone number","description":"Returns the number with its most recent messages and calls.","tags":["phoneNumbers"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The phone number.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Phone numbers_getNumber_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"release-number","summary":"Release a phone number","description":"Release a number — standard SMS/call or iMessage. Immediate and irreversible.\n\nOn a **flat-rate subscription**, releasing a number lowers the subscription quantity by one and Stripe prorates the change — but the quantity never falls below **one number**. Releasing your last number therefore leaves the subscription active at the single-number rate rather than cancelling it, and the next number you provision is free (see Provision a phone number). To stop paying entirely, cancel the subscription with Cancel the subscription at period end.","tags":["phoneNumbers"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Released.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Success"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"update-number","summary":"Update a phone number","description":"Update a number's properties. Send any subset of the fields below —\nat least one is required; omitted fields are left unchanged.\nA new `inboundInstruction` takes effect on the next inbound call;\nin-progress calls are unaffected.\n\n**iMessage display identity.** On numbers with the `imessage`\ncapability, `firstName`, `lastName`, and an avatar photo set the\nidentity shown beside the number's messages in recipients' Messages\napps. Sending any of these fields for a number without the `imessage`\ncapability is rejected with `400`.\n\n**The avatar photo** is set either by uploading a file (the `avatar`\npart, `multipart/form-data` only) or by passing a publicly reachable\nimage URL (`avatarUrl`, JSON or multipart) that Dial downloads\nserver-side. Accepted types: jpeg, png, gif, webp; max 5 MB. A square\nimage of 512×512 or larger is recommended — it is shown as a circle.\nDial mirrors the photo into its own storage and serves it from the\nstable public URL returned as `avatarUrl`. The photo can be\n**replaced but not removed** — `avatarUrl: null` is rejected with\n`400`. Identity changes can take a few minutes to propagate to\nrecipients' devices.\n","tags":["phoneNumbers"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Phone numbers_updateNumber_Response_200"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body too large — the avatar exceeds the 5 MB limit.","content":{"application/json":{"schema":{"description":"Any type"}}}},"502":{"description":"The display-identity update couldn't be completed upstream — nothing was changed. Safe to retry.","content":{"application/json":{"schema":{"description":"Any type"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"inboundInstruction":{"type":"string","description":"New inbound voice-agent system prompt for this number."},"inboundVoiceGender":{"$ref":"#/components/schemas/ApiV1NumbersIdPatchRequestBodyContentApplicationJsonSchemaInboundVoiceGender","description":"Voice gender for the AI agent on inbound calls to this number. Send null to clear it (the voice reverts to the default, female). Takes effect on the next inbound call."},"inboundLanguage":{"type":["string","null"],"description":"BCP-47 language tag for the AI voice agent on inbound calls to this number. Send null to clear it — the language is then detected from the caller's country prefix on each call, with the agent handling both that language and `en-US`. Takes effect on the next inbound call."},"nickname":{"type":["string","null"],"description":"Human-readable label for the number. Trimmed; at most 100 characters. Send null or an empty string to clear it."},"firstName":{"type":["string","null"],"description":"iMessage display first name shown beside this number's messages in recipients' Messages apps. Trimmed; at most 30 characters. Send null or an empty string to clear it. Only valid on numbers with the `imessage` capability — rejected with `400` otherwise."},"lastName":{"type":["string","null"],"description":"iMessage display last name. Trimmed; at most 30 characters. Send null or an empty string to clear it. Only valid on numbers with the `imessage` capability — rejected with `400` otherwise."},"avatarUrl":{"type":"string","format":"uri","description":"Publicly reachable http(s) URL of an image to set as the number's iMessage avatar photo. Dial downloads it server-side, mirrors it into its own storage, and serves it from the `avatarUrl` returned on the number. jpeg, png, gif, or webp; max 5 MB. The photo can be replaced but not removed — null is rejected with `400`. Only valid on numbers with the `imessage` capability."},"maxCallDurationSeconds":{"type":["integer","null"],"minimum":60,"maximum":3600,"description":"Cap on the length of calls on this number, in seconds, for both inbound calls to the number and outbound calls placed from it. The effective cap on a call is the first one set in priority order — the per-call cap, then this number cap, then the account cap — falling back to the global default. This number cap overrides the account cap (raising or lowering the limit) but is itself overridden by a per-call cap. Must be between 60 and 3600 seconds (1 minute to 1 hour); a value outside that range returns 400. Send null to clear it (falls back to the account cap or global default). Free accounts — accounts that have never added credit and never subscribed — are capped at 300 seconds (5 minutes), reported as `limits.maxCallDurationSeconds` on the account: a higher value sent here returns 400, and a cap inherited from another level is clamped to 300. When a cap is active, Dial automatically injects a warning to the agent ~40 seconds before the hard cut, prompting it to wrap up naturally."}}}}}}}},"/api/v1/numbers/{id}/10dlc":{"get":{"operationId":"get-ten-dlc-registration","summary":"Get 10DLC registration","description":"Returns this number's 10DLC registration: the brand (who is registering) and the campaign (what they send), exactly as submitted.\nA number's registration status is also available — without the submitted details — as the `tenDlc` object on the phone number itself.","tags":["phoneNumbers"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The registration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Phone numbers_getTenDlcRegistration_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"10DLC registration isn't available here. Either it doesn't apply to this number — it isn't US, or it has the `imessage` capability (error code `tendlc_not_applicable`) — or the account isn't pay-as-you-go and has no existing registration for the number (error code `tendlc_not_available`). The `tenDlc` object on the phone number is null in both cases.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No registration has been submitted for this number, or the number doesn't exist on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"submit-ten-dlc-registration","summary":"Submit 10DLC registration","description":"Register a US number for 10DLC — the carrier registration required before\na number can send SMS or MMS to US phone numbers.\n\nUS carriers **block** outbound SMS and MMS sent from an unregistered\n10-digit number to a US phone number. Registering this number lifts that\nblock. Inbound texts, voice calls in either direction, and messaging to\nnumbers outside the US are unaffected.\n\n**Eligibility.** The number must be US, have the `sms` capability, and\nnot have the `imessage` capability (iMessage numbers send over Apple\nMessages for Business, not carrier SMS). 10DLC registration is currently\noffered on pay-as-you-go accounts only. An ineligible number returns\n`403` — read the `tenDlc` object on the number to know in advance.\n\n**Fee.** A one-time **$25.00** per number, taken from the account's credit\nbalance when the submission is accepted. A balance below the fee returns\n`402`. **Resubmitting after a rejection is free** — the number is charged\nat most once, ever.\n\n**What happens next.** The submission lands in `in_review` while Dial\nchecks the details, moves to `with_carrier` once filed with the carrier\nregistry, and finishes as `approved` or `rejected`. Carriers usually\ndecide within 3–5 business days. Dial emails the account owner on approval\nand on rejection; a rejection carries a `reason` explaining exactly what\nto change before resubmitting.\n\n**Body.** Two parts mirroring what the carrier registry registers: a\n**brand** (who is registering) and a **campaign** (what they send). A\nnumber is registered by joining the campaign. Send them as\n`application/json`, or — to attach an opt-in screenshot — as\n`multipart/form-data` with a `data` text part holding that same JSON\nobject plus one `optInImage` file part.\n\n### Brand — both kinds\n\nEvery field is required.\n\n| Key | Description |\n|---|---|\n| `brandName` | The registered business or DBA name; for a sole proprietor, the name they trade under. Carriers reject an account id or an email address here. |\n| `firstName`, `lastName` | The person responsible for the registration. |\n| `email` | Contact email. |\n| `phone` | Mobile number in E.164. For a sole proprietor this receives a verification SMS — reply `YES` within 24 hours. **It may not be a Dial number**: carriers won't verify a number issued by a messaging provider, so use a mobile the person answers directly. |\n| `street`, `city`, `zip` | The address on the registration. Validated against public records, so a typo is a rejection. |\n| `country` | ISO 3166-1 alpha-2. **Sole proprietor: `US` or `CA` only** — that programme is US/Canada-only. A business may be registered from any country the registry accepts; a non-US brand needs its own country's tax ID and may draw extra vetting. Defaults to `US`. |\n| `state` | State or province of the registered address — required whatever the country. The two-letter code for the US and Canada (`CA`, `ON`); the region name elsewhere. |\n\n### Brand — `business` only\n\n| Key | Description |\n|---|---|\n| `businessType` | Legal form. One of `Co-operative`, `Corporation`, `Limited Liability Corporation`, `Non-profit Corporation`, `Partnership`. A sole proprietorship is not a business brand — register as `sole_proprietor` instead. |\n| `businessIndustry` | Industry code, e.g. `PROFESSIONAL_SERVICES`. See the enum. |\n| `registrationIdType` | The kind of registration number. One of `EIN`, `DUNS`, `CBN`, `CN`, `ACN`, `CIN`, `VAT`, `VATRN`, `RN`, `Other`. |\n| `registrationNumber` | The number itself — in the US, the EIN. |\n| `websiteUrl` | Full `https://` URL. Reviewers check that it matches the brand. |\n| `jobPosition` | One of `Director`, `GM`, `VP`, `CEO`, `CFO`, `General Counsel`, `Other`. |\n| `businessTitle` | The actual job title, free text, e.g. `Head of Support`. |\n| `businessRegionsOfOperation` | Where the business operates. One of `USA_AND_CANADA`, `AFRICA`, `ASIA`, `EUROPE`, `LATIN_AMERICA`. Defaults to `USA_AND_CANADA`. |\n| `companyType` | One of `private`, `public`, `non-profit`, `government`. Defaults to `private`. |\n| `stockExchange`, `stockTicker` | **Required when `companyType` is `public`**, ignored otherwise. Carriers check both against public sources, so they must match. |\n\n### Campaign — both kinds\n\n| Key | Description |\n|---|---|\n| `description` | Who is messaged and why. 40–4096 characters. Reviewers weigh this heavily; a single word is rejected. |\n| `messageFlow` | How people opt in, naming every method used. 40–1920 characters (room above that is reserved for compliance material Dial appends when filing). |\n| `optInImage` | Optional screenshot of the opt-in step as customers see it — the consent checkbox, form, or settings screen the `messageFlow` describes. Reviewers approve much faster when they can see the consent screen. Sent as the `optInImage` file part of a `multipart/form-data` request (max 2 MB; JPEG, PNG, WebP, or GIF). On a resubmission, leaving it out keeps the stored screenshot; sending `\"optInImage\": null` inside `campaign` removes it. |\n| `samples` | Two to five sample messages, each 20–1024 characters. Each must name the brand and carry an opt-out instruction. If only one message text will ever be sent, repeat it. |\n| `containsUrls`, `containsPhones` | Booleans — whether messages will contain links or phone numbers. Optional; default false. |\n| `privacyPolicyUrl`, `termsUrl` | Full `https://` URLs — carriers require both on every campaign. **Required for a `business`**, and reviewers check they sit on the `websiteUrl` you registered. Optional for a sole proprietor: omit them and Dial publishes a privacy notice and terms page for the brand, built from this submission, and registers those. Supplying your own always wins. |\n\n### Campaign — `business` only\n\n| Key | Description |\n|---|---|\n| `useCase` | What the campaign is for, e.g. `CUSTOMER_CARE`. Optional; defaults to `MIXED`. A sole proprietor campaign is always `SOLE_PROPRIETOR` and takes no value here. |\n\n### Campaign — `sole_proprietor` only\n\n| Key | Description |\n|---|---|\n| `acceptPublishedNotice` | Boolean. **Required when you send neither `privacyPolicyUrl` nor `termsUrl`** — it authorises Dial to publish a privacy notice and terms page for your brand, built from this submission and naming you as the operator, and to register those URLs with carriers. Send your own two URLs instead and this field is ignored. Sending neither the URLs nor `true` here is a `400` — `tendlc_validation_failed`, keyed on `acceptPublishedNotice` — because carriers require the two pages and one of you has to provide them. |\n","tags":["phoneNumbers"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Submitted, and the fee charged unless this was a free resubmission.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Phone numbers_submitTenDlcRegistration_Response_200"}}}},"400":{"description":"The body failed validation. For field-level problems the error code is `tendlc_validation_failed` and `error` is an object mapping each offending field key to a human-readable message. Keys are the flat field names, not the `brand.` / `campaign.` paths, so a form can highlight them directly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"The account's credit balance is below the $25.00 registration fee. Add credit and submit again. (error code `insufficient_credit`.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"10DLC registration isn't available here. Either it doesn't apply to this number — it isn't US, or it has the `imessage` capability (error code `tendlc_not_applicable`) — or the account isn't pay-as-you-go and has no existing registration for the number (error code `tendlc_not_available`). The `tenDlc` object on the phone number is null in both cases.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"This number already has a registration that is in review, filed with the carrier registry, or approved. Only a rejected registration can be resubmitted. (error code `tendlc_already_registered`.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"$ref":"#/components/schemas/ApiV1NumbersId10DlcPostRequestBodyContentApplicationJsonSchemaKind","description":"Who is registering. A sole proprietor operates under their own name and has no EIN — lower throughput, quicker to approve. A business is a registered company with an EIN."},"brand":{"$ref":"#/components/schemas/TenDlcBrand"},"campaign":{"$ref":"#/components/schemas/TenDlcCampaign"}},"required":["kind","brand","campaign"]}}}}}},"/api/v1/messages":{"get":{"operationId":"list-messages","summary":"List messages","description":"Returns up to 100 of the most recent messages on the account's numbers.","tags":["messages"],"parameters":[{"name":"numberId","in":"query","description":"Filter to a single phone number.","required":false,"schema":{"type":"string"}},{"name":"direction","in":"query","required":false,"schema":{"$ref":"#/components/schemas/ApiV1MessagesGetParametersDirection"}},{"name":"since","in":"query","description":"Only messages created after this timestamp.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Messages, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Messages_listMessages_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"send-message","summary":"Send a message","description":"Sends a message from one of your Dial numbers.\n\n**The channel is determined by the number, not the request.** A standard\nnumber sends SMS/MMS. An iMessage number sends iMessage, automatically\nfalling back to RCS/SMS when the recipient can't receive iMessage — you\ndon't (and can't) pick the channel per message. For standard numbers,\n`status` tracks delivery (`sent` → `delivered`/`undelivered`/`failed`,\nwith a `statusError` reason on failure); iMessage sends report\n`status: \"unknown\"`.\n\n**Media.** Attach media as public URLs (`mediaUrls`, JSON or multipart) or\nas uploaded `media` file parts (multipart only):\n- Standard numbers: up to **10** items combined, **5 MB** each.\n- iMessage numbers: **at most one** attachment (URL or file).\n\n`body` is optional when media is attached — a media-only message sends\njust the attachment (its `body` is recorded as an empty string). A request\nwith neither a non-empty `body` nor media is rejected with `400`.\n\n**Content size (standard numbers).** A standard-number message is validated\nagainst the carrier's content-size limits before it is sent, and one that\nexceeds them is rejected with `400` (\"Content size exceeds carrier limit.\")\nrather than sent.\n\nThe `body` limit depends on the characters used. Plain text allows **1530\ncharacters**, but a single character outside the basic SMS alphabet — an\nem dash, a curly quote, an emoji, any non-Latin script — switches the whole\nmessage to a wider encoding and lowers the limit to **670 characters**.\nAttached media may total at most **5 MB**.\n\nEven within these limits an individual carrier may still refuse an\noversized message at delivery — that surfaces as a failed `status` with\nthe same `statusError` reason.\n\n**Voice messages (iMessage numbers).** When the attachment is a single\naudio file, it is delivered as a native voice message. If a `body` is\nalso present, the recipient receives two messages — the text, then the\nvoice message — but the API still returns (and records) a single Message.\nIf the recipient can't receive voice messages (no iMessage/RCS), the\naudio is delivered as a link over SMS instead. Set `forceAudioFile: true`\nto always send audio as a regular file attachment in one message.\n`forceAudioFile` has no effect on standard numbers, non-audio media, or\nmulti-attachment sends — audio attached to a standard number's message is\nalways MMS media.\n\nSupported types: images (jpeg, png, gif, webp, bmp), audio (mp3, m4a, ogg,\nwav, amr), video (mp4, 3gpp), pdf, vCard, and iCalendar; any other type is\nrejected with `400` (HEIC and SVG are not supported). Dial mirrors every\nattachment into its own storage and serves it from a stable public URL\n(see Get public media); the message's `media` array carries those URLs.\nFor standard numbers, true MMS delivery is supported only for US and Canada\n— to other destinations the carrier delivers an SMS with a link to the\nmedia.\n\n**Destination limits** (standard numbers): accounts on a **subscription**\ncan send only to **US** numbers — any other destination is rejected with\n`400`. Pay-as-you-go accounts can send to any supported destination (a\ndestination with no SMS price tier is rejected with `400`).\n\n**Choosing the from-number.** Provide exactly one of `fromNumber` (a\nflexible reference: phone-number ID, one of your numbers in E.164\nformat, or a nickname) or the legacy `fromNumberId` (ID only). Sending\nboth — or neither — is rejected with `400`.\n\nNot idempotent — there is no idempotency key, so retrying a failed request\ncan send a duplicate message. On an ambiguous failure, confirm via List\nmessages before re-sending.\n","tags":["messages"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Message queued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Messages_sendMessage_Response_201"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Insufficient Dial credit. The number is pay-as-you-go and the account's credit balance is at or below zero. Add credit or start a subscription. (error code `insufficient_credit`.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request body too large — the media exceeds the 10-item / 5 MB-per-file limits.","content":{"application/json":{"schema":{"description":"Any type"}}}},"429":{"description":"The recipient is rate-limited. New contacts on iMessage numbers can only receive a limited number of messages until they reply; once they respond, the limit lifts. (error code `rate_limited`.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","description":"Recipient phone number in E.164 format."},"fromNumber":{"type":"string","description":"The Dial number to send from, referenced flexibly: a phone-number ID, one of your numbers in E.164 format, or a number's nickname. A nickname shared by more than one of your numbers is rejected with `400`. Provide exactly one of `fromNumber` or `fromNumberId`."},"fromNumberId":{"type":"string","description":"ID of the Dial number to send from (see List phone numbers). Legacy alternative to `fromNumber` — provide exactly one of the two."},"body":{"type":"string","description":"Message text. Optional when media is attached; a request with neither a non-empty body nor media is rejected with `400`."},"forceAudioFile":{"type":"boolean","default":false,"description":"Send an audio attachment as a regular file attachment instead of an iMessage voice message. No effect on standard numbers or non-audio media."},"mediaUrls":{"type":"array","items":{"type":"string","format":"uri"},"description":"Publicly reachable http(s) URLs of media to attach. Dial downloads each one server-side and re-hosts it; the source URL is preserved on the media item as `originalUrl`. iMessage numbers accept at most one item."}},"required":["to"]}}}}}},"/api/v1/messages/{messageId}/reply":{"post":{"operationId":"reply-to-message","summary":"Reply to a message","description":"Sends a threaded reply or a reaction targeted at an existing message.\n\n**The sender is derived from the target message.** The reply goes out\nfrom the Dial number the target message belongs to, to the other party\nof that message — there is no `to` or `fromNumberId`. A reply always\nstays in the conversation the target message is part of.\n\nThe request carries **exactly one** of:\n\n- `body` — a text reply. On iMessage numbers it is delivered as a\n  native threaded reply (quoting the target message) when the recipient\n  supports threads, and as a regular message otherwise. On standard\n  numbers it is delivered as a regular SMS.\n- `reaction` — a reaction to the target message: one of the six\n  reaction names (`love`, `like`, `dislike`, `laugh`, `emphasize`,\n  `question`) or a **single emoji** (one visible symbol — skin-tone and\n  multi-person emoji count as one; anything else is rejected with\n  `400`). On iMessage numbers the reaction is delivered natively (a\n  Tapback). When the recipient can only receive SMS — including\n  everything sent from standard numbers — an **emoji** reaction is\n  delivered as a regular message whose body is the emoji, and a\n  reaction **name** is rejected with `400` (names have no SMS\n  rendering; send an emoji instead).\n\n**Targets.** Any message on the account can be targeted, with one\ncurrent restriction: on iMessage numbers the target must be an\n**inbound** message — replying to your own sent messages isn't\nsupported on those numbers yet. Standard numbers accept targets in\nboth directions. A **reaction cannot itself be the target** — replying\nor reacting to a reaction is rejected with `400`.\n\nThe created message is recorded like any outbound message and billed\nthe same as Send a message. Its `replyToId` points at the target\nmessage; `reaction` carries the reaction string when one was sent (a\nreaction delivered natively has an empty `body`; one delivered as a\nregular message carries the emoji in `body` too).\n\nNot idempotent — retrying a failed request can send a duplicate reply.\nOn an ambiguous failure, confirm via List messages before re-sending.\n","tags":["messages"],"parameters":[{"name":"messageId","in":"path","description":"ID of the message to reply or react to (see List messages).","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"Reply queued. The created message, with `replyToId` set to the target message's ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Messages_replyToMessage_Response_201"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Insufficient Dial credit. The number is pay-as-you-go and the account's credit balance is at or below zero. Add credit or start a subscription. (error code `insufficient_credit`.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"The recipient is rate-limited. New contacts on iMessage numbers can only receive a limited number of messages until they reply; once they respond, the limit lifts. (error code `rate_limited`.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"body":{"type":"string","description":"Reply text. Exactly one of `body` and `reaction` is required. On a standard number the same content-size limit as Send a message applies (1530 characters, or 670 if the text uses characters outside the basic SMS alphabet); a longer reply is rejected with `400`."},"reaction":{"type":"string","description":"Reaction to send: `love`, `like`, `dislike`, `laugh`, `emphasize`, `question`, or a single emoji. Exactly one of `body` and `reaction` is required."}}}}}}}},"/api/v1/typing":{"post":{"operationId":"send-typing","summary":"Set a typing indicator","description":"Starts or stops a typing indicator shown to the recipient, as if someone\nwere composing a message from your number.\n\n**iMessage numbers show it; standard (SMS) numbers ignore it.** SMS has\nno typing concept, so for a standard from-number the request succeeds and\ndoes nothing — the response is identical either way, letting you call\nthis unconditionally before a send.\n\nThe indicator is fire-and-forget: nothing is persisted and no event is\nemitted. Delivering a message or reaction from the same number\n**clears the indicator natively** on the recipient's device — platform\nbehavior, not Dial's; start again after a send if you're still\ncomposing. Dial itself never clears it: send `value: false` when you\nstop composing without sending. (A stale indicator may also clear on\nits own device-side.) Free of charge.\n","tags":["messages"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Indicator set (or silently ignored for a from-number without typing support).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Messages_sendTyping_Response_200"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"toNumber":{"type":"string","description":"Recipient phone number in E.164 format."},"value":{"type":"boolean","description":"`true` shows the typing indicator; `false` clears it."},"fromNumber":{"type":"string","description":"The Dial number the indicator appears from, referenced flexibly: a phone-number ID, one of your numbers in E.164 format, or a number's nickname. A nickname shared by more than one of your numbers is rejected with `400`; a ref that matches none of your numbers returns `404`."}},"required":["toNumber","value","fromNumber"]}}}}}},"/public-media/{file}":{"get":{"operationId":"get-public-media","summary":"Get public media","description":"Serves a message media attachment. No authentication required — the unguessable media ID in the path is the access token, mirroring how carriers serve MMS media. Usually responds with a `302` redirect to short-lived object storage; while a just-received attachment is still being mirrored, the body is streamed directly instead. URLs are stable and do not expire.","tags":["messages"],"parameters":[{"name":"file","in":"path","description":"The media filename, `<mediaId>.<ext>` — exactly as given in a message's `media[].url`. The extension is cosmetic; lookup is by ID.","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Media bytes, streamed (only while mirroring is in flight).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Messages_getPublicMedia_Response_200"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/calls":{"get":{"operationId":"list-calls","summary":"List calls","description":"Returns up to 100 of the most recent calls on the account's numbers.","tags":["calls"],"parameters":[{"name":"numberId","in":"query","description":"Filter to a single phone number.","required":false,"schema":{"type":"string"}},{"name":"direction","in":"query","required":false,"schema":{"$ref":"#/components/schemas/ApiV1CallsGetParametersDirection"}},{"name":"since","in":"query","description":"Only calls created after this timestamp.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Calls, newest first.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Calls_listCalls_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"make-call","summary":"Place an AI voice call","description":"Places an outbound voice call handled by an AI voice agent using the supplied instruction. Supports idempotent retries via the optional `Idempotency-Key` header: replaying the same key returns the original call (`200`) instead of dialing again, and a duplicate that arrives while the original is still processing waits for it. A non-2xx response guarantees no live call: if the call already fired when the failure happened, Dial cancels it before responding. Without the header the request is not idempotent — retrying can place a duplicate call; on an ambiguous failure, confirm via List calls before retrying. Choose the from-number with exactly one of `fromNumber` (phone-number ID, one of your numbers in E.164 format, or a nickname) or the legacy `fromNumberId` (ID only) — both, or neither, is rejected with `400`.","tags":["calls"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}},{"name":"Idempotency-Key","in":"header","description":"A unique client-generated key (e.g. a UUID) identifying this call attempt. Reuse the same key when retrying a failed or ambiguous request: if the original attempt placed the call, the retry returns that call with `200` instead of placing a second one. Keys are scoped to your account.","required":false,"schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"A call with this `Idempotency-Key` was already placed — returns the original call instead of dialing again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Calls_makeCall_Response_200"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"Insufficient Dial credit. The number is pay-as-you-go and the account's credit balance is at or below zero. Add credit or start a subscription. (error code `insufficient_credit`.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Idempotency conflict — the original request with this `Idempotency-Key` is still processing and didn't finish within the wait window. Retry with the same key shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Concurrent-call limit reached. A free account — one that has never added credit and never subscribed — can have at most 2 calls in progress at once, inbound and outbound combined. Wait for a call to end, or add credit / start a subscription to lift the limit permanently. (error code `call_limit_reached`.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"type":"string","description":"Recipient phone number in E.164 format."},"fromNumber":{"type":"string","description":"The Dial number to call from, referenced flexibly: a phone-number ID, one of your numbers in E.164 format, or a number's nickname. A nickname shared by more than one of your numbers is rejected with `400`. Provide exactly one of `fromNumber` or `fromNumberId`."},"fromNumberId":{"type":"string","description":"ID of the Dial number to call from. Legacy alternative to `fromNumber` — provide exactly one of the two."},"outboundInstruction":{"type":"string","description":"System prompt for the AI voice agent during the call."},"language":{"type":"string","description":"BCP-47 language tag for the voice agent. Optional — when omitted, Dial detects the language from the destination number's country prefix and the agent handles both that language and `en-US`. Pass an explicit tag to pin the call to a single language."},"maxCallDurationSeconds":{"type":"integer","minimum":60,"maximum":3600,"description":"Optional per-call cap on this call's length, in seconds. Highest priority: when set it is the effective cap, overriding the number and account caps (it can raise or lower the limit). Must be between 60 and 3600 seconds (1 minute to 1 hour); a value outside that range returns 400. Omit to fall back to the number cap, then the account cap, then the global default. Free accounts — accounts that have never added credit and never subscribed — are capped at 300 seconds (5 minutes), reported as `limits.maxCallDurationSeconds` on the account: a higher value sent here returns 400, and a cap inherited from the number or account is clamped to 300. When a cap is active, Dial automatically injects a warning to the agent ~40 seconds before the hard cut, prompting it to wrap up naturally."},"voiceGender":{"$ref":"#/components/schemas/ApiV1CallsPostRequestBodyContentApplicationJsonSchemaVoiceGender","description":"Gender of the AI voice agent for this call. Optional — when omitted, the voice is female (the default for every language). Pass `male` or `female` to choose explicitly; the choice also tells the agent how to refer to itself in gendered languages."},"transferTo":{"type":"string","description":"Forward-to number in E.164 format. Optional — when set, the agent waits for a real human (riding out hold music and IVR menus) and then cold-transfers the call to this number. Must differ from both `to` and the call's from number. When the hand-off occurs, the call's `transferredAt` is stamped."}},"required":["to","outboundInstruction"]}}}}}},"/api/v1/calls/{id}":{"get":{"operationId":"get-call","summary":"Get a call","description":"Returns a single call, including its status, duration, and transcript (once available).","tags":["calls"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Calls_getCall_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/events/wait":{"post":{"operationId":"wait-for-event","summary":"Wait for an event","description":"Long-polls until the next event of `eventType` arrives on the account\n(optionally matching `filters`/`regexFilters`), or until `timeout` seconds\nelapse. Useful for one-shot waits such as receiving an inbound SMS code.\nFor a continuous stream, use Open an event stream instead.\n","tags":["events"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The matching event.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Events_waitForEvent_Response_200"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"408":{"description":"No matching event arrived within the timeout.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"eventType":{"type":"string","description":"Event type to wait for."},"filters":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Exact-match on top-level event fields."},"regexFilters":{"type":"object","additionalProperties":{"description":"Any type"},"description":"Regex-match on top-level event fields."},"timeout":{"type":"integer","minimum":1,"maximum":60,"default":30,"description":"Max seconds to wait."}},"required":["eventType"]}}}}}},"/api/v1/listen/subscribe":{"post":{"operationId":"subscribe-to-events","summary":"Open an event stream","description":"Mints a short-lived, account-scoped subscription token for the live event\nstream. The SDKs call this for you behind `new_events_connection()`; the CLI\nuses it for the `listen` daemon. Re-mint before `ttlSeconds` elapses to keep\nthe stream open.\n\nDelivery is presence-based, not at-least-once: events that occur while you are\ndisconnected are replayed only if you reconnect within 2 minutes. For guaranteed\nat-least-once delivery, webhooks and destinations are coming soon.\n","tags":["events"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscribeToken"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The event stream is not configured on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks":{"get":{"operationId":"list-webhooks","summary":"List webhook subscriptions","tags":["webhooks"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The account's webhook subscriptions.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhooks_listWebhooks_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create-webhook","summary":"Create a webhook subscription","tags":["webhooks"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created subscription. The full signing secret is included once here.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhooks_createWebhook_Response_201"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}}}}},"/api/v1/webhooks/{id}":{"get":{"operationId":"get-webhook","summary":"Get a webhook subscription","tags":["webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The subscription.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhooks_getWebhook_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"delete-webhook","summary":"Delete a webhook subscription","tags":["webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Deleted.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks/{id}/secret":{"get":{"operationId":"reveal-webhook-secret","summary":"Reveal the signing secret","tags":["webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The decrypted signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhooks_revealWebhookSecret_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/webhooks/{id}/ping":{"post":{"operationId":"fire-webhook-ping","summary":"Fire a webhook.ping event at this subscription","tags":["webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Ping enqueued for delivery.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhooks_fireWebhookPing_Response_202"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/context-mcps":{"get":{"operationId":"list-context-mcps","summary":"List Context MCP connections","tags":["contextMcp"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The account's Context MCP connections.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Context MCP_listContextMcps_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"create-context-mcp","summary":"Connect a Context MCP server","description":"Registers an MCP server and wires its tools into the account's voice agent. Dial probes the server's URL to determine how it authenticates:\n\n- **Unauthenticated or static** — the connection is wired immediately\n  and returned with `status: \"connected\"`.\n\n- **OAuth 2.1–protected** — the response includes an `authorizationUrl`\n  and `status: \"pending_auth\"`. Open that URL in a browser to grant\n  consent; Dial completes the connection on the OAuth callback and then\n  manages token refresh for you. The server's authorization server must\n  support Dynamic Client Registration (RFC 7591).\n\n\nIf the server is unreachable, or responds to the probe with a server error (HTTP 5xx), the request is rejected with `400` and a message naming the status Dial observed — the fault is on your server, so fix it and try again.","tags":["contextMcp"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"The created connection. For OAuth servers, `authorizationUrl` is present and the connection stays `pending_auth` until consent completes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Context MCP_createContextMcp_Response_201"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContextMcpRequest"}}}}}},"/api/v1/context-mcps/{id}":{"get":{"operationId":"get-context-mcp","summary":"Get a Context MCP connection","tags":["contextMcp"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The connection.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Context MCP_getContextMcp_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"delete-context-mcp","summary":"Disconnect a Context MCP server","description":"Unwires the MCP's tools from the agent and stops token refresh.","tags":["contextMcp"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Disconnected.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/self-hosted":{"get":{"operationId":"get-self-hosted","summary":"Get Self-Hosted configuration","description":"Returns the account's Self-Hosted configuration. The signing secret is never returned here — only a masked preview (`secretMasked`). Use `GET /api/v1/self-hosted/secret` to copy the full value.","tags":["selfHosted"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The Self-Hosted configuration.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelfHostedConfig"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"apply-self-hosted-action","summary":"Apply a Self-Hosted state transition","description":"Applies **one** Self-Hosted state transition, discriminated by `action`: `save` a mode's config (no on/off or active-mode change), `activate` a mode (enable Self-Hosted and route calls through it — enable or switch), `disable`, or `request_access`. Each mode (`llm`/`audio`) has its own independent config.\n\n`save` and `activate` require `access: \"granted\"` and return **403** otherwise; `disable` and `request_access` never do, so an account whose access was withdrawn can still switch Self-Hosted off.\n\nThe signing secret for a mode is minted the first time you `save` a `wsUrl` for it. Retrieve it from `GET /api/v1/self-hosted/secret?mode=…` and set up your server's `X-Dial-Signature` verification *before* you `activate` (activating routes your calls at the server immediately). Returns the full configuration after the change.","tags":["selfHosted"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The full configuration after the change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelfHostedConfig"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Self-Hosted isn't approved for this account. Submit a `request_access` action; `access` on the response body says where the request stands.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelfHostedAction"}}}}}},"/api/v1/self-hosted/secret":{"get":{"operationId":"reveal-self-hosted-secret","summary":"Reveal the Self-Hosted signing secret","description":"Returns the full HMAC signing secret for copying. Each mode has its own secret, minted when you first set that mode's `wsUrl`. Use `mode` to pick which; defaults to the active mode.","tags":["selfHosted"],"parameters":[{"name":"mode","in":"query","description":"Which mode's secret to reveal. Defaults to the active mode.","required":false,"schema":{"$ref":"#/components/schemas/ApiV1SelfHostedSecretGetParametersMode"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Self-Hosted_revealSelfHostedSecret_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/members":{"get":{"operationId":"list-members","summary":"List account members","description":"Returns everyone with access to this account. The owner is always the first entry and has `role: \"owner\"` with a `null` id — the owner is the account's own email address, not a revocable membership. Invited people appear with `role: \"member\"` and are `pending` until they accept.","tags":["members"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Everyone with access to the account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Members_listMembers_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"invite-member","summary":"Invite a member","description":"Emails an invitation link to `email`. The invite is locked to that address: accepting it requires a verification code sent to that inbox, so holding the link alone grants nothing. The invite expires after 7 days; inviting an address that already has a pending invite re-sends it and refreshes the expiry.","tags":["members"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"The pending membership. An invitation email has been sent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Members_inviteMember_Response_201"}}}},"400":{"description":"The address failed validation — not a valid email, a disposable domain, the account owner's own address, or already an active member of this account. The last two are judged by mailbox, not by spelling: an address that differs from the owner's — or from an existing member's — only by a `+tag`, by dots in a `gmail.com` local part, or by `googlemail.com` vs `gmail.com` is the same person.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteMemberRequest"}}}}}},"/api/v1/members/{id}":{"delete":{"operationId":"remove-member","summary":"Remove a member","description":"Revokes a membership and deletes that member's API key, ending their access immediately. Works on both pending invites and active members. The owner cannot be removed.","tags":["members"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"The member was removed.","content":{"application/json":{"schema":{"type":"object","properties":{}}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The requested resource was not found on this account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/members/invites/{token}":{"get":{"operationId":"get-member-invite","summary":"Look up an invitation","description":"Resolves an invitation token so the join page can show who invited whom. Unauthenticated — the token is the credential. Returns only the invited address, the inviter's address, and the expiry; nothing else about the account is exposed.","tags":["members"],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The invitation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Members_getMemberInvite_Response_200"}}}},"404":{"description":"The token is unknown, already accepted, or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/members/invites/{token}/accept":{"post":{"operationId":"accept-member-invite","summary":"Accept an invitation","description":"Completes an invitation and returns the new member's API key. Requires a verification code issued by Create an account for the *invited* address — a code issued for any other address is rejected. Unauthenticated: the token plus the emailed code are the credentials. No new account is created and no phone number is provisioned; the caller joins the inviting account.","tags":["members"],"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The invitation was accepted. The API key is shown once — store it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Members_acceptMemberInvite_Response_200"}}}},"400":{"description":"The request body failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"The verification code is invalid, expired, already used, or was issued for a different address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The token is unknown, already accepted, or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptMemberInviteRequest"}}}}}},"/api/v1/memberships":{"get":{"operationId":"list-memberships","summary":"List accounts you can access","description":"Returns every account the authenticated identity can reach — the account it owns, if any, plus every account it has been invited to and accepted. Use it to offer an account switcher. Most identities have exactly one entry.","tags":["members"],"parameters":[{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The accessible accounts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Members_listMemberships_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/memberships/{accountId}/key":{"post":{"operationId":"get-membership-key","summary":"Get your API key for another account","description":"Returns the authenticated identity's own API key for `accountId`, so a client can switch which account it is acting on. Only accounts listed by List accounts you can access are permitted; the key returned always belongs to the calling identity, never to another person.","tags":["members"],"parameters":[{"name":"accountId","in":"path","required":true,"schema":{"type":"string"}},{"name":"Authorization","in":"header","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The caller's API key for that account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Members_getMembershipKey_Response_200"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"The identity has no access to that account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"servers":[{"url":"https://api.getdial.ai","description":"Dial REST API"}],"components":{"schemas":{"Authentication_signup_Response_200":{"type":"object","properties":{"verificationId":{"type":"string"}},"title":"Authentication_signup_Response_200"},"ErrorError":{"oneOf":[{"type":"string"},{"type":"object","additionalProperties":{"description":"Any type"}}],"description":"An error message, or a validation-error object for 400 responses.","title":"ErrorError"},"Error":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/ErrorError","description":"An error message, or a validation-error object for 400 responses."}},"title":"Error"},"AuthenticatedAccount":{"type":"object","properties":{"apiKey":{"type":["string","null"],"description":"Your API key. Shown once, when the account is created."},"accountId":{"type":"string"},"created":{"type":"boolean","description":"True when this call created the account."},"phoneNumber":{"type":["string","null"],"description":"The Dial number provisioned for the account, if any. This is not the phone number used to register."},"phoneNumberId":{"type":["string","null"]},"message":{"type":"string"}},"description":"A completed sign-in or registration. Returned by both [Verify a sign-up code](#operation/verifySignup) (when signing in) and [Verify a phone number](#operation/verifyNumber) (when registering).","title":"AuthenticatedAccount"},"PendingPhoneRegistration":{"type":"object","properties":{"registrationId":{"type":"string","description":"Identifies this registration for the phone-verification calls."}},"required":["registrationId"],"description":"The email is verified but no account exists yet, because registering requires a verified phone number. Continue with [Register a phone number](#operation/registerNumber).","title":"PendingPhoneRegistration"},"Authentication_verifySignup_Response_200":{"oneOf":[{"$ref":"#/components/schemas/AuthenticatedAccount"},{"$ref":"#/components/schemas/PendingPhoneRegistration"}],"title":"Authentication_verifySignup_Response_200"},"ApiV1AuthRegisterNumberPostResponsesContentApplicationJsonSchemaStatus":{"type":"string","enum":["pending"],"title":"ApiV1AuthRegisterNumberPostResponsesContentApplicationJsonSchemaStatus"},"Authentication_registerNumber_Response_200":{"type":"object","properties":{"registrationId":{"type":"string"},"phoneNumber":{"type":"string","description":"The number in canonical E.164 form."},"status":{"$ref":"#/components/schemas/ApiV1AuthRegisterNumberPostResponsesContentApplicationJsonSchemaStatus"}},"title":"Authentication_registerNumber_Response_200"},"AccountRole":{"type":"string","enum":["owner","member"],"description":"The calling API key's role in this account. `member` keys can do everything except delete the account.","title":"AccountRole"},"ApiKey":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":["string","null"]},"preview":{"type":"string","description":"First characters of the key, for identification."},"createdAt":{"type":"string","format":"date-time"}},"title":"ApiKey"},"AccountLimits":{"type":"object","properties":{"maxCallDurationSeconds":{"type":"integer","description":"Hard cap on the length of every call, in seconds. Overrides any per-call, per-number, or account cap. A higher value sent to Place a call, Update a phone number, or Update the current account returns 400; a cap inherited from another level is clamped to this value instead."},"maxConcurrentCalls":{"type":"integer","description":"Maximum number of calls in progress at once, inbound and outbound combined. An outbound call over the limit is rejected with 429 (`call_limit_reached`); an inbound call over the limit is declined."}},"required":["maxCallDurationSeconds","maxConcurrentCalls"],"description":"Guardrails applied to a free account — one that has never added credit and never subscribed. Null once the account has paid: both limits lift permanently on the first top-up or subscription. Read this instead of hardcoding the values, which can change.","title":"AccountLimits"},"Account":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string","format":"email","description":"The account owner's address. This identifies the account and is the same for every member — it is not the caller's own address."},"role":{"$ref":"#/components/schemas/AccountRole","description":"The calling API key's role in this account. `member` keys can do everything except delete the account."},"callerEmail":{"type":"string","format":"email","description":"The caller's own address, as opposed to `email` above (the owner's). The two are identical for an owner key."},"createdAt":{"type":"string","format":"date-time"},"apiKeys":{"type":"array","items":{"$ref":"#/components/schemas/ApiKey"}},"maxCallDurationSeconds":{"type":["integer","null"],"description":"Call-length cap in seconds; the effective cap on a call is the first one set in priority order — per-call, then number, then account — falling back to the global default. A higher-priority level overrides a lower one. Null means this level sets no cap. Free accounts (never added credit, never subscribed) are additionally capped by `limits.maxCallDurationSeconds` regardless of any configured cap."},"limits":{"oneOf":[{"$ref":"#/components/schemas/AccountLimits"},{"type":"null"}],"description":"Guardrails applied to a free account — one that has never added credit and never subscribed. Null once the account has paid: both limits lift permanently on the first top-up or subscription. Read this instead of hardcoding the values, which can change."}},"title":"Account"},"Account_deleteAccount_Response_200":{"type":"object","properties":{"deleted":{"type":"boolean"}},"title":"Account_deleteAccount_Response_200"},"ApiV1UsageGetParametersDays":{"type":"string","enum":["7","30","90"],"title":"ApiV1UsageGetParametersDays"},"UsageStat":{"type":"object","properties":{"value":{"type":"integer","description":"Total for the selected window."},"deltaPct":{"type":["number","null"],"format":"double","description":"Percent change vs the previous equal window, or null when that window had no activity."},"spark":{"type":"array","items":{"type":"integer"},"description":"Per-day values across the selected window, oldest first."}},"title":"UsageStat"},"UsageStats":{"type":"object","properties":{"messages":{"$ref":"#/components/schemas/UsageStat"},"calls":{"$ref":"#/components/schemas/UsageStat"},"minutes":{"$ref":"#/components/schemas/UsageStat"}},"description":"Headline metrics for the selected window — each with a per-day spark series and percent change vs the previous equal window.\n","title":"UsageStats"},"UsageSeriesItems":{"type":"object","properties":{"date":{"type":"string","format":"date"},"messages":{"type":"integer"},"calls":{"type":"integer"}},"title":"UsageSeriesItems"},"UsageCallDurationHistogramItems":{"type":"object","properties":{"label":{"type":"string"},"count":{"type":"integer"},"pct":{"type":"number","format":"double","description":"Fraction of the sample (0–1)."}},"title":"UsageCallDurationHistogramItems"},"UsageCallDuration":{"type":"object","properties":{"sampleSize":{"type":"integer","description":"Number of connected calls in the window."},"medianSec":{"type":"integer"},"p95Sec":{"type":"integer"},"longestSec":{"type":"integer"},"histogram":{"type":"array","items":{"$ref":"#/components/schemas/UsageCallDurationHistogramItems"},"description":"Duration distribution across fixed buckets."}},"description":"Voice-call talk-time stats for connected calls (duration > 0) in the window.","title":"UsageCallDuration"},"UsageTopNumbersItems":{"type":"object","properties":{"number":{"type":"string"},"nickname":{"type":["string","null"],"description":"User-set label for the number, or null. The dashboard falls back to the number's creation age when absent."},"createdAt":{"type":"string","format":"date-time"},"count":{"type":"integer"}},"title":"UsageTopNumbersItems"},"UsageBreakdownRow":{"type":"object","properties":{"d7":{"type":"integer","description":"Last 7 days."},"d30":{"type":"integer","description":"Last 30 days."},"all":{"type":"integer","description":"All time."}},"description":"Totals for a metric over fixed windows.","title":"UsageBreakdownRow"},"UsageBreakdown":{"type":"object","properties":{"messages":{"$ref":"#/components/schemas/UsageBreakdownRow"},"calls":{"$ref":"#/components/schemas/UsageBreakdownRow"},"minutes":{"$ref":"#/components/schemas/UsageBreakdownRow"}},"description":"Fixed-window totals (last 7 days, last 30 days, all time).","title":"UsageBreakdown"},"UsageTotals":{"type":"object","properties":{"messages":{"type":"integer"},"calls":{"type":"integer"},"numbers":{"type":"integer"}},"description":"All-time totals.","title":"UsageTotals"},"PeriodVoiceLeaf":{"type":"object","properties":{"calls":{"type":"integer"},"minutes":{"type":"integer"}},"title":"PeriodVoiceLeaf"},"PeriodVoiceGeo":{"type":"object","properties":{"domestic":{"$ref":"#/components/schemas/PeriodVoiceLeaf"},"intl":{"$ref":"#/components/schemas/PeriodVoiceLeaf"}},"title":"PeriodVoiceGeo"},"PeriodBreakdownVoice":{"type":"object","properties":{"outbound":{"$ref":"#/components/schemas/PeriodVoiceGeo"},"inbound":{"$ref":"#/components/schemas/PeriodVoiceGeo"}},"title":"PeriodBreakdownVoice"},"PeriodMessageLeaf":{"type":"object","properties":{"count":{"type":"integer"}},"title":"PeriodMessageLeaf"},"PeriodMessageGeo":{"type":"object","properties":{"domestic":{"$ref":"#/components/schemas/PeriodMessageLeaf"},"intl":{"$ref":"#/components/schemas/PeriodMessageLeaf"}},"title":"PeriodMessageGeo"},"PeriodBreakdownMessages":{"type":"object","properties":{"outbound":{"$ref":"#/components/schemas/PeriodMessageGeo"},"inbound":{"$ref":"#/components/schemas/PeriodMessageGeo"}},"title":"PeriodBreakdownMessages"},"PeriodBreakdown":{"type":"object","properties":{"voice":{"$ref":"#/components/schemas/PeriodBreakdownVoice"},"messages":{"$ref":"#/components/schemas/PeriodBreakdownMessages"}},"description":"Period usage split by direction (outbound/inbound) and geography (domestic/international).","title":"PeriodBreakdown"},"CurrentPeriodNumbersItems":{"type":"object","properties":{"id":{"type":"string"},"number":{"type":"string"},"nickname":{"type":["string","null"],"description":"User-set label for the number, or null. The dashboard falls back to the number's creation age when absent."},"createdAt":{"type":"string","format":"date-time"},"breakdown":{"$ref":"#/components/schemas/PeriodBreakdown"}},"title":"CurrentPeriodNumbersItems"},"CurrentPeriod":{"type":"object","properties":{"start":{"type":"string","format":"date-time","description":"Period start (inclusive)."},"end":{"type":"string","format":"date-time","description":"Period end (exclusive); the period renews on this date."},"daysTotal":{"type":"integer"},"daysElapsed":{"type":"integer"},"daysLeft":{"type":"integer"},"all":{"$ref":"#/components/schemas/PeriodBreakdown"},"numbers":{"type":"array","items":{"$ref":"#/components/schemas/CurrentPeriodNumbersItems"},"description":"Per-number breakdown, sorted by total period activity descending."}},"description":"Usage for the current period — a monthly cycle anchored to the account's creation date (jumps of one month from `createdAt`; stands in until billing and pricing are defined). Independent of the `days` window. Split per number (plus an `all` aggregate) by direction and domestic/international.\n","title":"CurrentPeriod"},"Usage":{"type":"object","properties":{"periodDays":{"type":"integer","description":"The selected window in days (7, 30, or 90)."},"stats":{"$ref":"#/components/schemas/UsageStats","description":"Headline metrics for the selected window — each with a per-day spark series and percent change vs the previous equal window.\n"},"series":{"type":"array","items":{"$ref":"#/components/schemas/UsageSeriesItems"},"description":"One entry per calendar day in the selected window (UTC), oldest first."},"callDuration":{"$ref":"#/components/schemas/UsageCallDuration","description":"Voice-call talk-time stats for connected calls (duration > 0) in the window."},"topNumbers":{"type":"array","items":{"$ref":"#/components/schemas/UsageTopNumbersItems"},"description":"Account numbers ranked by total activity (messages + calls) in the window, descending (up to 6)."},"breakdown":{"$ref":"#/components/schemas/UsageBreakdown","description":"Fixed-window totals (last 7 days, last 30 days, all time)."},"totals":{"$ref":"#/components/schemas/UsageTotals","description":"All-time totals."},"currentPeriod":{"$ref":"#/components/schemas/CurrentPeriod"}},"description":"Activity analytics for the selected window (`days`) plus fixed-window breakdown totals. All figures are derived from real message and call records.\n","title":"Usage"},"BillingSubscriptionInterval":{"type":"string","enum":["monthly","annual"],"title":"BillingSubscriptionInterval"},"BillingSubscription":{"type":"object","properties":{"periodStart":{"type":"string","format":"date-time"},"periodEnd":{"type":"string","format":"date-time"},"quantity":{"type":"integer","minimum":0,"description":"How many of your phone numbers this subscription covers (one unit each). `0` while you hold none — the subscription stays active and still bills for **one** number (its minimum), so the amount charged is `max(quantity, 1)` × the unit price, and the next number you provision is free."},"interval":{"$ref":"#/components/schemas/BillingSubscriptionInterval"},"cancelAtPeriodEnd":{"type":"boolean","description":"True when the subscription is scheduled to cancel at periodEnd (coverage stays until then, then the account reverts to PAYG). Read live from Stripe, the source of truth."}},"description":"The current subscription, or null when the account is pay-as-you-go.","title":"BillingSubscription"},"BillingNumbersItemsMode":{"type":"string","enum":["PAYG","FIXED"],"description":"PAYG bills the wallet; FIXED is covered by a subscription.","title":"BillingNumbersItemsMode"},"BillingNumbersItems":{"type":"object","properties":{"id":{"type":"string"},"number":{"type":"string"},"nickname":{"type":["string","null"],"description":"User-assigned label for the number, or null if unset."},"mode":{"$ref":"#/components/schemas/BillingNumbersItemsMode","description":"PAYG bills the wallet; FIXED is covered by a subscription."}},"title":"BillingNumbersItems"},"BillingDepositsItemsKind":{"type":"string","enum":["card","welcome","manual"],"description":"`card` — paid Stripe top-up; `welcome` — automatic signup credit; `manual` — internal grant by the Dial team.","title":"BillingDepositsItemsKind"},"BillingDepositsItems":{"type":"object","properties":{"createdAt":{"type":"string","format":"date-time"},"amountCents":{"type":"integer","description":"Amount credited, in USD cents (positive)."},"kind":{"$ref":"#/components/schemas/BillingDepositsItemsKind","description":"`card` — paid Stripe top-up; `welcome` — automatic signup credit; `manual` — internal grant by the Dial team."},"invoiceId":{"type":["string","null"],"description":"Stripe invoice id backing this deposit (only `card` top-ups have one; null otherwise). Pass it to Download an invoice to get the Stripe-hosted invoice and PDF."}},"title":"BillingDepositsItems"},"BillingPricing":{"type":"object","properties":{"monthlyCents":{"type":"integer","description":"Per-number price billed monthly."},"annualCents":{"type":"integer","description":"Per-number price billed annually."}},"description":"Per-number subscription unit prices (USD cents), read from Stripe (the source of truth). Stripe is a dependency of this endpoint.","title":"BillingPricing"},"BillingPaymentMethodsItems":{"type":"object","properties":{"id":{"type":"string","description":"Stripe PaymentMethod id (pm_…)."},"type":{"type":"string","description":"Stripe PaymentMethod type: card, link, …"},"brand":{"type":"string","description":"Card brand (visa, mastercard, amex, …); empty for non-card methods."},"last4":{"type":"string","description":"Card last four; empty for non-card methods."},"expMonth":{"type":"integer","description":"Card expiry month; 0 for non-card methods."},"expYear":{"type":"integer","description":"Card expiry year; 0 for non-card methods."},"email":{"type":["string","null"],"description":"Identifying email for account-style methods (e.g. Link); null otherwise."},"isDefault":{"type":"boolean","description":"True for the customer's default payment method (invoices + subscription renewals)."}},"title":"BillingPaymentMethodsItems"},"Billing":{"type":"object","properties":{"balanceCents":{"type":"integer","description":"Credit-wallet balance in USD cents. May be negative."},"numbersReleaseAt":{"type":["string","null"],"format":"date-time","description":"When all of the account's numbers will be released for non-payment, or null when nothing is at risk. Non-null only for a pay-as-you-go account whose balance is negative: it's the end of the 30-day grace period (measured from when the balance first went negative). Topping the balance back to zero or above clears it. Subscription-covered accounts are never at risk, so this is always null for them."},"subscription":{"oneOf":[{"$ref":"#/components/schemas/BillingSubscription"},{"type":"null"}],"description":"The current subscription, or null when the account is pay-as-you-go."},"numbers":{"type":"array","items":{"$ref":"#/components/schemas/BillingNumbersItems"}},"deposits":{"type":"array","items":{"$ref":"#/components/schemas/BillingDepositsItems"},"description":"Recent credits added to the wallet, most recent first."},"pricing":{"$ref":"#/components/schemas/BillingPricing","description":"Per-number subscription unit prices (USD cents), read from Stripe (the source of truth). Stripe is a dependency of this endpoint."},"paymentMethods":{"type":"array","items":{"$ref":"#/components/schemas/BillingPaymentMethodsItems"},"description":"Saved payment methods on the account's Stripe customer (all reusable types — card, Link, …), used for top-ups and subscription billing. Empty until one is added. Read live from Stripe, with the default first."}},"description":"Account billing status (GET /api/v1/billing).","title":"Billing"},"Billing_createCreditsCheckout_Response_200":{"type":"object","properties":{"clientSecret":{"type":"string"}},"title":"Billing_createCreditsCheckout_Response_200"},"ApiV1BillingCheckoutSubscriptionPostRequestBodyContentApplicationJsonSchemaInterval":{"type":"string","enum":["monthly","annual"],"title":"ApiV1BillingCheckoutSubscriptionPostRequestBodyContentApplicationJsonSchemaInterval"},"Billing_createSubscriptionCheckout_Response_200":{"type":"object","properties":{"clientSecret":{"type":"string"}},"title":"Billing_createSubscriptionCheckout_Response_200"},"Billing_cancelSubscription_Response_200":{"type":"object","properties":{"canceledAtPeriodEnd":{"type":"boolean"},"cancelAt":{"type":["integer","null"],"description":"Unix timestamp (seconds) the subscription ends."}},"title":"Billing_cancelSubscription_Response_200"},"Billing_resumeSubscription_Response_200":{"type":"object","properties":{"resumed":{"type":"boolean"}},"title":"Billing_resumeSubscription_Response_200"},"ApiV1BillingSubscriptionChangePostRequestBodyContentApplicationJsonSchemaInterval":{"type":"string","enum":["monthly","annual"],"title":"ApiV1BillingSubscriptionChangePostRequestBodyContentApplicationJsonSchemaInterval"},"ApiV1BillingSubscriptionChangePostResponsesContentApplicationJsonSchemaInterval":{"type":"string","enum":["monthly","annual"],"title":"ApiV1BillingSubscriptionChangePostResponsesContentApplicationJsonSchemaInterval"},"Billing_changeSubscriptionInterval_Response_200":{"type":"object","properties":{"changed":{"type":"boolean","description":"False when already on the target interval."},"interval":{"$ref":"#/components/schemas/ApiV1BillingSubscriptionChangePostResponsesContentApplicationJsonSchemaInterval"}},"title":"Billing_changeSubscriptionInterval_Response_200"},"ApiV1BillingSubscriptionPreviewPostRequestBodyContentApplicationJsonSchemaInterval":{"type":"string","enum":["monthly","annual"],"description":"Target billing interval. Defaults to the current interval.","title":"ApiV1BillingSubscriptionPreviewPostRequestBodyContentApplicationJsonSchemaInterval"},"ApiV1BillingSubscriptionPreviewPostResponsesContentApplicationJsonSchemaLinesItems":{"type":"object","properties":{"description":{"type":"string"},"amountCents":{"type":"integer"}},"title":"ApiV1BillingSubscriptionPreviewPostResponsesContentApplicationJsonSchemaLinesItems"},"Billing_previewSubscriptionChange_Response_200":{"type":"object","properties":{"amountDueCents":{"type":"integer","description":"Amount that would be charged today, in USD cents (prorations and discounts included)."},"currency":{"type":"string"},"prorationDate":{"type":"integer","description":"Unix timestamp (seconds) the proration was calculated as of. Pass it back as `prorationDate` to the change endpoint to charge exactly this quoted amount."},"discountCents":{"type":"integer","description":"Total discount applied by the coupon, in USD cents (0 when no coupon or no effect)."},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ApiV1BillingSubscriptionPreviewPostResponsesContentApplicationJsonSchemaLinesItems"},"description":"The invoice preview line items (proration credits appear as negatives)."}},"title":"Billing_previewSubscriptionChange_Response_200"},"Billing_createPaymentMethodSetup_Response_200":{"type":"object","properties":{"clientSecret":{"type":"string"}},"title":"Billing_createPaymentMethodSetup_Response_200"},"Billing_removePaymentMethod_Response_200":{"type":"object","properties":{"removed":{"type":"boolean"}},"title":"Billing_removePaymentMethod_Response_200"},"Billing_setDefaultPaymentMethod_Response_200":{"type":"object","properties":{"default":{"type":"boolean"}},"title":"Billing_setDefaultPaymentMethod_Response_200"},"Billing_getInvoice_Response_200":{"type":"object","properties":{"hostedInvoiceUrl":{"type":["string","null"],"description":"Stripe-hosted invoice page (has a download button), or null."},"invoicePdf":{"type":["string","null"],"description":"Direct link to the invoice PDF, or null."}},"title":"Billing_getInvoice_Response_200"},"ApiV1BillingActivityGetParametersFilter":{"type":"string","enum":["all","usage","payment-credits"],"default":"all","title":"ApiV1BillingActivityGetParametersFilter"},"ApiV1BillingActivityGetResponsesContentApplicationJsonSchemaDataItemsType":{"type":"string","enum":["usage","credit","payment"],"title":"ApiV1BillingActivityGetResponsesContentApplicationJsonSchemaDataItemsType"},"ApiV1BillingActivityGetResponsesContentApplicationJsonSchemaDataItemsAttribution":{"type":"string","enum":["wallet","entitlement"],"title":"ApiV1BillingActivityGetResponsesContentApplicationJsonSchemaDataItemsAttribution"},"ApiV1BillingActivityGetResponsesContentApplicationJsonSchemaDataItemsKind":{"type":"string","enum":["card","welcome","manual"],"description":"Credit rows only.","title":"ApiV1BillingActivityGetResponsesContentApplicationJsonSchemaDataItemsKind"},"ApiV1BillingActivityGetResponsesContentApplicationJsonSchemaDataItems":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/ApiV1BillingActivityGetResponsesContentApplicationJsonSchemaDataItemsType"},"occurredAt":{"type":"string","format":"date-time"},"balanceAfterCents":{"type":"integer","description":"Credit-wallet balance in USD cents immediately after this row — a running balance; the newest item equals the current wallet balance. Rows that don't move the wallet carry the balance as of their moment."},"amountCents":{"type":"integer","description":"Credits/payments: amount; usage: see totalCents."},"totalCents":{"type":"integer","description":"Usage rows only — billed amount in USD cents."},"fareName":{"type":"string"},"number":{"type":["string","null"]},"billedQuantity":{"type":"integer"},"attribution":{"$ref":"#/components/schemas/ApiV1BillingActivityGetResponsesContentApplicationJsonSchemaDataItemsAttribution"},"phoneNumberId":{"type":"string"},"callId":{"type":["string","null"]},"messageId":{"type":["string","null"]},"kind":{"$ref":"#/components/schemas/ApiV1BillingActivityGetResponsesContentApplicationJsonSchemaDataItemsKind","description":"Credit rows only."},"invoiceId":{"type":["string","null"],"description":"Stripe invoice id (credit/payment rows); pass to Download an invoice."},"reason":{"type":"string","description":"Payment rows only — Stripe billing reason."}},"description":"An activity item. `type` selects the shape: `usage` (fareName, number, billedQuantity, totalCents, attribution, phoneNumberId, callId, messageId); `credit` (amountCents, kind, invoiceId); `payment` (amountCents, invoiceId, reason). Every item also carries `balanceAfterCents`.","title":"ApiV1BillingActivityGetResponsesContentApplicationJsonSchemaDataItems"},"Billing_listBillingActivity_Response_200":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ApiV1BillingActivityGetResponsesContentApplicationJsonSchemaDataItems"}},"hasMore":{"type":"boolean","description":"True when more items exist older than the last one returned."}},"title":"Billing_listBillingActivity_Response_200"},"PhoneNumberCapabilitiesItems":{"type":"string","enum":["sms","call","imessage"],"title":"PhoneNumberCapabilitiesItems"},"PhoneNumberSetupStatus":{"type":"string","enum":["provisioning","ready","failed"],"description":"Whether the number is ready to use. An SMS/call number is always `ready`. An `imessage` number is `provisioning` while it's being set up and only becomes usable for sending or calling once `ready` (`failed` on a setup error). Poll List phone numbers to await `ready`.","title":"PhoneNumberSetupStatus"},"PhoneNumberInboundVoiceGender":{"type":"string","enum":["male","female"],"description":"Voice gender the AI agent uses on inbound calls to this number. Null when unset — the voice then uses the default, female. Set and cleared with Update a phone number.","title":"PhoneNumberInboundVoiceGender"},"PhoneNumberTenDlcStatus":{"type":"string","enum":["not_registered","in_review","with_carrier","approved","rejected"],"description":"Where this number's registration stands.\n- `not_registered` — 10DLC applies to this number but nothing has\n  been submitted. Outbound US SMS/MMS is blocked by carriers.\n- `in_review` — submitted; Dial is checking the details before\n  filing them with the carrier registry.\n- `with_carrier` — filed with the carrier registry; a decision\n  usually lands within 3–5 business days.\n- `approved` — registered. The number can send outbound SMS and\n  MMS to US phone numbers.\n- `rejected` — changes are needed before it can be registered.\n  See `reason`, then resubmit (no additional fee).\n\nThe brand and the campaign are vetted separately, and this is the combined outcome: `approved` means both cleared, and `rejected` means either was turned down.","title":"PhoneNumberTenDlcStatus"},"PhoneNumberTenDlcKind":{"type":"string","enum":["sole_proprietor","business"],"description":"Who the number was registered as. Null while `status` is `not_registered`.","title":"PhoneNumberTenDlcKind"},"PhoneNumberTenDlc":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/PhoneNumberTenDlcStatus","description":"Where this number's registration stands.\n- `not_registered` — 10DLC applies to this number but nothing has\n  been submitted. Outbound US SMS/MMS is blocked by carriers.\n- `in_review` — submitted; Dial is checking the details before\n  filing them with the carrier registry.\n- `with_carrier` — filed with the carrier registry; a decision\n  usually lands within 3–5 business days.\n- `approved` — registered. The number can send outbound SMS and\n  MMS to US phone numbers.\n- `rejected` — changes are needed before it can be registered.\n  See `reason`, then resubmit (no additional fee).\n\nThe brand and the campaign are vetted separately, and this is the combined outcome: `approved` means both cleared, and `rejected` means either was turned down."},"kind":{"$ref":"#/components/schemas/PhoneNumberTenDlcKind","description":"Who the number was registered as. Null while `status` is `not_registered`."},"submittedAt":{"type":["string","null"],"format":"date-time","description":"When the current submission was sent. Null while `status` is `not_registered`. A resubmission after a rejection updates this."},"reviewedAt":{"type":["string","null"],"format":"date-time","description":"When the registration was approved or rejected. Null until then."},"reason":{"type":["string","null"],"description":"What needs to change, verbatim as shown to the account owner. Non-null only when `status` is `rejected`."}},"required":["status","kind","submittedAt","reviewedAt","reason"],"description":"US 10DLC carrier registration for this number. Null when 10DLC does not apply to it — a non-US number, a number with the `imessage` capability (those send over Apple Messages for Business, not carrier SMS), or a number on an account that isn't pay-as-you-go and has no registration yet. Otherwise an object whose `status` says where the registration stands, starting at `not_registered`.\nUS carriers block outbound SMS and MMS sent from an unregistered 10-digit number to US phone numbers. Registering lifts that block for this number. Inbound texts, voice calls in either direction, and messaging to numbers outside the US are unaffected.","title":"PhoneNumberTenDlc"},"PhoneNumber":{"type":"object","properties":{"id":{"type":"string"},"number":{"type":"string","description":"The number in E.164 format."},"nickname":{"type":["string","null"],"description":"Optional human-readable label for the number, e.g. \"Support line\". Free text, at most 100 characters, not required to be unique. Null when unset. Set and cleared with Update a phone number."},"firstName":{"type":["string","null"],"description":"iMessage display first name — with `lastName` and the avatar photo, the identity shown beside this number's messages in recipients' Messages apps. At most 30 characters. Null when unset, and always null on numbers without the `imessage` capability. Set and cleared with Update a phone number."},"lastName":{"type":["string","null"],"description":"iMessage display last name. At most 30 characters. Null when unset, and always null on numbers without the `imessage` capability. Set and cleared with Update a phone number."},"avatarUrl":{"type":["string","null"],"description":"Public URL of the number's current iMessage avatar photo, hosted by Dial. Null when no photo has been set, and always null on numbers without the `imessage` capability. Set with Update a phone number; the photo can be replaced but not removed."},"country":{"type":"string","description":"ISO-3166-1 alpha-2 country code of the number. Always `US` — only US numbers can be provisioned at this time."},"capabilities":{"type":"array","items":{"$ref":"#/components/schemas/PhoneNumberCapabilitiesItems"},"description":"Channels the number supports. `[\"sms\",\"call\"]` for a standard number; `[\"sms\",\"call\",\"imessage\"]` for an iMessage number (which sends iMessage with automatic fallback to RCS/SMS). Mirrors the `capabilities` accepted when provisioning."},"setupStatus":{"$ref":"#/components/schemas/PhoneNumberSetupStatus","description":"Whether the number is ready to use. An SMS/call number is always `ready`. An `imessage` number is `provisioning` while it's being set up and only becomes usable for sending or calling once `ready` (`failed` on a setup error). Poll List phone numbers to await `ready`."},"setupError":{"type":["string","null"],"description":"Human-readable reason when `setupStatus` is `failed`; null otherwise."},"inboundInstruction":{"type":["string","null"],"description":"System prompt the AI voice agent uses on inbound calls to this number. Set at provisioning time and changeable with Update a phone number. Null for numbers provisioned before this field existed — those reject inbound calls until an instruction is set."},"inboundVoiceGender":{"$ref":"#/components/schemas/PhoneNumberInboundVoiceGender","description":"Voice gender the AI agent uses on inbound calls to this number. Null when unset — the voice then uses the default, female. Set and cleared with Update a phone number."},"inboundLanguage":{"type":["string","null"],"description":"BCP-47 language tag the AI voice agent uses on inbound calls to this number. Null when unset — the language is then detected from the caller's country prefix on each call, with the agent handling both that language and `en-US`. Set and cleared with Update a phone number."},"accountId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"maxCallDurationSeconds":{"type":["integer","null"],"description":"Call-length cap in seconds; the effective cap on a call is the first one set in priority order — per-call, then number, then account — falling back to the global default. A higher-priority level overrides a lower one. Null means this level sets no cap. Free accounts (never added credit, never subscribed) are additionally hard-capped at 300 seconds regardless of any configured cap."},"tenDlc":{"oneOf":[{"$ref":"#/components/schemas/PhoneNumberTenDlc"},{"type":"null"}],"description":"US 10DLC carrier registration for this number. Null when 10DLC does not apply to it — a non-US number, a number with the `imessage` capability (those send over Apple Messages for Business, not carrier SMS), or a number on an account that isn't pay-as-you-go and has no registration yet. Otherwise an object whose `status` says where the registration stands, starting at `not_registered`.\nUS carriers block outbound SMS and MMS sent from an unregistered 10-digit number to US phone numbers. Registering lifts that block for this number. Inbound texts, voice calls in either direction, and messaging to numbers outside the US are unaffected."}},"title":"PhoneNumber"},"Phone numbers_listNumbers_Response_200":{"type":"object","properties":{"numbers":{"type":"array","items":{"$ref":"#/components/schemas/PhoneNumber"}}},"title":"Phone numbers_listNumbers_Response_200"},"ApiV1NumbersPostRequestBodyContentApplicationJsonSchemaCapabilitiesItems":{"type":"string","enum":["sms","call","imessage"],"title":"ApiV1NumbersPostRequestBodyContentApplicationJsonSchemaCapabilitiesItems"},"ApiV1NumbersPostRequestBodyContentApplicationJsonSchemaInboundVoiceGender":{"type":"string","enum":["male","female"],"description":"Voice gender for the AI agent on inbound calls to this number. Optional — when omitted, the voice is female (the default for every language). Pass `male` to override. Update later with Update a phone number.","title":"ApiV1NumbersPostRequestBodyContentApplicationJsonSchemaInboundVoiceGender"},"Phone numbers_provisionNumber_Response_201":{"type":"object","properties":{"number":{"$ref":"#/components/schemas/PhoneNumber"}},"title":"Phone numbers_provisionNumber_Response_201"},"Phone numbers_getNumber_Response_200":{"type":"object","properties":{"number":{"$ref":"#/components/schemas/PhoneNumber"}},"title":"Phone numbers_getNumber_Response_200"},"Success":{"type":"object","properties":{"success":{"type":"boolean"}},"title":"Success"},"ApiV1NumbersIdPatchRequestBodyContentApplicationJsonSchemaInboundVoiceGender":{"type":"string","enum":["male","female"],"description":"Voice gender for the AI agent on inbound calls to this number. Send null to clear it (the voice reverts to the default, female). Takes effect on the next inbound call.","title":"ApiV1NumbersIdPatchRequestBodyContentApplicationJsonSchemaInboundVoiceGender"},"Phone numbers_updateNumber_Response_200":{"type":"object","properties":{"number":{"$ref":"#/components/schemas/PhoneNumber"}},"title":"Phone numbers_updateNumber_Response_200"},"TenDlcRegistrationStatus":{"type":"string","enum":["in_review","with_carrier","approved","rejected"],"description":"Where the registration stands. Unlike the `tenDlc` summary on a phone number, this object only exists once something has been submitted, so `not_registered` never appears here.","title":"TenDlcRegistrationStatus"},"TenDlcRegistrationKind":{"type":"string","enum":["sole_proprietor","business"],"description":"Who the number is registered as. A sole proprietor operates under their own name and has no EIN — lower throughput, quicker to approve. A business is a registered company with an EIN.","title":"TenDlcRegistrationKind"},"TenDlcBrandBusinessType":{"type":"string","enum":["Co-operative","Corporation","Limited Liability Corporation","Non-profit Corporation","Partnership"],"description":"`business` only. The legal form. A sole proprietorship is not a business brand — register as `sole_proprietor` instead.","title":"TenDlcBrandBusinessType"},"TenDlcBrandBusinessIndustry":{"type":"string","enum":["AGRICULTURE","AUTOMOTIVE","BANKING","CONSTRUCTION","CONSUMER","EDUCATION","ELECTRONICS","ENGINEERING","ENERGY","FAST_MOVING_CONSUMER_GOODS","FINANCIAL","FINTECH","FOOD_AND_BEVERAGE","GOVERNMENT","HEALTHCARE","HOSPITALITY","INSURANCE","JEWELRY","LEGAL","MANUFACTURING","MEDIA","NOT_FOR_PROFIT","OIL_AND_GAS","ONLINE","PROFESSIONAL_SERVICES","RAW_MATERIALS","REAL_ESTATE","RELIGION","RETAIL","TECHNOLOGY","TELECOMMUNICATIONS","TRANSPORTATION","TRAVEL"],"description":"`business` only.","title":"TenDlcBrandBusinessIndustry"},"TenDlcBrandVertical":{"type":"string","enum":["AGRICULTURE","COMMUNICATION","CONSTRUCTION","EDUCATION","ENERGY","ENTERTAINMENT","FINANCIAL","GAMBLING","GOVERNMENT","HEALTHCARE","HOSPITALITY","HUMAN_RESOURCES","INSURANCE","LEGAL","MANUFACTURING","NGO","POLITICAL","POSTAL","PROFESSIONAL","REAL_ESTATE","RETAIL","TECHNOLOGY","TRANSPORTATION"],"description":"`sole_proprietor` only, and required. The trade the brand operates in.\nThis is the sole proprietor's counterpart to `businessIndustry`, but **the two take different values** — the registry validates them against separate vocabularies. `PROFESSIONAL` here is `PROFESSIONAL_SERVICES` there, and several industries have no vertical at all (`TRAVEL`, `MEDIA`, `TELECOMMUNICATIONS`, `RELIGION`, `ONLINE`, `NOT_FOR_PROFIT`, `AUTOMOTIVE`, `BANKING`). Send a value from this list, not from `businessIndustry`'s.","title":"TenDlcBrandVertical"},"TenDlcBrandRegistrationIdType":{"type":"string","enum":["EIN","DUNS","CBN","CN","ACN","CIN","VAT","VATRN","RN","Other"],"description":"`business` only. Which registration number follows.","title":"TenDlcBrandRegistrationIdType"},"TenDlcBrandJobPosition":{"type":"string","enum":["Director","GM","VP","CEO","CFO","General Counsel","Other"],"description":"`business` only. The representative's role.","title":"TenDlcBrandJobPosition"},"TenDlcBrandBusinessRegionsOfOperation":{"type":"string","enum":["USA_AND_CANADA","AFRICA","ASIA","EUROPE","LATIN_AMERICA"],"description":"`business` only. Where the business operates.","title":"TenDlcBrandBusinessRegionsOfOperation"},"TenDlcBrandCompanyType":{"type":"string","enum":["private","public","non-profit","government"],"description":"`business` only.","title":"TenDlcBrandCompanyType"},"TenDlcBrandStockExchange":{"type":"string","enum":["NASDAQ","NYSE","AMEX","AMX","ASX","B3","BME","BSE","FRA","ICEX","JPX","JSE","KRX","LON","NSE","OMX","SEHK","SGX","SSE","STO","SWX","SZSE","TSX","TWSE","VSE","OTHER"],"description":"`business` only, and required when `companyType` is `public`. Carriers check it against public sources.","title":"TenDlcBrandStockExchange"},"TenDlcBrand":{"type":"object","properties":{"brandName":{"type":"string","description":"The registered business or DBA name; for a sole proprietor, the name they trade under. Carriers reject an account id or an email address."},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string","description":"Mobile number in **E.164** — it must carry its country code, because a brand registered outside the US would otherwise be guessed at. For a sole proprietor this receives the carrier's verification SMS, answered by replying `YES` within 24 hours. It may not be a Dial number — carriers won't verify a number issued by a messaging provider."},"street":{"type":"string"},"city":{"type":"string"},"state":{"type":"string","description":"State or province of the registered address, required whatever the country. The two-letter code for the US and Canada; the region name elsewhere."},"zip":{"type":"string"},"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code of the registered address, defaulting to `US`.\nThe NUMBER being registered is always US — that is what 10DLC is — but the business need not be. A **business** may be registered from any country the registry accepts, with its own country's tax ID in `registrationNumber` (a non-US brand may draw additional vetting). A **sole proprietor** must be in the US or Canada, so only `US` and `CA` are accepted for that kind."},"businessType":{"$ref":"#/components/schemas/TenDlcBrandBusinessType","description":"`business` only. The legal form. A sole proprietorship is not a business brand — register as `sole_proprietor` instead."},"businessIndustry":{"$ref":"#/components/schemas/TenDlcBrandBusinessIndustry","description":"`business` only."},"vertical":{"$ref":"#/components/schemas/TenDlcBrandVertical","description":"`sole_proprietor` only, and required. The trade the brand operates in.\nThis is the sole proprietor's counterpart to `businessIndustry`, but **the two take different values** — the registry validates them against separate vocabularies. `PROFESSIONAL` here is `PROFESSIONAL_SERVICES` there, and several industries have no vertical at all (`TRAVEL`, `MEDIA`, `TELECOMMUNICATIONS`, `RELIGION`, `ONLINE`, `NOT_FOR_PROFIT`, `AUTOMOTIVE`, `BANKING`). Send a value from this list, not from `businessIndustry`'s."},"registrationIdType":{"$ref":"#/components/schemas/TenDlcBrandRegistrationIdType","description":"`business` only. Which registration number follows."},"registrationNumber":{"type":"string","description":"`business` only. In the US, the EIN."},"websiteUrl":{"type":"string","description":"`business` only. Reviewers check it matches the brand."},"jobPosition":{"$ref":"#/components/schemas/TenDlcBrandJobPosition","description":"`business` only. The representative's role."},"businessTitle":{"type":"string","description":"`business` only. The actual job title, free text."},"businessRegionsOfOperation":{"$ref":"#/components/schemas/TenDlcBrandBusinessRegionsOfOperation","description":"`business` only. Where the business operates."},"companyType":{"$ref":"#/components/schemas/TenDlcBrandCompanyType","description":"`business` only."},"stockExchange":{"$ref":"#/components/schemas/TenDlcBrandStockExchange","description":"`business` only, and required when `companyType` is `public`. Carriers check it against public sources."},"stockTicker":{"type":"string","description":"`business` only, and required when `companyType` is `public`."}},"required":["brandName","firstName","lastName","email","phone","street","city","state","zip","country"],"description":"Who is registering — the business identity carriers vet. The `business`-only fields are absent on a sole proprietor registration.","title":"TenDlcBrand"},"TenDlcCampaignUseCase":{"type":"string","enum":["MIXED","MARKETING","CUSTOMER_CARE","2FA","ACCOUNT_NOTIFICATION","DELIVERY_NOTIFICATION","FRAUD_ALERT","SECURITY_ALERT","HIGHER_EDUCATION","POLLING_VOTING","PUBLIC_SERVICE_ANNOUNCEMENT","SOLE_PROPRIETOR"],"description":"What the campaign is for. Optional on submit for a `business`, defaulting to `MIXED`. Always `SOLE_PROPRIETOR` for a sole proprietor, which takes no value on submit.","title":"TenDlcCampaignUseCase"},"TenDlcCampaign":{"type":"object","properties":{"useCase":{"$ref":"#/components/schemas/TenDlcCampaignUseCase","description":"What the campaign is for. Optional on submit for a `business`, defaulting to `MIXED`. Always `SOLE_PROPRIETOR` for a sole proprietor, which takes no value on submit."},"description":{"type":"string","minLength":40,"maxLength":4096,"description":"Who is messaged and why. Reviewers weigh this heavily — a single word like \"Marketing\" is rejected."},"messageFlow":{"type":"string","minLength":40,"maxLength":1920,"description":"How people opt in. If more than one method is used, all must be named. Room above 1920 characters is reserved for compliance material Dial appends when filing with the carrier registry."},"samples":{"type":"array","items":{"type":"string","minLength":20,"maxLength":1024},"description":"Sample messages. Each must name the brand and carry an opt-out instruction. If only one message text will ever be sent, repeat it — carriers require at least two."},"privacyPolicyUrl":{"type":"string","description":"The privacy policy registered with the campaign. Your own if you gave one; otherwise, for a sole proprietor, the notice Dial publishes for the brand."},"termsUrl":{"type":"string","description":"The terms registered with the campaign — your own, or the page Dial publishes for a sole proprietor who gave none."},"containsUrls":{"type":"boolean","description":"Whether messages will contain links. Defaults to false."},"containsPhones":{"type":"boolean","description":"Whether messages will contain phone numbers. Defaults to false."},"acceptPublishedNotice":{"type":"boolean","description":"Accepted on submit, never returned. Required on a sole proprietor campaign that sends neither `privacyPolicyUrl` nor `termsUrl`: it authorises Dial to publish those two pages for the brand and register them. Ignored when you send your own URLs."},"optInImage":{"description":"Accepted on submit, never returned. The screenshot itself travels as the `optInImage` file part of a `multipart/form-data` request; this JSON key exists only to **remove** a stored screenshot — send `null` on a resubmission. Leaving both out keeps whatever is stored."},"optInImageUrl":{"type":["string","null"],"description":"Where the submitted opt-in screenshot is hosted — the image carrier reviewers see. `null` when none was attached."}},"required":["useCase","description","messageFlow","samples","containsUrls","containsPhones"],"description":"What the number sends — the use case carriers approve. A sole proprietor registration's use case is always `SOLE_PROPRIETOR`, and its policy URLs are the ones Dial publishes unless it supplied its own.","title":"TenDlcCampaign"},"TenDlcRegistration":{"type":"object","properties":{"phoneNumberId":{"type":"string"},"status":{"$ref":"#/components/schemas/TenDlcRegistrationStatus","description":"Where the registration stands. Unlike the `tenDlc` summary on a phone number, this object only exists once something has been submitted, so `not_registered` never appears here."},"kind":{"$ref":"#/components/schemas/TenDlcRegistrationKind","description":"Who the number is registered as. A sole proprietor operates under their own name and has no EIN — lower throughput, quicker to approve. A business is a registered company with an EIN."},"brand":{"$ref":"#/components/schemas/TenDlcBrand"},"campaign":{"$ref":"#/components/schemas/TenDlcCampaign"},"feeCents":{"type":"integer","description":"The one-time registration fee charged for this number, in USD cents."},"reason":{"type":["string","null"],"description":"What needs to change. Non-null only when `status` is `rejected`."},"submittedAt":{"type":"string","format":"date-time"},"reviewedAt":{"type":["string","null"],"format":"date-time","description":"When the registration was approved or rejected; null until then."}},"required":["phoneNumberId","status","kind","brand","campaign","feeCents","reason","submittedAt","reviewedAt"],"description":"A number's 10DLC registration — the brand, the campaign, and where the registration stands. Returned by Get and Submit 10DLC registration.","title":"TenDlcRegistration"},"Phone numbers_getTenDlcRegistration_Response_200":{"type":"object","properties":{"registration":{"$ref":"#/components/schemas/TenDlcRegistration"}},"title":"Phone numbers_getTenDlcRegistration_Response_200"},"ApiV1NumbersId10DlcPostRequestBodyContentApplicationJsonSchemaKind":{"type":"string","enum":["sole_proprietor","business"],"description":"Who is registering. A sole proprietor operates under their own name and has no EIN — lower throughput, quicker to approve. A business is a registered company with an EIN.","title":"ApiV1NumbersId10DlcPostRequestBodyContentApplicationJsonSchemaKind"},"Phone numbers_submitTenDlcRegistration_Response_200":{"type":"object","properties":{"registration":{"$ref":"#/components/schemas/TenDlcRegistration"}},"title":"Phone numbers_submitTenDlcRegistration_Response_200"},"ApiV1MessagesGetParametersDirection":{"type":"string","enum":["inbound","outbound"],"title":"ApiV1MessagesGetParametersDirection"},"MessageDirection":{"type":"string","enum":["inbound","outbound"],"title":"MessageDirection"},"MessageChannel":{"type":"string","enum":["sms","imessage","rcs","unknown"],"description":"The channel the message was delivered on. `sms` for SMS/call numbers. For iMessage numbers, inbound messages report the channel actually used — `imessage`, `rcs`, or `sms`. Outbound iMessage sends report `unknown`, because the iMessage channel does not confirm which channel was ultimately used.","title":"MessageChannel"},"MessageService":{"type":"string","enum":["imessage","rcs","sms"],"description":"For iMessage numbers, the confirmed delivery channel. Null for SMS/call numbers (where `channel` already says `sms`). Deprecated — prefer `channel`, which is authoritative.","title":"MessageService"},"MessageStatus":{"type":"string","enum":["sent","delivered","undelivered","failed","unknown","received"],"description":"Delivery status. Outbound SMS/call messages start at `sent` and reach a terminal `delivered`, `undelivered`, or `failed`. Outbound iMessage messages are `unknown` (the iMessage channel does not report delivery). Inbound messages are `received`.","title":"MessageStatus"},"MessageMedia":{"type":"object","properties":{"id":{"type":"string","description":"Public media ID — an unguessable 32-character token."},"url":{"type":"string","format":"uri","description":"Stable public URL serving the media (see Get public media). Safe to use directly as an image source; requires no authentication."},"contentType":{"type":"string","description":"MIME type of the media."},"originalUrl":{"type":["string","null"],"description":"The caller-supplied source URL on outbound messages. Null on inbound messages and when the media was uploaded directly as bytes — inbound media is always served from `url`."}},"description":"A media attachment on a message, hosted by Dial.","title":"MessageMedia"},"Message":{"type":"object","properties":{"id":{"type":"string"},"phoneNumberId":{"type":"string"},"from":{"type":"string","description":"Sender in E.164 format."},"to":{"type":"string","description":"Recipient in E.164 format."},"body":{"type":"string"},"direction":{"$ref":"#/components/schemas/MessageDirection"},"channel":{"$ref":"#/components/schemas/MessageChannel","description":"The channel the message was delivered on. `sms` for SMS/call numbers. For iMessage numbers, inbound messages report the channel actually used — `imessage`, `rcs`, or `sms`. Outbound iMessage sends report `unknown`, because the iMessage channel does not confirm which channel was ultimately used."},"service":{"$ref":"#/components/schemas/MessageService","description":"For iMessage numbers, the confirmed delivery channel. Null for SMS/call numbers (where `channel` already says `sms`). Deprecated — prefer `channel`, which is authoritative."},"status":{"$ref":"#/components/schemas/MessageStatus","description":"Delivery status. Outbound SMS/call messages start at `sent` and reach a terminal `delivered`, `undelivered`, or `failed`. Outbound iMessage messages are `unknown` (the iMessage channel does not report delivery). Inbound messages are `received`."},"statusError":{"type":["string","null"],"description":"A human-readable reason, present only when `status` is `undelivered` or `failed` (for example, when a carrier rejects the message). Null otherwise."},"media":{"type":"array","items":{"$ref":"#/components/schemas/MessageMedia"},"description":"Media attachments on the message, in send order. Empty for plain text messages."},"replyToId":{"type":["string","null"],"description":"ID of the message this one replies or reacts to. Set on messages created via Reply to a message, and on inbound threaded replies and reactions received on iMessage numbers. Null for ordinary messages, or when the target of an inbound reply isn't a message on your account."},"reaction":{"type":["string","null"],"description":"The reaction this message carries — a reaction name (`love`, `like`, `dislike`, `laugh`, `emphasize`, `question`) or an emoji — when the message is a reaction, sent or received. Null otherwise. A reaction delivered natively has an empty `body`; a reaction delivered as a regular message over SMS carries the emoji in `body` too."},"createdAt":{"type":"string","format":"date-time"}},"title":"Message"},"Messages_listMessages_Response_200":{"type":"object","properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}},"title":"Messages_listMessages_Response_200"},"Messages_sendMessage_Response_201":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}},"title":"Messages_sendMessage_Response_201"},"Messages_replyToMessage_Response_201":{"type":"object","properties":{"message":{"$ref":"#/components/schemas/Message"}},"title":"Messages_replyToMessage_Response_201"},"Messages_sendTyping_Response_200":{"type":"object","properties":{"ok":{"type":"boolean"}},"title":"Messages_sendTyping_Response_200"},"Messages_getPublicMedia_Response_200":{"type":"object","properties":{},"description":"Empty response body","title":"Messages_getPublicMedia_Response_200"},"ApiV1CallsGetParametersDirection":{"type":"string","enum":["inbound","outbound"],"title":"ApiV1CallsGetParametersDirection"},"CallDirection":{"type":"string","enum":["inbound","outbound"],"title":"CallDirection"},"CallVoiceRuntime":{"type":"string","enum":["managed","self-hosted-audio"],"description":"Which voice runtime served this call. `managed` — Dial's voice agent ran the call; this includes Self-Hosted's LLM mode, where Dial still handles the audio and only the LLM is yours. `self-hosted-audio` — the call's raw audio was streamed to your own server, so Dial never heard the conversation and produced no transcript or recording for it — `transcript` is always `null`. See the [Self-Hosted guide](/documentation/platform/self-hosted). Null on calls that predate this field.","title":"CallVoiceRuntime"},"Call":{"type":"object","properties":{"id":{"type":"string"},"phoneNumberId":{"type":"string"},"from":{"type":"string"},"to":{"type":"string"},"direction":{"$ref":"#/components/schemas/CallDirection"},"status":{"type":"string"},"duration":{"type":"integer","description":"Call duration in seconds."},"transcript":{"type":["string","null"],"description":"Transcript text, available after the call ends."},"voiceRuntime":{"$ref":"#/components/schemas/CallVoiceRuntime","description":"Which voice runtime served this call. `managed` — Dial's voice agent ran the call; this includes Self-Hosted's LLM mode, where Dial still handles the audio and only the LLM is yours. `self-hosted-audio` — the call's raw audio was streamed to your own server, so Dial never heard the conversation and produced no transcript or recording for it — `transcript` is always `null`. See the [Self-Hosted guide](/documentation/platform/self-hosted). Null on calls that predate this field."},"instruction":{"type":["string","null"],"description":"The system prompt the AI voice agent ran with for this call.\nFor outbound calls, this is the `outboundInstruction` passed to\n`POST /api/v1/calls`. For inbound calls, this is a snapshot of the\ndestination number's `inboundInstruction` at the moment the call was\nanswered — later edits to the number do not retroactively change it.\n"},"transferTo":{"type":["string","null"],"description":"Forward-to number (E.164) requested when the call was placed, or `null` when no transfer was requested. See the `transferTo` field on `POST /api/v1/calls`."},"transferredAt":{"type":["string","null"],"format":"date-time","description":"Timestamp of the moment the call was cold-transferred to `transferTo`, or `null` if no hand-off occurred."},"createdAt":{"type":"string","format":"date-time"}},"title":"Call"},"Calls_listCalls_Response_200":{"type":"object","properties":{"calls":{"type":"array","items":{"$ref":"#/components/schemas/Call"}}},"title":"Calls_listCalls_Response_200"},"ApiV1CallsPostRequestBodyContentApplicationJsonSchemaVoiceGender":{"type":"string","enum":["male","female"],"description":"Gender of the AI voice agent for this call. Optional — when omitted, the voice is female (the default for every language). Pass `male` or `female` to choose explicitly; the choice also tells the agent how to refer to itself in gendered languages.","title":"ApiV1CallsPostRequestBodyContentApplicationJsonSchemaVoiceGender"},"Calls_makeCall_Response_200":{"type":"object","properties":{"call":{"$ref":"#/components/schemas/Call"}},"title":"Calls_makeCall_Response_200"},"Calls_getCall_Response_200":{"type":"object","properties":{"call":{"$ref":"#/components/schemas/Call"}},"title":"Calls_getCall_Response_200"},"MessageReceivedEventObject":{"type":"string","enum":["event"],"title":"MessageReceivedEventObject"},"MessageReceivedEventType":{"type":"string","enum":["message.received"],"title":"MessageReceivedEventType"},"RelatedObjectType":{"type":"string","enum":["call","message"],"title":"RelatedObjectType"},"RelatedObject":{"type":"object","properties":{"id":{"type":"string"},"type":{"$ref":"#/components/schemas/RelatedObjectType"},"url":{"type":["string","null"]}},"required":["id","type","url"],"description":"A pointer to the REST resource this event concerns. `url` is the get-by-id path when one exists (calls), or null when it does not yet (messages have no get-by-id endpoint).","title":"RelatedObject"},"MessageReceivedEventDataChannel":{"type":"string","enum":["sms","imessage","rcs","unknown"],"description":"The channel the inbound message arrived on: `sms`, `imessage`, or `rcs` (`unknown` when the channel can't be determined).","title":"MessageReceivedEventDataChannel"},"MessageReceivedEventDataSource":{"type":"string","enum":["external","internal"],"description":"`external` — delivered by a real carrier via the inbound webhook.\n`internal` — synthesized by Dial itself (e.g. dashboard test\ntools); the row is real but no SMS was sent over the wire.\n","title":"MessageReceivedEventDataSource"},"MessageReceivedEventData":{"type":"object","properties":{"messageId":{"type":"string","description":"The Dial message id (matches `Message.id`)."},"from":{"type":"string"},"to":{"type":"string"},"channel":{"$ref":"#/components/schemas/MessageReceivedEventDataChannel","description":"The channel the inbound message arrived on: `sms`, `imessage`, or `rcs` (`unknown` when the channel can't be determined)."},"body":{"type":"string"},"source":{"$ref":"#/components/schemas/MessageReceivedEventDataSource","description":"`external` — delivered by a real carrier via the inbound webhook.\n`internal` — synthesized by Dial itself (e.g. dashboard test\ntools); the row is real but no SMS was sent over the wire.\n"}},"required":["messageId","from","to","channel","body","source"],"title":"MessageReceivedEventData"},"CallEndedEventObject":{"type":"string","enum":["event"],"title":"CallEndedEventObject"},"CallEndedEventType":{"type":"string","enum":["call.ended"],"title":"CallEndedEventType"},"CallEndedEventDataDirection":{"type":"string","enum":["inbound","outbound"],"title":"CallEndedEventDataDirection"},"CallEndedEventDataStatus":{"type":"string","enum":["completed","busy","no-answer","failed","canceled"],"description":"The call's terminal status.","title":"CallEndedEventDataStatus"},"CallEndedEventData":{"type":"object","properties":{"callId":{"type":"string","description":"The Dial call id (matches `Call.id`)."},"from":{"type":"string"},"to":{"type":"string"},"direction":{"$ref":"#/components/schemas/CallEndedEventDataDirection"},"durationSeconds":{"type":["integer","null"]},"status":{"$ref":"#/components/schemas/CallEndedEventDataStatus","description":"The call's terminal status."},"canceled":{"type":"boolean","description":"True if the call was cancelled before it ended (dashboard terminate or cancel API) — even if `status` is `completed`."},"transcriptAvailable":{"type":"boolean","description":"Whether a transcript exists. When true, a `call.transcribed` event follows once the transcript is processed."}},"required":["callId","from","to","direction","durationSeconds","status","canceled","transcriptAvailable"],"title":"CallEndedEventData"},"CallTranscribedEventObject":{"type":"string","enum":["event"],"title":"CallTranscribedEventObject"},"CallTranscribedEventType":{"type":"string","enum":["call.transcribed"],"title":"CallTranscribedEventType"},"CallTranscribedEventData":{"type":"object","properties":{"callId":{"type":"string","description":"The Dial call id (matches `Call.id`)."}},"required":["callId"],"title":"CallTranscribedEventData"},"Event":{"oneOf":[{"type":"object","properties":{"type":{"$ref":"#/components/schemas/MessageReceivedEventType"},"id":{"type":"string","description":"Stable event id (also the webhook X-Dial-Event-ID)."},"object":{"$ref":"#/components/schemas/MessageReceivedEventObject"},"version":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"relatedObject":{"$ref":"#/components/schemas/RelatedObject"},"data":{"$ref":"#/components/schemas/MessageReceivedEventData"}},"required":["type","id","object","version","createdAt","relatedObject","data"],"description":"message.received variant"},{"type":"object","properties":{"type":{"$ref":"#/components/schemas/CallEndedEventType"},"id":{"type":"string","description":"Stable event id (also the webhook X-Dial-Event-ID)."},"object":{"$ref":"#/components/schemas/CallEndedEventObject"},"version":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"relatedObject":{"$ref":"#/components/schemas/RelatedObject"},"data":{"$ref":"#/components/schemas/CallEndedEventData"}},"required":["type","id","object","version","createdAt","relatedObject","data"],"description":"call.ended variant"},{"type":"object","properties":{"type":{"$ref":"#/components/schemas/CallTranscribedEventType"},"id":{"type":"string","description":"Stable event id (also the webhook X-Dial-Event-ID)."},"object":{"$ref":"#/components/schemas/CallTranscribedEventObject"},"version":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"},"relatedObject":{"$ref":"#/components/schemas/RelatedObject"},"data":{"$ref":"#/components/schemas/CallTranscribedEventData"}},"required":["type","id","object","version","createdAt","relatedObject","data"],"description":"call.transcribed variant"}],"discriminator":{"propertyName":"type"},"description":"An account event. Every event shares one envelope — `id`, `object`\n(\"event\"), `type`, `version`, `createdAt`, `relatedObject` — and a\n`data` payload whose shape the `type` selects. Field names are camelCase.\nToday: `message.received` (an inbound SMS), `call.ended` (a call\nfinished), and `call.transcribed` (a call's transcript is ready).\n","title":"Event"},"Events_waitForEvent_Response_200":{"type":"object","properties":{"event":{"$ref":"#/components/schemas/Event"}},"title":"Events_waitForEvent_Response_200"},"SubscribeToken":{"type":"object","properties":{"subscribeKey":{"type":"string"},"channel":{"type":"string"},"token":{"type":"string"},"ttlSeconds":{"type":"integer"}},"title":"SubscribeToken"},"WebhookSubscription":{"type":"object","properties":{"id":{"type":"string"},"targetUrl":{"type":"string","format":"uri"},"eventTypes":{"type":"array","items":{"type":"string"},"description":"[\"*\"] for all events, or explicit types."},"secretMasked":{"type":"string","description":"Masked secret. Full value via the create response or the /secret endpoint."},"lastPingSucceededAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"}},"title":"WebhookSubscription"},"Webhooks_listWebhooks_Response_200":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscription"}}},"title":"Webhooks_listWebhooks_Response_200"},"CreateWebhookRequestEventTypesItems":{"type":"string","enum":["message.received","call.status_changed","call.ended","call.transcribed","*"],"title":"CreateWebhookRequestEventTypesItems"},"CreateWebhookRequest":{"type":"object","properties":{"targetUrl":{"type":"string","format":"uri","description":"HTTPS only; not a private/loopback host."},"eventTypes":{"type":"array","items":{"$ref":"#/components/schemas/CreateWebhookRequestEventTypesItems"}}},"required":["targetUrl","eventTypes"],"title":"CreateWebhookRequest"},"Webhooks_createWebhook_Response_201":{"type":"object","properties":{"webhook":{"$ref":"#/components/schemas/WebhookSubscription"}},"title":"Webhooks_createWebhook_Response_201"},"Webhooks_getWebhook_Response_200":{"type":"object","properties":{"webhook":{"$ref":"#/components/schemas/WebhookSubscription"}},"title":"Webhooks_getWebhook_Response_200"},"Webhooks_revealWebhookSecret_Response_200":{"type":"object","properties":{"secret":{"type":"string"}},"title":"Webhooks_revealWebhookSecret_Response_200"},"Webhooks_fireWebhookPing_Response_202":{"type":"object","properties":{"eventId":{"type":"string"}},"title":"Webhooks_fireWebhookPing_Response_202"},"ContextMcpAuthMode":{"type":"string","enum":["none","static","oauth"],"description":"How the server authenticates, detected on connect. `none`: unauthenticated. `static`: fixed headers you supplied. `oauth`: OAuth 2.1, with tokens managed by Dial.","title":"ContextMcpAuthMode"},"ContextMcpStatus":{"type":"string","enum":["pending_auth","connected","error"],"description":"`pending_auth`: awaiting OAuth consent. `connected`: tools are wired to the agent. `error`: connection or token refresh failed (see `lastError`).","title":"ContextMcpStatus"},"ContextMcp":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string","format":"uri","description":"The MCP server URL."},"authMode":{"$ref":"#/components/schemas/ContextMcpAuthMode","description":"How the server authenticates, detected on connect. `none`: unauthenticated. `static`: fixed headers you supplied. `oauth`: OAuth 2.1, with tokens managed by Dial."},"status":{"$ref":"#/components/schemas/ContextMcpStatus","description":"`pending_auth`: awaiting OAuth consent. `connected`: tools are wired to the agent. `error`: connection or token refresh failed (see `lastError`)."},"toolCount":{"type":"integer","description":"Number of MCP tools wired into the agent."},"timeoutMs":{"type":["integer","null"],"description":"Connection timeout in ms; null uses the default (120000)."},"headersMasked":{"type":"object","additionalProperties":{"type":"string"},"description":"Extra static request headers, with values masked. The OAuth-managed `Authorization` header is never returned."},"queryParams":{"type":"object","additionalProperties":{"type":"string"},"description":"Query parameters appended to the connection URL, with values masked."},"lastError":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}},"title":"ContextMcp"},"Context MCP_listContextMcps_Response_200":{"type":"object","properties":{"contextMcps":{"type":"array","items":{"$ref":"#/components/schemas/ContextMcp"}}},"title":"Context MCP_listContextMcps_Response_200"},"CreateContextMcpRequest":{"type":"object","properties":{"name":{"type":"string","description":"Display name for the connection."},"url":{"type":"string","format":"uri","description":"HTTPS MCP server URL (http allowed only for localhost in development)."},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Extra static headers to send on every connection. For an OAuth-protected server, any `Authorization` header you set is ignored — Dial manages it."},"queryParams":{"type":"object","additionalProperties":{"type":"string"},"description":"Query parameters to append to the connection URL."},"timeoutMs":{"type":"integer","description":"Connection timeout in ms. Defaults to 120000."}},"required":["name","url"],"title":"CreateContextMcpRequest"},"Context MCP_createContextMcp_Response_201":{"type":"object","properties":{"contextMcp":{"$ref":"#/components/schemas/ContextMcp"},"authorizationUrl":{"type":["string","null"],"format":"uri","description":"Present only for OAuth-protected servers. Open in a browser to grant consent and finish connecting."}},"required":["contextMcp"],"title":"Context MCP_createContextMcp_Response_201"},"Context MCP_getContextMcp_Response_200":{"type":"object","properties":{"contextMcp":{"$ref":"#/components/schemas/ContextMcp"}},"title":"Context MCP_getContextMcp_Response_200"},"SelfHostedAccess":{"type":"string","enum":["none","pending","granted","denied"],"description":"Whether this account may configure Self-Hosted mode. Self-Hosted lets a third-party server drive live calls, so accounts are approved before they can point calls at one.\n\n`none` — no access and no request on file; submit one with the `request_access` action. `pending` — a request is awaiting review. `granted` — you may `save` and `activate` freely. `denied` — the request was reviewed and turned down; `accessRequest.reviewNote` says why.\n\nAccounts that already had Self-Hosted before approval was introduced are `granted` — nothing changed for them.","title":"SelfHostedAccess"},"SelfHostedAccessRequestStatus":{"type":"string","enum":["pending","approved","denied"],"description":"Where this particular request landed.","title":"SelfHostedAccessRequestStatus"},"SelfHostedAccessRequest":{"type":"object","properties":{"useCase":{"type":"string","description":"What you told us you plan to build on Self-Hosted mode."},"companyUrl":{"type":["string","null"],"description":"The company or project URL you supplied, if any."},"status":{"$ref":"#/components/schemas/SelfHostedAccessRequestStatus","description":"Where this particular request landed."},"submittedAt":{"type":"string","format":"date-time"},"reviewedAt":{"type":["string","null"],"format":"date-time"},"reviewNote":{"type":["string","null"],"description":"The reviewer's note — the reason shown when a request is denied."}},"required":["useCase","status","submittedAt"],"description":"An access request you submitted, and how it was reviewed.","title":"SelfHostedAccessRequest"},"SelfHostedConfigActiveMode":{"type":"string","enum":["llm","audio"],"description":"Which mode drives calls when `enabled`: `\"llm\"` (Dial runs voice; your server drives the conversation in text) or `\"audio\"` (Dial pipes the raw call audio to your server, full duplex).","title":"SelfHostedConfigActiveMode"},"SelfHostedLlmConfigType":{"type":"string","enum":["llm"],"title":"SelfHostedLlmConfigType"},"SelfHostedLlmConfig":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/SelfHostedLlmConfigType"},"wsUrl":{"type":"string","format":"uri","description":"The `wss://` URL of the server Dial connects to for each call."},"urlKey":{"type":"string","description":"The unguessable per-account path segment Dial uses when connecting. Stable once the URL is first set."},"secretMasked":{"type":"string","description":"A masked preview of the signing secret (`shs_••••••••<last4>`). Copy the full value from `GET /api/v1/self-hosted/secret?mode=llm`."}},"required":["type","wsUrl","urlKey","secretMasked"],"description":"The LLM variant — Dial handles telephony, speech-to-text, and text-to-speech; your server answers with the agent's text turns over the Self-hosted LLM protocol.","title":"SelfHostedLlmConfig"},"SelfHostedAudioConfigType":{"type":"string","enum":["audio"],"title":"SelfHostedAudioConfigType"},"SelfHostedAudioFormat":{"type":"string","enum":["mulaw_8000","alaw_8000","l16_8000","l16_16000","l16_24000"],"description":"An audio-pipe format: G.711 μ-law/A-law at 8 kHz, or 16-bit little-endian linear PCM at 8/16/24 kHz. Defaults to `mulaw_8000`.","title":"SelfHostedAudioFormat"},"SelfHostedAudioConfig":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/SelfHostedAudioConfigType"},"wsUrl":{"type":"string","format":"uri","description":"The `wss://` URL of the server Dial connects to for each call."},"secretMasked":{"type":"string","description":"A masked preview of the signing secret (`shs_••••••••<last4>`). Copy the full value from `GET /api/v1/self-hosted/secret?mode=audio`."},"audioInboundFormat":{"$ref":"#/components/schemas/SelfHostedAudioFormat"},"audioOutboundFormat":{"$ref":"#/components/schemas/SelfHostedAudioFormat"}},"required":["type","wsUrl","secretMasked","audioInboundFormat","audioOutboundFormat"],"description":"The audio variant — Dial pipes the raw call audio to your server, full duplex, over the Self-hosted audio protocol. Your server brings the entire voice stack.","title":"SelfHostedAudioConfig"},"SelfHostedConfig":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether Self-Hosted mode is currently driving the account's calls."},"access":{"$ref":"#/components/schemas/SelfHostedAccess"},"accessRequest":{"oneOf":[{"$ref":"#/components/schemas/SelfHostedAccessRequest"},{"type":"null"}],"description":"Your most recent access request, or `null` if you have never submitted one (including accounts that already have access without asking)."},"activeMode":{"$ref":"#/components/schemas/SelfHostedConfigActiveMode","description":"Which mode drives calls when `enabled`: `\"llm\"` (Dial runs voice; your server drives the conversation in text) or `\"audio\"` (Dial pipes the raw call audio to your server, full duplex)."},"llm":{"oneOf":[{"$ref":"#/components/schemas/SelfHostedLlmConfig"},{"type":"null"}],"description":"The LLM-mode config, or `null` until that mode is configured."},"audio":{"oneOf":[{"$ref":"#/components/schemas/SelfHostedAudioConfig"},{"type":"null"}],"description":"The audio-mode config, or `null` until that mode is configured."}},"required":["enabled","access","activeMode"],"description":"The account's Self-Hosted configuration. Each mode owns an **independent** config (`llm` and `audio`), both readable at once; `activeMode` is which one drives calls when `enabled`.","title":"SelfHostedConfig"},"SelfHostedSaveActionAction":{"type":"string","enum":["save"],"title":"SelfHostedSaveActionAction"},"SelfHostedLlmConfigInputType":{"type":"string","enum":["llm"],"title":"SelfHostedLlmConfigInputType"},"SelfHostedAudioConfigInputType":{"type":"string","enum":["audio"],"title":"SelfHostedAudioConfigInputType"},"SelfHostedConfigInput":{"oneOf":[{"type":"object","properties":{"type":{"$ref":"#/components/schemas/SelfHostedLlmConfigInputType"},"wsUrl":{"type":"string","format":"uri","description":"The `wss://` URL Dial connects to for each call."}},"required":["type","wsUrl"],"description":"llm variant"},{"type":"object","properties":{"type":{"$ref":"#/components/schemas/SelfHostedAudioConfigInputType"},"wsUrl":{"type":"string","format":"uri","description":"The `wss://` URL Dial connects to for each call."},"audioInboundFormat":{"$ref":"#/components/schemas/SelfHostedAudioFormat"},"audioOutboundFormat":{"$ref":"#/components/schemas/SelfHostedAudioFormat"}},"required":["type","wsUrl"],"description":"audio variant"}],"discriminator":{"propertyName":"type"},"description":"A mode's config, tagged by `type` (the mode).","title":"SelfHostedConfigInput"},"SelfHostedActivateActionAction":{"type":"string","enum":["activate"],"title":"SelfHostedActivateActionAction"},"SelfHostedDisableActionAction":{"type":"string","enum":["disable"],"title":"SelfHostedDisableActionAction"},"SelfHostedRequestAccessActionAction":{"type":"string","enum":["request_access"],"title":"SelfHostedRequestAccessActionAction"},"SelfHostedAction":{"oneOf":[{"type":"object","properties":{"action":{"$ref":"#/components/schemas/SelfHostedSaveActionAction"},"config":{"$ref":"#/components/schemas/SelfHostedConfigInput"}},"required":["action","config"],"description":"Persist a mode's config. Does **not** change which mode is active or whether Self-Hosted is on. The signing secret (and, for `llm`, the `urlKey`) is minted the first time a mode is saved."},{"type":"object","properties":{"action":{"$ref":"#/components/schemas/SelfHostedActivateActionAction"},"config":{"$ref":"#/components/schemas/SelfHostedConfigInput"}},"required":["action","config"],"description":"A `save` (same `config` body) that **also** enables Self-Hosted and routes calls through `config.type` — enable, or switch from the other mode. Affects new calls only."},{"type":"object","properties":{"action":{"$ref":"#/components/schemas/SelfHostedDisableActionAction"}},"required":["action"],"description":"Turn Self-Hosted off. Both modes' configs are kept."},{"type":"object","properties":{"action":{"$ref":"#/components/schemas/SelfHostedRequestAccessActionAction"},"useCase":{"type":"string","minLength":30,"maxLength":2000,"description":"What you plan to build: who you'll be calling or messaging, and why your own server needs to drive the conversation. Reviewed by a human, so specifics get approved faster than a one-liner."},"companyUrl":{"type":"string","format":"uri","description":"Your company or project URL. Optional, but it speeds up review."}},"required":["action","useCase"],"description":"Ask for approval to use Self-Hosted mode. Valid only while `access` is `none` or `denied` — a `pending` request must be reviewed first, and a `granted` account has nothing to ask for. Returns the configuration with `access` moved to `pending`."}],"discriminator":{"propertyName":"action"},"description":"One Self-Hosted state transition, tagged by `action`: `save` (persist a mode's config), `activate` (a `save` that also enables/switches to that mode), `disable` (turn off), or `request_access` (ask for approval to use Self-Hosted at all).","title":"SelfHostedAction"},"ApiV1SelfHostedSecretGetParametersMode":{"type":"string","enum":["llm","audio"],"title":"ApiV1SelfHostedSecretGetParametersMode"},"Self-Hosted_revealSelfHostedSecret_Response_200":{"type":"object","properties":{"secret":{"type":"string","description":"The HMAC signing secret used to verify `X-Dial-Signature`."}},"required":["secret"],"title":"Self-Hosted_revealSelfHostedSecret_Response_200"},"AccountMemberRole":{"type":"string","enum":["owner","member"],"title":"AccountMemberRole"},"AccountMemberStatus":{"type":"string","enum":["pending","active"],"description":"`pending` until the invitation is accepted. A pending member has no API key and no access. The owner is always `active`.","title":"AccountMemberStatus"},"AccountMember":{"type":"object","properties":{"id":{"type":["string","null"],"description":"The membership id, used to remove the member. Always `null` for the owner, who is the account's own email address and cannot be removed."},"email":{"type":"string","format":"email"},"role":{"$ref":"#/components/schemas/AccountMemberRole"},"status":{"$ref":"#/components/schemas/AccountMemberStatus","description":"`pending` until the invitation is accepted. A pending member has no API key and no access. The owner is always `active`."},"invitedByEmail":{"type":["string","null"],"format":"email","description":"Who sent the invitation. Null for the owner."},"invitedAt":{"type":["string","null"],"format":"date-time"},"acceptedAt":{"type":["string","null"],"format":"date-time","description":"When the invitation was accepted, or null while pending."},"lastActiveAt":{"type":["string","null"],"format":"date-time","description":"When this member's API key was last used, or null if never used."}},"required":["id","email","role","status"],"title":"AccountMember"},"Members_listMembers_Response_200":{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/AccountMember"}}},"required":["members"],"title":"Members_listMembers_Response_200"},"InviteMemberRequest":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"The address to invite. The invitation is locked to it."}},"required":["email"],"title":"InviteMemberRequest"},"Members_inviteMember_Response_201":{"type":"object","properties":{"member":{"$ref":"#/components/schemas/AccountMember"}},"required":["member"],"title":"Members_inviteMember_Response_201"},"MemberInvite":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"The address this invitation was issued to."},"invitedByEmail":{"type":"string","format":"email","description":"Who sent the invitation."},"expiresAt":{"type":"string","format":"date-time"}},"required":["email","invitedByEmail","expiresAt"],"title":"MemberInvite"},"Members_getMemberInvite_Response_200":{"type":"object","properties":{"invite":{"$ref":"#/components/schemas/MemberInvite"}},"required":["invite"],"title":"Members_getMemberInvite_Response_200"},"AcceptMemberInviteRequest":{"type":"object","properties":{"verificationId":{"type":"string","description":"The `verificationId` returned by Create an account for the invited address."},"code":{"type":"string","minLength":6,"maxLength":6,"description":"The 6-digit code emailed to the invited address."}},"required":["verificationId","code"],"title":"AcceptMemberInviteRequest"},"Members_acceptMemberInvite_Response_200":{"type":"object","properties":{"accountId":{"type":"string","description":"The account the caller has joined."},"apiKey":{"type":"string","description":"The member's own API key. Shown once."},"member":{"$ref":"#/components/schemas/AccountMember"}},"required":["accountId","apiKey","member"],"title":"Members_acceptMemberInvite_Response_200"},"MembershipRole":{"type":"string","enum":["owner","member"],"description":"Your role in that account.","title":"MembershipRole"},"Membership":{"type":"object","properties":{"accountId":{"type":"string"},"accountEmail":{"type":"string","format":"email","description":"The owner's address, which identifies the account."},"role":{"$ref":"#/components/schemas/MembershipRole","description":"Your role in that account."}},"required":["accountId","accountEmail","role"],"title":"Membership"},"Members_listMemberships_Response_200":{"type":"object","properties":{"memberships":{"type":"array","items":{"$ref":"#/components/schemas/Membership"}}},"required":["memberships"],"title":"Members_listMemberships_Response_200"},"Members_getMembershipKey_Response_200":{"type":"object","properties":{"accountId":{"type":"string"},"apiKey":{"type":"string"}},"required":["accountId","apiKey"],"title":"Members_getMembershipKey_Response_200"}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your Dial API key, sent as `Authorization: Bearer sk_live_...`"}}}}