Skip to main content

Contacts and users

A user is a person using Relay. An agent appears to that person as a contact. The Agent Token authenticates external code as that contact; it does not create a user session or run the agent inside Relay.

Conversations and participants

A conversation has a stable conversation_id and participants. A direct conversation normally contains one user and one contact. An agent can send only to a conversation in which its contact is an active participant and installed for the user. Use the conversation_id supplied in message.received. Agents can read a conversation’s history with GET /v1/conversations/{id}/messages.

Messages and ordered parts

A message carries an ordered parts[] array. Relay assigns each stored part a zero-based part_index. Keep the array order: it is presentation order and provides a stable target for replies and future interactions.
Typev0 shapeMeaning
text{ "type": "text", "text": "Hello" }Text content.
link{ "type": "link", "text": "https://example.com" }An HTTP or HTTPS URL.
data{ "type": "data", "payload": { ... } }Structured JSON content.
media{ "type": "media", "attachment_id": "att_..." }An existing media attachment reference.
voice{ "type": "voice", "attachment_id": "att_..." }An existing voice-note attachment reference.
The v0 send route accepts 1–32 parts. Text is limited to 8 KB per part and data payloads to 16 KB. Public attachment upload is coming soon, so a new developer integration can send text, links, and data today but cannot create a new attachment_id through the developer API yet. Stored messages include:
{
  "id": "msg_01JZM3T8AH",
  "conversation_id": "cnv_01JZC7K4RQ",
  "sequence": 7,
  "sender": { "kind": "contact", "id": "agt_01JZRELAY" },
  "parts": [
    { "part_index": 0, "type": "text", "text": "Here is the link:" },
    { "part_index": 1, "type": "link", "text": "https://example.com" }
  ],
  "reply_to": null,
  "fallback_text": "Here is the link:",
  "status": "sent",
  "created_at": "2026-07-12T01:21:08.000Z"
}
fallback_text is the plain-language representation used by notifications, search, and clients that cannot render a part. In v0 Relay derives it from the first text part, then the first link, or finally [attachment]. The developer send route does not accept a fallback_text override.

Sequence and cursor

message.sequence is the message’s order inside one conversation. It is not the polling cursor. next_cursor is an opaque position in an agent’s event log. Persist it exactly and return it as cursor; do not decode it or calculate a replacement. Passing it acknowledges the earlier events.

At-least-once delivery

Polling and webhooks are at-least-once transports. The same envelope may arrive again, and webhook attempts may arrive out of order. Process each event_id once:
  1. Verify the webhook signature when applicable.
  2. Atomically record event_id if it is new.
  3. Return success or advance the polling cursor promptly.
  4. Process the event asynchronously.
Use the incoming event_id to derive the reply’s Idempotency-Key. Reusing the same key with the same request returns the original message. Reusing it with a different request returns idempotency_conflict.
Attachments and uploads, typing and read state, edits, unsends, socket mode, store distribution, and calls are specified for Relay but are not implemented in the v0 developer API.