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 two services, each with its own base URL:

  • Control planehttp://localhost:8080 — agents, runs, sessions, triggers, connections, artifacts, observability.
  • Inference planehttp://localhost:8081 — the OpenAI-compatible data plane (/v1/*) and model management (/admin/*).

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?}. 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 MCP connections 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.

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.
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 with per-document status, chunk counts, and errors.
POST /rag/sources/{id}/documents?filename=… Upload one document as a raw body (the Content-Type header is the file's mime; 50 MiB cap). 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).

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_version400 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/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