All requests carry your service-account token and ideally a pinned Gleap-Version. Send an Idempotency-Key (any unique string, e.g. a UUID) on POSTs you might retry.
Base URL
The API host depends on the data region of your project:
Data region | API host |
|---|---|
EU (default) |
|
US |
|
https://api.gleap.io with your region's host. API tokens are per project and only valid in the project's region.1. Upsert the contact
Contacts are addressed by your user identifier and deduplicate with SDK-created contacts on the same id.
curl -X PUT https://api.gleap.io/v3/s2s/contacts/AZ-4711 \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "name": "Aynur M.", "email": "[email protected]", "language": "az",
"customData": { "tier": "gold" } }'The response includes unread — the contact's total unread conversation count for your badge.
2. Open a conversation
The first message rides along. workflowId starts a workflow exactly like the SDK's startConversation; agentId engages an AI agent directly (the two are mutually exclusive). attributes values land as ticket attributes — define an attribute in Settings → Data attributes with shared visibility disabled and it is visible to your agents but never to the customer (the AI-handover-summary pattern):
curl -X POST https://api.gleap.io/v3/s2s/contacts/AZ-4711/conversations \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: 9f1c2e4a-4b0e-4c1a-9d5e-000000000001" \
-d '{
"title": "Card blocked after travel",
"message": "My card was blocked while I was abroad.",
"workflowId": "<workflow id from the dashboard>",
"attributes": { "aiSummary": "Customer travelled, card auto-blocked. AI could not lift the block." },
"source": "your-app"
}'{ "id": "cnv_…", "number": 1042, "status": "OPEN", "closed": false,
"workflowActive": true, "contact": { "userId": "AZ-4711" }, "…": "…" }workflow.step.presented event, or fetch GET /v3/s2s/conversations/{id}/workflow.3. Send and read messages
curl -X POST https://api.gleap.io/v3/s2s/conversations/cnv_…/messages \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "text": "I still cannot pay with the card." }'Read oldest → newest, cursor-paginated (max 100 per page). Pass after = the last message id you already have:
curl "https://api.gleap.io/v3/s2s/conversations/cnv_…/messages?after=msg_…&limit=100" \
-H "Authorization: Bearer $TOKEN"{ "items": [ { "id": "msg_…", "author": { "type": "agent", "name": "Aynur" },
"text": "We have lifted the block.", "createdAt": "…" } ],
"has_more": false, "next_after": null }author.type is contact, agent, bot or system. Internal team notes never appear on this surface.
4. Mark read
Reading messages never changes unread state — send the explicit receipt when your UI displays the conversation:
curl -X POST https://api.gleap.io/v3/s2s/conversations/cnv_…/read \
-H "Authorization: Bearer $TOKEN"5. List the contact's conversations
Most recent activity first; bot-only conversations without human interaction are hidden, exactly like the widget's list:
curl "https://api.gleap.io/v3/s2s/contacts/AZ-4711/conversations?limit=50" \
-H "Authorization: Bearer $TOKEN"6. Close / reopen
curl -X POST https://api.gleap.io/v3/s2s/conversations/cnv_…/close -H "Authorization: Bearer $TOKEN"While closed, customer messages are rejected with problem code conversation_closed; POST …/reopen re-enables them. Your support team closing a conversation in the dashboard behaves identically and emits the same conversation.closed event.
Uploads & voice notes
curl -X POST https://api.gleap.io/v3/s2s/uploads -H "Authorization: Bearer $TOKEN" \
-F "[email protected]"
# → { "url": "…", "name": "receipt.png", "size": 18342, "contentType": "image/png" }
curl -X POST https://api.gleap.io/v3/s2s/uploads/audio -H "Authorization: Bearer $TOKEN" \
-F "[email protected]"
# → { "url": "…", "durationMs": 7430 } (transcoded to MP3 server-side)Attach the returned descriptor to a message's attachments. Max 25 MB per file.
Diagnostics: events & network logs
In the SDK flow the widget attaches the recent Gleap.trackEvent() buffer and Gleap.logNetwork() requests to a conversation when it is created. Without the SDK, push the same snapshots yourself — per conversation, so events from other sessions of the same contact never mix in:
curl -X PUT https://api.gleap.io/v3/s2s/conversations/$CONVERSATION_ID/events \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"events":[{"name":"screen_opened","date":"2026-09-03T08:00:00Z","data":{"screen":"cards"}}]}'
curl -X PUT https://api.gleap.io/v3/s2s/conversations/$CONVERSATION_ID/network-logs \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"requests":[{"url":"https://api.example.com/v1/cards","type":"GET","date":"2026-09-03T08:00:01Z","duration":123,"success":true}]}'Both return 204 and show up in the conversation's Logs panel for agents. Each call replaces the previous snapshot for that conversation (an empty array clears it). Limits: 500 events / 100 requests, 2 MB per call. Nothing is emitted on webhooks or the stream.
POST /v3/s2s/contacts/{userId}/events is different: those events belong to the contact, feed outbound rules and the contact timeline, and are not attached to any conversation.
