Skip to content
Flowgento

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.

ScopeGrants
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":[...]}}
CodeHTTPWhen
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

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