Skip to main content
This transcript creates an agent token, waits for a message, and replies. You need only curl.
These examples use https://api.relayapp.im. To use a local Relay server, set RELAY_API_URL=http://localhost:8788 instead.
1

Create an agent

In the Relay app, choose Create your own, enter the contact details, and create it. Copy the Agent Token before closing the confirmation: Relay shows it once.
export RELAY_API_URL="https://api.relayapp.im"
export RELAY_AGENT_TOKEN="relay_agt_live_..."
2

Verify the token

curl -sS "$RELAY_API_URL/v1/agents/me" \
  -H "Authorization: Bearer $RELAY_AGENT_TOKEN"
{
  "agent": {
    "id": "agt_01JZRELAY",
    "handle": "scheduler",
    "display_name": "Scheduler",
    "tagline": "Finds a time that works",
    "avatar_url": null,
    "distribution": "store",
    "created_at": "2026-07-12T01:20:00.000Z"
  }
}
3

Poll for a message

Send the new contact a message in Relay, then long-poll for up to 25 seconds:
curl -sS "$RELAY_API_URL/v1/events?timeout=25" \
  -H "Authorization: Bearer $RELAY_AGENT_TOKEN"
{
  "events": [
    {
      "event_id": "evt_01JZE9M2XW",
      "event_type": "message.received",
      "agent_id": "agt_01JZRELAY",
      "created_at": "2026-07-12T01:21:03.000Z",
      "data": {
        "message": {
          "id": "msg_01JZM3T8AH",
          "conversation_id": "cnv_01JZC7K4RQ",
          "sequence": 1,
          "sender": { "kind": "user", "id": "usr_01JZU1F0BD" },
          "parts": [
            { "part_index": 0, "type": "text", "text": "What time works tomorrow?" }
          ],
          "reply_to": null,
          "fallback_text": "What time works tomorrow?",
          "status": "sent",
          "created_at": "2026-07-12T01:21:03.000Z"
        }
      }
    }
  ],
  "next_cursor": "djE6MQ=="
}
Save next_cursor. Pass it back as cursor on the next request; doing so acknowledges everything before that cursor. Treat the cursor as opaque. Because delivery is at least once, also store processed event_id values and ignore duplicates.
4

Reply

Use the received message’s conversation_id. The idempotency key must be 8–255 characters; deriving it from the event ID makes a retry safe.
curl -sS -X POST "$RELAY_API_URL/v1/messages" \
  -H "Authorization: Bearer $RELAY_AGENT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reply-evt_01JZE9M2XW" \
  -d '{
    "conversation_id": "cnv_01JZC7K4RQ",
    "parts": [
      { "type": "text", "text": "Tomorrow at 2:00 PM works for me." }
    ],
    "reply_to": { "message_id": "msg_01JZM3T8AH", "part_index": 0 }
  }'
Relay responds with 202 Accepted:
{
  "message_id": "msg_01JZM4Q9VN",
  "message": {
    "id": "msg_01JZM4Q9VN",
    "conversation_id": "cnv_01JZC7K4RQ",
    "sequence": 2,
    "sender": { "kind": "contact", "id": "agt_01JZRELAY" },
    "parts": [
      { "part_index": 0, "type": "text", "text": "Tomorrow at 2:00 PM works for me." }
    ],
    "reply_to": { "message_id": "msg_01JZM3T8AH", "part_index": 0 },
    "fallback_text": "Tomorrow at 2:00 PM works for me.",
    "status": "sent",
    "created_at": "2026-07-12T01:21:08.000Z"
  }
}
5

Continue from the cursor

curl -sS "$RELAY_API_URL/v1/events?cursor=djE6MQ%3D%3D&timeout=25" \
  -H "Authorization: Bearer $RELAY_AGENT_TOKEN"
If no new message arrives before the timeout, Relay returns an empty page and the same logical cursor:
{ "events": [], "next_cursor": "djE6MQ==" }
Next, read Concepts or switch event delivery to Webhooks.