Recording
/api/recording/* is the ingest surface for Clone's Recording layer. Producers push CloneEvent rows here; the server validates them against packages/schema/events.schema.json, persists them under a RecordingSession keyed by session_id, and is fully idempotent on the per-event id.
Endpoints
| Method | Path | Purpose |
|---|---|---|
| POST | /api/recording/events/ | Batch ingest. Body is a JSON array (1–1000 items). |
| GET | /api/recording/events/<id>/ | Fetch a single event with full payload. |
| GET | /api/recording/sessions/ | List the requester's 50 most recent sessions. |
| GET | /api/recording/sessions/<id>/events/ | List events for a session, oldest first. ?limit= (default 500, max 2000). |
Ingest semantics
- Idempotent on
id. Re-posting the sameidreturns aduplicatescount; the row is unchanged. Producers can retry freely. - Per-item validation. Each event in the batch is validated independently. The endpoint returns
accepted,duplicates, and aninvalid: [{ index, errors }]array; one bad row does not fail the batch. - Session ownership. The first event for a given
session_idcreates theRecordingSessionand locks it torequest.user. Subsequent events from a different user with the same id are rejected assession belongs to another user. - Session bookends. A
session.startedevent re-anchors the session'sstarted_at,source, andsource_detaileven if it arrives out of order; a latersession.stoppedevent setsended_atif the new timestamp is later than the existing one.
Example
curl -sS -X POST https://api.clone.is/api/recording/events/ \
-H "X-Clone-API-Key: $CLONE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '[
{
"id": "sess-1-start",
"session_id": "demo-1",
"occurred_at": "2026-05-05T12:00:00Z",
"source": "cli",
"type": "session.started"
},
{
"id": "sess-1-prompt",
"session_id": "demo-1",
"occurred_at": "2026-05-05T12:00:05Z",
"source": "agent",
"source_detail": "claude-code",
"type": "agent.prompt",
"agent": "Claude Code",
"prompt": "Run the test suite."
}
]'
Response shape
{
"accepted": 2,
"duplicates": 0,
"invalid": []
}
HTTP 201 if any event was newly persisted; HTTP 200 otherwise. Per-item failures look like:
{ "index": 0, "errors": ["'source' is a required property"] }
The first three schema errors per row are surfaced; remaining errors are truncated. The full validation rule set lives in Schema.