{"openapi":"3.1.0","info":{"title":"FireTrace API","version":"1.0.0","description":"Key-authenticated API of a FireTrace deployment. Every request carries `Authorization: Bearer ft_live_<keyId>_<secret>`; the key decides the project and the scopes. `traces:write`: Record traces (ingestion API and MCP record_trace). `traces:read`: List and read traces and project stats (REST and MCP read tools). `traces:delete`: Delete traces (REST and MCP delete_trace)."},"servers":[{"url":"https://tracing.art3m1s.me"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"ft_live_<keyId>_<secret>","description":"Project API key. Scopes: traces:write, traces:read, traces:delete."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","requestId"],"properties":{"code":{"type":"string","enum":["invalid_json","invalid_trace","invalid_request","invalid_api_key","insufficient_scope","not_found","trace_id_conflict","trace_finished","span_conflict","conflict","payload_too_large","quota_exhausted","trial_limit_reached","not_configured","internal_error"]},"message":{"type":"string"},"requestId":{"type":"string"}}}}},"IngestRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"schemaVersion":{"type":"number","const":1},"trace":{"type":"object","properties":{"id":{"type":"string","pattern":"^[0-9a-fA-F]{32}$"},"name":{"type":"string","minLength":1,"maxLength":500},"status":{"type":"string","enum":["ok","error","unset"]},"startedAt":{"type":"string"},"endedAt":{"type":"string"},"provider":{"type":"string","minLength":1,"maxLength":200},"model":{"type":"string","minLength":1,"maxLength":200},"sessionId":{"type":"string","minLength":1,"maxLength":200},"userId":{"type":"string","minLength":1,"maxLength":200},"tags":{"default":[],"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"input":{"$ref":"#/$defs/__schema0"},"output":{"$ref":"#/$defs/__schema0"},"metadata":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"usage":{"default":{},"type":"object","properties":{"inputTokens":{"type":"integer","minimum":0,"maximum":9007199254740991},"outputTokens":{"type":"integer","minimum":0,"maximum":9007199254740991},"totalTokens":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"costUsd":{"type":"number","minimum":0},"spans":{"maxItems":200,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[0-9a-fA-F]{16}$"},"parentSpanId":{"anyOf":[{"type":"string","pattern":"^[0-9a-fA-F]{16}$"},{"type":"null"}]},"name":{"type":"string","minLength":1,"maxLength":500},"kind":{"default":"custom","type":"string","enum":["llm","agent","tool","chain","retriever","embedding","reranker","custom"]},"status":{"default":"unset","type":"string","enum":["ok","error","unset"]},"startedAt":{"type":"string"},"endedAt":{"type":"string"},"provider":{"type":"string","minLength":1,"maxLength":200},"model":{"type":"string","minLength":1,"maxLength":200},"input":{"$ref":"#/$defs/__schema0"},"output":{"$ref":"#/$defs/__schema0"},"attributes":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"events":{"default":[],"maxItems":50,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":500},"timestamp":{"type":"string"},"attributes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["name","timestamp"],"additionalProperties":false}},"usage":{"type":"object","properties":{"inputTokens":{"type":"integer","minimum":0,"maximum":9007199254740991},"outputTokens":{"type":"integer","minimum":0,"maximum":9007199254740991},"totalTokens":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"costUsd":{"type":"number","minimum":0}},"required":["id","name","startedAt","endedAt"],"additionalProperties":false}}},"required":["id","name","startedAt"],"additionalProperties":false}},"required":["schemaVersion","trace"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]}}},"SpansRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"schemaVersion":{"type":"number","const":1},"spans":{"minItems":1,"maxItems":200,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[0-9a-fA-F]{16}$"},"parentSpanId":{"anyOf":[{"type":"string","pattern":"^[0-9a-fA-F]{16}$"},{"type":"null"}]},"name":{"type":"string","minLength":1,"maxLength":500},"kind":{"default":"custom","type":"string","enum":["llm","agent","tool","chain","retriever","embedding","reranker","custom"]},"status":{"default":"unset","type":"string","enum":["ok","error","unset"]},"startedAt":{"type":"string"},"endedAt":{"type":"string"},"provider":{"type":"string","minLength":1,"maxLength":200},"model":{"type":"string","minLength":1,"maxLength":200},"input":{"$ref":"#/$defs/__schema0"},"output":{"$ref":"#/$defs/__schema0"},"attributes":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"events":{"default":[],"maxItems":50,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":500},"timestamp":{"type":"string"},"attributes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["name","timestamp"],"additionalProperties":false}},"usage":{"type":"object","properties":{"inputTokens":{"type":"integer","minimum":0,"maximum":9007199254740991},"outputTokens":{"type":"integer","minimum":0,"maximum":9007199254740991},"totalTokens":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"costUsd":{"type":"number","minimum":0}},"required":["id","name","startedAt","endedAt"],"additionalProperties":false}}},"required":["schemaVersion","spans"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]}}},"SpansResult":{"type":"object","required":["ok","traceId","added","duplicate","spanCount","requestId"],"properties":{"ok":{"type":"boolean"},"traceId":{"type":"string"},"added":{"type":"integer","description":"Spans written by this request"},"duplicate":{"type":"integer","description":"Spans already stored with identical content; nothing written for them"},"spanCount":{"type":"integer","description":"Spans on the trace after this request"},"requestId":{"type":"string"}}},"EndRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"schemaVersion":{"type":"number","const":1},"endedAt":{"type":"string"},"status":{"type":"string","enum":["ok","error","unset"]},"provider":{"type":"string","minLength":1,"maxLength":200},"model":{"type":"string","minLength":1,"maxLength":200},"output":{"$ref":"#/$defs/__schema0"},"usage":{"type":"object","properties":{"inputTokens":{"type":"integer","minimum":0,"maximum":9007199254740991},"outputTokens":{"type":"integer","minimum":0,"maximum":9007199254740991},"totalTokens":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"costUsd":{"type":"number","minimum":0},"metadata":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"tags":{"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":64}},"spans":{"maxItems":200,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","pattern":"^[0-9a-fA-F]{16}$"},"parentSpanId":{"anyOf":[{"type":"string","pattern":"^[0-9a-fA-F]{16}$"},{"type":"null"}]},"name":{"type":"string","minLength":1,"maxLength":500},"kind":{"default":"custom","type":"string","enum":["llm","agent","tool","chain","retriever","embedding","reranker","custom"]},"status":{"default":"unset","type":"string","enum":["ok","error","unset"]},"startedAt":{"type":"string"},"endedAt":{"type":"string"},"provider":{"type":"string","minLength":1,"maxLength":200},"model":{"type":"string","minLength":1,"maxLength":200},"input":{"$ref":"#/$defs/__schema0"},"output":{"$ref":"#/$defs/__schema0"},"attributes":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}},"events":{"default":[],"maxItems":50,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":500},"timestamp":{"type":"string"},"attributes":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}},"required":["name","timestamp"],"additionalProperties":false}},"usage":{"type":"object","properties":{"inputTokens":{"type":"integer","minimum":0,"maximum":9007199254740991},"outputTokens":{"type":"integer","minimum":0,"maximum":9007199254740991},"totalTokens":{"type":"integer","minimum":0,"maximum":9007199254740991}},"additionalProperties":false},"costUsd":{"type":"number","minimum":0}},"required":["id","name","startedAt","endedAt"],"additionalProperties":false}}},"required":["schemaVersion","endedAt"],"additionalProperties":false,"$defs":{"__schema0":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"},{"type":"array","items":{"$ref":"#/$defs/__schema0"}},{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"$ref":"#/$defs/__schema0"}}]}}},"EndResult":{"type":"object","required":["ok","traceId","duplicate","spanCount","requestId"],"properties":{"ok":{"type":"boolean"},"traceId":{"type":"string"},"duplicate":{"type":"boolean","description":"True when the trace had already ended with this exact body; nothing written"},"spanCount":{"type":"integer"},"requestId":{"type":"string"}}},"MetadataPatch":{"type":"object","required":["metadata"],"additionalProperties":false,"properties":{"metadata":{"type":"object","additionalProperties":true,"description":"Keys to merge into the trace's metadata. Shallow: a key here replaces that top-level key outright; keys not mentioned are left alone."}}},"MetadataPatchResult":{"type":"object","required":["ok","traceId","metadata","changed","requestId"],"properties":{"ok":{"type":"boolean"},"traceId":{"type":"string"},"metadata":{"type":"object","additionalProperties":true},"changed":{"type":"boolean","description":"False when the merge matched what was already stored; nothing written"},"requestId":{"type":"string"}}},"TraceSummary":{"type":"object","required":["id","name","status","startedAt","endedAt","durationMs","spanCount","errorCount"],"properties":{"id":{"type":"string","pattern":"^[0-9a-f]{32}$"},"name":{"type":"string"},"status":{"type":"string","enum":["ok","error","unset","running"],"description":"`running` until the trace's end request arrives (streamed traces); the client-set status afterwards"},"environment":{"type":["string","null"],"description":"Environment of the API key that recorded the trace, stamped by the server at ingest; null = unassigned (keys without one, and every trace recorded before environments existed)"},"startedAt":{"type":"string","format":"date-time"},"endedAt":{"type":["string","null"],"format":"date-time","description":"Null while the trace is running"},"durationMs":{"type":["integer","null"],"description":"Null while the trace is running"},"provider":{"type":["string","null"]},"model":{"type":["string","null"]},"sessionId":{"type":["string","null"]},"userId":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}},"usage":{"type":"object","properties":{"inputTokens":{"type":"integer","minimum":0},"outputTokens":{"type":"integer","minimum":0},"totalTokens":{"type":"integer","minimum":0}}},"costUsd":{"type":["number","null"]},"spanCount":{"type":"integer"},"errorCount":{"type":"integer"},"estimatedBytes":{"type":"integer"},"ingestedAt":{"type":["string","null"],"format":"date-time"},"scores":{"type":"object","description":"Newest score per name. The full history is at /api/v1/traces/{traceId}/scores.","additionalProperties":{"$ref":"#/components/schemas/ScoreSummary"}}}},"Span":{"type":"object","required":["id","traceId","parentSpanId","name","kind","status","startedAt","endedAt","durationMs"],"properties":{"id":{"type":"string","pattern":"^[0-9a-f]{16}$"},"traceId":{"type":"string"},"parentSpanId":{"type":["string","null"]},"name":{"type":"string"},"kind":{"type":"string","enum":["llm","agent","tool","chain","retriever","embedding","reranker","custom"]},"status":{"type":"string","enum":["ok","error","unset"]},"startedAt":{"type":"string","format":"date-time"},"endedAt":{"type":"string","format":"date-time"},"durationMs":{"type":"integer"},"provider":{"type":["string","null"]},"model":{"type":["string","null"]},"input":{},"output":{},"attributes":{"type":"object","additionalProperties":true},"events":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"timestamp":{"type":"string","format":"date-time"},"attributes":{"type":["object","null"],"additionalProperties":true}}}},"usage":{"type":"object","properties":{"inputTokens":{"type":"integer","minimum":0},"outputTokens":{"type":"integer","minimum":0},"totalTokens":{"type":"integer","minimum":0}},"nullable":true},"costUsd":{"type":["number","null"]}}},"ScoreSummary":{"type":"object","required":["scoreId","dataType","value","evaluatorId"],"properties":{"scoreId":{"type":"string"},"dataType":{"type":"string","enum":["numeric","categorical","boolean"]},"value":{"oneOf":[{"type":"number"},{"type":"string"},{"type":"boolean"}],"description":"A number for numeric, a string for categorical, a boolean for boolean scores"},"evaluatorId":{"type":["string","null"]}}},"ScoreInput":{"type":"object","required":["name","dataType","value"],"additionalProperties":false,"properties":{"name":{"type":"string","pattern":"^[A-Za-z0-9_-]+$","maxLength":64,"description":"Doubles as the score's display name; letters, digits, '_' and '-'"},"dataType":{"type":"string","enum":["numeric","categorical","boolean"]},"value":{"oneOf":[{"type":"number"},{"type":"string"},{"type":"boolean"}],"description":"A number for numeric, a string for categorical, a boolean for boolean scores"},"comment":{"type":"string","maxLength":2000},"spanId":{"type":"string","pattern":"^[0-9a-fA-F]{16}$","description":"Scope the score to one span instead of the whole trace"}}},"Score":{"type":"object","required":["id","traceId","name","dataType","value","source","createdAt"],"properties":{"id":{"type":"string","pattern":"^[0-9a-f]{16}$"},"traceId":{"type":"string","pattern":"^[0-9a-f]{32}$"},"spanId":{"type":["string","null"]},"name":{"type":"string"},"dataType":{"type":"string","enum":["numeric","categorical","boolean"]},"value":{"oneOf":[{"type":"number"},{"type":"string"},{"type":"boolean"}],"description":"A number for numeric, a string for categorical, a boolean for boolean scores"},"comment":{"type":["string","null"]},"source":{"type":"string","enum":["api","annotation","eval"]},"evaluatorId":{"type":["string","null"]},"runId":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"}}},"ScorePage":{"type":"object","required":["scores","nextCursor","pageSize"],"properties":{"scores":{"type":"array","items":{"$ref":"#/components/schemas/Score"}},"nextCursor":{"type":["string","null"]},"pageSize":{"type":"integer"}}},"TracePage":{"type":"object","required":["traces","nextCursor","prevCursor","pageSize"],"properties":{"traces":{"type":"array","items":{"$ref":"#/components/schemas/TraceSummary"}},"nextCursor":{"type":["string","null"]},"prevCursor":{"type":["string","null"]},"pageSize":{"type":"integer"}}},"Project":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string"},"traceCount":{"type":"integer"},"spanCount":{"type":"integer"},"estimatedBytes":{"type":"integer","description":"FireTrace's serialized-size estimate, not Firebase's billable measurement"},"lastTraceAt":{"type":["string","null"],"format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"storage":{"type":"object","properties":{"limitBytes":{"type":"integer"},"level":{"type":"string","enum":["ok","warning","critical"]}}},"keyScopes":{"type":"array","items":{"type":"string","enum":["traces:write","traces:read","traces:delete"]}}}},"IngestResult":{"type":"object","required":["ok","traceId","projectId","spanCount","duplicate","running","requestId"],"properties":{"ok":{"type":"boolean"},"traceId":{"type":"string"},"projectId":{"type":"string"},"spanCount":{"type":"integer"},"duplicate":{"type":"boolean"},"running":{"type":"boolean","description":"True when the body had no endedAt and the trace waits for its end request"},"requestId":{"type":"string"}}}}},"paths":{"/api/v1/project":{"get":{"operationId":"getProject","summary":"The project this key belongs to, with counters and storage estimate","description":"Requires `traces:read`.","responses":{"200":{"description":"Project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/traces":{"post":{"operationId":"recordTrace","summary":"Record one trace: complete when endedAt is present, otherwise running","description":"Requires `traces:write`. With `trace.endedAt` the body is one complete, immutable trace. Without it the trace is stored as `running` (send no `status`): append finished spans with `POST /api/v1/traces/{traceId}/spans` and close it with `POST /api/v1/traces/{traceId}/end`. Idempotent per trace id: an identical resend returns 200 with duplicate=true; a different body for the same id returns 409. Limits: 200 spans, 50 events per span, 20 tags, 2097152 request bytes, 768000 bytes per stored document.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestRequest"}}}},"responses":{"200":{"description":"Identical duplicate; nothing written","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResult"}}}},"201":{"description":"Stored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestResult"}}}},"400":{"description":"Invalid JSON or schema violation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Trace id reused with different content","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Body or document too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Firestore quota exhausted; nothing written","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listTraces","summary":"List traces newest first with cursor pagination","description":"Requires `traces:read`. Filters combine with AND. Use `after`/`before` cursors from a previous page; never offsets. `sort=slowest` (by durationMs) and `sort=costliest` (by costUsd; traces without a cost are omitted) combine only with `status`, `model`, `name`, `tag` and `environment`; adding `sessionId`, `userId`, `from` or `to` is a 400. A cursor is only valid under the sort that produced it. The query string is strict: an unknown parameter, or an unknown value for `status`, `sort`, `environment`, `from` or `to`, is a `400 invalid_request` naming it, so a misspelled filter can never come back as unfiltered data.","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["ok","error","unset","running"]},"description":"`running` selects streamed traces that have not ended yet"},{"name":"model","in":"query","schema":{"type":"string"}},{"name":"name","in":"query","schema":{"type":"string"},"description":"Exact trace name"},{"name":"tag","in":"query","schema":{"type":"string"},"description":"One tag the trace must carry"},{"name":"environment","in":"query","schema":{"type":"string","pattern":"^([a-z0-9][a-z0-9_-]{0,31}|unassigned)$"},"description":"Environment stamped from the recording key (case-folded), or `unassigned` for traces recorded by keys without one"},{"name":"sessionId","in":"query","schema":{"type":"string"}},{"name":"userId","in":"query","schema":{"type":"string"}},{"name":"sort","in":"query","schema":{"type":"string","enum":["newest","slowest","costliest"],"default":"newest"}},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"Inclusive lower bound on startedAt"},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Inclusive upper bound on startedAt"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"after","in":"query","schema":{"type":"string"},"description":"nextCursor from a previous page (older traces)"},{"name":"before","in":"query","schema":{"type":"string"},"description":"prevCursor from a previous page (newer traces)"}],"responses":{"200":{"description":"A page of traces","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TracePage"}}}},"400":{"description":"Unknown parameter or value, or an invalid cursor; the message names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/traces/{traceId}":{"parameters":[{"name":"traceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{32}$"}}],"get":{"operationId":"getTrace","summary":"One trace with all of its spans and scores","description":"Requires `traces:read`.","responses":{"200":{"description":"Trace, spans, and scores","content":{"application/json":{"schema":{"type":"object","required":["trace","spans","scores"],"properties":{"trace":{"allOf":[{"$ref":"#/components/schemas/TraceSummary"},{"type":"object","properties":{"input":{},"output":{},"metadata":{"type":"object"},"metadataUpdatedAt":{"type":["string","null"],"format":"date-time"},"bodyHash":{"type":"string"}}}]},"spans":{"type":"array","items":{"$ref":"#/components/schemas/Span"}},"scores":{"type":"array","description":"Every score of the trace, newest first","items":{"$ref":"#/components/schemas/Score"}}}}}}},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such trace in this project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"patchTraceMetadata","summary":"Merge keys into a stored trace's metadata","description":"Requires `traces:write`. The only mutable part of a stored trace: everything it was ingested with stays write-once. The merge is shallow, and `bodyHash` is deliberately not recomputed so re-sending the original trace is still a duplicate rather than a `409`. Metadata is not indexed, so it cannot be filtered, ordered, or aggregated by.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetadataPatch"}}}},"responses":{"200":{"description":"Merged metadata (written, or unchanged when it matched)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MetadataPatchResult"}}}},"400":{"description":"Invalid JSON, a field other than metadata, or a key Firestore refuses","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such trace in this project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request over 2 MiB, or the merged document over the per-document limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Firestore quota exhausted; nothing written","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteTrace","summary":"Delete a trace and all of its spans","description":"Requires `traces:delete`. This is the only way data leaves Firestore besides the dashboard; FireTrace never deletes on its own.","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"traceId":{"type":"string"}}}}}},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such trace in this project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/traces/{traceId}/spans":{"parameters":[{"name":"traceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{32}$"}}],"post":{"operationId":"appendSpans","summary":"Append finished spans to a running trace","description":"Requires `traces:write`. Each span is complete and immutable, exactly as in the whole-trace form; a span whose parent has not arrived yet is fine (children usually finish first). Idempotent per span id: a span already stored with identical content counts as a duplicate, one stored with different content fails the whole batch with 409 `span_conflict` and nothing is written. The trace may hold at most 200 spans in total.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpansRequest"}}}},"responses":{"200":{"description":"Spans written and/or recognised as duplicates","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpansResult"}}}},"400":{"description":"Invalid JSON, a schema violation, or the span limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such trace in this project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The trace has already ended (trace_finished), or a span id is reused with different content (span_conflict)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request or a span document too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Firestore quota exhausted; nothing written","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/traces/{traceId}/end":{"parameters":[{"name":"traceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{32}$"}}],"post":{"operationId":"endTrace","summary":"Close a running trace","description":"Requires `traces:write`. Sets `endedAt`, `status` (default `unset`) and the other end-only fields, merges `metadata` shallowly, adds `tags`, and may carry the last batch of finished spans in `spans`. Only now does the trace count in the dashboard's rollups. Idempotent: the same end body again is a 200 duplicate; a different one after the trace has ended is a 409 `trace_finished`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EndRequest"}}}},"responses":{"200":{"description":"Ended (or an identical duplicate; nothing written)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EndResult"}}}},"400":{"description":"Invalid JSON, a schema violation, endedAt before startedAt, or the span limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such trace in this project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The trace already ended with a different body (trace_finished), or a span id is reused with different content (span_conflict)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request too large, or the finished trace document over the per-document limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Firestore quota exhausted; nothing written","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/traces/{traceId}/scores":{"parameters":[{"name":"traceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{32}$"}}],"post":{"operationId":"addScore","summary":"Attach a score to a stored trace","description":"Requires `traces:write`. A score is a judgement made after the run: a rating, a reviewer's verdict, an evaluator's result. Scores are append-only; adding a name again records a newer score, and the trace's `scores` summary keeps the newest per name. Unlike metadata, scores are indexed: list them per trace or across the project with `GET /api/v1/scores`. Limits: 100 scores per trace, names of up to 64 characters, comments of up to 2000 characters.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScoreInput"}}}},"responses":{"201":{"description":"Stored","content":{"application/json":{"schema":{"type":"object","required":["ok","score","requestId"],"properties":{"ok":{"type":"boolean"},"score":{"$ref":"#/components/schemas/Score"},"requestId":{"type":"string"}}}}}},"400":{"description":"Invalid JSON, or a body that is not a score (the message names the field)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such trace in this project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The trace already holds the maximum number of scores","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Request over 2 MiB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Firestore quota exhausted; nothing written","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listTraceScores","summary":"Every score of one trace, newest first","description":"Requires `traces:read`.","responses":{"200":{"description":"Scores of the trace","content":{"application/json":{"schema":{"type":"object","required":["traceId","scores"],"properties":{"traceId":{"type":"string"},"scores":{"type":"array","items":{"$ref":"#/components/schemas/Score"}}}}}}},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such trace in this project","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/traces/{traceId}/scores/{scoreId}":{"parameters":[{"name":"traceId","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{32}$"}},{"name":"scoreId","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}}],"delete":{"operationId":"deleteScore","summary":"Delete one score","description":"Requires `traces:delete`. If it was the newest score of its name, the previous one takes its place in the trace's summary.","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"traceId":{"type":"string"},"scoreId":{"type":"string"}}}}}},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such score on this trace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/scores":{"get":{"operationId":"listScores","summary":"List scores across the project newest first with cursor pagination","description":"Requires `traces:read`. Filters combine with AND. `environment` is resolved through each score's trace (scores never store one). The query string is strict: an unknown parameter or value is a `400 invalid_request` naming it.","parameters":[{"name":"name","in":"query","schema":{"type":"string"},"description":"Exact score name"},{"name":"environment","in":"query","schema":{"type":"string","pattern":"^([a-z0-9][a-z0-9_-]{0,31}|unassigned)$"},"description":"Only scores whose trace is in this environment, or `unassigned` for traces without one"},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"Inclusive lower bound on createdAt"},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Inclusive upper bound on createdAt"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":500,"default":50}},{"name":"after","in":"query","schema":{"type":"string"},"description":"nextCursor from a previous page (older scores)"}],"responses":{"200":{"description":"A page of scores","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScorePage"}}}},"400":{"description":"Unknown parameter or value, or an invalid cursor; the message names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/mcp":{"post":{"operationId":"mcp","summary":"Model Context Protocol endpoint (Streamable HTTP, stateless)","description":"JSON-RPC 2.0 over HTTP as specified by MCP. Authenticate with the same bearer key; the tools offered depend on the key's scopes. See docs/mcp.md.","responses":{"200":{"description":"JSON-RPC response"},"401":{"description":"Missing, invalid, revoked, or expired API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key lacks the required scope, or (on instances with trial mode) the account has used its trial traces (trial_limit_reached)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Unexpected error or unconfigured deployment (includes a requestId)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}