---
title: "Use the runtime API"
description: "Start and inspect jobs through the edge API."
canonical_url: "https://docs.oz0.ai/build/apps/runtime-api"
---
# Use the runtime API

> Start and inspect jobs through the edge API.

For: application developers.

Call `oz0.runtime.v1.RuntimeService` on the edge's HTTPS port, `7443`, to run agents and inspect their jobs. The API supports Connect with JSON or protobuf, gRPC and gRPC-Web. The CLI uses this same API, and an application can use an HTTP client, gRPC library or client SDK.

<table>
<thead>
  <tr>
    <th>
      Method
    </th>
    
    <th>
      Does
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        StartJob
      </code>
    </td>
    
    <td>
      Starts an agent. The job's ID is the start's key: the same start again answers with that job, running or ended, and any other start under the ID is refused. See <a href="https://docs.oz0.ai/build/apps/runtime-api#job-ids-and-retries">
        job IDs and retries
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GetJob
      </code>
    </td>
    
    <td>
      Status, answer or error, child job IDs, and cost per branch
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        CancelJob
      </code>
    </td>
    
    <td>
      Cancels a job and its children
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ListJobs
      </code>
    </td>
    
    <td>
      Recent top-level jobs, newest first
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        WatchJob
      </code>
    </td>
    
    <td>
      A stream of what the job and its child jobs do as it happens: text, tool calls, tool results, children and approvals, ending with the job's final state. Each event has a <code>
        seq
      </code>
      
       and a <code>
        resume_token
      </code>
      
      ; a watcher that reconnects with <code>
        since_seq
      </code>
      
       and the token gets only what came after, on any edge. See <a href="https://docs.oz0.ai/build/apps/live-feeds#resume">
        Live feeds
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        OpenFeed
      </code>
      
      , <code>
        UpdateFeed
      </code>
    </td>
    
    <td>
      One stream for many conversations and jobs, and the person's own chats, that a browser tab or an app keeps open; <code>
        UpdateFeed
      </code>
      
       adds and removes what it follows, on any edge. See <a href="https://docs.oz0.ai/build/apps/live-feeds">
        Live feeds
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ListApprovals
      </code>
    </td>
    
    <td>
      Lists approvals oldest first, for a job, job tree or accessible tenants. See <a href="#listing-waiting-approvals">
        filters and partial results
      </a>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DecideApproval
      </code>
    </td>
    
    <td>
      Approves or denies one, with a comment; admins and operators may. See <a href="https://docs.oz0.ai/agents/approvals">
        Approvals
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GetJobTrace
      </code>
    </td>
    
    <td>
      The job's steps from its history, with what each cost, and findings about what looks wrong. See <a href="https://docs.oz0.ai/agents/history">
        Job history
      </a>
      
      . For a job whose ID (or its tree's root's) starts with <code>
        cov.
      </code>
      
      , also the decision points it reached (<a href="https://docs.oz0.ai/improve/evals/coverage#policy-path-coverage">
        policy-path coverage
      </a>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GetJobStep
      </code>
    </td>
    
    <td>
      What one step read and wrote; step <code>
        job
      </code>
      
       is what the job itself was started with and answered. It can hold customer data: admins and operators may, and each call is in the audit log
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GetCatalog
      </code>
    </td>
    
    <td>
      The tenant's agents and flows, with their input and output schemas
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ListStartable
      </code>
    </td>
    
    <td>
      Lists what <code>
        StartJob
      </code>
      
       would allow for this caller, with conversation filters. See <a href="#listing-startable-agents-and-flows">
        startable catalog
      </a>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GetJobBoard
      </code>
    </td>
    
    <td>
      The notes and findings the agents of a job's tree pinned to its shared board; their text and tags need the content permission, and a view of them is audited. See <a href="https://docs.oz0.ai/agents/colleagues">
        Colleagues and the board
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GetJobWait
      </code>
    </td>
    
    <td>
      Why a job waits, in plain words: its pending steps, their queues and attempts, the last failure, and what each waits for. See <a href="https://docs.oz0.ai/fleet/queues#why-is-a-job-waiting">
        why is a job waiting?
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ListQueues
      </code>
    </td>
    
    <td>
      The tenant's task queues with their backlog, their oldest task and the nodes that poll them, and why the others do not. See <a href="https://docs.oz0.ai/fleet/queues#queues">
        queues
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        SetJobTags
      </code>
    </td>
    
    <td>
      Adds tags to a job and removes others, even after it ended; operators and above may
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ExportJob
      </code>
    </td>
    
    <td>
      A job as one JSON document: the job, its trace, findings and tags, and with the content permission each step's input and output, which is audited
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        CompareJobs
      </code>
    </td>
    
    <td>
      Two jobs side by side, their steps aligned. See <a href="https://docs.oz0.ai/improve/search-and-compare#compare-two-jobs">
        compare two jobs
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        RetryJob
      </code>
    </td>
    
    <td>
      Starts a job again with the same agent, input, tags and budget, under a new ID; operators and above may. With an <code>
        Idempotency-Key
      </code>
      
       header, a retry of the call answers with the job the first one started (<code>
        retried
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        FireRoutine
      </code>
      
      , <code>
        ListRoutineFires
      </code>
      
      , <code>
        BackfillRoutine
      </code>
    </td>
    
    <td>
      Run a routine now (or as a dry run), list its fires, and run a schedule's fires for a past period. See <a href="https://docs.oz0.ai/build/parts/routines#from-your-own-app">
        routines
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ListEvents
      </code>
    </td>
    
    <td>
      The tenant's event sources, its latest events and the jobs that wait for one; an event's text needs the content permission. See <a href="https://docs.oz0.ai/agents/waiting-for-events">
        waiting for events
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GetConversation
      </code>
      
      , <code>
        ListConversations
      </code>
    </td>
    
    <td>
      A conversation's latest turns (50, or <code>
        limit
      </code>
      
       up to 200), by its ID or one of its jobs, with what was said and answered for those with the content permission (audited); and the tenant's latest conversations, facts only. <code>
        StartJob
      </code>
      
       continues one with <code>
        continue_from
      </code>
      
       or <code>
        conversation
      </code>
      
      . Someone else's private chat needs <code>
        reason
      </code>
      
      . See <a href="https://docs.oz0.ai/agents/conversations">
        conversations
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ListMyConversations
      </code>
      
      , <code>
        UpdateMyConversation
      </code>
      
      , <code>
        ShareConversation
      </code>
      
      , <code>
        UnshareConversation
      </code>
      
      , <code>
        ListConversationShares
      </code>
    </td>
    
    <td>
      A person's chats, for accounts only: their own and those shared with them, pinned first, with a cursor; pin, archive, rename, mark read, remove from the list (which erases nothing); the owner's shares. See <a href="https://docs.oz0.ai/agents/conversations/access-and-history#chats-yours-private-by-default">
        chats
      </a>
    </td>
  </tr>
</tbody>
</table>

### Listing waiting approvals

The approvals list shows what waits for a person's decision across the tenant's running jobs, oldest first. `job` narrows it to one job; adding `tree` includes jobs it delegated to.

Across tenants, `every_tenant` or `tenants` (at most 20 named tenants) lists approvals in each tenant the caller may read. Each approval names its `tenant`. The answer reports `tenants_read`, `tenants_refused` where the caller lacks the required role, and `tenants_unread` for tenants beyond the first 20 by name or served by another region. One unread or refused tenant does not fail the whole request.

### Listing startable agents and flows

The startable catalog lists agents and flows that `StartJob` would accept from the caller now. It applies start permissions or agent grants, key scopes, retirement and kill switches.

Entries say whether they take part in conversations and whose conversations they may continue. `conversations_only` narrows new-chat choices; `continues` narrows hand-over targets. A reader gets an empty list with the reason. The console's chat picker uses this catalog. See [the chat drawer](https://docs.oz0.ai/getting-started/web-ui/chats#the-chat-drawer).

## MCP, A2A and AG-UI

The same agents, for the same callers, are also an [MCP server](https://docs.oz0.ai/build/apps/mcp-server) at `POST /mcp/<tenant>`, with tasks for long jobs, [A2A agents](https://docs.oz0.ai/build/apps/a2a) with signed Agent Cards at `/a2a/<tenant>/<agent>`, and an [AG-UI route](https://docs.oz0.ai/build/apps/ag-ui) at `POST /v1/agui/agents/<agent>` for apps built on an AG-UI client, where approvals and questions are the protocol's interrupts. All of them start jobs through `StartJob`, so its rules hold there too.

## Typed HTTP routes and the OpenAPI document

Next to the Connect API, the edge has plain HTTP routes, for the same callers:

<table>
<thead>
  <tr>
    <th>
      Route
    </th>
    
    <th>
      Does
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        POST /v1/agents/&lt;name>/jobs
      </code>
    </td>
    
    <td>
      Starts an agent or a flow with its input as the body: JSON, or <code>
        text/plain
      </code>
      
       for a text input. It answers <code>
        201
      </code>
      
       with the job.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET /v1/jobs/&lt;id>
      </code>
    </td>
    
    <td>
      The job: its status, and its answer once it has one
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET /v1/openapi.json
      </code>
    </td>
    
    <td>
      An OpenAPI 3.1 document of the tenant's agents and flows; <code>
        ?plugin=&lt;name>
      </code>
      
       for one plugin's
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET /v1/jobs/&lt;id>/events
      </code>
    </td>
    
    <td>
      The job's events as Server-Sent Events, what <code>
        WatchJob
      </code>
      
       streams, resumed with <code>
        Last-Event-ID
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST /v1/feeds
      </code>
      
      , <code>
        GET /v1/feeds/&lt;id>/events
      </code>
    </td>
    
    <td>
      A feed made for Server-Sent Events, and its events
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST /v1/stream-tickets
      </code>
    </td>
    
    <td>
      A one-time ticket that opens one of those streams in a browser's <code>
        EventSource
      </code>
      
      , which cannot send a key
    </td>
  </tr>
</tbody>
</table>

The streams, their tickets and the tenant's allowed origins for pages elsewhere are on [Live feeds](https://docs.oz0.ai/build/apps/live-feeds/browser-and-access#server-sent-events). An API key in a URL is refused on them.

```bash [Terminal]
curl --cacert ca.crt -H "Authorization: Bearer $OZ0_API_KEY" -H 'Content-Type: application/json' \
  -d '{"repo": "acme/api", "pr": 42}' 'https://edge.example.com:7443/v1/agents/acme-reviewer/jobs?tenant=acme'
curl --cacert ca.crt -H "Authorization: Bearer $OZ0_API_KEY" 'https://edge.example.com:7443/v1/openapi.json?tenant=acme' > oz0.json
```

The document has one operation per agent, `run_<name>`. Its request body is the agent's input schema, and its response is the job, with `output` typed by the agent's output schema. Generate a client from it with any OpenAPI generator. It changes when a plugin changes an agent or a schema, and its `ETag` says when. A plugin installed before Orchestrator Zero put schemas in plugins' bundles needs `plugin update` before its agents' schemas appear.

With thousands of agents the whole document is megabytes (17.6 MB for 10,000): `?plugin=<name>`, and `oz0 client generate --plugin <name>`, describe one plugin's agents and flows, under an `ETag` that names the plugin.

## Start a job with curl

```bash [Terminal]
curl --cacert ca.crt -H "Authorization: Bearer $OZ0_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"agent": "acme-reviewer", "input": {"repo": "acme/api", "pr": 42}, "timeout": "900s", "tenant": "acme"}' \
  https://edge.example.com:7443/oz0.runtime.v1.RuntimeService/StartJob
```

```json
{ "job": { "id": "job-bb270cf1-...", "agent": "acme-reviewer", "status": "JOB_STATUS_RUNNING", "startedAt": "..." } }
```

Then poll it:

```bash [Terminal]
curl --cacert ca.crt -H "Authorization: Bearer $OZ0_API_KEY" \
  -H 'Content-Type: application/json' -d '{"id": "job-bb270cf1-...", "tenant": "acme"}' \
  https://edge.example.com:7443/oz0.runtime.v1.RuntimeService/GetJob
```

## Requests and responses

`StartJob` takes:

<field-group>
<field name="agent" type="string" :required="true">

The agent's or the flow's name, as the catalog lists it.

</field>

<field name="input" type="value">

Text, or JSON that matches the agent's input schema. The edge checks it against the schema before the job starts: an input that does not fit is refused with `invalid_argument` (HTTP 400) and what is wrong, and no job is made.

</field>

<field name="tenant" type="string">

The tenant to run in; empty means `default`.

</field>

<field name="id" type="string">

An ID for the job, which is the start's idempotency key; empty means a new one (derived from the `Idempotency-Key` header when the request has one). May not contain `/`, `\`, `"` or control characters. The same start again under it answers with that job and `retried: true`; another start under it is refused with `already_exists`. See [job IDs and retries](https://docs.oz0.ai/build/apps/runtime-api#job-ids-and-retries).

</field>

<field name="timeout" type="duration">

How long the job may run in all, on top of the agent's own limits.

</field>

<field name="recordedCalls" type="RecordedCall[]">

Model answers to give the job instead of calling a provider: the lines of an eval's cassette (`provider`, `path`, `requestSha256`, `status`, `contentType`, `body`). The edge answers the job's model calls, and its child jobs', from them in order, at no cost. It needs the tenant's admin role, and the job is marked as a replay. Leave `id` empty: such a job always gets a new ID of its own, runs for at most seven days, and while its recording is kept, a `StartJob` with that `id` is refused (`FailedPrecondition`). The [test bench](https://docs.oz0.ai/build/plugins/studio/test-and-explain#the-test-bench) uses it.

</field>
</field-group>

A `Job` has `id`, `agent`, `status`, `startedAt`, `closedAt`, `output` (the answer, text or JSON), `error`, `children` (direct child job IDs), `approvals` (what it waits for, while it runs), `flow` (true for a flow's job), `recording` (for a job started with `recordedCalls`: how many it had, how many were used, and what differed) and `cost`. `cost` holds the totals (`usd`, `calls`, `inputTokens`, `outputTokens`) and `branches`: one entry per job in the tree, the job itself first, with its agent, models, nodes, tokens, cached tokens and cost, and `translated` when its calls went through a [translating provider](https://docs.oz0.ai/spend/translating-providers).

`StartJob` answers with `job` and `retried`, which is true when the start was a retry and nothing started again.

## Job IDs and retries

A start may reach the edge and be applied even when its answer is lost on the way back, so a client that retries must
not start a second job. The job's ID is the start's idempotency key:

- The same start again under an ID, the same agent, input, tags, team, budget, timeout, data subjects,
conversation and other options, from the same caller, answers with the job of that ID as it is, running or ended,
with `retried: true`. Nothing runs again, nothing is counted against a quota again, and nothing is audited as a start.
- Another start under the ID (another input, agent, option or caller) is refused with `already_exists` (HTTP 409)
and a detail, `oz0.runtime.v1.IdConflict`, whose `reason` is `oz0_id_conflict`, with the job's ID and, when you may
read that job, its start time. The refusal comes before anything of the start happens: no quota, budget, turn or
file hand-over. A job that ended never runs again under its ID: start a new job under a new ID.
- Without an ID, send an `Idempotency-Key` header (1 to 128 visible ASCII characters, such as a UUID) instead: the
edge derives the job's ID from you and the key, so the same start with the same key is the same job, and the same key
with another request is refused as above. `oz0 run` and the SDKs send one on every start without an ID and retry
`unavailable` with it. A start with `recordedCalls` takes none: it always gets a new ID.
- An ID the edge gave (a start without an ID or a key) is the job's alone: no start with that `id` is a retry of it.
- How long: an ID stays taken for as long as its job's history is kept, the tenant's retention. After that it is
free again.
- Another caller's job: the caller is part of the request, so a start under someone else's job ID is refused, never
joined.

`RetryJob` and `FireRoutine` take the `Idempotency-Key` header the same way: a retry of the call answers with the job
or fire the first call started (`retried: true`), and another request under the key is refused.

The REST start
(`POST /v1/agents/{agent}/jobs`) takes the header too, answers a retry with `200` instead of `201`, and its JSON error
carries `reason`, `job` and `started_at` for a refused ID.

An [AG-UI](https://docs.oz0.ai/build/apps/ag-ui) resume sent again with the same
run ID (or `Idempotency-Key`) answers the decisions its first try made as made, and decides what is left. The admin
API's calls take the same header: see [the admin API](https://docs.oz0.ai/reference/admin-api).

The protobuf definition is in `proto/oz0/runtime/v1/` in the repository: the service in `runtime.proto`, its messages in the `runtime_*.proto` files beside it.

## Client SDKs

Typed clients for three languages are built from that file in the same change as the API, and carry the server's version: the [Python SDK](https://docs.oz0.ai/build/apps/sdk-python) (`oz0-client`), the [TypeScript SDK](https://docs.oz0.ai/build/apps/sdk-typescript) (`@oz0/client`, Node 24) and the [Go SDK](https://docs.oz0.ai/build/apps/sdk-go). Each starts a job and waits for it, watches it, answers its questions and decides its approvals, and reads the catalog and what jobs cost; every other method is there too, typed. For one tenant's agents with their own input and output types, generate wrappers from its OpenAPI document:

```bash [Terminal]
oz0 client generate --tenant acme --lang python --out src/oz0agents
```

`--lang` is `python`, `ts` or `go`. The command reads the document with the CLI context's operator certificate, or with an API key from `OZ0_API_KEY` when `--url` or `OZ0_URL` names the edge (`--ca-file` for its CA), or from a saved file with `--from`. Run again, it leaves a file generated from the same catalog alone, so an app's CI can run it on every build; a file from another version of the generator, or for another tenant or Go package, is written again.

From v1.0 the API only grows: `make proto-breaking` checks every change against the last release with `buf breaking`, so an SDK or a client you generated yourself keeps working with newer servers. For another language, generate a client from the protobuf file with [buf](https://buf.build) or `protoc`.

## Related tasks

- [Runtime API authentication](https://docs.oz0.ai/build/apps/runtime-api/authentication): Authenticate clients and apps acting for individual people.

<legacy-sections>

- <span id="admin-keys">

Section

</span>

: [admin keys](https://docs.oz0.ai/build/apps/runtime-api/authentication#admin-keys)
- <span id="apps-that-act-as-their-people">

Section

</span>

: [apps that act as their people](https://docs.oz0.ai/build/apps/runtime-api/authentication#apps-that-act-as-their-people)
- <span id="authentication">

Section

</span>

: [authentication](https://docs.oz0.ai/build/apps/runtime-api/authentication#authentication)
- <span id="keys-scoped-to-agents-and-flows">

Section

</span>

: [keys scoped to agents and flows](https://docs.oz0.ai/build/apps/runtime-api/authentication#keys-scoped-to-agents-and-flows)
- <span id="what-a-key-creates-is-labelled-with-it">

Section

</span>

: [what a key creates is labelled with it](https://docs.oz0.ai/build/apps/runtime-api/authentication#what-a-key-creates-is-labelled-with-it)

</legacy-sections>


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
