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 |
Create communications ¶
Create communicationsPOST/brands/:brand/communications
Example URI
POST /brands/:brand/communications
URI Parameters
- brand
string(required) Example: 83fa26db-10ce-4869-ae24-a5bd9c210a73
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` | String |
| `service` | String |
Message block:
| Field | Type |
| --- | --- |
| `text` | String |
| `service` | String |
| `delivery` | String |
| `conversation_id` | String |
`service`, `outcome` and `delivery` are free text: they are owned by the
platforms, so unknown values are stored rather than 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: 83fa26db-10ce-4869-ae24-a5bd9c210a73
Response
200Body
{
"code": "OK",
"data": {
"type": "communication",
"id": "e7547d74-88a2-432a-a8fe-6d2a5ca78492",
"brand": "83fa26db-10ce-4869-ae24-a5bd9c210a73",
"source": "default",
"kind": "Message",
"source_record_id": "message-record-example-1",
"idempotency_key": "e0042e06104eabe70c2a70cf3695a1d8",
"direction": "Outgoing",
"owner": "b71a204c-688f-4b48-aacc-29113e595ee3",
"attachments": [],
"occurred_at": 1788259260,
"created_at": 1789624522,
"updated_at": 1789624522,
"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.