Vanguard AI

Responses

POST /v1/responses is one model call with tools you define and run yourself. The model sees your tools; when it wants one, the call comes back to you unexecuted. You run it in your own system, against your own data and with your own permissions, send the result back, and call again.

Nothing is stored between calls: every request carries the whole conversation, and each one is a single model step, answered synchronously.

You wantUse
A model that acts on your data: searches your listings, reads your orders, checks your stockResponses
JSON in a shape you define, pulled out of free textResponses with responseFormat
The platform’s own tools (OCR, speech, web search) run for you behind one callAgents
A chat with conversations stored for you and replies streamed token by tokenChat

Authorization

Call it with an API key that carries the responses scope, or from a dashboard session. Add the scope to a key on the API keys page; you can change an existing key’s scopes without issuing a new one. A key without it gets 403 SCOPE_FORBIDDEN.

The key needs nothing else. Your tools run on your side, so they need no scope here.

The tool loop

POST /v1/responses API key
  1. Send the conversation and your tools.
  2. If finishReason is tool-calls, run each call in output.toolCalls yourself.
  3. Append the model’s turn (an assistant message carrying those toolCalls, unchanged) and one tool message per call with your result.
  4. Call again with the same tools. Repeat until finishReason is stop.

The loop is yours, and so is its length: decide how many steps you allow before you give up. Each step is a separate call, billed on its own.

Step 1: call with your tools

200 The model wants to call your tool.
{
  "status": "success",
  "id": "0b5e9c1a-...",
  "model": "fast",
  "output": {
    "text": "",
    "toolCalls": [
      {
        "id": "call_242746",
        "name": "search_gigs",
        "input": { "query": "logo design", "city": "Vientiane" },
        "signature": "CiQB..."
      }
    ]
  },
  "finishReason": "tool-calls",
  "usage": { "inputTokens": 107, "outputTokens": 128 },
  "credits": 1,
  "latencyMs": 2522
}

Step 2: run the tool yourself

The API does not run search_gigs, and cannot: all it knows is the description and schema you sent. input was written by a model, so treat it like any other untrusted input. Check it before you use it, and run the tool with the end user’s permissions, not your own.

Step 3: send the result back

200 The final answer.
{
  "status": "success",
  "id": "7c41d2e8-...",
  "model": "fast",
  "output": {
    "text": "Noy in Vientiane does professional logo design, from 250,000 kip.",
    "toolCalls": []
  },
  "finishReason": "stop",
  "usage": { "inputTokens": 236, "outputTokens": 38 },
  "credits": 1,
  "latencyMs": 1480
}

The whole loop in TypeScript

Rules the history must follow

Each of these is a 400 INVALID_REQUEST whose message names the message at fault. They are checked before anything is charged.

  • Every tool message answers a call from the immediately preceding assistant message, matching its toolCallId and name, exactly once.
  • Every call is answered before the next user or assistant message, and before the history ends.
  • Every tool named anywhere in the history is declared in tools. Send the full tool list on every step of the loop.
  • Tool call ids are unique across the conversation.
  • An assistant message has content, toolCalls, or both. A user message’s content is not blank.

Request

JSON body

model *
string
A model id from GET /v1/chat/models. When you send tools, pick one whose supportsTools is true.
messages *
array
The whole conversation so far, 1 to 80 messages. Nothing is stored between calls. Shapes below.
system
string
Up to 20,000 characters. Sent exactly as written; no platform prompt is added.
tools
array
Up to 32 tools the model may ask for. You run them. Shape below.
toolChoice
string or object
"auto" (the default), "none", "required", or { "name": "search_gigs" } to force one tool.
responseFormat
object
{ "type": "json_schema", "name": "...", "schema": { ... } }. The answer comes back parsed in output.object. Cannot be combined with tools.
maxOutputTokens
integer
1 to 4,096, default 1,024. Reasoning models count their thinking against it.
temperature
number
0 to 1. Ignored by models that do not take one.
endUserId
string
Your own id for the person behind the request, up to 200 characters. Accepted and ignored. Safe to keep sending.
metadata
object
{ "feature": "..." }, up to 64 characters. Accepted and validated; not used for anything yet.

Messages

roleFieldsNotes
usercontentText. Not blank.
assistantcontent, toolCallsBoth optional, but not both empty. Each tool call is { id, name, input, signature }, copied from a response; signature only when the response had one.
tooltoolCallId, name, outputYour result for one call. output is a string: serialize objects with JSON.stringify.

Tools

FieldRules
nameLetters, digits and underscores, 1 to 64 characters. Unique within tools.
descriptionUp to 1,000 characters. It is what the model reads when deciding whether to call the tool, so say when to use it, not only what it is.
inputSchemaA JSON Schema with "type": "object" at the root, up to 8 KB serialized. properties, if present, is an object; required is an array of property names.

Response

FieldMeaning
idA fresh id for this call. Nothing is stored under it.
modelThe model that answered.
output.textThe answer. Empty when the model only called tools.
output.toolCallsThe calls for you to run, each { id, name, input, signature }. Empty unless finishReason is tool-calls.
output.objectOnly with responseFormat, and only when finishReason is stop: the answer, parsed. A truncated answer has none.
finishReasonstop, tool-calls, length (it hit maxOutputTokens) or content-filter (the provider withheld the answer).
usageinputTokens and outputTokens, as the provider counted them.
creditsWhat this call cost.
latencyMsHow long the call took on our side.

Structured output

Add responseFormat and the model answers in JSON that follows your schema. The parsed value is in output.object, and the raw JSON text in output.text.

200 The answer, parsed.
{
  "status": "success",
  "id": "e2a7f310-...",
  "model": "smart",
  "output": {
    "text": "{\"category\":\"design\",\"city\":\"Luang Prabang\",\"budgetLak\":300000,\"deadlineDays\":14}",
    "toolCalls": [],
    "object": {
      "category": "design",
      "city": "Luang Prabang",
      "budgetLak": 300000,
      "deadlineDays": 14
    }
  },
  "finishReason": "stop",
  "usage": { "inputTokens": 142, "outputTokens": 61 },
  "credits": 1,
  "latencyMs": 1910
}
  • It works the same on both models. Strict mode is off, so an ordinary schema is accepted as written; you do not need additionalProperties: false on every object.
  • The schema steers the model; it is not a guarantee. Validate output.object before you store it.
  • An answer that does not parse as JSON is a 502 GENERATION_FAILED, refunded.
  • responseFormat.name is letters, digits, _ and -, 1 to 64 characters. The schema follows the same rules as a tool’s inputSchema: an object at the root, up to 8 KB.

Pricing

A call is billed as the model turn it is, at the same rates as Chat and Agents:

  • the model’s creditsPerMessage, reserved before the call, plus
  • round(outputTokens / 1000) × creditsPer1kOutput, settled after it.

Both rates are listed on each model in GET /v1/chat/models. Tools cost nothing here, because they run on your side. On a model priced at 1 credit per message and 1 credit per 1,000 output tokens, a call that writes 2,400 output tokens costs 1 + 2 = 3 credits.

A 502 or 504 is refunded in full. An answer that stops at length or content-filter is charged: the model did the work. In your usage, the spend appears under chat: and the model’s id, next to a zero-credit responses:call row for each request, so you can count calls.

Limits

LimitValue
Messages per request1 to 80
Tools per requestUp to 32
system20,000 characters
Tool description1,000 characters
inputSchema, responseFormat.schema8 KB serialized, an object at the root
maxOutputTokens1 to 4,096, default 1,024
Request body400,000 characters
Time per call25 seconds, then 504 TIMEOUT and a refund

The whole history travels on every call, so a long loop grows towards the body limit. Drop the oldest turns and keep tool outputs short: return the fields the model needs, not whole database rows. Your plan’s per-minute rate limit applies as it does everywhere else.

Errors

HTTPCodeWhen
400INVALID_REQUESTThe body is not JSON or breaks the shape above; a history rule is broken; toolChoice names a tool that is not in tools, or forces one when there are none; responseFormat is sent with tools
400INVALID_MODELAn unknown or unavailable model, or tools sent to a model without tool support
400INVALID_TOOL_SCHEMAAn inputSchema is not an object schema or is over 8 KB, or a tool name appears twice
402QUOTA_EXCEEDEDNot enough credits
403SCOPE_FORBIDDENThe key does not carry responses
403SERVICE_DISABLEDResponses are switched off for now
403VERIFICATION_REQUIREDYour organization has to complete identity verification first
413PAYLOAD_TOO_LARGEThe body is over 400,000 characters. Trim the oldest messages or shorten tool outputs
429RATE_LIMITEDToo many requests this minute
429USAGE_LIMIT_REACHEDYour daily or monthly credit cap is reached
502GENERATION_FAILEDThe model failed, returned only a tool call that could not be parsed, or gave a responseFormat answer that is not JSON. Refunded
504TIMEOUTNo answer within 25 seconds. Refunded

See Errors for the shared envelope.