The current version, how versions change, the deprecation policy, and how to migrate from documentation that described a v1 API.
| Version | v2 |
| Base URL | https://v2.ghala.io |
| Path prefix | /api/v2 |
| Spec | /api/v2/openapi.json |
v2 is the only public API version. Every page in this reference carries a version badge next to its title so you can tell at a glance which version it documents.
This trips people up, so it is worth separating clearly.
/api/v1 on v2.ghala.io is real, but it is not for you. It is Ghala's internal dashboard API: the thing the dashboard itself calls. It is session-authenticated, it changes without notice, and it is not documented, not supported, and not covered by any compatibility promise. Do not build against it.
api.ghala.io/api/v1 was never real. An earlier version of this documentation described a public API at that host, with ghala_... API keys and a { "success": true, "data": ... } envelope. That API was never served. The host returns 404 for every path and always has. If you wrote code against it, that code never worked; there is nothing to migrate, only something to replace.
The Developer API is /api/v2 on v2.ghala.io, and that is the whole story.
If you have code, notes, or an assistant's answer based on the old documentation, here is what actually changed. Every row on the left describes something that was documented but never served.
| Previously documented | Actual API |
|---|---|
https://api.ghala.io/api/v1 |
https://v2.ghala.io/api/v2 |
Authorization: Bearer ghala_... API key |
Authorization: Bearer <the number's WhatsApp access token> |
| One key for the whole account | One token per connected number |
{ "success": true, "data": { } } envelope |
The object itself, unwrapped |
{ "success": false, "error": { "code", "message", "details" } } |
{ "code", "message" }, flat |
SCREAMING_SNAKE error codes (INVALID_PHONE) |
lower_snake codes (outside_messaging_window) |
text: { body: "..." } |
text: "..." |
template: { name, language: { code }, components } |
template_name, template_language, template_components |
image: { url, caption } |
media_url, media_caption, media_filename |
POST /contacts, GET /contacts |
Not on the public API. Dashboard only |
POST /templates to create |
Not on the public API. Dashboard only. GET /api/v2/templates reads |
GET /analytics/messages |
Not on the public API. Dashboard only |
POST /api/v2/webhooks to register an endpoint |
Never served. Register in Developer → Events |
Page-and-limit pagination with a total |
Cursor pagination: cursor, next_cursor, has_more |
Per-tier requests-per-minute quotas and X-RateLimit-* headers |
No published Ghala quotas and no such headers. 429 reflects WhatsApp rate-limiting the number |
Concretely, a send changes from this shape:
{
"to": "255712345678",
"type": "text",
"text": { "body": "Hello" }
}
to this:
{
"to": "255712345678",
"type": "text",
"text": "Hello"
}
Start from the API Reference rather than adapting old code field by field. The surface is two endpoints; rewriting is faster than porting.
September 2026
api.ghala.io/api/v1 are gone; the canonical base is https://v2.ghala.io with the /api/v2 prefix.POST, GET, and DELETE /api/v2/webhooks. These were documented but never served — the paths return 404. Event subscriptions are managed in Dashboard → Developer → Events.GET /api/v2/templates, which was live but entirely undocumented, including sendable, unsendable_reason, quality_score, and the approved components list.interactive messages — reply buttons, list menus, and URL buttons — and the audio type for voice notes. All four were live and undocumented.400 on a send is a template-parameter mismatch, not a malformed URL; 429 is returned when WhatsApp rate-limits the number; the subscription_exists code was removed along with the endpoint it belonged to.This is a documentation changelog, not an API changelog. It records where the documentation was wrong, which is the more useful thing to know right now.
/api/v2. A change that would break existing integrations gets a new prefix rather than being applied in place.X-Ghala-Event; do not assume an enum is closed. The example webhook receivers log and continue on an unknown event type, which is the behaviour to copy.code, not on message. Error messages are written for humans and get reworded. Codes are the contract./api/v2/openapi.json is generated from the running service. Where this documentation and the spec disagree, the spec is right — and that disagreement is a bug worth reporting.There is no formally published deprecation window today. What is committed to:
/api/v2 will not change shape underneath you.If your integration is significant enough that you need a contractual notice period, ask — that is a conversation, not a documentation question.
There is no public status page at present. If sends start failing across the board:
code and status — a 401 or 402 is a credential or plan problem, not an outage.live flag that has gone false is a number problem, not a platform one.Idempotency-Key, the status code, and the error code.