WA.cr Developer Docs

Graph-compatible proxy (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.

Auth

Authorization: Bearer <wacr_live_… | wacr_test_… | oauth-token>. Same credentials and per-credential rate limit (120/60s) as /v1, but errors are Meta-shaped (authorizeGraphProxymetaError), never the /v1 envelope.

Ownership gate (security-critical)

The leading {id} is verified against the tenant before anything is forwarded:

id kindverification
phone_number_id, waba_idstrict — matched against whatsapp_accounts (RLS-bound)
meta_template_id (template edit)strict — matched against whatsapp_templates
media_id, flow idstransitive — Meta-enforced via the tenant's own scoped token
business_iddenied (unmapped_business) — no mapping stored today

A cross-tenant or unmapped id → Meta-shaped 400 OAuthException.

Allow-list (default DENY)

Path (after /wa/v{version}/)MethodsHandling
{phone_number_id}/messagesPOSTbilled re-dispatch via send core (dispatch:auto)
{phone_number_id}/marketing_messagesPOSTbilled re-dispatch, forced MM Lite
{waba_id}/message_templatesPOST / GET / DELETEcreate/list/delete (create+delete via template core → local row + audit)
{meta_template_id}POSTtemplate edit via core
{phone_number_id}/media, {media_id}POST / GETpassthrough (multipart upload streamed)
{phone_number_id}/whatsapp_business_profileGET / POSTpassthrough
{phone_number_id}/block_usersGET / POST / DELETEpassthrough
{waba_id}/flows, {flow_id}/{assets|publish|deprecate|validate}, {flow_id} (DELETE)POST / GET / DELETEpassthrough

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.

Behaviour notes

Examples

# 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.