Supported endpoints#
kenari forwards only a fixed set of WhatsApp Cloud API calls. A request is forwarded only when its method, path shape and target id all match a row below, and the id belongs to a number or WhatsApp Business Account (WABA) connected to your kenari account.
All paths are relative to the API base URL, https://api.kenari.dev. {version} is a Graph API version in the form v<n>.0 (for example v21.0). kenari sends the version to Meta exactly as you wrote it.
Allow-list#
| Method | Path | The id must be | Notes |
|---|---|---|---|
POST | /{version}/{phone_number_id}/messages | a phone number on your account | Send any message type. See Sending messages. |
PUT | /{version}/{phone_number_id}/messages | a phone number on your account | Also accepted on the messages edge. |
POST | /{version}/{phone_number_id}/media | a phone number on your account | Upload media. See Media. |
GET | /{version}/{media_id}?phone_number_id={phone_number_id} | a media id, scoped by a phone number on your account | phone_number_id is required. |
DELETE | /{version}/{media_id}?phone_number_id={phone_number_id} | a media id, scoped by a phone number on your account | phone_number_id is required. |
GET | /{version}/{waba_id}/message_templates | a WABA on your account | List templates. See Templates. |
POST | /{version}/{waba_id}/message_templates | a WABA on your account | Create a template. |
DELETE | /{version}/{waba_id}/message_templates | a WABA on your account | Delete a template. |
GET | /{version}/{phone_number_id} | a phone number on your account | Read the number's fields. |
GET | /{version}/{phone_number_id}/whatsapp_business_profile | a phone number on your account | Read-only. |
The combination matters, not just the path. For example, POST /{version}/{phone_number_id}/messages is allowed, but POST /{version}/{waba_id}/messages is not, and GET on /media is not.
Anything else returns 404#
These all get the same response, 404 with a KenariRoutingException body:
- a path that isn't
/{version}/{id}or/{version}/{id}/{edge} - a method other than
GET,POST,PUTorDELETE - an edge or method that isn't in the table above
- an id that doesn't belong to your account, including ids owned by someone else
A number or WABA that belongs to you but that you disconnected returns 422 KenariNotConnectedException instead (see Errors).
{ "error": { "message": "Unknown route or object.", "type": "KenariRoutingException", "code": 1003, "fbtrace_id": "req_…" }}The 404 is identical whether the route doesn't exist or the id isn't yours, so nobody can use kenari to find out which ids exist.
A path that doesn't match the /{version}/{id}[/{edge}] shape is rejected before your API key is checked, so it returns 404 even with no Authorization header.
What kenari does to a forwarded request#
| What happens | |
|---|---|
Authorization | Your kn_… key is checked and removed. kenari sends Authorization: Bearer <Meta access token> for the number's WABA. |
| Other request headers | Only Content-Type and Accept go to Meta, unchanged. Multipart boundaries are kept. |
| Query string | Sent as you wrote it, except that any access_token or appsecret_proof you include is removed. kenari appends its own appsecret_proof. |
| Request body | Streamed to Meta byte for byte. It is never parsed or re-serialised. Limit: 100 MB (see Media). |
| Response status and body | Meta's status and body come back unchanged. |
| Response headers | Only these of Meta's headers are passed on: Content-Type, Content-Length, Content-Disposition, X-FB-Trace-Id, X-FB-Rev, X-Business-Use-Case-Usage, X-App-Usage. |
| Added response headers | RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset (see Rate limits) and X-Kenari-Request-Id. |
Timeouts#
| Request | Timeout for Meta's response |
|---|---|
/{phone_number_id}/media and /{media_id} | 120 seconds |
/{phone_number_id}/messages | 30 seconds |
| Everything else | 30 seconds |
If Meta doesn't answer in time, kenari returns 503 KenariUpstreamException. kenari never retries a request for you. See Errors.