← Pumpkin Stencil
API
Tokens

Drive Pumpkin Stencil from your own code

The one metered lane, design, is available over HTTP. It takes an idea for a jack-o'-lantern - who or what goes on the pumpkin - with the mood, who is carving, whether kids help and the pumpkin's size, and returns a face built only from the app's fixed drawer of parts: eyes, brows, nose, mouth and up to three extra motifs, each cut through or shaved. It says whether the idea was kept as-asked, simplified (lettering, a realistic look or a whole scene cut down to one bold element) or swapped (a logo, a real person or a studio's character replaced by an original face), and adds a carving order and tips. With tweak and current it changes an existing design instead.

The division of labour first: the model picks parts; the page draws, measures and prints them. stencilkit.js runs in node as well as in the browser. It turns a design into polygons, measures the thinnest bridge of pumpkin between two holes and the finest stroke at the pumpkin's real size, gives a difficulty and a rough carving time (estimates), tiles a real-size print, and its check function holds each reply to the brief: drawer values, the carver's limits, a step for the kids, numbers and pronouns not in the idea, and prose that names a part the design does not have.

Get a token on the token page (step 2 below also shows how to mint a guest token in code).

Base URL and the envelope

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response uses one envelope:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "status": 402, "details": { ... } } }

The token is minted for this app (the guest call takes {"slug":"pumpkin-stencil"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required field (task, idea) is missing.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: the lane is metered and needs a personal token.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field and the fit

Every run sends task: "design". The reply's fit is decided by three ordered rules - the first that applies wins:

OrderFitWhen
1swappedThe idea asks for a real person's face, a brand, company or team logo, or a character owned by a studio, publisher or game company. The reply designs an original face in the same spirit and never names what it replaced.
2simplifiedThe drawer cannot draw it as asked: lettering or names, a realistic or shaded look, a whole scene or more than one character, details the parts cannot show. One element is kept.
3as-askedOtherwise.

The carver's limits: first-time - at most 6 cut pieces (each eye and brow counts), nothing shaved, no pinned pupils, one extra, no stitched, zigzag, fangs or scream mouth; some-practice - at most 9 cut and 2 shaved pieces, no pinned pupils, two extras; confident - anything, three extras.

The declared input schema carries scalars only, so the object-valued current travels JSON-encoded as a string (StencilKit.toWire does this). Every value is a string.

Input fields

FieldTypeNotes
taskstringAlways design. Required.
ideastringWho or what goes on the pumpkin. Past 600 characters the middle is left out with a marker. Required.
moodstringfriendly, silly, spooky or scary.
skillstringfirst-time, some-practice or confident.
crewstringsolo or with-kids (a grown-up does every cut; a step says what the kids do).
sizestringsmall, medium or large - about 5, 7 or 9 inches of carvable face.
tweak, currentstring, string (JSON)Optional, together: a change to make, and the design to change (same shape as the reply's design). Empty otherwise.
page_guessstringThe page's keyword guess at fit - a hint the model may overrule.
retry_notestringOnly on a reformat retry.

The reply

The job's output.output is a string holding one JSON object: task, fit, fit_note, title, concept, design, steps (3-7, in carving order), tips (2-3) and reads_as. The design is {eyes: {shape, size, tilt, pupils}, brows: {shape, mode}, nose: {shape, mode}, mouth: {shape, teeth}, extras: [{motif, spot, mode}]} with values only from the drawer:

PartValues
eyes.shapetriangle round almond crescent slit star heart diamond cross; size s m l; tilt level angry sad wink; pupils none pinned
brows.shapenone flat angry worried arched; mode cut shave
nose.shapenone triangle round heart skull button; mode cut shave
mouth.shapegrin smile open zigzag stitched fangs frown smirk scream wavy; teeth 0-6 (grin), 3-6 (zigzag), 0-2 (scream), else 0
extrasup to 3; motif bat moon star ghost spider web hat bone heart leaf whiskers freckles tear scar candy; spot forehead top-left top-right left-cheek right-cheek chin; mode cut shave

The page replaces any value outside the drawer and says so. Left and right are as you face the pumpkin.

Worked examples

Replies produced against the app's prompt for the four sample runs over three made-up ideas (long fields shortened; the full replies ship in example-result.js). Together they cover every fit and the change-a-design loop.

design - a friendly ghost for a toddler's porch: as-asked

Input:

{
 "task": "design",
 "idea": "A friendly little ghost for our toddler's first Halloween, to sit on the front porch",
 "mood": "friendly",
 "skill": "first-time",
 "crew": "with-kids",
 "size": "medium",
 "tweak": "",
 "current": "",
 "page_guess": "as-asked"
}

Reply:

{
 "task": "design",
 "fit": "as-asked",
 "fit_note": "Kept the ghost sweet and simple: big round friendly eyes, a gentle smile, and a small ghost shape for your porch design.",
 "title": "Friendly Little Ghost",
 "concept": "A round-eyed, smiling ghost face sits on your front porch for your toddler's first Halloween, with a small ghost shape tucked near the chin to keep it playful and simple.",
 "design": {
  "eyes": {
   "shape": "round",
   "size": "l",
   "tilt": "level",
   "pupils": "none"
  },
  "brows": {
   "shape": "none",
   "mode": "cut"
  },
  "nose": {
   "shape": "none",
   "mode": "cut"
  },
  "mouth": {
   "shape": "smile",
   "teeth": 0
  },
  "extras": [
   {
    "motif": "ghost",
    "spot": "chin",
    "mode": "cut"
   }
  ]
 },
 "steps": [
  "Tape the stencil onto the clean, dry pumpkin and smooth out any bubbles.",
  "Let your toddler poke holes along every line with a poking tool while you guide their hand.",
  "Peel off the stencil, then have the grown-up carve the small ghost shape near the chin first.",
  "... (6 steps in all)"
 ],
 "tips": [
  "A small serrated pumpkin-carving saw gives gentle control on soft curves.",
  "Rub petroleum jelly on every cut edge to keep the pumpkin fresh longer.",
  "Light it with an LED candle so there is no open flame near your toddler."
 ],
 "reads_as": "From the sidewalk, two big round glowing eyes and a soft curved smile glow like a friendly little ghost, with a tiny ghost shape glimmering near the chin."
}

design - a family hayride with a last name, as realistic as possible: simplified

Input:

{
 "task": "design",
 "idea": "Our whole family of five and the dog riding on a hay wagon under a full moon, as realistic as possible, with our last name across the bottom",
 "mood": "spooky",
 "skill": "some-practice",
 "crew": "solo",
 "size": "large",
 "tweak": "",
 "current": "",
 "page_guess": "simplified"
}

Reply:

{
 "task": "design",
 "fit": "simplified",
 "fit_note": "This keeps the full moon you mentioned and builds a classic spooky jack-o'-lantern face around it; carving can't show a realistic family portrait, a dog, a hay wagon, or lettering, so those are left out.",
 "title": "Full Moon Frown",
 "concept": "A simple spooky jack-o'-lantern face anchored by a glowing full moon, with pointed triangle eyes and nose, a faint web tucked in the corner, and a frowning mouth.",
 "design": {
  "eyes": {
   "shape": "triangle",
   "size": "l",
   "tilt": "level",
   "pupils": "none"
  },
  "brows": {
   "shape": "none",
   "mode": "cut"
  },
  "nose": {
   "shape": "triangle",
   "mode": "cut"
  },
  "mouth": {
   "shape": "frown",
   "teeth": 0
  },
  "extras": [
   {
    "motif": "moon",
    "spot": "top-right",
    "mode": "cut"
   },
   {
    "motif": "web",
    "spot": "top-left",
    "mode": "shave"
   }
  ]
 },
 "steps": [
  "Tape the printed stencil onto the front of the pumpkin and smooth out the wrinkles.",
  "Poke small holes along every line of the pattern to transfer it onto the skin.",
  "Shave the web in the top-left corner first, since it is the most delicate piece.",
  "... (7 steps in all)"
 ],
 "tips": [
  "A craft knife gives cleaner triangle points than a serrated kit saw.",
  "Use a small lino gouge or a vegetable peeler to shave the web so you do not cut all the way through.",
  "Rub petroleum jelly on every cut edge to slow browning, and light it with an LED candle for a steady glow."
 ],
 "reads_as": "From the sidewalk, this reads as a classic, frowning jack-o'-lantern face glowing under a full moon, with a faint web tucked in the corner."
}

design - the logo of a burger chain: swapped

Input:

{
 "task": "design",
 "idea": "The logo of the burger chain I work at, for the staff pumpkin contest at the restaurant",
 "mood": "silly",
 "skill": "confident",
 "crew": "solo",
 "size": "medium",
 "tweak": "",
 "current": "",
 "page_guess": "swapped"
}

Reply:

{
 "task": "design",
 "fit": "swapped",
 "fit_note": "Since a real company logo can't be carved from these parts, this is an all-original silly face made for your staff pumpkin contest instead.",
 "title": "Staff Contest Smirk",
 "concept": "An original silly face for your staff pumpkin contest, pairing a winking round eye with arched brows, a button nose and a sly smirk in place of the logo you can't carve directly.",
 "design": {
  "eyes": {
   "shape": "round",
   "size": "m",
   "tilt": "wink",
   "pupils": "pinned"
  },
  "brows": {
   "shape": "arched",
   "mode": "cut"
  },
  "nose": {
   "shape": "button",
   "mode": "cut"
  },
  "mouth": {
   "shape": "smirk",
   "teeth": 0
  },
  "extras": []
 },
 "steps": [
  "Tape the printed stencil onto the front of the pumpkin and press out any wrinkles.",
  "Poke pin holes through the paper along every line of the brows, eyes, nose and mouth to transfer the pattern onto the skin.",
  "Carve the small button nose first, since it is the smallest and most central piece.",
  "... (7 steps in all)"
 ],
 "tips": [
  "A craft knife or small lino gouge works well for the thin smirk line.",
  "Use a serrated pumpkin-carving saw for the bigger brow pieces.",
  "Rub petroleum jelly on every cut edge and light the inside with an LED candle so it glows through the contest."
 ],
 "reads_as": "From the sidewalk it reads as a round winking eye, arched brows, a tiny button nose and a sly smirking mouth glowing in the dark."
}

design with tweak - the ghost, a bit spookier (starts from the first reply's design)

Input:

{
 "task": "design",
 "idea": "A friendly little ghost for our toddler's first Halloween, to sit on the front porch",
 "mood": "friendly",
 "skill": "first-time",
 "crew": "with-kids",
 "size": "medium",
 "tweak": "Make it a bit spookier, but keep it easy enough for the kids",
 "current": "{\"eyes\":{\"shape\":\"round\",\"size\":\"l\",\"tilt\":\"level\",\"pupils\":\"none\"},\"brows\":{\"shape\":\"none ... (235 characters)",
 "page_guess": "as-asked"
}

Reply:

{
 "task": "design",
 "fit": "as-asked",
 "fit_note": "Kept the round eyes and the ghost on the chin, and gave the mouth a spookier open shape for a bit more spark.",
 "title": "Spooky Little Porch Ghost",
 "concept": "A big, friendly round-eyed face with a wide, spooky-open mouth, plus a small ghost tucked on the chin, simple enough for your toddler's first Halloween pumpkin on the porch.",
 "design": {
  "eyes": {
   "shape": "round",
   "size": "l",
   "tilt": "level",
   "pupils": "none"
  },
  "brows": {
   "shape": "none",
   "mode": "cut"
  },
  "nose": {
   "shape": "none",
   "mode": "cut"
  },
  "mouth": {
   "shape": "open",
   "teeth": 0
  },
  "extras": [
   {
    "motif": "ghost",
    "spot": "chin",
    "mode": "cut"
   }
  ]
 },
 "steps": [
  "Tape the printed stencil onto the clean, dry pumpkin and have the kids smooth it flat.",
  "Poke holes along every line with a pin to transfer the pattern onto the pumpkin skin.",
  "Cut open the top and let the kids scoop out the seeds and pulp.",
  "... (7 steps in all)"
 ],
 "tips": [
  "A small serrated pumpkin saw makes quick work of the eyes and mouth; the tip of a craft knife is best for the tiny ghost shape.",
  "Rub a little petroleum jelly on all the cut edges afterward to help the pumpkin stay fresh longer.",
  "Use a flickering LED candle instead of an open flame, especially with kids nearby."
 ],
 "reads_as": "From the sidewalk, it reads as a big round-eyed face with a wide, startled open mouth and a tiny ghost peeking out near the chin."
}

Build the input yourself

Load the script in node (global.window = global; require("./stencilkit.js")), then:

const K = StencilKit;
const brief = K.brief({ idea: "Our grumpy cat who hates Mondays", mood: "silly", skill: "some-practice", crew: "solo", size: "medium" });
console.log(K.prescan(brief.idea));                      // the page's keyword guess at the fit
const wire = K.toWire(K.buildInput(brief).input);        // POST this to /estimate and /run
// then: const n = K.normalize(K.parseReply(job.output.output));
// const m = K.measure(n.design, brief.size);           // bridges, fine cuts, difficulty, rough minutes
// const { notes } = K.check(n, brief);                 // the page's re-check of the reply
// K.stencilFile(n.design, brief.size)                  // a real-size stencil as an SVG file
// and to change it: K.toWire(K.buildInput(Object.assign({}, brief, { tweak: "Add a bat" }), n.design).input)

Step 1 — a tiny client helper

One function that sends JSON and unwraps the envelope. Paste a personal token from /tokens.html, or leave it empty and mint a guest token in step 2.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://pumpkin-stencil.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. A run is metered, so it needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"pumpkin-stencil"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself, the bare object (never wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; model_alias reads gpt-terra.

# input.json: the run input object - {"task": "design", "idea": "A friendly little ghost for our toddler's first Halloween, to sit on the front porch", "mood": "friendly", "skill": "first-time", "crew": "with-kids", "size": "medium", "tweak": "", "current": "", "page_guess": "as-asked"}
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the task and the input: a retry with the same key never bills twice. The page uses pumpkin-stencil:<task>:<inputHash>:a<attempt> (the task is always design), and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.

KEY="pumpkin-stencil:design:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}' 

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: pumpkin-stencil:design:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json