API Documentation
Communications ¶
Overview
A communication is one entry in a client’s call or text log: a single call, or a single message, together with the parties involved. Clients push their logs here so the records can appear on a contact’s timeline.
| Field | Accepted values |
|---|---|
kind |
Call, Message |
direction |
Incoming, Outgoing, Missed, Unknown |
participants[].identifier_type |
Phone, Email |
call.outcome |
Completed, Missed, NoAnswer, Busy, Failed, Cancelled, Unknown |
message.delivery |
Pending, Sent, Received, Delivered, Read, Failed, Unknown |
Create communications ¶
Create communicationsPOST/brands/:brand/communications
Example URI
POST /brands/:brand/communications
URI Parameters
- brand
string(required) Example: 07d763a7-6d4f-4294-8084-26e7df8abc3c
Request
Body
[
{
"idempotency_key": "call:4821:1725360000000",
"source_record_id": "4821",
"kind": "Call",
"occurred_at": "2026-09-01T10:40:00Z",
"direction": "Incoming",
"participants": [
{
"identifier": "+12025550123",
"identifier_type": "Phone",
"role": "from",
"display_name": "Synthetic Contact"
}
],
"call": {
"duration_seconds": 83,
"outcome": "Completed",
"service": "cellular"
}
},
{
"idempotency_key": "e0042e06104eabe70c2a70cf3695a1d8",
"source_record_id": "message-record-example-1",
"kind": "Message",
"occurred_at": "2026-09-01T10:41:00.123Z",
"direction": "Outgoing",
"participants": [
{
"identifier": "[email protected]",
"identifier_type": "Email"
}
],
"message": {
"text": "Synthetic contract-test message.",
"service": "imessage",
"conversation_id": "conversation-example-1",
"delivery": "Sent"
}
}
]Response
204Body
| Field | Type | Notes |
| --- | --- | --- |
| `idempotency_key` | String, required | Max 128 characters. Treated as opaque — not parsed. |
| `source_record_id` | String, required | Max 255 characters. The client's own stable ID for the record. |
| `kind` | Enum, required | `Call` or `Message`. Determines which block applies. |
| `occurred_at` | String, required | ISO 8601 **with an offset** (`Z`, `+02:00`). A value with no offset is rejected, because it is ambiguous. |
| `direction` | Enum, required | Relative to the owner. |
| `participants` | Array, required | At least one. A participant needs `identifier` and `identifier_type`; `role` and `display_name` are optional. |
| `call` | Object | Required when `kind` is `Call`. |
| `message` | Object | Required when `kind` is `Message`. |
Call block:
| Field | Type |
| --- | --- |
| `duration_seconds` | Integer |
| `outcome` | Enum |
| `service` | String |
Message block:
| Field | Type |
| --- | --- |
| `text` | String |
| `service` | String |
| `delivery` | Enum |
| `conversation_id` | String, **required** |
`conversation_id` is required on every message and must not be empty. It is the
client's own grouping for a thread.
`service` is free text: it is owned by the platforms, so an unknown value is
stored rather than rejected. `outcome` and `delivery` are closed sets — a value
outside them is rejected.
Properties that are not listed above are ignored. `brand` and `owner` are
resolved server-side and have no effect if sent.Get the latest communication ¶
Get the latest communicationGET/brands/:brand/communications/latest
Reports where your sync currently stands, as a cursor to resume from.
Scoped to the authenticated user, exactly like ingest: this returns your own sync position, never another agent’s.
Example URI
GET /brands/:brand/communications/latest
URI Parameters
- brand
string(required) Example: 07d763a7-6d4f-4294-8084-26e7df8abc3c
Response
200Body
{
"code": "OK",
"data": {
"type": "communication",
"id": "26a9547a-71ad-4865-b77d-b5b836a7ef1d",
"brand": "07d763a7-6d4f-4294-8084-26e7df8abc3c",
"source": "default",
"kind": "Message",
"source_record_id": "message-record-example-1",
"idempotency_key": "e0042e06104eabe70c2a70cf3695a1d8",
"direction": "Outgoing",
"owner": "e96fbfef-8955-49b1-88a5-b8e02186a9c5",
"attachments": [],
"occurred_at": 1788259260,
"created_at": 1793470533,
"updated_at": 1793470533,
"deleted_at": null,
"duration_seconds": null,
"outcome": null,
"call_service": null,
"text": "Synthetic contract-test message.",
"message_service": "imessage",
"delivery": "Sent",
"conversation_id": "conversation-example-1",
"participants": [
{
"ordinal": 0,
"identifier": "[email protected]",
"identifier_type": "Email",
"normalized_identifier": "[email protected]",
"role": null,
"display_name": null
}
]
}
}
| Field | Notes |
| --- | --- |
| `cursor` | Opaque token for your sync position. Treat it as a token to hand back, not something to parse or build. `null` when you have no records yet. |
The cursor points at the record with the greatest `occurred_at`, and names it by
the `kind` and `source_record_id` you sent — so every value in it is one you
recognise, and no server-side id is exposed.
`occurred_at`, `kind` and `source_record_id` together form the ordering key, not
`occurred_at` alone: two records can share a timestamp, and a cursor that could
not separate them would skip or repeat records.