API Reference
Parameters
What a request body may carry on each of the three routes — and what happens to the fields we do not read.
Three routes take a JSON body: POST /api/v1/chat/completions for text and image models alike, POST /api/v1/images for image models, and POST /api/v1/messages for text models in the native Messages API shape. They share a key and a balance, not a set of fields: a parameter that shapes a frame means nothing to a text model, and the sampling parameters never reach an image model. On chat/completions the kind of model named in model decides which fields are read.
A field you leave out is left out. The gateway does not substitute values of its own and does not pad the body before it goes upstream — on the Messages route the body travels to the model exactly as it arrived, with only the model identifier resolved. The one exception is max_tokens on chat/completions, where the executor requires a ceiling and 4096 is filled in when none is sent. The Default column below is otherwise the conventional value, not something we inject; sending it explicitly is not the same as omitting it.
A key we do not know is not an error. Bodies written for another gateway carry fields we have no use for, and refusing them would break a client that is otherwise correct. What is refused is a field we do read carrying a value we cannot read — and the refusal names the field.
Where fields apply
| Field | Type | Default | chat · text | chat · image | images | messages |
|---|---|---|---|---|---|---|
| model | string | required | Read | Read | Read | Read |
| messages | array | required | Read | Read | — | Forwarded |
| prompt | string | required | — | — | Read | — |
| input_references | array | none | — | — | Read | — |
| stream | boolean | false | Read | Read | Read | Read |
| n | integer | 1 | Read | Read | Read | — |
| max_tokens | integer | 4096 · required | Read | — | — | Read |
| temperature | float | not set | Forwarded | — | — | Forwarded |
| top_p | float | not set | Forwarded | — | — | Forwarded |
| top_k | integer | not set | Forwarded | — | — | Forwarded |
| stop | string or array | not set | Read | — | — | — |
| stop_sequences | array | not set | — | — | — | Forwarded |
| system | string or array | not set | — | — | — | Forwarded |
| tools | array | none | Read | — | — | Forwarded |
| tool_choice | string or object | auto | Read | — | — | Forwarded |
| parallel_tool_calls | boolean | true | Read | — | — | — |
| cache_control | object | none | Read | — | — | Forwarded |
| stream_options | object | none | No effect | — | — | — |
| image_config | object | none | No effect | Read | Read | — |
| size | string | auto | No effect | Read | Read | — |
| resolution, image_size | string | auto | No effect | Read | Read | — |
| aspect_ratio, aspect | string | auto | No effect | Read | Read | — |
| quality | enum | high | No effect | Read | Read | — |
| output_format | enum | none | No effect | Read | Read | — |
| output_compression | integer | none | No effect | Read | Read | — |
| background | enum | auto | No effect | Read | Read | — |
| seed | integer | none | No effect | No effect | No effect | — |
| user | string | none | No effect | No effect | No effect | — |
| response_format, logprobs, logit_bias | mixed | none | No effect | — | — | — |
| presence_penalty, frequency_penalty | float | none | No effect | — | — | — |
Scroll the table horizontally for the per-route columns.
The four right-hand columns are the routes, with chat/completions split by the kind of model it is serving — a text model reads one set of fields, an image model another — then images and v1/messages. Read means the gateway acts on the field itself. Forwarded means it reaches the model untouched: we neither validate it nor replace it, so its accepted range is the model's and a value outside that range comes back as a 400 carrying the model's own wording. No effect means the field is accepted rather than refused and changes nothing about the result; on a text model it is also noted in our log. A dash means the route does not look at the field at all.
The request
Model
- Key:
model - Required, string, a catalogue identifier
- Default: —
The identifier from the catalogue, in the form vendor/model. It decides both what answers and at what rate, and it is never quietly swapped for something close: an identifier that is not in the catalogue — a missing or empty model included — comes back 404, and one that is listed but not served yet comes back 400 saying so.
It also decides which reply you get on chat/completions: a text model answers with text, an image model with a frame, and the catalogue says which is which in architecture.output_modalities. The other two routes are not interchangeable: a text model named on /api/v1/images, or an image model named on the Messages route, is refused with 400 rather than answered from the wrong place, because a request that quietly lands on the wrong executor surfaces later as a strange reply and gets debugged in the prompt.
Messages
- Key:
messages - Required, array of message objects
- Default: —
The conversation, in the shape the OpenAI libraries send. On a text model every turn travels as itself: system and developer become the standing instruction wherever they stand in the array, user carries text and image_url parts, assistant carries earlier answers and their tool_calls, and tool carries a result under its tool_call_id. On an image model the text of all messages is joined into one instruction with the framing parts placed first, and images attached in any image_url part are collected from the whole array as references. On the Messages route the array is the model's own and travels untouched.
Structure that cannot be read is 400 naming the position: a message that is not an object, a content that is neither string nor array, an image_url without a url string, a tool call without an id or a name. A content part of a kind we cannot use — audio, a file — is a 400 on a text model, because dropping it would change the question; on an image model it is skipped instead.
Prompt
- Key:
prompt - Required on
POST /api/v1/images, string, at least one non-blank character - Default: —
The instruction for the images route, which takes a single string instead of an array. It is checked before anything is reserved or spent, so a body without it — or with null, an empty string, or a value that is not a string at all — costs nothing and comes back 400. On chat/completions the instruction comes from messages and this field is not read.
Input References
- Key:
input_references - Optional, array, up to 16 items
- Default: none
The frames to work from on the images route. An item is either a data URL or an http(s) URL, written as a bare string or as an object with image_url. Links are fetched before the balance is touched, so one that does not open is a 400 naming which reference failed, and nothing is charged for it.
On chat/completions the same job belongs to image_url parts inside messages; the limit of sixteen attachments is shared by both. Attachments to an image model are billed as input at the model's input rate, at a fixed count of about 2,900 tokens each; the catalogue's pricing.image is that count at the input rate. An image sent to a text model is not fetched by us at all: a data URL travels as bytes, and a link is opened by the model provider.
N
- Key:
n - Optional, integer, 1 to 10 on an image model, exactly 1 on a text model
- Default: 1
On an image model, how many frames come back from one request; each of them is charged. A number above the ceiling is brought down to it rather than refused, so asking for fifty returns ten. On a text model the only accepted value is 1: n: 2 is 400, because two answers would be two requests and two charges under one identifier. A value that is not a number is 400 everywhere.
This one is read at the top level only. Inside image_config it has no effect, unlike the geometry fields below.
Stream
- Key:
stream - Optional, boolean
- Default: false
Turns the reply into server-sent events on all three routes. On the two OpenAI-shaped routes the value has to be a real boolean — the string "true" is 400, because a stream and a single JSON document are two different replies and guessing wrong breaks the parsing either way.
A text model streams as it writes, on either text route. A streamed frame is still a finished frame: the events carry the whole image when it is ready, not a picture that fills in. stream_options is accepted next to it and changes nothing — the last chunk of a text stream carries usage whether or not include_usage asked for it. Event shapes and how a stream ends
Seed and user
- Key:
seed, user - Optional, integer and string
- Default: none
Both are accepted on the OpenAI-shaped routes and neither is executed. They are taken rather than refused because a client that sends them on every call is not doing anything wrong, and they are named here rather than passed over in silence because seed promises repeatability: two identical requests with the same seed will still differ, and someone comparing them would go looking for the reason in the prompt.
On a text model the same treatment — accepted, dropped before the request goes upstream, a line in our log — applies to response_format, logit_bias, logprobs, presence_penalty and frequency_penalty: the executor has no equivalent, and there is no JSON mode on this gateway.
Tools and caching
These are read on chat/completions for text models, in the OpenAI shape. On the Messages route the native equivalents travel untouched in the body, as the client library builds them. A whole tool round, as JSON
Tools
- Key:
tools - Optional, array of function declarations
- Default: none
Each entry is {type: "function", function: {…}} with a name, an optional description and a JSON-schema parameters object; a tool declared without a schema gets an empty object schema. The model answers with tool_calls and finish_reason: "tool_calls" when it wants one run; you run it and post the result back as a message with role: tool. An entry that is not a function, or a function without a name, is 400 naming its position.
Tool choice
- Key:
tool_choice - Optional, string or object
- Default: auto
auto lets the model decide, none forbids tool use for this turn, required demands one, and {type: "function", function: {name}} names the one to call. Any other value is 400. Sent without tools it is dropped rather than refused: many clients send tool_choice: "auto" by default, and refusing on it would close the door to those that asked for no tools at all.
Parallel tool calls
- Key:
parallel_tool_calls - Optional, boolean
- Default: true
false makes the model call at most one tool per turn. It is read together with tool_choice and has no meaning without tools.
Cache control
- Key:
cache_control - Optional, object
{type: "ephemeral", ttl?}, on a content part or at the top level - Default: none
Prompt caching, in both forms the OpenAI shape allows. On a text or image_url part it is an explicit cache point: everything up to that part is written to the cache on the first call and read from it on the next. At the top level of the body it is the automatic form: the point is placed on the last block of the conversation and moves forward as the conversation grows. Cache writes and reads are billed at their own rates, published per model in the catalogue, and what was read is reported in usage.prompt_tokens_details.cached_tokens. On the Messages route the same marks travel on content blocks in the native shape.
Sampling
These reach the model on both text routes: as you wrote them on POST /api/v1/messages, and translated field for field on chat/completions. Ranges below are the ones the model applies, not limits of ours; a value it will not take comes back as a 400 that repeats its explanation, so the reason is in the response rather than in a log somewhere.
Max Tokens
- Key:
max_tokens - Required on the Messages route, optional on
chat/completions; integer, 1 or above - Default: 4096 on chat/completions, none on messages
The upper limit on what the model may generate in reply. It also decides how much of your balance is held while the request runs: the reserve is taken against this ceiling and settled against the tokens actually used, so an inflated max_tokens can make a request fail on balance that would otherwise have fit. How the reserve works
The executor requires a ceiling, and the OpenAI shape does not: on chat/completions a body without one gets 4096 filled in, and the field is also read under its newer name max_completion_tokens. On the Messages route nothing is filled in, and a body without it is refused by the model with its own 400.
The ceiling of the model itself is lower than any number you can write, and asking for more than it allows is refused with its own message — max_tokens: 100000 > 64000 and the like — relayed as 400.
Temperature
- Key:
temperature - Optional, float, 0.0 and above
- Default: not set
This setting influences the variety in the model's responses. Lower values lead to more predictable and typical answers, higher ones to more diverse and less common ones. At 0 the same input gives you very nearly the same output every time.
Some models refuse this field outright. For those it is removed before the body goes upstream, on either text route, rather than answered with a 400 — and the catalogue leaves it out of that model's supported_parameters, which is where to confirm what the model you are calling accepts.
Top P
- Key:
top_p - Optional, float, 0.0 to 1.0
- Default: not set
This limits the choice to the most likely tokens whose probabilities add up to P. A lower value makes the answer more predictable; the usual setting leaves the full range of choices open.
It and temperature narrow the same choice from different ends, and turning both at once makes the result hard to reason about — pick one. The models that refuse temperature refuse this one too, and it is removed for them the same way.
Top K
- Key:
top_k - Optional, integer, 1 or above
- Default: not set
This limits the model's choice of tokens at each step to a smaller set. A value of 1 means it always takes the single most likely next token, which is as predictable as the model gets. Left unset, all the candidates stay in play. Confirm what the model you are calling accepts.
Stop Sequences
- Key:
stop, stop_sequences - Optional, string or array of strings
- Default: not set
Generation stops as soon as one of these strings appears, and the string itself is not part of the reply. Useful when you are parsing the output and a delimiter is the honest end of it.
On chat/completions the field is stop and takes a single string or an array, as in the OpenAI shape; on the Messages route it is stop_sequences, always an array. The catalogue lists it under the normalised name stop. Confirm what the model you are calling accepts.
System
- Key:
system - Optional, string or an array of content blocks
- Default: not set
The standing instruction for the whole exchange. On the Messages route it is a field of its own, not the first element of messages, and a system message pushed into the array instead is not the same thing.
On chat/completions the equivalent is a message with role: system or role: developer. A text model receives it as its standing instruction, wherever it stands in the array; an image model reads it first and puts it at the front of the prompt — framing goes ahead of what it frames. An image inside a system message is a 400 on a text model.
Image geometry
These fields belong to image models. They live at the top level of the body or inside image_config, and both places are read, because clients put them in different places and reading only one loses half the settings without a trace. Send each one once: when a field appears twice, every copy is still validated — a typo in the one that loses does not slip through — but which of the two wins is not worth relying on. Sent to a text model they are accepted and change nothing.
curl https://api.gtokens.io/api/v1/chat/completions \
-H "Authorization: Bearer sk-sl-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-image-2",
"messages": [{"role": "user", "content": "A lighthouse at dawn"}],
"size": "1080x1920",
"image_config": {"aspect_ratio": "9:16", "quality": "high"}
}'- aspect_ratio
- The shape of the frame:
16:9,9:16,9:19.5and the like, with fractional sides allowed.autohands the choice to the executor. Also accepted asaspect. - image_size, resolution
- A tier —
512,1K,2K,4K— or exact pixels, which are just as clear a request. The tier fixes the size you are delivered; the frame is billed by the output tokens it takes, and those follow its shape. - size
- Exact pixels,
1080x1920, honoured to the pixel. A tier name is accepted here too and behaves like the field above. Beyond 3840 px on a side or 8.29 megapixels (8,294,400 px) in total the request is refused with400before anything is reserved. - quality
auto,low,mediumorhigh. A request rather than a guarantee: at some sizes the executor returns its own level whatever you asked for.- output_format, output_compression
png,jpegorwebp, with a 0–100 quality for the two lossy ones. Checked before the frame is drawn, so an unsupported format costs nothing. Compression has no meaning forpngand is ignored there.- background
auto,transparentoropaque. Transparency needs a format that carries alpha, and only some models support it — for a few, the requested frame matters too. Elsewhere it is a400up front that names the model, not the frame size, with nothing billed: better than an opaque frame and a surprise.
Worth knowing
- A body that contradicts itself is refused. Exact pixels next to an aspect ratio they do not match, or next to a tier of a different class, is
400naming both fields — not a silent decision about which of your two wishes to drop. Values that agree are fine:1080x1920alongside9:16passes, because it is the same frame said twice. - An unreadable value is refused too, and the message names the field.
size,resolution,image_size,aspect_ratio,quality,output_formatandbackgroundall take a fixed set of values, and so dotool_choiceand, on a text model,role. A typo in any of them is an error rather than a shrug, because a 200 with the wrong shape sends you looking through your prompts.autois legal in every image field: it is not a typo, it is declining to ask. - On a text model, n is one or nothing. The executor produces one answer per request, and running the request twice under one identifier would mean two charges the log could not tell apart. So
n: 2is400, not a second reply — and not one reply quietly returned twice. - An unknown key is not an error, but a tier is not a shortcut either. Fields the route does not read are ignored, so a body written for another gateway still works. What a tier will not do is move your request to a different executor: asking for a larger tier changes the size you are handed, not who draws it.
What a model supports
Which of these a given model takes is answered by the catalogue, not by this page. Each record in GET /api/v1/models carries supported_parameters: for text models max_tokens, temperature, top_p, top_k, stop, tools and tool_choice — minus the sampling fields on models that refuse them; for image models the list is empty, and for anything not served yet it is empty as well. default_parameters is always {} — there are no values of ours waiting behind your request, apart from the max_tokens ceiling named above.
The same record carries pricing, the per-token rates the charge is computed from — input, output, cache write and cache read for text models; for an image model, pricing.prompt for the input and pricing.image_output for the frame — and nothing per size: there is no scale by area, because the count a frame takes is set by its shape more than by its area. The measured counts for the common shapes are on the guide, and usage on every reply carries the count and the charge of that request. It is worth fetching the record once rather than assuming: the rate you calculate from is the rate you are charged at.
