NatterBY MASONS MAILNatter for business
On this page
Natter for business

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.

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.