Requests and responses
Requests
Section titled “Requests”- Use HTTPS. The base URL is
https://api.sanadwa.com/v1. - Send the API key in the
Authorizationheader. See Authentication. - For
POSTrequests, send a JSON body with the headerContent-Type: application/json. The one exception is the CSV import,POST /contacts/import, which takesmultipart/form-data. - Keep JSON bodies under 100 KB, except
POST /contacts, which takes up to 1 MB. A larger body gets413 PAYLOAD_TOO_LARGE. A CSV file for the import may be up to 5 MB. - Query parameters (for
GETrequests) go in the URL, for example/templates?page=2&limit=50. DELETErequests 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.
Responses
Section titled “Responses”Every response body is JSON. Every response has a success field.
Success
Section titled “Success”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 path that does not exist
Section titled “A path that does not exist”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."}Errors
Section titled “Errors”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.
Request id
Section titled “Request id”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-2c4d1e0f9a7bDates and times
Section titled “Dates and times”Times are strings in ISO 8601 format, in UTC, for example 2026-11-11T08:00:00.000Z.
Phone numbers
Section titled “Phone numbers”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.
Versioning
Section titled “Versioning”The version is part of the URL: /v1. This section explains what we promise for v1.
What we will not change within v1
Section titled “What we will not change within 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.
What can change within v1
Section titled “What can change within v1”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
categoryor a message’sstatus. - The text of
messageandmessage_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.
How we announce changes
Section titled “How we announce changes”- 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.