API reference

Sessions

Open a conversation, list conversations, read a transcript, and close one.

Create a session#

HTTP
POST /v1/assistants/{assistant_id}/sessions

Opens a conversation. Do this once, then send as many turns as you like against the session_id it returns.

Body#

channelstringchat or voice. Defaults to chat. Updates itself as the conversation moves between doors.
visitorobjectAnswers to the assistant's pre-chat form, keyed by its field keys.
metadataobjectYour own labels. Up to 16 keys, string values up to 500 characters.

Request#

Terminal
curl -X POST \
  "https://studio.zeevaa.ai/api/v1/assistants/9f2b1c84-6e3a-4d17-b0c5-2e7a8f41d9b3/sessions" \
  -H "Authorization: Bearer $ZEEVAA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "chat",
    "visitor": { "name": "Sara", "email": "sara@example.com" },
    "metadata": { "user_id": "u_8812", "plan": "pro" }
  }'

Response#

JSON
{
  "session_id": "c31f9a70-84b2-4e05-9d6c-1a7f3b2e6d48",
  "assistant_id": "9f2b1c84-6e3a-4d17-b0c5-2e7a8f41d9b3",
  "status": "active",
  "channel": "chat",
  "greeting": "Hi Sara! How can I help you today?",
  "max_call_seconds": 600,
  "started_at": "2026-08-04T09:14:22.108Z"
}

Keys in visitor that the assistant's form does not declare are dropped rather than rejected — an extra field in your payload is not an outage. Required fields that are missing return 422.

Errors#

404 assistant_not_foundUnknown, or not reachable by this key.
409 assistant_not_publishedThe assistant is a draft or disabled.
422 missing_visitor_fieldsRequired pre-chat fields were not supplied. The message names them.

List sessions#

HTTP
GET /v1/sessions

Conversations in the key's workspace, newest first.

Query parameters#

assistant_idstringRestrict to one assistant.
statusstringactive, ended, expired or blocked.
sourcestringapi, web or embed. Defaults to api — your integration's own conversations. Pass all for everything.
from / tostringISO 8601 bounds on started_at.
limitinteger1–100. Default 50.
cursorstringFrom next_cursor on the previous page.

Response#

JSON
{
  "sessions": [
    {
      "id": "c31f9a70-84b2-4e05-9d6c-1a7f3b2e6d48",
      "assistant_id": "9f2b1c84-6e3a-4d17-b0c5-2e7a8f41d9b3",
      "assistant_name": "Marina sales desk",
      "title": "What do you have with two bedrooms?",
      "status": "ended",
      "channel": "chat",
      "source": "api",
      "ended_reason": "visitor_left",
      "summary": "Asked about two-bedroom availability under 2M…",
      "visitor": { "name": "Sara", "email": "sara@example.com" },
      "metadata": { "user_id": "u_8812" },
      "message_count": 6,
      "tool_call_count": 2,
      "started_at": "2026-08-04T09:14:22.108Z",
      "ended_at": "2026-08-04T09:21:47.930Z",
      "duration_ms": 445822
    }
  ],
  "next_cursor": "c2Vzc2lvbjoxNzU0",
  "has_more": true
}

title is taken from the first thing the person said. summary is generated after the conversation ends and may be null for one that is still running.

Sessions with no turns are omitted. Somebody who opened a connection and left is not a conversation, and listing them would make the history mostly noise.

Retrieve a transcript#

HTTP
GET /v1/sessions/{session_id}

One conversation in full.

Response#

JSON
{
  "id": "c31f9a70-84b2-4e05-9d6c-1a7f3b2e6d48",
  "assistant_id": "9f2b1c84-6e3a-4d17-b0c5-2e7a8f41d9b3",
  "status": "ended",
  "channel": "chat",
  "source": "api",
  "visitor": { "name": "Sara", "email": "sara@example.com" },
  "metadata": { "user_id": "u_8812" },
  "summary": "Asked about two-bedroom availability under 2M…",
  "started_at": "2026-08-04T09:14:22.108Z",
  "ended_at": "2026-08-04T09:21:47.930Z",
  "duration_ms": 445822,
  "usage": {
    "input_tokens": 4820,
    "output_tokens": 913,
    "total_tokens": 5733,
    "tts_characters": 612,
    "stt_seconds": 44.2
  },
  "turns": [
    {
      "seq": 1,
      "id": "msg_01H9",
      "role": "user",
      "content": "What do you have with two bedrooms under 2M?",
      "input_mode": "text",
      "offset_ms": 0,
      "created_at": "2026-08-04T09:14:31.882Z"
    },
    {
      "seq": 2,
      "id": "msg_01HA",
      "role": "assistant",
      "content": "We have three that match your budget…",
      "input_mode": "text",
      "offset_ms": 1204,
      "first_token_ms": 842,
      "created_at": "2026-08-04T09:14:36.117Z",
      "tool_calls": [
        {
          "name": "search_records",
          "status": "success",
          "duration_ms": 38,
          "input": { "bedrooms": 2, "price": { "lte": 2000000 } },
          "output": { "total": 3 }
        }
      ]
    }
  ]
}

Turn fields#

seqintegerThe sort key. Assigned on write, monotonic, and it cannot tie. Order by this, never by a timestamp — two turns can share a millisecond.
rolestringuser or assistant.
input_modestringtext or voice — how this particular turn arrived.
offset_msintegerMilliseconds from the start of the conversation. This is what builds a timeline.
first_token_msintegerGeneration start to first streamed word, on assistant turns. The number that decides whether a voice call feels alive.
tool_callsarrayTools used on this turn, with inputs, outputs and durations.

Errors#

404 session_not_foundUnknown, belongs to another workspace, or aged out of the assistant's retention window.

End a session#

HTTP
POST /v1/sessions/{session_id}/end

Body#

reasonstringvisitor_left, assistant_ended or handoff. Defaults to visitor_left.

Any other value is recorded as visitor_left. An integration cannot label its own conversations a successful handoff, because outcome reporting that anyone can write is not reporting.

Request#

Terminal
curl -X POST \
  "https://studio.zeevaa.ai/api/v1/sessions/c31f9a70-84b2-4e05-9d6c-1a7f3b2e6d48/end" \
  -H "Authorization: Bearer $ZEEVAA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason": "visitor_left"}'

Response#

JSON
{
  "id": "c31f9a70-84b2-4e05-9d6c-1a7f3b2e6d48",
  "status": "ended",
  "ended_reason": "visitor_left",
  "ended_at": "2026-08-04T09:21:47.930Z",
  "duration_ms": 445822
}

Idempotent. Calling it on an already-closed session returns the session as it stands, with the reason that was recorded first — a later, vaguer reason never overwrites a more specific one.

Next#

  • Chat — send a turn
  • Usage — what it all cost