Measured endpoints

Characters, measured

  • Standard bases draw on a canvas 28px larger than image_size (64 → 92, 104 → 132). A directions reference is placed on it as is, centred, pixel for pixel.
  • Pro Flash returns exactly the requested canvas, and refuses a style_image larger than it ("Style image must fit the native canvas"). Its /pro-flash/cost answer for operation=character was image 5 up to 96px, 6 from 100 to 208px, 9 at 224px and above, plus rotations equal to v3's ceil(side² × 8 / 65536) on the padded square canvas; two live jobs (7 and 2) billed exactly the estimate. first_frame bills the rotations only.
  • A 64px create-character-state moved the balance by about 22 against the 20 to 40 tier documented; other jobs were running on the account, so it is a rough number.
  • animate-character with custom_start_frame and end_frame may return frames taller than the rotation (92 × 104 for a crouch). enhance_prompt writes the expanded text into the animation's animation_type. A v3 text loop on a 96px pro-flash base came back at 108 × 108.
  • Template loops need a skeleton template. On a pro-flash base with template_id: custom a walking-8-frames job failed upstream at once; on a round robot fitted to mannequin the same template returned a different, humanoid character standing nearly still. A v3 text loop ("walking forward in place, short legs stepping") kept the robot and walked. For anything that is not a biped or one of the four quadrupeds, animate from text.
  • Request validation is strict: an unknown body field is a 422 listing every problem at once, which is a free way to check a body's shape.
  • portrait-character-pro (direction: character_to_portrait, a full-body sprite in, a bust out) is job-based: POST returns background_job_id, GET /portrait-character-pro/{job_id} answers 423 while drawing and, once status: "completed", a download_url (its own usage is always null; read the billed amount from GET /background-jobs/{background_job_id} instead, same as every other job here). Not the couple-generations "conversion" the name suggests: the smallest result_size (16px) billed 20 generations, the same order of magnitude as a state or 1dir. POST /characters/{id}/portrait (attaching a portrait image to a character record) is a separate, synchronous, free call (usage.generations: 0); its response is the only place the stored portrait's URL appears; the character's own GET /characters/{id} never gains a portrait field, so PixelKiln has to remember the URL itself. character_to_portrait and SetPortrait are two independent calls with no automatic link between them.
  • transfer-outfit-v2 (a reference outfit image plus 2–16 existing frames back wearing it) is job-based the same way, polled through GET /background-jobs/{id} only (no dedicated status endpoint). A 2-frame, 92×92 job billed 20 generations — again a real generation cost, not a cheap frame edit. The completed job returns the result frames inline as base64 in last_response.quantized_images, not as storage URLs like every other multi-frame result here.