Skip to content

API reference

A quick reference to the HTTP endpoints you are most likely to call directly — for scripting, automation, or debugging. TheYgent has three services with an API, each with its own base URL:

  • Control plane — http://localhost:8080 — agents, runs, sessions, triggers, connections, artifacts, observability, and the AI gateway's configuration and reports.
  • Inference plane — http://localhost:8081 — the OpenAI-compatible data plane (/v1/*) and model management (/admin/*).
  • AI gateway — http://localhost:8082 — Claude Code's Anthropic-format traffic, plus a small admin surface.

The interface uses these same endpoints, so anything you can do in the UI you can do here. This page lists the user-facing surface; it is not the full internal API.

Wire conventions

Control-plane request bodies and stored run/session records use snake_case (session_id, content_hash). The immediate run result envelope and streaming (SSE) frames use camelCase (runId). The inference plane (/v1/* and /admin/*) is camelCase throughout. The graph document (ir) is always camelCase. Keep them straight when you assemble a body.

Authentication

Every control-plane endpoint below requires a bearer token — Authorization: Bearer <token> — where the token is either your interactive session or, for scripts and your own frontends, an API key minted from your profile. Endpoints are gated by role (viewer / editor / admin); see Users, roles & sign-in. Three surfaces differ: /healthz + /readyz are open, webhook delivery POST /hooks/{id} carries an HMAC signature instead, and POST /agents/{id}/invoke accepts an API key or the shared THEYGENT_INVOKE_TOKEN. The inference plane is a separate, locally-trusted service — still do not expose either plane to an untrusted network.


Control plane

Health

Method Path Purpose
GET /healthz Liveness — is the process up? Returns {"status":"ok"}.
GET /readyz Readiness — 200 when ready, 503 not-ready naming the down dependency (database or inference plane).

Agents

Published agents are immutable, content-addressed versions. See Drafts & publishing and Agent versioning.

Method Path Purpose
POST /agents Publish a new agent from an IR document. Body {ir, name?}. 201; reusing an existing id → 409 agent_exists.
GET /agents List agents, newest first (limit 1–200 default 50, before cursor).
GET /agents/{id} One agent with its versions, newest first.
POST /agents/{id}/versions Publish a new version. Identical content under an existing version is an idempotent 200; different content under the same version → 409 version_conflict.
GET /agents/{id}/versions/{version} The full stored IR + canvas layout for one version.
GET / PUT /agents/{id}/io-policy Read/set the per-agent I/O capture policy. PUT body {io_capture, io_retention_seconds?, redact_rules?}; io_retention_seconds is a positive number of seconds after which captured payloads are deleted (omit it to follow the platform's telemetry.io_retention_days). Setting it never changes the content hash.
DELETE /agents/{id} Delete the whole agent: all its versions, its triggers (schedules stop firing), and its I/O policy, in one step. 204; unknown id → 404 agent_not_found. Past runs, chats, and drafts survive — they keep the agent id and content hash as breadcrumbs.

Individual versions are immutable — there is no endpoint to edit or delete a single version. Deleting the agent removes every version at once; there is no delete for runs.

Samples

The shipped example-agent catalog. Installing publishes an ordinary agent (same registry, same gate as POST /agents) with your chosen models stamped into its bindings, creating any MCP connections the sample needs. See Sample agents.

Method Path Purpose
GET /samples The catalog: title, capabilities, model slots, connections, and per-sample installed state. Any signed-in role.
POST /samples/install Install by catalog id. Body {ids, models: {slot: {logical_id, binding}}}. Editor role. Unfilled slots → 400 sample_models_required, an illegal binding → 400 sample_model_invalid (nothing written in either case); unknown id → 404 sample_not_found. Beyond agents (children first for composition samples), an install creates the sample's connections (MCP servers, or a provider's token connection with no token yet) and RAG sources (reused by name on reinstall) and registers any shipped trigger disabled. The response report carries one entry per sample — installed, already_installed, or error (that sample rolled back and persisted nothing; the rest of the batch still ran) — with connections, rag_sources, and warnings (degraded-install notes, e.g. seed ingest could not start). Reinstalling after deleting only some of a sample's agents repairs the missing pieces.

Drafts

The editor's autosave tier: mutable work-in-progress graphs. A draft is never validated as a graph (a half-wired document must still save; only "the ir is a JSON object" is enforced → 400 invalid_draft) and never hashed. The canvas layout (view) is stored alongside the document. See Drafts & publishing.

Method Path Purpose
POST /drafts Create a draft. Body {ir, agent_id?} — agent_id links the draft to the published agent it edits (optional, immutable after create). 201.
PUT /drafts/{id} Replace the draft's document (the autosave write). Body {ir}.
GET /drafts List drafts, most recently edited first (limit 1–200 default 50, before cursor, optional agent_id filter). Summaries only — no ir/view.
GET /drafts/{id} The full draft, ir + view — what the editor reloads.
DELETE /drafts/{id} Discard a draft. 204.

Unknown ids are 404 draft_not_found. Unlike versions, drafts are freely deletable — the editor deletes the working draft itself after a successful publish.

Runs

Statuses: created → streaming → waiting → completed | failed. See Runs & sessions.

Method Path Purpose
POST /runs Plain prompt run. Body {input, model, params?, stream?, session_id?}. model is a logical id. Streams SSE by default.
POST /graphs/runs Run an inline IR graph — no publish needed (this is what the editor's Test panel uses). Body {ir, input, stream?, session_id?}. Invalid IR → 400 invalid_ir (no run row created).
POST /agents/{id}/runs Run a published agent by reference. Body {input, version?, content_hash?, session_id?, stream?}.
POST /agents/{id}/invoke Token-authed, non-interactive sibling of the above. Same body but stream defaults false. Requires a bearer token.
POST /agents/{id}/durable-runs Run a published agent on the durable runtime (the only way to run human/subgraph/loop/map agents). Returns 202 {run_id}; poll GET /runs/{id}. Needs durable mode.
GET /runs List runs, newest first (limit, before).
GET /runs/{id} Run detail, including output, error, content_hash, trigger_id, awaiting_node, completed_at.
POST /runs/{id}/resume Deliver input to a run paused (waiting) at a human node. Body {input}. Returns 202.

Start a streaming prompt run:

curl -N -X POST http://localhost:8080/runs \
  -H 'Content-Type: application/json' \
  -d '{"input": "Summarize the theory of relativity.", "model": "triage-fast", "stream": true}'

The stream is Server-Sent Events: run (status frames), delta (answer tokens), reasoning (model thinking, kept out of the output), terminated by data: [DONE]. Set "stream": false for a single JSON response {runId, status, output}.

Run a published agent (non-streaming) with a session:

curl -X POST http://localhost:8080/agents/agent.support/runs \
  -H 'Content-Type: application/json' \
  -d '{"input": {"question": "Where is my order?"}, "session_id": "ses_abc", "stream": false}'

Invoke an agent with the invoke token:

curl -X POST http://localhost:8080/agents/agent.support/invoke \
  -H 'Authorization: Bearer YOUR_INVOKE_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"input": {"question": "Where is my order?"}}'

Resume a waiting run:

curl -X POST http://localhost:8080/runs/RUN_ID/resume \
  -H 'Content-Type: application/json' \
  -d '{"input": {"approved": true}}'

Observability

Per-run trace data. See Observability.

Method Path Purpose
GET /runs/{id}/trace The persisted span tree (timing, status, attribution) — no payloads.
GET /runs/{id}/trace/stream Live SSE overlay: events span.open, span.close, done; : keepalive comment every 15s.
GET /runs/{id}/nodes/{node_id}/io Lazy per-node input/output payloads, gated by the capture policy (a gated state is a reason, never an error).

Triggers & webhooks

A trigger deploys a published, pinned agent behind an unattended entry point. See Triggers & webhooks.

Method Path Purpose
POST /triggers Create a trigger. Body {agent_id, kind, version?|content_hash?, config, enabled?}. Pin exactly one of version/content_hash.
GET /triggers List triggers (webhook secrets redacted to "***").
GET / PATCH / DELETE /triggers/{id} Read / update (enabled, config only — kind and pin are immutable) / delete a trigger.
POST /hooks/{id} Fire a webhook trigger. The raw JSON body is the run input; requires the X-TheYgent-Signature header. Returns 202.

Trigger config keys: schedule needs config.cron (a valid cron expression) and takes optional config.input; webhook needs config.secret (the inbound signing secret).

Fire a webhook — the signature is the HMAC-SHA256 of the raw request body keyed by the trigger's secret:

BODY='{"question":"Where is my order?"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "YOUR_WEBHOOK_SECRET" | awk '{print $2}')
curl -X POST http://localhost:8080/hooks/TRIGGER_ID \
  -H "X-TheYgent-Signature: sha256=$SIG" \
  -H 'Content-Type: application/json' \
  -d "$BODY"

A bad or missing signature → 401 invalid_signature; a disabled trigger → 409 trigger_disabled.

Sessions

Opt-in conversational memory. See Runs & sessions.

Method Path Purpose
GET /sessions List sessions, newest activity first.
GET /sessions/{id} Session detail with messages in order.
POST /sessions Create/ensure a session. Body {id?, metadata?}; a missing id is minted server-side.
POST /sessions/{id}/turns Append a {user_content, assistant_content} pair (both non-blank).
DELETE /sessions/{id} Delete the session and its messages; its runs are detached, not deleted.

Connections

Named tool/MCP credentials. The secret is write-only and never returned. See Tools and MCP servers.

Method Path Purpose
POST /connections Create. Body {name, kind, config, secret?, enabled?}. kind is http_auth or mcp_server.
GET /connections List (responses expose hasSecret only; secret-ish config keys redacted).
GET / PATCH / DELETE /connections/{id} Read / update (a non-empty secret rotates in place without changing any content hash) / delete.

Crawlers

The Crawlers browser's read path: the provider's public store, proxied and cached for five minutes, and an Actor's input schema converted into a crawler node config. Editor role. Nothing is created — installing is the editor dropping the returned config on the canvas. provider defaults to apify, the only one; another value is 400 crawler_provider_unknown. See Crawler.

Method Path Purpose
GET /crawlers/catalog Search the store. Query search?, category?, sort_by? (popularity · newest · relevance), limit? (≤ 100), offset?. Returns {entries, total, offset, limit}; each entry carries actor, title, author, categories, pricing {model, priceUsd, unit}, users30d, runs30d, successRate30d. The store unreachable → 502 crawler_catalog_error.
GET /crawlers/catalog/actor?actor=… One Actor: its entry, readmeSummary, defaultRunOptions, its input fields (form-ready, each marked exposable when a model could fill it), and suggested — a complete node config plus unfillable required fields and a suggestedId. An id that isn't username/actor-name → 400 invalid_actor; an unknown Actor → 404 crawler_not_found; no input schema → 404 crawler_schema_unavailable.
POST /crawlers/catalog/convert Re-derive a node config for a choice of exposed fields. Body {actor, provider?, expose?, scalar?, input?, description?, connection?, limits?} (camelCase, like the config). Returns the same shape as suggested; parameterSchema always comes from the Actor's real input schema.

RAG sources

Retrieval collections agents search through the rag node. See RAG sources.

Method Path Purpose
POST /rag/sources Create. Body {name, kind, embedding_model, config}. kind is upload or crawl; for crawl, config carries root_url (required), max_pages?, render_js?. embedding_model is a logical id, never an engine name. Creating a crawl source does not start the crawl — call :ingest.
GET /rag/sources List (keyset pagination via limit + before).
GET / PATCH / DELETE /rag/sources/{id} Read (status + live ingest progress) / rename or edit crawl config / delete with all documents and vectors.
GET /rag/sources/{id}/documents The source's documents, oldest first, with per-document status, chunk counts, and errors. Paged: limit (1–500, default 500) and after. The response is {documents, next_after}; pass next_after as after for the next page, and next_after: null marks the last page. An after that names no document of the source is a 400 invalid_rag_cursor.
POST /rag/sources/{id}/documents?filename=… Upload one document as a raw body (the Content-Type header is the file's mime). The size cap is the rag.max_upload_bytes setting, 50 MiB by default; a larger body is a 413 document_too_large, refused from its Content-Length before the body is read. Returns 202; poll the source for progress.
POST /rag/sources/{id}:ingest Start (or re-run) the crawl. 202; unchanged pages are hash-skipped. 409 while an ingest is already running.
POST /rag/sources/{id}:cancel Cancel the in-flight ingest (a no-op on a settled source).
POST /rag/sources/{id}/query Run the same hybrid retrieval a rag node runs. Body {query, top_k?, min_similarity?}; returns the match list.
curl -X POST "http://localhost:8080/rag/sources/rag_01ABC/documents?filename=handbook.pdf" \
  -H 'Content-Type: application/pdf' \
  --data-binary @handbook.pdf

Artifacts

Audio/image blobs by reference. See Voice.

Method Path Purpose
POST /artifacts Upload raw bytes (the Content-Type header is the stored mime). Returns 201 {ref, contentType, bytes}.
GET /artifacts/{ref} Fetch a stored art_… blob (only art_* ids are served).
PUT /artifacts/{ref} Store raw bytes under a ref you supply — the restore path an import uses so refs embedded in imported runs keep resolving. The ref must be a full art_ + 26-character id exactly as POST /artifacts mints them (400 invalid_artifact_ref otherwise). An existing ref is never overwritten: a fresh write returns 201 {ref, contentType, bytes, created: true}, a ref that already exists returns 200 with the stored artifact's metadata and created: false.
curl -X POST http://localhost:8080/artifacts \
  -H 'Content-Type: audio/webm' \
  --data-binary @recording.webm

MCP servers

Register external tool servers. See MCP servers and MCP tools.

Name-keyed registrations (plain stdio/remote servers, no secrets — openapi/graphql must be created as connections instead):

Method Path Purpose
PUT / GET / DELETE /admin/mcp/servers/{name} Register / read / remove a server config.
GET /admin/mcp/servers List registered servers (env values are never echoed).
GET /admin/mcp/servers/{name}/tools List the server's tools (lazy-connects; unreachable → 503 mcp_unavailable).
POST /admin/mcp/servers/{name}:warm / :close Start / stop the server process.

Connection-backed servers (everything the MCP page creates — hub installs, remote servers with auth, OpenAPI/GraphQL servers) get the same operational surface through their connection id:

Method Path Purpose
GET /connections/{id}/mcp/tools List the server's tools through the connection (lazy-connects; unreachable → 503 mcp_unavailable).
POST /connections/{id}/mcp:warm / :close Connect the server now / tear down the live connection (the connection row is kept).
POST /connections/{id}/mcp-oauth:start Begin interactive authorization for an auth.type: oauth connection. Returns {status, authorizationUrl?} — open the URL in a browser; the provider redirects back to /mcp/oauth/callback.
GET /connections/{id}/mcp-oauth Authorization status: {authorized, pending, lastError, connected}. Tokens are never returned.

MCP hubs (catalog)

Browse MCP registries and install published servers. Bodies and responses on this surface are camelCase. See MCP servers.

Method Path Purpose
GET /admin/mcp/registries The browsable registries: the two built-ins plus any from THEYGENT_MCP_REGISTRIES.
GET /admin/mcp/catalog One page of a registry's servers (registry, search, limit 1–100, cursor); entries carry installed-state stamps. A registry that can't be reached → 502 mcp_registry_error.
GET /admin/mcp/catalog/entry One server (registry, name, version default latest) with its install candidates and their declared inputs.
POST /admin/mcp/catalog/install Install a candidate as an mcp_server connection. Body {registry, name, version?, candidateId, connectionName, values, useOauth?}. Secret-flagged values go to the encrypted store server-side. Returns 201 with the connection.
POST /admin/mcp/generated:preview The tools an OpenAPI/GraphQL server would derive — nothing is created. Body {kind, spec?|specUrl?, url?, allowMutations?}.

Bench store

The Bench records results and presets here; testing itself runs against the inference plane. See The Bench.

Method Path Purpose
POST / GET /bench/runs Record / list bench results (filter by logical_id, agent_id, suite_id, case_id).
GET /bench/compare?a=&b= Per-metric deltas between two saved results.
POST / GET / DELETE /bench/presets Save / list / delete named param presets.
POST / GET /bench/suites Store / list golden-case suites (API-only; no UI yet).

AI gateway

What the AI Gateway page uses. Everyone manages their own gateway keys and reads their own records; admins manage everything else. Request bodies and answers are snake_case.

Method Path Purpose
GET /gateway/me Your billing profile (passthrough or org_key) and live gateway-key count.
POST / GET /gateway/keys Create a gateway key ({name}; the tyg_… token is in this response only) / list yours (?all=true for everyone's, admin).
DELETE /gateway/keys/{id} Revoke a key; gateways refuse it within one key sync.
GET /gateway/report Totals and groupings for a range: since, until, by (class, client, session, model, served-model, developer, decision, reason, shadow-model, day), scope (mine or org), plus setup warnings and subscription seats.
GET /gateway/sessions · /gateway/sessions/{id} Sessions most recently active first (since, before, limit) · one session's timeline of requests and decisions (after, limit).
GET / PUT /gateway/config The latest configuration (its version is the ETag; 404 gateway_config_not_found before any) / save the next version (admin; If-Match refuses a stale edit with 412 config_version_conflict; 422 invalid_gateway_config).
GET /gateway/config/versions · /gateway/config/versions/{n} The history · one version.
GET / PUT /gateway/fingerprints · /gateway/fingerprints/{fingerprint} The side-request catalog · review one kind (admin; saves a new configuration version).
GET /gateway/instances Each gateway's latest heartbeat (offline after 120 s).
GET / PUT /gateway/developers · /gateway/developers/{user_id} Every account's billing (admin) · set one: {"credential_profile": "org_key"} (or "passthrough").
POST / GET / DELETE /gateway/service-keys · /gateway/service-keys/{id} Gateways' service keys (admin; the tygs_… token is in the create response only).
GET /gateway/archive · /gateway/archive/{day} The archived days, with their request counts and sizes · one day's records as gzip-compressed JSON lines, exactly as the gateway sent them (admin; 404 archive_not_found, 400 invalid_day).
curl "http://localhost:8080/gateway/report?since=2026-10-01T00:00:00Z&by=day,model" \
  -H "Authorization: Bearer tyk_your-api-key"

Import & export

The control-plane half of the transfer bundle — the Settings Import / Export tab drives these plus the inference plane's pair below. See Import & export.

Method Path Purpose
POST /export Build a bundle. Body {"include": [...]} — any non-empty subset of agents, runs, traces, sessions, mcp, rag (traces implies runs); anything else → 400 invalid_include. Returns the bundle JSON. Never contains secret values, secret refs, webhook signing secrets, or MCP env/header values (key names only).
POST /import Apply a bundle (the same shape; sections optional). Id-preserving, idempotent, skip-on-exists; one bad entry never aborts the rest. Returns {report} with per-section counts and a flat warnings list. Not a bundle → 400 invalid_bundle; a future format_version → 400 unsupported_bundle_version.
curl -X POST http://localhost:8080/export \
  -H 'Content-Type: application/json' \
  -d '{"include": ["agents", "sessions"]}' -o control-plane.json

curl -X POST http://localhost:8080/import \
  -H 'Content-Type: application/json' \
  --data-binary @control-plane.json

Inference plane

The data plane is OpenAI-compatible. In every /v1/* call, the model field is a logical id you registered — never an engine name. See Models & engines.

Data plane (/v1/*)

Method Path Purpose
GET /v1/models List registered logical ids as OpenAI model objects.
POST /v1/chat/completions Chat (and vision — messages content may be text + image_url parts). Streaming and non-streaming.
POST /v1/embeddings Embeddings. input is a string or list of strings.
POST /v1/audio/transcriptions Speech-to-text. Multipart form with model + file.
POST /v1/audio/speech Text-to-speech. {model, input, voice?}; returns raw audio bytes.
POST /v1/images/generations Text-to-image. {model, prompt, size?, n?, steps?}; returns {data:[{b64_json}]}.
curl http://localhost:8081/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model": "triage-fast", "messages": [{"role":"user","content":"Hello"}]}'

Model management (/admin/*)

Method Path Purpose
PUT /admin/models/{id} Register/replace a model under a logical id.
GET /admin/models List registered models with live state.
GET / DELETE /admin/models/{id} Read one / unregister (evicts then deletes).
GET /admin/models/{id}/capabilities Probe tool-calling, vision, reasoning, max context, modalities.
POST /admin/models/{id}:warm / :evict Pre-load / free the engine.
GET /admin/models/{id}/logs?lines=200 The tail of a managed model's engine log: {logicalId, path, lines}. 404 engine_log_not_found for a reachable model or one whose engine hasn't run.
GET /admin/engines Currently resident engines and maxResident.
GET /admin/credentials List local credential names with hasValue (values are never read back).
PUT / DELETE /admin/credentials/{name} Set / remove a local credential (values are write-only).
GET /admin/catalog/models Browse installable models (search, sort, limit, engines, size, modality).
GET /admin/catalog/models/{repo} Model detail with per-quant variants and RAM fit badges.
POST /admin/catalog/install Start a download+install job. Body {repo, engine, variantId, logicalId}.
GET /admin/catalog/downloads / /{job_id} Track download progress.
POST /admin/catalog/downloads/{job_id}:cancel Cancel an in-flight install.
GET /admin/export The registry as a bundle: every registration verbatim (runtime state stripped), catalog-install provenance, and credential names only — never values or weights. See Import & export.
POST /admin/import Apply a registry bundle: already-registered ids are skipped; catalog-installed models re-download their weights in-plane (returned in downloads); a local-path model without install provenance registers with a weights_unavailable warning. Malformed body → 400 invalid_bundle.

Register a hosted, OpenAI-compatible model:

curl -X PUT http://localhost:8081/admin/models/claude-fast \
  -H 'Content-Type: application/json' \
  -d '{"binding":"openai-compatible","baseUrl":"https://api.example.com/v1","model":"provider-model-name","credentialRef":"secret://MY_API_KEY"}'

See Remote models for the reachable-binding fields.

Health

Method Path Purpose
GET /healthz Liveness.
GET /readyz Per-(engine, modality) readiness — each slot with a reason and install hint when not ready. Stays 200 while any engine is available.
curl http://localhost:8081/readyz

AI gateway

Every path except the gateway's own is forwarded to the Anthropic API exactly as sent — POST /v1/messages, count_tokens, GET /v1/models and whatever Claude Code adds — and answered exactly as Anthropic answers. In team mode each request needs a gateway key (X-TheYgent-Gateway-Key: tyg_…, or the key as Authorization: Bearer for developers billed to the organization). The gateway's own refusals carry an x-theygent-gateway-error header; see errors the gateway returns.

Its own surface is camelCase:

Method Path Purpose
GET /healthz Liveness.
GET /readyz 200 when ready; 503 while starting or until developers' keys are loaded (team mode). Names the gateway id, mode and configuration version.
GET /admin/status Identity, mode, configuration and key-set versions, the request log (recorded, exported, waiting), upstream health.
GET /admin/credentials Whether the organization's key and the service key are set, and where the org key came from — never a value. readOnly says why the page may not write it: env, personal_mode or deployment_only.
PUT / DELETE /admin/credentials/org-api-key Set ({"api_key": "…"}) / remove the organization's key on this gateway. 409 org_key_pinned, org_key_not_applicable or org_key_deployment_only when it cannot; 422 invalid_api_key.

The /admin/* paths take your TheYgent bearer (a session or a personal API key) and admit admins only — the gateway asks the control plane, and answers 503 if it cannot. A personal gateway with no control plane leaves them open, on loopback.