Protocol
Making requests
The inference endpoint — body, response, streaming, feature tagging, idempotency.
api.usewind.app/v1/ai/{model} Bearer <access_token> The model is in the path — any model Wind offers, or one in the app’s
allowed_models if you’ve restricted it. The body is
OpenAI-shaped.
Calling by feature
api.usewind.app/v1/ai/features/{slug} Bearer <access_token> Same body and response, but the model comes from the feature’s default_model
(set in the console) instead of the path. Changing models then needs no deploy.
The feature must exist and have a default_model, or the call fails with
400 feature_model_not_configured. If the app sets
allowed_models, the resolved model must be in it.
Models
Wind serves OpenAI, Anthropic (Claude) and Google (Gemini) models behind the same request and response shape. The current list, with what Wind pays per token, is public:
curl https://api.usewind.app/v1/models
[{ "id": "claude-sonnet-5-5", "provider": "anthropic",
"input_cost_per_1k_usd": "0.002000", "output_cost_per_1k_usd": "0.010000" }, …]
Use these ids exactly in the path. Users pay the provider cost plus Wind’s fee
and your margin (see Concepts). Models
are added and retired over time; a call to a retired model fails with
400 invalid_request, so check the list when choosing models rather than
assuming one stays available forever.
GPT-5 and o-series models are reasoning models: they only accept the default
temperature, so leave it out for them.
Headers
| Header | Required | Notes |
|---|---|---|
Authorization | yes | Bearer <access_token> — the token is the connection |
Idempotency-Key | yes | a UUID; replays return the original result and never double-charge. Reusing a key with a different body is 409 idempotency_key_reused |
X-Wind-Feature | no | a feature slug — analytics bucket + that feature’s guardrails |
Content-Type | yes | application/json |
Body
{
"messages": [{ "role": "user", "content": "Describe this plate." }],
"max_tokens": 500,
"temperature": 0.7,
"stream": false
}
max_tokens is optional. When omitted, the authorization hold’s cap resolves
request → feature max_tokens → app default_max_tokens → model ceiling.
Response
The provider completion, plus a wind block:
{
"id": "chatcmpl-…",
"choices": [ … ],
"usage": { "prompt_tokens": 42, "completion_tokens": 130, "total_tokens": 172 },
"wind": {
"feature": "plate-scan",
"provider_cost_mc": 184000,
"wind_fee_mc": 18400,
"dev_margin_mc": 27600,
"user_charge_mc": 230000,
"allocation_remaining_mc": 476120000
}
}
Tools
Pass OpenAI-style function tools (plus optional tool_choice and
parallel_tool_calls). This works the same for every provider: Wind translates
to Anthropic’s and Gemini’s formats and back, so responses always carry
OpenAI-shaped tool_calls and finish_reason: "tool_calls".
{
"messages": [{ "role": "user", "content": "Weather in Oslo?" }],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] }
}
}]
}
To send results back, append the assistant message unchanged, then one
tool message per call:
{ "role": "tool", "tool_call_id": "call_…", "content": "{\"temp_c\": 12}" }
Keep the assistant message exactly as Wind returned it. On Gemini models a tool
call can carry extra_content.google.thought_signature, and Gemini
rejects the next turn if it’s missing.
parallel_tool_calls: false is honored on OpenAI and Anthropic models and
ignored on Gemini. In sandbox mock mode the canned model calls the first
(or forced) tool with {} arguments, then answers in text once the last
message is a tool result. That’s enough to exercise your tool loop end to end.
Images
Content parts of type image_url take a data: URL or a public http(s) URL.
For Anthropic and Gemini models Wind downloads remote images itself (max 5MB
each, 20 per request), so the provider never has to reach your host. A URL that
can’t be fetched, isn’t an image, or points at a private address is rejected
with 400 invalid_request before anything is held or billed.
Streaming
Set "stream": true. Wind proxies the provider’s SSE stream; the wind block
arrives as a final event once the response completes and the ledger row is
written.
Metering & idempotency
Every call writes one immutable ledger row (see Concepts).
A retried request with the same Idempotency-Key returns the original
response and is not billed again — use this for network retries and on
502 provider_error.
The response never includes the user’s total balance — only
allocation_remaining_mc for this connection.