Images with the google-genai SDK
generateContent, with your parts array and generation config unchanged — answered by Seedream, which is what you are buying.
contents, the same generationConfig. Asking for gemini-… here is a 404 that lists what we do serve, because the alternative is you believing you bought something you did not. Tokenify is not affiliated with Google.What you change
Three things: the base URL, the key, and the model name.
from google import genai
from google.genai import types
client = genai.Client(
api_key=os.environ["TOKENIFY_API_KEY"],
http_options=types.HttpOptions(base_url="https://api.tokenify.dev"),
)
res = client.models.generate_content(
model="seedream-5.0-pro",
contents="A still life of three pears, soft window light",
config=types.GenerateContentConfig(
response_modalities=["IMAGE"],
image_config=types.ImageConfig(aspect_ratio="16:9", image_size="1K"),
),
)
for part in res.candidates[0].content.parts:
if part.inline_data:
open("out.jpg", "wb").write(part.inline_data.data)import { GoogleGenAI } from "@google/genai"
const ai = new GoogleGenAI({
apiKey: process.env.TOKENIFY_API_KEY,
httpOptions: { baseUrl: "https://api.tokenify.dev" },
})
const res = await ai.models.generateContent({
model: "seedream-5.0-pro",
contents: "A still life of three pears, soft window light",
config: { responseModalities: ["IMAGE"], imageConfig: { aspectRatio: "16:9", imageSize: "1K" } },
})The SDK reads a base URL from GOOGLE_GEMINI_BASE_URL too, so an environment variable is enough if you would rather not touch the client. The key goes in x-goog-api-key, which is where the SDK puts it; ?key= and Authorization: Bearer are accepted as well, and a header wins over a query parameter.
Model names
The path puts the model before a colon, so it cannot contain a slash — use the plain name. Both of these spellings work for each model:
| Model | Name here |
|---|---|
| Seedream 5.0 Pro | seedream-5.0-pro · seedream-5-0-pro |
| Seedream 5.0 Lite | seedream-5.0-lite · seedream-5-0-lite |
| Seedream 5.0 Flash | seedream-5.0-flash · seedream-5-0-flash |
Sizes
imageConfig.aspectRatio and imageConfig.imageSize become one exact width and height — exact because you named the shape, so the model is not left to choose a different one.
| imageSize | Here |
|---|---|
| 512 | The model's smallest tier — nothing here generates anything that small. |
| 1K | The 1K tier where the model has one, otherwise its smallest. |
| 2K | The 2K tier. |
| 4K | The 4K tier where the model has one, otherwise its largest. |
| (absent) | The model's own default. |
The ratios published for each tier on the image generation page resolve to exactly those sizes. The other ratios this SDK accepts — 1:4, 4:1, 1:8, 8:1, 4:5 and 5:4 — are computed: the tier’s own area at the ratio you asked for, each side rounded to a multiple of 16 (which is the grid every published size sits on), then clamped into the model’s pixel range. The reply tells you what you got, and the width and height we sent are what you are charged for.
A ratio outside the model’s range — beyond 1:16 either way — is a 400 rather than a squashed picture, and so is one that cannot be produced between 0.92 megapixels and 4.62 megapixels.
What carries over, and what does not
| Field | Here |
|---|---|
contents[].parts[].text | The prompt. Several text parts are joined. |
contents[].parts[].inlineData | A reference image. snake_case inline_data is accepted too. |
contents[].parts[].fileData | A reference image, if fileUri is a public https URL. Their file service URIs mean nothing here. |
generationConfig.candidateCount | How many images, within the model’s ceiling. |
generationConfig.responseModalities | IMAGE or [TEXT, IMAGE]. A text-only request is a 400 — there would be no image in the reply. |
generationConfig.imageConfig | The size and the shape. See above. |
temperature, seed, topP, topK, safetySettings, systemInstruction, thinkingConfig, tools | Accepted and ignored. None of them changes what these models generate, and refusing them would break an unchanged SDK call. |
:streamGenerateContent | A 400 for now. The reply is one document. |
A content refusal comes back the way yours does: a 200 with finishReason: "IMAGE_SAFETY" and no image part. Nothing is charged for it.
Layer decomposition is not available through this shape — it has no equivalent concept here. Use either of the other two.
Errors
google.rpc.Status, which is what the SDK raises from:
{ "error": { "code": 400, "message": "…", "status": "INVALID_ARGUMENT" } }UNAUTHENTICATED for a key problem, PERMISSION_DENIED for a key that may not use that model, NOT_FOUND for a model we do not serve, RESOURCE_EXHAUSTED for a rate limit or an empty balance, UNAVAILABLE and INTERNAL for our side.
Last updated 2026-10-12.