Get started

Recurz API · v1

One key. One URL.
Every product.

Every product, one shape. Learn one endpoint and you can guess the rest: the same IDs, errors, paging and versioning everywhere, served as REST, GraphQL and MCP from one schema.

https://api.recurz.ai/tracez/v1/sessions/ses_01J9ZK4M7T/turns
One hostfor every product Productone per Recurz product Versionper product, never breaks Nounplural, snake_case IDprefixed, so you can tell what it is
# Any product, same token, same shape
curl https://api.recurz.ai/tracez/v1/sessions?harness=codex \
  -H "Authorization: Bearer $RECURZ_TOKEN"

{"data": [{"id": "9f2c41…", "title": "Fix the flaky auth test", "source": "codex",
           "steps": 214, "errors": 3, "live": false}],
 "next_cursor": "1760541002113", "has_more": true}

Preview This is the v1 layout. Endpoints marked live answer on api.recurz.ai today; built ones run inside their product (Tracez's at tracez.recurz.ai) and are on their way here. The cookbook uses today's addresses.

Three ways in

Every operation exists on all three, spelled the way each one's users expect. Tracez has all three today; the other products have REST and MCP, and every endpoint below says what is live.

REST

Live

Plain HTTPS and JSON, with an OpenAPI 3.1 spec per product.

GET  /tracez/v1/sessions/{id}
POST /tracez/v1/sessions/{id}/redact

GraphQL

Tracez live

One graph across every product. Follow a finding to its session to its turns in one query.

finding(id: "fnd_01J9…") {
  kind session { turns(first: 5) { … } }
}

MCP

Live

For agents. One server per product, or one for the whole platform.

https://api.recurz.ai/tracez/v1/mcp   get_session
https://api.recurz.ai/mcp             tracez_get_session

One operation, three spellings. Each column follows the convention its own users already know, so nothing reads like a translation.

RESTHTTP verb on a plural noun, like Stripe GraphQLcamelCase fields, Relay connections and inputs MCP toolverb_noun, like GitHub's MCP server
GET /sessionssessions(first:)list_sessions
GET /sessions/{id}session(id:)get_session
POST /sessions/{id}/redactredactSession(input:)redact_session

Products

Each product owns a few nouns and the verbs on them. LiveBuilt, not on the API yetPlanned

Tracez Every run, in one store

Sessions, turns, artifacts and a change feed for every agent run from every harness.

api.recurz.ai/tracez/v1
  • GET/sessionsList sessions; filter with q, harness, project
  • GET/sessions/{id}Get one session
  • GET/sessions/{id}/turnsPage through its turns, with revisions
  • GET/sessions/{id}/childrenEvery sub-agent it spawned, as a tree
  • POST/sessions/{id}/redactScrub a session, keep its shape
  • DELETE/sessions/{id}Delete it for good
  • POST/artifactsAttach a file to a session
  • GET/changes?follow=trueEvery change, live, resumable
  • POST/searchFull-text search across turns, with highlights
  • POST/saved_searchesKeep a search; follow it for new matches
  • GET/rollups?period=daySessions, turns, cost and findings per day
  • POST/legal_holdsKeep sessions past retention
  • POST/exportsStart an export of everything

Model calls go through Steerz unchanged: OpenAI(base_url="https://steerz.recurz.ai/v1") or Anthropic(base_url="https://steerz.recurz.ai") with a Steerz key (stz_…), and the route it picked comes back in x-steerz-* headers. Collectors and OTLP exporters send to ingest.recurz.ai.

The same everywhere

The rules every product follows, so nothing in the second product surprises you after the first.

IDs say what they are

Opaque, prefixed, sortable by time. Paste one anywhere and you know what it is.

ses_ session fnd_ finding run_ run job_ job org_ org req_ request

Tracez v1 session ids are the 64-character hex ids your harness already logs; ses_ ids arrive with v2. Money is in US dollars, in fields ending _usd.

Lists page the same way

?limit= and ?cursor= in; data, next_cursor and has_more out. Add ?follow=true to any list to get it live over Server-Sent Events, resumable with Last-Event-ID.

One error shape

{"error": {"type": "invalid_request",
  "code": "session_not_found",
  "param": "session_id",
  "request_id": "req_01J9ZP…"}}

The shape OpenAI and Anthropic clients already parse. code is stable; message is for people.

Safe to retry

Send Idempotency-Key on any POST and a retry returns the first result instead of doing it twice. Every response carries Recurz-Request-Id.

Long work is a run

Evals and other long jobs return a run with one status set: queued, running, succeeded, failed, cancelled. Poll it, follow it, or get a webhook.

Boring formats

snake_case fields. Times in RFC 3339 UTC. Money as a decimal string, "cost_usd": "0.0042", never a float. Big numbers as strings.

Hear about it Webhooks and SSE live

One event log per org. Webhooks and Server-Sent Events read it today, with GraphQL subscriptions to follow, and every one resumes from the same event ID.

# Every event, on every transport
{"id": "evt_01J9ZQ4C2W",
 "type": "sentinelz.finding.created",
 "created_at": "2026-10-09T15:30:02Z",
 "api_version": "sentinelz/v1",
 "livemode": true,
 "data": {"object": {"id": "fnd_01J9ZM2Q8B",
                     "kind": "loop",
                     "session_id": "ses_01J9ZK4M7T", …}}}
Way inWhat you write
WebhookServers that should be calledPOST /platform/v1/webhook_endpoints
{"url": "…", "events": ["sentinelz.finding.*"]}
SSEScripts, dashboards, curlGET /platform/v1/events?types=sentinelz.*&follow=true
GraphQLUIs that want linked objectssubscription { events(types: […]) {
  … on Finding { session { title } } } }

Named the way you'd guess

{product}.{noun}.{verb}, like Stripe's invoice.paid with the product in front: tracez.rollup.ready, loadoutz.run.failed. Subscribe with globs like sentinelz.*.

Standard Webhooks

Signed to the Standard Webhooks spec, so existing libraries verify them in one line. Retried for three days, replayable from any event, and every attempt is listed under deliveries.

The object you already know

data.object is the resource exactly as its own GET returns it, at the version you pinned. Delivery is at least once, so dedupe on the event ID.

Versioning

If it's on this page, it won't break.

Keys

API key

Authorization: Bearer recurz_key_live_… on every product except Steerz, or recurz_key_test_… for test data. Steerz at steerz.recurz.ai takes its own stz_… keys, as Authorization: Bearer or x-api-key. A key belongs to one org, so no call ever takes an org parameter. Keys are recognisable by prefix, so a leaked one gets caught by secret scanning.

Device secret

recurz_device_…, made on the machine by recurz-collector login. It can only send transcripts to ingest.recurz.ai, and you can revoke one machine without touching anything else.

Cookbook

Common jobs end to end against Tracez as it runs today, each with a sequence diagram and the exact calls.

Specs: signed in, Tracez serves its own at tracez.recurz.ai/api/specs/ (openapi.json, tracez.proto, schema.graphql, mcp-tools.json); every product's at api.recurz.ai/{product}/v1/openapi.json is on the way. Want in? founders@recurz.ai