API reference
API overview
Base URL, versioning, content types, and which surface to use for what.
The Public API sends WhatsApp messages, provisions templates and reads conversations with an API key. It is the same messaging engine the dashboard uses, exposed for server-to-server and frontend integrations.
Choose the right surface first
There are two ways to send a message, and picking the wrong one is the most expensive mistake an integrator makes. Read this before anything else.
- POST /v1/notifications/send
Use this for anything you tell a customer about their order, confirmation, status change, shipping, delivery, payment, refund, return, OTP, cart recovery, back-in-stock.
You send an event key and named variables. Template selection, parameter ordering, the 24-hour window and approval state are handled for you.
- POST /v1/messages/send
Use this only to reply to a customer who messaged you in the last 24 hours, or to send a template you built and ordered the parameters for yourself.
It is a thin pass-through to Meta. You choose the message type and hand-build the positional component arrays; nothing is checked for you.
Base URL and versioning
- Base URL
https://whatsapp-api.growcord.in/api- Version prefix
- Every endpoint sits under
/v1, so a full URL readshttps://whatsapp-api.growcord.in/api/v1/notifications/send. - Format
- JSON in and JSON out. Set
Content-Type: application/jsonon every body-carrying method. Responses are always JSON, including errors. - Auth
Authorization: Bearer sk_live_…on every request. See Authentication.
curl https://whatsapp-api.growcord.in/api/v1/phones \
-H "Authorization: Bearer sk_live_xxx"Request bodies are capped at 1 MB
A larger body is rejected by the JSON parser with 413 before it reaches any route, so no handler and no billing runs. In practice this only bites on POST /v1/notifications/send/batch: 500 recipients each carrying long product names and tracking URLs can approach the cap. If a batch is rejected for size, split it and send the halves.
Headers on the way back
Every /v1 response carries the rate-limit headers. A successful send also carries what it cost, which is the only reliable way to know the price, the rate card is per country and per conversation category and a plan can change all of it.
Rate limit, on every response
| Field | Required | Type | Description |
|---|---|---|---|
| RateLimit-Limit | No | integer | Requests permitted in the 60-second window for this key. Example: 120 |
| RateLimit-Remaining | No | integer | Requests left in the current window. Example: 118 |
| RateLimit-Reset | No | integer | Seconds until the window resets. Example: 47 |
Billing, on a successful send
| Field | Required | Type | Description |
|---|---|---|---|
| X-Message-Category | No | string | MARKETING, UTILITY, AUTHENTICATION or SERVICE. This decides the price. |
| X-Message-Price | No | decimal string | Sell price for this message. Example: 0.0014 |
| X-Credits-Charged | No | integer | Credits deducted. Credit-billed plans only. |
| X-Credits-Remaining | No | integer | Balance after the deduction. Credit-billed plans only. |
| X-Wallet-Balance | No | decimal string | Balance after the deduction. Wallet-billed plans only. |
The billing headers come from the guard that reserves payment, so they appear on POST /v1/notifications/send and POST /v1/messages/send. POST /v1/notifications/send/batch bills each recipient separately inside the loop and returns no billing headers at all, read results instead.
Every endpoint
Twenty endpoints, all under /v1.
Notifications, reference
GET /v1/notifications/catalog, the 18 events, their variables and their template bodies.GET /v1/notifications/events, which events can be delivered right now on a number.POST /v1/notifications/templates/submit, submit the catalog to Meta for approval.POST /v1/notifications/send, send one event to one recipient.POST /v1/notifications/send/batch, one event, up to 500 recipients.GET /v1/notifications/status/:messageId, delivery status for a message you sent.
Messaging, reference
POST /v1/messages/send, text, template, media, location or interactive.GET /v1/messages, message history, newest first.GET /v1/conversations, conversation threads, most recently active first.
Phone numbers, reference
GET /v1/phones, the numbers on your account and their quality rating.PATCH /v1/phones/:phoneNumberId/webhook, set or clear the inbound callback URL.
Templates, reference
GET /v1/templates, templates for a number, live from Meta.GET /v1/templates/:id, one template with its components.POST /v1/templates, submit a template you wrote yourself.
Bots, reference
GET /v1/bots, list bots.GET /v1/bots/:botId, one bot, including its node graph.POST /v1/bots, create a bot.PATCH /v1/bots/:botId, update a bot.DELETE /v1/bots/:botId, delete a bot permanently.
Credits
GET /v1/credits, balance and the 20 most recent ledger entries. Documented below.
Checking your balance
Returns the message-credit balance now, plus the 20 most recent ledger entries. Use it to alert before a 402 stops your sends rather than after.
Parameters: none.
Response 200
| Field | Required | Type | Description |
|---|---|---|---|
| balance | Yes | integer | Credits available now. |
| transactions[] | Yes | array | Up to 20 ledger entries, newest first. |
| transactions[].id | Yes | string | Ledger row id. |
| transactions[].amount | Yes | integer | Signed, negative for a deduction. |
| transactions[].balanceAfter | Yes | integer | Balance immediately after this movement. |
| transactions[].type | Yes | string | DEDUCT, REFUND, GRANT or REVOKE. |
| transactions[].reason | No | string | null | The event key for a send, or free text for an administrator grant. |
| transactions[].metadata | No | string | null | A JSON string, not an object. Carries category, countryCode, metaCost, sellPrice and chargeId. Parse it before reading it. |
| transactions[].createdAt | Yes | string | ISO 8601. |
| transactions[].grantedBy | No | object | null | { id, email, name } for administrator actions, null otherwise. |
{
"balance": 4821,
"transactions": [
{
"id": "clx8f2a0b0003abcd3456qrst",
"amount": -1,
"balanceAfter": 4821,
"type": "DEDUCT",
"reason": "order_shipped",
"metadata": "{\"category\":\"UTILITY\",\"countryCode\":\"IN\",\"metaCost\":\"0.0014\",\"sellPrice\":\"0.0014\",\"chargeId\":\"clx8f2a0b0004abcd7890uvwx\"}",
"createdAt": "2026-08-05T10:00:04.000Z",
"grantedBy": null
},
{
"id": "clx8f2a0b0000abcd0000aaaa",
"amount": 5000,
"balanceAfter": 5000,
"type": "GRANT",
"reason": "initial top-up",
"metadata": null,
"createdAt": "2026-07-01T00:00:00.000Z",
"grantedBy": { "id": "usr_admin", "email": "[email protected]", "name": "Admin" }
}
]
}A refunded send stays visible as two rows, the DEDUCT and the REFUND, rather than a row that disappears.
Errors: 401, 429.
What a message costs
Never assume one credit per message. Meta prices marketing conversations several times higher than utility ones and that ratio is passed through, so the cost of a send is derived from the recipient's country, the template's category and your plan's markup.
- AUTHENTICATION
otp_verification,password_reset,order_verification.- UTILITY
- Order, shipping, payment and return events.
- MARKETING
cart_recovery,review_request,back_in_stock. Several times the price of a utility message.- SERVICE
- A free-form reply inside an open 24-hour window, any
typeother thantemplateon/v1/messages/send. Free. It succeeds even on a zero balance.
Read X-Message-Category and X-Credits-Charged off the response rather than hard-coding a price. The full pricing story is on Credits and billing.
When something goes wrong
error takes two shapes, a string for everything except schema validation, and a Zod object of formErrors and fieldErrors for validation failures. Type-check it before you log it or your logs fill with [object Object].
Every status, every code, and which failures are worth retrying are on Errors and retries. Two things are worth knowing before you write a client: 429 means two unrelated things and only one of them should be retried, and 409 TEMPLATE_NOT_APPROVED is what a brand-new account hits first.
