Skip to content
Tracon

HTTP API

168 operations across 130 paths. This page is the shape they all share; the groups in the sidebar are the operations themselves, each with what it does and what it returns.

The OpenAPI document is published as /openapi/tracon.json — load it into Scalar, Swagger UI, Postman, or a client generator.

Generated operation pages use {prefix}. Replace it with the value passed to MapTracon; the project template uses /tracon.

app.MapTracon("/tracon");

The management API (/api/*) drives the control plane: agents, runs, sessions, skills, workflows, evals, experiments, jobs, and governance.

OpenAI-compatible endpoints (/v1/*) let an OpenAI client talk to your agents with the familiar request and streaming formats. Configure its base URL and authentication, and set model to the agent name — which provider model the agent calls is server-side policy. See the OpenAI API guide for copyable clients and the compatibility boundary.

A bearer token in the Authorization header, holding either the configured static token or an API key. See securing the endpoints for the layers and how scopes narrow roles.

{prefix}/api/meta answers without authentication — the console needs to learn which method to present. It returns nothing sensitive.

Run endpoints answer with text/event-stream, one frame per run event, and the first frame reports the run id.

Terminal window
curl -N -X POST http://localhost:5081/tracon/api/agents/support/run \
-H 'Content-Type: application/json' \
-d '{"message":"Where is order 4182?"}'

Two headers change the shape:

Header Effect
Idempotency-Key One JSON response instead of a stream — a replay cannot be rebuilt from a stream
Prefer: respond-async 202 Accepted plus a Location header; poll the job

A run that stops on a tool call needing a human decision sends an approvals frame before the stream ends — the pending request’s id and tool name, plus whatever an approval presenter resolved for it. See Approvals for the queued-run mailbox shape, which answers the same request differently.

With Quotas:PublishThresholdToRunStream turned on (off by default), a run whose completion crosses a quota threshold also gets a custom frame before done — the same notice, byte for byte, that GET /api/runs/{runId}/events carries for that run.

GET /api/runs/{runId}/events is a separate SSE contract with its own, larger set of frame names (run.started, tool.invoking, …) — see the two-contract table in Runs and recording.

List endpoints that page use skip and take. skip defaults to 0, take to 50, and take is clamped to 1..200 rather than rejected — an out-of-range value never fails a request.

Ordering is usually newest first, which means an item can move between pages while a client is paging. Use the id as identity, never the position.

Some lists are deliberately not paged — agents, skills, quotas, retention policies — because their size is bounded by configuration rather than by traffic.

Failures are application/problem+json with a title and a detail. Two conventions run through the whole API:

A resource you may not see is reported as 404, not 403. Another tenant’s run, session, or conversation is reported as missing, so the API does not confirm that it exists.

A field the schema declares as non-nullable rejects an explicit null. Omitting a list is fine — {"message": "hello"} is a complete run request, and every list the schema does not require simply defaults to empty. Sending {"message": "hello", "approvals": null} is not the same thing: it states a value for a field whose contract has no null, and the API answers 400 rather than acting on it. A field that genuinely distinguishes “not provided” from “provided empty” is declared nullable in the schema and still accepts null.

Error text is English and is not translated. The same failure has to read the same way in a log, a test, and a support ticket. The console translates its own labels and shows server text as it is.

Status Means
400 The request is malformed or fails validation
401 Missing or invalid credentials
403 Authenticated, but not allowed — including the loopback restriction
404 Not found, or not yours
409 Conflicting state — a name in use, a running experiment, a code-defined agent, a client-side tool that cannot be replayed
422 A content guard blocked the content
429 A quota or rate limit was exceeded
501 The capability is not registered — the workflow engine, voice, or knowledge
502 The model provider failed or never answered — including a request that timed out
503 The process is draining in-flight runs before it stops; retry shortly

501 is worth its own note: it means “this build does not have that package wired up”, which is a different problem from a wrong address, and the API says so rather than answering 404.

A provider that times out is a 502, not an empty 200. .NET reports an HttpClient request timeout as a TaskCanceledException, and a run that ends because the caller cancelled has no error to report — so an implementation that reads the exception type alone answers a timed-out provider with a clean, empty success. Tracon decides from the request instead: unless the caller’s connection actually went away, a run that ends this way is a failure, the status is 502, and the run is recorded as Failed with the Timeout error class.

A 502’s detail is deliberately shallow — it never carries the failing provider’s own error text, only its exception type and a correlation id you can match against your server’s log. The same rule applies to a streaming run’s error frame and to MCP tool-call errors. See errors are classified for why.

Pick one from the sidebar. Each operation shows every declared media type, parameters, responses, and response headers. HTTP schemas expands all 254 request and response contracts with required fields, defaults, and validation constraints from the OpenAPI snapshot.

The Tracon packages do not generate the OpenAPI document themselves — they carry route metadata, and your own AddOpenApi() call produces the document. Taking an OpenAPI dependency would force it, and its transitive CVE exposure, onto every consumer.

The consequence: the title, version, and server list in the published snapshot come from the host that generated it. In your document they come from your application. The paths, schemas, and descriptions are the same.

Because Tracon maps plain minimal API endpoints on your own IEndpointRouteBuilder, they are also picked up by your OpenAPI/Swagger generator, right alongside your own endpoints — no separate setup on Tracon’s side turns this on or off.

flowchart LR
    accTitle: How Tracon endpoints reach your OpenAPI document
    accDescr: Tracon endpoints and your own endpoints both sit in your route table and both flow into your OpenAPI generator; a ShouldInclude or DocInclusionPredicate filter on the Tracon tag decides what reaches the document it produces.
    subgraph routes["Your route table"]
        tracon["Tracon endpoints<br/>(tag: Tracon)"]
        yours["Your own endpoints"]
    end
    generator["Your AddOpenApi() /<br/>AddSwaggerGen() call"]
    doc["Document your generator<br/>produces"]

    tracon --> generator
    yours --> generator
    generator -->|"ShouldInclude /<br/>DocInclusionPredicate"| doc

Every Tracon endpoint carries the Tracon tag. If your document should not describe Tracon’s operations — for example, one you publish to external partners — filter on that tag in your own OpenAPI setup; Tracon has no built-in switch for this.

With Microsoft.AspNetCore.OpenApi:

using Microsoft.AspNetCore.Http.Metadata;
builder.Services.AddOpenApi(options =>
{
options.ShouldInclude = description =>
!description.ActionDescriptor.EndpointMetadata
.OfType<ITagsMetadata>()
.Any(tags => tags.Tags.Contains("Tracon"));
});

With Swashbuckle:

builder.Services.AddSwaggerGen(options =>
{
options.DocInclusionPredicate((_, apiDescription) =>
!apiDescription.ActionDescriptor.EndpointMetadata
.OfType<ITagsMetadata>()
.Any(tags => tags.Tags.Contains("Tracon")));
});

The filter only changes what your document describes. Tracon’s endpoints stay reachable; they just stop appearing in your Swagger UI or generated document.