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
usagefield in the response over an immediate balance diff. Confirmed again below: a standard character base's balance read unchanged immediately aftergetCharacterreported it complete. usageis returned inline by the synchronous endpoints:{"type":"generations","generations":1}.GET /background-jobs/{id}carriesusageon a completed job, at the top level and duplicated atlast_response.billing_usage(same shape, plusbilling_charged: trueon the nested copy); confirmed live for amapobject (1 generation, matched a balance diff taken later) and a standard character base (1 generation). The resource-level GET does not: neitherGET /objects/{id}/GET /map-objects/{id}norGET /characters/{id}carriesusageonce the job is gone, completed or not.pollreads 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 realusageis unseen;GET /characters/{id}carries none.pollnow treats a dollar-denominated zero as not known, trieslast_response.billing_usage, and keepsbackgroundJobIdin the character's lock metadata. inpaint-v3andedit-images-v2both 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'slast_responseis{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}]andquantized_images: [...]in place ofimage/quantized_image, andgeneration_mode: "edit_images"— the request's ownedit_imagesarray (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-v3is 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 bracketinpaint-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 intogenerationCost'selsebranch 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-v2beyond its own 32×32 floor remains completely unmeasured.
