Measured endpoints

Validation ledger

Which PixelLab endpoints PixelKiln calls, and how much is actually known about each, so live spending can be chosen by risk. Audited 5 November 2026 from src/client.ts, src/pixellab-vfx.ts, this file, the other docs, the skill references, the benchmark and showcase lockfiles, and git history. No PixelLab call was made for it. Each row is one endpoint (or one tightly coupled family of paths: a POST and the GET that polls it). The Evidence is the section named in the "What is known" cell.

Status

  • Measured live: a real call was billed and the completed response shape was seen. The cell says what was measured.
  • Run live, not measured: it ran against the account, but the bill or a variant was not recorded.
  • Mock only: wrapped and unit-tested against mocked HTTP, with no live run in the docs.
  • Schema only: wrapped from PixelLab's OpenAPI or MCP schema, with no live run, and the docs call the completed-job shape or the price a guess.
  • Not wrapped: PixelLab has it and PixelKiln does not call it.
  • Unknown: the repo does not establish a status. See Needs a human look.

Money at risk is how much a wrong estimate or an unreadable response could cost in one call: Low up to 3 generations, Medium 4 to 15, High over 15, or billed per direction or per batch. Failed requests are not billed, so a rejected body costs nothing; the exposure is an accepted job that bills more than planned or finishes in a shape PixelKiln cannot read.

target_palette is sent on 17 routes since 0.92.0. Only imagePro, imageProFlash and tiles ran live with it (each matched the plan); the other 14 are mock only (objectPro base, state and pro animation, 1dir, Pro terrain, uiAsset, uiElement, Pro and Pro Flash character bases, states, portraits, outfit transfer, interpolate, edit-animation), and their rows say so.

Free acceptance probe (November 2026): for 13 of those 14 routes, a request carrying only a valid four-colour target_palette and none of the required fields was answered with 422s that named the missing fields and nothing about target_palette, while the same request with an invalid palette (a non-hex colour, max_colors 999) was answered with two target_palette errors. So PixelLab's validator knows the field and its shape on those routes, which a mocked test cannot show. The probe cannot show that the palette is obeyed or what the call costs, and a failed validation bills nothing. The 14th route, /objects/{id}/animations (the objectPro pro animation), has no required fields, so a palette-only request could have been accepted and billed; it was not probed.

EndpointPixelKiln featureStatusWhat is known (cost / sizes / shape)Gaps (never seen)Money at risk
GET /balancedoctor, gen balance reads, galleryMeasured liveFree. Count is at subscription.generations (Reproducing these numbers).Only the subscription shape is documented; a credit-only account's reply is not recorded.Low
GET /pro-flash/costPro Flash estimates for character and imageProFlashMeasured liveFree quote. For operation=character: image 5 up to 96px, 6 to 208px, 9 above, plus rotations; two live jobs (7 and 2) billed exactly the quote (Characters, measured).Quotes for create, edit and inpaint are recorded from PixelLab's MCP get_pro_flash_capabilities, not from this route (roadmap, "Pro Flash for plain image create, edit, and inpaint").Low
GET /background-jobs/{id}every job poll; where the billed amount is readMeasured liveusage at the top level and again at last_response.billing_usage; confirmed for a map object (1), a standard character base (1) and the skeleton jobs (Limits and billing).Jobs are deleted 8 hours after finishing, so a bill not read in time is lost (the v3 base bill was).Low
POST /create-image-pixfluxpixfluxMeasured live1 generation, PNG inline, about 10 s; color_image is a hard palette lock (130 badges, 65 of 65 in each set) (Single-image generators, measured). Ran in benchmarks/provider-environments/pixellab.None recorded.Low
POST /map-objects (+ GET /map-objects/{id}, falling back to GET /objects)mapMeasured live1 generation, job about 30 s; the job record expires after 8 hours but the image survives (Object lifecycle). color_image returns 500.The scene field (background_image + inpainting): "Unverified until a real call is billed" (GENERATORS.md, map).Low
POST /generate-image-v2imageProMeasured liveA 64×64 call billed 10 (40 before the cut) and 10 again with target_palette; the completed job was read and a candidate downloaded through gen (October 2026 Pro price cut, Target palette).Canvases above 65536px² (15 and 25 tiers) are priced from the shared Pro ladder, not measured here; reference_images and style_image are unmodeled.High
POST /create-image-pro-flashimageProFlashMeasured liveA 32×32 still with a four-colour target_palette billed 5, in only those colours (November 2026; GENERATORS.md).Larger canvases (quote 9 at 256px) and the style-image path were never billed.Medium
POST /edit-image-pro-flashrevision engine: "pro-flash", image-to-imageMock onlyQuote 5 at 64px, provisional, from MCP (roadmap, "Pro Flash for plain image create, edit, and inpaint").Never billed; completed-job shape never read live; the reference edit method is unmodeled.Medium
POST /inpaint-image-pro-flashrevision engine: "pro-flash", inpaintMock onlyQuotes 5 at 64px and 6 at 128px, provisional, from MCP (same roadmap entry).Never billed; completed-job shape never read live; context_image and output_methods unmodeled.Medium
POST /image-to-pixelart-pro-flashpixelkiln pixelateMeasured liveA 256×256 and a 384×384 render each billed 6; the image is last_response.images[0]; the result is opaque on a near-white field (Image to Pixel Art Pro Flash, measured).Whether 6 is flat above 384px or with a larger source.Medium
POST /inpaint-v3revision inpaintMeasured liveNine pre-cut points from 32×32 to 512×512 (20 / 25 / 40); after the cut 256, 288, 352 and 384px billed 10, 15, 25, 25. last_response.image is a single object (Limits and billing, October 2026 Pro price cut).The 20-to-25 break sits in (65536px², 82944px²] and 25-to-40 in (102400px², 123904px²]; 32px to 128px and 512px were not re-measured after the cut.High
POST /edit-images-v2revision image-to-imageMeasured liveOne 32×32 call billed 20 (before the cut); images is an array of {type, base64, width, height} (Limits and billing).Nothing above 32×32 and nothing after the cut; the plan borrows proTierCost.High
POST /remove-backgroundrevision remove-backgroundMeasured live1 generation, synchronous {usage, image}; keeps the palette (Post-processing utilities, measured). Measured as a loose call in the July 2026 reference.The complex task and the foreground text hint (roadmap, "Remove background").Low
POST /reduce-colorsrevision reduce-colorsMeasured live0.1 generations at 32×32, balance diff and usage agree (Cleanup tier, measured, September 2026).Larger canvases; the price of a multi-frame set call is unmeasured.Low
POST /correct-pixelartrevision correct-pixelartMeasured live0.1 generations at 32×32 (Cleanup tier, measured).Larger canvases and multi-frame sets, as above.Low
POST /unzoompixelkiln unzoomUnknown"Cost unmeasured; PixelLab's MCP tool description claims 0.1" (Cleanup tier, measured). Nothing records a live run.Everything.Low
POST /normal-maprevision normal-map, pixelkiln normal-mapMeasured liveAn 8-frame 1304×26 strip and a 64×64 revision each billed 0.5; the map comes back the input's size with matching alpha (Normal map, October 2026).The directx convention; strips near the 32-frame limit.Low
POST /generate-ui-v2uiElementSchema onlyDocumented 10 to 25; "no call has been billed yet" (GENERATORS.md). Planned on the 10/15/25 ladder. target_palette sent, mock only.The price, and the completed-job shape (pollUiElement reads it defensively); the concept-image path.High
POST /create-1-direction-object (+ GET /objects/{id})1dirMeasured liveA 32px, 3-description batch billed 20 (the pre-cut floor) and item_descriptions[0] owned slot 0 (Candidates); candidate counts follow size. target_palette sent, mock only.No bill since the October 2026 cut (10 / 15 / 25 is PixelLab's published ladder); tiers above the floor under item_descriptions.High
POST /objects/{id}/select-frames1dir and batch pickRun live, not measuredPromoted three candidates in the live batch run (Candidates); indices is plural.Its cost was not recorded.Low
GET /objects?limit=&offset=adopt, salvageMeasured liveFree; a March 2026 sprite still resolved four months on (Object lifecycle).None recorded.Low
POST /create-object-pro-flashobjectPro baseMock onlyAssumed to price like character pro-flash: 6 at 64px, 1 from a reference (GENERATORS.md, "Cost is not independently measured"). target_palette sent, mock only.Price, completed-job shape, 8-direction output.Medium
POST /objects/{id}/statesobjectPro stateMock onlySame assumption. target_palette sent, mock only.Price and shape.Medium
POST /objects/{id}/animationsobjectPro animation (v3, pro)Mock onlySame assumption; pro mode is documented 10 to 25 per direction. target_palette sent on pro only, mock only.Price, shape, per-direction billing.High
PATCH /objects/{id}/tags, PATCH /characters/{id}/tagstagging in fetch, adopt, salvageUnknownOnly the negative is documented: pixflux, pixen and bitforge results "cannot be tagged" (Object lifecycle).A recorded live call.Low
DELETE /objects/{id}, POST /objects/{id}/dismiss-review, DELETE /characters/{id}salvage, account cleanupUnknownNot discussed in the docs.A recorded live call.Low
DELETE /characters/{id}/animationsreplacing a loop's directionUnknown"Deleting one direction of a shared group has not been run live" (Skeleton-driven motion, measured).Whether the rest of the group keeps its motion.Low
POST /create-tiles-pro (+ GET /tiles-pro/{id})tilesMeasured liveOne numbered description, 4 tiles at 16px, with a Game Boy target_palette, billed 10, every candidate in the palette (November 2026). GET has no status: 423 while drawing (Tilesets: measured once per kind).tile_feature sets (roads, tileset, building) and tile_rules; large tile-size × variation canvases that reach the top tier.High
POST /create-tileset (+ GET /tilesets/{id})terrain, top-downMeasured livePro 32px billed 15 (three sets, one connected through lower_base_tile_id, one with edge slope); Pro 64px billed 25 and returned 25 tiles at transition 0.5; standard sets billed 3 each (planned 4), one connected through upper_base_tile_id (Tilesets: measured once per kind, October 2026). target_palette on Pro, mock only.Pro below 32px; standard above 32px; a standard set cannot connect to a Pro set.High
POST /create-tileset-sidescrollerterrain, terrainOrientation: "sidescroller"Measured liveOne 16px set at transition 0.25 billed 3; 16 tiles; usage is null on the GET, so the bill comes from the job (Tilesets: measured once per kind).32px tiles and other transition sizes (tile counts assumed 16); the call returned metadata.transition_size: 0.0 for 0.25.Low
POST /create-isometric-tile (+ GET /isometric-tiles/{id})isometricTileMeasured liveOne 32px block billed 1 generation, not the schema's usd: 0.02 example (Standalone isometric tile).Other sizes and shapes; flat pricing across 16 to 64px is an assumption.Low
POST /create-ui-asset (+ GET /ui-assets/{id})uiAssetMeasured liveOne 256×192 call billed 20, the low end of the quoted 20 to 40; the plan prices 40 (UI panels, September 2026).A second size; pixelkiln plan over-reads.High
POST /generate-font-pro (+ GET /generate-font-pro/{id})pixelkiln fontUnknownDocumented at 15 since the cut; "its cost is not yet measured" (Not yet explored). Nothing records a live run.Price and the non-{usage, image} response shape.Medium
POST /create-character-with-8-directions, /create-character-with-4-directionscharacter base, mode: standardMeasured live1 generation (read from the job's usage); canvas 28px larger than image_size (Limits and billing, Characters, measured, September 2026).The 4-direction body and quadruped templates are not recorded.Low
POST /create-character-v3character base, mode: v3Run live, not measuredA base once recorded billed: {amount: 0, unit: "usd"} against an estimate of 2 while the submit reported 1 generation; the job had expired, so the real bill is unseen (Limits and billing).The real bill at any size.Low (Medium at 256px, 9)
POST /create-character-procharacter base, mode: proUnknownPlanned 10 to 25 by canvas (GENERATORS.md). No bill recorded in the docs. target_palette sent, mock only.Everything.High
POST /create-character-pro-flashcharacter base, mode: pro-flashMeasured liveTwo live jobs (7 and 2, the second from a reference) billed exactly the estimate; returns exactly the requested canvas (Characters, measured, September 2026). target_palette sent, mock only.Sizes beyond 96px; source_image_id and the style-image path.High
POST /create-character-statecharacter stateRun live, not measuredA 64px state "moved the balance by about 22 against the 20 to 40 tier", with other jobs running (Characters, measured). target_palette sent, mock only.A clean bill, and any bill since the cut.High
POST /animate-character, mode template or v3character loops without skeleton-v3Run live, not measuredA v3 text loop on a 96px pro-flash base came back 108×108; template loops need a skeleton template (Characters, measured). Plan: template 1, v3 ceil(size² × frames / 65536).Billed amounts were not recorded; billed per direction.High
POST /animate-character, mode: procharacter loops, mode: proUnknownPlanned 10 to 25 by canvas (GENERATORS.md). Nothing records a live run.Everything.High
POST /characters/animations, mode: skeleton-v3character loops, mode: "skeleton-v3" (template or sentence)Measured liveOn a v3 character: a template loop billed 3; a sentence loop (no template) billed 3, 8 frames at 96×96 from a 64px character; west added with animation_group_id billed 3 and joined the group. A standard-engine character is refused with a 500 "no stored 3D skeleton" (Skeleton-driven motion, measured, October 2026).pro and pro-flash bases; other frame counts; the price per direction across 8 directions is untested.High
POST /portrait-character-pro (+ GET /portrait-character-pro/{job_id})character portraitMeasured liveA 16px character_to_portrait billed 20 (before the cut); the GET reports usage: null, so read the job (Characters, measured). target_palette sent, mock only.The price after the cut (10 to 25); larger sizes.High
POST /characters/{id}/portraitcharacter portrait attachMeasured liveFree (usage.generations: 0); the reply holds the only copy of the stored URL (Characters, measured).None recorded.Low
POST /transfer-outfit-v2character outfitMeasured liveA 2-frame 92×92 job billed 20 (before the cut); frames return inline in last_response.quantized_images (Characters, measured). target_palette sent, mock only.Other frame counts (2 to 16) and canvases; the price after the cut.High
GET /characters/{id}, GET /characters?limit=&offset=adopt, fetch --refresh, salvageMeasured liveFree; carries no usage; image URLs carry ?t=<updated_at> (Object lifecycle).None recorded.Low
POST /animate-with-text-v3revision animateSchema onlyRequest fields from the OpenAPI document; the completed-job shape is a guess (REVISIONS.md). Cost borrows character's v3 formula.Price, completed shape, frame-list field.High
POST /animate-pixminimaxrevision animate-pixminimaxSchema onlySame as above. PixelLab's MCP now prices it by time (64×64 at 4 / 8 / 16 / 40 frames: 1 / 1 / 2 / 6); the placeholder gives 1 / 1 / 1 / 3 (roadmap, "Generated effects with PixMiniMax").Price, completed shape, and whether the input frame is in the output.High
POST /animate-with-skeleton-v3revision animate-skeletonMeasured live8 frames at 64×64 billed 3 and returned 8 frames at 64×64; description and action are both required, and sent with neither the endpoint returns 422 (Skeleton-driven motion, measured, October 2026).Other frame counts (3 = 2 and 15 = 4 are PixelLab's anchors, unbilled).Low
POST /estimate-skeletonpixelkiln estimate-skeleton, gallery pose helperRun live, not measuredRan on a 64×64 sprite before the 8-frame revision (REVISIONS.md).Its cost; the reply's usage is typed unknown.Low
POST /interpolation-v2revision interpolateSchema onlyNo usage example in the schema; price borrows the 10/15/25 Pro tiers (REVISIONS.md). target_palette sent, mock only.Price, completed shape.High
POST /edit-animation-v2revision edit-animationSchema onlySame; priced on the packed frame grid (four 32×32 frames price as 64×64). target_palette sent, mock only.Price, completed shape.High
POST /vfx, lite artist, no spritepixelkiln vfx --description, vfx block descriptionMeasured liveOne effect billed 1 (balance 5104 to 5103); 163×26, 8 frames, sheet and atlas cut into 8 correct frames (VFX artist, October 2026).The files shape (list or map) is undocumented; PixelKiln reads three.Low
POST /vfx, sprite, pro artist, effect_id change or game_packsameMock onlyQuoted from the OpenAPI table: pro 6 (8 with a sprite), 14 and 28 for larger sprites; plans count twice the quote (VFX artist).Every variant above.High
MCP tools/call vfx_workbench at api.pixellab.ai/mcppixelkiln vfx, vfx block preset or recipeMeasured liveFree on a subscription; same recipe re-rendered byte-identical; exports a sheet, GIF, atlas and optional game pack (VFX workbench (MCP only), measured, October 2026)."Busy, retry" is the only recorded error; sprites by asset are not wired.Low
POST /create-image-pixen, /create-image-bitforgenoneNot wrappedEach 1 generation; bitforge returned blobs for a simple prompt (Single-image generators, measured).n/an/a
POST /rotate, /resize, /image-to-pixelartnoneNot wrappedEach 1 generation; all three break a locked palette (Post-processing utilities, measured).n/an/a
POST /inpaint, /edit-image (not v2 or v3)noneNot wrapped"Untouched by any of this" (Not yet explored).n/an/a
POST /generate-with-style-v2, /image-to-pixelart-pro, /create-8-direction-objectnoneNot wrappedStyle transfer at 20 before the cut, not re-measured; objectPro covers the 8-direction object.n/an/a
POST /animate-with-text-v2, /generate-8-rotations-v2, /tilesets, /tilesets-sidescroller, legacy /animate-with-skeletonnoneNot wrappedPixelKiln calls the /create-tileset* aliases, not /tilesets*; the legacy skeleton route takes exactly 3 frames (roadmap, "The legacy /animate-with-skeleton").n/an/a
Character lip-sync, vocal animation, talking GIF, portrait_to_characternoneNot wrappedNamed out of scope in PIXELLAB.md.n/an/a

Where a wrong guess costs money

The High and Medium rows that are Mock only, Schema only or Run live, not measured, most exposed first. A live run of the top ones also settles the most uncertainty per generation.

  1. /animate-pixminimax (Schema only). The estimate under-reads long clips (a 40-frame 64×64 clip costs 6, the placeholder says 3), so --budget can be set too low, and the completed shape is a guess.
  2. /interpolation-v2 and /edit-animation-v2 (Schema only). Priced from borrowed 10/15/25 tiers with no usage example; a whole frame set goes in one call, so a miss is a full Pro tier, and the finished job is read defensively.
  3. /animate-with-text-v3 (Schema only). Up to 16 generations a call by PixelKiln's own formula, billed per animation, completed shape guessed.
  4. /objects/{id}/animations, /create-object-pro-flash, /objects/{id}/states (Mock only). objectPro assumes character pro-flash prices; its pro animations are 10 to 25 per direction.
  5. /generate-ui-v2 (Schema only). 10 to 25, never billed, shape read defensively.
  6. /animate-character template and v3 (Run live, not measured). One job per direction, so an 8-direction loop is eight bills no one has recorded.
  7. /create-character-state (Run live, not measured). One rough bill (about 22) taken beside other jobs and before the cut.
  8. /vfx with a sprite, the pro artist, a change or a game pack (Mock only). Up to 28 generations a call, and plans reserve twice the quote.
  9. /edit-image-pro-flash, /inpaint-image-pro-flash (Mock only). 5 to 6 per call on provisional quotes, no completed response seen.
  10. /create-character-v3 (Run live, not measured). 2 at 64px, up to 9 at 256px, and the one recorded bill was an unreadable zero.

Also worth a second look because they are measured on an old price list: the 1dir, /portrait-character-pro, /transfer-outfit-v2 and /edit-images-v2 bills all predate the October 2026 cut, and /inpaint-v3 was only re-measured from 256px to 384px.

Needs a human look

The repo does not establish whether or how these ran live:

  • POST /create-character-pro and POST /animate-character with mode: "pro": the docs plan 10 to 25 and record no bill.
  • POST /generate-font-pro: documented price only (pixelkiln font).
  • POST /unzoom: documented price only (pixelkiln unzoom).
  • PATCH tags on objects and characters, DELETE /objects/{id}, DELETE /characters/{id}, POST /objects/{id}/dismiss-review: used by fetch, adopt and salvage, and no doc records a live call.
  • DELETE /characters/{id}/animations: only the one-direction-of-a-shared-group case is called out as not run.

Keeping the ledger current

Update the row in the same change that records a live call. When a call is billed, take the amount from the finished job's usage (or last_response.billing_usage), not from a balance diff taken straight after, since billing lags; read it before the job record expires 8 hours later. Move the row's status, write the measurement and its month into "What is known", and remove the gap it closes. If a call ran but the bill was not captured, set the row to Run live, not measured rather than leaving it Mock only.