Tokenify

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.jpg
The image comes back as base64 in data[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_formatYou getKept for
b64_jsonThe bytes, in data[0].b64_json. About 130 KB of base64 for a 1024×1024 JPEG.Nothing is stored.
urlA 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.

We never return the model vendor’s own storage URL. Their reply carries one; its host, its path and the access key in its query would each tell you who served the request, so the gateway asks them for bytes on every call and publishes its own link or none at all.

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.

ModelPer imageLarger imagesImage 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.028Same priceFree
bytedance/seedream-5.0-flash$0.018Same priceFree

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.

ModelAccepted sizes
bytedance/seedream-5.0-pro1024 × 1024, 1024 × 1536, 1536 × 1024, 1920 × 1920, 2048 × 2048
bytedance/seedream-5.0-lite1920 × 1920, 2048 × 2048
bytedance/seedream-5.0-flash1024 × 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

LimitDefaultOn exceeding
Images per minute60429 ipm_exceeded, with Retry-After
Requests per minute600429 rpm_exceeded
Images per request1400, 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.

Generation takes seconds, not milliseconds — measured through this gateway, between about ten seconds and a minute depending on the model and the size, with the largest sizes on the slowest model at the top of that range. There is no streaming: the reply arrives when the image does, so set a client timeout above a minute.

Errors

StatusCodeWhat it means
400invalid_sizeThe size is not one this model accepts. The message lists the ones it does.
400invalid_requestNo prompt, or a parameter that would bill more than one image.
400wrong_endpointA chat model sent here, or an image model sent to /v1/chat/completions.
404image_expiredA stored image that has passed its 7 days, or a link that was never ours.
502image_store_failedWe generated the image and could not store it. Not charged.
402insufficient_creditThe price of the image is above your balance. Nothing was sent upstream.
429ipm_exceededAbove your key’s images a minute. Retry-After says when.
502upstream_unavailableThe model refused or failed. Not charged.

Where to go next

Last updated 2026-10-09.