Reference
API documentation
Version 1 (B4) · 2026-06-09
The Flowgento REST API lets server-side code send WhatsApp messages, manage contacts, list conversations, and trigger broadcasts on behalf of a workspace. All requests are HTTPS, JSON, and authenticated by a per-tenant API key. Outbound webhooks deliver real-time events to your endpoint.
Base URL
https://api.flowgento.com/api/v1 Authentication
Every request to /api/v1 must include an API key as a Bearer token. Both header forms are accepted; if both are present, X-API-Key wins.
Authorization: Bearer fg_live_<43 random chars>
# or
X-API-Key: fg_live_<43 random chars> Keys are issued from Settings → API in the tenant app (OWNER / ADMIN only, requires Vistaar plan or higher). The full plaintext key is shown once at creation and never again — Flowgento stores only an argon2id hash. Lost keys must be revoked + recreated.
Sanity check
curl -H "Authorization: Bearer $KEY" \
https://api.flowgento.com/api/v1/ping
# 200 OK
{"ok":true,"tenantId":"...","timestamp":"..."} Scopes
Each API key carries a set of scopes that gate which endpoints it may call. Pick scopes when you create the key in Settings → API. A request to an endpoint whose scope is missing returns 403 API_SCOPE_REQUIRED. Pre-existing keys created before scopes shipped are treated as carrying every scope; rotate them when you can to apply least-privilege.
| Scope | Grants |
|---|---|
| messages:write | Send text / template messages on behalf of the workspace. |
| contacts:read | List or fetch contact records. |
| contacts:write | Create contacts and assign tags. |
| conversations:read | List conversations and read message history. |
| broadcasts:write | Create + schedule broadcasts. |
| webhooks:manage | Reserved for future endpoint-management calls (today only the SPA can edit endpoints). |
Rate limits
120 requests per minute per workspace, shared across every key in that workspace. The cap window resets on a sliding 60-second window from your first request. Every response includes:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1707823840 # epoch seconds when the window resets
# Modern RFC draft-7 equivalents are also emitted:
RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset
When you exceed the cap you get 429 RATE_LIMITED plus a Retry-After: <seconds> header. Back off accordingly — retrying immediately will simply use up your next slot.
Error envelope
All 4xx and 5xx responses use a consistent shape. The HTTP status, the machine-readable code, and the human-readable message are all stable; treat code as your branching key.
{"error":{"code":"VALIDATION_ERROR","message":"Request validation failed","details":[...]}} | Code | HTTP | When |
|---|---|---|
| API_KEY_REQUIRED | 401 | No Authorization: Bearer and no X-API-Key header. |
| API_KEY_INVALID | 401 | Key format is wrong, prefix is unknown, or the argon2 hash check failed. |
| API_KEY_REVOKED | 401 | The key was revoked in Settings → API. |
| API_SCOPE_REQUIRED | 403 | Auth succeeded but the key lacks the scope the endpoint demands. |
| VALIDATION_ERROR | 400 | Body or query failed JSON-schema / zod validation. Details include the offending path(s). |
| RATE_LIMITED | 429 | Per-tenant rate cap exceeded. See Retry-After (seconds) and the X-RateLimit-* headers. |
| PLAN_LIMIT_API_ACCESS | 403 | Tenant plan does not include API access (Vistaar or higher required). |
| CONTACT_DUPLICATE | 409 | POST /contacts with a phone that already exists in this workspace. |
| CONTACT_NOT_FOUND | 404 | GET /contacts/:id for an id that does not belong to this tenant. |
| CONVERSATION_NOT_FOUND | 404 | GET /conversations/:id/messages for an unknown conversation. |
| TEMPLATE_NOT_FOUND | 404 | POST /messages (type=template) with a name that is not APPROVED in this workspace. |
| SERVICE_WINDOW_CLOSED | 400 | POST /messages (type=text) when the 24h customer-initiated window is closed. Send an approved template to re-engage. |
| WABA_NOT_CONFIGURED | 400 | No live WhatsApp Business Account on the workspace — connect one in Settings → WhatsApp first. |
Endpoints
POST /messages
Scope: messages:write
Send a text or template message. The conversation thread is found-or-created on the workspace's primary live WABA — the resulting message appears in the SPA inbox UI exactly as if a human sent it. Text sends are subject to the 24h customer-initiated window; template sends are allowed any time.
# Text (window must be open)
curl -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"to":"+919876543210","type":"text","text":"Hello!"}' \
https://api.flowgento.com/api/v1/messages
# Template
curl -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"to":"+919876543210","type":"template","template":{"name":"welcome_message","language":"en_US","variables":{"1":"Alex"}}}' \
https://api.flowgento.com/api/v1/messages
# 201 Created
{"message":{"id":"cmq...","conversationId":"cmq...","status":"SENT","text":"Hello!","metaMessageId":"wamid..."}} GET /contacts
Scope: contacts:read
List or search contacts. Returns an envelope { data, page, pageSize, total }. Query params: q (search name / phone / email), tag (comma-separated tag ids — OR semantics), page, pageSize (max 100).
curl -H "Authorization: Bearer $KEY" \
"https://api.flowgento.com/api/v1/contacts?page=1&pageSize=20&q=alex" POST /contacts
Scope: contacts:write
Create a contact. Phone is normalised to E.164. Returns 409 CONTACT_DUPLICATE if a contact with the same phone already exists in this workspace.
curl -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"phone":"+919876543210","name":"Alex","email":"alex@example.com","tags":["tag_id_1","tag_id_2"]}' \
https://api.flowgento.com/api/v1/contacts GET /contacts/:id
Scope: contacts:read
curl -H "Authorization: Bearer $KEY" https://api.flowgento.com/api/v1/contacts/cmq...
# 200 OK { "contact": {...} }
# 404 CONTACT_NOT_FOUND if the id does not belong to your workspace POST /contacts/:id/tags
Scope: contacts:write
curl -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"tagIds":["tag_id_1","tag_id_2"]}' \
https://api.flowgento.com/api/v1/contacts/cmq.../tags GET /conversations
Scope: conversations:read
List conversations. Query: status (OPEN / RESOLVED / SNOOZED / ALL, default ALL), page, pageSize. Envelope shape mirrors /contacts.
curl -H "Authorization: Bearer $KEY" \
"https://api.flowgento.com/api/v1/conversations?status=OPEN&page=1&pageSize=20" GET /conversations/:id/messages
Scope: conversations:read
Paginated message history, newest first. Use ?before=<ISO timestamp> for keyset pagination beyond the first page.
curl -H "Authorization: Bearer $KEY" \
"https://api.flowgento.com/api/v1/conversations/cmq.../messages?pageSize=50&before=2026-06-08T00:00:00Z" POST /broadcasts
Scope: broadcasts:write
Create and immediately schedule a broadcast. Provide either an inline recipients list or a segment spec (multi-criteria from the SPA wizard) — exactly one. The same backend pipeline that powers the inbox wizard runs the send.
curl -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"name":"Summer launch","templateId":"cmq...","variablesMapping":{"1":"Alex"},"recipients":[{"phone":"+919876543210"}]}' \
https://api.flowgento.com/api/v1/broadcasts Webhooks
Subscribe to workspace events from Settings → Webhooks. Each endpoint POSTs a signed JSON envelope to your URL on every matching event, retries up to 5 times with exponential backoff (~1m, 2m, 4m, 8m, 16m) on non-2xx responses, and times out at 10 seconds per attempt.
Delivery headers
POST https://your.receiver/...
Content-Type: application/json
X-Flowgento-Event: message.received
X-Flowgento-Delivery: <uuid>
X-Flowgento-Signature: sha256=<hex hmac-sha256 of the raw body>
User-Agent: Flowgento-Webhook/1.0 Envelope
{"event":"message.received","deliveryId":"<uuid>","emittedAt":"2026-06-08T...","payload":{ ... per-event fields ... }} Events
| Event | Payload fields |
|---|---|
| message.received | messageId, conversationId, contactId, contactPhone, contactName, wabaId, text, contentType, hasAttachments, timestamp |
| message.status | messageId, conversationId, status (SENT|DELIVERED|READ|FAILED), errorCode, errorMessage, updatedAt |
| conversation.created | conversationId, wabaId, contactId, contactPhone, contactName, createdAt |
| conversation.resolved | conversationId, wabaId, contactId, contactPhone, resolvedAt, resolvedByUserId |
| contact.created | contactId, phone, name, email, source, createdAt |
| contact.tagged | contactId, tagIds[], tagNames[] |
Verifying the signature (Node)
import crypto from 'node:crypto';
function verify(rawBody, signatureHeader, endpointSecret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', endpointSecret)
.update(rawBody) // raw, unparsed bytes — not JSON.parse'd
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expected),
);
} Reject the request and return any non-2xx if the signature doesn't match. Flowgento will retry, so a brief receiver outage won't lose events. Each attempt is logged on the endpoint's "Recent deliveries" panel in Settings → Webhooks.
Need help?
Email partnerships@flowgento.com with the workspace name, the key prefix (the fg_live_xxxxxxxx portion, never the full key), and a short description of what you're building. We respond within one business day.