Aller au contenu

Workflow API reference

Ce contenu n’est pas encore disponible dans votre langue.

connect.synapsai.app hosts the SynapsAI API; REST is under /v1.

Base URL: https://connect.synapsai.app. Every request carries exactly Authorization: Bearer sk_live_… — one space, no other spelling; anything else is 401 INVALID_KEY (see keys and scopes). Bodies are JSON. Every error is {"code": "…", "message": "…"} with the status below.

The API is part of the GROWTH, PRO and ENTERPRISE plans: on a lower plan every endpoint, GET /v1/me included, answers 403 PLAN_FEATURE_UNAVAILABLE.

Method · pathScopeAnswers
GET /v1/meany{accountId, keyName, keyPrefix, scopes, plan} — checks a key
GET /v1/agentsagents:reada numbered page of your agents, all in one page; each has key (the agentKey the other endpoints take) and title
POST /v1/agents/{agentKey}/messagesmessages:writesend a message to an agent (below)
GET /v1/messages/{messageId}messages:writestatus and reply of a message (below)
GET /v1/conversationsconversations:reada numbered page of conversations
GET /v1/conversations/{chatId}conversations:readone conversation with csat and its messages
GET /v1/contactscontacts:reada numbered page of contacts; search, page, size, and inWindow=WHATSAPP|MESSENGER|INSTAGRAM for the contacts inside that channel’s 24-hour window (each then has windowExpiresAt)
GET /v1/contacts/{id}contacts:readone contact
POST /v1/contacts:upsertcontacts:writecreate or update a contact (below)
GET /v1/leadsleads:reada cursor page of leads
GET /v1/agents/{agentKey}/templatescampaigns:readthe approved WhatsApp templates — {items[{name, language, category, body, variables, examples}], lastSyncedAt}
POST /v1/agents/{agentKey}/campaignscampaigns:writesend a campaign, Idempotency-Key required → 202 (guide)
GET /v1/campaignscampaigns:reada numbered page of campaigns; agentKey, origin, status
GET /v1/campaigns/{id}campaigns:readone campaign with its sent, delivered, read and failed counts
GET /v1/campaigns/{id}/deliveriescampaigns:reada numbered page of per-recipient rows; status
POST /v1/campaigns/{id}:cancelcampaigns:writecancel a scheduled campaign — 409 CAMPAIGN_NOT_CANCELLABLE once it has started sending
GET /v1/agents/{agentKey}/hookshooks:writea numbered page of this agent’s API subscriptions, all in one page
POST /v1/agents/{agentKey}/hookshooks:writesubscribe (below) → 201
DELETE /v1/hooks/{id}hooks:writeunsubscribe → 204; 404 for a hook created in the console or by another account

GET /v1/conversations takes agentKey, from, to (ISO-8601), status, origin (e.g. API, CHAT, WHATSAPP), csatMin, csatMax (1–5), page (from 0) and size (1–100, default 20).

  • With csatMin or csatMax set, conversations that have no rating are left out. csatMin greater than csatMax is 400 INVALID_REQUEST.
  • A list item is a summary: chatId (the id GET /v1/conversations/{chatId} takes), agentKey, origin, status, closeReason, externalUserId, contactId, lastMessageText, lastMessageAt, userMessageCount, occurredAt. The rating itself (csat: {score, feedback, submittedAt, source}) is on the conversation.
EndpointShapeNext page
GET /v1/conversations, GET /v1/contacts{"items": [...], "page": 0, "size": 20, "total": 230}ask page + 1 while (page + 1) × size < total
GET /v1/agents, GET /v1/agents/{agentKey}/hooksthe same shape, everything in page 0 (size = total)—
GET /v1/leads{"items": [...], "nextCursor": "…", "total": 12, "deliveryIssues": [...]}pass cursor=<nextCursor>; nextCursor is null on the last page

Every list is one of these two shapes — never a bare array. Keep your own page counter: request page n, then n + 1.

GET /v1/leads?agentKey=shop-bot (agentKey required) also takes outcome — a CRM outcome such as WRITTEN_DEAL, WRITTEN_CONTACT, SKIPPED_BELOW_THRESHOLD — cursor and limit (1–100). Each item:

{ "leadId": "job_7f2", "chatId": "6713f0c2a1b2c3d4e5f60718", "contactId": "c_91", "contactName": "Ana",
"recordedAt": "2026-10-28T12:10:00Z", "origin": "API", "score": 82, "interest": "3 units, delivery Friday",
"dealTitle": "3 × drill 4 mm", "qualified": true, "status": "NO_DESTINATION", "destinations": [] }

status is SENT, PARTIAL, NOT_SENT or NO_DESTINATION (captured, with no CRM or hook to send it to).

StatuscodeWhen
400INVALID_REQUESTa parameter or body field breaks its rules (the message names it)
400IDENTIFIER_REQUIREDcontacts:upsert without any identifier
400INVALID_PHONE, INVALID_EMAILcontacts:upsert with a phone that is not E.164, or an invalid e-mail
400INVALID_DESTINATIONa subscription url that is not a public https:// host on port 443
401INVALID_KEYmissing, unknown, revoked or expired key — one answer for all
403SCOPE_MISSINGthe key lacks the scope
403PLAN_FEATURE_UNAVAILABLEthe account is below GROWTH
404NOT_FOUND, MESSAGE_NOT_FOUND, HOOK_NOT_FOUND, AGENT_NOT_FOUNDnothing with that id in your account
409HOOK_LIMIT_REACHEDa 21st API subscription on the agent
422NO_EVENTS, INVALID_EVENT, INVALID_EXTERNAL_IDa subscription with no events, an event that cannot be subscribed through the API, or an externalSubscriptionId over 128 characters
429RATE_LIMITEDmore than 60 requests a minute for the key (Retry-After: 1), or the edge limit per key / per IP (Retry-After: 60); wait that many seconds
503UPSTREAM_UNAVAILABLE, AUTH_UNAVAILABLESynapsAI could not answer in time (a timeout or a store that did not answer, e.g. a message status that cannot be read is a 503, never a 404) — see Retrying a 503
503SERVICE_DISABLEDthe service is switched off for now; retry later
500INTERNAL_ERRORunexpected; nothing reveals more

Retrying a 503. GET requests and POST /v1/agents/{agentKey}/messages sent with an idempotencyKey are safe to repeat as they are: within 24 h, a repeated message with the same key and body returns the first messageId. The other writes — POST /v1/contacts:upsert, POST /v1/agents/{agentKey}/hooks, DELETE /v1/hooks/{id} — may have been applied before the 503 reached you: read first (the contact, GET /v1/agents/{agentKey}/hooks), then retry only what is missing. The n8n node and the Make app retry the safe calls only.

POST /v1/agents/shop-bot/messages
Authorization: Bearer sk_live_…
Content-Type: application/json
{
"externalUserId": "crm-123",
"text": "Do you have the 4 mm drill in stock?",
"contact": { "name": "Ana", "phone": "+5071234567", "email": "ana@example.com" },
"metadata": { "dealId": "D-9" },
"callbackUrl": "https://n8n.example.com/webhook-waiting/42/…",
"idempotencyKey": "order-7781-q1"
}
FieldRules
externalUserIdrequired, 1–128 characters — your stable id for the person; the same id continues the conversation
textrequired, 1–4000 characters
contactIdoptional, ≤ 64 characters; attaches the conversation to an existing contact of your account. Needs contacts:write too
contactoptional name (≤ 256), phone (≤ 32, E.164), email (≤ 254), strings only: they fill blank fields of this conversation’s contact. Contacts are never merged automatically
metadataoptional object of ≤ 20 scalar values (string, number, boolean), ≤ 2 KB; echoed back in the reply webhook, never shown to the agent
callbackUrloptional; https://, port 443, ≤ 2048 characters, a host name (no IP address, no user:password). When set, the reply goes only there; otherwise to your message.replied subscriptions
idempotencyKeyoptional, 1–128 characters; kept 24 h. After 24 h the same key starts a new message

The whole body may be at most 32 KB. Attachments are not accepted yet.

Answers:

  • 202 {"messageId": "msg_…", "status": "QUEUED"} — accepted. A messageId is random (msg_ + 22 characters); keep it to correlate the reply.
  • 200 {"messageId": "msg_…", "status": "DUPLICATE"} — the same idempotencyKey with the same body within 24 h; the first messageId, no second turn.
StatuscodeWhen
400INVALID_JSONthe body is not a JSON object
400EXTERNAL_USER_REQUIRED / INVALID_EXTERNAL_USER_IDexternalUserId missing / not a 1–128 character string
400TEXT_REQUIRED / TEXT_TOO_LONGtext missing / over 4000 characters
400INVALID_CONTACT, INVALID_CONTACT_ID, INVALID_METADATA, INVALID_IDEMPOTENCY_KEYthat field breaks the rules above
400INVALID_CALLBACK_URLnot https on 443, an IP address, credentials in the URL, or too long
400ATTACHMENTS_UNSUPPORTEDthe body carries attachments
401INVALID_KEYmissing, unknown, revoked or expired key, or a header that is not exactly Bearer sk_live_…
403SCOPE_MISSINGno messages:write, or contactId without contacts:write
403AGENT_FORBIDDENthe agent is not yours, or its API channel is off (same answer for both)
403PLAN_FEATURE_UNAVAILABLEthe account is below GROWTH
404NOT_FOUNDwrong path
405METHOD_NOT_ALLOWEDanything but POST
409IDEMPOTENCY_MISMATCHthe same idempotencyKey with a different body within 24 h
413PAYLOAD_TOO_LARGEbody over 32 KB
429LOOP_GUARDmore than 20 messages a minute for this externalUserId (that user only)
429RATE_LIMITEDmore than 600 messages a minute for the whole account
429RATE_LIMITEDthe edge limit per key (120/min) or per IP — with Retry-After: 60
503AUTH_UNAVAILABLE, PLAN_UNAVAILABLE, UPSTREAM_UNAVAILABLE, ROUTING_UNAVAILABLE, INGRESS_STORE_UNAVAILABLE, QUEUE_UNAVAILABLE, CONFIG_UNAVAILABLEa dependency did not answer; nothing was queued — retry with the same idempotencyKey
500INTERNALunexpected

A revoked key stops new calls at once; a turn already queued still completes and its reply is delivered.

GET /v1/messages/{messageId} →

{
"messageId": "msg_4kQ2v9XbT1nE7sLp0aZc3R",
"agentKey": "shop-bot",
"conversationId": "6713f0c2a1b2c3d4e5f60718",
"status": "REPLIED",
"reply": { "text": "Yes, we have 12 in stock.", "components": [], "repliedAt": "2026-10-28T12:00:04Z" },
"errorCode": null
}

status is QUEUED, REPLIED or FAILED (errorCode says why, e.g. INSUFFICIENT_CREDITS).

POST /v1/contacts:upsert
{ "phone": "+5071234567", "email": "ana@example.com", "name": "Ana", "mode": "fill" }

SynapsAI looks for a contact of your account in this order — contactId, phone (E.164), email (lower-case), externalUserId (API channel) — and the first hit wins. Nothing found: a contact is created. mode is fill (default: only blank fields are set) or overwrite.

→ 200 {"contactId": "…", "created": false, "matchedBy": "PHONE"} (CONTACT_ID, PHONE, EMAIL, EXTERNAL_USER_ID, or NONE when created). 400 IDENTIFIER_REQUIRED when none of contactId, phone, email, externalUserId is given.

This is the only way two contacts become one, and it happens only when you call it.

POST /v1/agents/shop-bot/hooks
{ "url": "https://hooks.example.com/synapsai", "events": ["message.replied", "lead.captured"],
"includes": { "contact": true }, "externalSubscriptionId": "my-flow-17" }

→ 201 {"id": "…", "url": "…", "events": [...], "signingSecret": "whsec_…", …} — the secret is shown once; keep it to verify deliveries. GET /v1/agents/{agentKey}/hooks lists them (items).

  • Events: message.replied, message.operator, lead.captured, the campaign events campaign.message.delivered, campaign.message.read, campaign.message.failed and campaign.completed (scope campaigns:read), and the lifecycle events conversation.started, conversation.ended, handoff.requested, operator.joined, operator.left, handoff.ended (conversation.idle is console-only: 422 INVALID_EVENT). A message.replied subscription also receives message.failed.
  • includes is an object of sections, none by default. The key must hold the scope that reads the same data through the API (403 SCOPE_MISSING otherwise):
    • contact, operatorEmail → contacts:read;
    • intel, lead, handoff, operator, messages, transcript, memory → conversations:read;
    • the message.replied and message.operator events (they carry conversation text) → conversations:read;
    • the lead.captured event → leads:read;
    • the campaign.* events → campaigns:read.
  • 409 HOOK_LIMIT_REACHED past 20 API subscriptions per agent.
  • Subscriptions follow the lifecycle hooks rules: retries, quarantine after repeated failures, hook.ping to come back. If the account moves below GROWTH they are paused.

Every delivery is a signed POST with the lifecycle-hook headers: X-Synapsai-Event, X-Synapsai-Webhook-Id (the same on every retry — deduplicate on it), X-Synapsai-Timestamp and X-Synapsai-Signature. The secret is the agent’s API signing secret for a callbackUrl, and the subscription’s secret for a subscription. See verifying signatures. The Make app cannot check the signature (Make has no raw body), so its triggers treat each delivery as a notification and re-read the event by id with your key — a forged POST to a Make webhook URL cannot inject data.

{
"id": "evt_5d1c9e0b7a2f4c3e8b6a1d0f9e8c7b6a",
"type": "message.replied",
"apiVersion": "2026-10",
"createdAt": "2026-10-28T12:00:04Z",
"account": { "id": "acc_0123" },
"agent": { "key": "shop-bot", "name": "Shop assistant" },
"conversation": { "id": "6713f0c2a1b2c3d4e5f60718", "channel": "API", "language": "en" },
"message": {
"id": "msg_4kQ2v9XbT1nE7sLp0aZc3R",
"externalUserId": "crm-123",
"contactId": "c_91",
"text": "Yes, we have 12 in stock. Want me to reserve one?",
"components": [ { "type": "buttons", "buttons": [ { "label": "Reserve", "value": "reserve" } ] } ],
"metadata": { "dealId": "D-9" },
"repliedAt": "2026-10-28T12:00:04Z"
}
}
  • message.id is the messageId your request got back — correlate on it. conversation.id is the chatId.
  • components are the agent’s rich answers (buttons, carousels, lists, charts) in SynapsAI’s channel-neutral form; text is always a plain-text version you can send anywhere.
  • message.operator has the same message block plus operator: {name}: a console operator answered.
  • message.failed has message: {id, externalUserId, contactId, metadata} and error: {code, message}.
  • lead.captured carries lead — {score, interest, dealTitle, budget: {amount, currency}, timeframe, contactDeclined}, with no name, e-mail or phone. To send the person to a CRM, subscribe with includes: {"contact": true} (needs contacts:read) and read contact: {id, name, email, phone, channelUserId, tags}.
  • The delivery id (= X-Synapsai-Webhook-Id) is evt_ + 32 hex characters, stable across retries.