V Back to Vantulo

API reference

Call Vantulo from your own apps with a secret key. The same key works with OpenAI-style chat completions and with the Anthropic Messages API.

Keys

Sign in, open API keys in the sidebar, and create a key. The full secret (vnt_…) is shown once. Revoking a key stops it immediately.

Send the secret on every request:

Authorization: Bearer vnt_YOUR_KEY

The x-api-key header works the same way. Key management (GET and POST /api/keys, DELETE /api/keys/:id) uses your browser session, not the secret.

Base URL

Replace the host with wherever this server is running. The examples use http://localhost:3999.

http://localhost:3999/v1

OpenAI-style: chat completions

POST /v1/chat/completions. Point an OpenAI SDK at http://localhost:3999/v1 (the /v1 is part of this base URL).

curl http://localhost:3999/v1/chat/completions \
  -H "Authorization: Bearer vnt_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"vantulo","messages":[{"role":"user","content":"Hello"}]}'
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:3999/v1",
    api_key="vnt_YOUR_KEY",
)

reply = client.chat.completions.create(
    model="vantulo",
    messages=[{"role": "user", "content": "Hello"}],
)
print(reply.choices[0].message.content)
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "http://localhost:3999/v1",
  apiKey: "vnt_YOUR_KEY",
});

const reply = await client.chat.completions.create({
  model: "vantulo",
  messages: [{ role: "user", content: "Hello" }],
});

A successful response:

{
  "id": "chatcmpl-…",
  "object": "chat.completion",
  "created": 1750000000,
  "model": "vantulo",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "Hello." },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 20,
    "completion_tokens": 4,
    "total_tokens": 24
  }
}

Anthropic-style: messages

POST /v1/messages. Point an Anthropic SDK at http://localhost:3999 with no /v1 on the end — the client adds /v1/messages itself. Send x-api-key and anthropic-version: 2023-06-01. Authorization: Bearer is also accepted.

curl http://localhost:3999/v1/messages \
  -H "x-api-key: vnt_YOUR_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model":"vantulo","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}'
import anthropic

client = anthropic.Anthropic(
    base_url="http://localhost:3999",
    api_key="vnt_YOUR_KEY",
)

reply = client.messages.create(
    model="vantulo",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}],
)
print(reply.content[0].text)

A successful response is a normal Anthropic message. model is the name you sent (vantulo when you omit it).

{
  "id": "msg_…",
  "type": "message",
  "role": "assistant",
  "model": "vantulo",
  "content": [{ "type": "text", "text": "Hello." }],
  "stop_reason": "end_turn",
  "usage": { "input_tokens": 20, "output_tokens": 4 }
}
FieldWhat it does
modelvantulo, or vantulo-thinking. Other names are accepted and echoed back.
max_tokensRequired. Capped by the server’s own ceiling.
messagesRequired. Roles user and assistant. Content is a string or a list of text, thinking, tool use, and tool result blocks.
systemA string or a list of text blocks.
tools, tool_choicePassed through. The model can call the tools you define.
stop_sequencesPassed through.
temperature0 to 1. Omitted automatically when thinking is on.
thinking{ "type": "enabled", "budget_tokens": N }. vantulo-thinking turns thinking on for you.
streamtrue returns Anthropic SSE: event: lines and data: lines, including message_start, content deltas, and message_stop.

Image blocks are supported: send { "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": "…" } } or a source.type of url. Requests carrying images are answered by a vision-capable model. POST /v1/messages/count_tokens takes the same body and returns { "input_tokens": N }, estimated from the text length. It does not spend the daily request allowance.

Errors use the Anthropic envelope: { "type": "error", "error": { "type", "message" } }. A reply blocked while streaming arrives as event: error.

OpenAI request fields

FieldWhat it does
modelvantulo, or vantulo-thinking to reason before answering. Other names are accepted and echoed back.
messagesRequired. Roles: system, developer, user, assistant. Content is a string or a list of { "type": "text", "text": "…" }. Must include a user message and end with one. No limit on how many (set API_MAX_MESSAGES to cap it).
streamtrue sends server-sent events, then data: [DONE].
max_tokensCap on the reply. max_completion_tokens is accepted as an alias. The server also has its own ceiling.
temperatureNumber from 0 to 1. Values above 1 are clamped. Ignored when thinking is on.
stopA string or up to 4 strings.
thinkingtrue turns on extended thinking. max_tokens has to leave room for it (the error message says how much).
web_searchtrue lets Vantulo search the web. The answer text is what comes back.
response_formatAny type other than text asks for raw JSON.
stream_options.include_usageOn a stream, adds a final chunk with token counts.

n must be 1. Tool and function calling is not supported. Image and file parts are rejected.

With thinking on, the reply message may include reasoning. While streaming, reasoning arrives as delta.reasoning_content and the answer as delta.content.

Streaming

curl http://localhost:3999/v1/chat/completions \
  -H "Authorization: Bearer vnt_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -N \
  -d '{"model":"vantulo","stream":true,"messages":[{"role":"user","content":"Hello"}]}'

Each event is data: {…}. The first chunk sets the assistant role. Later chunks carry delta.content. The last choice chunk has finish_reason of stop, length, or content_filter.

Models

GET /v1/models requires a key and returns vantulo and vantulo-thinking. GET /v1 is a short index and does not require a key.

Limits and billing

Defaults are 60 requests per minute and 500 requests per UTC day, per key. Change them with API_RPM and API_DAILY_LIMIT in .env (0 removes that cap). Responses include X-RateLimit-Limit-Requests, X-RateLimit-Remaining-Requests, X-RateLimit-Limit-Day, and X-RateLimit-Remaining-Day when those caps are on.

Every call runs on this server’s model account. The operator pays for it. Turn the API off with API_ENABLED=false.

Safety

The same guard that screens chat screens API input, system instructions, tool definitions, and the reply (including tool-call JSON) as it is generated. A blocked OpenAI-style request returns HTTP 400:

{
  "error": {
    "message": "Vantulo's safeguards flagged this request as possible harmful or unsafe content, so it can't respond.",
    "type": "content_filter",
    "code": "content_filter"
  }
}

If a reply is blocked mid-stream, already-sent text stops and finish_reason is content_filter. A non-streaming call returns the 400 above and does not include the reply.

Errors

HTTPcodeWhen
400content_filterSafety guard blocked the request or the reply.
400invalid_request_errorBad JSON, missing messages, tools, or n other than 1.
401missing_api_key / invalid_api_key / revoked_api_keyAuth failed.
429rate_limit / daily_limitPer-key cap. Retry-After is set.
503api_disabledAPI_ENABLED is off.

Browsers may call /v1 from another origin. Responses include Access-Control-Allow-Origin: *. Don’t put a secret key in a public web page.