What we fixed in the relay: Claude Code images, fairer billing, fewer failed requests
· api, billing, claude-code, images, topxai
Claude Code image reads on non-Claude models, no charge for an image answer without an image, cache billing fixes and the requests that stopped failing.
This week we went through the relay's protocol conversion, streaming and billing code change by change, and shipped two releases on October 23. Most of the fixes are for requests that failed when a client spoke one protocol and the model another. A few change what a request costs, each time toward what actually happened. The catalogue and the price list did not change, and no client needs to change anything. The changelog lists every item; this post covers the ones you are most likely to notice.
Claude Code images on Kimi, DeepSeek, GLM and Grok
Claude Code speaks the Anthropic Messages API. When it reads a PNG or takes a screenshot, the image comes back inside a tool_result block. Kimi, DeepSeek, GLM and Grok are served through Chat Completions, where a tool message can only carry text, so until now the relay refused such a request with a 400 and the session stopped there.
Now the text of the tool result stays in the tool message and the image moves to the user message right after the tool results, which keeps parallel tool results together as Chat Completions requires. GLM-5.3-Abliterated reads text only, so it gets the note it already got on Chat Completions, "[Attachment omitted: this model accepts text only.]", for every image and document on /v1/messages. A PDF or another document inside a tool_result is still a 400 on these models; send documents to Claude or gpt-6.1-sol.
Long sessions also convert faster. To name each tool result, the conversion used to read the whole conversation again for every result, so a 400-turn Claude Code session took about a second per request before it was sent. It now takes 21 ms.
Billing changes, each toward what happened
A cached Claude stream could be charged twice for its prompt. Claude reports cached tokens separately from input_tokens, so a fully cached prompt reports zero input. When such a stream ended before its final usage, the relay took the zero as missing and added an estimate of the whole prompt at the input price, on top of the cache read. A zero now counts as missing only when the cached tokens are zero too.
Images are billed per image, and the count now follows what came back. An entry that carries only revised_prompt is not an image, and an image sent as one url entry and one base64 entry is billed once. An answer with no image at all is an error and nothing is charged, MCP call fee included. Before, such an answer was billed for the n you asked for. If a provider charges us for an attempt like that, we absorb it.
OpenAI's Responses API lets a request ask for a 12-hour prompt cache with prompt_cache_options.ttl: "12h". OpenAI prices that cache write at 2.5 times the input rate, twice its default write, while TopxAI sells one cache-write price per model. The relay therefore no longer forwards the TTL to OpenAI, as it already did for Kimi's 1-hour option: your cache writes are the default kind at the price on the model page, and cache reads are unchanged.
Requests that stopped failing
- Two consecutive assistant messages with tool calls, sent to Claude, kept only the second message's calls; the first message's tool results then had nothing to answer and Claude returned a 400.
- Responses input that ends with the model's own answer, which some clients send when they retry a stream, went to Claude as a prefill. Claude 5.x rejects a prefill, so that answer is now left out of the request.
- A Responses tool output made of content parts (text and images) reached Claude in the Responses shape and was rejected. It now arrives as text and image blocks.
- Kimi K3 can declare tools in the middle of a conversation with a system message that carries
tools. The relay dropped the member, and a latertool_choice: "required"failed for lack of tools. - On
/v1/messages, per-messageoutput_configandsafeguardsnow reach Claude, and everyanthropic-betaheader line is forwarded instead of the first one only. - When a Chat upstream sent the last reasoning token and the first answer text in one chunk, a
/v1/messagesclient lost that text.
Slow connections and large events
Every write to a stream used to get a fixed 30 seconds. A generated 2K image arrives as one event of several megabytes, so a connection slower than about 100 KB/s could not take it in time: the write failed, the client saw a broken stream, and the request had already been charged. Each stream now gets time in proportion to what it has been sent, as if read at 8 KiB/s, plus 30 seconds, up to 30 minutes for one write. A client that stops reading is still cut off.
Security
The read-only key endpoints /api/usage/token/ and /api/log/token now apply a key's IP allow list, as /v1 already did. The relay is built with Go 1.26.10, which carries the standard library's TLS, certificate and HTTP fixes since 1.26.6. When the relay follows a redirect from https to plain http, for example while downloading a generated video, it no longer sends credentials along, and a redirect to another host no longer carries the provider key header.
What changes for you
Usually nothing. If a key has an IP allow list, its usage and log endpoints now refuse addresses outside it, as /v1 does. If you set prompt_cache_options.ttl for GPT models, your writes are now the default kind. And on GLM-5.3-Abliterated, /v1/messages requests with images now succeed with the note instead of failing with a 400.
Related
- Changelog
- Claude Code with TopxAI: ANTHROPIC_BASE_URL and the key
- How requests are billed
- Image generation
- API reference: every endpoint, with an OpenAPI specification
- Jev 1.14 on TopxAI: images, Box questions and what they cost
- Moving to Claude Sonnet 5.5 on TopxAI
- Moving to GPT-6.1 Sol on TopxAI
Other languages: 简体中文