Vanguard AI

An agent is a configuration you create once — instructions, a model, and the capabilities it is allowed to use — and then call from your own product with a single API key.

The difference from calling the services directly is who does the orchestration. Without an agent, extracting a date from a photo of an ID card is your job: call /v1/ocr/scan, read the fields, decide whether the answer is there, then call the chat API to phrase it. With an agent you send the photo and the question, and it works out for itself that it needs OCR first.

Build one in the dashboard, or through the endpoints below.

Authorization

An agent is reached with an ordinary API key that carries the agents scope. Add it on the API keys page — you can change a key’s scopes at any time, without issuing a new one.

The key needs nothing else. What the agent may reach for is decided by its own capability list, not by the key: an agent granted OCR can read documents even though the key never mentions OCR. One grant, in the place you chose the capability.

Restricting a key to one agent

A key’s agents scope decides whether it may reach Agent Builder at all. Which agents it may call is a separate setting on the same key, and by default it is all of them — so a key issued for one integration can drive every agent you own.

Give each integration its own key and restrict it:

// PATCH /v1/auth/keys/{keyId}
{ "agentIds": ["8f2a1c4e-…"] }   // only this agent
{ "agentIds": null }              // lift the restriction

Anything outside a key’s list answers 404, not 403 — a 403 would confirm the agent exists and tell the holder of a narrow key what else you run. GET /v1/agents filters for the same reason: a restricted key sees only the agents it may call.

An empty array means the key may call no agents, which is not the same as null. Set it deliberately or not at all.

Invoking an agent

POST /v1/agents/{id}/invoke API key

Starts the agent and returns immediately. The answer is collected separately, by polling or by subscribing to the stream.

Body

message
string
What you are asking the agent. Required unless you send a file.
file
file
Multipart only. Repeat the field to send several. Images, PDFs, Office documents and audio.
files
array
JSON only. Each item is { fileName, mimeType } plus either `data` (base64) or `url`.
sessionId
uuid
Continue an earlier conversation. Returned by every invocation as `sessionId`.

Query

wait
boolean
Block for up to 25 seconds and return the finished invocation if it lands in time. Falls back to the 202 below.
202 Accepted — the agent is running.
{
  "status": "success",
  "invocationId": "8f2a1c4e-...",
  "sessionId": "b71d9e02-...",
  "state": "queued",
  "stream": "/v1/agents/invocations/8f2a1c4e-.../stream",
  "poll": "/v1/agents/invocations/8f2a1c4e-..."
}

Reading the result

GET /v1/agents/invocations/{invocationId} API key
GET /v1/agents/invocations/{invocationId}/stream API key

Poll the first for the finished answer, or subscribe to the second for output as it is produced. The stream replays from the beginning when you connect, so a dropped connection costs you nothing: reconnect and you get the whole answer, not just the part that came after.

200 The finished invocation.
{
  "status": "success",
  "invocation": {
    "id": "8f2a1c4e-...",
    "sessionId": "b71d9e02-...",
    "status": "success",
    "output": "This is a Lao national ID card issued to ... It expires on 12 April 2029.",
    "steps": [{ "tool": "ocr", "status": "done" }],
    "credits": 4,
    "latencyMs": 6210,
    "error": null
  }
}

steps is the list of capabilities the agent actually used, in order. It is worth logging: it is how you see what your agent is doing and what it is spending.

Cancelling a run

POST /v1/agents/invocations/{invocationId}/cancel API key

Stops a run in flight. Returns { "status": "success", "stopped": true }, or stopped: false when there was nothing still running — a finished invocation is not an error to cancel.

Whatever the agent had already produced is kept, and anything it already spent stays spent: the model tokens were generated and the tools it called did their work. Nothing further is charged.

Stream events

Each frame is one JSON object on a data: line.

typeFieldsMeaning
textdeltaA fragment of the answer. Append them.
toolname, statusA capability started, finished, or failed.
fileid, fileName, mimeTypeThe agent produced a file — generated audio, an image, a document.
doneoutput, credits, stepsThe final answer and what it cost.
errorcode, messageThe run failed. Nothing was charged for work that did not happen.

Continuing a conversation

Every invocation returns a sessionId. Send it back and the agent remembers the earlier turns; leave it out and each call is independent.

{ "message": "and in Lao?", "sessionId": "b71d9e02-..." }

Sending files

Multipart is the simplest route — repeat the file field for several. With a JSON body, each entry in files carries either base64 data or a url we fetch on your behalf.

A URL is fetched before the agent starts, so a bad link is a 400 you can act on rather than a run that fails halfway through. Private and internal addresses are refused.

Character, skills and memory

An agent is more than one prompt. Three fields, and each answers a different question.

Personality — who it is

personality is composed ahead of the instructions and never merged into them. Keeping them apart is not tidiness: instructions get edited whenever the job changes, which is often, while character is set once and left alone. In one field, every instruction tweak risks rewriting the agent’s personality by accident.

If you are moving from OpenClaw or Hermes, this is where SOUL.md goes, and AGENTS.md goes in systemPrompt.

Skills — what it knows how to do

GET /v1/agents/{id}/skills API key
POST /v1/agents/{id}/skills API key
PATCH /v1/agents/{id}/skills/{skillId} API key
DELETE /v1/agents/{id}/skills/{skillId} API key

A skill is a written procedure the agent looks up when it needs one. Its prompt carries only the titles and one-line descriptions — so an agent can hold a hundred procedures for about the cost of a hundred lines, and pays for the body of one only when it decides that one is relevant.

POST accepts a whole SKILL.md in markdown and reads the front matter out of it, so a skill written for OpenClaw, Hermes or eve moves across as a paste rather than a rewrite. Explicit name and description fields win over the front matter when you send them. Names are slugged, and posting the same name again replaces that skill rather than failing — which is what makes re-importing a directory work.

---
name: expense-claims
description: how to file an expense claim
---

# Filing an expense claim

1. …

Loading a skill adds instructions to the turn. It never adds a way to run anything, which is why skills are safe to accept from anyone and need no sandbox.

Memory — who it is talking to

GET /v1/agents/{id}/memories?endUserId=… API key
DELETE /v1/agents/{id}/memories/{memoryId} API key
DELETE /v1/agents/{id}/memories?endUserId=… API key

Pass endUserId on invoke — your own identifier for the person the agent is talking to — and the agent starts remembering them. Leave it out and it has no memory beyond the thread itself.

That identifier is what makes memory outlive a session. Without it, the only stable handle is the conversation, so the agent would begin every new conversation knowing nothing; with it, what it learns accumulates across every thread that person ever opens. It is opaque to us and scoped to your agent, so your user-42 is never anyone else’s.

{ "message": "what did we decide last time?", "endUserId": "user-42" }

Memory is written after a conversation, by a pass over what was said — not by the agent deciding mid-sentence to save something. Models given a “remember this” tool use it constantly and on the wrong things; reading the whole exchange once it is over produces far less, and what it produces is what mattered. Sensitive details — card numbers, ID numbers, passwords — are skipped even when volunteered. The pass is not charged: it runs after your request has already returned, and a charge you had no chance to decline would be indefensible.

Everything remembered is readable and erasable through the endpoints above. DELETE with endUserId and no memory id forgets that person completely.

Managing agents

GET /v1/agents API key
POST /v1/agents API key
GET /v1/agents/{id} API key
PATCH /v1/agents/{id} API key
DELETE /v1/agents/{id} API key

Body (create / update)

name *
string
What you will recognise it by.
model *
string
A model id from GET /v1/chat/models.
systemPrompt
string
The agent's standing instructions.
capabilities
string[]
Ids from GET /v1/agents/capabilities. Anything not listed is never offered to the model.
projectId
uuid
A project whose documents the agent may answer from.
maxSteps
integer
How many tools it may chain in one answer. 1-8, default 5.

PATCH updates only the fields you send. Setting status to "disabled" stops the agent answering without deleting it, so the id your code names stays valid.

GET /v1/agents/{id}/invocations API key

The recent invocations for one agent, newest first.

Capabilities

GET /v1/agents/capabilities API key

Everything an agent can be granted, with its price. kind: "service" prices are the floor — speech is billed per minute and synthesis per 1,000 characters, so a long input costs more than the figure shown.

200
{
  "status": "success",
  "capabilities": [
    { "id": "ocr", "label": "OCR", "kind": "service", "credits": 1, "available": true },
    { "id": "stt", "label": "Speech to Text", "kind": "service", "credits": 5, "available": true },
    { "id": "tts", "label": "Text to Speech", "kind": "service", "credits": 2, "available": true },
    { "id": "docproc", "label": "Document Processing", "kind": "service", "credits": 2, "available": true },
    { "id": "web_search", "kind": "tool", "credits": 2, "available": true },
    { "id": "fetch_url", "kind": "tool", "credits": 1, "available": true },
    { "id": "generate_image", "kind": "tool", "credits": 10, "available": true }
  ]
}

An unavailable capability is one this deployment has no key or bucket for. It is listed rather than hidden so an agent that was using it can still show you why it stopped.

Errors

CodeMeaning
SCOPE_FORBIDDENThe key does not carry the agents scope.
AGENT_DISABLEDThe agent exists but is switched off.
INVALID_CAPABILITYA capability id that does not exist. The message lists the valid ones.
INVALID_MODELA model id that is not in GET /v1/chat/models.
PAYLOAD_TOO_LARGEA file above the per-file limit.
FILE_FETCH_FAILEDA url in files could not be fetched, or pointed somewhere not allowed.
QUOTA_EXCEEDEDNot enough credits. Top up and retry.

See Errors for the shared envelope.