---
title: "Get one call's full internal lifecycle event log"
method: GET
path: "/v1/calls/{call_id}/events"
---

# Get one call's full internal lifecycle event log

`GET /v1/calls/{call_id}/events`

## Request

- Base URL: `https://api.skysay.ai`
- URL: `https://api.skysay.ai/v1/calls/{call_id}/events`
- Auth: HTTP bearer

One bounded page of this call's call_events rows, oldest first, each with its raw event payload. This is the platform's own internal bookkeeping log, not a webhook replay: it includes types never delivered to any webhook endpoint at all (e.g. wallet.reserved, call.authorization, call.policy_evaluated, number.matched), and every row here is this row's own id/type/payload, never the signed webhook envelope. call.failed is written here too (payload shape varies by writer -- see CallEvent.payload), always thinner than the richer result body its webhook delivers separately. call.completed is the one terminal type NEVER written here at all -- it is derived from the call's final status and delivered only to webhooks; look at status on GET /v1/calls/{call_id} for that outcome instead. The response body is the plain array it has always been; the paging state is in headers. Do NOT read one page as the whole history: check X-Has-More, and continue with after=<the last row's page_cursor> until it is false. A long-running call with many agent tool invocations (each is its own call.agent_tool_call row) is exactly the case that needs more than one page.

## Path parameters

- `call_id` string, required

## Query parameters

- `limit` integer
- `after` string

## Response `200`

One page of call events, oldest first

- CallEvent[]
  - `call_id` string, required
  - `created_at` string, date-time, required
  - `id` string, required
  - `organization_id` string, required
  - `page_cursor` string, required: This row's position in the timeline. Pass the LAST row's value as `after` to continue from here. Opaque: its encoding is not part of the contract, and it is not the row's id.
  - `payload` object, required: Event-specific detail; shape varies by writer, not just by type -- call.failed alone has at least four shapes: a policy block carries {reason, reasons}; local/queued-origination failure carries {reason} (the worker-queue originate failure adds worker_job_id and error); a reconciler repair carries {source, reason, previous_status, evidence, observed_at}; a gateway-lifecycle failure (SIP/room) carries an unrelated {gateway_event_id, gateway_id, source_component, source, observed_at, details}. Always thinner than the richer result body the call.failed webhook delivers separately.
  - `type` string, required: e.g. call.created, call.queued, call.dialing, call.answered, media.started, call.ended, call.failed, call.canceled, call.live_blocked, plus internal-only bookkeeping types such as wallet.reserved, wallet.settled, call.authorization, call.policy_evaluated, number.matched. call.completed is the one terminal type never written here -- it is only ever delivered to webhooks.

## Other responses

- `400`: limit is not a number or is out of range, or after is not a page cursor.
- `404`: Call not found

---

[API](https://skmtc.dev/skysay/apis/skysay-api.md) · [All operations](https://skmtc.dev/skysay/apis/skysay-api/llms.txt) · [OpenAPI document](https://skmtc.dev/skysay/apis/skysay-api/revisions/148c7e111260?raw)
