Back to top

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 communications
POST/brands/:brand/communications

Example URI

POST /brands/:brand/communications
URI Parameters
HideShow
brand
string (required) Example: 07d763a7-6d4f-4294-8084-26e7df8abc3c
Request
HideShow
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  204
HideShow
Body
| 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 communication
GET/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
HideShow
brand
string (required) Example: 07d763a7-6d4f-4294-8084-26e7df8abc3c
Response  200
HideShow
Body
{
          "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.

Generated by aglio on 31 Oct 2026