Tokenify

Images with the OpenAI SDK

images.generate and images.edit, unchanged, against our base URL — and the handful of places we answer differently.

What you change

Two lines: the base URL and the key. Then a model id from the catalogue, because we do not serve OpenAI’s image models and will not pretend to — a request for one is a 404 listing what we do have, rather than something else quietly generating the picture.

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="2K",
)
open("out.jpg", "wb").write(base64.b64decode(res.data[0].b64_json))
import OpenAI from "openai"

const client = new OpenAI({
baseURL: "https://api.tokenify.dev/v1",
apiKey: process.env.TOKENIFY_API_KEY,
})

const res = await client.images.generate({
model: "bytedance/seedream-5.0-pro",
prompt: "A still life of three pears, soft window light",
size: "2K",
})
writeFileSync("out.jpg", Buffer.from(res.data[0].b64_json, "base64"))
The SDK sends no response_format unless you set one, and we answer base64 when it is absent — which is what the SDK expects, so res.data[0].b64_json is populated with no changes on your side.

Editing

client.images.edit(...) posts multipart form data with the image as a file part, and POST https://api.tokenify.dev/v1/images/edits takes it. Several images go as image[], which is what both SDKs send for a list, and they keep the order you gave them.

res = client.images.edit(
model="bytedance/seedream-5.0-pro",
image=[open("subject.png", "rb"), open("style.png", "rb")],
prompt="Put the subject in the style of the second image",
size="2K",
)

There is no separate editing model: these models edit by being handed the image alongside the prompt, so an edit is a generation with references. That also means the reference images are charged the way references are charged — see the price table.

Where we answer differently

OpenAIHereWhy
mask400These models have no masked editing. Ignoring a mask would repaint the whole picture and bill for it, so it is refused by name.
response_formatHonouredOpenAI's image models always return base64 and ignore this field. Ours takes url as well, and the link is ours, kept 7 days.
n1 to 4OpenAI allows up to 10. Above what the model makes is a 400 rather than a clamp, because n decides what you are charged.
sizeTiers or a rangeDifferent models, different sizes. A tier name lets the model pick the shape from your prompt; the range is per model and published in /v1/models.
qualityAccepted, ignoredNothing here varies by quality setting, so it is accepted to keep the SDK call unchanged and listed as ignored rather than pretended.
stream400Not yet. The reply is one JSON document; a request for a stream used to produce a bill and no image.
backgroundOn some modelsTransparent backgrounds need a model that does them and output_format: "png". On a model without it, a 400 rather than an opaque image at full price.
Unknown fields400Named in error.param. A parameter dropped on the floor is a request that cost what you expected and did something else.

The full lists — what works, what is accepted and ignored, what is refused — are on the image generation page.

Errors

The envelope is OpenAI’s, so the SDK’s own exception types work unchanged: {"error": {"message", "type", "code", "param"}}. param names the field when there is one, which is what an SDK surfaces in the exception message. Statuses and codes are on the errors page.

We never forward the model vendor’s error body. Theirs names them, and on these models it says “RPM limit exceeded” for a model that simply does not serve a request — which would send you looking for a rate limit you are nowhere near.

Last updated 2026-10-12.