Ingest a session batch
curl --request POST \
--url https://api.get-exo.com/v1/ingest/session-batch \
--header 'Content-Type: application/json' \
--header 'X-Exo-API-Key: <api-key>' \
--data '
{
"events": [
{
"claudeCodeVersion": "<string>",
"cwd": "<string>",
"lineTimestamp": "2023-11-07T05:31:56Z",
"lineType": "<string>",
"lineUuid": "<string>",
"orgId": "<string>",
"payload": {},
"sessionId": "<string>",
"userId": "<string>",
"agentId": "<string>",
"authorRole": "<string>",
"gitBranch": "<string>",
"parentSessionId": "<string>"
}
]
}
'import requests
url = "https://api.get-exo.com/v1/ingest/session-batch"
payload = { "events": [
{
"claudeCodeVersion": "<string>",
"cwd": "<string>",
"lineTimestamp": "2023-11-07T05:31:56Z",
"lineType": "<string>",
"lineUuid": "<string>",
"orgId": "<string>",
"payload": {},
"sessionId": "<string>",
"userId": "<string>",
"agentId": "<string>",
"authorRole": "<string>",
"gitBranch": "<string>",
"parentSessionId": "<string>"
}
] }
headers = {
"X-Exo-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-Exo-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
events: [
{
claudeCodeVersion: '<string>',
cwd: '<string>',
lineTimestamp: '2023-11-07T05:31:56Z',
lineType: '<string>',
lineUuid: '<string>',
orgId: '<string>',
payload: {},
sessionId: '<string>',
userId: '<string>',
agentId: '<string>',
authorRole: '<string>',
gitBranch: '<string>',
parentSessionId: '<string>'
}
]
})
};
fetch('https://api.get-exo.com/v1/ingest/session-batch', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"deduped": 123,
"ingested": 123
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}Coding sessions
Ingest a session batch
Accept up to 500 coding-session events in one round trip and return immediately.
POST
/
v1
/
ingest
/
session-batch
Ingest a session batch
curl --request POST \
--url https://api.get-exo.com/v1/ingest/session-batch \
--header 'Content-Type: application/json' \
--header 'X-Exo-API-Key: <api-key>' \
--data '
{
"events": [
{
"claudeCodeVersion": "<string>",
"cwd": "<string>",
"lineTimestamp": "2023-11-07T05:31:56Z",
"lineType": "<string>",
"lineUuid": "<string>",
"orgId": "<string>",
"payload": {},
"sessionId": "<string>",
"userId": "<string>",
"agentId": "<string>",
"authorRole": "<string>",
"gitBranch": "<string>",
"parentSessionId": "<string>"
}
]
}
'import requests
url = "https://api.get-exo.com/v1/ingest/session-batch"
payload = { "events": [
{
"claudeCodeVersion": "<string>",
"cwd": "<string>",
"lineTimestamp": "2023-11-07T05:31:56Z",
"lineType": "<string>",
"lineUuid": "<string>",
"orgId": "<string>",
"payload": {},
"sessionId": "<string>",
"userId": "<string>",
"agentId": "<string>",
"authorRole": "<string>",
"gitBranch": "<string>",
"parentSessionId": "<string>"
}
] }
headers = {
"X-Exo-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-Exo-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
events: [
{
claudeCodeVersion: '<string>',
cwd: '<string>',
lineTimestamp: '2023-11-07T05:31:56Z',
lineType: '<string>',
lineUuid: '<string>',
orgId: '<string>',
payload: {},
sessionId: '<string>',
userId: '<string>',
agentId: '<string>',
authorRole: '<string>',
gitBranch: '<string>',
parentSessionId: '<string>'
}
]
})
};
fetch('https://api.get-exo.com/v1/ingest/session-batch', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"deduped": 123,
"ingested": 123
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}{
"code": "<string>",
"detail": "<string>",
"status": 123,
"title": "<string>",
"type": "<string>",
"documentationUrl": "<string>",
"errors": [
{
"code": "<string>",
"field": "<string>",
"message": "<string>",
"pointer": "<string>"
}
],
"requestId": "",
"suggestedAction": "<string>"
}Accept up to 500 coding-session events in one round trip and return immediately. This is the route a backfill should use.
Events are queued under the credential’s org and user in a single insert, with the same per-event deduplication as the single-event route, so a retried batch is reported as
Every error is an RFC 9457
deduped rather than stored twice. Call POST /v1/ingest/session-finalize once the last batch is accepted.
Authorization
This route requires thewrite scope.
A missing or invalid credential returns 401 authentication_error. A valid credential without the scope returns 403 permission_denied, and the problem body names the exact scope required.
Headers
These are request conventions the contract does not declare as parameters, so they do not appear in the schema tables below.| Header | Applies | What it does |
|---|---|---|
X-Exo-API-Key | Required | Your Exo API key, exo_<env>_<token>. Authorization: Bearer exo_... is accepted as an alternate. |
X-Exo-Subject | Refused | This route scopes data by the key owner. Sending the header returns 400 subject_not_supported, rather than silently reading the wrong partition. |
Idempotency-Key | Not honoured | This route does not replay: the same per-event deduplication covers a retried batch. |
X-Request-Id | Response | Returned on every response, including 204s. Quote it in a support request. |
Success responses
| Status | Meaning |
|---|---|
202 | How many events were queued and how many were duplicates. |
Errors
| Status | Code | When |
|---|---|---|
400 | invalid_request | Malformed request, for example an invalid cursor. |
401 | authentication_error | Missing or invalid credentials. |
403 | permission_denied | Missing scope, or an unprovisioned subject selector. |
404 | not_found | The resource does not exist, or is not visible to this caller. |
409 | conflict | A conflict with the current state of the resource. |
422 | validation_error | The request was understood but failed field validation. |
429 | rate_limit_exceeded | Rate limit exceeded. Retry after the Retry-After interval. |
503 | service_degraded | A dependency is temporarily unavailable. Safe to retry. |
application/problem+json body carrying a stable code, a requestId, and a suggestedAction where Exo has one. See Errors.
Notes
- Up to 500 events per call. This is the route a backfill should use.
- Call finalize once the last batch is accepted, or the layers that session ingestion does not build on its own will not be built.
Authorizations
ApiKeyHeaderBearerAuth
An Exo API key (exo_...) sent as the X-Exo-API-Key header.
Body
application/json
POST /v1/ingest/session-batch: bulk from daemon + retroactive bootstrap.
Maximum array length:
500Show child attributes
Show child attributes
⌘I