Business API
Connect text and media messaging, customer-initiated calls, your business profile and signed webhooks.
Owner setup is required. Verify a business number, create a Live or Test key, configure your webhook and invite a consenting Natter user to start the conversation.
The Natter Business API is free for now. It is intended to become a paid service at a planned price of €0.001 per message (one tenth of a cent), set only to cover running costs. We will confirm prices at least 30 days before any charges begin, and nothing you use before then will be charged.
Test: Separate environmentLive: Free for now
Set up your business account or read the integration reference below.
Operation reference
These routes require a key belonging to your business and the selected environment. Select an operation to see its fields and responses. The full schema is available in the hosted OpenAPI download.
GET/v1/business/{environment}/accountRead the authenticated Business account
Required permission: Account info (account:view)
Reads current account authority. Account reads remain available while paused, suspended or banned. This grants no permission to send.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
countryRequired |
string | null |
ISO 3166-1 alpha-2 country of the saved public profile address, or null when none is saved. |
environmentsRequired |
array of "sandbox" | "live" |
Environments this business has commissioned (live, sandbox). A key still works only in its own environment. Maximum items: 2. |
featuresRequired |
object |
See the full schema for validation rules. |
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
nameRequired |
string |
See the full schema for validation rules. |
statusRequired |
"active" | "throttled" | "paused" |
"suspended" | "banned"
|
See the full schema for validation rules. |
status_reasonOptional |
string |
Optional current reason; omitted when empty. |
sender_number_idRequired |
string | null (uuid) |
Opaque ID of the selected verified sender for this environment, not the phone number. |
capabilitiesRequired |
array of "text" | "image" |
"document" | "audio" | "video" |
"voice_calls" | "video_calls"
|
Always includes text. Additional values reflect this environment's currently enabled feature settings: images -> image, files -> document, voice_messages -> audio, videos -> video, voice_calls and video_calls. This reports enabled features, not the caller key's permissions or current contact permission; calls still require Messaging & calls. Minimum items: 1. Maximum items: 7. |
verifiedRequired |
null | object |
Read-only. An object only after a Masons Mail admin approved this business as verified; null otherwise. Automated checks alone never set it, and no API call changes it. |
customer_linkRequired |
object |
D423: the customer link of this environment (Business API 0.10.0). |
Read the authenticated Business account: response fields: features
| Field | Value | Details |
|---|---|---|
imagesRequired |
boolean |
Current permission. Defaults to false. |
videosRequired |
boolean |
Current permission. Defaults to false. |
filesRequired |
boolean |
Current permission. Defaults to false. |
voice_messagesRequired |
boolean |
Current permission. Defaults to false. |
voice_callsRequired |
boolean |
Current permission. Defaults to false. |
video_callsRequired |
boolean |
Current permission. Defaults to false. |
Read the authenticated Business account: response fields: verified
Variant 1
The verified-business badge (Natter D405): when Masons Mail approved it and the plain facts it confirmed. Natter apps that read presentation level 8 receive the same value.
| Field | Value | Details |
|---|---|---|
atRequired |
string (date-time) |
UTC approval time, second precision. |
checksRequired |
array of string |
company_registration: Masons Mail confirmed the company's registration. domain:<domain>: the business proved control of that lower-case domain with a DNS TXT record. Minimum items: 1. Maximum items: 16. |
Read the authenticated Business account: response fields: customer_link
D423: the customer link of this environment (Business API 0.10.0).
| Field | Value | Details |
|---|---|---|
urlRequired |
string (uri) |
The permanent customer link for this environment: https://natterapp.com/business/{business_id}/{environment}. It never changes. Share it, print it as a QR code or put it on your website: on a phone with Natter it opens the conversation with you (after the customer reviews and accepts); without Natter it shows your display name, visible description and picture with the way to the App Store or Google Play. |
activeRequired |
boolean |
Whether the link opens a conversation right now: the business is active or throttled, its owner authority is current, and this environment has a sender with a usable selected number. False while setup is incomplete, paused or suspended; the link itself stays the same. |
Responses
200- Current account metadata.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/profileRead your public Business profile
Required permission: Profile & appearance (profile:manage)
Returns the customer-facing display name, description, support contacts, address and links. The registered account name remains separate. This read never returns the private operational contact or asserts company verification. Keys require Profile & appearance (profile:manage) in their own environment. Live, Test and the owner portal share this saved profile and revision. The flat public body is unchanged; use the strong ETag header for a conditional PUT. Historical saved records remain readable even if they exceed the new write aggregate limit.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
display_nameRequired |
string |
At most 256 UTF-8 bytes. Separate from the registered business name. Minimum characters: 2. Maximum characters: 128. |
descriptionRequired |
string |
Maximum characters: 1,000. |
websiteRequired |
string |
Empty or an HTTPS URL without username or password. Maximum characters: 2,048. |
support_emailRequired |
string |
Maximum characters: 254. |
support_phoneRequired |
string |
Public support contact only; this does not prove number ownership or enable messaging. |
addressRequired |
object |
See the full schema for validation rules. |
linksRequired |
array of object |
Maximum items: 5. |
Read your public Business profile: response fields: address
| Field | Value | Details |
|---|---|---|
streetRequired |
string |
Maximum characters: 200. |
cityRequired |
string |
Maximum characters: 100. |
postal_codeRequired |
string |
Maximum characters: 32. |
countryRequired |
string |
See the full schema for validation rules. |
Read your public Business profile: response fields: links[]
| Field | Value | Details |
|---|---|---|
labelRequired |
string |
Minimum characters: 1. Maximum characters: 40. |
urlRequired |
string |
Empty or an HTTPS URL without username or password. Minimum characters: 1. Maximum characters: 2,048. |
Response headers
ETag- Opaque strong validator bound to this business and profile revision. Keep the quotes when sending If-Match.
Responses
200- Only public profile fields. Optional fields are empty strings or arrays.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
PUT/v1/business/{environment}/profileUpdate your saved public Business profile
Required permission: Profile & appearance (profile:manage)
Requires a full-access key or a restricted key with Profile & appearance (profile:manage). Live, Test and the owner portal edit the same saved profile. Send all public fields with the exact strong ETag returned by GET in If-Match; no weak, wildcard, multiple or wrong-business validators. Never include the private operational contact: it is rejected and cannot be changed by this API. Missing If-Match returns 428; stale revisions return 412. Read and reconcile before retrying after a conflict or lost response. Key, owner and selected verified sender authority are checked again in the transaction. JSON body limit: 32768 bytes. New public objects must serialize to at most 15360 UTF-8 bytes using compact JSON.stringify-compatible escaping, within the existing 16384-byte database record bound. Text bounds count UTF-16 code units; display name also has a 256 UTF-8-byte limit. HTTPS URLs require a nonempty host, no credentials, whitespace or backslashes, a valid explicit port 1-65535 if supplied, and at most 2048 UTF-8 bytes. All declared fields, address fields and link fields are required; optional values are empty strings or arrays. Nulls, duplicate or unknown fields and invalid Unicode are rejected.
If-Match (header, required): Exact strong ETag from this
business's last profile GET, including quotes. See the full schema for
validation rules.
JSON request
Send one JSON object with only the fields below.
| Field | Value | Details |
|---|---|---|
display_nameRequired |
string |
At most 256 UTF-8 bytes. Separate from the registered business name. Minimum characters: 2. Maximum characters: 128. |
descriptionRequired |
string |
Maximum characters: 1,000. |
websiteRequired |
string |
Empty or an HTTPS URL without username or password. Maximum characters: 2,048. |
support_emailRequired |
string |
Maximum characters: 254. |
support_phoneRequired |
string |
Public support contact only; this does not prove number ownership or enable messaging. |
addressRequired |
object |
See the full schema for validation rules. |
linksRequired |
array of object |
Maximum items: 5. |
Update your saved public Business profile: request fields: address
| Field | Value | Details |
|---|---|---|
streetRequired |
string |
Maximum characters: 200. |
cityRequired |
string |
Maximum characters: 100. |
postal_codeRequired |
string |
Maximum characters: 32. |
countryRequired |
string |
See the full schema for validation rules. |
Update your saved public Business profile: request fields: links[]
| Field | Value | Details |
|---|---|---|
labelRequired |
string |
Minimum characters: 1. Maximum characters: 40. |
urlRequired |
string |
Empty or an HTTPS URL without username or password. Minimum characters: 1. Maximum characters: 2,048. |
Successful response
| Field | Value | Details |
|---|---|---|
display_nameRequired |
string |
At most 256 UTF-8 bytes. Separate from the registered business name. Minimum characters: 2. Maximum characters: 128. |
descriptionRequired |
string |
Maximum characters: 1,000. |
websiteRequired |
string |
Empty or an HTTPS URL without username or password. Maximum characters: 2,048. |
support_emailRequired |
string |
Maximum characters: 254. |
support_phoneRequired |
string |
Public support contact only; this does not prove number ownership or enable messaging. |
addressRequired |
object |
See the full schema for validation rules. |
linksRequired |
array of object |
Maximum items: 5. |
Update your saved public Business profile: response fields: address
| Field | Value | Details |
|---|---|---|
streetRequired |
string |
Maximum characters: 200. |
cityRequired |
string |
Maximum characters: 100. |
postal_codeRequired |
string |
Maximum characters: 32. |
countryRequired |
string |
See the full schema for validation rules. |
Update your saved public Business profile: response fields: links[]
| Field | Value | Details |
|---|---|---|
labelRequired |
string |
Minimum characters: 1. Maximum characters: 40. |
urlRequired |
string |
Empty or an HTTPS URL without username or password. Minimum characters: 1. Maximum characters: 2,048. |
Response headers
ETag- New strong validator for the saved revision.
Responses
200- Saved public profile. Private operational contact remains unchanged.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks Profile & appearance (profile:manage), belongs to another environment, or current owner or selected sender authority no longer permits access.
405- The method is not supported for this exact route.
412- The profile changed after the supplied ETag. Read, reconcile and retry with the new validator.
422- The display name uses a reserved name (Masons Mail, Natter, Idelego, an authority such as police or a tax agency, or official support for one of them) or a banned word. Nothing was saved or charged. Masons Mail can allow a name for a business on request.
428- If-Match is required. Read the profile to obtain its ETag.
429- business_save_limit: the business has used today’s saves. Per UTC calendar day a business may make 5 text saves (profile, colours, features and visibility together) and 2 picture uploads (avatar, chat wallpaper, profile background), 1 once it has more than 100,000 customers. Live, Test and the owner portal share these budgets. Nothing was written. Wait Retry-After seconds (until the next UTC midnight), then send the same request again.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
POST/v1/business/{environment}/messagesQueue a text or media reply
Required permission: Messaging & calls (messaging:manage)
Requires the current customer relationship in this environment, an eligible customer message less than 183 elapsed days (4392 hours) ago, an active/throttled business and current key/endpoint/account/member authority. Merely opening a chat, receipts and background updates do not renew permission. Opt-out and independent blocks prevent a new send. The required runtime admission callback additionally controls commissioning and sandbox recipients; setup is still required for each environment. First acceptance creates sealed queued work, not a sent/delivered/read claim. Matching retries require the same API key and unchanged recipient/type/text/media_id within the 24-hour idempotency window and still-current permission; HTTP 200 returns the same operation ID with current state. A different API key cannot adopt old work. Retry never extends deadlines. A retained expired key returns idempotency_expired; after bounded idempotency cleanup the server can no longer deduplicate it, so never rely on replay protection beyond 24 hours. Media requests reference a ready upload from POST media; they do not contain file bytes or URLs. The upload must belong to the same key and contact, and its feature must still be enabled. Both variants return the same receipt and sent/failed/read status contract; no delivered status is emitted.
Idempotency-Key (header, required): Exactly one header. 1–200
visible ASCII characters (0x21–0x7e); no spaces. Scoped to this
Business/environment. Use a new value for a distinct send. Minimum
characters: 1. Maximum characters: 200.
JSON request
At most 32,768 UTF-8 bytes for the complete JSON body; unknown/duplicate/case-aliased fields are invalid.
Text message
| Field | Value | Details |
|---|---|---|
toRequired |
string |
Use a scoped user_ref received from this Business's inbound message event. Arbitrary phone numbers are not resolved. Minimum characters: 10. Maximum characters: 105. |
typeRequired |
"text" |
See the full schema for validation rules. |
textRequired |
string |
Nonempty valid Unicode text, at most 4,096 UTF-16 code units. An astral character counts as two units. JSON Schema maxLength alone cannot express this exact runtime rule. Minimum characters: 1. Maximum characters: 4,096. Maximum UTF-16 code units: 4,096. |
Media message
| Field | Value | Details |
|---|---|---|
toRequired |
string |
Use a scoped user_ref received from this Business's inbound message event. Arbitrary phone numbers are not resolved. Minimum characters: 10. Maximum characters: 105. |
typeRequired |
"image" | "document" | "audio" |
"video"
|
Must match the uploaded media type and an enabled feature. |
media_idRequired |
string (uuid) |
Ready outbound upload created by this exact API key for this exact business, environment and contact. Temporary; use before expires_at. |
textOptional |
string |
Optional caption, at most 4,096 UTF-16 code units. Omit or send an empty string for no caption. Maximum characters: 4,096. Maximum UTF-16 code units: 4,096. |
Successful response
| Field | Value | Details |
|---|---|---|
message_idRequired |
string (uuid) |
See the full schema for validation rules. |
conversation_idRequired |
string |
Opaque Business/environment conversation reference. This is not the internal relay conversation ID. Minimum characters: 1. |
user_refRequired |
string |
Opaque reference scoped to this Business/environment/customer incarnation. Operation contact references are bounded to 256 UTF-8 bytes; the current POST recipient parser accepts only 1-96 ASCII identifier characters. References created by the private customer-entry primitive fit that parser. Minimum characters: 1. Maximum characters: 256. Maximum UTF-8 bytes: 256. |
stateRequired |
"queued" | "accepted" |
"failed"
|
queued includes private preparation. accepted means the relay accepted the encrypted send. failed includes cancelled work. |
statusOptional |
"sent" | "read" | "failed"
|
Omitted until a Business status exists. sent means relay acceptance, read means authenticated customer read. There is no Business delivered status. |
created_atRequired |
string (date-time) |
See the full schema for validation rules. |
idempotency_expires_atRequired |
string (date-time) |
Original created_at plus 24 hours, unchanged by replay; receipt metadata may remain for up to 30 days. |
Responses
200- Matching idempotent replay; original operation identity and deadlines are preserved.
202- New request durably queued. It has not yet been accepted by the relay.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
402- payment_required: the Live price is positive and this business's prepaid credit or invoice credit limit cannot cover the send.
403- Permission/environment mismatch, paused or suspended/banned business, or current customer relationship/endpoint permission prevents this send. Banned sending uses business_suspended.
405- The method is not supported for this exact route.
409- The recipient/text differs under a retained request key, or its retained 24-hour replay deadline has expired.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/messages/{message_id}Read one scoped operation receipt
Required permission: Messaging & calls (messaging:manage)
Returns content-free state for this Business/environment, using a current key with Messaging & calls (messaging:manage, or an older key's converted messages:send). It need not be the original sending key. No message body, phone number, hosted device identity, history or cryptographic state is returned. Receipts remain available for at most 30 days from creation; removal of required account/contact parents can shorten availability. Unknown, expired and foreign operations share the same 404.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
message_idRequired |
string (uuid) |
See the full schema for validation rules. |
conversation_idRequired |
string |
Opaque Business/environment conversation reference. This is not the internal relay conversation ID. Minimum characters: 1. |
user_refRequired |
string |
Opaque reference scoped to this Business/environment/customer incarnation. Operation contact references are bounded to 256 UTF-8 bytes; the current POST recipient parser accepts only 1-96 ASCII identifier characters. References created by the private customer-entry primitive fit that parser. Minimum characters: 1. Maximum characters: 256. Maximum UTF-8 bytes: 256. |
stateRequired |
"queued" | "accepted" |
"failed"
|
queued includes private preparation. accepted means the relay accepted the encrypted send. failed includes cancelled work. |
statusOptional |
"sent" | "read" | "failed"
|
Omitted until a Business status exists. sent means relay acceptance, read means authenticated customer read. There is no Business delivered status. |
created_atRequired |
string (date-time) |
See the full schema for validation rules. |
idempotency_expires_atRequired |
string (date-time) |
Original created_at plus 24 hours, unchanged by replay; receipt metadata may remain for up to 30 days. |
Responses
200- Current operation receipt.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
404- Unknown, expired, foreign or non-outbound operation, or noncanonical message UUID.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/webhookRead webhook metadata
Required permission: Webhook (webhooks:manage)
Requires the current scoped key. Maintenance/read access remains available while paused, suspended or banned. No signing secret is returned. A missing configuration returns HTTP 200 with webhook:null.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
webhookRequired |
object | null |
See the full schema for validation rules. |
Read webhook metadata: response fields: webhook
Variant 1
| Field | Value | Details |
|---|---|---|
urlRequired |
string (uri) |
Canonical stored HTTPS destination; may contain customer query parameters. |
config_revisionRequired |
integer (int64) |
Monotonically increasing database revision. Every successful PUT advances it; delete/recreate does not reuse it. Minimum: 1. |
rotated_atRequired |
string (date-time) |
See the full schema for validation rules. |
Responses
200- Current metadata or null.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
PUT/v1/business/{environment}/webhookSet destination and rotate its signing secret
Required permission: Webhook (webhooks:manage)
Every successful PUT, including an identical replay, rotates the secret and advances the database revision. This operation has no Idempotency-Key replay contract. The signing secret is returned once after commit; losing that response requires another rotation. GET never recovers it. The HTTPS destination is syntax/public-address validated and DNS checked before the write; no test message is posted. Dispatch resolves and pins public DNS again. Rotation does not readdress previously queued content to the new revision: old-revision payload is disposed by bounded cleanup. Maintenance remains allowed while paused/suspended/banned, without authorizing delivery.
JSON request
At most 16,384 bytes of strict UTF-8 JSON.
| Field | Value | Details |
|---|---|---|
urlRequired |
string (uri) |
HTTPS destination, at most 2,048 UTF-8 bytes, no user-info, fragment or opaque URI. Host must resolve only to permitted public addresses; private/local/special ranges and configured stack addresses are rejected. Path/query are preserved; canonical authority is stored. Explicit ports 1–65535 are supported. Minimum characters: 1. Maximum characters: 2,048. Maximum UTF-8 bytes: 2,048. |
Successful response
| Field | Value | Details |
|---|---|---|
webhookRequired |
object |
See the full schema for validation rules. |
signing_secretRequired |
string |
Returned once after successful rotation. Save securely; unavailable from GET. It is the HMAC key as a string. Minimum characters: 43. Maximum characters: 43. |
Set destination and rotate its signing secret: response fields: webhook
| Field | Value | Details |
|---|---|---|
urlRequired |
string (uri) |
Canonical stored HTTPS destination; may contain customer query parameters. |
config_revisionRequired |
integer (int64) |
Monotonically increasing database revision. Every successful PUT advances it; delete/recreate does not reuse it. Minimum: 1. |
rotated_atRequired |
string (date-time) |
See the full schema for validation rules. |
Responses
200- Persisted destination metadata and one-time signing secret.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
DELETE/v1/business/{environment}/webhookDelete the scoped webhook configuration
Required permission: Webhook (webhooks:manage)
Idempotent deletion of this Business/environment's configuration. Missing configuration also returns 204. Legacy and other environment rows are unchanged. Queued content is not rerouted; bounded cleanup disposes its now-missing destination. No response body.
No request body.
Responses
204- Configuration is absent.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/numbersList the business's numbers
Required permission: Account info (account:view)
Read-only. Lists every number of the business with its verification state and the environments that send from it. Phone numbers are never returned. Add, verify, label or remove numbers in the Business portal.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
numbersRequired |
array of object |
Maximum items: 100. |
List the business's numbers: response fields: numbers[]
A business phone number as metadata only. The number itself is never returned by the API.
| Field | Value | Details |
|---|---|---|
number_idRequired |
string (uuid) |
Opaque ID; sender_number_id on GET account refers to it. |
labelRequired |
string |
Owner label such as Support. Empty when none is set. Maximum characters: 80. |
countryRequired |
string |
See the full schema for validation rules. |
statusRequired |
"verified" | "pending" |
pending means the SMS check was started and not completed. |
verified_atRequired |
string | null (date-time) |
See the full schema for validation rules. |
created_atRequired |
string (date-time) |
See the full schema for validation rules. |
sender_environmentsRequired |
array of "sandbox" | "live" |
Environments that currently send from this number. Maximum items: 2. |
Responses
200- Current numbers, oldest first.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/keysList this environment's API keys
Required permission: Account info (account:view)
Read-only. Lists up to 100 keys of this environment, newest first, including revoked keys. Secrets are never returned. Create, replace or revoke keys in the Business portal.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
keysRequired |
array of object |
Maximum items: 100. |
List this environment's API keys: response fields: keys[]
API key metadata. The secret, its hash and salt are never returned.
| Field | Value | Details |
|---|---|---|
key_idRequired |
string (uuid) |
See the full schema for validation rules. |
labelRequired |
string |
Maximum characters: 320. |
prefixRequired |
string |
The public start of the key (natter_ plus the first eight hex digits of key_id), for finding it in your configuration. |
accessRequired |
"full" | "restricted" |
full: every operation, gated only by the business's feature switches and state. restricted: only the operations of the listed permission groups. Changed only by the owner in the Business portal. |
permissionsRequired |
array of "messaging:manage" |
"customers:read" | "billing:read" |
"profile:manage" | "webhooks:manage" |
"account:view" | "messages:send" |
"calls:manage"
|
The key's permission groups. A full-access key lists all six. A restricted key lists the groups the owner chose; an older restricted key may list a converted messages:send or calls:manage, half of Messaging & calls. Minimum items: 1. Maximum items: 6. |
scopesRequired |
array of "account:read" | "messages:send" |
"webhooks:manage" | "profile:write" |
"calls:manage"
|
Compatibility list of the scope names used before permission groups. A name is listed only when the key may call every operation that scope used to allow: a full-access key lists all five; a key with Messaging & calls lists messages:send and calls:manage. account:read needs Account info, Billing, Customers, Profile & appearance and the messaging half of Messaging & calls. New integrations should read permissions. Whether calls can actually be answered also depends on the voice_calls/video_calls features reported by GET /account. Minimum items: 0. Maximum items: 5. |
created_atRequired |
string (date-time) |
See the full schema for validation rules. |
last_used_atRequired |
string | null (date-time) |
Null when no use has been recorded. |
revoked_atRequired |
string | null (date-time) |
See the full schema for validation rules. |
currentRequired |
boolean |
True for the key that made this request. |
Responses
200- Key metadata.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
POST/v1/business/{environment}/webhook/testSend a signed test event
Required permission: Webhook (webhooks:manage)
Sends one test event to the configured endpoint through the normal delivery path: the same destination checks, X-Natter-Signature, X-Natter-Event-Id and ten-second limit as customer events. It is a single attempt and is not retried; the result is returned and recorded in the delivery log. Send no request body. At most ten tests per minute per environment. Maintenance remains allowed while paused, suspended or banned.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
testRequired |
object |
One POST to your endpoint. No payload, URL or response body is kept. |
Send a signed test event: response fields: test
One POST to your endpoint. No payload, URL or response body is kept.
| Field | Value | Details |
|---|---|---|
attempted_atRequired |
string (date-time) |
See the full schema for validation rules. |
event_idRequired |
string (uuid) |
The X-Natter-Event-Id that was sent. |
kindRequired |
"message" | "status" | "call" |
"test" | "consent"
|
See the full schema for validation rules. |
attemptRequired |
integer |
1 for the first try of an event; retries count up. Minimum: 1. |
http_statusRequired |
integer | null |
Null when no HTTP response arrived. Minimum: 100. Maximum: 599. |
latency_msRequired |
integer |
Minimum: 0. |
errorRequired |
null | "http_failed" |
"transport_failed"
|
Null when delivered (2xx). http_failed: a non-2xx response, including redirects. transport_failed: DNS, refused destination, TLS, connection or timeout. |
deliveredRequired |
boolean |
See the full schema for validation rules. |
Responses
200- The attempt, delivered or not.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
409- No webhook is configured for this environment.
429- Ten tests were already sent in the last minute.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/webhook/deliveriesList recent webhook delivery attempts
Required permission: Webhook (webhooks:manage)
The last 50 attempts of this environment from the last seven days, newest first: event kind and ID, attempt number, HTTP status, latency and failure code. Payloads are never stored.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
deliveriesRequired |
array of object |
Newest first: at most the last 50 attempts of this environment from the last seven days. Maximum items: 50. |
List recent webhook delivery attempts: response fields: deliveries[]
One POST to your endpoint. No payload, URL or response body is kept.
| Field | Value | Details |
|---|---|---|
attempted_atRequired |
string (date-time) |
See the full schema for validation rules. |
event_idRequired |
string (uuid) |
The X-Natter-Event-Id that was sent. |
kindRequired |
"message" | "status" | "call" |
"test" | "consent"
|
See the full schema for validation rules. |
attemptRequired |
integer |
1 for the first try of an event; retries count up. Minimum: 1. |
http_statusRequired |
integer | null |
Null when no HTTP response arrived. Minimum: 100. Maximum: 599. |
latency_msRequired |
integer |
Minimum: 0. |
errorRequired |
null | "http_failed" |
"transport_failed"
|
Null when delivered (2xx). http_failed: a non-2xx response, including redirects. transport_failed: DNS, refused destination, TLS, connection or timeout. |
deliveredRequired |
boolean |
See the full schema for validation rules. |
Responses
200- Recent attempts.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/usageRead usage and processing metrics
Required permission: Billing (billing:read)
Read only this business and environment using Billing (billing:read). Month-to-date totals, seven-day processing metrics and monthly history (the current month unless month or from/to is given). Available during a price hold; never grants sending permission.
month (query, optional): One UTC month, YYYY-MM. Not in the
future and at most 36 months back. See the full schema for validation rules.
from (query, optional): First month of a range, YYYY-MM; use
with to. A range covers at most 12 months. See the full schema for
validation rules.
to (query, optional): Last month of a range, YYYY-MM; not in
the future. See the full schema for validation rules.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
usage_periodRequired |
object |
Month-to-date UTC ledger period. |
metrics_periodRequired |
object |
Rolling seven-day operational period. |
pricingRequired |
object |
See the full schema for validation rules. |
usageRequired |
object |
Durable accepted outbound units and recorded price; zero-price events count. |
trafficRequired |
object |
Content-free operation counts in the operational period. |
processingRequired |
object |
Submission to server acceptance for accepted outbound samples; milliseconds, null without samples. Not phone delivery latency. |
webhooksRequired |
object |
Current pending queue, failures retained within seven days, and retry attempts among those retained rows. Not lifetime webhook totals. |
dailyRequired |
array of object |
Month-to-date UTC daily ledger entries. Maximum items: 31. |
historyOptional |
object |
Monthly usage for the requested months, oldest first. |
Read usage and processing metrics: response fields: usage_period
Month-to-date UTC ledger period.
| Field | Value | Details |
|---|---|---|
fromRequired |
string (date-time) |
See the full schema for validation rules. |
untilRequired |
string (date-time) |
See the full schema for validation rules. |
Read usage and processing metrics: response fields: metrics_period
Rolling seven-day operational period.
| Field | Value | Details |
|---|---|---|
fromRequired |
string (date-time) |
See the full schema for validation rules. |
untilRequired |
string (date-time) |
See the full schema for validation rules. |
Read usage and processing metrics: response fields: pricing
| Field | Value | Details |
|---|---|---|
currencyRequired |
"EUR" |
See the full schema for validation rules. |
outbound_message_micro_eurRequired
|
integer | null |
See the full schema for validation rules. |
revisionRequired |
integer | null |
See the full schema for validation rules. |
sending_enabledRequired |
boolean |
See the full schema for validation rules. |
payment_requiredRequired |
boolean |
See the full schema for validation rules. |
Read usage and processing metrics: response fields: usage
Durable accepted outbound units and recorded price; zero-price events count.
| Field | Value | Details |
|---|---|---|
outbound_messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
amount_micro_eurRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
Read usage and processing metrics: response fields: traffic
Content-free operation counts in the operational period.
| Field | Value | Details |
|---|---|---|
incoming_messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
submitted_messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
accepted_messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
failed_messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
read_messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
pending_messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
retriesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
Read usage and processing metrics: response fields: processing
Submission to server acceptance for accepted outbound samples; milliseconds, null without samples. Not phone delivery latency.
| Field | Value | Details |
|---|---|---|
samplesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
average_msRequired |
number | null |
See the full schema for validation rules. |
p95_msRequired |
number | null |
See the full schema for validation rules. |
maximum_msRequired |
number | null |
See the full schema for validation rules. |
Read usage and processing metrics: response fields: webhooks
Current pending queue, failures retained within seven days, and retry attempts among those retained rows. Not lifetime webhook totals.
| Field | Value | Details |
|---|---|---|
pendingRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
failedRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
retriesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
Read usage and processing metrics: response fields: daily[]
| Field | Value | Details |
|---|---|---|
dayRequired |
string (date) |
See the full schema for validation rules. |
outbound_messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
amount_micro_eurRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
Read usage and processing metrics: response fields: history
Monthly usage for the requested months, oldest first.
| Field | Value | Details |
|---|---|---|
fromRequired |
string |
See the full schema for validation rules. |
toRequired |
string |
See the full schema for validation rules. |
monthsRequired |
array of object |
Maximum items: 12. |
Read usage and processing metrics: response fields: history: months[]
One month of metered usage.
| Field | Value | Details |
|---|---|---|
monthRequired |
string |
UTC calendar month. |
fromRequired |
string (date-time) |
See the full schema for validation rules. |
untilRequired |
string (date-time) |
Month end, or now for the current month. |
completeRequired |
boolean |
The month has ended; its figures are final. |
currencyRequired |
"EUR" |
See the full schema for validation rules. |
outbound_messagesRequired |
integer |
Accepted outbound sends from the durable usage ledger. Minimum: 0. Maximum: 9,007,199,254,740,991. |
amount_micro_eurRequired |
integer |
Sum of each send's price in force at acceptance, in millionths of one euro. Minimum: 0. Maximum: 1,000,000,000,000,000. |
outbound_by_typeRequired |
object |
See the full schema for validation rules. |
outbound_media_messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
outbound_media_bytesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
inbound_messagesRequired |
integer | null |
Customer messages counted from operation receipts, which are kept 720 hours. Null when the month began before that horizon. Inbound messages and calls are not metered. |
Read usage and processing metrics: response fields: history: months[]: outbound_by_type
| Field | Value | Details |
|---|---|---|
textRequired |
object |
See the full schema for validation rules. |
imageRequired |
object |
See the full schema for validation rules. |
documentRequired |
object |
See the full schema for validation rules. |
audioRequired |
object |
See the full schema for validation rules. |
videoRequired |
object |
See the full schema for validation rules. |
Read usage and processing metrics: response fields: history: months[]: outbound_by_type: text
| Field | Value | Details |
|---|---|---|
messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
amount_micro_eurRequired |
integer |
Minimum: 0. Maximum: 1,000,000,000,000,000. |
Read usage and processing metrics: response fields: history: months[]: outbound_by_type: image
| Field | Value | Details |
|---|---|---|
messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
amount_micro_eurRequired |
integer |
Minimum: 0. Maximum: 1,000,000,000,000,000. |
Read usage and processing metrics: response fields: history: months[]: outbound_by_type: document
| Field | Value | Details |
|---|---|---|
messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
amount_micro_eurRequired |
integer |
Minimum: 0. Maximum: 1,000,000,000,000,000. |
Read usage and processing metrics: response fields: history: months[]: outbound_by_type: audio
| Field | Value | Details |
|---|---|---|
messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
amount_micro_eurRequired |
integer |
Minimum: 0. Maximum: 1,000,000,000,000,000. |
Read usage and processing metrics: response fields: history: months[]: outbound_by_type: video
| Field | Value | Details |
|---|---|---|
messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
amount_micro_eurRequired |
integer |
Minimum: 0. Maximum: 1,000,000,000,000,000. |
Responses
200- Read usage and processing metrics.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/pricingRead current message pricing
Required permission: Billing (billing:read)
Read only this business and environment using Billing (billing:read). Available during a price hold; never grants sending permission.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
pricingRequired |
object |
See the full schema for validation rules. |
Read current message pricing: response fields: pricing
| Field | Value | Details |
|---|---|---|
currencyRequired |
"EUR" |
See the full schema for validation rules. |
outbound_message_micro_eurRequired
|
integer | null |
Current unit price in millionths of one euro; null means unavailable, not free. |
revisionRequired |
integer | null |
See the full schema for validation rules. |
sending_enabledRequired |
boolean |
A fresh price permits sending: zero always, a positive price while this business's prepaid credit or invoice credit limit covers a whole send. Account and customer permission checks still apply. |
payment_requiredRequired |
boolean |
True while the price is positive and this business's available funds cannot cover one send. |
Responses
200- Read current message pricing.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/billingRead the billing account
Required permission: Billing (billing:read)
Mode (prepaid or monthly invoice), balance, credit limit, low-balance warning and whether payment is required, for this business and environment. Mode and limits cannot be changed through the API.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
billingRequired |
object |
Billing account for this business and environment. Mode and limits are changed by Masons Mail or in the portal, never through the API. |
pricingRequired |
object |
See the full schema for validation rules. |
Read the billing account: response fields: billing
Billing account for this business and environment. Mode and limits are changed by Masons Mail or in the portal, never through the API.
| Field | Value | Details |
|---|---|---|
modeRequired |
"prepaid" | "invoice" |
prepaid (the default) spends credit; invoice is enabled by Masons Mail and accrues to a monthly invoice up to the credit limit. |
currencyRequired |
"EUR" |
See the full schema for validation rules. |
balance_micro_eurRequired |
integer |
Credits minus priced usage. Negative only in invoice mode. Minimum: -1,000,000,000,000,000. Maximum: 1,000,000,000,000,000. |
credit_limit_micro_eurRequired |
integer |
Invoice mode only; zero in prepaid mode. Minimum: 0. Maximum: 1,000,000,000,000,000. |
available_micro_eurRequired |
integer |
The balance, plus the credit limit in invoice mode. Minimum: -1,000,000,000,000,000. Maximum: 1,000,000,000,000,000. |
low_balance_threshold_micro_eurRequired
|
integer |
Minimum: 0. Maximum: 1,000,000,000,000,000. |
low_balanceRequired |
boolean |
The price is positive and available funds are below the threshold. |
payment_requiredRequired |
boolean |
The price is positive and available funds cannot cover one send: Live sends return 402 payment_required. |
sending_enabledRequired |
boolean |
See the full schema for validation rules. |
revisionRequired |
integer |
Settings revision; 0 means the untouched default. Minimum: 0. Maximum: 9,007,199,254,740,991. |
Read the billing account: response fields: pricing
| Field | Value | Details |
|---|---|---|
currencyRequired |
"EUR" |
See the full schema for validation rules. |
outbound_message_micro_eurRequired
|
integer | null |
Current unit price in millionths of one euro; null means unavailable, not free. |
revisionRequired |
integer | null |
See the full schema for validation rules. |
sending_enabledRequired |
boolean |
A fresh price permits sending: zero always, a positive price while this business's prepaid credit or invoice credit limit covers a whole send. Account and customer permission checks still apply. |
payment_requiredRequired |
boolean |
True while the price is positive and this business's available funds cannot cover one send. |
Responses
200- Read the billing account.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/billing/ledgerList balance movements
Required permission: Billing (billing:read)
Newest first: one usage debit per accepted send at the price in force, plus top-ups, adjustments and invoice payments.
limit (query, optional): Page size, 1 to 100 (default 50).
Minimum: 1. Maximum: 100.
before (query, optional): The next value from the previous
page. Maximum characters: 64.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
ledgerRequired |
object |
See the full schema for validation rules. |
List balance movements: response fields: ledger
| Field | Value | Details |
|---|---|---|
currencyRequired |
"EUR" |
See the full schema for validation rules. |
entriesRequired |
array of object |
Maximum items: 100. |
nextRequired |
string | null |
Pass as before= for the next (older) page; null at the end. |
List balance movements: response fields: ledger: entries[]
One balance movement.
| Field | Value | Details |
|---|---|---|
idRequired |
string |
See the full schema for validation rules. |
kindRequired |
"usage" | "top_up" | "adjustment" |
"invoice_payment"
|
See the full schema for validation rules. |
atRequired |
string (date-time) |
See the full schema for validation rules. |
amount_micro_eurRequired |
integer |
Signed: usage is a debit (zero while the price is zero), credits are positive, adjustments either sign. Minimum: -1,000,000,000,000,000. Maximum: 1,000,000,000,000,000. |
message_typeRequired |
"text" | "image" | "document" |
"audio" | "video" | null
|
Usage entries only. |
invoice_idRequired |
string (uuid) | null |
Invoice payment entries only. |
reasonRequired |
string | null |
Adjustment reason recorded by Masons Mail. |
balance_after_micro_eurRequired |
integer | null |
Credit entries only. |
Responses
200- List balance movements.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/invoicesList invoices
Required permission: Billing (billing:read)
Up to 36 monthly records for this business and environment, newest first. Empty until a month with usage or a billing account has closed.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
invoicesRequired |
array of object |
Maximum items: 36. |
List invoices: response fields: invoices[]
One record per closed UTC month, totalled from the same usage ledger as /usage. Issued about one hour after the month ends.
| Field | Value | Details |
|---|---|---|
idRequired |
string (uuid) |
See the full schema for validation rules. |
periodRequired |
string |
See the full schema for validation rules. |
period_startRequired |
string (date-time) |
See the full schema for validation rules. |
period_endRequired |
string (date-time) |
See the full schema for validation rules. |
modeRequired |
"prepaid" | "invoice" |
Prepaid records are issued paid: the usage was already paid from credit. |
currencyRequired |
"EUR" |
See the full schema for validation rules. |
outbound_messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
amount_micro_eurRequired |
integer |
Minimum: 0. Maximum: 1,000,000,000,000,000. |
statusRequired |
"open" | "paid" |
See the full schema for validation rules. |
issued_atRequired |
string (date-time) |
See the full schema for validation rules. |
due_atRequired |
string (date-time) | null |
See the full schema for validation rules. |
paid_atRequired |
string (date-time) | null |
See the full schema for validation rules. |
linesRequired |
array of object |
Maximum items: 16. |
List invoices: response fields: invoices[]: lines[]
| Field | Value | Details |
|---|---|---|
categoryRequired |
"outbound_message" |
See the full schema for validation rules. |
message_typeRequired |
"text" | "image" | "document" |
"audio" | "video"
|
See the full schema for validation rules. |
quantityRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
unit_price_micro_eurRequired |
integer |
Minimum: 0. Maximum: 2,147,483,647. |
amount_micro_eurRequired |
integer |
Minimum: 0. Maximum: 1,000,000,000,000,000. |
Responses
200- List invoices.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/invoices/{invoice_id}Read one invoice
Required permission: Billing (billing:read)
One monthly record with its lines.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
invoiceRequired |
object |
One record per closed UTC month, totalled from the same usage ledger as /usage. Issued about one hour after the month ends. |
Read one invoice: response fields: invoice
One record per closed UTC month, totalled from the same usage ledger as /usage. Issued about one hour after the month ends.
| Field | Value | Details |
|---|---|---|
idRequired |
string (uuid) |
See the full schema for validation rules. |
periodRequired |
string |
See the full schema for validation rules. |
period_startRequired |
string (date-time) |
See the full schema for validation rules. |
period_endRequired |
string (date-time) |
See the full schema for validation rules. |
modeRequired |
"prepaid" | "invoice" |
Prepaid records are issued paid: the usage was already paid from credit. |
currencyRequired |
"EUR" |
See the full schema for validation rules. |
outbound_messagesRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
amount_micro_eurRequired |
integer |
Minimum: 0. Maximum: 1,000,000,000,000,000. |
statusRequired |
"open" | "paid" |
See the full schema for validation rules. |
issued_atRequired |
string (date-time) |
See the full schema for validation rules. |
due_atRequired |
string (date-time) | null |
See the full schema for validation rules. |
paid_atRequired |
string (date-time) | null |
See the full schema for validation rules. |
linesRequired |
array of object |
Maximum items: 16. |
Read one invoice: response fields: invoice: lines[]
| Field | Value | Details |
|---|---|---|
categoryRequired |
"outbound_message" |
See the full schema for validation rules. |
message_typeRequired |
"text" | "image" | "document" |
"audio" | "video"
|
See the full schema for validation rules. |
quantityRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
unit_price_micro_eurRequired |
integer |
Minimum: 0. Maximum: 2,147,483,647. |
amount_micro_eurRequired |
integer |
Minimum: 0. Maximum: 1,000,000,000,000,000. |
Responses
200- Read one invoice.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment.
404- not_found: no invoice with this id in this business/environment.
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/directoryRead the Natter search listing
Required permission: Profile & appearance (profile:manage)
Whether the business is listed in the Natter app's business search (the owner's switch, on by default), whether Masons Mail has unlisted it, whether it is searchable now and, if not, what is missing. A name close to an earlier-verified business is marked similar_to_verified: it is still listed but labelled and ranked below that business.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
versionRequired |
1 |
See the full schema for validation rules. |
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
listedRequired |
boolean |
The owner's switch (default true). |
admin_unlistedRequired |
boolean |
Masons Mail removed the business from search. |
searchableRequired |
boolean |
Every condition below is met now. |
missingRequired |
array of "not_verified" | "profile" |
"profile_picture" | "live_number" |
"business_inactive" | "owner_unlisted" |
"admin_unlisted"
|
What keeps the business out of name search now, in this order. Maximum items: 7. |
similar_to_verifiedRequired |
boolean |
The display name is close to an earlier-verified business; shown with a warning and ranked below it. |
Responses
200- The current listing.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key scope, owner or business authority does not permit this request.
404- not_found: the environment is not live.
405- The method is not supported for this exact route.
503- Business settings are temporarily unavailable (business_settings_unavailable or business_unavailable). temporarily_unavailable (D393): the business owner authority lease lapsed moments ago and is being renewed; it always carries Retry-After: 5. Nothing was written. Retry the same request after Retry-After when it is present.
PUT/v1/business/{environment}/directoryList or unlist the business in Natter search
Required permission: Profile & appearance (profile:manage)
Send exactly {"listed": true} or {"listed": false}. A change counts as one of the day's text saves (shared with profile and settings saves); sending the current value changes and charges nothing. Masons Mail's own unlisting cannot be undone here. Unlisted businesses stay reachable by their link, QR code and exact business number.
JSON request
Send one JSON object with only the fields below.
| Field | Value | Details |
|---|---|---|
listedRequired |
boolean |
See the full schema for validation rules. |
Successful response
| Field | Value | Details |
|---|---|---|
versionRequired |
1 |
See the full schema for validation rules. |
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
listedRequired |
boolean |
The owner's switch (default true). |
admin_unlistedRequired |
boolean |
Masons Mail removed the business from search. |
searchableRequired |
boolean |
Every condition below is met now. |
missingRequired |
array of "not_verified" | "profile" |
"profile_picture" | "live_number" |
"business_inactive" | "owner_unlisted" |
"admin_unlisted"
|
What keeps the business out of name search now, in this order. Maximum items: 7. |
similar_to_verifiedRequired |
boolean |
The display name is close to an earlier-verified business; shown with a warning and ranked below it. |
Responses
200- The saved listing.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key scope, owner or business authority does not permit this request.
404- not_found: the environment is not live.
405- The method is not supported for this exact route.
429- business_save_limit: the business has used today’s saves. Per UTC calendar day a business may make 5 text saves (profile, colours, features and visibility together) and 2 picture uploads (avatar, chat wallpaper, profile background), 1 once it has more than 100,000 customers. Live, Test and the owner portal share these budgets. Nothing was written. Wait Retry-After seconds (until the next UTC midnight), then send the same request again.
503- Business settings are temporarily unavailable (business_settings_unavailable or business_unavailable). temporarily_unavailable (D393): the business owner authority lease lapsed moments ago and is being renewed; it always carries Retry-After: 5. Nothing was written. Retry the same request after Retry-After when it is present.
GET/v1/business/{environment}/settingsRead appearance, visibility and feature settings
Required permission: Profile & appearance (profile:manage)
Current settings for this business and environment. Missing configuration returns revision zero and media/call switches off. Private contacts are not part of this document.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
versionRequired |
1 |
See the full schema for validation rules. |
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
revisionRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
updated_atRequired |
string | null (date-time) |
See the full schema for validation rules. |
settingsRequired |
object |
See the full schema for validation rules. |
Read appearance, visibility and feature settings: response fields: settings
| Field | Value | Details |
|---|---|---|
featuresRequired |
object |
See the full schema for validation rules. |
appearanceRequired |
object |
See the full schema for validation rules. |
visibilityRequired |
object |
See the full schema for validation rules. |
Read appearance, visibility and feature settings: response fields: settings: features
| Field | Value | Details |
|---|---|---|
imagesRequired |
boolean |
Current permission. Defaults to false. |
videosRequired |
boolean |
Current permission. Defaults to false. |
filesRequired |
boolean |
Current permission. Defaults to false. |
voice_messagesRequired |
boolean |
Current permission. Defaults to false. |
voice_callsRequired |
boolean |
Current permission. Defaults to false. |
video_callsRequired |
boolean |
Current permission. Defaults to false. |
Read appearance, visibility and feature settings: response fields: settings: appearance
| Field | Value | Details |
|---|---|---|
avatar_asset_idRequired |
string | null (uuid) |
A current asset uploaded for this exact business, environment and slot, or null. |
chat_wallpaper_asset_idRequired |
string | null (uuid) |
A current asset uploaded for this exact business, environment and slot, or null. |
profile_background_asset_idRequired
|
string | null (uuid) |
A current asset uploaded for this exact business, environment and slot, or null. |
accent_colorRequired |
string |
Six-digit RGB color. |
background_colorRequired |
string |
Six-digit RGB color. |
incoming_bubble_colorRequired |
string |
Six-digit RGB color. |
outgoing_bubble_colorRequired |
string |
Six-digit RGB color. |
Read appearance, visibility and feature settings: response fields: settings: visibility
| Field | Value | Details |
|---|---|---|
descriptionRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
websiteRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
support_emailRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
support_phoneRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
addressRequired |
boolean |
Show street, city and postal code. Country has its own independent switch. |
countryRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
linksRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
Response headers
ETag- Strong settings revision validator, for example "business-settings-3".
Responses
200- Current settings. Treat the ETag as opaque and keep it with this business/environment.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key permission, owner or selected sender authority does not permit this request.
503- Business settings are temporarily unavailable (business_settings_unavailable or business_unavailable). temporarily_unavailable (D393): the business owner authority lease lapsed moments ago and is being renewed; it always carries Retry-After: 5. Nothing was written. Retry the same request after Retry-After when it is present.
PUT/v1/business/{environment}/settingsSave appearance, visibility and feature settings
Required permission: Profile & appearance (profile:manage)
Updates the same saved settings as the portal. Send all fields, the expected_revision from GET, and that GET response ETag as If-Match. Hidden values are removed from consumer responses without erasing the owner's saved profile. An asset must belong to the exact business, environment and slot. Textual profile values are business-wide; these settings are environment-specific.
If-Match (header, required): The strong ETag returned by the
corresponding settings GET. See the full schema for validation rules.
JSON request
Complete settings document; at most 24,576 UTF-8 bytes. No missing, duplicate, unknown or null flags.
| Field | Value | Details |
|---|---|---|
expected_revisionRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,990. |
settingsRequired |
object |
See the full schema for validation rules. |
Save appearance, visibility and feature settings: request fields: settings
| Field | Value | Details |
|---|---|---|
featuresRequired |
object |
See the full schema for validation rules. |
appearanceRequired |
object |
See the full schema for validation rules. |
visibilityRequired |
object |
See the full schema for validation rules. |
Save appearance, visibility and feature settings: request fields: settings: features
| Field | Value | Details |
|---|---|---|
imagesRequired |
boolean |
Current permission. Defaults to false. |
videosRequired |
boolean |
Current permission. Defaults to false. |
filesRequired |
boolean |
Current permission. Defaults to false. |
voice_messagesRequired |
boolean |
Current permission. Defaults to false. |
voice_callsRequired |
boolean |
Current permission. Defaults to false. |
video_callsRequired |
boolean |
Current permission. Defaults to false. |
Save appearance, visibility and feature settings: request fields: settings: appearance
| Field | Value | Details |
|---|---|---|
avatar_asset_idRequired |
string | null (uuid) |
A current asset uploaded for this exact business, environment and slot, or null. |
chat_wallpaper_asset_idRequired |
string | null (uuid) |
A current asset uploaded for this exact business, environment and slot, or null. |
profile_background_asset_idRequired
|
string | null (uuid) |
A current asset uploaded for this exact business, environment and slot, or null. |
accent_colorRequired |
string |
Six-digit RGB color. |
background_colorRequired |
string |
Six-digit RGB color. |
incoming_bubble_colorRequired |
string |
Six-digit RGB color. |
outgoing_bubble_colorRequired |
string |
Six-digit RGB color. |
Save appearance, visibility and feature settings: request fields: settings: visibility
| Field | Value | Details |
|---|---|---|
descriptionRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
websiteRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
support_emailRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
support_phoneRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
addressRequired |
boolean |
Show street, city and postal code. Country has its own independent switch. |
countryRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
linksRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
Successful response
| Field | Value | Details |
|---|---|---|
versionRequired |
1 |
See the full schema for validation rules. |
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
revisionRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
updated_atRequired |
string | null (date-time) |
See the full schema for validation rules. |
settingsRequired |
object |
See the full schema for validation rules. |
Save appearance, visibility and feature settings: response fields: settings
| Field | Value | Details |
|---|---|---|
featuresRequired |
object |
See the full schema for validation rules. |
appearanceRequired |
object |
See the full schema for validation rules. |
visibilityRequired |
object |
See the full schema for validation rules. |
Save appearance, visibility and feature settings: response fields: settings: features
| Field | Value | Details |
|---|---|---|
imagesRequired |
boolean |
Current permission. Defaults to false. |
videosRequired |
boolean |
Current permission. Defaults to false. |
filesRequired |
boolean |
Current permission. Defaults to false. |
voice_messagesRequired |
boolean |
Current permission. Defaults to false. |
voice_callsRequired |
boolean |
Current permission. Defaults to false. |
video_callsRequired |
boolean |
Current permission. Defaults to false. |
Save appearance, visibility and feature settings: response fields: settings: appearance
| Field | Value | Details |
|---|---|---|
avatar_asset_idRequired |
string | null (uuid) |
A current asset uploaded for this exact business, environment and slot, or null. |
chat_wallpaper_asset_idRequired |
string | null (uuid) |
A current asset uploaded for this exact business, environment and slot, or null. |
profile_background_asset_idRequired
|
string | null (uuid) |
A current asset uploaded for this exact business, environment and slot, or null. |
accent_colorRequired |
string |
Six-digit RGB color. |
background_colorRequired |
string |
Six-digit RGB color. |
incoming_bubble_colorRequired |
string |
Six-digit RGB color. |
outgoing_bubble_colorRequired |
string |
Six-digit RGB color. |
Save appearance, visibility and feature settings: response fields: settings: visibility
| Field | Value | Details |
|---|---|---|
descriptionRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
websiteRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
support_emailRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
support_phoneRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
addressRequired |
boolean |
Show street, city and postal code. Country has its own independent switch. |
countryRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
linksRequired |
boolean |
Show this public section to customers. Defaults to true. Hidden values remain editable by the owner. |
Response headers
ETag- Strong settings revision validator, for example "business-settings-3".
Responses
200- Saved settings and the next revision.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key permission, owner or selected sender authority does not permit this request.
404- Selected asset is missing, expired or not owned by this business/environment/slot.
409- business_settings_changed: a concurrent edit changed the revision. Read and reconcile before a deliberate retry.
412- precondition_failed: If-Match does not exactly match expected_revision.
428- precondition_required: If-Match is missing.
429- business_save_limit: the business has used today’s saves. Per UTC calendar day a business may make 5 text saves (profile, colours, features and visibility together) and 2 picture uploads (avatar, chat wallpaper, profile background), 1 once it has more than 100,000 customers. Live, Test and the owner portal share these budgets. Nothing was written. Wait Retry-After seconds (until the next UTC midnight), then send the same request again.
503- Business settings are temporarily unavailable (business_settings_unavailable or business_unavailable). temporarily_unavailable (D393): the business owner authority lease lapsed moments ago and is being renewed; it always carries Retry-After: 5. Nothing was written. Retry the same request after Retry-After when it is present.
GET/v1/business/{environment}/welcomeRead the welcome and terms screen
Required permission: Profile & appearance (profile:manage)
The optional screen the Natter app shows after Natter's own fixed Business notice, for this business and environment. Missing configuration returns revision zero and welcome null.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
versionRequired |
1 |
See the full schema for validation rules. |
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
revisionRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
updated_atRequired |
string | null (date-time) |
See the full schema for validation rules. |
welcomeRequired |
object | null |
null: no welcome screen (never set or turned off). |
content_versionRequired |
string | null |
hex(SHA-256("natter-welcome-v1" NUL text NUL terms_url NUL privacy_url)), an absent link being empty. The version customers agree to; null without a welcome. |
agreements_valid_fromRequired |
string | null (date-time) |
Agreements recorded at or after this instant count. It moves when the welcome is first set (or set again after being off) and when the content changes with reask_on_change. |
Read the welcome and terms screen: response fields: welcome
Variant 1
| Field | Value | Details |
|---|---|---|
textRequired |
string |
Plain text shown after Natter's fixed Business notice. At most 1000 UTF-16 code units, no leading or trailing white space, no control characters other than line breaks and tabs. Not markdown or HTML. Minimum characters: 1. Maximum characters: 1,000. |
terms_urlRequired |
string | null (uri) |
Optional link to your terms, validated like the profile website (https only); null when absent. Maximum characters: 2,048. |
privacy_urlRequired |
string | null (uri) |
Optional link to your privacy policy, validated like the profile website (https only); null when absent. Maximum characters: 2,048. |
require_agreementRequired |
boolean |
true: the customer must tap Agree and continue before chatting, and both sides' new messages are refused until they do. false: shown once, for information. |
reask_on_changeRequired |
boolean |
When a later save changes the text or a link, customers who already agreed must agree again. |
Response headers
ETag- Strong welcome revision validator, for example "business-welcome-3".
Responses
200- Current welcome screen. Keep the ETag with this business/environment.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key scope, owner or selected sender authority does not permit this request.
503- Business settings are temporarily unavailable (business_settings_unavailable or business_unavailable). temporarily_unavailable (D393): the business owner authority lease lapsed moments ago and is being renewed; it always carries Retry-After: 5. Nothing was written. Retry the same request after Retry-After when it is present.
PUT/v1/business/{environment}/welcomeSave the welcome and terms screen
Required permission: Profile & appearance (profile:manage)
Send expected_revision from GET and that response's ETag as If-Match. A save is one of the day's text saves. Changing the text or a link changes content_version; with reask_on_change, customers who agreed earlier must agree again before either side can send new messages.
If-Match (header, required): The strong ETag returned by the
corresponding welcome GET. See the full schema for validation rules.
JSON request
Complete welcome document; at most 16,384 UTF-8 bytes. No missing, duplicate or unknown fields.
| Field | Value | Details |
|---|---|---|
expected_revisionRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,990. |
welcomeRequired |
object | null |
null turns the welcome screen off. |
Save the welcome and terms screen: request fields: welcome
Variant 1
| Field | Value | Details |
|---|---|---|
textRequired |
string |
Plain text shown after Natter's fixed Business notice. At most 1000 UTF-16 code units, no leading or trailing white space, no control characters other than line breaks and tabs. Not markdown or HTML. Minimum characters: 1. Maximum characters: 1,000. |
terms_urlRequired |
string | null (uri) |
Optional link to your terms, validated like the profile website (https only); null when absent. Maximum characters: 2,048. |
privacy_urlRequired |
string | null (uri) |
Optional link to your privacy policy, validated like the profile website (https only); null when absent. Maximum characters: 2,048. |
require_agreementRequired |
boolean |
true: the customer must tap Agree and continue before chatting, and both sides' new messages are refused until they do. false: shown once, for information. |
reask_on_changeRequired |
boolean |
When a later save changes the text or a link, customers who already agreed must agree again. |
Successful response
| Field | Value | Details |
|---|---|---|
versionRequired |
1 |
See the full schema for validation rules. |
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
revisionRequired |
integer |
Minimum: 0. Maximum: 9,007,199,254,740,991. |
updated_atRequired |
string | null (date-time) |
See the full schema for validation rules. |
welcomeRequired |
object | null |
null: no welcome screen (never set or turned off). |
content_versionRequired |
string | null |
hex(SHA-256("natter-welcome-v1" NUL text NUL terms_url NUL privacy_url)), an absent link being empty. The version customers agree to; null without a welcome. |
agreements_valid_fromRequired |
string | null (date-time) |
Agreements recorded at or after this instant count. It moves when the welcome is first set (or set again after being off) and when the content changes with reask_on_change. |
Save the welcome and terms screen: response fields: welcome
Variant 1
| Field | Value | Details |
|---|---|---|
textRequired |
string |
Plain text shown after Natter's fixed Business notice. At most 1000 UTF-16 code units, no leading or trailing white space, no control characters other than line breaks and tabs. Not markdown or HTML. Minimum characters: 1. Maximum characters: 1,000. |
terms_urlRequired |
string | null (uri) |
Optional link to your terms, validated like the profile website (https only); null when absent. Maximum characters: 2,048. |
privacy_urlRequired |
string | null (uri) |
Optional link to your privacy policy, validated like the profile website (https only); null when absent. Maximum characters: 2,048. |
require_agreementRequired |
boolean |
true: the customer must tap Agree and continue before chatting, and both sides' new messages are refused until they do. false: shown once, for information. |
reask_on_changeRequired |
boolean |
When a later save changes the text or a link, customers who already agreed must agree again. |
Response headers
ETag- Strong welcome revision validator, for example "business-welcome-3".
Responses
200- Saved welcome screen and the next revision.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key scope, owner or selected sender authority does not permit this request.
409- business_settings_changed: a concurrent edit changed the welcome revision. Read and reconcile before a deliberate retry.
412- precondition_failed: If-Match does not exactly match expected_revision.
428- precondition_required: If-Match is missing.
429- business_save_limit: the business has used today’s saves. Per UTC calendar day a business may make 5 text saves (profile, colours, features and visibility together) and 2 picture uploads (avatar, chat wallpaper, profile background), 1 once it has more than 100,000 customers. Live, Test and the owner portal share these budgets. Nothing was written. Wait Retry-After seconds (until the next UTC midnight), then send the same request again.
503- Business settings are temporarily unavailable (business_settings_unavailable or business_unavailable). temporarily_unavailable (D393): the business owner authority lease lapsed moments ago and is being renewed; it always carries Retry-After: 5. Nothing was written. Retry the same request after Retry-After when it is present.
POST/v1/business/{environment}/assets/{asset}Upload a profile image or wallpaper
Required permission: Profile & appearance (profile:manage)
POST raw JPEG/PNG bytes to assets/avatar, assets/chat_wallpaper or assets/profile_background. At most 2 MiB, 4 megapixels and 4096 pixels per side; normalized avatars at most 512 KiB. Metadata is stripped by decoding/re-encoding. Upload does not publish: set the returned id in appearance via PUT settings. Unselected uploads expire after 24 hours. Tenant/environment capacity is 512 assets or 256 MiB. No remote image URLs are accepted.
Upload request
Raw image bytes; this is not JSON, base64 or multipart.
Content-Type: image/jpeg or image/png
Successful response
| Field | Value | Details |
|---|---|---|
idRequired |
string (uuid) |
See the full schema for validation rules. |
kindRequired |
"avatar" | "chat_wallpaper" |
"profile_background"
|
See the full schema for validation rules. |
content_typeRequired |
"image/png" | "image/jpeg" |
See the full schema for validation rules. |
bytesRequired |
integer |
Minimum: 1. Maximum: 2,097,152. |
widthRequired |
integer |
Minimum: 1. Maximum: 4,096. |
heightRequired |
integer |
Minimum: 1. Maximum: 4,096. |
Responses
201- Normalized asset metadata. Save its ID in the matching settings slot.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key permission, owner or selected sender authority does not permit this request.
413- business_asset_limit: input, normalized image or tenant capacity exceeds its limit.
429- business_save_limit: the business has used today’s saves. Per UTC calendar day a business may make 5 text saves (profile, colours, features and visibility together) and 2 picture uploads (avatar, chat wallpaper, profile background), 1 once it has more than 100,000 customers. Live, Test and the owner portal share these budgets. Nothing was written. Wait Retry-After seconds (until the next UTC midnight), then send the same request again.
503- Business settings are temporarily unavailable (business_settings_unavailable or business_unavailable). temporarily_unavailable (D393): the business owner authority lease lapsed moments ago and is being renewed; it always carries Retry-After: 5. Nothing was written. Retry the same request after Retry-After when it is present.
GET/v1/business/{environment}/assets/{asset}Read an owned profile image or wallpaper
Required permission: Profile & appearance (profile:manage)
Returns an owned, unexpired normalized image. Customers use their authenticated conversation asset route and may read only currently selected images.
If-None-Match (header, optional): The ETag of a copy you
already hold. Asset IDs are immutable, so a matching validator returns 304
without the image. See the full schema for validation rules.
No request body.
Successful response
Content-Type: image/jpeg.
Content-Type: image/png.
Response headers
Cache-Control- private, max-age=31536000, immutable. Private because every read is authorized for its caller; never store it in a shared cache.
ETag- Strong validator for this asset ID, for example "business-asset-<asset UUID>".
Responses
200- Raw normalized image. Asset IDs are immutable: a changed picture is a new ID, so the response may be kept privately for a year.
304- If-None-Match matched this asset's ETag after authorization. No body; keep using your copy.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key permission, owner or selected sender authority does not permit this request.
404- business_asset_not_found: no matching owned, unexpired asset.
503- Business settings are temporarily unavailable (business_settings_unavailable or business_unavailable). temporarily_unavailable (D393): the business owner authority lease lapsed moments ago and is being renewed; it always carries Retry-After: 5. Nothing was written. Retry the same request after Retry-After when it is present.
DELETE/v1/business/{environment}/assets/{asset}Delete an unused profile image or wallpaper
Required permission: Profile & appearance (profile:manage)
Deletes an owned asset only when no current or retained settings revision references it. Replacing a setting may retain its previous image for bounded rollback; no customer can read that image once it is no longer selected.
No request body.
Responses
204- Unused asset deleted.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key permission, owner or selected sender authority does not permit this request.
404- business_asset_not_found.
409- business_asset_in_use: a retained settings revision still references the image.
503- Business settings are temporarily unavailable (business_settings_unavailable or business_unavailable). temporarily_unavailable (D393): the business owner authority lease lapsed moments ago and is being renewed; it always carries Retry-After: 5. Nothing was written. Retry the same request after Retry-After when it is present.
POST/v1/business/{environment}/mediaUpload media for one customer
Required permission: Messaging & calls (messaging:manage)
Exactly two multipart parts: metadata JSON first, file binary second; reject extra parts and Content-Encoding. Metadata is at most 16,384 bytes. File limits: image/document/audio 26,214,400 bytes, video 47,185,920 bytes; complete HTTP body at most 47,218,688 bytes. Images must be JPEG/PNG with matching dimensions; MP4 must contain ftyp and mdat. For audio/mp4, duration/waveform are bounded caller metadata. A voice note may instead be uploaded as audio/ogg (Opus in Ogg, as WhatsApp and most TTS engines produce): the server checks that the bytes really are one complete Ogg/Opus stream, converts it once, before encryption, to mono AAC-LC m4a (24 kHz, 32 kbit/s), measures duration_ms and the 48-level waveform itself, and stores and delivers only the m4a; the response describes the converted file. Unsupported or corrupt audio answers 415 unsupported_audio; a note over 1 hour answers 422 audio_too_long. Upload is bound to this exact key, business, environment and contact. It does not send a message: POST messages with the returned media_id before expires_at. Two transfer slots per API runtime, within the 16-request pool; absolute 120-second transfer deadline.
Upload request
Send metadata first as application/json, then file as binary with its declared MIME. Exactly two parts, in that order. No Content-Encoding.
Content-Type: multipart/form-data
| Field | Value | Details |
|---|---|---|
metadataRequired |
object | object | object | object | object |
Metadata JSON, at most 16,384 UTF-8 bytes. Exactly one variant; fields depend on type. |
fileRequired |
string (binary) |
Second and final multipart part, named file. Raw bytes matching metadata.mime_type; 25 MiB for image/document/audio or 45 MiB for video. An audio/ogg voice note is converted to audio/mp4 before it is stored. No base64 or remote URL. |
Upload media for one customer: multipart fields: metadata
Image upload metadata
File body must contain 1..26,214,400 bytes. Send the metadata JSON part before the file part.
| Field | Value | Details |
|---|---|---|
toRequired |
string |
Use a scoped user_ref received from this Business's inbound message event. Arbitrary phone numbers are not resolved. Minimum characters: 10. Maximum characters: 105. |
typeRequired |
"image" |
See the full schema for validation rules. |
nameRequired |
string |
Safe filename, at most 255 UTF-8 bytes. No surrounding whitespace, slash, backslash, control characters, bidi overrides/isolates, or the names . and .. . Minimum characters: 1. Maximum characters: 255. Maximum UTF-8 bytes: 255. |
mime_typeRequired |
"image/jpeg" | "image/png" |
See the full schema for validation rules. |
widthRequired |
integer |
Width in pixels. Width times height must not exceed 40,000,000 pixels; metadata must match the uploaded image. Minimum: 1. Maximum: 16,384. |
heightRequired |
integer |
Height in pixels. Width times height must not exceed 40,000,000 pixels; metadata must match the uploaded image. Minimum: 1. Maximum: 16,384. |
duration_msOptional |
0 |
Omit for this media type. |
waveformOptional |
array of integer |
Omit for this media type. Maximum items: 0. |
Document upload metadata
File body must contain 1..26,214,400 bytes. Send the metadata JSON part before the file part.
| Field | Value | Details |
|---|---|---|
toRequired |
string |
Use a scoped user_ref received from this Business's inbound message event. Arbitrary phone numbers are not resolved. Minimum characters: 10. Maximum characters: 105. |
typeRequired |
"document" |
See the full schema for validation rules. |
nameRequired |
string |
Safe filename, at most 255 UTF-8 bytes. No surrounding whitespace, slash, backslash, control characters, bidi overrides/isolates, or the names . and .. . Minimum characters: 1. Maximum characters: 255. Maximum UTF-8 bytes: 255. |
mime_typeRequired |
string |
Lowercase MIME type with no parameters, at most 127 bytes. image/, audio/ and video/ types must use their dedicated media variant. Maximum characters: 127. |
widthOptional |
0 |
Omit for this media type. |
heightOptional |
0 |
Omit for this media type. |
duration_msOptional |
0 |
Omit for this media type. |
waveformOptional |
array of integer |
Omit for this media type. Maximum items: 0. |
Audio upload metadata
File body must contain 1..26,214,400 bytes. Send the metadata JSON part before the file part.
| Field | Value | Details |
|---|---|---|
toRequired |
string |
Use a scoped user_ref received from this Business's inbound message event. Arbitrary phone numbers are not resolved. Minimum characters: 10. Maximum characters: 105. |
typeRequired |
"audio" |
See the full schema for validation rules. |
nameRequired |
string |
Safe filename, at most 255 UTF-8 bytes. No surrounding whitespace, slash, backslash, control characters, bidi overrides/isolates, or the names . and .. . Minimum characters: 1. Maximum characters: 255. Maximum UTF-8 bytes: 255. |
mime_typeRequired |
"audio/mp4" |
See the full schema for validation rules. |
widthOptional |
0 |
Omit for this media type. |
heightOptional |
0 |
Omit for this media type. |
duration_msRequired |
integer |
Clip duration in milliseconds, supplied by the caller. Minimum: 1. Maximum: 3,600,000. |
waveformRequired |
array of integer |
Required audio waveform: exactly 48 integer levels, each 0..1024. Minimum items: 48. Maximum items: 48. |
Voice note (Ogg/Opus) upload metadata
File body must contain 1..26,214,400 bytes of one complete Ogg/Opus stream, at most 1 hour long. Omit duration_ms, waveform, width and height: the server converts the note to mono AAC-LC audio/mp4 and measures duration and waveform from the decoded audio. Send the metadata JSON part before the file part.
| Field | Value | Details |
|---|---|---|
toRequired |
string |
Use a scoped user_ref received from this Business's inbound message event. Arbitrary phone numbers are not resolved. Minimum characters: 10. Maximum characters: 105. |
typeRequired |
"audio" |
See the full schema for validation rules. |
nameRequired |
string |
Safe filename, at most 255 UTF-8 bytes. No surrounding whitespace, slash, backslash, control characters, bidi overrides/isolates, or the names . and .. . The stored name ends in .m4a: a trailing .ogg, .opus or .oga is replaced, otherwise .m4a is appended (voice.m4a if that would exceed 255 bytes). Minimum characters: 1. Maximum characters: 255. Maximum UTF-8 bytes: 255. |
mime_typeRequired |
"audio/ogg" | "audio/ogg; codecs=opus"
|
Opus in Ogg. The declared type does not select the parser: the bytes are inspected as Ogg/Opus, and anything else answers 415 unsupported_audio. |
Video upload metadata
File body must contain 1..47,185,920 bytes. Send the metadata JSON part before the file part.
| Field | Value | Details |
|---|---|---|
toRequired |
string |
Use a scoped user_ref received from this Business's inbound message event. Arbitrary phone numbers are not resolved. Minimum characters: 10. Maximum characters: 105. |
typeRequired |
"video" |
See the full schema for validation rules. |
nameRequired |
string |
Safe filename, at most 255 UTF-8 bytes. No surrounding whitespace, slash, backslash, control characters, bidi overrides/isolates, or the names . and .. . Minimum characters: 1. Maximum characters: 255. Maximum UTF-8 bytes: 255. |
mime_typeRequired |
"video/mp4" |
See the full schema for validation rules. |
widthRequired |
integer |
Width in pixels. Width times height must not exceed 40,000,000 pixels; metadata must match the uploaded image. Minimum: 1. Maximum: 16,384. |
heightRequired |
integer |
Height in pixels. Width times height must not exceed 40,000,000 pixels; metadata must match the uploaded image. Minimum: 1. Maximum: 16,384. |
duration_msRequired |
integer |
Clip duration in milliseconds, supplied by the caller. Minimum: 1. Maximum: 60,000. |
waveformOptional |
array of integer |
Omit for this media type. Maximum items: 0. |
Part metadata Content-Type: application/json.
Part file Content-Type: */*.
Successful response
Width and height occur only for image/video; duration_ms only for audio/video. No waveform, content URL, descriptor or encryption key is returned. An audio/ogg voice upload is described as the converted file: mime_type audio/mp4, the .m4a name, the converted byte count and the measured duration_ms.
| Field | Value | Details |
|---|---|---|
media_idRequired |
string (uuid) |
Opaque temporary handle; not a public URL or underlying encrypted blob ID. |
typeRequired |
"image" | "document" | "audio" |
"video"
|
See the full schema for validation rules. |
nameRequired |
string |
Safe filename, at most 255 UTF-8 bytes. No surrounding whitespace, slash, backslash, control characters, bidi overrides/isolates, or the names . and .. . Minimum characters: 1. Maximum characters: 255. Maximum UTF-8 bytes: 255. |
mime_typeRequired |
string |
Validated original MIME. Uploaded images are JPEG/PNG; received images can also be WebP. Audio is audio/mp4, video is video/mp4, documents exclude image/audio/video MIME types. |
bytesRequired |
integer |
Plaintext byte count. At most 26,214,400 for image/document/audio, or 47,185,920 for video. Minimum: 1. Maximum: 47,185,920. |
widthOptional |
integer |
Width in pixels. Width times height must not exceed 40,000,000 pixels; metadata must match the uploaded image. Minimum: 1. Maximum: 16,384. |
heightOptional |
integer |
Height in pixels. Width times height must not exceed 40,000,000 pixels; metadata must match the uploaded image. Minimum: 1. Maximum: 16,384. |
duration_msOptional |
integer |
Present only for audio/video. Audio at most 3,600,000 ms; video at most 60,000 ms. Minimum: 1. Maximum: 3,600,000. |
stateRequired |
"ready" |
See the full schema for validation rules. |
expires_atRequired |
string (date-time) |
Absolute handle deadline, no later than 24 hours or underlying media/upload expiry. Sending or ACK can remove it sooner. |
Responses
201- Ready upload metadata. Use this exact key and recipient for the subsequent message.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key permission, owner, verified selected number, customer permission, block or feature setting does not permit this operation.
404- not_found: no currently accessible resource in this business/environment.
415- unsupported_audio: metadata.mime_type is audio/ogg but the file is not one complete, decodable Opus stream in Ogg (for example Vorbis or FLAC in Ogg, Opus in WebM, an MP4, a truncated or damaged file, or chained streams). Nothing was stored. Send real Ogg/Opus, or upload audio/mp4 instead.
422- audio_too_long: an audio/ogg voice note is longer than 3,600,000 ms (1 hour), measured from the stream and again from the decoded audio. Nothing was stored.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/media/{media_id}Read temporary media metadata
Required permission: Messaging & calls (messaging:manage)
Current key, owner, selected verified number, business/environment, contact window, block and feature permissions are checked again before decrypted reads. Outbound handles are private to the exact uploading key; any current key with Messaging & calls (messaging:manage, or an older key's converted messages:send) in the same business/environment can read an admitted inbound handle. A handle does not grant permission or renew the contact window.
No request body.
Successful response
Width and height occur only for image/video; duration_ms only for audio/video. No waveform, content URL, descriptor or encryption key is returned. An audio/ogg voice upload is described as the converted file: mime_type audio/mp4, the .m4a name, the converted byte count and the measured duration_ms.
| Field | Value | Details |
|---|---|---|
media_idRequired |
string (uuid) |
Opaque temporary handle; not a public URL or underlying encrypted blob ID. |
typeRequired |
"image" | "document" | "audio" |
"video"
|
See the full schema for validation rules. |
nameRequired |
string |
Safe filename, at most 255 UTF-8 bytes. No surrounding whitespace, slash, backslash, control characters, bidi overrides/isolates, or the names . and .. . Minimum characters: 1. Maximum characters: 255. Maximum UTF-8 bytes: 255. |
mime_typeRequired |
string |
Validated original MIME. Uploaded images are JPEG/PNG; received images can also be WebP. Audio is audio/mp4, video is video/mp4, documents exclude image/audio/video MIME types. |
bytesRequired |
integer |
Plaintext byte count. At most 26,214,400 for image/document/audio, or 47,185,920 for video. Minimum: 1. Maximum: 47,185,920. |
widthOptional |
integer |
Width in pixels. Width times height must not exceed 40,000,000 pixels; metadata must match the uploaded image. Minimum: 1. Maximum: 16,384. |
heightOptional |
integer |
Height in pixels. Width times height must not exceed 40,000,000 pixels; metadata must match the uploaded image. Minimum: 1. Maximum: 16,384. |
duration_msOptional |
integer |
Present only for audio/video. Audio at most 3,600,000 ms; video at most 60,000 ms. Minimum: 1. Maximum: 3,600,000. |
stateRequired |
"ready" |
See the full schema for validation rules. |
expires_atRequired |
string (date-time) |
Absolute handle deadline, no later than 24 hours or underlying media/upload expiry. Sending or ACK can remove it sooner. |
Responses
200- Ready media metadata; no plaintext file content.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key permission, owner, verified selected number, customer permission, block or feature setting does not permit this operation.
404- not_found: no currently accessible resource in this business/environment.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/media/{media_id}/contentDownload received customer media
Required permission: Messaging & calls (messaging:manage)
Current key, owner, selected verified number, business/environment, contact window, block and feature permissions are checked again before decrypted reads. Outbound handles are private to the exact uploading key; any current key with Messaging & calls (messaging:manage, or an older key's converted messages:send) in the same business/environment can read an admitted inbound handle. A handle does not grant permission or renew the contact window. Inbound only; outbound upload bytes are not exposed. Download before expires_at, validate the response and durably retain required bytes on your own server before ACK. Absolute 120-second deadline and two shared transfer slots. No redirect or public media URL.
No request body.
Successful response
Content-Type: */*.
Response headers
Content-Type- Validated original media MIME type, also present in media metadata.
Content-Disposition- attachment; filename is the validated media name.
Content-Length- Plaintext byte count, matching the media metadata.
X-Natter-Media-ID- Matches the requested temporary media handle.
Responses
200- Original inbound bytes with validated MIME and attachment disposition. Reading does not ACK the media.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key permission, owner, verified selected number, customer permission, block or feature setting does not permit this operation.
404- not_found: no currently accessible resource in this business/environment.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
POST/v1/business/{environment}/media/{media_id}/ackAcknowledge received customer media
Required permission: Messaging & calls (messaging:manage)
Current key, owner, selected verified number, business/environment, contact window, block and feature permissions are checked again before decrypted reads. Outbound handles are private to the exact uploading key; any current key with Messaging & calls (messaging:manage, or an older key's converted messages:send) in the same business/environment can read an admitted inbound handle. A handle does not grant permission or renew the contact window. Send no request body or transfer encoding. Inbound only; acknowledges the hosted endpoint target and removes its private descriptor/handle. It does not acknowledge other recipients. Webhook HTTP 2xx and a successful download are separate from this explicit media ACK. After ACK, the handle is unavailable; a repeat can return 404.
No request body.
Responses
204- Inbound media acknowledged and private handle removed.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- Current key permission, owner, verified selected number, customer permission, block or feature setting does not permit this operation.
404- not_found: no currently accessible resource in this business/environment.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/callsList current customer calls
Required permission: Messaging & calls (messaging:manage)
Only customer-initiated calls are supported. Requires a current full-access key or a restricted key with Messaging & calls (messaging:manage, or an older key's converted calls:manage), the same business/environment and a current consenting customer relationship; voice_calls/video_calls must allow the authenticated offer. No Business call creation, waiting, renegotiation or format-2 offer support. Returns at most eight current endpoint calls. Use this when no webhook is configured; not a call history. Listing authenticates the customer offers just like individual GET.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
callsRequired |
array of object |
At most eight current calls for this hosted endpoint, ordered by creation and call ID. No historical call archive. Maximum items: 8. |
List current customer calls: response fields: calls[]
| Field | Value | Details |
|---|---|---|
call_idRequired |
string |
Maximum characters: 96. |
user_refRequired |
string |
Opaque reference scoped to this Business/environment/customer incarnation. Operation contact references are bounded to 256 UTF-8 bytes; the current POST recipient parser accepts only 1-96 ASCII identifier characters. References created by the private customer-entry primitive fit that parser. Minimum characters: 1. Maximum characters: 256. Maximum UTF-8 bytes: 256. |
conversation_idRequired |
string |
Opaque Business/environment conversation reference. This is not the internal relay conversation ID. Minimum characters: 1. |
stateRequired |
"ringing" | "answering" |
"answered" | "ended"
|
See the full schema for validation rules. |
mediaRequired |
"audio" | "video" |
See the full schema for validation rules. |
offer_sdpOptional |
string |
Authenticated customer SDP, when available. Pass it to your WebRTC implementation; never log or expose it in a webhook. Maximum characters: 32,768. Maximum UTF-8 bytes: 32,768. |
offer_transactionRequired |
string |
Customer offer transaction. Incoming signal description_transaction refers to this value. Minimum characters: 1. Maximum characters: 96. |
answer_transactionOptional |
string |
Present after an answer; reference it when sending Business ICE candidates. Minimum characters: 1. Maximum characters: 96. |
created_atRequired |
string (date-time) |
Call creation time. |
expires_atRequired |
string (date-time) |
Current ring, setup or active-call deadline. End the local session when this expires; no indefinite ringing or call lease. |
Responses
200- Current call state or committed action receipt.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- call_not_allowed or current Business authority: customer/contact/key/feature authority does not allow this action.
404- not_found: no currently accessible resource in this business/environment.
409- call_busy, call_changed or idempotency_conflict. Re-read current state; never change a body under an existing transaction.
410- call_ended: call is terminal or its current deadline expired. Stop local WebRTC and polling.
422- invalid_call_packet: native authenticated offer or signaling packet cannot be admitted.
429- call_limit: bounded call/signaling/TURN capacity reached.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/calls/{call_id}Read a customer call and offer
Required permission: Messaging & calls (messaging:manage)
Only customer-initiated calls are supported. Requires a current full-access key or a restricted key with Messaging & calls (messaging:manage, or an older key's converted calls:manage), the same business/environment and a current consenting customer relationship; voice_calls/video_calls must allow the authenticated offer. No Business call creation, waiting, renegotiation or format-2 offer support. Authenticates the native offer and records its native confirmation once before returning SDP. This does not answer the call. Your integration supplies its own WebRTC engine.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
call_idRequired |
string |
Maximum characters: 96. |
user_refRequired |
string |
Opaque reference scoped to this Business/environment/customer incarnation. Operation contact references are bounded to 256 UTF-8 bytes; the current POST recipient parser accepts only 1-96 ASCII identifier characters. References created by the private customer-entry primitive fit that parser. Minimum characters: 1. Maximum characters: 256. Maximum UTF-8 bytes: 256. |
conversation_idRequired |
string |
Opaque Business/environment conversation reference. This is not the internal relay conversation ID. Minimum characters: 1. |
stateRequired |
"ringing" | "answering" |
"answered" | "ended"
|
See the full schema for validation rules. |
mediaRequired |
"audio" | "video" |
See the full schema for validation rules. |
offer_sdpOptional |
string |
Authenticated customer SDP, when available. Pass it to your WebRTC implementation; never log or expose it in a webhook. Maximum characters: 32,768. Maximum UTF-8 bytes: 32,768. |
offer_transactionRequired |
string |
Customer offer transaction. Incoming signal description_transaction refers to this value. Minimum characters: 1. Maximum characters: 96. |
answer_transactionOptional |
string |
Present after an answer; reference it when sending Business ICE candidates. Minimum characters: 1. Maximum characters: 96. |
created_atRequired |
string (date-time) |
Call creation time. |
expires_atRequired |
string (date-time) |
Current ring, setup or active-call deadline. End the local session when this expires; no indefinite ringing or call lease. |
Responses
200- Current call state or committed action receipt.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- call_not_allowed or current Business authority: customer/contact/key/feature authority does not allow this action.
404- not_found: no currently accessible resource in this business/environment.
409- call_busy, call_changed or idempotency_conflict. Re-read current state; never change a body under an existing transaction.
410- call_ended: call is terminal or its current deadline expired. Stop local WebRTC and polling.
422- invalid_call_packet: native authenticated offer or signaling packet cannot be admitted.
429- call_limit: bounded call/signaling/TURN capacity reached.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
POST/v1/business/{environment}/calls/{call_id}/answerAnswer a customer call
Required permission: Messaging & calls (messaging:manage)
Only customer-initiated calls are supported. Requires a current full-access key or a restricted key with Messaging & calls (messaging:manage, or an older key's converted calls:manage), the same business/environment and a current consenting customer relationship; voice_calls/video_calls must allow the authenticated offer. No Business call creation, waiting, renegotiation or format-2 offer support. Apply offer_sdp to your WebRTC engine and send its generated answer here. Reuse the exact transaction, key and body after a lost response. A changed request under the same transaction conflicts; no Idempotency-Key header is used for call actions.
JSON request
JSON answer, at most 131,072 bytes; sdp itself at most 32,768 UTF-8 bytes.
| Field | Value | Details |
|---|---|---|
transactionRequired |
string |
Unique transaction for one call action. Reuse the exact value, key and unchanged body after a lost response; never reuse it for another action. Values starting bc_ are reserved. Minimum characters: 1. Maximum characters: 96. |
sdpRequired |
string |
Your WebRTC SDP answer: valid UTF-8, starts v=0, no NUL, at most 32,768 bytes. This endpoint does not generate the answer for you. Minimum characters: 3. Maximum characters: 32,768. Maximum UTF-8 bytes: 32,768. |
Successful response
| Field | Value | Details |
|---|---|---|
call_idRequired |
string |
Maximum characters: 96. |
transactionRequired |
string |
Unique transaction for one call action. Reuse the exact value, key and unchanged body after a lost response; never reuse it for another action. Values starting bc_ are reserved. Minimum characters: 1. Maximum characters: 96. |
actionRequired |
"answer" | "signal" | "end"
|
See the full schema for validation rules. |
applied_atRequired |
string (date-time) |
Time the action was committed; retries preserve it. |
ring_expires_atRequired |
string (date-time) |
Original ringing deadline; active call lifetime is in CallView.expires_at. |
sequenceRequired |
integer |
Minimum: 0. |
replayedRequired |
boolean |
True for a matching transaction retry. A different key/body/action under the transaction conflicts. |
claim_transactionRequired |
string | null |
Native answer-claim receipt, when applicable; otherwise null. Do not invent or modify it. |
setup_expires_atRequired |
string | null (date-time) |
Setup deadline when applicable; otherwise null. |
Responses
200- Current call state or committed action receipt.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- call_not_allowed or current Business authority: customer/contact/key/feature authority does not allow this action.
404- not_found: no currently accessible resource in this business/environment.
409- call_busy, call_changed or idempotency_conflict. Re-read current state; never change a body under an existing transaction.
410- call_ended: call is terminal or its current deadline expired. Stop local WebRTC and polling.
422- invalid_call_packet: native authenticated offer or signaling packet cannot be admitted.
429- call_limit: bounded call/signaling/TURN capacity reached.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/calls/{call_id}/signalsRead customer ICE candidates
Required permission: Messaging & calls (messaging:manage)
Only customer-initiated calls are supported. Requires a current full-access key or a restricted key with Messaging & calls (messaging:manage, or an older key's converted calls:manage), the same business/environment and a current consenting customer relationship; voice_calls/video_calls must allow the authenticated offer. No Business call creation, waiting, renegotiation or format-2 offer support. Returns at most ten signals. Always continue from next_after, including an empty page. No other query parameter is accepted; after must be canonical decimal 0..512 (no leading zero, sign, duplicate or percent encoding).
after (query, optional): Previous next_after cursor. Omit for
the first page or send after=0; use the returned next_after even when
signals is empty. Minimum: 0. Maximum: 512. Default: 0.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
signalsRequired |
array of object |
At most ten authenticated customer signals per page. Maximum items: 10. |
next_afterRequired |
integer |
Always use this cursor for your next GET, even when signals is empty; it can advance past discarded entries. Minimum: 0. Maximum: 512. |
Read customer ICE candidates: response fields: signals[]
| Field | Value | Details |
|---|---|---|
sequenceRequired |
integer |
Minimum: 1. Maximum: 512. |
transactionRequired |
string |
Authenticated customer signal transaction. Minimum characters: 1. Maximum characters: 96. |
description_transactionRequired |
string |
For Business POST, the successful answer transaction; for received customer signals, the customer offer transaction. Minimum characters: 1. Maximum characters: 96. |
candidatesRequired |
array of object |
At most 16 candidates. An empty array is valid only with complete=true. Maximum items: 16. |
completeRequired |
boolean |
True when ICE gathering for this description has completed. |
Read customer ICE candidates: response fields: signals[]: candidates[]
| Field | Value | Details |
|---|---|---|
candidateRequired |
string |
Nonempty ICE candidate; valid UTF-8 with no control characters. Minimum characters: 1. Maximum characters: 4,096. Maximum UTF-8 bytes: 4,096. |
sdp_midOptional |
string | null |
SDP media identifier. Null or omitted when absent; a supplied string must be nonempty and contain no control characters. Minimum characters: 1. Maximum characters: 64. Maximum UTF-8 bytes: 64. |
sdp_mline_indexOptional |
integer | null |
SDP media-line index; null or omitted when absent. Minimum: 0. Maximum: 31. |
username_fragmentOptional |
string | null |
ICE username fragment. Null or omitted when absent; a supplied string must be nonempty and contain no control characters. Minimum characters: 1. Maximum characters: 256. Maximum UTF-8 bytes: 256. |
Responses
200- Current call state or committed action receipt.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- call_not_allowed or current Business authority: customer/contact/key/feature authority does not allow this action.
404- not_found: no currently accessible resource in this business/environment.
409- call_busy, call_changed or idempotency_conflict. Re-read current state; never change a body under an existing transaction.
410- call_ended: call is terminal or its current deadline expired. Stop local WebRTC and polling.
422- invalid_call_packet: native authenticated offer or signaling packet cannot be admitted.
429- call_limit: bounded call/signaling/TURN capacity reached.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
POST/v1/business/{environment}/calls/{call_id}/signalsSend Business ICE candidates
Required permission: Messaging & calls (messaging:manage)
Only customer-initiated calls are supported. Requires a current full-access key or a restricted key with Messaging & calls (messaging:manage, or an older key's converted calls:manage), the same business/environment and a current consenting customer relationship; voice_calls/video_calls must allow the authenticated offer. No Business call creation, waiting, renegotiation or format-2 offer support. Only after a successful answer. description_transaction must equal that answer transaction. At most 16 candidates; an empty array needs complete=true. Reuse the exact transaction/key/body after a lost response. At most 512 outgoing signaling frames per call.
JSON request
JSON ICE batch, at most 32,768 bytes. The normalized ICE payload must fit 16,384 UTF-8 bytes.
Complete request at most 32,768 bytes; the normalized ICE payload (description transaction, candidate fields and completion flag) must fit 16,384 UTF-8 bytes. No renegotiation; description_transaction must equal the successful answer transaction.
| Field | Value | Details |
|---|---|---|
transactionRequired |
string |
Unique transaction for one call action. Reuse the exact value, key and unchanged body after a lost response; never reuse it for another action. Values starting bc_ are reserved. Minimum characters: 1. Maximum characters: 96. |
description_transactionRequired |
string |
For Business POST, the successful answer transaction; for received customer signals, the customer offer transaction. Minimum characters: 1. Maximum characters: 96. |
candidatesRequired |
array of object |
At most 16 candidates. An empty array is valid only with complete=true. Maximum items: 16. |
completeRequired |
boolean |
True when ICE gathering for this description has completed. |
Send Business ICE candidates: request fields: candidates[]
| Field | Value | Details |
|---|---|---|
candidateRequired |
string |
Nonempty ICE candidate; valid UTF-8 with no control characters. Minimum characters: 1. Maximum characters: 4,096. Maximum UTF-8 bytes: 4,096. |
sdp_midOptional |
string | null |
SDP media identifier. Null or omitted when absent; a supplied string must be nonempty and contain no control characters. Minimum characters: 1. Maximum characters: 64. Maximum UTF-8 bytes: 64. |
sdp_mline_indexOptional |
integer | null |
SDP media-line index; null or omitted when absent. Minimum: 0. Maximum: 31. |
username_fragmentOptional |
string | null |
ICE username fragment. Null or omitted when absent; a supplied string must be nonempty and contain no control characters. Minimum characters: 1. Maximum characters: 256. Maximum UTF-8 bytes: 256. |
Successful response
| Field | Value | Details |
|---|---|---|
call_idRequired |
string |
Maximum characters: 96. |
transactionRequired |
string |
Unique transaction for one call action. Reuse the exact value, key and unchanged body after a lost response; never reuse it for another action. Values starting bc_ are reserved. Minimum characters: 1. Maximum characters: 96. |
actionRequired |
"answer" | "signal" | "end"
|
See the full schema for validation rules. |
applied_atRequired |
string (date-time) |
Time the action was committed; retries preserve it. |
ring_expires_atRequired |
string (date-time) |
Original ringing deadline; active call lifetime is in CallView.expires_at. |
sequenceRequired |
integer |
Minimum: 0. |
replayedRequired |
boolean |
True for a matching transaction retry. A different key/body/action under the transaction conflicts. |
claim_transactionRequired |
string | null |
Native answer-claim receipt, when applicable; otherwise null. Do not invent or modify it. |
setup_expires_atRequired |
string | null (date-time) |
Setup deadline when applicable; otherwise null. |
Responses
200- Current call state or committed action receipt.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- call_not_allowed or current Business authority: customer/contact/key/feature authority does not allow this action.
404- not_found: no currently accessible resource in this business/environment.
409- call_busy, call_changed or idempotency_conflict. Re-read current state; never change a body under an existing transaction.
410- call_ended: call is terminal or its current deadline expired. Stop local WebRTC and polling.
422- invalid_call_packet: native authenticated offer or signaling packet cannot be admitted.
429- call_limit: bounded call/signaling/TURN capacity reached.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
POST/v1/business/{environment}/calls/{call_id}/heartbeatKeep an answered call active
Required permission: Messaging & calls (messaging:manage)
Only customer-initiated calls are supported. Requires a current full-access key or a restricted key with Messaging & calls (messaging:manage, or an older key's converted calls:manage), the same business/environment and a current consenting customer relationship; voice_calls/video_calls must allow the authenticated offer. No Business call creation, waiting, renegotiation or format-2 offer support. Send no body. Only the answered hosted endpoint can renew its 180-second lease. Send heartbeats before the returned expires_at; the customer lease and other call deadlines still apply.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
call_idRequired |
string |
Maximum characters: 96. |
user_refRequired |
string |
Opaque reference scoped to this Business/environment/customer incarnation. Operation contact references are bounded to 256 UTF-8 bytes; the current POST recipient parser accepts only 1-96 ASCII identifier characters. References created by the private customer-entry primitive fit that parser. Minimum characters: 1. Maximum characters: 256. Maximum UTF-8 bytes: 256. |
conversation_idRequired |
string |
Opaque Business/environment conversation reference. This is not the internal relay conversation ID. Minimum characters: 1. |
stateRequired |
"ringing" | "answering" |
"answered" | "ended"
|
See the full schema for validation rules. |
mediaRequired |
"audio" | "video" |
See the full schema for validation rules. |
offer_sdpOptional |
string |
Authenticated customer SDP, when available. Pass it to your WebRTC implementation; never log or expose it in a webhook. Maximum characters: 32,768. Maximum UTF-8 bytes: 32,768. |
offer_transactionRequired |
string |
Customer offer transaction. Incoming signal description_transaction refers to this value. Minimum characters: 1. Maximum characters: 96. |
answer_transactionOptional |
string |
Present after an answer; reference it when sending Business ICE candidates. Minimum characters: 1. Maximum characters: 96. |
created_atRequired |
string (date-time) |
Call creation time. |
expires_atRequired |
string (date-time) |
Current ring, setup or active-call deadline. End the local session when this expires; no indefinite ringing or call lease. |
Responses
200- Current call state or committed action receipt.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- call_not_allowed or current Business authority: customer/contact/key/feature authority does not allow this action.
404- not_found: no currently accessible resource in this business/environment.
409- call_busy, call_changed or idempotency_conflict. Re-read current state; never change a body under an existing transaction.
410- call_ended: call is terminal or its current deadline expired. Stop local WebRTC and polling.
422- invalid_call_packet: native authenticated offer or signaling packet cannot be admitted.
429- call_limit: bounded call/signaling/TURN capacity reached.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
POST/v1/business/{environment}/calls/{call_id}/endDecline or end a customer call
Required permission: Messaging & calls (messaging:manage)
Only customer-initiated calls are supported. Requires a current full-access key or a restricted key with Messaging & calls (messaging:manage, or an older key's converted calls:manage), the same business/environment and a current consenting customer relationship; voice_calls/video_calls must allow the authenticated offer. No Business call creation, waiting, renegotiation or format-2 offer support. Use decline or hangup. Exact same-key transaction retries remain supported after private call state is erased, subject to retained receipts and current authority. Stop your WebRTC engine and heartbeats when terminal.
JSON request
JSON end action, at most 1,024 bytes.
| Field | Value | Details |
|---|---|---|
transactionRequired |
string |
Unique transaction for one call action. Reuse the exact value, key and unchanged body after a lost response; never reuse it for another action. Values starting bc_ are reserved. Minimum characters: 1. Maximum characters: 96. |
reasonRequired |
"decline" | "hangup" |
Decline a ringing call or hang up an accepted call. |
Successful response
| Field | Value | Details |
|---|---|---|
call_idRequired |
string |
Maximum characters: 96. |
transactionRequired |
string |
Unique transaction for one call action. Reuse the exact value, key and unchanged body after a lost response; never reuse it for another action. Values starting bc_ are reserved. Minimum characters: 1. Maximum characters: 96. |
actionRequired |
"answer" | "signal" | "end"
|
See the full schema for validation rules. |
applied_atRequired |
string (date-time) |
Time the action was committed; retries preserve it. |
ring_expires_atRequired |
string (date-time) |
Original ringing deadline; active call lifetime is in CallView.expires_at. |
sequenceRequired |
integer |
Minimum: 0. |
replayedRequired |
boolean |
True for a matching transaction retry. A different key/body/action under the transaction conflicts. |
claim_transactionRequired |
string | null |
Native answer-claim receipt, when applicable; otherwise null. Do not invent or modify it. |
setup_expires_atRequired |
string | null (date-time) |
Setup deadline when applicable; otherwise null. |
Responses
200- Current call state or committed action receipt.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- call_not_allowed or current Business authority: customer/contact/key/feature authority does not allow this action.
404- not_found: no currently accessible resource in this business/environment.
409- call_busy, call_changed or idempotency_conflict. Re-read current state; never change a body under an existing transaction.
410- call_ended: call is terminal or its current deadline expired. Stop local WebRTC and polling.
422- invalid_call_packet: native authenticated offer or signaling packet cannot be admitted.
429- call_limit: bounded call/signaling/TURN capacity reached.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/calls/{call_id}/turnGet short-lived TURN credentials
Required permission: Messaging & calls (messaging:manage)
Only customer-initiated calls are supported. Requires a current full-access key or a restricted key with Messaging & calls (messaging:manage, or an older key's converted calls:manage), the same business/environment and a current consenting customer relationship; voice_calls/video_calls must allow the authenticated offer. No Business call creation, waiting, renegotiation or format-2 offer support. Provides TURN credentials for your WebRTC engine, scoped by current call authority. Keep credentials on the call integration; do not log or persist them beyond expiry.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
urisRequired |
array of string |
TURN URIs for UDP and TCP transports. Minimum items: 2. Maximum items: 2. |
usernameRequired |
string |
Short-lived TURN credential; treat as secret. |
passwordRequired |
string |
Short-lived TURN credential; treat as secret. |
ttlRequired |
integer |
Credential lifetime in seconds, subject to server configuration. Minimum: 60. Maximum: 600. |
expires_atRequired |
string (date-time) |
Absolute TURN credential expiry. |
Responses
200- Current call state or committed action receipt.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- call_not_allowed or current Business authority: customer/contact/key/feature authority does not allow this action.
404- not_found: no currently accessible resource in this business/environment.
409- call_busy, call_changed or idempotency_conflict. Re-read current state; never change a body under an existing transaction.
410- call_ended: call is terminal or its current deadline expired. Stop local WebRTC and polling.
422- invalid_call_packet: native authenticated offer or signaling packet cannot be admitted.
429- call_limit: bounded call/signaling/TURN capacity reached.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/contactsList customers you can message
Required permission: Customers (customers:read)
Read only this business and environment using Billing (billing:read). Lists customers who have messaged the business. A customer must message first; you may reply for 4392 hours (183 days) after their last message unless they opt out or block the business. Opening a chat does not count and such customers are not listed. Returns the per-business user_ref used by webhooks. phone is the customer's verified number exactly when their last message event delivered it to you (every business receives it, D412) and that identity is still current; null for a customer who opted out or whose last message event carried no number (only a message accepted before Business API 0.9.0). can_message reflects the window and opt-out only; a paused business or unavailable number can still refuse a send. Never grants sending permission. Paging: results are pages of at most limit customers in the requested order (last message or first contact, newest first; ties by user_ref). To read every customer, request without a cursor, then repeat the same request with cursor set to next_cursor until next_cursor is null; keep every other parameter unchanged, because a cursor belongs to its order. A customer who messages while you page moves to the front of the last-message order, so use first_contact order (which never moves) or the export to read a fixed set. counts covers every listed customer in this environment regardless of filter or page; for a business with 10,000 or more customers they may be up to 60 seconds older than as_of (see counts_as_of). match_count is how many customers match the status and any search or date filter; with a search or date filter it stops counting at 10,000 and match_count_exact is then false.
status (query, optional): Only customers with this status.
Default all. Default: "all".
sort (query, optional): last_inbound (default): most recent
last message first. first_contact: most recent first contact first. Default:
"last_inbound".
user_ref_prefix (query, optional): Only customers whose
user_ref starts with this value (case-sensitive). Pass a whole user_ref for
an exact match. Minimum characters: 1. Maximum characters: 96.
phone (query, optional): Only the customer whose verified
number is this E.164 number; finds only a customer whose number is returned
in phone. URL-encode the leading + as %2B, or omit it. See the full schema
for validation rules.
last_inbound_from (query, optional): Only customers whose last
message is at or after this RFC 3339 instant (microsecond precision at
most). See the full schema for validation rules.
last_inbound_to (query, optional): Only customers whose last
message is before this RFC 3339 instant. Must be after last_inbound_from.
See the full schema for validation rules.
limit (query, optional): Page size, 1-200 in canonical decimal.
Default 50. Minimum: 1. Maximum: 200. Default: 50.
cursor (query, optional): Opaque next_cursor from the previous
page of the same request. Repeat every other parameter unchanged; a cursor
from one sort order is refused for the other. Minimum characters: 1. Maximum
characters: 256.
No request body.
Successful response
| Field | Value | Details |
|---|---|---|
business_idRequired |
string (uuid) |
See the full schema for validation rules. |
environmentRequired |
"sandbox" | "live" |
See the full schema for validation rules. |
as_ofRequired |
string (date-time) |
The single instant used for every status in this response. |
window_hoursRequired |
4392 |
See the full schema for validation rules. |
statusRequired |
"all" | "open" | "expired" |
"opted_out"
|
See the full schema for validation rules. |
sortRequired |
"last_inbound" | "first_contact"
|
See the full schema for validation rules. |
countsRequired |
object |
See the full schema for validation rules. |
counts_as_ofRequired |
string (date-time) |
When counts were computed: as_of, or up to 60 seconds earlier for a business with 10,000 or more customers. |
match_countRequired |
integer |
Customers matching status plus any search or date filter; at most 10,000 when match_count_exact is false. Minimum: 0. |
match_count_exactRequired |
boolean |
False when a search or date filter matched more than 10,000 customers. |
contactsRequired |
array of object |
Maximum items: 200. |
next_cursorRequired |
string | null |
Pass as cursor for the next page; null on the last page. |
List customers you can message: response fields: counts
| Field | Value | Details |
|---|---|---|
openRequired |
integer |
Minimum: 0. |
expiring_within_7_daysRequired |
integer |
Open customers whose window closes within 168 hours of as_of. Minimum: 0. |
expiredRequired |
integer |
Minimum: 0. |
opted_outRequired |
integer |
Minimum: 0. |
totalRequired |
integer |
open + expired + opted_out. Minimum: 0. |
List customers you can message: response fields: contacts[]
| Field | Value | Details |
|---|---|---|
user_refRequired |
string |
Opaque reference scoped to this Business/environment/customer incarnation. Operation contact references are bounded to 256 UTF-8 bytes; the current POST recipient parser accepts only 1-96 ASCII identifier characters. References created by the private customer-entry primitive fit that parser. Minimum characters: 1. Maximum characters: 256. Maximum UTF-8 bytes: 256. |
first_contact_atRequired |
string (date-time) |
When this customer first opened the chat with the business. |
last_inbound_atRequired |
string (date-time) |
This customer's last message to you. |
window_expires_atRequired |
string (date-time) |
last_inbound_at plus 4392 hours. At or after this instant you can no longer message this customer until they message again. |
statusRequired |
"open" | "expired" |
"opted_out"
|
open: window_expires_at is after as_of and the customer has not opted out. opted_out takes precedence over the window. |
can_messageRequired |
boolean |
True exactly when status is open. |
opted_outRequired |
boolean |
The customer typed a stop word, turned replies off or blocked the business. |
opted_out_atRequired |
string | null (date-time) |
When the customer opted out; null when not opted out or for a block. |
phoneRequired |
string | null |
The verified number this customer's last message event delivered to you, while that identity is still current; null otherwise and always null for an opted-out customer. |
consentRequired |
object | null |
The customer's last agreement to your welcome terms, or null. Natter keeps only this marker; the full record was sent to you as the consent webhook event. Compare version with the current content_version. |
List customers you can message: response fields: contacts[]: consent
Variant 1
| Field | Value | Details |
|---|---|---|
versionRequired |
string |
The welcome content_version the customer agreed to. |
agreed_atRequired |
string (date-time) |
See the full schema for validation rules. |
Responses
200- Customers in this environment, with counts.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment (forbidden).
405- The method is not supported for this exact route.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
GET/v1/business/{environment}/contacts/exportDownload customers as CSV
Required permission: Customers (customers:read)
Every customer matching the filter, as one streamed text/csv file, using account:read. Takes the same status, sort, user_ref_prefix, phone and last_inbound_from/last_inbound_to parameters as the list, but no limit or cursor. The file is one consistent snapshot: every customer matching at X-Natter-As-Of appears exactly once with the status at that instant. Columns: user_ref, status, last_inbound_at, window_expires_at (empty for an opted-out customer), first_contact_at, phone (empty where the customer's last message event carried no number, and for an opted-out customer). Timestamps are RFC 3339 UTC. A business may start 10 exports per UTC hour across Live, Test, the API and the owner portal; the eleventh is 429 export_limit with Retry-After. A download can take up to ten minutes; if anything fails after the first byte the connection is closed before the end of the file, so treat an incomplete transfer as a failed export.
status (query, optional): Only customers with this status.
Default all. Default: "all".
sort (query, optional): last_inbound (default): most recent
last message first. first_contact: most recent first contact first. Default:
"last_inbound".
user_ref_prefix (query, optional): Only customers whose
user_ref starts with this value (case-sensitive). Pass a whole user_ref for
an exact match. Minimum characters: 1. Maximum characters: 96.
phone (query, optional): Only the customer whose verified
number is this E.164 number; finds only a customer whose number is returned
in phone. URL-encode the leading + as %2B, or omit it. See the full schema
for validation rules.
last_inbound_from (query, optional): Only customers whose last
message is at or after this RFC 3339 instant (microsecond precision at
most). See the full schema for validation rules.
last_inbound_to (query, optional): Only customers whose last
message is before this RFC 3339 instant. Must be after last_inbound_from.
See the full schema for validation rules.
No request body.
Successful response
Content-Type: text/csv. Header row
user_ref,status,last_inbound_at,window_expires_at,first_contact_at,phone,
then one row per customer.
Response headers
Content-Disposition- attachment; filename="natter-customers-<environment>-<YYYYMMDDTHHMMSSZ>.csv"
X-Natter-As-Of- The instant every row's status was computed at (RFC 3339).
Responses
200- The CSV file, streamed.
400- Invalid request body, header or noncanonical path. Oversized JSON also uses this response.
401- Missing, malformed, unknown or revoked Business API key.
403- The current key lacks the required permission or belongs to a different environment (forbidden).
405- The method is not supported for this exact route.
429- The business has started 10 exports in this UTC hour.
503- Candidate private HTTP resource controls: PostgreSQL validation and runtime publication are pending. The 16-request and 20-second limits below are not guarantees of the current deployed service. A dependency, transport deadline capability or required admission policy is unavailable, or the composed API's shared per-instance cap of 16 active requests is full. Local capacity refusal happens before authentication/body work and supplies Retry-After: 1. An early HTTP/1 refusal closes its connection without waiting for the remaining upload; HTTP/2 refusal is stream-scoped. This is not a tenant quota or paid throughput tier. Requests have an absolute 20-second read/write deadline, shortened by an earlier caller deadline; cancellation interrupts admitted I/O. A timeout does not prove that an action failed to commit: preserve the original send idempotency key/body on retry. Webhook PUT retry rotates its secret again. Distributed tenant/key/recipient rate limits, queue limits and fair scheduling remain commissioning gates. No success is implied by this error. temporarily_unavailable (D393): the key, scope, environment and number are allowed, but the business owner authority lease lapsed moments ago and has not been renewed yet (for example while a Natter or Masons Mail service restarts). Nothing was accepted. It always carries Retry-After: 5; retry the same request (a send with its original idempotency key and body) after that delay. A revoked or suspended owner, or a lease lapsed for more than two minutes, is refused with 403 forbidden instead and is not retryable.
Request headers
Use your Live key on your server, or a Test key with the sandbox route. Replace the key placeholder; never include it in a browser or phone app.
POST /v1/business/live/messages
Host: api.natterapp.com
Authorization: Bearer <your-business-api-key>
Content-Type: application/json
Idempotency-Key: booking-reply-001
Use the same idempotency key and unchanged body when retrying a send after a lost response. The JSON body example and retry rules follow below.
Connect your business
Create a business in the portal, verify its phone number, then open API keys and create a Live key, or select Test for a separate test connection. Copy the key once and keep it on your server. A lost key must be replaced; the portal does not reveal old credentials.
Open Webhooks and save the public HTTPS endpoint where your server accepts events. Copy the signing secret once and validate incoming events as described below.
Return to Overview, open the phone invitation on a consenting phone with the latest Natter app, review the business and start the conversation. The customer sends the first message. Use the opaque customer reference in that incoming webhook when submitting a reply; a phone number or an opened invitation does not grant permission.
Key creation, webhook configuration and actual message acceptance are separate steps. A copied link, saved URL or successful API request is not proof that the phone displayed a message. Use the receipt and event states below to track progress.
Credentials and environments
Requests use Authorization: Bearer <business-api-key>. The
route explicitly selects sandbox or live; the key must
belong to that environment. A key never becomes a consumer/device credential.
Every key has an access level. A full access key, the default
in the Business portal, may use every API capability; what it can actually do
follows the business's own settings: the feature switches (voice_calls
and video_calls for calls, the media switches for each attachment
type), status, environment, owner authority and selected number. Turning on
voice calls in Appearance & features lets the same key answer calls; no new
key is needed. A restricted key holds the permission groups the
owner chose, so each tool gets only what it needs (an accounting tool only
Billing, a CRM only Customers). Every operation belongs to exactly one group:
| Group | Name | Operations |
|---|---|---|
| Messaging & calls | messaging:manage |
Send text, media and voice messages, read receipts and received media, answer and end customer calls |
| Customers | customers:read |
List, search and export customers, including their phone numbers |
| Billing | billing:read |
Usage, pricing, billing, the ledger and invoices |
| Profile & appearance | profile:manage |
Read and update the profile, chat settings and images |
| Webhook | webhooks:manage |
Read, set, delete and test the webhook, and read its deliveries |
| Account info | account:view |
GET /account, numbers and keys |
A full key holds all six. The owner chooses the groups in the Business portal
after selecting a currently verified number. A restricted key from before the
groups existed that could send messages but not answer calls holds
messages:send, the messaging half of Messaging & calls (one
that could answer calls only holds calls:manage); these were kept
so no key gained anything when the groups arrived, and they are never issued
again. Only the signed-in owner can change a key's access or groups, in the
Business portal; no API request changes access or permissions, and the key's
secret stays the same. Retried creation requests never change a key's access
silently. Answering customer-initiated calls needs a full key or a restricted
key with Messaging & calls. Neither permits the business to start a call or
contact a new customer. There is one current key and one selected sender number
per business environment. Replacing a key revokes the old key; removing its
selected number disables that connection. Additional verified numbers do not
create multiple active senders. The request does not take an arbitrary sender
number. This does not enable paid billing. Revocation and current permissions
are checked on every request and again before accepting a new send into the
queue.
Test uses the API environment name sandbox. Its contacts, keys,
endpoint identities, webhooks and accounting remain separate from Live. The
phone invitation and webhook must use the same environment as the key. Both
require explicit customer consent and the first customer message. Live sending
needs a fresh configured price, which is zero today; a missing or stale price
holds new sends and dispatch. A positive price is paid from the business's
prepaid credit or, in monthly invoice mode, its credit limit; a send it cannot
cover is refused with 402 payment_required. Account, pricing,
usage, billing and webhook maintenance remain accessible during a price hold.
Free Live usage is still recorded in the accounting ledger. No card or paid
subscription is required while the price is zero.
Read your account
GET /v1/business/{environment}/account requires Account info
(account:view).
The response contains business_id, environment,
name, status, sender_number_id,
capabilities and, when set, status_reason. The sender
ID is an opaque identifier, not the phone number. A suspended business can still
read its own status. It cannot send a new message. The response contains no
provider credentials or hosted device identity.
capabilities always includes text. Enabled settings
add image, document, audio,
video, voice_calls or video_calls. The
corresponding settings are images, files,
voice_messages, videos, voice_calls and
video_calls. All six switches default to false. This list reports
enabled features, not the current key's permissions or permission to contact
a particular person. An enabled call button is answered by a full-access key, or
by a restricted key with Messaging & calls.
The response also contains country (the saved profile address
country, or null), environments (the environments this business has
commissioned) and features (this environment's six feature
switches, as in GET settings).
GET /v1/business/{environment}/numbers and
GET /v1/business/{environment}/keys also require Account info
(account:view) and are read-only. Numbers list each
number_id with its label, country,
status (verified or pending) and the
sender_environments that send from it; the phone number itself is
never returned. Keys list this environment's keys, newest first and
including revoked ones, with label, access (full
or restricted), permissions (the key's groups; a
full key lists all six), scopes (compatibility: the scope names
used before the groups, each listed only when the key may still call everything
it used to allow, so a full key lists account:read,
messages:send, webhooks:manage,
profile:write and calls:manage, and a key with
Messaging & calls lists calls:manage), created_at,
last_used_at, revoked_at and a
prefix such as natter_1a2b3c4d;
current marks the key that made the request. No secret, hash or
salt is returned. Add, verify or remove numbers and create, replace or revoke
keys in the Business portal.
Read pricing and usage
GET /v1/business/{environment}/pricing and
GET /v1/business/{environment}/usage require Billing
(billing:read), as do billing, the ledger and invoices. No separate
billing credential or OAuth application is needed. Keep the key on your server.
An accounting tool can have a restricted key with only Billing.
Pricing returns the business and environment plus pricing: currency
EUR, outbound_message_micro_eur,
revision, sending_enabled and
payment_required. One euro is 1,000,000 micro-euros. Null pricing
means it could not be established; never treat null as free. The configured zero
price still creates one durable usage event per accepted outbound message.
Retrying the same accepted operation does not charge twice.
Accepted media messages also create exactly one ledger event, including their media type and byte count; current pricing is zero. An upload without a message submission is not an accepted message. Calls are not billed as message units. These accounting facts do not claim a separate call-usage or media-breakdown report that is not returned by the usage API.
Usage returns month-to-date UTC ledger totals and daily entries, with a separate rolling seven-day operational period. Traffic counts cover incoming messages, submitted and accepted outbound messages, failures, read events, pending work and retry attempts. Processing timings measure submission to server acceptance for accepted outbound samples only. They do not measure phone display, network delivery or human reading time. No samples returns null latencies rather than a fabricated zero.
Webhook pending counts describe the current retained queue. Failed counts cover failures retained in the last seven days; retries count attempts among the retained queue and those recent failures. Successful webhook payloads are erased, so these figures are not lifetime webhook delivery totals. Reports contain no messages, phone numbers, customer references, IP addresses or locations. No payment-method or balance data is fabricated when payments have not been commissioned.
Read and update your public profile
GET /v1/business/{environment}/profile requires Profile &
appearance (profile:manage).
This returns the public profile saved in the Business portal:
display_name, description, website,
support_email, support_phone,
address (street, city,
postal_code, country) and up to five
links (label, url). Optional fields are
empty strings or arrays. Your public display name is separate from the
registered name returned by the account endpoint.
The response body stays flat; its revision is in the strong
ETag header, for example
"business-profile-00000000-0000-4000-8000-000000000001-3". Treat that value as opaque and keep it with the business it belongs to. Live,
Test and the portal edit the same saved profile; the key still
must match the route's environment. Responses use
Cache-Control: no-store.
PUT /v1/business/{environment}/profile requires Profile &
appearance (profile:manage),
Content-Type: application/json and exactly one
If-Match header containing the last ETag. Send all public fields;
use an empty string or array to clear an optional field. For example:
{
"display_name": "Example support",
"description": "Talk to our support team.",
"website": "https://business.example",
"support_email": "support@business.example",
"support_phone": "",
"address": { "street": "", "city": "", "postal_code": "", "country": "" },
"links": []
}
A successful update returns HTTP 200 with the saved public body and a new ETag.
A missing If-Match returns HTTP 428
profile_revision_required; weak, wildcard, multiple or
wrong-business validators return HTTP 400 invalid_request. A
concurrent portal or API edit returns HTTP 412
business_profile_changed. Read again, reconcile the changes and
submit with the new ETag. After a lost response, read before retrying. The
portal uses the same revision with its existing HTTP 409 conflict response.
New writes accept at most 32,768 body bytes and 15,360 bytes for the compact
public object serialized with JSON.stringify-compatible UTF-8 encoding. This
leaves room inside the existing 16,384-byte database record limit. Existing
larger saved public records remain readable. Text limits count UTF-16 code
units: name 2–128 (also at most 256 UTF-8 bytes), description 1,000, street 200,
city 100, postal code 32 and link label 40. Empty optional text is allowed. URLs
must start with https://, contain a host, no credentials,
whitespace or backslashes, and any explicit port must be 1–65535. URLs are
limited to 2,048 UTF-8 bytes. Email addresses use the portal's practical
mailbox format. Invalid Unicode, duplicate, unknown, null or missing fields are
rejected, including nested address and link fields.
The private operational contact is never returned, accepted or changed through this API. A listed support number is not automatically verified or enabled for messaging. There is no self-declared company-verification badge. Current key permission, owner authority and the selected verified sender number are checked at the database transaction, including after lock waits.
The Natter phone reads public details through its authenticated existing business conversation. The response includes the business, environment and revision, never the private contact. Blocked, stopped and expired messaging relationships may still view this public information; reading does not unblock, renew a messaging window or grant sending permission. It cannot discover unrelated accounts and grants no Feed, Atlas or location access.
Appearance and feature controls
GET /v1/business/{environment}/settings requires
profile:manage. The response contains the exact business and
environment, revision, updated_at and
settings. Keep its ETag with that document. These
settings are separate for Live and Test; the saved textual profile remains
shared by both environments.
settings.features has six switches: images,
videos, files, voice_messages,
voice_calls and video_calls. All default to false. The
phone hides or disables unavailable actions and the server checks current
permissions again. A visible button does not bypass revocation or a later
setting change. Calls are customer-initiated; the business answers through its
authorized calling integration.
settings.visibility independently controls
description, website, support_email,
support_phone, address, country and
links. These default to true. Address controls street, city and
postal code; country has its own switch. Hidden values remain saved for the
owner and authorized API reads, but are removed from customer responses.
Visibility never exposes the private business contact or creates a verification
badge.
settings.appearance contains avatar_asset_id,
chat_wallpaper_asset_id and
profile_background_asset_id, each an uploaded asset UUID or null.
It also contains accent_color, background_color,
incoming_bubble_color and outgoing_bubble_color, each
in six-digit #RRGGBB format. The business controls its own chat
appearance. Customers retain their accessibility, mute, block and report
controls.
PUT /v1/business/{environment}/settings requires
profile:manage. Send a complete JSON object containing
expected_revision and settings, plus the last strong
settings ETag in If-Match. The body limit is 24,576 UTF-8 bytes.
Unknown, missing, duplicate or null fields are rejected. A successful response
returns the next revision and ETag. Missing If-Match returns 428
precondition_required; a header inconsistent with the body revision
returns 412 precondition_failed; a concurrent saved change returns
409 business_settings_changed. Read and reconcile before
deliberately retrying. Profile and settings writes have independent revisions; a
partial save must not be presented as complete.
Welcome and terms screen
The Natter app always shows its own fixed Business notice the first time a customer opens your chat. You cannot change or remove it. After it, the app can show one more screen that you set: a short welcome text with optional links to your terms and privacy policy.
GET /v1/business/{environment}/welcome requires
profile:manage. The response contains the exact business and
environment, revision, updated_at,
welcome, content_version and
agreements_valid_from. welcome is null when you have
no welcome screen. Live and Test are saved separately. Keep the response
ETag with that document.
welcome has five fields: text (plain text, 1 to 1000
UTF-16 units, no markup), terms_url and
privacy_url (https links, or null),
require_agreement and reask_on_change. With
require_agreement the customer must tap "Agree and
continue" before chatting. Without it the screen is shown once, for
information.
content_version is the SHA-256 of the text and both links:
hex(SHA-256("natter-welcome-v1" NUL text NUL terms_url NUL
privacy_url)), an absent link being empty. Changing the text or a link changes it. With
reask_on_change, customers who agreed to an earlier version must
agree again; without it, earlier agreements still count.
agreements_valid_from is the instant from which an agreement
counts.
PUT /v1/business/{environment}/welcome requires
profile:manage. Send expected_revision and
welcome (or null to turn the screen off), plus the last welcome
ETag in If-Match. The body limit is 16,384 UTF-8 bytes. The
preconditions and the 409 business_settings_changed conflict work
as for settings. Each save is one of the day's text saves.
Natter keeps only a marker per customer: the version they agreed to and when. It
sends you the full record once, as the signed consent event below,
and lists the marker as consent on each customer in
GET /v1/business/{environment}/contacts. The marker is deleted with
the contact, the customer's account or your business.
While require_agreement is on, a customer without a current
agreement cannot message you (the app shows your screen first and the server
refuses their messages), and your sends to them are refused with 403
consent_required. Wait for their consent event.
Upload profile pictures and wallpapers
POST /v1/business/{environment}/assets/{kind} requires
profile:manage. Use avatar,
chat_wallpaper or profile_background as the kind. Send
raw PNG/JPEG bytes with the matching Content-Type. This is not JSON, base64,
multipart or an external image URL. The limit is 2 MiB, 4 megapixels and 4096
pixels per side; a normalized avatar is limited to 512 KiB. Decoding and
re-encoding removes image metadata.
A successful upload returns 201 with id, kind,
content_type, bytes, width and
height. Uploading alone does not publish the image: save its ID in
the corresponding appearance setting. Only an asset belonging to the same
business, environment and kind is accepted. Unselected uploads expire after 24
hours. Each business/environment is limited to 512 assets or 256 MiB.
GET /v1/business/{environment}/assets/{id} requires
profile:manage and returns an owned, unexpired image.
DELETE on that route requires profile:manage and
returns 204 after deletion, or 409 business_asset_in_use while a
current or retained settings revision references it. Previous images may remain
briefly available to the owner for bounded rollback; customers can fetch only
the currently selected images through their authenticated conversation. A
profile read never grants access to another business's assets.
Submit a text reply
POST /v1/business/{environment}/messages requires Messaging &
calls (messaging:manage),
Content-Type: application/json and one
Idempotency-Key header.
{
"to": "user_ref:<opaque-customer-reference>",
"type": "text",
"text": "How can we help?"
}
The reference comes from this business's contact in the same environment. Treat it as opaque. This endpoint does not resolve arbitrary phone numbers or create permission to contact someone. The customer must have started the relationship and sent an eligible message in the last 183 elapsed days. Opt-out and independent blocks still apply. Merely opening a chat, delivery/read receipts and background updates do not renew that window.
Text is limited to 4,096 UTF-16 code units and the JSON body to 32,768 bytes. Empty text, unknown fields, duplicate JSON keys, differently capitalized field names and unsupported content types are rejected. Query parameters and encoded path aliases are not accepted.
The first accepted submission returns HTTP 202 and a receipt:
{
"message_id": "00000000-0000-4000-8000-000000000001",
"conversation_id": "<opaque-conversation-reference>",
"user_ref": "<opaque-customer-reference>",
"state": "queued",
"created_at": "2026-10-12T12:00:00Z",
"idempotency_expires_at": "2026-10-13T12:00:00Z"
}
Queued means the durable request exists. It does not mean the relay or phone
received the message. After relay acceptance, the receipt becomes
state: accepted with status: sent; an authenticated
customer read can advance it to status: read. A terminal failure
uses state: failed. The Business status contract has no
delivered event.
Retry the exact body with the same key after a lost response. Within the
idempotency window, a matching request returns HTTP 200 and the same operation
ID, with its current status. Changing the recipient/text under that key returns
idempotency_conflict. The original key remains bound to the
operation; another API key does not take ownership of its duplicate submission.
Retries never extend the original deadline or authorize a new send after
permission changes. After the 24-hour window, a retained duplicate returns
idempotency_expired. Do not rely on deduplication beyond that
window.
Upload and send message media
Enable the matching feature in Appearance & features, or through the settings API. Media permission is checked in both directions, including after upload and before a decrypted read. Changing a setting, removing a number, revoking a key or losing customer permission can prevent a later step even when upload succeeded.
POST /v1/business/{environment}/media requires
messaging:manage. Send multipart/form-data with
exactly two parts in this order:
metadata JSON first, file binary second. The part names are
metadata and file. Use
application/json for the metadata part and the matching MIME type
for the file. There is no base64, remote URL or Content-Encoding. Extra parts
are invalid. The metadata JSON is limited to 16,384 bytes.
For example, the first part for a JPEG is:
{
"to": "user_ref:<opaque-customer-reference>",
"type": "image",
"name": "booking.jpg",
"mime_type": "image/jpeg",
"width": 1200,
"height": 800
}
Follow it with the actual JPEG bytes as the file part. The
image's dimensions and MIME must match the bytes. All types require a safe
filename of at most 255 UTF-8 bytes, without surrounding whitespace, path
separators, control characters or bidi override/isolate characters. The names
. and .. are invalid.
| Type | Feature setting | File format and limit | Additional metadata |
|---|---|---|---|
image |
images |
JPEG or PNG, at most 26,214,400 bytes (25 MiB) |
width and height, each 1..16384, at most
40,000,000 pixels together.
|
document |
files |
Non-image/audio/video MIME, at most 26,214,400 bytes |
Lowercase mime_type with no parameters, at most 127 bytes.
|
audio |
voice_messages |
audio/mp4, at most 26,214,400 bytes |
duration_ms 1..3,600,000 and exactly 48 integer
waveform levels, each 0..1024.
|
audio |
voice_messages |
audio/ogg (Opus), at most 26,214,400 bytes, 1 hour |
None: omit duration_ms and waveform; the
server measures them.
|
video |
videos |
video/mp4, at most 47,185,920 bytes (45 MiB) |
width, height, at most 40,000,000 pixels, and
duration_ms 1..60,000.
|
Audio and video must be complete MP4 files containing ftyp and
mdat. For audio/mp4, duration and waveform are bounded
metadata supplied by your integration, not generated by this upload endpoint.
Omit fields that do not apply to the chosen media type. A transfer has a
120-second absolute deadline; the complete multipart body is limited to
47,218,688 bytes.
A voice note can also be uploaded as Ogg/Opus, the format WhatsApp and most
text-to-speech engines produce: type audio,
mime_type audio/ogg (or
audio/ogg; codecs=opus), and no duration_ms or
waveform. The server checks that the bytes really are one complete
Ogg/Opus stream (the declared type is not trusted), converts it once, before
encryption, to mono AAC-LC in an m4a at 24 kHz and 32 kbit/s, measures the
duration and the 48-level waveform from the decoded audio, and stores and
delivers only the converted file. The response therefore shows
mime_type audio/mp4, the name ending in
.m4a and the converted bytes. Vorbis or FLAC in Ogg,
Opus in WebM, an MP4, and damaged or truncated files answer
415 unsupported_audio; a note over one hour answers
422 audio_too_long. audio/mp4 uploads are unchanged.
HTTP 201 returns media_id, type, name,
mime_type, bytes, state: ready,
expires_at, and dimensions/duration where applicable. Upload alone
does not send a message. With the
same API key and customer reference, submit the handle to the
normal message route with a new Idempotency-Key:
{
"to": "user_ref:<opaque-customer-reference>",
"type": "image",
"media_id": "00000000-0000-4000-8000-000000000002",
"text": "Your booking details."
}
The optional text is a caption with the same 4,096 UTF-16-unit
limit as outbound text; omit it or use an empty string for no caption. The
message type must match the upload. The JSON body still has a 32,768-byte limit.
New sends return 202; exact retries return 200 and the same receipt. Retry with
the unchanged recipient, type, media ID and caption, using the same API key and
idempotency key. A replacement API key cannot adopt an old upload. The normal
contact window, block checks and sent/failed/read semantics apply.
Handles and sealed private descriptors last no more than 24 hours, and no longer
than the underlying upload/media lifetime. Use the returned
expires_at; do not assume a full day. Accepted outbound delivery
erases the private descriptor. Temporary media storage is not a retained
chat-history service. Profile artwork uses the separate
assets routes and cannot be submitted as message media.
Receive and acknowledge customer media
An inbound message webhook has the same envelope as text. For
media, data.type is image, document,
audio or video, data.text is its caption
(possibly empty), and data.media contains the media metadata and
temporary handle. Existing text events remain unchanged and omit
media. No webhook includes raw file bytes, public media URLs,
native account/device IDs, encryption keys or the private descriptor.
GET /v1/business/{environment}/media/{media_id} requires
messaging:manage and returns the current metadata. Any current key
with Messaging & calls for this business and environment can retrieve an
admitted inbound handle; outbound metadata remains private to the uploading key.
GET /v1/business/{environment}/media/{media_id}/content requires
messaging:manage and returns inbound bytes only.
Outbound upload content is not exposed. Check the validated Content-Type,
attachment Content-Disposition, Content-Length and
X-Natter-Media-ID. Received image metadata may include WebP as well
as JPEG/PNG. Retrieve and durably store required bytes before expiry; a 2xx
webhook response does not download or acknowledge them. Current owner, key,
number, contact and feature permissions are checked before returning decrypted
content. A successful content GET is not an ACK.
After durable handling, send no body to
POST /v1/business/{environment}/media/{media_id}/ack with
messaging:manage. HTTP 204 acknowledges only the hosted endpoint
target and removes its private descriptor and handle. It does not acknowledge
other recipients. The handle then becomes unavailable; a repeat ACK can return
404. Encrypted relay chunks follow their existing recipient-ACK or expiry
cleanup. Without ACK, the private handle expires within its original bounded
lifetime.
Answer customer-initiated calls
Enable voice_calls and/or video_calls in the matching
environment. A full-access key can then answer straight away. A restricted key
needs Messaging & calls: choose Restricted and
Messaging & calls when creating or replacing the key, or
with Change access on the keys page. All call routes below
require that permission, the current verified selected number and the
customer's current relationship/permission. Turning the feature off ends a
ringing call (410 call_ended for the same key); turning it on again
does not revive it. Feature permissions are checked against the authenticated
offer, not a caller-supplied type flag.
The customer starts the call from Natter. There is no Business create-call route, Business-initiated incoming call, call waiting, renegotiation or format-2 offer support in this integration. Your business supplies its own WebRTC engine to handle SDP, ICE and audio/video. Natter does not generate the SDP answer, synthesize audio or run your agent's media engine. Streaming text/audio messages are separate and are not available.
A signed kind: call webhook announces a ringing call. It contains
no SDP or ICE. If no webhook is configured, poll
GET /v1/business/{environment}/calls; the response is
{"calls":[]} or at most eight current calls. There is no
call-history endpoint.
| Step | Request | Result and next action |
|---|---|---|
| Read offer | GET /calls/{call_id} |
Authenticates the customer offer and returns offer_sdp,
offer_transaction, media, current state and
expiry. It does not answer.
|
| Prepare WebRTC | GET /calls/{call_id}/turn |
Short-lived uris, username,
password, ttl in seconds and
expires_at. Configure your WebRTC engine and apply the
customer SDP.
|
| Answer | POST /calls/{call_id}/answer |
Send your generated SDP answer with a unique transaction;
returns a committed call receipt.
|
| Receive ICE | GET /calls/{call_id}/signals?after=0 |
Apply authenticated customer candidates to WebRTC; continue with
next_after.
|
| Send ICE | POST /calls/{call_id}/signals |
Send your candidates, referencing the successful answer transaction. |
| Keep active | POST /calls/{call_id}/heartbeat |
No body. The answered endpoint renews its 180-second lease; other participant/deadline checks still apply. |
| Finish | POST /calls/{call_id}/end |
Use decline or hangup with a unique
transaction, then stop local WebRTC, polling and heartbeats.
|
Every abbreviated path in this table starts with
/v1/business/{environment}. GET and heartbeat requests have no body
or transfer encoding. Read the call's expires_at, which tracks
the current ringing, setup or active-call deadline. Heartbeat before it expires;
an elapsed lease is not renewed by continuing to poll. TURN credentials last at
most 600 seconds and must be treated as secrets. Do not log SDP, ICE or TURN
credentials.
The answer body is
{"transaction":"answer_001","sdp":"<your
WebRTC SDP answer>"}. Use a distinct opaque transaction of 1..96 ASCII letters, digits, underscore
or hyphen; the bc_ prefix is reserved. SDP must start
v=0, be valid UTF-8 without NUL and contain at most 32,768 bytes.
The complete answer request is limited to 131,072 bytes. Keep your actual SDP
unchanged when retrying; the placeholder is not a valid working answer.
A completed ICE gathering message with no remaining candidates is:
{
"transaction": "signal_001",
"description_transaction": "answer_001",
"candidates": [],
"complete": true
}
Otherwise supply up to 16 candidates. Each contains a nonempty
candidate string of at most 4,096 UTF-8 bytes; optional
sdp_mid (64 bytes), sdp_mline_index (0..31) and
username_fragment (256 bytes) can be null. Supplied strings cannot
be empty or contain control characters. The complete signal request is at most
32,768 bytes and its normalized ICE payload at most 16,384 bytes. There are at
most 512 outgoing signal frames per call. An empty candidate array requires
complete: true; description_transaction must match the
successful answer transaction. Incoming customer signals refer to
offer_transaction.
Signal reads return signals and next_after, with at
most ten signals per page. The only permitted query is one canonical
after integer from 0 to 512; no signs, leading zeros, duplicate
parameters or percent encoding. Omit it for the first page or use
after=0.
Always continue from `next_after`, even when `signals` is empty: the cursor can advance past discarded entries. Stop polling when the call
ends or expires.
End a call with:
{ "transaction": "end_001", "reason": "hangup" }
The end body is limited to 1,024 bytes. Answer, signal and end return
call_id, transaction, action,
applied_at, ring_expires_at, sequence,
replayed, claim_transaction and
setup_expires_at; the last two can be null. Call actions use their
body transaction, not the messaging Idempotency-Key header. After a
lost response, retry the same transaction, key, call, action and unchanged body.
A matching retry has replayed: true; a changed request conflicts.
Retained end receipts can replay even after private call state is erased,
subject to current authority and receipt retention.
Call SDP and traffic keys are sealed at rest and erased at terminal transition or their bounded expiry, no later than 24 hours for private call state. Native call/signaling and receipt lifetimes still apply. A successful API answer is not proof that audio reached either participant; verify the WebRTC connection and media on both ends.
Read a message receipt
GET /v1/business/{environment}/messages/{message_id} requires
messaging:manage.
The response has the same receipt shape as submission. Lookup is scoped to the current business and environment. An unknown, expired or foreign operation returns the same 404. The receipt is available for at most 30 days from creation. It contains no message body, phone number, native account/device ID or encryption material. This is an operation receipt, not a chat-history endpoint.
Configure a webhook
GET, PUT and
DELETE /v1/business/{environment}/webhook require
webhooks:manage. Configuration remains accessible while sending is
paused, suspended or banned. Maintaining a destination never grants permission
to send messages.
PUT accepts only a url field in a JSON body of at most
16,384 bytes:
{ "url": "https://business.example/natter/events" }
The destination must be public HTTPS. Private, local and other excluded
addresses are refused; redirects are not followed. Saving validates the
destination without sending a test message. Every successful
PUT rotates the signing secret, including a repeated request with
the same URL. Save the returned signing_secret before leaving the
response. It is returned once. If that response is lost, rotate again;
GET cannot recover it.
The response includes webhook metadata (url,
config_revision, rotated_at) and the one-time secret.
GET returns {"webhook":null} if no
destination exists, otherwise the metadata without any secret.
DELETE returns 204 whether the destination existed or not. Neither
action affects another business or environment. Replacement and deletion
invalidate outstanding deliveries for the previous configuration.
POST /v1/business/{environment}/webhook/test (no body) sends one
signed test event to the configured destination through the same
delivery path as customer events and returns the attempt:
http_status, latency_ms, error and
delivered. A test is a single attempt and is not retried. It
returns 409 webhook_not_configured without a destination and 429
webhook_test_limited after ten tests in a minute. The
test event has only id, kind,
created_at, business_id and
data.environment: verify its signature, return 2xx and discard it.
GET /v1/business/{environment}/webhook/deliveries lists the last 50
attempts of this environment from the last seven days, newest first, with event
kind and ID, attempt number, HTTP status, latency and failure code (http_failed
or transport_failed). Payloads are never stored.
Receive signed events
The dispatcher sends message, status and
call events, and a test event when you ask for one.
Business status events are sent, read or
failed; there is no Business delivered status. These
events use the same sandbox/live partition as the destination. The exact schemas
are in openapi/business-hosted.v1.json.
Validate X-Natter-Signature before parsing or acting on the JSON
body. Its format is t=<unix-seconds>,v1=<hex-hmac>,
where the HMAC is SHA-256 of the timestamp, a literal period and the
unchanged raw request body. Use the returned signing-secret
string as the key bytes, without base64-decoding it. Compare signatures in
constant time and reject timestamps outside a short tolerance appropriate for
your server clock.
Use X-Natter-Event-Id to deduplicate a successfully processed
event. A retry keeps the event ID and body, while its signature timestamp
changes. Return 2xx only after your system durably accepts the event. A timeout
or lost response can cause a repeat even when your system received the previous
attempt. HTTP redirects are failures, not permission to send the event
elsewhere.
Verified number on inbound messages
Every inbound message event includes both
data.phone and data.identity_epoch whenever the
customer has a verified number. This applies to every business, to text and
media, in sandbox and live; there is no setting to request or approve.
phone is canonical E.164, starting with +, obtained
from successful Natter SMS or WhatsApp verification. A typed profile/support
number, development code or unverified registration attempt is never a source.
No number lookup is added to the outbound API; keep replying with the existing
user_ref.
identity_epoch is an opaque lowercase UUIDv4 string. Compare it for
equality; do not interpret or generate it. It stays stable across ordinary
sessions for the same active number binding. A new account or successful number
verification on a different device creates a new epoch. Ordinary reconnect and
an owner-approved linked device do not. This tracks verified possession
continuity, not a legal identity or a guarantee about who is holding an unlocked
phone. Never retain an old external account link solely because a later event
has the same number with a different epoch. Do not downgrade that guard if an
older retry arrives after a new epoch.
Natter snapshots the verified identity at the original server acceptance time.
Retries keep those exact signed body bytes. An identity that is no longer
current is fenced from dispatch; it is never replaced with another holder's
number or epoch. A customer without a verified number on record cannot message a
business: the app is refused with number_verification_required and
asks them to verify. Every message event therefore carries both fields; only a
message accepted before Business API 0.9.0 can lack them. Natter never
fabricates an identity or emits one field alone. Explicit customer stop/block
controls remain available.
The app explains before messaging: When you message this business, it receives your verified phone number. Existing conversations show that disclosure before the first send with the updated app. No external account-link button is required.
Accept both exact message shapes, with the pair and (for messages accepted before Business API 0.9.0) without it. Queued messages are never rewritten. An enriched event always contains both fields. Status events, call events, envelope fields and signing rules stay unchanged. A business that misuses customer numbers is paused or suspended under the business terms, which stops its message events.
The following synthetic event is formatted for readability. The signature covers
the exact compact UTF-8 raw_body in
docs/examples/business-inbound-identity.signed.json, with no
trailing newline. The number, identifiers and secret are fake. Its signing
secret is example-only-not-a-real-signing-secret, timestamp is
1792152000, and X-Natter-Event-Id is
d67d6379-2ba3-5b8f-b350-34259173bfae.
X-Natter-Signature is
t=1792152000,v1=3abb44b88d0da429829ae41115d23c67f0c668a9a392ca6767315c31d87f59ef.
{
"id": "d67d6379-2ba3-5b8f-b350-34259173bfae",
"kind": "message",
"created_at": "2026-10-16T12:00:00Z",
"business_id": "00000000-0000-4000-8000-000000000004",
"data": {
"message_id": "00000000-0000-4000-8000-000000000001",
"conversation_id": "example_conversation",
"user_ref": "example_user",
"type": "text",
"text": "Hello",
"received_at": "2026-10-16T12:00:00Z",
"phone": "+12025550123",
"identity_epoch": "de84b9a7-9299-4e34-98e0-48e9f107d8e3"
}
}
The downloadable source fixture is
docs/examples/business-inbound-identity.signed.json. Its raw-body
signature is checked by the documentation tests. It is an example, not an event
to replay against a live system.
Webhook acceptance and call events
When an inbound message receives 2xx, the worker atomically records a pending Delivered receipt for the customer's phone and removes that webhook delivery. The runtime dispatches that receipt through the same guarded native lane as other outbound work. It does not assert that a person at the business read the message. A lost HTTP response or failed database commit cannot establish this receipt. Subsequent opt-out can prevent a repeat, so this boundary cannot guarantee a customer tick for every event the business may have received.
Media message events require the separate content download and explicit media ACK above. The normal message webhook acknowledgement does not replace that ACK.
An incoming-call notification uses the same signing secret, HMAC bytes, event-ID deduplication and retry mechanism:
{
"id": "00000000-0000-4000-8000-000000000003",
"kind": "call",
"created_at": "2026-10-16T12:00:00Z",
"business_id": "00000000-0000-4000-8000-000000000004",
"data": {
"call_id": "call_example",
"conversation_id": "opaque_conversation",
"user_ref": "opaque_customer",
"state": "ringing",
"expires_at": "2026-10-16T12:00:30Z"
}
}
This notification carries no SDP, ICE, media keys or native customer identity. It is enqueued with call creation; stale or ended rings are discarded before dispatch. A received notification grants no permission to answer. Read the call through the authenticated API to check current authority, state and expiry. Calls do not emit message-status events.
Consent events
When a customer agrees to your welcome terms, you receive a signed
consent event with the same signing secret, HMAC bytes, event-ID
deduplication, retries and delivery log as every other event:
{
"id": "00000000-0000-4000-8000-000000000005",
"kind": "consent",
"created_at": "2026-10-18T12:00:00.123456Z",
"business_id": "00000000-0000-4000-8000-000000000004",
"data": {
"conversation_id": "opaque_conversation",
"user_ref": "opaque_customer",
"phone": "+46700000000",
"identity_epoch": "00000000-0000-4000-8000-000000000006",
"profile_name": "Ana",
"agreed_at": "2026-10-18T12:00:00.123456Z",
"version": "0efcf777b95a8c3e27d00757f145997ada20d96626684e360c06a9dedb9bf2c5",
"welcome_text_hash": "ebb203f6dbbc71e0a7fe99ddd19bb17ad6678c33df6545dd6f2d03dd2e5e1a93",
"terms_url": "https://example.com/terms",
"privacy_url": null
}
}
phone and identity_epoch are the customer's
verified number and epoch, as on message events. profile_name is
present only when the customer's app shared a display name.
version is the content_version they agreed to and
welcome_text_hash is hex(SHA-256(text)) (this example
is for the text "Welcome to Example."). Store the event if you need
the record: Natter keeps only the version and time. Agreeing again to a version
that still counts sends nothing.
Errors
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request |
Body, header or path is invalid. |
| 401 | invalid_api_key |
Key is absent, malformed, unknown or revoked. |
| 403 | forbidden |
Scope or environment does not match, or the owner withdrew access. |
| 403 | business_paused / business_suspended |
New sending is disabled for the account. |
| 403 | no_relationship |
Current customer permission does not allow this send. |
| 403 | consent_required |
The customer has not agreed to your current welcome terms. |
| 404 | not_found |
Route or scoped receipt is unavailable. |
| 405 | method_not_allowed |
Use the method in the Allow header. |
| 409 |
idempotency_conflict / idempotency_expired
|
The supplied request key cannot admit this request. |
| 403 | call_not_allowed |
Current call, feature or customer authority rejects the action. |
| 409 | call_busy / call_changed |
Call state changed; read it before deciding what to do next. |
| 410 | call_ended |
Call is terminal or expired; stop WebRTC and polling. |
| 422 | invalid_call_packet |
The authenticated native call/signaling packet could not be admitted. |
| 429 | call_limit |
Call/signaling/TURN limit; respect Retry-After: 1 while the
call is active.
|
| 503 | business_unavailable |
A required service or admission check is unavailable. |
| 503 | temporarily_unavailable |
Owner authority is renewing; nothing was accepted. Retry after
Retry-After.
|
Responses use Cache-Control: no-store. Errors never include
database/provider internals.
Owner authority renewal (D393): every request needs the
business owner's current authority, which Masons Mail renews every few
seconds. While a Natter or Masons Mail service restarts that renewal can lapse
briefly. A request that would otherwise be accepted is then refused with 503
temporarily_unavailable and Retry-After: 5; nothing
was accepted or written. Retry the same request after that delay (a send with
its original Idempotency-Key and unchanged body), as you would any
503, and do not treat it as a permanent refusal. A key without the permission or
environment, a lost business number, an owner who revoked or suspended access,
or a lapse longer than two minutes is refused with 403 instead; do not retry a
403.
Request limits: the hosted runtime applies the following bounds. A sandbox key does not bypass them or enable live sending.
The composed API admits at most 16 simultaneous requests per API instance,
shared by all methods and resources. A full instance immediately returns 503
business_unavailable with Retry-After: 1; it does not
wait for a slot or begin authentication/body processing. An early refusal closes
its HTTP/1 connection without waiting for the remaining upload; HTTP/2 keeps the
refusal scoped to its stream. This is a technical capacity bound, not a
per-business allowance or paid throughput tier. Ordinary requests have an
absolute 20-second read/write deadline. Media upload and content download have
an absolute 120-second deadline and also share at most two simultaneous transfer
slots per API instance, within that same 16-request pool. Deadlines are
shortened by an earlier caller deadline. Cancellation interrupts admitted I/O.
Middleware must preserve deadline support; an unsupported transport is refused.
A timeout can follow a committed action: retry a send with its original
idempotency key and body, and remember that retrying webhook PUT rotates the
secret again.
Distributed tenant/key/recipient rate limits, fair worker scheduling, expiry cleanup and restore checks remain commissioning requirements. The local request cap does not bound queued work or establish those runtime guarantees, and private tests do not replace actual device acceptance.
Supported features
Enabled features support image, document, audio and video messages plus customer-initiated voice/video calls. Upload message media before sending its handle; explicitly acknowledge received media after durable handling. Calls need a full-access key (or a restricted key with Messaging & calls, messaging:manage; older converted calls:manage keys also work), the voice or video call feature turned on and your own WebRTC engine. Profile images, wallpapers, visibility and feature permissions use the same settings as the portal. Streaming text/audio and business-initiated calls are not available in this release.
Business API access does not include personal Feeds, Atlas or customer locations. The Natter Business API is free for now. It is intended to become a paid service at a planned price of €0.001 per message (one tenth of a cent), set only to cover running costs. We will confirm prices at least 30 days before any charges begin, and nothing you use before then will be charged.
OpenAPI downloads
Business API · OpenAPI JSON
Version 0.10.0. The Business API and signed event schemas described on this
page.
Natter app/provider v1 reference · OpenAPI JSON
The separate app/provider API contract. The unused Matrix Business routes
were removed. Use the Business API above for Business integration.