API reference
Create a task
On this page
POST /api/v1/generate
Submit a generation. We validate your key, debit credits for the model, forward the request upstream, and return either an inline result (chat) or a task id to poll (media).
| Field | Type | Description |
|---|---|---|
| modelId | string | Model id from the Market (e.g. veo-3-1, nano-banana-pro). The alias `model` is also accepted. |
| input | object | Model-specific parameters — see each model's page for its fields. |
| callbackUrl | string? | Optional https webhook; we POST the finished task to it (see Webhooks). |
curl -X POST https://you.bot/api/v1/generate \
-H "Authorization: Bearer $YOUBOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "modelId": "veo-3-1", "input": { "prompt": "a city at dusk", "aspectRatio": "16:9" } }'Media model response (poll the task id):
{ "taskId": "a1b2c3d4e5f6g7h8", "model": "veo-3-1", "creditsCharged": 312 }Chat/LLM model response (inline, no polling):
{ "model": "claude-fable-5", "text": "…", "creditsCharged": 24 }**`model` is the answer to "which model served this request?"** On a chat call it is read from the model's own reply, not from our request — so you can assert it per call in your own code instead of asking the assistant, which cannot report its own identity. It is always the same model id you requested; if it were ever anything else, the request would have failed instead. (On a media create call it echoes the model the task was accepted for; media task APIs return no model of their own.)
**Conversations (chat models).** Send `messages` inside `input` instead of `prompt` to pass a whole conversation, exactly as the model receives it: an array of `{ "role", "content" }` objects with `role` one of `user`, `assistant` or `system`. Up to 20 messages per request. `content` is a string, or an array of content blocks for a model that accepts images; a message may also carry `images`. Send `prompt` **or** `messages`, not both — a request with both is refused (400 `prompt_and_messages`) rather than one of them being ignored. Anything malformed is refused too, naming the message: a role we do not accept, a `content` that is not a string or array, an empty message, or more than 20 messages. Nothing is dropped silently, and nothing is charged for a refused request.
curl -X POST https://you.bot/api/v1/generate \
-H "Authorization: Bearer $YOUBOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"modelId": "claude-opus-5",
"input": {
"messages": [
{ "role": "user", "content": "I want to build an AI reseller site" },
{ "role": "assistant", "content": "Happy to help — a few technical decisions first." },
{ "role": "user", "content": "List the Claude input and output prices." }
]
}
}'**Web search (chat models that list `web_search`).** Send `web_search: true` inside `input` and the model searches the web before answering. On the model+route pairs where we have measured that the search can be required, we require it — so a request that asks for search gets a search, instead of the model deciding to answer from memory. Two things follow, and both are visible on your bill and your clock: the search results are fed back to the model as **input tokens**, so a searched turn can cost many times a plain one (measured on one model: 39 input tokens becomes 16,221), and a searched turn is slower — a few seconds becomes up to ten. Send `web_search: true` on the turns that need fresh facts, not on every turn. If the reply comes back as raw search results instead of an answer, the request is refused with 422 `search_not_performed` and **nothing is charged**.
**Streaming (chat models).** Send `stream: true` inside `input` and the response is an event stream (`Content-Type: text/event-stream`) instead of a JSON object. **It is opt-in on this endpoint:** omit it and you get the single JSON object described above, so existing integrations are unaffected. The frames are:
event: start
data: {"model":"claude-fable-5","taskId":"a1b2c3…"}
data: {"text":"Once upon"}
data: {"text":" a time"}
event: done
data: {"taskId":"a1b2c3…"}`start` carries the same `model` value the JSON response would, and a `taskId`. Each middle frame carries the text added since the previous frame — concatenate them for the full reply. `done` closes the stream. The exact amount charged is settled from the model's own token counts after the stream ends, so it is not in the stream; look the `taskId` up in your usage log for the final figure. The same `taskId` is also returned in the `X-Task-Id` response header, and the model in `X-Model`. Streaming applies to chat models that list `stream` as an input parameter; media models are unaffected.