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's max_prompt_chars says so. @handle attaches the photos of the cast member with that handle, as in the studio.
  • model (string): a model id. Default openai_medium.
  • size (string): 1K, 2K or 4K, the resolution, where larger costs more. The exact pixels depend on the model and the shape. Default 1K.
  • aspect_ratio (string): 1:1, 16:9, 4:3, 3:4 or 9:16, or auto for the shape of the first reference. Default 1:1.
  • format (string): image, gif for an animated GIF, or svg for a vector drawing. Default image. GIFs and SVGs come from the openai_ models and need a set shape, not auto. An SVG is 1K and takes no references.
  • frames (integer): only for a GIF, which needs it: 8, 12 or 16.
  • frame_ms (integer): only for a GIF, which needs it: how long each frame shows, 100, 200 or 400 milliseconds.
  • references (array of strings): images to work from, each a data URL such as data:image/png;base64,iVBORw0... of a PNG, JPEG or WebP under 20 MB. A model takes at most its max_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.json

The 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, failed or cancelled. The last three are final.
  • prompt, model, size, aspect_ratio, format, frames, frame_ms: as requested. frames and frame_ms are null unless the format is gif.
  • reference_count (integer): how many references you sent.
  • created_at (string): when it was created.
  • images (array): its finished images, each with id, url, content_type, width and height. Rely on status to 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 costs 0.
  • error (object or null): set when failed, with a code and a message. The code declined means the request itself was refused, by the model's content rules or a limit, so the same request fails again. The code failed means 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 429 with Retry-After.
  • 40 tasks running at once per account, studio work included. Past that, a create answers 429 until 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)