> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tawked.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sending for an application

> Name the application on every Verify and WhatsApp call, and read the errors a partner key can meet.

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](/quickstart) and the [WhatsApp](/whatsapp/overview) 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`.

<CodeGroup>
  ```bash cURL theme={"dark"}
  curl -X POST https://tawked.com/v1/verify/start \
    -H "Authorization: Bearer tk_partner_xxxxxxxxxxxxxxxxxxxx" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: order-42-otp-1" \
    -d '{ "to": "0551234567", "lang": "ar", "application": "store_88", "reference": "order-42", "client_ip": "203.0.113.7" }'
  ```

  ```javascript Node.js theme={"dark"}
  const res = await fetch('https://tawked.com/v1/verify/start', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.TAWKED_PARTNER_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': 'order-42-otp-1',
    },
    body: JSON.stringify({ to: '0551234567', lang: 'ar', application: 'store_88', reference: 'order-42', client_ip: '203.0.113.7' }),
  });
  const { id } = await res.json();
  ```

  ```python Python theme={"dark"}
  import os, requests

  res = requests.post(
      "https://tawked.com/v1/verify/start",
      headers={"Authorization": f"Bearer {os.environ['TAWKED_PARTNER_KEY']}", "Idempotency-Key": "order-42-otp-1"},
      json={"to": "0551234567", "lang": "ar", "application": "store_88", "reference": "order-42", "client_ip": "203.0.113.7"},
  )
  verification_id = res.json()["id"]
  ```
</CodeGroup>

Then check with the same application:

```bash theme={"dark"}
curl -X POST https://tawked.com/v1/verify/check \
  -H "Authorization: Bearer tk_partner_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "id": "b7e5c2b0-9c1a-4e2f-8f2a-3a6b0e9d1c44", "code": "482913", "application": "store_88" }'
```

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](/errors) page.

| HTTP | `error`              | Cause                                                                                                                                                                                | What to do                                                   |
| ---- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ |
| 401  | `unauthorized`       | Bad key, or your partner account is suspended.                                                                                                                                       | Check the key; contact support if the account was suspended. |
| 400  | `service_required`   | No `application` (or `service`) in the request.                                                                                                                                      | Pass it.                                                     |
| 404  | `service_not_found`  | `application` is not one of your `external_id` values.                                                                                                                               | Provision it first, or fix the id.                           |
| 403  | `account_not_active` | The application is `rejected` or `suspended`, you suspended it (`disabled: true`), or your account is banned; for codes, also while it is still in `review`. All look the same here. | Read the application: `status` and `disabled` say which.     |
| 403  | `application_paused` | Codes only: the protection guard paused it (`paused: true`).                                                                                                                         | Look at the traffic, then unpause.                           |

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`](/api-reference/partner/list-verifications) for codes and [`GET /v1/partner/messages`](/api-reference/partner/list-whatsapp-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.

```bash theme={"dark"}
curl "https://tawked.com/v1/partner/verifications?application=store_88&status=verified&from=2026-09-01&limit=50" \
  -H "Authorization: Bearer tk_partner_xxxxxxxxxxxxxxxxxxxx"
```

```json theme={"dark"}
{
  "data": [
    {
      "id": "b7e5c2b0-9c1a-4e2f-8f2a-3a6b0e9d1c44",
      "application_id": "c2a10f3e-8b7a-4d2e-9c1a-1a2b3c4d5e6f",
      "external_id": "store_88",
      "status": "verified",
      "channel": "sms",
      "to": "+966551234567",
      "reference": "order-42",
      "attempts_remaining": 0,
      "resends_remaining": 0,
      "expires_at": "2026-09-26T12:34:56.789Z",
      "verified_at": "2026-09-26T12:31:02.145Z",
      "created_at": "2026-09-26T12:29:56.789Z",
      "mode": "partner",
      "lang": "ar",
      "price_halalas": 9
    }
  ],
  "next_cursor": "eyJ0IjoiMjAyNi0wOS0yNiAxMjoyOTo1Ni4wMDAwMDAiLCJpZCI6ImI3ZTVjMmIwLTljMWEtNGUyZi04ZjJhLTNhNmIwZTlkMWM0NCJ9",
  "has_more": false
}
```

* **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`](/api-reference/partner/get-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](/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](#reading-what-was-sent) show these sends with `mode: "partner_test"`, and [`GET /v1/partner/profile`](/api-reference/partner/get-the-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`](/api-reference/whatsapp/list-templates) with `?application=`, [`POST /v1/whatsapp/messages`](/api-reference/whatsapp/send-a-template-message) with it in the body, [`GET /v1/whatsapp/messages/{id}`](/api-reference/whatsapp/get-a-message) 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](/partners/owners).

The `message.*` events reach your webhook once you [subscribe](/partners/events#subscriptions) to them, and the [messages log](#reading-what-was-sent) 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](/partners/whatsapp). Read a message's status back with `GET /v1/whatsapp/messages/{id}`.
