Integration API¶
Use the Tuor API from your backend service to send model traces and retrieve reviewed outputs. The Web Console handles organization setup, project creation, API key management, analytics, and reviewer workflows.
All integration endpoints are versioned under /v1. Examples use https://api.tuor.dev as the base URL.
Authentication¶
Send your API key in the X-API-Key header:
API keys are created in the Web Console and are scoped to the organization that issued them. Store them server-side only; do not expose them in browser code.
Limits and payload constraints¶
The ingest limit is 60 trace creates per minute per organization, shared by all of its API keys. Every integration endpoint has its own per-route limit and returns 429 Too Many Requests with Retry-After when exceeded.
Every JSON payload field is independently validated:
- Maximum size: 5 MB per field.
- Maximum nesting depth: 10 levels.
Oversize or over-nested payloads are rejected with 422 Unprocessable Entity.
Independently, the total request body is capped at 20 MiB. Requests over
that limit are rejected with 413 Payload Too Large before field-level
validation runs.
Core endpoints¶
| Method | Path | Use when |
|---|---|---|
POST |
/v1/traces/ |
Send an ordered model trajectory and structured output to Tuor for review. |
GET |
/v1/traces/{trace_id} |
Fetch one trace and its current review result. |
GET |
/v1/traces/ |
List or filter traces by project, status, or tag. |
GET |
/v1/traces/export |
Stream reviewed traces as JSONL for evals, dashboards, or training pipelines. |
GET |
/v1/tags/ |
List tags and copy IDs for trace tagging or export filters. |
POST |
/v1/traces/{trace_id}/tags |
Attach tags for routing, cohorting, or downstream filtering. |
DELETE |
/v1/traces/{trace_id}/tags/{tag_id} |
Remove a tag from a trace. |
Webhook receiver setup is covered separately in Webhooks.
Send a trace¶
POST /v1/traces/¶
Creates a new trace with status pending.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
project_id |
string | yes | The project that owns this trace. Copy it from the project's ID column in the Web Console. |
spans |
array | yes | Ordered provider-neutral trajectory. Create spans omit id; Tuor assigns one to every returned span. |
model_output |
object | yes | Structured JSON of what your model produced. Nested object leaves render as editable dot-path fields. |
output_schema |
object | no | Per-field schema that gives a path a typed reviewer control, keyed by dot path. Each entry is one of three types: enum (one-of-N label picker, e.g. {"invoice.status": {"type": "enum", "options": ["paid", "due"], "multi": false, "open": true}}), boolean (true/false toggle, {"type": "boolean"}), or json (an arbitrary-JSON subtree kept as a single editable value rather than expanded into child rows, {"type": "json"}). Paths absent from the schema are unconstrained JSON and their control is inferred from the value's type. Validated on ingest/correction, and included in exports. |
trace_config |
object | no | Metadata such as model, temperature, internal_run_id, or prompt_version_id. Included in exports. |
curl -X POST "https://api.tuor.dev/v1/traces/" \
-H "X-API-Key: $TUOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"project_id": "proj_abc123",
"spans": [
{
"type": "message",
"role": "system",
"content": [
{"type": "text", "value": "Extract the company name and return JSON."}
]
},
{
"type": "message",
"role": "user",
"content": [
{"type": "text", "value": "Invoice from Acme Corp."}
]
}
],
"model_output": {
"invoice": {
"company": "Acme Corp"
}
},
"trace_config": {
"model": "gpt-4o",
"internal_run_id": "run_01j2abcde"
}
}'
Span shapes¶
spans is a flat ordered array. Supported top-level types are:
{"type": "reasoning", "role": "assistant", "content": [{"type": "text", "value": "I should verify the total."}]}
{
"type": "tool_request",
"role": "assistant",
"call_id": "lookup_1",
"name": "lookup_invoice",
"content": [{"type": "json", "value": {"invoice_id": "inv_123"}}]
}
{
"type": "tool_response",
"role": "system",
"call_id": "lookup_1",
"content": [{"type": "json", "value": {"total": 1250.45}}]
}
{
"type": "event",
"role": "system",
"category": "metadata",
"content": [{"type": "json", "value": {"model": "gpt-4o", "usage": {"output_tokens": 84}, "stop_reason": "stop"}}]
}
Event categories are state for explicit lifecycle changes, metadata for measurements and descriptive attributes, and error for explicit non-tool failures. There is no event name field; keep provider-specific labels in content. Tool failures remain in tool-response content unless the provider supplies a separate failure record. EventSpans remain in detail responses and exports, but are excluded from prompt refinement. They are distinct from review-history entries in the response's top-level events array.
Every span, including events, accepts any of the ordered text, json, encrypted, refusal, image, or document blocks. Empty content arrays are allowed. Base64 media uses:
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"filename": "chart.png",
"value": "iVBORw0KGgo..."
}
}
filename and call_id are optional.
Unsupported or malformed spans are returned as { "type": "unknown", "content": [{ "type": "json", "value": <original JSON> }] }; unsupported or malformed blocks become { "type": "json", "value": <original block> }. This includes recognized block types with unsupported fields, so the complete block remains recoverable. Provider messages with missing or invalid roles become unknown spans rather than being attributed to the user. Anthropic search results retain their complete structure as JSON blocks. Missing, orphaned, duplicated, ambiguous, and out-of-order tool relationships are accepted and rendered as supplied.
Canonical spans no longer include extra. On direct canonical input, supplied extra and unexpected span attributes become a following metadata event with JSON blocks. A caller-supplied id is retained there as data; the API mints its own span ID. Missing, null, or empty-object extra produces no additional event; other JSON values are preserved unchanged. Separate source objects remain separate blocks, so overlapping keys are not overwritten. Stored span-level extra is migrated to an immediately following metadata event without interpreting its fields. A key named extra inside arbitrary JSON content is unaffected.
Response 201 returns the full TraceDetailResponse projection. Its exact top-level keys are id, created_by, org_id, project_id, status, spans, model_output, corrected_output, final_output, trace_config, output_schema, version, created_at, deleted_at, deleted_by, restored_at, restored_by, tags, and events.
| Field | Meaning |
|---|---|
id |
Store this if you want to fetch the trace later. |
status |
Starts as pending; later becomes approved, rejected, or corrected. |
spans |
Canonical ordered spans, including Tuor-generated internal IDs. Always present after creation. |
model_output |
The original structured review target you sent. |
output_schema |
The output field schema you sent, or null. |
corrected_output |
Reviewer-edited output, present after a correction. |
final_output |
The authoritative result: original output if approved, corrected output if corrected, null if rejected or still pending. |
trace_config |
Your metadata, round-tripped unchanged. |
tags |
Active tag summaries attached to the trace, shaped as {id, name, color}. |
events |
Public TraceEventResponse timeline entries for the trace. |
version |
Optimistic-lock version for advanced lifecycle writes. |
Read review results¶
GET /v1/traces/{trace_id}¶
Fetch one trace by ID. Use this when your system wants to poll for the final review outcome. The detail response uses the trace shape above, but returns tags as structured {id, name, color} summaries and includes an events array of public TraceEventResponse objects.
Each event has exactly id, org_id, trace_id, event_type, actor_type, actor_id, occurred_at, trace_version_before, trace_version_after, status_before, status_after, final_output_before, final_output_after, corrected_output_before, and corrected_output_after. The server returns the complete event sequence in ascending trace-version order; the Web Console presents that same sequence newest first without filtering or adding entries. When displaying a correction, the Web Console derives a display-only change list from these authoritative snapshots.
GET /v1/traces/¶
List traces with pagination and filters. Each item uses the lean TraceListItemResponse projection with exactly id, created_by, org_id, project_id, status, version, created_at, deleted_at, and structured tags. List items do not include spans, output/config JSON, lifecycle actor metadata, or events; fetch the detail endpoint when those fields are needed.
| Parameter | Type | Notes |
|---|---|---|
project_id |
string | Restrict to one project. |
status |
pending | approved | rejected | corrected |
Filter by review outcome. |
tag |
string | Restrict to traces carrying this tag ID. |
skip |
int | Offset for pagination. Default 0. |
limit |
int | Page size. Default 50, max 500. |
include_deleted |
bool | Include soft-deleted traces in the results. Default false. |
deleted_only |
bool | Return only soft-deleted traces. Default false. |
Export reviewed data¶
GET /v1/traces/export¶
Streams one JSON object per line. Use this for offline evals, dashboards, prompt analysis, or training-data jobs.
| Parameter | Type | Notes |
|---|---|---|
project_id |
string | Restrict to one project. |
status |
string | Commonly approved or corrected for training/eval datasets. |
tag |
string | Restrict to traces carrying this tag ID. |
from_date, to_date |
ISO 8601 | Filter by created_at. |
exclude_fields |
comma-separated paths | Remove nested keys through arrays, for example spans.content.source.value. |
include_deleted |
bool | Include soft-deleted traces in the stream. Default false. |
curl "https://api.tuor.dev/v1/traces/export?status=corrected&exclude_fields=spans.content.source.value" \
-H "X-API-Key: $TUOR_API_KEY"
Each JSONL row uses the compatibility/full-data TraceExportResponse projection. Its exact top-level keys are id, created_by, org_id, project_id, status, spans, model_output, corrected_output, final_output, trace_config, output_schema, version, created_at, deleted_at, deleted_by, restored_at, restored_by, and tags. Datetimes are ISO 8601 strings, status is its value string, and tags are active tag names. Trace events are not included.
{
"id": "trace_abc123",
"created_by": "user_2def3gh",
"org_id": "org_2xyz789",
"project_id": "proj_abc123",
"status": "corrected",
"spans": [
{
"id": "span_abc123",
"type": "message",
"role": "user",
"content": [{"type": "text", "value": "..."}]
}
],
"model_output": { "...": "..." },
"corrected_output": { "...": "..." },
"final_output": { "...": "..." },
"trace_config": { "...": "..." },
"output_schema": null,
"version": 2,
"created_at": "2026-05-20T10:00:00Z",
"deleted_at": null,
"deleted_by": null,
"restored_at": null,
"restored_by": null,
"tags": ["critical", "q2-audit"]
}
Exports intentionally share the public trace contract rather than raw ORM serialization. Most downstream jobs can rely on status, final_output, trace_config, and tags; fetch GET /v1/traces/{trace_id} when you also need the event timeline.
Tags¶
Tags are optional. Use them when you need to route review work or export a specific cohort.
Create and manage tags in the Web Console under Settings -> Tags. Integration code needs tag IDs, not tag names. To discover IDs, call GET /v1/tags/ and store the returned id for each tag you plan to apply.
The response is the full public tag response (TagResponse), not an abbreviated projection — every field below is always present. The fields most integrations need are id, name, color, and description:
[
{
"id": "tag_critical",
"name": "critical",
"color": "#ef4444",
"description": "High-priority reviews",
"org_id": "org_2xyz789",
"created_by": "user_2def3gh",
"version": 1,
"created_at": "2026-05-20T10:00:00Z",
"updated_at": "2026-05-20T10:00:00Z",
"updated_by": "user_2def3gh",
"deleted_at": null,
"deleted_by": null,
"restored_at": null,
"restored_by": null
}
]
curl -X POST "https://api.tuor.dev/v1/traces/trace_abc123/tags" \
-H "X-API-Key: $TUOR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "tag_ids": ["tag_critical"] }'
A trace can have up to 10 tags.
Advanced¶
Most integrations do not need these endpoints on day one.
| Area | Endpoints | Notes |
|---|---|---|
| Prompt versions | GET /v1/projects/{project_id}/prompts, POST /v1/projects/{project_id}/prompts, POST /v1/projects/{project_id}/prompts/refine, POST /v1/projects/{project_id}/prompts/accept-refinement |
Use when your service fetches active prompts from Tuor at runtime. See Dynamic Prompts. |
| Trace lifecycle automation | POST /v1/traces/{trace_id}/review, POST /v1/traces/{trace_id}/reset, DELETE /v1/traces/{trace_id}, POST /v1/traces/{trace_id}/restore |
The Web Console is the normal review surface. These endpoints require expected_version to prevent conflicting writes. |
| Project and tag administration | /v1/projects/*, /v1/tags/* |
Usually handled in the Web Console. Useful only for provisioning automation. Webhook changes and project deletion are admin-only and not available to API keys. |
Error responses¶
Errors are returned as JSON:
Common status codes:
| Status | Meaning |
|---|---|
400 |
Malformed request. |
401 |
Missing or invalid API key. |
402 |
Trace limit reached. |
403 |
Caller is authenticated but not allowed to do this. Either a Web Console (user session) request has no organization selected, or the action is admin-only (changing a project's webhook, deleting a project, removing demo data, creating API keys) and the caller isn't an organization admin. API keys are never admins, so they always get 403 on admin-only actions. |
404 |
Resource does not exist or is not visible to this organization. |
409 |
Optimistic-lock mismatch or forbidden lifecycle transition. |
422 |
Payload exceeds size/depth limits or fails schema validation. |
429 |
Rate limit exceeded. Respect Retry-After. |