API reference
Authentication
API keys, the Bearer header, the domain allowlist, and rate limits.
Every request to /v1 is authenticated with an API key sent as a Bearer token. There are no other credentials, no signed requests, no OAuth, no session cookies.
The Bearer header
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx
Content-Type: application/jsonRequest headers
| Field | Required | Type | Description |
|---|---|---|---|
| Authorization | Yes | string | Bearer <key>. The word Bearer, one space, then the key. Keys start with sk_live_. |
| Content-Type | No | string | application/json. Required on POST and PATCH, without it the body is not parsed and every field reads as missing. |
curl https://whatsapp-api.growcord.in/api/v1/phones \
-H "Authorization: Bearer sk_live_xxx"Getting a key, and losing one
Generate a key in the dashboard under API Keys → Generate New Key. The full secret is shown once, at creation. Copy it then.

The key's last-used timestamp is written at most once per minute per key, so it tells you whether a key is live, not how many times it was called. Use it to spot a key nothing uses any more, not as an audit trail.
Authentication errors
All of these come back as 401 with a string error, except the last two.
- 401, no header
Missing API key. Send it as: Authorization: Bearer <key>. There is noAuthorizationheader, or it does not begin withBearer.- 401, empty token
Missing API key. The header is present but the token afterBeareris empty.- 401, unknown key
Invalid or inactive API key. The key does not exist, was revoked, or the owning account is disabled.- 403, suspended
This organisation has been suspendedwithcode: "ORG_SUSPENDED". A suspended organisation stops its API traffic as well as its dashboard, so keys stop working together. An administrator has to lift it.- 500, lookup failed
Authentication error, the key lookup itself failed. This is us, not you. Retry with back-off.
{ "error": "Missing API key. Send it as: Authorization: Bearer <key>" }The domain allowlist
A key can restrict which websites may use it, under API Keys → Manage → Allowed domains. Leave the list empty and there is no restriction at all.
Entry forms
| Field | Required | Type | Description |
|---|---|---|---|
| shop.example.com | No | host | Matches that exact host. |
| https://shop.example.com | No | origin | Matches that exact host. The scheme is stripped. |
| *.example.com | No | wildcard | Matches example.com and any subdomain of it. |
Matching is on the host only and is case-insensitive. The request's Origin header is used, falling back to Referer.
Why a request with no Origin passes
The allowlist is a browser control. It works because a browser sets Origin itself and a page cannot forge it, so a key embedded in your storefront cannot be lifted and replayed from another site.
A request that sends no Origin and no Referer passes even when the allowlist is populated. That is deliberate, for two reasons:
- No origin header means it is not a browser. It is your backend, a cron job or a webhook handler, exactly the callers the allowlist was never meant to police. An e-commerce backend sending order notifications has no origin to send.
- It would buy no security anyway. Anyone holding the key outside a browser can set
Originto any value they like, so rejecting no-origin requests stops honest servers and nobody else.
One key can therefore serve both your storefront and your backend.
CORS is enabled and reflects the calling origin, so browser preflight succeeds before the allowlist is evaluated. The allowlist, not CORS, is what rejects a disallowed site, a blocked call fails with a JSON 403, not a CORS error.
{
"error": "Origin \"https://evil.example\" is not allowed for this API key. Add it under API Keys → Manage → Allowed domains, or call from your server (no Origin header) instead."
}Rate limits
Every key gets its own budget over a rolling 60-second window, keyed on the key rather than on your server's IP address. Several servers sharing one key share one budget; two keys on one server get one each.
- Default
rateLimitPerMinis 120 requests per minute.- Window
- Rolling 60 seconds.
- Change it
- API Keys → Manage.
Response headers, on every /v1 response
| Field | Required | Type | Description |
|---|---|---|---|
| RateLimit-Limit | No | integer | Requests permitted in the window. Example: 120 |
| RateLimit-Remaining | No | integer | Requests left in the current window. Example: 118 |
| RateLimit-Reset | No | integer | Seconds until the window resets. Example: 47 |
RateLimit-Limit: 120
RateLimit-Remaining: 118
RateLimit-Reset: 47Exceed it and you get a bare 429:
{ "error": "Rate limit exceeded for this API key. Slow down or raise the key's limit." }A batch send counts as one request against the rate limit however many recipients it carries, which is the main reason to use POST /v1/notifications/send/batch for a fan-out rather than a loop of single sends.
