Endpoints
GET /v1/harnesseslists configured agents visible to the Workspace key.POST /v1/harnessescreates a configured agent.GET /v1/harnesses/{id}reads one configured agent.PUT /v1/harnesses/{id}updates one configured agent.DELETE /v1/harnesses/{id}deletes one configured agent. A deleted agent stops at once and is removed 30 days later, with its plugins; until then it can be restored by emailing contact@harnessrouter.ai. A database connection it holds is removed at once, so a restored agent needs it entered again. Its tasks are not deleted with it.
Fields
- name, base
- Required. The base is one of the ids from GET /v1/bases.
- base_label, default_model, system_prompt
- The label shown for the base, the model a turn uses when the request names none, and the runtime agent's standing instructions.
- mcp_servers, skills, plugins
- MCP servers the agent may call, skills it may load, and installed Agent Plugins packages.
- disabled_tools
- Built-in tool names this agent may not use. How firmly that holds depends on the base; see below.
- additional_headers
- Header names a caller may pass on a task request. Only names are declared here; the values travel per request.
- env
- Variables every turn's shell and tools start with. See below.
- environment
- The environment id whose project every task reads at /env/<slug>.
- max_step, timeout_seconds, max_cost_usd
- Per-turn budgets. GET /v1/bases returns the defaults: 400 steps, 7200 seconds, 10 USD.
How firmly a disabled tool stays disabled
This depends on the base, and GET /v1/bases says which you are getting: every tool it lists carries an enforcement of either hard or instruction.
hard means the tool is denied by the agent's own runtime and the model cannot reach it. instruction means the runtime offers no per-tool switch, so the disabled list is written into the agent's instructions as a request it is expected to honour.
On a base whose tools report instruction, treat the disabled list as a strong steer rather than a control you can rely on. Anything that must not happen belongs behind a credential the task never receives, or a base that enforces it as hard.
Load tools only when needed
An agent with many tools from its plugs can start each task with a short list instead of every tool definition: a few core tools, plus three tools it uses to find the rest, tool_search, tool_describe and tool_call. It finds, reads and calls everything else by name as it works, with the same permissions, approvals and logs as a direct call.
Set it per agent through the API, with lazy and pinned on POST /v1/harnesses/{id}/servers/plugs. The console does not show this setting.
- lazy
truegives the short list;falselists every tool. Leave it out to keep the agent's current setting.- pinned
- Tools listed by name on every task besides the core tools, at most 8. Name each one as the agent sees it, the plug and the tool joined by an underscore, such as
browser_navigate; undertoolsthe names stay bare. Leave it out to keep the current pins. - Fewer than 25 tools
- The setting changes nothing, since listing them all costs less than a search. The browser alone has fewer, so the example below shows the request's shape and changes nothing on its own.
- Tools left out
- A tool you leave out of
toolsis never found, described or called.
POST /v1/harnesses/{id}/servers/plugs
{"plugs": ["browser"], "lazy": true, "pinned": ["browser_navigate"]}The env channel
env is a map of variable names to values that every turn's shell and tools start with. A value takes one of three forms: a literal; $headers.X-Api-Key, which reads a header the caller sent on that task request and which must be declared in additional_headers; or secret:harness-mcp-<name>, the reference PUT /v1/mcp-secrets/{ref} returns when you store a secret with the service, where {ref} is a name you choose. A stored secret is scoped to your organization.
- Up to 64 variables.
- Names beginning HR_, HARNESS_, OPENAI_, ANTHROPIC_, GEMINI_, GOOGLE_, AWS_, AZURE_, LD_ or DYLD_ are reserved, so the platform's own wiring cannot be shadowed.
An env value is given to the shell, never written into the agent's prompt, and is redacted from traces. A key a task needs can travel this way without becoming part of the conversation.
Create shape
POST /v1/harnesses
Content-Type: application/json
{
"name": "Contract Review Agent",
"base": "claude-code",
"default_model": "<allowed model id>",
"system_prompt": "<runtime-agent instructions>",
"mcp_servers": [],
"skills": []
}Environment and per-request secrets
A configured agent can carry an env map. Every shell command and tool the agent runs starts with those variables set, so a token, a key or any long exact value reaches the work without ever being written into the prompt. The agent reads it from the shell, and a value that came through a reference never reaches the trace, the stored response or the stream, even when the agent prints it.
A value is resolved on every turn. $headers.X-Name takes the header of that name from the request that started the turn, which must be listed in additional_headers. secret:harness-mcp-<name> (the reference PUT /v1/mcp-secrets/{ref} returns, where {ref} is a name you choose) takes that stored secret. Anything else is used as written, so keep literals to non-secrets such as a region or a mode.
- Names
- Letters, digits and underscores, up to 64 variables. Names the runtime sets for itself are refused: the HR_, HARNESS_, OPENAI_, ANTHROPIC_, GEMINI_, GOOGLE_, AWS_, AZURE_, LD_ and DYLD_ prefixes, PATH, HOME and the shell's own, the interpreters' import paths, the trust roots and the proxy variables.
- Missing header
- A
$headersreference whose header is absent on a request leaves the variable unset, so a shell test for it says what happened. - Redaction
- A value that came through a
$headersorsecret:reference is replaced with[redacted]in everything that leaves the sandbox: the response body, the stream, the trace and the stored record. A literal is not redacted. - Scope
- A
secret:reference reads only a secret stored throughPUT /v1/mcp-secrets/{ref}in your own organization, never another organization's and never the platform's. - What it protects
- The record, not the agent. A value the agent can read, it can also write to a file or send elsewhere if a prompt or a page it reads tells it to. Hand the agent a token scoped to the user and the task, short-lived and least privilege, exactly as you would to a person.
PUT /v1/harnesses/{id}
Content-Type: application/json
{
"name": "Deploy Agent",
"base": "codex",
"additional_headers": ["X-Deploy-Token"],
"env": {
"DEPLOY_TOKEN": "$headers.X-Deploy-Token",
"DEPLOY_REGION": "us-east-1"
}
}
POST /v1/responses
X-Deploy-Token: <the token for this user>
{ "model": "{id}", "input": "Deploy the current build. The token is in $DEPLOY_TOKEN." }Creating, updating, and deleting configured agents through these endpoints is harness lifecycle management, which UHP places at conformance class Full in its Harnesses chapter. See harness management in the UHP Harnesses chapter.
Ready to run this against a live workspace? Keys take under a minute.

