WA.cr Developer Docs
api.wa.cr/wa/v{version}/…)A Meta Cloud-API-compatible proxy. A developer who already has Graph-API code can point it at WA.cr by swapping only the host and using a WA.cr API key instead of a Meta token:
- https://graph.facebook.com/v25.0/<PHONE_NUMBER_ID>/messages
+ https://api.wa.cr/wa/v25.0/<PHONE_NUMBER_ID>/messages
Authorization: Bearer wacr_live_… # not a Meta token
WA.cr resolves the credential → tenant, proves the {id} in the path belongs to
that tenant, injects the tenant's real Meta token (from Secret Manager), meters
billing + writes audit, and returns Meta's response — including Meta's
{ "error": { … } } envelope — verbatim. The developer never holds a Meta token.
This is the WhatsApp-shaped compatibility shim. For a channel-agnostic surface,
use the native /v1/messages.
Published publicly. This file is the single source of truth and is rendered verbatim at
https://api.wa.cr/wa/docs(a build-time SSG page in the API app — edit this markdown and the page follows). A plain-English introduction for developers lives in the help centre under For developers → "Point your existing WhatsApp Cloud API code at WA.cr", which links here.
Authorization: Bearer <wacr_live_… | wacr_test_… | oauth-token>. Same credentials
and per-credential rate limit (120/60s) as /v1, but errors are Meta-shaped
(authorizeGraphProxy → metaError), never the /v1 envelope.
The leading {id} is verified against the tenant before anything is forwarded:
| id kind | verification |
|---|---|
phone_number_id, waba_id | strict — matched against whatsapp_accounts (RLS-bound) |
meta_template_id (template edit) | strict — matched against whatsapp_templates |
media_id, flow ids | transitive — Meta-enforced via the tenant's own scoped token |
business_id | denied (unmapped_business) — no mapping stored today |
A cross-tenant or unmapped id → Meta-shaped 400 OAuthException.
Path (after /wa/v{version}/) | Methods | Handling |
|---|---|---|
{phone_number_id}/messages | POST | billed re-dispatch via send core (dispatch:auto) |
{phone_number_id}/marketing_messages | POST | billed re-dispatch, forced MM Lite |
{waba_id}/message_templates | POST / GET / DELETE | create/list/delete (create+delete via template core → local row + audit) |
{meta_template_id} | POST | template edit via core |
{phone_number_id}/media, {media_id} | POST / GET | passthrough (multipart upload streamed) |
{phone_number_id}/whatsapp_business_profile | GET / POST | passthrough |
{phone_number_id}/block_users | GET / POST / DELETE | passthrough |
{waba_id}/flows, {flow_id}/{assets|publish|deprecate|validate}, {flow_id} (DELETE) | POST / GET / DELETE | passthrough |
Denied (Meta-shaped 4xx): register, subscribed_apps, oauth, signups,
and anything not listed — these would let a tenant re-wire our own Meta plumbing.
Bare single-segment shapes disambiguate by method: GET = media read,
POST = template edit, DELETE = flow delete.
/messages + /marketing_messages
decode the Meta body and re-dispatch through sendTenantWhatsAppMessage, then
reshape the result into Meta's exact { messaging_product, contacts, messages }
envelope. Wallet/trial/footer/plan gates apply (a raw Meta client wouldn't see
these — intended).whatsapp_templates + the audit trail track Meta./wa/:path* entry.# text (billed → send core → Meta envelope)
curl -sS https://api.wa.cr/wa/v25.0/<PNID>/messages -H "Authorization: Bearer wacr_live_…" \
-H "Content-Type: application/json" \
-d '{"messaging_product":"whatsapp","to":"9198…","type":"text","text":{"body":"hi"}}'
# template list (verbatim Meta) / denied endpoint (Meta-shaped 400)
curl -sS "https://api.wa.cr/wa/v25.0/<WABA>/message_templates?limit=5" -H "Authorization: Bearer wacr_live_…"
curl -sS -X POST https://api.wa.cr/wa/v25.0/<PNID>/register -H "Authorization: Bearer wacr_live_…"
See also: Marketing Messages Lite,
omnichannel /v1/messages, docs/api/openapi.yaml.