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}/resendandPOST /v1/verify/{id}/cancel; - as a query parameter,
?application=store_88, onGET /v1/verify/{id}, since a GET has no body.
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.
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 on the Errors page: 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. A partner key always has full access, so 403 insufficient_scope on the shared ladder never answers it.
Reading what was sent
GET /v1/partner/verifications lists the codes your key sent across every client application, newest first. Each row is the public shape of GET /v1/verify/{id} plus application_id and your external_id, the key mode it was sent under (partner for your key), the lang and the price_halalas it was charged at.
- Filters.
application(yourexternal_id; one that is not yours answers404 not_found),reference(exact),fromandto(ISO 8601, oncreated_at),status(pending,verified,failed,expired,canceled, read by the clock likeGET /v1/verify/{id}) andmode. A value outside the vocabulary answers400 invalid_requestwith the field named. - Paging. Keyset, no totals:
limitis 1 to 100 (50 when omitted),next_cursorpoints after the last row you received whether or not more rows exist now, andhas_moresays whether another page is waiting. Pass it back ascursor; an empty page answersnull. 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.
GET /v1/partner/usage reports codes sent, 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, codes 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 console under Clients → API and webhook, Sandbox key) 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 Sandbox key card 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). - Reads work, writes do not. Every partner management write (provisioning, updates, suspend, archive, the webhook, retries) answers
403 live_key_requiredwith 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.
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.