Skip to content

REST API

The UI does everything through a JSON API under /api, and a script with an API token calls the same routes. Each instance serves its OpenAPI spec at /api/openapi.json (or /api/openapi.yaml) and interactive docs with every route and schema at /api/docs. Neither needs a sign-in, and umpteenth openapi prints the same spec without a running server. The API endpoints reference renders that spec here on one page, with the parameters, request body and response fields of every route.

In the examples, https://umpteenth.example.com stands for your instance and $UMPTEENTH_API_TOKEN holds an API token.

Create a token under Settings → API tokens with Create token. The dialog asks for a Name and an optional Expires date, and a token without a date never expires. Umpteenth shows the new token, which starts with ump_, once in the API token created dialog, so copy it before you click Done.

Send it as a bearer token:

Terminal window
curl -H "Authorization: Bearer $UMPTEENTH_API_TOKEN" https://umpteenth.example.com/api/jobs

A token belongs to the workspace you created it in and acts with your role there, so a member’s token can run jobs and can’t change providers. It follows later role changes, and it stops working once you leave the workspace or an admin removes or deactivates you. An instance admin’s token gets only the role of their membership, without the admin’s rights in other workspaces.

A few routes need a signed-in session and answer a token with 403 and “This can only be done while signed in, not with an API token”:

  • creating or deleting API tokens, so a leaked token can’t mint its own successor
  • changing members, roles and invites
  • listing, creating, switching, renaming, leaving, handing over or deleting workspaces, and accepting invites
  • the instance admin routes under /api/admin
  • starting an OAuth login to an MCP server

The token list shows a Last used time for each token, accurate to the minute, and an Expired badge on a token past its date. Members see their own tokens there, and admins see every token of the workspace with its Created by column. Delete a token with its trash icon, and every call that uses it fails from then on. A wrong, deleted or expired token gets 401 with the code invalid_token, and so does a token whose creator lost access.

The API accepts secret values and provider API keys but never returns them: secrets come back without their values, and providers report hasApiKey.

Error answers share one JSON shape:

{
"code": "validation_failed",
"message": "sort has unknown sort key \"price\"",
"fields": [{ "field": "sort", "code": "invalid", "message": "has unknown sort key \"price\"" }],
"requestId": "9c1f0e4b7a2d3c58"
}
Code Status Typical cause
validation_failed 400 A field or query parameter failed its checks, listed in fields
invalid_request_body 400 The body isn’t valid JSON for the route
invalid_token 401 The API token or webhook token is wrong, deleted or expired
not_signed_in 401 The request carried neither a token nor a session
forbidden 403 The caller’s role doesn’t allow the route, or a token called a route that needs a signed-in session
not_found 404 The job, run or other resource doesn’t exist
conflict 409 The action doesn’t fit the current state, such as cancelling a finished run
rate_limited 429 Too many webhook or login calls; retry after the seconds in the Retry-After header

Every answer carries an X-Request-ID header, and error bodies repeat it as requestId. Send your own X-Request-ID of up to 64 characters to find a call in the server log. The server logs each API call that answers 400 or above with its request ID, and the other calls only at the debug log level.

POST /api/jobs/<job ID>/runs starts a run, like Run now in the UI. The job ID is the part after /jobs/ in the job page’s URL.

Terminal window
curl -X POST "https://umpteenth.example.com/api/jobs/$JOB_ID/runs" \
-H "Authorization: Bearer $UMPTEENTH_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"input": {"repo": "acme/api"}, "instructions": "Skip draft pull requests."}'

Both fields are optional:

Field Effect
input Any JSON value, which the run finds in /ump/input.json. Without it the file holds {}.
instructions Up to 10,000 characters that the agent receives as “Additional instructions for this run”. A scripted run’s main script never sees them, so they reach the agent only if the run falls back.

The answer names the new run and its status:

{ "runId": "0199846e-3b7c-7d2a-9f4e-5c1b2a3d4e6f", "status": "queued" }

queued covers a run that starts at once and a run that waits behind another one under the Queue overlap policy. skipped means another run of the job was active and the job’s overlap policy is Skip. Umpteenth records the skipped run with the error “Skipped because another run of this job was still active”. Triggers and schedules explains the policies.

These runs show the trigger API in the UI. A paused job accepts them, because the Enabled switch only stops schedule and webhook runs. To pause or resume a job from a script, send PATCH /api/jobs/<job ID> with {"enabled": false} or {"enabled": true}. An unknown or deleted job answers 404.

GET /api/runs/<run ID> returns a run with its status, cost and results:

Terminal window
curl -H "Authorization: Bearer $UMPTEENTH_API_TOKEN" "https://umpteenth.example.com/api/runs/$RUN_ID"

The fields you’ll reach for first:

Field Contents
status The run’s status, listed below
mode explore, assisted or scripted
trigger manual, schedule, webhook, api or retry
summary The run’s Markdown summary
outputs The run’s outputs as a JSON object
error The reason the run didn’t succeed
cost The run’s cost in micro-USD, so 420000 means $0.42
turns, tokIn, tokOut The agent’s turns, and the input and output tokens of the run’s model calls, ump llm included
queuedAt, startedAt, finishedAt Unix timestamps in milliseconds
msTotal Milliseconds from leaving the queue to the end of the run
fellBack true when a scripted run handed over to the agent
reflection skipped, pending, done or failed
reflectionCost, verifyCost The cost of learning from the run and of verifying a scripted run, in micro-USD

A run is live while its status is queued, provisioning, running or verifying. It is final once it reads succeeded, failed, timed_out, cancelled or skipped. Reading a run explains each status.

To wait for the result in a script, poll until the status is final:

Terminal window
while :; do
status=$(curl -fsS -H "Authorization: Bearer $UMPTEENTH_API_TOKEN" \
"https://umpteenth.example.com/api/runs/$RUN_ID" | jq -r .status)
case "$status" in
queued|provisioning|running|verifying) sleep 10 ;;
*) echo "$status"; break ;;
esac
done

GET /api/runs/<run ID>/events returns the run’s timeline as a JSON array, oldest first. Each event has a seq, a ts in Unix milliseconds, a type, spanId, parentSpanId, ms and a payload whose shape depends on the type. The route returns up to limit events (500 by default, 2,000 at most) after the sequence number in after, so pass the last seq you got to read the next batch.

GET /api/runs/<run ID>/stream sends the same timeline as server-sent events: it replays the stored events, then follows the run live.

Terminal window
curl -N -H "Authorization: Bearer $UMPTEENTH_API_TOKEN" "https://umpteenth.example.com/api/runs/$RUN_ID/stream"
Event Data
event A timeline entry as /events returns it, with its seq as the SSE ID
delta A live fragment of the model’s output or a command’s output, which Umpteenth doesn’t store
status The run’s new status
end The final status, after which Umpteenth closes the stream

To resume after a dropped connection, send the last SSE ID as Last-Event-ID or as ?after=<seq>. Umpteenth sends a keepalive comment every 20 seconds.

GET /api/events streams a run message for each status change of any run or its reflection on the instance, with kind (run or reflection), runId, jobId and status. It starts at the moment you connect and replays nothing.

Behind a reverse proxy, turn response buffering off for both streams, as Reverse proxy shows.

GET /api/runs/<run ID>/artifacts lists the files the run left in /ump/outputs:

[{ "name": "digest.md", "size": 2048 }, { "name": "reports/week.csv", "size": 31544 }]

A file from a subdirectory keeps its relative path as its name. GET /api/runs/<run ID>/artifact?path=<name> downloads one:

Terminal window
curl -H "Authorization: Bearer $UMPTEENTH_API_TOKEN" -o digest.md \
"https://umpteenth.example.com/api/runs/$RUN_ID/artifact?path=digest.md"

The run page’s Cancel, Retry and Learn from this run buttons call these routes:

Route Effect
POST /api/runs/<run ID>/cancel Stops a live run, which ends as cancelled. A queued run ends at once with “Cancelled before it started”. A finished run answers 409 with “The run is not running”.
POST /api/runs/<run ID>/retry Starts a new run of the same job with the same input and instructions and the trigger retry. The answer has the shape of Start a run.
POST /api/runs/<run ID>/learn Starts reflection on a run that succeeded, failed or timed out and answers 202 with {"reflection": "pending"}. Any other run, and a run with reflection in progress, answers 409.

Reading a run describes the same actions in the UI.

A job’s webhook, POST /hooks/<job ID>, lives outside /api and takes the job’s own webhook token instead of an API token. POST /api/jobs/<job ID>/webhook-token creates that token or replaces the current one, the same as Generate token and Rotate token in the job’s Webhook card. The answer holds the token, which starts with umh_, and the webhook’s path:

{ "token": "umh_…", "url": "/hooks/01998412-9a5d-7e01-b3c6-2f8e7d6a5b40" }

Umpteenth returns the token in this answer alone, and the previous token stops working at once. Triggers and schedules covers the request body, the answers and the rate limit.

List routes share four query parameters and answer with the same envelope:

Parameter Default Notes
page 1 Counts from 1
pageSize 25 1 to 100; a larger value answers 400
sort Per route Up to three comma-separated keys, each with a - in front for descending, such as -cost,number
search None Free text, up to 200 characters
{ "items": [], "page": 1, "pageSize": 25, "total": 0 }

Filters take comma-separated values. The two lists you’ll script most:

GET /api/runs GET /api/jobs
Filters status, job (job IDs), mode, trigger, from and to (queued time in Unix milliseconds, to exclusive) enabled (true or false)
Sort keys queuedAt (default -queuedAt), startedAt, finishedAt, duration, cost, turns, status, job, number, tokens name (default), createdAt, updatedAt, nextRunAt, enabled, runCount
search matches Summary, error and job name Name and instruction

The ten most expensive failed or timed-out runs:

Terminal window
curl -H "Authorization: Bearer $UMPTEENTH_API_TOKEN" \
"https://umpteenth.example.com/api/runs?status=failed,timed_out&sort=-cost&pageSize=10"

The API endpoints reference and /api/docs list each of these with its fields.

Area Routes
Jobs GET and POST /api/jobs, GET, PATCH and DELETE /api/jobs/{id} (DELETE archives the job, keeps its runs and cancels its queued ones), POST /api/jobs/compile
Job state GET /api/jobs/{id}/state, GET, PUT and DELETE /api/jobs/{id}/state/{key}
Playbook GET and PUT /api/jobs/{id}/playbook, GET /api/jobs/{id}/playbook/versions and /versions/{version}, POST /api/jobs/{id}/playbook/rollback
Job images GET /api/jobs/{id}/images, POST /api/jobs/{id}/images/rebuild
Job secrets and MCP servers GET and PUT /api/jobs/{id}/secrets and /api/jobs/{id}/mcp-servers
Secrets /api/secrets and /api/secrets/{id}
Providers and models /api/providers, /api/models and /api/catalog/models
MCP servers /api/mcp-servers and /api/mcp-servers/{id}
Settings GET and PATCH /api/settings, POST /api/settings/notifications/test
Stats GET /api/stats/overview, GET /api/jobs/{id}/stats
System GET /api/system/info with the version, the database and the sandbox backend