Skip to content

Requests and responses

  • Use HTTPS. The base URL is https://api.sanadwa.com/v1.
  • Send the API key in the Authorization header. See Authentication.
  • For POST requests, send a JSON body with the header Content-Type: application/json. The one exception is the CSV import, POST /contacts/import, which takes multipart/form-data.
  • Keep JSON bodies under 100 KB, except POST /contacts, which takes up to 1 MB. A larger body gets 413 PAYLOAD_TOO_LARGE. A CSV file for the import may be up to 5 MB.
  • Query parameters (for GET requests) go in the URL, for example /templates?page=2&limit=50.
  • DELETE requests have no body.

If the body is not valid JSON, you get:

{
"success": false,
"message": "Request body is not valid JSON.",
"code": "INVALID_JSON"
}

Send only the fields documented in the API reference. The send endpoints, POST /campaigns and each contact in POST /contacts reject unknown fields with 400 validation_error. Other endpoints ignore them.

Every response body is JSON. Every response has a success field.

A successful response has "success": true and an HTTP status of 200, or 201 when something was created (POST /tags, POST /campaigns). The other fields depend on the endpoint. For example, GET /health returns:

{
"success": true,
"tenant_id": 1234,
"scope": "send",
"server_time": "2026-11-11T08:00:00.000Z"
}

Most endpoints return their result in data: a list in a data array (see Pagination), one object in a data object. The two send endpoints and GET /health return their fields at the top level. Check the API reference for each endpoint.

Ids of tags, contacts and campaigns are strings of digits, for example "312". Store them as strings, not numbers. WhatsApp message ids start with wamid..

A URL that is not an endpoint answers 404 with the code not_found:

{
"success": false,
"code": "not_found",
"message": "This endpoint does not exist. Check the HTTP method and the path."
}

An error response has an HTTP status of 400 or higher and "success": false:

{
"success": false,
"code": "phone_not_found",
"message": "phone_number_id not registered for this tenant."
}
Field Always present Meaning
success Yes Always false for errors.
code Almost always A machine-readable error code. Use this in your code.
message Yes A short English explanation for people. It can change; do not parse it.
message_ar No The same explanation in Arabic. Only some errors have it.
details No Extra data for some errors, for example how many credits are missing.

Some error codes are lowercase (phone_not_found) and some are uppercase (PREPAID_CREDIT_CAP_REACHED). Compare codes exactly as they are written in Errors.

Base your logic on the HTTP status and the code. The Errors page lists every code.

Responses include an X-Request-Id header with a unique id for that request. Log it. When you contact support about a request, include its X-Request-Id.

X-Request-Id: 3f2b8c1e-5a7d-4e9f-8b6a-2c4d1e0f9a7b

Times are strings in ISO 8601 format, in UTC, for example 2026-11-11T08:00:00.000Z.

Send phone numbers in E.164 format: +, the country code, then the number, with no spaces. For example +12025550123. Saudi mobile numbers begin with +9665.

The version is part of the URL: /v1. This section explains what we promise for v1.

Within v1 we will not make a breaking change. Code that works today against v1 keeps working.

A breaking change is any of these:

  • Removing or renaming an endpoint, a request field, a response field, a header or an error code.
  • Changing the type or the meaning of a field (for example from a number to a string).
  • Making an optional request field required, or adding a new required field.
  • Changing the HTTP status of an existing success or error.
  • Changing the format of an id or a value you may store (for example message_id).
  • Lowering the default rate limit.
  • Changing how authentication, scopes, idempotency or webhook signatures work.
  • Removing a webhook event, or removing or renaming a field in a webhook payload.

These changes are not breaking. Write your code so that they do not break it:

  • New endpoints.
  • New optional request fields and new query parameters.
  • New fields in responses and in webhook payloads. Ignore fields you do not know.
  • New error codes. Treat an unknown code by its HTTP status.
  • New webhook event types. If a webhook is subscribed to every event, it will start to receive them. Ignore events you do not handle, and answer them with 2xx.
  • New values in fields that hold a status or a type that comes from WhatsApp, such as a template’s category or a message’s status.
  • The text of message and message_ar.
  • The order of fields in JSON, and the order of items in a list unless the list’s order is documented.
  • Fixes that make the API behave as this documentation describes.
  • Every change to the API is listed in the Changelog with its date.
  • If we ever need a breaking change, we will publish a new version (/v2) and announce it in the changelog and to account admins at least 6 months before v1 stops working.
  • In rare cases, a security fix or a change required by WhatsApp (Meta) may need less notice. We will tell affected customers directly.