Skip to main content
A partner key is account-level, so every Verify and WhatsApp call says which application it is for. Everything else about those endpoints is exactly as the Quickstart and the WhatsApp pages describe: same shapes, same statuses, same errors, plus the ones below.

The application field

Pass your external_id as application:
  • in the JSON body of POST /v1/verify/start, POST /v1/verify/check, POST /v1/verify/{id}/resend, POST /v1/verify/{id}/cancel and POST /v1/whatsapp/messages;
  • as a query parameter, ?application=store_88, on GET /v1/verify/{id}, GET /v1/whatsapp/messages/{id} and GET /v1/whatsapp/templates, since a GET has no body.
The legacy name service is accepted identically everywhere. A key can only check, read, resend or cancel a verification started under the same application; another application’s id answers 404 not_found.
Then check with the same application:
The message carries the application’s reviewed brand, never anything from the request, and the send is charged to your balance at your price. An Idempotency-Key is scoped to your key and the application together, so the same key under two applications is two sends.

What blocks a send

Checked in this order, before the endpoint’s own logic. The first three are the partner-key branch of the shared ladder on the Errors page. Everything after that is the ordinary ladder: request shape, channel and destination, the per-destination hourly cap, the guard, your balance (402 insufficient_credits is your balance, not the merchant’s), then the send itself.

Reading what was sent

Two logs list what your key sent across every application, newest first: GET /v1/partner/verifications for codes and GET /v1/partner/messages for WhatsApp messages. Each row is the public shape of GET /v1/verify/{id} or GET /v1/whatsapp/messages/{id} plus application_id and your external_id; a verification also carries the key mode it was sent under (partner for your key), the lang and the price_halalas it was charged at.
  • Filters. Both take application (your external_id; one that is not yours answers 404 not_found), reference (exact), from and to (ISO 8601, on created_at). Verifications add status (pending, verified, failed, expired, canceled, read by the clock like GET /v1/verify/{id}) and mode; messages add status (accepted, sent, delivered, read, failed) and template. A value outside the vocabulary answers 400 invalid_request with the field named.
  • Paging. Keyset, no totals: limit is 1 to 100 (50 when omitted), next_cursor points after the last row you received whether or not more rows exist now, and has_more says whether another page is waiting. Pass it back as cursor; an empty page answers null. Rows created in the same second are ordered by id, so a cursor never skips or repeats one.
  • Archived applications keep their rows, with their external_id.
For sums rather than rows, GET /v1/partner/usage reports sends, verified codes and spend per application over a window.

Rate limit

The per-key limit of 120 requests per minute counts every call your key makes, sends and partner-API calls together. Spread bursts across a minute, or ask support for a higher limit.

The sandbox key

A partner sandbox key (tk_partner_test_..., created in the partner console under Integration) is the same account and the same endpoints with the sandbox limits, so the code you write against it is the code you ship:
  • One destination. Sends reach only the phone the partner account’s owner verified at signup (the Integration page shows it). Anything else answers 422 sandbox_unverified_destination.
  • A visible stamp. Every message carries the [TEST] / [تجريبي] stamp, and is charged like a live send.
  • Daily caps instead of the lifetime one: 20 sends per application and 200 per account a day (Riyadh time), answering 429 sandbox_quota_exceeded.
  • Applications in review can send, so a merchant can be tested before it is approved; rejected, suspended, disabled and archived ones cannot (403 account_not_active).
  • SMS only. The WhatsApp endpoints answer 403 live_key_required.
  • Reads work, writes do not. Every partner management write (provisioning, updates, suspend, archive, owners, the webhook, retries) answers 403 live_key_required with the sandbox key, so a key that leaks into a test runner can change nothing. Provision test merchants with the live key, then send with the sandbox key.
The logs show these sends with mode: "partner_test", and GET /v1/partner/profile answers key.mode accordingly. One sandbox key is active at a time; rotating it revokes the old one at once.

WhatsApp with a partner key

The WhatsApp endpoints take application too: GET /v1/whatsapp/templates with ?application=, POST /v1/whatsapp/messages with it in the body, GET /v1/whatsapp/messages/{id} with ?application=. Review gates codes only: the WhatsApp endpoints answer for an application in review as for an active one, provided a number is connected. Who connects the number depends on who runs the application:
  • Your own application (no owner): you connect it from the partner console, on the application’s WhatsApp tab, through Meta’s Embedded Signup under your own Meta business. Review does not hold WhatsApp back; only a rejected or suspended application cannot connect.
  • A handed-over application: the owner connects their own WhatsApp Business number from their dashboard. You keep sending through it with application, and you manage its templates and the agents on its team from the partner console. See Application owners.
The message.* events reach your webhook once you subscribe to them, and the messages log lists every message; GET /v1/whatsapp/messages/{id} reads one back. The number, the templates and the team are managed over the API too: see WhatsApp for your applications. Read a message’s status back with GET /v1/whatsapp/messages/{id}.