Skip to main content
The Connexease Public API is a panel-independent layer that lets customers manage their WhatsApp Business operations programmatically from their own servers. It has three goals:
  • Proxy / always fresh: read endpoints have no caching — data comes straight from the source (Meta Graph API / ClickHouse), so you don’t deal with Meta’s token/credential complexity.
  • Org-scoped & secure: every request is isolated to the organization resolved from your secret key; you cannot reach another organization’s data.
  • Simple contract: all endpoints share the same response envelope, error model, pagination, and Prefer behavior.

Authentication

Secret key authentication for the Public API.

Templates

Create, list, inspect, update, and delete message templates.

Business Profile

Read and update the WhatsApp business profile.

Analytics

Message volume: summary cards + time series.

Media

Upload media and get the single-use handle for template headers and the profile picture.

Developers

Configure webhooks and create API keys for an application.

Error Codes

Every code the Public API can return, and how to handle each class.

Base URL

All Public API paths in this reference are relative to a single base URL:
Throughout the docs, a path like /wa/{app_id}/templates means {base_url}/api/v1/wa/{app_id}/templates.
On-premises installations are served from your own domain and carry an extra /public segment: https://{your_domain}/public/api/v1/wa/{app_id}/templates. Everything else — authentication, request bodies, responses, error codes — is identical.

Authentication

Every endpoint in this reference requires a secret key:
  • Use it server-to-server; never embed the secret key in a browser/client.
  • organization_id is resolved automatically from the key — you don’t pass it.
  • The app_id you operate on must belong to your organization, otherwise you get 404.
See Secret Key authentication for details.

Shared conventions

These apply to every endpoint:
  • Response envelope — success: { "data": ..., "isSuccess": true }; error: { "errors": { "code", "group", "description" }, "isSuccess": false }, plus fields on field-validation errors — detailed in Error envelope below.
  • Prefer header (on updates)return=representation (default, returns the current record) or return=minimal (no body). The response reports it via Preference-Applied.
  • Cursor pagination (on lists)pagingMetadata.nextCursor / previousCursor + hasNext / hasPrevious / pageSize.
  • Dates — ISO-8601 (2026-06-08T00:00:00).
  • Null fields are dropped from responses (e.g. an unset profile field, or components without expand).

Error envelope

Every failure shares one shape. code identifies the error, group classifies it, and description is a human-readable message with any params already interpolated into it.
string
The error code — VAL_*, WA_*, META_*, APPLICATION_*, ORGANIZATION_SECRET_*, REQ_001, or SYS_001.
string
The category: VALIDATION, NOT_FOUND, CONFLICT, AUTHENTICATION, NOT_ALLOWED, LIMIT_EXCEEDED, SERVICE_UNAVAILABLE, or SYSTEM.
string
The message, with params already substituted in.
object
The raw values that were interpolated into description (e.g. { "max": 2 }). Present only when the message is parametric — useful if you localise messages yourself.
object
Field-level validation errors only. A map of field name → { code, description, params }. Absent on every other kind of error.

Field validation: 422 with errors.fields

When one or more submitted values break a rule, the response is 422 with code: "VAL_000" and a fields map naming each offending field. Nested fields are dot-joined (body.text), and list indices are omitted.
Read errors.fields[...].code, not the prose. Endpoint-specific codes (WA_VAL_*, WA_TPL_*) surface here exactly as they do at the top level.

Malformed request: 400 with REQ_001

A missing required field, an unparseable number or UUID, or invalid JSON is answered with 400 and REQ_001 — not 422, and with no fields map, because the request could not be read well enough to validate it.
The one exception: if the missing field carries its own error code, it is reported as a normal 422 field error instead.

Generic validation codes

Standard constraint failures (VAL_000VAL_019) appear inside errors.fields[...].code on any endpoint. The full list, together with every other code the Public API can return, is on Error Codes. Endpoint pages list only their own domain-specific codes.

Endpoint quick reference

Path parameters are shown in {braces}; query parameters are listed separately so the route stays readable.
/wa/{app_id}/templates/metrics is matched before /wa/{app_id}/templates/{source_id}, so metrics is never treated as a template ID.
Media upload is the one endpoint that is not JSON. It takes a multipart/form-data body with a single file part; everything else on this list sends and receives JSON.

Quickstart

An end-to-end walkthrough. Every request below uses the header Authorization: Bearer sk_... and a path relative to the Base URL given above.

Step 1 — See the current state (analytics)

Returns a summary (total / sent / received / template messages) plus a daily series. Omit appId for the whole organization, or repeat it (?appId=a&appId=b) for specific apps. See Get Messages.

Step 2 — Create a template

Response:
New templates usually start as PENDING (under review). See Create Template.
Need an image, video, or document header? Upload the file first with POST /wa/{app_id}/media/upload and put the returned handle in header.examples alongside "format": "IMAGE". See Upload Media.
Running a campaign with a deadline? Add limitedTimeOffer (text up to 16 chars, optional hasExpiration countdown) to a MARKETING template. It restricts the rest of the template — no footer, media-only header, at most two COPY_CODE / URL buttons. See Limited-time offer rules.
Showing several products at once? Send carousel with 2–10 identically-structured cards, each with its own media handle, body (max 160 chars), and 1–2 buttons. The top level then keeps only its body. Carousel is MARKETING-only and create-only — it cannot be edited later. See Carousel rules.

Step 3 — List templates / track status

Filter by status / category / language, search with search, and page with the returned cursors. See Get Templates.

Step 4 — Inspect a template (with components)

expand=components adds the header/body/footer/buttons detail. See Get Template Detail.

Step 5 — Update an APPROVED template (send the full set!)

Editing is full-replace — send every component you want to keep (at minimum body), or the missing ones are removed. After a successful edit the status returns to PENDING. See Update Template.

Step 6 — Update the business profile

Profile updates are partial — only the fields you send change; the rest are preserved. See Update Business Profile.
Changing the profile picture is a two-step flow: upload the image with POST /wa/{app_id}/media/upload, then send the returned handle as profilePictureReference on this call. The handle is single-use — it is consumed by the update and cannot be sent again.

Step 7 — Measure how the template performed

Returns an overall summary, a per-template summary, and a gap-filled daily series with deliveredRate, readRate, clickRate, and spend already computed. Repeat templateId for up to 10 templates, and keep the range within 90 days. Days Meta has not aggregated yet come back zeroed with isPending: true. See Get Template Metrics.

Step 8 — Retire a template

Deletion removes only the language version behind that source_id, and it cannot be undone — re-creating the template starts a fresh review and yields a new sourceId. See Delete Template.

Common errors & tips


Next steps

Templates

The full template lifecycle.

Business Profile

Read and update the business profile.

Analytics

Message volume analytics.

Template Metrics

Per-template delivery, read, click, and cost metrics.

Media

Upload media for template headers and the profile picture.