API Reference

Agent Runs

Observability and a server-side safety backstop for your autonomous agents. Reliant does not run your agent loop — you do. You report each step; Reliant records the full trace and enforces two brakes: the budget (steps / tokens / USD) and the kill switch. Agent Guard (argument validation, action policy, human approval) is an optional layer on top.


The loop model

flow
your loop Reliant ───────── ─────── create / fetch the Agent ─────▶ config: budgets, kill switch, tools (schema + policy) reliant.agentRun() ─────▶ creates the run, returns a handle ┌─ LLM turn │ handle.logStep(llm_turn) ──▶ stores it, accrues tokens/cost, checks the budget │ handle.guardTool(name,args) ─▶ validates args + policy → allow | block | escalate │ run the tool (if allow) │ handle.logStep(tool_call) ──▶ stores it │ if (handle.halted) break ◀── budget exhausted or kill switch └─ repeat handle.end() ─────▶ closes the run, returns the totals

All routes below authenticate with the X-Reliant-Key header and are scoped to the project that key belongs to. The base URL is https://reliant.api.br (or your own host — see Run Reliant Locally).

Plans: the plain trace (agentRun + logStep) is available on every tier. As soon as a tool declares requires_approval or a non-empty policy, or the agent sets approval_webhook_url, Agent Guard applies and requires starter or above.

Create an agent

An agent is the reusable config; a run is one execution of it. POST /agents upserts by name within the project — calling it again with the same name updates the config and reactivates a soft-deleted agent.

http
POST https://reliant.api.br/agents Headers: Content-Type: application/json X-Reliant-Key: rel_...
json — request body
{ "name": "support-agent", "description": "Answers tickets and issues refunds", "max_steps": 40, "max_tokens": 200000, "max_usd": 5, "kill_switch": false, "approval_webhook_url": "https://your-app.com/hooks/reliant", "webhook_secret": "whsec_...", "approval_timeout_seconds": 3600, "tools": [ { "name": "search_kb", "args_schema": { "type": "object", "required": ["query"], "properties": { "query": { "type": "string" } }, "additionalProperties": false } }, { "name": "issue_refund", "args_schema": { "type": "object", "required": ["order_id", "amount_usd"], "properties": { "order_id": { "type": "string" }, "amount_usd": { "type": "number" } }, "additionalProperties": false }, "requires_approval": true, "on_violation": "escalate", "policy": { "max_value": { "field": "amount_usd", "limit": 500 }, "rate_limit": { "max": 20, "period": "daily" }, "safe_result": { "status": "refund_pending_review" } } } ] }
FieldTypeDescription
namestringrequiredUnique per project.
max_steps / max_tokens / max_usdnumberoptionalBudget backstop. A run halts when spend reaches any of these. Omit for no cap.
kill_switchbooleanoptionalWhen true, every run of this agent halts on its next call and no new runs start.
approval_webhook_urlstringoptionalFired with a signed agent.approval_requested payload on every escalation.
webhook_secretstringoptionalHMAC-SHA256 key for the X-Reliant-Signature header on the webhook.
approval_timeout_secondsnumberoptionalPending approvals past this age flip to expired (checked lazily on read).
tools[].namestringrequiredMust match the tool name your loop passes to guard-tool.
tools[].args_schemaobjectrequiredA valid JSON Schema. Proposed tool args are validated against it.
tools[].requires_approvalbooleanoptionalEvery call to this tool escalates to a human. Default false.
tools[].on_violationenumoptionalblock (default) or escalate — what a policy violation does.
tools[].policyobjectoptionalAction policy — see the policy fields table below.
typescript — SDK
const agent = await reliant.agents.create({ name: 'support-agent', maxSteps: 40, maxTokens: 200_000, maxUsd: 5, tools: [ { name: 'search_kb', argsSchema: { type: 'object', required: ['query'], properties: { query: { type: 'string' } }, additionalProperties: false } }, { name: 'issue_refund', argsSchema: { type: 'object', required: ['order_id', 'amount_usd'], properties: { order_id: { type: 'string' }, amount_usd: { type: 'number' } }, additionalProperties: false }, requiresApproval: true, onViolation: 'escalate', policy: { max_value: { field: 'amount_usd', limit: 500 }, rate_limit: { max: 20, period: 'daily' }, safe_result: { status: 'refund_pending_review' } } }, ], }) // reliant.agents.list() / get(id) / update(id, params) / delete(id)

Start a run

http
POST https://reliant.api.br/agent-runs { "agent": "support-agent", // id or name "goal": "Resolve ticket #8421", "external_id": "ticket-8421", "max_usd": 2 // per-run override of the agent budget }
json — 201 response
{ "run_id": "clyy...", "agent_id": "clxx...", "status": "running", "budget": { "max_steps": 40, "max_tokens": 200000, "max_usd": 2 } }

If the agent's kill switch is on, this returns 423 and no run is created.

typescript — SDK
const run = await reliant.agentRun({ agent: 'support-agent', goal: 'Resolve ticket #8421', externalId: 'ticket-8421', maxUsd: 2, }) run.id // 'clyy...' run.budget // { max_steps, max_tokens, max_usd } run.halted // false — becomes true when the server says stop run.haltReason // null | 'budget_steps' | 'budget_tokens' | 'budget_usd' | 'kill_switch'

Log a step

Call this for each llm_turn, each executed tool_call, and the final_output. Reliant creates approval_gate steps itself on escalation — you do not log those.

http
POST https://reliant.api.br/agent-runs/{run_id}/steps { "type": "llm_turn", "output": [ ... ], "tokens": 1840, "cost_usd": 0.021, "latency_ms": 900 }
FieldTypeDescription
typeenumrequiredllm_turn | tool_call | final_output | approval_gate
tool_namestringoptionalFor tool_call steps.
input / outputanyoptionalRecorded verbatim in the trace.
validation_errors / retry_loganyoptionalYour own retry bookkeeping, if any.
tokens / cost_usdnumberoptionalAccrued into the run total and checked against max_tokens / max_usd.
latency_msnumberoptionalWall time for the step.
json — response
{ "ok": true, "halt": false, "halt_reason": null, "budget_remaining": { "steps": 39, "tokens": 198160, "usd": 1.979 } }

When halt is true, stop the loop — further logStep / guard-tool calls return 409. The SDK also flips run.halted / run.haltReason.

halt_reasonMeaning
budget_stepsStep count reached max_steps.
budget_tokensAccrued tokens reached max_tokens.
budget_usdAccrued cost reached max_usd.
kill_switchThe agent's kill switch was turned on.

Guard a tool call

Call this before dispatching any tool. On allow nothing is stored — you log the tool_call step after running it. On block / escalate the attempt is stored (as a tool_call or approval_gate step) and counts toward the step budget.

http
POST https://reliant.api.br/agent-runs/{run_id}/guard-tool { "tool_name": "issue_refund", "args": { "order_id": "o_991", "amount_usd": 120 }, "context": { "confidence": 0.9 } }

Order of checks

#Check
1Kill switchblock + halt.
2Schema — args validated against args_schema. Failure → block with validation_errors + retry_guidance. Unregistered tool → passes (tool_registered: false).
3Policyconfidence_min, max_value, allowed_values, blocked_values, active_hours. Violation → block or escalate per on_violation.
4Rate limit — per hourly/daily/monthly bucket. Over → block / escalate.
5requires_approval — always escalate.

Response

json — allow
{ "decision": "allow", "validated": true, "tool_registered": true, "validated_args": { "order_id": "o_991", "amount_usd": 120 }, "halt": false, "halt_reason": null, "budget_remaining": { "steps": 38, "tokens": 198160, "usd": 1.979 } }
json — block
{ "decision": "block", "validated": true, "tool_registered": true, "validation_errors": [ { "path": "/amount_usd", "message": "must be number", "keyword": "type", "params": {} } ], "retry_guidance": "The arguments you produced for the tool \"issue_refund\" did not match its schema. ...", "policy_rule": "max_value", "policy_reason": "\"amount_usd\" (1200) exceeds the limit of 500", "safe_result": { "status": "refund_pending_review" }, "step_index": 7, "halt": false, "halt_reason": null, "budget_remaining": { "steps": 37, "tokens": 198160, "usd": 1.979 } }
json — escalate
{ "decision": "escalate", "validated": true, "tool_registered": true, "approval_id": "clzz...", "policy_rule": "requires_approval", "policy_reason": "tool requires human approval for every call", "safe_result": { "status": "refund_pending_review" }, "step_index": 7, "halt": false, "halt_reason": null, "budget_remaining": { "steps": 37, "tokens": 198160, "usd": 1.979 } }

On block: do not run the tool; feed retry_guidance back to the model. On escalate: wait on approval_id (next section) before proceeding.

Policy fields (tools[].policy)

FieldShapeRule
confidence_minnumber 0..1optionalcontext.confidence must be ≥ this.
max_value{ field, limit }optionalargs[field] must be a number ≤ limit (dot-path supported).
allowed_values{ field, values[] }optionalargs[field] must be one of values.
blocked_values{ field, values[] }optionalargs[field] must not be one of values.
active_hours{ start, end, days? }optionalHH:MM server-local window; days is 0–6 (Sun–Sat).
rate_limit{ max, period }optionalMax calls per hourly | daily | monthly bucket.
safe_resultanyoptionalEchoed back on block / escalate so the agent has a safe value to use.

Approvals

On an escalation Reliant creates an approval_gate step and a pending AgentApproval, and fires the signed webhook if the agent has one.

http
GET /agent-runs/{run_id}/approvals?status=pending GET /agent-runs/{run_id}/approvals/{approval_id} POST /agent-runs/{run_id}/approvals/{approval_id}/approve { "decided_by": "ana@acme.com" } POST /agent-runs/{run_id}/approvals/{approval_id}/deny { "decided_by": "ana@acme.com" } # project-wide inbox (what the dashboard uses) GET /agent-approvals?status=pending&agent_id=...&limit=50 POST /agent-approvals/{approval_id}/approve POST /agent-approvals/{approval_id}/deny POST /agent-approvals/sweep # bulk-expire due approvals
json — approval
{ "id": "clzz...", "run_id": "clyy...", "agent_id": "clxx...", "step_index": 7, "tool_name": "issue_refund", "args": { "order_id": "o_991", "amount_usd": 1200 }, "reason": "\"amount_usd\" (1200) exceeds the limit of 500", "status": "pending", // pending | approved | denied | expired "decided_by": null, "decided_at": null, "expires_at": "2026-09-10T15:00:00.000Z", "created_at": "2026-09-10T14:00:00.000Z" }

Expiry is lazy — a due approval flips to expired on the next read, so waitForApproval resolves without a cron. The webhook payload is agent.approval_requested with header X-Reliant-Signature: sha256=<hmac of the raw body with webhook_secret>.

typescript — SDK
// in the loop — poll until resolved const verdict = await run.waitForApproval(guard.approval_id, { pollMs: 3000, timeoutMs: 600_000 }) // verdict.status: 'approved' | 'denied' | 'timeout' if (verdict.status === 'approved') { const args = verdict.validated_args ?? guard.validated_args // dispatch, then run.logStep({ type: 'tool_call', ... }) } // out of band — resolve from your own tooling const { approvals } = await reliant.listAgentApprovals({ status: 'pending' }) await reliant.resolveAgentApproval(approvalId, 'approve', 'ana@acme.com')

Framework adapters

The SDK ships wrappers so you don't hand-write the guard/dispatch/log dance. Both call guardTool + logStep for you.

typescript
import { guardAnthropicToolUses, wrapAISDKTools } from 'reliant-js' // Anthropic — pass the content array from messages.create const { toolResults, halted, blocked, pendingApprovals } = await guardAnthropicToolUses( run, resp.content, { dispatch: (name, args) => myTools[name](args), onEscalate: 'wait' }, ) messages.push({ role: 'user', content: toolResults }) if (halted) break // Vercel AI SDK — wraps the tools object; each execute() self-guards const guardedTools = wrapAISDKTools(myTools, run, { onEscalate: 'wait' }) await generateText({ model, tools: guardedTools, prompt })

onEscalate: 'wait' polls the approval and dispatches if approved; 'block' (default) returns the guidance immediately and leaves the approval_id for you to resolve out of band. Adapters do not emit llm_turn steps — add those yourself if you want them in the trace.


End a run

http
POST https://reliant.api.br/agent-runs/{run_id}/end { "status": "completed", "summary": "Refund issued" } // status: 'completed' | 'failed'
json — response
{ "id": "clyy...", "status": "completed", "halt_reason": null, "steps": 12, "tokens": 40213, "usd": 0.48, "started_at": "2026-09-10T14:00:00.000Z", "ended_at": "2026-09-10T14:03:11.000Z" }

end never clobbers a halt — if the run already stopped, it returns the existing summary unchanged.


Inspect runs

http
GET /agent-runs?agent_id=...&status=running&page=1&limit=20 GET /agent-runs/{run_id} # run + steps[] in order — the full trace

Listing is clipped to your plan's retention window. Each step in the detail response carries index, type, tool_name, input/output, validation_errors, policy_decision/policy_reason, and tokens/latency_ms/cost_usd.

Live stream

A WebSocket pushes run events as they happen (this is what the dashboard uses; there is no SDK helper — open a raw socket):

ws
wss://reliant.api.br/agent-runs/stream?key=rel_...&agent_id=<optional> # event types connected | run.started | step | run.halted | run.completed | run.failed approval.requested | approval.resolved

Durable checkpoint & resume

Push opaque loop state so a fresh process can recover a run after a crash. Reliant stores it verbatim and never executes anything.

http
PUT /agent-runs/{run_id}/checkpoint { "state": { ... }, "cursor": 12 } GET /agent-runs/{run_id}/checkpoint GET /agent-runs/{run_id}/resume # run status + checkpoint + any pending approval
typescript — SDK
await run.saveCheckpoint(myOpaqueState, cursor) // after each step or turn // in a new process const { run, status, checkpoint, pendingApproval } = await reliant.resumeAgentRun(runId) if (status !== 'running') { /* already finished / halted */ } else { /* rehydrate from checkpoint.state and keep going on the same handle */ }

Replay

A dry-run that answers “would this schema/policy change break past traffic?”. It re-validates every recorded tool_call and re-runs Agent Guard policy against a modified tool config. No LLM calls; no writes to the run — only the report is saved. llm_turn steps are not re-executed.

http
POST /agent-runs/{run_id}/replay { "agent_id": "clxx...", // optional — replay against a different agent's tools "tools": [ { "name": "issue_refund", "policy": { "max_value": { "field": "amount_usd", "limit": 100 } } } ] } GET /agent-runs/{run_id}/replays GET /agent-replays/{replay_id}
json — report.summary
{ "total_steps": 12, "tool_calls_evaluated": 5, "unchanged": 3, "newly_blocked": 1, "newly_allowed": 0, "escalation_changes": 1, "validation_now_failing": 0, "skipped": 0 }
typescript — SDK
const { report } = await reliant.replayAgentRun(runId, { tools: [{ name: 'issue_refund', policy: { max_value: { field: 'amount_usd', limit: 100 } } }], }) // reliant.listAgentReplays(runId) / reliant.getAgentReplay(replayId)