Skip to content
View as Markdown

Requests & responses

The rules that hold for every endpoint, so each reference page can stay short.

Requests#

  • Base URL: https://api.saei.automize.sa/v1. HTTPS only.
  • Bodies are JSON with Content-Type: application/json. A body that is not valid JSON is refused with invalid_json rather than read as empty. The one exception is a file upload (POST /media), sent as multipart/form-data.
  • Phone numbers come back in E.164: +966501234567. You may send them as 00966…, 966… or a local 05… too (see Arabic & Saudi specifics). Arabic text and emoji are sent as-is (UTF-8).
  • Filters on lists are query parameters; booleans are true or false.

Ids#

Every object has an id that starts with its kind, so a log line says what it names, and a wrong kind of id is answered with a clear 404:

PrefixObjectPrefixObject
msg_a message you sentct_contact
imsg_a customer's WhatsApp messagetag_tag
cmsg_a campaign recipient's messagefld_custom field
msg_test_a test-mode messageconv_conversation
num_WhatsApp numberusr_team member
tpl_templatecmp_campaign
med_uploaded fileflow_ / run_flow / flow run
fres_WhatsApp Flow form responsewe_ / del_webhook endpoint / delivery
evt_eventreq_request

Treat ids as opaque strings up to 64 characters: do not parse them or assume they are numbers.

Errors#

Every error has the same shape, and the HTTP status matches its type:

JSON
{
  "error": {
    "type": "invalid_request_error",
    "code": "window_closed",
    "message": "The 24-hour customer service window is closed. Send an approved template instead.",
    "param": "type",
    "doc_url": "https://app.saei.automize.sa/docs/errors#window_closed",
    "request_id": "req_01j9x3k2m4n5p6q7r8s9t0v1w2"
  }
}

Branch on code, never on message (messages may be reworded). When several fields are wrong, details lists each one. Every code, with how to fix it, is on the Errors page.

Messages are in English. Send Accept-Language: ar and field validation messages (parameter_missing, parameter_invalid) come back in Arabic; the code is the same in both.

Request ids#

Every response carries a Request-Id header (and errors repeat it in request_id). Log it: support can find the exact request from it, and you can look it up yourself in Developers → Logs.

Pagination#

Lists are newest first and paginated with a cursor:

JSON
{ "object": "list", "data": [ ... ], "has_more": true, "next_cursor": "eyJpZCI6..." }

Ask for the next page with ?cursor=<next_cursor>, and set the page size with ?limit= (1–100, default 25). A cursor does not skip or repeat items when new ones arrive.

Rate limits#

Each key has a request budget per minute. Every response says where you are:

HeaderMeaning
RateLimit-LimitRequests allowed per window
RateLimit-RemainingRequests left in this window
RateLimit-ResetSeconds until the window resets

Over the limit you get 429 rate_limited with Retry-After in seconds. Pace with the headers instead of retrying in a tight loop.

Retries and idempotency#

A timeout leaves you not knowing whether a POST landed. Send an Idempotency-Key header (a UUID is ideal) and retrying is always safe:

Shell
curl -X POST https://api.saei.automize.sa/v1/messages \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: 4f6c1c1e-2d0a-4b53-9d4f-58a1c6e7b1aa" \
  -H "Content-Type: application/json" \
  -d '{ "to": "+966501234567", "type": "template", "template": { "name": "order_update", "language": "ar", "variables": { "body": ["Sara", "#10482"] } } }'
  • Same key, same body: you get the first response again, with Idempotent-Replayed: true. Nothing is sent twice.
  • Same key, different body: idempotency_key_reused.
  • Keys are remembered for 24 hours. Errors from our side (5xx, 429) are not remembered, so a retry tries again.

Retry 429 and 5xx with exponential backoff; do not retry other 4xx without changing the request.