Data Export
Walk away with everything stored. Two surfaces: a per-project Data Export API for logs, metrics, usage, and execution audit, and a GDPR surface for personal-data export and account deletion. Downloads are NDJSON, the lifecycle is queue, poll, download, and the deletion path holds a grace window before the data goes.
- Max export window
90 daysWider ranges rejected with 400.- Download format
application/x-ndjsonAlways, regardless of requested format.- GDPR download window
30 daysPer-account cadence also 30 days.- Deletion grace
30 daysCancellable until the grace window expires.
Two export systems:
- Data Export API (
/exports/), Export request logs, aggregated metrics, and usage/billing data in CSV, JSON, or JSONL format. - GDPR Data Rights (
/gdpr/), Export all personal data or request account deletion with a configurable grace period.
Data Export API
The Data Export API supports project-scoped export of logs, metrics, usage, and execution-audit data. Logs, metrics, and usage exports are queued for background processing; execution exports are materialised synchronously on the create call.
All routes are mounted under /:project_id/v1/. Replace
:project_id with your project's external id (for example
proj_abc123). Authentication uses a project API key passed
as Authorization: Bearer xero_my-project_....
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /:project_id/v1/exports/logs | Queue a request-log export. Returns 202 with status pending. |
| POST | /:project_id/v1/exports/metrics | Queue an aggregated-metrics export. Returns 202 with status pending. |
| POST | /:project_id/v1/exports/usage | Queue a usage/billing export. Returns 202 with status pending. |
| POST | /:project_id/v1/exports/executions | Materialise an execution-audit NDJSON export synchronously. Returns 202 with status completed on success. |
| GET | /:project_id/v1/exports | List export jobs. Query params: limit (default 20, max 100), offset (default 0), status (optional filter). |
| GET | /:project_id/v1/exports/:id | Get export job status. |
| GET | /:project_id/v1/exports/:id/download | Download a completed export. The response is always application/x-ndjson served as export-<uuid>.ndjson. |
| DELETE | /:project_id/v1/exports/:id | Cancel a pending or processing export. The job transitions to status cancelled. |
Response Shape
Every endpoint that returns an export job uses the same DTO:
| Field | Type | Description |
|---|---|---|
id |
string (UUID) | Server-generated bare UUID. Subsequent status/download calls must use this exact value. |
export_type |
string | One of logs, metrics, usage, executions. |
format |
string | The requested format (recorded but only jsonl/NDJSON is materialised in-tree). |
status |
string | See Export Job Statuses. |
start_date / end_date |
string (ISO 8601) | The bounds the job was created with (echoed back as response fields even though the request uses since/until). |
filters |
object | Filter blob persisted verbatim. No typed schema is enforced at the router boundary. |
created_at / completed_at / failed_at |
string (ISO 8601), nullable | Lifecycle timestamps. |
error_message |
string, nullable | Populated when status is failed. |
download_url |
string, nullable | Canonical download path. Present once the job is completed. |
Export Types
All create-export bodies use since and until
(ISO 8601) for the export window. The legacy keys start_date
and end_date are not accepted on input and
will be rejected with a 400 invalid_request_error.
Logs Export
Export request-level logs with optional filtering:
POST /:project_id/v1/exports/logs
{
"since": "2026-01-01T00:00:00Z",
"until": "2026-01-31T23:59:59Z",
"format": "jsonl",
"filters": {
"endpoint_ids": ["endpoint-prod"],
"status_codes": [200, 429],
"model_name": "llama-3.1-8b",
"min_latency_ms": 100,
"max_latency_ms": 5000,
"region": "RegionOne"
}
}
Metrics Export
Export aggregated metrics:
POST /:project_id/v1/exports/metrics
{
"since": "2026-01-01T00:00:00Z",
"until": "2026-01-31T23:59:59Z",
"format": "jsonl"
}
Usage Export
Export usage and billing data:
POST /:project_id/v1/exports/usage
{
"since": "2026-01-01T00:00:00Z",
"until": "2026-01-31T23:59:59Z",
"format": "jsonl"
}
Executions Export
Export execution-audit rows as NDJSON. Unlike the other endpoints, this
one materialises the payload synchronously on the create call: the
response carries status completed (or failed)
and the download is immediately available. All body fields are optional;
an empty body or {} requests the default 24-hour window
ending at the current time.
POST /:project_id/v1/exports/executions
{
"since": "2026-01-01T00:00:00Z",
"until": "2026-01-31T23:59:59Z",
"format": "jsonl",
"filters": {}
}
Filter Options
Filters are persisted verbatim as a free-form JSON object and applied by the export worker. The keys below are the worker-recognised shape today; the router does not enforce a typed filter schema, so unknown keys are accepted and stored but may be ignored downstream.
| Filter | Type | Description |
|---|---|---|
endpoint_ids |
array of strings | Filter by endpoint slug(s). |
status_codes |
array of integers | Filter by HTTP response status codes. |
model_name |
string | Filter by model name. |
min_latency_ms |
integer | Minimum request latency in milliseconds. |
max_latency_ms |
integer | Maximum request latency in milliseconds. |
region |
string | Filter by region identifier. |
The maximum date range for any export is 90 days. Requests with a wider range are rejected with a 400 error.
Export Formats
| Format | Content Type | Description |
|---|---|---|
jsonl |
application/x-ndjson | One JSON object per line. The only format materialised in-tree today; recommended for all exports. |
json |
application/x-ndjson | Aspirational. Accepted by the create endpoints but the download always returns NDJSON. |
csv |
application/x-ndjson | Aspirational. Accepted by the create endpoints but the download always returns NDJSON. |
The download endpoint always returns
application/x-ndjson served as
export-<uuid>.ndjson, regardless of the
format recorded on the job. Content negotiation is not
supported and Accept-Encoding: gzip is ignored.
Only the executions export materialises its payload
synchronously on create. logs, metrics, and
usage jobs are persisted in the pending
state and remain queued until an out-of-tree worker picks them up.
Export Workflow
The general lifecycle for queued exports (logs, metrics, usage):
- Request, Submit an export request (
POST /:project_id/v1/exports/logs, etc.). The router responds with 202 Accepted and a job DTO whosestatusispending. - Poll, Check the job (
GET /:project_id/v1/exports/:id) untilstatusiscompletedorfailed. - Download, Fetch the NDJSON file (
GET /:project_id/v1/exports/:id/download).
The executions endpoint short-circuits steps 1 and 2: the
create call returns 202 with status: completed (or
failed) and the download is immediately available. The
id on every job is a bare UUID; the status and download
endpoints validate it as such and 404 on any other shape.
Export Job Statuses
| Status | Description |
|---|---|
pending |
Export is queued for processing. This is the initial status for logs, metrics, and usage jobs. |
processing |
Export is being generated by the worker. |
completed |
Export is ready for download. |
failed |
Export failed. Check error_message on the job DTO. |
cancelled |
Export was cancelled via DELETE /:project_id/v1/exports/:id before completion. |
Example Workflow
The examples below use proj_abc123 as a stand-in for the
project's external id. Replace it with your own.
# 1. Request an export (202 Accepted with status "pending")
curl -X POST https://xerotier.ai/proj_abc123/v1/exports/logs \
-H "Authorization: Bearer xero_my-project_abc123" \
-H "Content-Type: application/json" \
-d '{
"since": "2026-01-01T00:00:00Z",
"until": "2026-01-31T23:59:59Z",
"format": "jsonl"
}'
# Response: {"id": "d3a1b2c4-5678-49ab-9cde-0123456789ab", "status": "pending", ...}
# 2. Poll for completion
curl https://xerotier.ai/proj_abc123/v1/exports/d3a1b2c4-5678-49ab-9cde-0123456789ab \
-H "Authorization: Bearer xero_my-project_abc123"
# Response: {"id": "d3a1b2c4-...", "status": "completed", "download_url": "...", ...}
# 3. Download the NDJSON file (always application/x-ndjson)
curl -o export.ndjson \
https://xerotier.ai/proj_abc123/v1/exports/d3a1b2c4-5678-49ab-9cde-0123456789ab/download \
-H "Authorization: Bearer xero_my-project_abc123"
import requests
import time
PROJECT_ID = "proj_abc123"
BASE = f"https://xerotier.ai/{PROJECT_ID}/v1"
headers = {
"Authorization": "Bearer xero_my-project_abc123",
"Content-Type": "application/json"
}
# 1. Request an export
response = requests.post(
f"{BASE}/exports/logs",
headers=headers,
json={
"since": "2026-01-01T00:00:00Z",
"until": "2026-01-31T23:59:59Z",
"format": "jsonl"
}
)
response.raise_for_status() # 202 Accepted
export_id = response.json()["id"] # bare UUID
# 2. Poll for completion
while True:
status_resp = requests.get(f"{BASE}/exports/{export_id}", headers=headers)
status = status_resp.json()["status"]
if status == "completed":
break
if status in ("failed", "cancelled"):
raise Exception(f"Export {status}")
time.sleep(5)
# 3. Download the NDJSON payload
download = requests.get(f"{BASE}/exports/{export_id}/download", headers=headers)
download.raise_for_status()
with open("export.ndjson", "wb") as f:
f.write(download.content)
const PROJECT_ID = "proj_abc123";
const BASE = `https://xerotier.ai/${PROJECT_ID}/v1`;
const headers = {
"Authorization": "Bearer xero_my-project_abc123",
"Content-Type": "application/json"
};
// 1. Request an export
const createResp = await fetch(
`${BASE}/exports/logs`,
{
method: "POST",
headers,
body: JSON.stringify({
since: "2026-01-01T00:00:00Z",
until: "2026-01-31T23:59:59Z",
format: "jsonl"
})
}
);
const { id: exportId } = await createResp.json(); // bare UUID
// 2. Poll for completion
let status;
do {
const statusResp = await fetch(`${BASE}/exports/${exportId}`, { headers });
const job = await statusResp.json();
status = job.status;
if (status === "failed" || status === "cancelled") {
throw new Error(`Export ${status}`);
}
if (status !== "completed") {
await new Promise(r => setTimeout(r, 5000));
}
} while (status !== "completed");
// 3. Download the NDJSON payload (always application/x-ndjson)
const download = await fetch(`${BASE}/exports/${exportId}/download`, { headers });
const ndjson = await download.text();
console.log(`Exported ${ndjson.split("\n").filter(Boolean).length} records`);
GDPR Data Rights
The GDPR endpoints allow you to export all personal data associated with your account or request account deletion.
GDPR Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /gdpr/export | Request a GDPR data export. |
| GET | /gdpr/exports | List your export requests (paginated). |
| GET | /gdpr/export/:exportId | Get export request status. |
| GET | /gdpr/export/:exportId/download | Download the export file. |
| DELETE | /gdpr/export/:exportId | Delete an export. |
| POST | /gdpr/export/:exportId/cancel | Cancel a pending export. |
Export Status Values
| Status | Description |
|---|---|
pending |
Export request created, processing not yet started. |
processing |
Export is being generated. |
completed |
Export is ready for download. |
failed |
Export failed or was cancelled. Check errorMessage for details. |
expired |
Export file has expired (download window has passed). |
deleted |
Export was manually deleted by the user. |
Synchronous best-effort: POST /gdpr/export
runs payload generation inline and returns 202 Accepted
with the final status set on the same response. The status is
completed when generation and upload succeeded and
failed otherwise, callers should inspect the returned
status and errorMessage rather than assume
success.
Cancelling a GDPR export via
POST /gdpr/export/:exportId/cancel terminates the job but
transitions it to failed, not cancelled.
Only the deletion lifecycle (below) uses a distinct
cancelled status.
Exported Data
A GDPR export includes the personal data associated with your account:
- Project information
- Model metadata
- Endpoint configurations
- API key metadata (not the key values themselves)
- Usage events (capped at 10,000 records)
- Invoices
- Audit logs (capped at 10,000 records)
Exports are generated as JSON and stored securely in object storage. The download window and per-account export cadence are both 30 days by default; check with your deployment operator if you need different values.
Account Deletion
Account deletion is a two-step process with a grace period to prevent accidental data loss.
Deletion Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /gdpr/delete | Request account deletion. Returns a confirmation token. |
| GET | /gdpr/delete/:requestId | Get deletion request status. |
| POST | /gdpr/delete/:requestId/confirm | Confirm deletion using the confirmation token. Starts the grace period. |
| POST | /gdpr/delete/:requestId/cancel | Cancel deletion during the grace period. |
| GET | /gdpr/delete/preview | Preview what data would be deleted. |
Deletion Statuses
| Status | Description |
|---|---|
pending |
Request created, awaiting confirmation token submission. |
confirmed |
Token submitted; grace period is active. Cancellation is still possible. |
processing |
Grace period has elapsed; deletion is in progress. |
completed |
All project data has been permanently deleted. |
cancelled |
Deletion was cancelled during the grace period. |
failed |
Deletion encountered an error during processing. |
Deletion Process
- Preview (optional), Use
GET /gdpr/delete/previewto see a summary of what data would be removed before committing. - Request, Submit a deletion request (
POST /gdpr/delete). A secure confirmation token is generated and delivered via email (in production environments). - Confirm, Submit the confirmation token to
POST /gdpr/delete/:requestId/confirmwith the body{"confirmationToken": "TOKEN"}. Confirmation starts the grace period. - Grace period, The grace period is 30 days by default. You can cancel at any time during this window using
POST /gdpr/delete/:requestId/cancel. - Deletion, After the grace period expires, all account data is permanently deleted by a background job.
Before requesting deletion, use the preview endpoint to see exactly what data would be removed. Consider exporting your data first using the GDPR export feature.