Peckney API
Generate images, animated GIFs and SVG vectors from your own code. Every result lands in your Peckney library and is paid from your balance.
The base URL is https://peckney.com/v1. Requests and answers are JSON, times are UTC in ISO 8601, and money is in whole micro dollars: 1,000,000 micros make $1.00. AI agents can read this page as plain Markdown at https://peckney.com/docs/api.md.
Quick start
1. Create a key. Open Settings and choose Create key under API keys. Copy the key at once, because it shows only once.
2. Start a generation.
export PECKNEY_API_KEY="peck_..."
curl https://peckney.com/v1/generations \
-H "Authorization: Bearer $PECKNEY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "A ceramic espresso cup on an oak table, morning light", "model": "klein"}'It answers 202 Accepted at once with the new generation and its id, while the image renders in the background.
3. Poll it every 2 seconds, or after as many seconds as the Retry-After header says, until status is completed, failed or cancelled.
curl https://peckney.com/v1/generations/745 \
-H "Authorization: Bearer $PECKNEY_API_KEY"4. Download the image from images[0].url.
Authentication
Every request carries your key in the Authorization header:
Authorization: Bearer peck_...A key starts with peck_ and has 45 characters. It acts as your account: it spends your balance, its images land in your library and its work shows in your Tasks. Create, name and revoke keys in Settings, at most 10 per account. Keep a key on your server, away from browsers, apps and public repositories. A request without a valid key answers 401.
Models and prices
GET /v1/models lists every model with its price per image at each size, and the GIF and SVG prices of the models that make them. The prices today:
openai_low: GPT Image 2.6 (OpenAI, Low). $0.0307 at 1K, $0.056 at 2K, $0.089 at 4K. Up to 16 references. Makes GIFs and SVGs too.openai_medium: GPT Image 2.6 (OpenAI, Medium). $0.0495 at 1K, $0.0943 at 2K, $0.1528 at 4K. Up to 16 references. Makes GIFs and SVGs too.openai_high: GPT Image 2.6 (OpenAI, High). $0.0617 at 1K, $0.116 at 2K, $0.1869 at 4K. Up to 16 references. Makes GIFs and SVGs too.openai_xhigh: GPT Image 2.6 (OpenAI, Extra high). $0.1027 at 1K, $0.1993 at 2K, $0.3253 at 4K. Up to 16 references. Makes GIFs and SVGs too.openai_max: GPT Image 2.6 (OpenAI, Max). $0.2197 at 1K, $0.4372 at 2K, $0.7206 at 4K. Up to 16 references. Makes GIFs and SVGs too.google_minimal: Nano Banana 2.1 (Google, Minimal thinking). $0.0411 at 1K, $0.0579 at 2K, $0.1209 at 4K. Up to 14 references.google_medium: Nano Banana 2.1 (Google, Medium thinking). $0.0486 at 1K, $0.0654 at 2K, $0.1284 at 4K. Up to 14 references.google_high: Nano Banana 2.1 (Google, High thinking). $0.0636 at 1K, $0.0804 at 2K, $0.1434 at 4K. Up to 14 references.bfl_flux3: FLUX 3 Image (Black Forest Labs). $0.048 at 1K, $0.10 at 2K, $0.607 at 4K. Up to 10 references.gpt: GPT Image 2.6 (Kie.ai). $0.0421 at 1K, $0.0825 at 2K, $0.1351 at 4K. Up to 16 references.klein: FLUX 3 Klein (Kie.ai). $0.008 at 1K, $0.0125 at 2K, $0.0325 at 4K. Up to 10 references and 5,000 prompt characters.nano: Nano Banana Pro (Kie.ai). $0.09 at 1K, $0.09 at 2K, $0.12 at 4K. Up to 8 references.
Choose one with model. Without it, a generation uses openai_medium.
Create a generation
POST /v1/generations starts one image from your prompt. It answers 202 Accepted at once with the generation, a Location header that points to it and a Retry-After header.
prompt(string, required): what to draw. The model gets it exactly as written: unlike the studio's composer, the API never splits a prompt into several images and never adds words to it. At most 20,000 characters, or fewer where a model'smax_prompt_charssays so.@handleattaches the photos of the cast member with that handle, as in the studio.model(string): a modelid. Defaultopenai_medium.size(string):1K,2Kor4K, the resolution, where larger costs more. The exact pixels depend on the model and the shape. Default1K.aspect_ratio(string):1:1,16:9,4:3,3:4or9:16, orautofor the shape of the first reference. Default1:1.format(string):image,giffor an animated GIF, orsvgfor a vector drawing. Defaultimage. GIFs and SVGs come from theopenai_models and need a set shape, notauto. An SVG is1Kand takes no references.frames(integer): only for a GIF, which needs it:8,12or16.frame_ms(integer): only for a GIF, which needs it: how long each frame shows,100,200or400milliseconds.references(array of strings): images to work from, each a data URL such asdata:image/png;base64,iVBORw0...of a PNG, JPEG or WebP under 20 MB. A model takes at most itsmax_references, cast photos included, and the whole request must stay under 64 MB.
With a reference image, written to a file first because the encoded image is long:
printf '{"prompt": "The same cup on a marble counter", "model": "klein", "references": ["data:image/jpeg;base64,%s"]}' \
"$(base64 < cup.jpg | tr -d '\n')" > request.json
curl https://peckney.com/v1/generations \
-H "Authorization: Bearer $PECKNEY_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @request.jsonThe answer:
{
"id": 745,
"status": "queued",
"prompt": "A ceramic espresso cup on an oak table, morning light",
"model": "klein",
"size": "1K",
"aspect_ratio": "1:1",
"format": "image",
"frames": null,
"frame_ms": null,
"reference_count": 0,
"created_at": "2026-11-06T21:11:44.000Z",
"images": [],
"cost_micros": 8000,
"error": null
}Your balance pays the price when the image starts. A failed or cancelled image costs nothing, because its charge returns to your balance. When the balance cannot pay, the create answers 402.
Safe retries
To retry a create after a timeout without starting it twice, send an Idempotency-Key header with a unique value, such as a UUID. The same key with the same body answers the generation it already started, with the header Idempotent-Replayed: true. The same key with another body answers 422. A key stays tied to its generation.
The generation
id(integer): use it to poll or cancel.status(string):queued,running,completed,failedorcancelled. The last three are final.prompt,model,size,aspect_ratio,format,frames,frame_ms: as requested.framesandframe_msarenullunless the format isgif.reference_count(integer): how many references you sent.created_at(string): when it was created.images(array): its finished images, each withid,url,content_type,widthandheight. Rely onstatusto know it is done. The image lives in your library, so its URL works until you delete it there and shows the edits you make in the studio.cost_micros(integer): the price while it runs, then what you paid. A generation that ends without an image costs0.error(object or null): set whenfailed, with acodeand amessage. The codedeclinedmeans the request itself was refused, by the model's content rules or a limit, so the same request fails again. The codefailedmeans the service failed, so the same request can work later.
Get a generation
GET /v1/generations/{id} answers the generation. While it is queued or running, the answer carries Retry-After: 2, and polling faster gains nothing.
{
"id": 745,
"status": "completed",
"prompt": "A ceramic espresso cup on an oak table, morning light",
"model": "klein",
"size": "1K",
"aspect_ratio": "1:1",
"format": "image",
"frames": null,
"frame_ms": null,
"reference_count": 0,
"created_at": "2026-11-06T21:11:44.000Z",
"images": [
{
"id": 3199,
"url": "https://media.example.com/img-106403bbff80aef7.jpg",
"content_type": "image/jpeg",
"width": 1024,
"height": 1024
}
],
"cost_micros": 8000,
"error": null
}List generations
GET /v1/generations answers the generations you made through the API, newest first, as {"data": [...], "next_url": ...}. The query parameter limit takes 1 to 100, default 20, and before takes a generation id to list only older ones. next_url fetches the next, older page, and is null on the last one.
Cancel a generation
POST /v1/generations/{id}/cancel stops a queued or running generation and answers it. A queued generation is cancelled at once. A running one stops within seconds, so poll it until it is cancelled. An image it had not finished costs nothing. A final generation answers 409.
Account
GET /v1/account answers the account the key belongs to and its balance:
{
"email": "[email protected]",
"balance_micros": 4950000
}Limits
- 100 requests every 10 seconds per account. Past that, requests answer
429withRetry-After. - 40 tasks running at once per account, studio work included. Past that, a create answers
429until one finishes. - One image per generation. For several images, start several generations.
Errors
An error answers with its HTTP status and a JSON body of the type application/problem+json, as RFC 9457 defines it. Branch on code, which never changes. detail explains this case in words, and param names the field at fault when there is one.
{
"type": "https://peckney.com/docs/api#invalid_request",
"title": "Invalid request",
"status": 400,
"detail": "Model klein makes 1K, 2K, 4K, not \"8K\".",
"code": "invalid_request",
"param": "size"
}invalid_request
400 Invalid request. A field, header or query parameter is missing or wrong. param names it when one is at fault. Fix the request before sending it again.
unauthorized
401 Missing or invalid API key. The Authorization header is missing or malformed, or its key was revoked. Send Authorization: Bearer <key> with a key from Settings.
insufficient_balance
402 Insufficient balance. Your balance cannot pay for this image. Add funds, then send the request again.
forbidden
403 Account blocked. This account cannot use the API. Write to support.
not_found
404 Not found. No endpoint answers at this path, or no generation with this id belongs to your account.
method_not_allowed
405 Method not allowed. This endpoint takes another HTTP method, which the Allow header names.
conflict
409 Already finished. The generation already finished, so it cannot be cancelled.
idempotency_key_reused
422 Idempotency key reused. This Idempotency-Key already started a generation from a different request. Send a new key with a new request.
rate_limited
429 Too many requests. Your account sent more than 100 requests in 10 seconds. Wait as many seconds as Retry-After says.
too_many_active
429 Too many running. Your account already runs 40 tasks, studio work included. Wait as many seconds as Retry-After says, or until one finishes.
server_error
500 Server error. Something failed on our side and was reported. Try again in a minute.
Python example
Starts one image, polls it and saves it. It needs the requests package.
import os
import time
import uuid
import requests
API = "https://peckney.com/v1"
HEADERS = {"Authorization": "Bearer " + os.environ["PECKNEY_API_KEY"]}
def answer(response):
if not response.ok:
raise SystemExit(response.text)
return response.json()
response = requests.post(
API + "/generations",
headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
json={"prompt": "A ceramic espresso cup on an oak table, morning light", "model": "klein"},
timeout=60,
)
generation = answer(response)
while generation["status"] in ("queued", "running"):
time.sleep(int(response.headers.get("Retry-After", "2")))
response = requests.get(API + "/generations/" + str(generation["id"]), headers=HEADERS, timeout=60)
generation = answer(response)
if generation["status"] != "completed":
raise SystemExit(generation["status"] + ": " + str(generation["error"]))
image = generation["images"][0]
with open("image." + image["url"].rsplit(".", 1)[1], "wb") as file:
file.write(requests.get(image["url"], timeout=60).content)