Skip to main content
Everything the API accepts and returns is JSON, and every response uses one of two shapes. This page is the contract the individual endpoint pages assume.

Making a request

  • Base URL https://api.watx.in/v1.
  • Send Authorization: Bearer <key> on every request. See Authentication.
  • Send Content-Type: application/json on any request with a body.
  • Fields the endpoint does not know about are ignored.
  • A request body is capped at 32 MB.

The success envelope

A single resource comes back under data:
A list comes back as an array under data, with a meta block carrying the cursor for the next page:
Status codes follow the action: 200 for a read or an update, 201 for something created, 202 for a broadcast that has been queued. POST /contacts answers 200 when the phone number already belonged to a contact and 201 when it created one, so the status tells you which happened.

The error envelope

Branch on error.code — it is stable. error.message is written for a person reading a log and may be reworded.
404 rather than an empty list is deliberate on the single-resource endpoints: asking for something that belongs to another workspace and getting an empty result would read as “nothing happened here” instead of “this is not yours”.

Pagination

GET /contacts, GET /conversations and GET /conversations/{id}/messages page the same way. Results are newest first.
integer
default:"50"
How many rows to return. Maximum 100. A missing or unusable value falls back to 50, and anything above 100 is capped at 100.
string
The meta.next_cursor from the previous response, passed back unchanged. Cursors are opaque — do not parse or construct one. A cursor that cannot be read is ignored and you get the first page again.
string | null
The cursor for the next page, or null when this was the last page.
Cursors are keyset-based: they point at a position in the list rather than counting pages, so rows created while you are paging do not shift the ones you have not read yet, and nothing is skipped or repeated. Three list endpoints work differently, and each says so on its own page: GET /segments returns every segment with no paging at all; GET /webhooks returns every endpoint and always reports next_cursor: null; and GET /segments/{id}/contacts honours limit but has no cursor.

Rate limits

120 requests per minute, per key. The window starts at the first request and covers the next 60 seconds, after which the count resets. Keys are counted independently, so one busy integration cannot spend another’s allowance. Over the limit, the request is refused with 429 before it does any work:
A 429 carries four headers:
These headers appear on the 429 only. A successful response carries no rate-limit headers, so track your own send rate rather than reading a counter off every response.
If you are pushing a list of people, prefer the endpoint built for it: POST /broadcasts takes up to 1000 recipients in one request and sends them in the background, which is both faster and kinder to the limit than 1000 calls to POST /messages.

What to retry

There are no idempotency keys, so a retried POST /messages sends a second message. If a send times out, read the conversation with GET /conversations/{id}/messages before sending again. Two endpoints are naturally safe to repeat: POST /contacts finds or creates by phone number, so calling it twice leaves one contact, and POST /segments/{id}/contacts skips anyone already in the segment and tells you how many rows it actually added.

Timestamps and ids

Every timestamp is ISO 8601 in UTC (2026-09-14T10:15:00.000Z). Every id is a UUID. A nullable field is really null — phone is null for a contact who only exists on Instagram, last_inbound_at is null for someone who has never written to you.