Measured endpoints

Limits and billing

  • Submissions must be >2s apart. Concurrent background jobs: Tier 1 = 8, Tier 2 = 10, Tier 3 = 20.
  • Failed generations are not billed. Two failed forced-palette attempts moved the balance by zero, so probing an unfamiliar parameter is free.
  • Billing lags slightly. A bitforge call read as cost 0 immediately after and 1 a few seconds later. Trust the usage field in the response over an immediate balance diff. Confirmed again below: a standard character base's balance read unchanged immediately after getCharacter reported it complete.
  • usage is returned inline by the synchronous endpoints: {"type":"generations","generations":1}.
  • GET /background-jobs/{id} carries usage on a completed job, at the top level and duplicated at last_response.billing_usage (same shape, plus billing_charged: true on the nested copy); confirmed live for a map object (1 generation, matched a balance diff taken later) and a standard character base (1 generation). The resource-level GET does not: neither GET /objects/{id}/GET /map-objects/{id} nor GET /characters/{id} carries usage once the job is gone, completed or not. poll reads the object/character for the result but would need this same job id, still polled separately today, to learn what it actually cost.
  • A v3 character base once recorded billed: {amount: 0, unit: "usd"} against an estimate of 2 generations while the submit reported 1 generation consumed. Its job record had expired before the shape could be read (jobs are deleted 8 hours after finishing, and the lock had dropped the job id), so the real usage is unseen; GET /characters/{id} carries none. poll now treats a dollar-denominated zero as not known, tries last_response.billing_usage, and keeps backgroundJobId in the character's lock metadata.
  • inpaint-v3 and edit-images-v2 both confirmed live at their floor size: a 32×32 masked inpaint and a separate 32×32 whole-image edit each billed exactly 20 generations, both matching a balance diff taken after. inpaint-v3's last_response is {seed, type, image: {type, base64, width, height}, action, progress, billing_usage, generation_id, usage_cost_usd, billing_charged, generation_mode: "inpainting_v3", quantized_image: {type, base64, width, height}, generation_started_at, original_image_n_colors, quantized_image_n_colors}. edit-images-v2's is the same shape except plural and array-wrapped: images: [{type, base64, width, height}] and quantized_images: [...] in place of image/quantized_image, and generation_mode: "edit_images" — the request's own edit_images array (which supports several images per call) carries through to the response even though pixelkiln always sends and reads exactly one. In both, the first field is the edited result and the quantized one an automatically color-reduced copy pixelkiln does not read. inpaint-v3 is now also confirmed at the opposite end: a live 512×512 masked inpaint (the maximum the endpoint accepts) billed exactly 40 generations, response shape identical in structure to the 32×32 case. Seven more points, bisecting from both sides, disprove the borrowed tiering's breakpoints entirely and tightly bracket inpaint-v3's real 20/25/40 structure: 40×40 (1600px²), 128×128 (16384px²), and 256×256 (65536px²) all billed 20 (the borrowed model predicts 25 for 1600px²); 288×288 (82944px²) and 320×320 (102400px²) both billed 25 — the middle tier is real, just starting far higher than the borrowed model's 1024px²; 352×352 (123904px²) and 384×384 (147456px²) both billed 40. Nine points now read 1024px²→20, 1600px²→20, 16384px²→20, 65536px²→20, 82944px²→25, 102400px²→25, 123904px²→40, 147456px²→40, 262144px²→40. Both breakpoints are now tightly bracketed: 20→25 sits somewhere in (65536px², 82944px²] — a 1.27x range — and 25→40 somewhere in (102400px², 123904px²] — a 1.21x range. Every point at or above 2048px² falls into generationCost's else branch and returns 40 regardless: correct for 352×352, 384×384, and 512×512, but a full-tier overestimate for 288×288 and 320×320 (predicts 40, actually 25) — still safe (never bills more than predicted), just a larger miss than the earlier single-tier gap. edit-images-v2 beyond its own 32×32 floor remains completely unmeasured.