Image generation
One POST, the OpenAI Images shape, billed per image rather than per token. The reply is base64.
The call
POST https://api.tokenify.dev/v1/images/generations, with the same body the OpenAI Images API takes. The OpenAI SDK reaches it through client.images.generate with nothing changed but the base URL.
import base64
from openai import OpenAI
client = OpenAI(
base_url="https://api.tokenify.dev/v1",
api_key=os.environ["TOKENIFY_API_KEY"],
)
res = client.images.generate(
model="bytedance/seedream-5.0-pro",
prompt="A still life of three pears, soft window light",
size="1024x1024",
)
open("out.jpg", "wb").write(base64.b64decode(res.data[0].b64_json))Or without an SDK:
curl https://api.tokenify.dev/v1/images/generations \
-H "Authorization: Bearer $TOKENIFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "bytedance/seedream-5.0-pro",
"prompt": "A still life of three pears, soft window light",
"size": "1024x1024"
}' | jq -r '.data[0].b64_json' | base64 -d > out.jpgdata[0].b64_json by default. Ask for response_format: "url" and you get a link to storage of ours instead — we keep the image for 7 days and serve it. What you never get is a link to whoever generated it: their reply carries one, and we do not pass it on.Bytes or a link
response_format takes b64_json (the default) or url. The choice is about where the image lives, not about what it costs: both are the same request and the same charge.
| response_format | You get | Kept for |
|---|---|---|
b64_json | The bytes, in data[0].b64_json. About 130 KB of base64 for a 1024×1024 JPEG. | Nothing is stored. |
url | A link in data[0].url, to storage we control. The host is ours to change and the link may carry a signature — fetch it, do not parse it. | 7 days, then it stops working. |
After 7 days the object is deleted and the link stops working. Depending on where it is served from that is our image_expired error, a plain 404, or an expired signature — treat any non-200 on an image URL as “gone” rather than parsing it. Store the bytes if you need them for longer; re-fetching an old link is not a plan.
The link needs no API key, so it can go straight into an <img> tag or be handed to somebody else. That is the trade: it is unguessable — 160 bits of randomness, carrying nothing about your account, the model or the prompt — but anyone holding it can fetch the image until it expires. If that is not what you want, take the bytes and store them yourself.
What it costs
Per image, not per token. These models have no per-token price and no context window; the token counts their vendor reports are metering, and the charge is the price below times the number of images produced. A request that produces nothing is not charged.
| Model | Per image | Larger images | Image you send |
|---|---|---|---|
bytedance/seedream-5.0-pro | $0.036 | $0.072 from 2.61 megapixels | $0.0024 after the first 1 |
bytedance/seedream-5.0-lite | $0.028 | Same price | Free |
bytedance/seedream-5.0-flash | $0.018 | Same price | Free |
Where a model charges two prices, the size decides which one applies: at or above the pixel count in the table it is the dearer rate, below it the cheaper one. Every size we accept sits clear of that boundary rather than on it, so the price of a size is never ambiguous.
Credit is reserved at the price of the image you asked for before the request is sent, and the charge is the same figure — there is no estimate to settle against and nothing to refund, which is the one way per-image billing is simpler than per-token.
The watermark
Generated images carry an “AI generated” mark in the bottom-right corner by default. That is the model vendor’s default, not ours, and it is burned into the pixels rather than written into metadata. Send watermark: false to turn it off:
curl https://api.tokenify.dev/v1/images/generations -H "Authorization: Bearer $TOKENIFY_API_KEY" -H "Content-Type: application/json" -d '{
"model": "bytedance/seedream-5.0-pro",
"prompt": "A still life of three pears, soft window light",
"size": "1024x1024",
"watermark": false
}'Measured against the live models, both ways, before this sentence was written. Whether you should is your call: some jurisdictions require generated images to be labelled, and turning the mark off does not change what the image is.
Sizes
size must be one of the sizes listed for the model. This is a whitelist, not a guideline: anything else is a 400 that names the accepted set. Omit it and you get the first size listed.
| Model | Accepted sizes |
|---|---|
bytedance/seedream-5.0-pro | 1024 × 1024, 1024 × 1536, 1536 × 1024, 1920 × 1920, 2048 × 2048 |
bytedance/seedream-5.0-lite | 1920 × 1920, 2048 × 2048 |
bytedance/seedream-5.0-flash | 1024 × 1024, 1024 × 1536, 1536 × 1024, 1920 × 1920, 2048 × 2048 |
A short list is the model’s own constraint rather than ours. Seedream 5.0 Lite refuses anything below about 3.7 megapixels upstream, so offering a smaller size would mean passing you an error from a request that could never have worked.
Editing an image
Send an image alongside the prompt and the model edits it instead of starting from nothing. Seedream 5.0 Pro accepts this.
import base64
from openai import OpenAI
client = OpenAI(
base_url="https://api.tokenify.dev/v1",
api_key=os.environ["TOKENIFY_API_KEY"],
)
with open("in.jpg", "rb") as f:
source = base64.b64encode(f.read()).decode()
res = client.images.generate(
model="bytedance/seedream-5.0-pro",
prompt="Replace the background with a plain grey studio wall",
size="1024x1024",
# `image` is not in the OpenAI SDK's own signature, so it travels in
# extra_body. One string, or a list of them for several.
extra_body={"image": f"data:image/jpeg;base64,{source}"},
)
open("out.jpg", "wb").write(base64.b64decode(res.data[0].b64_json))The image may be raw base64 or a data: URL, and image takes either one or a list of them. The first image you send is free; the per-image charge in the table above applies to the rest, and usage.input_images on the reply says how many were counted.
One image per request
Every other parameter is ignored rather than forwarded. The request we send is built from an allow-list — the model, your prompt, the image you are editing, the size, the response format and watermark — so a field this page does not mention reaches nobody. That is deliberate: this endpoint bills per image, and a parameter we have not measured is a parameter that could change how many come back.
Each request produces one image. n above 1 is a 400, and so is any form of batched or sequential generation — the parameters that ask for several images in one call are refused rather than accepted and billed as several. For ten images, make ten requests; they are independent and can run in parallel up to your key’s image rate.
The reply
{
"created": 1760000000,
"model": "bytedance/seedream-5.0-pro",
// "b64_json" by default; with response_format: "url" this is
// { "url": "https://api.tokenify.dev/v1/img/…", "size": "1024x1024", "output_format": "jpeg" }
"data": [{ "b64_json": "...", "size": "1024x1024", "output_format": "jpeg" }],
"usage": {
"generated_images": 1,
"input_images": 0,
// Reported by the model vendor. Not what you are charged on — the charge is
// per image — and shown here only because it is what they meter.
"output_tokens": 4096,
"total_tokens": 4096
}
}generated_images and input_images are the two numbers the charge is computed from, and the same two appear against the request in your activity log, beside what it cost.
Limits and timing
| Limit | Default | On exceeding |
|---|---|---|
| Images per minute | 60 | 429 ipm_exceeded, with Retry-After |
| Requests per minute | 600 | 429 rpm_exceeded |
| Images per request | 1 | 400, not a cap |
The image models are limited in images rather than in tokens, because tokens per image move with the size the caller asks for — one token budget would buy a different number of pictures at every size, which is not a limit anybody can plan against. The token ceiling on your key is not consulted for these models, and the image ceiling is not consulted for the chat ones. Rate limits has the rest.
Errors
| Status | Code | What it means |
|---|---|---|
| 400 | invalid_size | The size is not one this model accepts. The message lists the ones it does. |
| 400 | invalid_request | No prompt, or a parameter that would bill more than one image. |
| 400 | wrong_endpoint | A chat model sent here, or an image model sent to /v1/chat/completions. |
| 404 | image_expired | A stored image that has passed its 7 days, or a link that was never ours. |
| 502 | image_store_failed | We generated the image and could not store it. Not charged. |
| 402 | insufficient_credit | The price of the image is above your balance. Nothing was sent upstream. |
| 429 | ipm_exceeded | Above your key’s images a minute. Retry-After says when. |
| 502 | upstream_unavailable | The model refused or failed. Not charged. |
Where to go next
- Seedream 5.0 Pro — the price, the sizes and a worked example.
- Seedream 5.0 Lite — the price, the sizes and a worked example.
- Seedream 5.0 Flash — the price, the sizes and a worked example.
Last updated 2026-10-09.