API Reference
List models
The whole catalogue in one response: what each model is called, what it accepts, what it costs and whether it is served yet.
GET/api/v1/models
The route takes no parameters. There is no paging, no search, no filter and no sort order to choose: the array holds every entry of the catalogue, ordered by id, and a client that wants a subset selects it itself. That makes the response safe to cache and to diff — the same catalogue always comes back in the same order.
curl https://api.gtokens.io/api/v1/modelsimport requests
models = requests.get("https://api.gtokens.io/api/v1/models", timeout=30).json()["data"]
# Everything the gateway serves right now, cheapest input first.
served = [m for m in models if m["status"] == "available"]
served.sort(key=lambda m: float(m["pricing"]["prompt"]))const r = await fetch('https://api.gtokens.io/api/v1/models')
const { data } = await r.json()
// output_modalities is the field to branch on: ["image"] draws, ["text"] writes.
const drawing = data.filter((m: any) => m.architecture.output_modalities.includes('image'))Alone among the routes in this reference, this one authenticates nothing. A key may be sent and is ignored, so a 200 from here says nothing about whether your key is valid, revoked or funded — a wrong key gets the same catalogue as a right one. The route that answers that question is GET /api/v1/key, which reads the header and returns the spend and the remaining balance.
Response
One object with one key — data, an array of model records. Every field below is present on every record, whether the model writes or draws: an image model is described by the same keys, and the difference is in what its rates count.
| Field | Type | Description |
|---|---|---|
| id | string | The catalogue identifier, in the form vendor/model. This is the value to put in model on a request; nothing else names a model. |
| canonical_slug | string | Always the same value as id. There are no aliases and no moving pointers, so one record never stands for another. |
| name | string | Display name, for your own interface. It is not routable — sending it as the model identifier fails. |
| created | integer | Unix seconds: when this catalogue entry was created here. Not a release date and not a training cutoff. |
| status | string | available or coming_soon. Our own field, described in the next section. |
| context_length | integer | null | Largest context the model accepts, in tokens. null means the model is not measured in tokens at all — an image model has no context window — or that the entry is not served yet. It never means zero. |
| architecture | object | modality (for example text+image->text), input_modalities and output_modalities. Branch on output_modalities: ["image"] is a model that draws, ["text"] one that writes. Entries that are not served yet claim text input only. |
| pricing | object | Rates as decimal strings, per token. Its own section below — the unit is the part that is easy to get wrong. |
| top_provider | object | context_length repeats the field above, max_completion_tokens is null because we impose no ceiling of our own, and is_moderated is true: content filtering happens upstream, and a refusal on those grounds arrives from there. |
| per_request_limits | null | Always null. Nothing is capped per request; what bounds a request is the balance and the ceilings on the account. |
| supported_parameters | string[] | Which request parameters reach the model: max_tokens, temperature, top_p, top_k, stop, tools and tool_choice on text models — minus the sampling fields on models that refuse them, which is why the record above lacks temperature. Empty on image models, whose geometry is set by fields of its own, and empty on anything not served yet. |
| default_parameters | object | Always empty: nothing is added to a request that you did not send yourself. |
Scroll the table sideways for the description.
available and coming_soon
The catalogue is wider than what runs today, and the record says which is which. available means the model answers now. coming_soon means it is listed and not reachable: the entry carries zeros in pricing, an empty supported_parameters and null for the context length. A zero there is "not priced yet", not "free".
The model you name is the model that answers, or the request is refused. Naming an entry that is listed but not served comes back 400, with error.metadata.error_type set to invalid_request and error.metadata.reason to model_not_available. An identifier that is not in the catalogue at all comes back 404. Both are refused before anything is reserved, so neither costs money.
A silent swap would be worse than either: it surfaces as a strange answer weeks later, and the search starts in your prompts instead of in the catalogue.
So the honest way to build a picker is to read the status rather than the name, and to re-read it: an entry moves to available without its identifier changing.
Pricing
Every value in pricing is the price in dollars of one token, not of a million. Multiply by the counters in usage and you have the amount, with no conversion in between. A rate that reads "0.000001" is a dollar per million tokens.
This is the one mistake on this page that passes silently: a client that assumes a per-million rate builds a bill that is off by a factor of a million while the response shape still looks correct.
Values are strings, always in plain decimal notation — never in exponent form — so a client that reads them with a regular expression does not meet 5E-7 where it expects digits. They are the rates your account is billed at. The catalogue page carries the same rates per million tokens, which is the readable form of the same number — if the two look different by six zeroes, that is the unit, not a disagreement.
| Field | Type | Description |
|---|---|---|
| prompt | string | Input tokens — the prompt and everything attached to it. |
| completion | string | Output tokens generated by the model. |
| input_cache_write | string | Writing an input token into the cache. |
| input_cache_read | string | Reading an input token back from the cache. |
| image | string | Price of one input image — a reference you attach. It is prompt multiplied by the 2,900 input tokens one attachment is counted as, which is why the same prompt with six references costs visibly more than the same prompt with none. |
| image_output | string | Rate per output token on a model that draws: the frame itself is output, and its token count depends on the shape of the frame. Equal to completion on such a model; "0" on models that do not draw. See the next section. |
| request | string | Always "0": there is no fee for the fact of a request. The key is present anyway, because a client summing prompt, completion and request would otherwise trip over a missing one. |
Scroll the table sideways for the description.
Image models
A model that draws is priced with the same object and the same unit. There is no price per frame and no scale by area: the prompt and every attached image are input at prompt, the frame is output at image_output, and the amount is the two counters of usage on the reply multiplied by those two rates, to within rounding in the sixth decimal — the same arithmetic a text model gets. One floor sits underneath it: a frame is never billed below what rendering it cost plus a small margin, so the figure to reconcile against is usage.cost rather than a product you compute yourself. The pricing block of an image model, from the live catalogue at one moment:
"pricing": {
"prompt": "0.0000084",
"completion": "0.0000315",
"input_cache_write": "0",
"input_cache_read": "0",
"image": "0.02436",
"image_output": "0.0000315",
"request": "0"
}What differs is where the output count comes from. A text model produces as many tokens as it writes; an image model produces a count set by the shape of the frame it draws, not by its area — a square frame takes noticeably more tokens than an oblong one of the same size, and orientation makes no difference. So the price of a frame cannot be read off the catalogue alone: the catalogue gives the rate, the reply gives the count in usage.completion_tokens, and usage.cost is what was charged. Measured counts for the common frame shapes, with what they come to at the current rate, are on the guide.
Example response
Abridged to a single record — the real response carries the whole catalogue. A field with nothing in it arrives as null rather than being dropped, so a record can be read by key without checking that the key exists.
{
"data": [
{
"id": "anthropic/claude-sonnet-5",
"canonical_slug": "anthropic/claude-sonnet-5",
"name": "Claude Sonnet 5",
"created": 1788343200,
"status": "available",
"context_length": 200000,
"architecture": {
"modality": "text+image->text",
"input_modalities": ["text", "image"],
"output_modalities": ["text"]
},
"pricing": {
"prompt": "0.000001",
"completion": "0.000005",
"input_cache_write": "0.00000125",
"input_cache_read": "0.0000001",
"image": "0.0029",
"image_output": "0",
"request": "0"
},
"top_provider": {
"context_length": 200000,
"max_completion_tokens": null,
"is_moderated": true
},
"per_request_limits": null,
"supported_parameters": ["max_tokens", "top_k", "stop", "tools", "tool_choice"],
"default_parameters": {}
}
]
}Read together, the record answers the four questions asked before a request is sent: what to put in model, whether the model draws or writes, how much context it takes, and what a token of it costs.
Errors
There is nothing here to authenticate and nothing to validate, so this route either answers 200 or fails on our side: a 500 in the shared error envelope means the gateway itself broke, and retrying is reasonable. The refusals that mention a model — 400 for an entry that is listed but not served, 404 for an identifier that is not in the catalogue — come from the routes that execute a request, not from this one.
