← Under Glass
Crew API
Tokens

Drive Under Glass from your own code

The one metered task, crew, is available over HTTP. It takes three to eight people (one per line, a name, a dash and a few words about them) and a stay length, and returns a verdict (seal the dome, seal after one swap, or back to the airlock), one post per person - a kind of crewmate, a dome job and a one-line reason - plus the glue, where it will rub, a four-stage crew log, three rituals, one warning sign and the habit to keep or the change to make.

The division of labour first: the page owns the keyword guess, the job order and the verdict rule; the model owns the casting; the page re-checks the reply. All three come from glasskit.js, which you can run yourself (see Build the input yourself). Under Glass is a game: the kinds of crewmate are a playful reading of a few words, not an assessment of anyone.

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":"under-glass"}), 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, stay, crew, guess) 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: casting a crew 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, the kinds, the jobs and the verdict

Every run sends task: crew; a missing or different one is still answered as crew. Each person gets one archetype: visionary (ideas, plans, art), executor (rotas, lists, budgets, follow-through), analyst (questions, puzzles, checking), connector (warmth, birthdays, hosting), facilitator (calming, settling disagreements) or specialist (a named trade or training). Connector and facilitator count as glue.

Each person gets a different job, one per biome: farmer (farm plot), air (air and water keeper, marsh), tender (biome tender, rainforest), fixer (machine deck), cook (galley), medic (sick bay), morale (morale officer, commons), chronicler (lookout). Jobs go in two passes: first to whoever's line names the work, then by each kind's preferred list.

The verdict follows the reply's own archetypes, first match wins: airlock if there are two different kinds or fewer; otherwise swap if nobody is a connector or facilitator, a crew of four or more has fewer than four kinds, or one kind is more than half the crew; otherwise seal.

The run input

Every value is a string (objects are never sent). app.js builds it with GlassKit.buildInput(form).input.

FieldRequiredWhat it holds
taskyesAlways crew.
stayyesa fortnight, six months or two years. The page times the crew log to it.
crewyesNumbered lines, 1. <name> - <what they are like>. Three to eight are sent; a description over 160 characters keeps its start.
guessyesThe page's keyword guess, one line per person: <n> | <archetype> | <job>. Only a guess - the model may cast differently.
clip_notenoWhat the page shortened, if anything.
retry_notenoSent only on the one automatic reformat retry.

The reply

The reply is one JSON object in job.output.output. The page parses it with GlassKit.parseReply and GlassKit.normalize (a cut-off stream is closed by GlassKit.closeJson and whatever parsed is shown).

{"task": "crew", "verdict": "seal | swap | airlock", "verdict_reason": "...", "title": "...",
 "crew": [{"n": 1, "name": "...", "archetype": "visionary | executor | analyst | connector | facilitator | specialist",
           "job": "farmer | air | tender | fixer | cook | medic | morale | chronicler", "why": "..."}],
 "glue": "a crew name, or an empty string", "fault_line": "...",
 "log": [{"stage": "forming", "moment": "..."}, {"stage": "storming", "moment": "..."},
         {"stage": "norming", "moment": "..."}, {"stage": "performing", "moment": "..."}],
 "rituals": ["...", "...", "..."],
 "watch": {"sign": "groupthink | one_voice | avoidance | unclear_roles | paralysis | blame | burnout", "why": "..."},
 "fix": "..."}
FieldWhat it holds
crewExactly one entry per numbered person, same numbers and order, name copied exactly. why at most 22 words, from the person's own line.
glueThe connector or facilitator who holds the crew together, or an empty string if there is none.
fault_lineTwo people whose habits will most likely rub, at most 30 words, good-humoured.
logFour moments of dome life, forming to performing, at most 25 words each, with no day or month counts (the page labels them for the stay).
ritualsThree crew rituals - a daily, a weekly and a milestone - at most 20 words each.
watchOne warning sign and why this crew is prone to it.
fixFor seal, the habit that keeps the crew working; for swap, the gap and one concrete change; for airlock, the missing kinds and who to add.

The page then checks the reply against its own facts (GlassKit.check): one post per person, names as written, every job held once, the verdict against the reply's own casting, the glue on the crew list and cast as a connector or facilitator, the log in its four stages with no counted days, three rituals, a known warning sign, the keyword guess where it differs, no he or she for anyone, and no internal field name in the prose.

Worked examples, one per verdict

These are the three free samples on the page: the exact input and a stand-in model's reply, unedited.

The flat on Quillfern Lane - seal

Input:

{
 "task": "crew",
 "stay": "two years",
 "crew": "1. Priya - always has a new idea for the flat, painted the mural in the hall\n2. Tom - keeps the cleaning rota, never misses bin day\n3. Wen - asks how do we know about everything, does the crossword in pen\n4. Dee - remembers everyone's birthday and throws the parties\n5. Sam - calm, the one who sorts out the thermostat arguments\n6. Joe - trained as a nurse, works nights",
 "guess": "1 | visionary | chronicler\n2 | executor | air\n3 | analyst | fixer\n4 | connector | morale\n5 | facilitator | cook\n6 | specialist | medic"
}

Reply:

{
 "task": "crew",
 "verdict": "seal",
 "verdict_reason": "Six different kinds of crewmate among six people, and Dee is the glue who keeps everyone close.",
 "title": "Two Years Under the Painted Dome",
 "crew": [
  {
   "n": 1,
   "name": "Priya",
   "archetype": "visionary",
   "job": "chronicler",
   "why": "Always dreaming up new ideas and painting the mural in the hall points to the lookout who keeps the crew's record."
  },
  {
   "n": 2,
   "name": "Tom",
   "archetype": "executor",
   "job": "air",
   "why": "Keeping the cleaning rota and never missing bin day shows the organised habit that matches tending the water and air."
  },
  {
   "n": 3,
   "name": "Wen",
   "archetype": "analyst",
   "job": "fixer",
   "why": "Asking how do we know and doing the crossword in pen shows a questioning mind that suits the machine deck."
  },
  {
   "n": 4,
   "name": "Dee",
   "archetype": "connector",
   "job": "morale",
   "why": "Remembering everyone's birthday and throwing the parties shows warmth for people, matching the parties and games of the commons."
  },
  {
   "n": 5,
   "name": "Sam",
   "archetype": "facilitator",
   "job": "cook",
   "why": "Staying calm and sorting out the thermostat arguments shows a settling touch, and a steady peacemaker suits the galley."
  },
  {
   "n": 6,
   "name": "Joe",
   "archetype": "specialist",
   "job": "medic",
   "why": "Training as a nurse and working nights names the exact trade that fits the sick bay."
  }
 ],
 "glue": "Dee",
 "fault_line": "Tom's rota and bin day habit may clash with Priya always wanting a new idea for the dome, routine against change.",
 "log": [
  {
   "stage": "forming",
   "moment": "Priya starts the first page of the crew's record while Tom walks the marsh explaining the water duties to everyone."
  },
  {
   "stage": "storming",
   "moment": "Tom insists every change go in the rota exactly as written, but Priya wants the record to read like a story, voices rising briefly."
  },
  {
   "stage": "norming",
   "moment": "Wen now checks the machine deck readings before Tom logs them in the rota, and the two trust each other's work."
  },
  {
   "stage": "performing",
   "moment": "Dee throws a commons celebration with a meal from the galley, and the machine deck, water and air checks all run without a hitch."
  }
 ],
 "rituals": [
  "Every morning Tom reads out the water and air rota while Joe checks on anyone who worked nights.",
  "Once a week everyone gathers for a crossword Wen sets using clues from dome life.",
  "To mark a milestone, Priya paints a new mural panel in the lookout's record for everyone to see."
 ],
 "watch": {
  "sign": "burnout",
  "why": "Joe is the only one covering the sick bay and already works nights, so covering it alone could wear Joe down over time."
 },
 "fix": "Checking in on Joe after night shifts and taking turns watching the sick bay when needed will keep this crew's balance intact."
}

The Sunday repair club - swap

Input:

{
 "task": "crew",
 "stay": "six months",
 "crew": "1. Rosa - reads the instructions twice and keeps the budget spreadsheet\n2. Ken - grows tomatoes and chillies on a tiny balcony\n3. Lou - full of plans for the next big trip\n4. Ade - fixes bikes and anything with a motor",
 "guess": "1 | executor | air\n2 | specialist | farmer\n3 | visionary | chronicler\n4 | specialist | fixer"
}

Reply:

{
 "task": "crew",
 "verdict": "swap",
 "verdict_reason": "Four crewmates split across three kinds of worker, and none of them is naturally warm or diplomatic enough to be the crew's glue.",
 "title": "The Seedling, Spreadsheet and Someday-Trip Crew",
 "crew": [
  {
   "n": 1,
   "name": "Rosa",
   "archetype": "executor",
   "job": "air",
   "why": "Reading the instructions twice and keeping the budget spreadsheet is exactly what tracking the marsh's levels needs."
  },
  {
   "n": 2,
   "name": "Ken",
   "archetype": "specialist",
   "job": "farmer",
   "why": "Growing tomatoes and chillies on a tiny balcony is exactly the kind of growing the farm plot needs."
  },
  {
   "n": 3,
   "name": "Lou",
   "archetype": "visionary",
   "job": "chronicler",
   "why": "Lou is full of plans for the next big trip, and that kind of imagination suits the lookout's record."
  },
  {
   "n": 4,
   "name": "Ade",
   "archetype": "specialist",
   "job": "fixer",
   "why": "Fixing bikes and anything with a motor is exactly the trade the machine deck needs."
  }
 ],
 "glue": "",
 "fault_line": "Rosa's twice-checked budget spreadsheet and Lou's plans for the next big trip may clash gently over what the crew can spend.",
 "log": [
  {
   "stage": "forming",
   "moment": "Rosa posts the budget spreadsheet in the commons while Ken checks on the tomato and chilli seedlings in the farm plot."
  },
  {
   "stage": "storming",
   "moment": "Rosa wants every expense logged in the spreadsheet while Lou keeps dreaming up extras for the next big trip, so they compromise."
  },
  {
   "stage": "norming",
   "moment": "Ade starts fixing the irrigation pump the moment Ken flags a dripping hose, and the farm plot runs smoothly again."
  },
  {
   "stage": "performing",
   "moment": "Lou notes down the first ripe chillies and the balanced budget sheet in the crew's record, pinned up for everyone to see."
  }
 ],
 "rituals": [
  "A daily walk-through of the machine deck and the tomato and chilli plot to catch small problems early.",
  "A weekly reading of the budget spreadsheet together, with everyone adding a line about what mattered most.",
  "When the first ripe chillies are picked, the whole crew gathers to mark it in the crew's record."
 ],
 "watch": {
  "sign": "avoidance",
  "why": "With no one whose role is to smooth disagreements, Rosa's budget focus and Lou's big trip plans could easily go unspoken."
 },
 "fix": "No one here is naturally warm or diplomatic; before sealing, have one crewmate agree to check that every disagreement gets properly heard."
}

The sketchbook club - airlock

Input:

{
 "task": "crew",
 "stay": "a fortnight",
 "crew": "1. Max - always pitching a new idea\n2. Ida - dreams up big plans and starts ten projects at once\n3. Leo - brainstorms for fun, gets bored by details\n4. Bo - sketches wild inventions in the margins",
 "guess": "1 | visionary | tender\n2 | visionary | farmer\n3 | visionary | air\n4 | visionary | chronicler"
}

Reply:

{
 "task": "crew",
 "verdict": "airlock",
 "verdict_reason": "All four people turned out to be the same kind of dreamer, with not even a second kind of crewmate aboard.",
 "title": "Four Dreamers, One Glass Dome",
 "crew": [
  {
   "n": 1,
   "name": "Max",
   "archetype": "visionary",
   "job": "tender",
   "why": "Max is always pitching a new idea, and that same kind of imagination suits the rainforest."
  },
  {
   "n": 2,
   "name": "Ida",
   "archetype": "visionary",
   "job": "farmer",
   "why": "Ida dreams up big plans and starts ten projects at once, and that same kind of imagination suits the farm plot."
  },
  {
   "n": 3,
   "name": "Leo",
   "archetype": "visionary",
   "job": "air",
   "why": "Leo brainstorms for fun and gets bored by details, and that same kind of imagination suits the marsh."
  },
  {
   "n": 4,
   "name": "Bo",
   "archetype": "visionary",
   "job": "chronicler",
   "why": "Bo sketches wild inventions in the margins, and that same sketching fits the lookout who keeps the record."
  }
 ],
 "glue": "",
 "fault_line": "Max always pitching new ideas and Ida starting ten projects at once will likely clash over whose plan the dome actually tries first.",
 "log": [
  {
   "stage": "forming",
   "moment": "Leo brainstorms names for the marsh equipment just for fun, while Bo sketches the new water lines in the margins of the log."
  },
  {
   "stage": "storming",
   "moment": "Max pitches a new plan for the rainforest just as Ida starts another project on the farm plot, and both insist theirs should come first."
  },
  {
   "stage": "norming",
   "moment": "Max and Ida agree to try one rainforest idea and one farm project at a time, taking turns pitching whose plan runs next."
  },
  {
   "stage": "performing",
   "moment": "Leo's marsh brainstorms become routine fixes for the water lines, and Bo's margin sketches become the crew's record of what was tried."
  }
 ],
 "rituals": [
  "Each morning, everyone pitches one fresh idea for the dome before heading off to the farm plot, marsh or rainforest.",
  "Once a week, Bo pins the best margin sketch to the lookout post for the whole crew to see.",
  "When the rainforest fills with Max's ideas and Ida's projects, the crew celebrates with a toast in the commons."
 ],
 "watch": {
  "sign": "groupthink",
  "why": "With everyone the same kind of idea-chaser and no one who questions or checks the plan, the crew could agree too quickly."
 },
 "fix": "This crew is all big-idea pitchers; add someone organised who finishes what others start, and someone warm who keeps the group steady."
}

Build the input yourself

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

const K = GlassKit;
const form = K.form({ stay: "half",
  crew: "Mo - calm, the one who keeps the peace\nAsh - grows all the veg on the allotment\nKit - asks why about everything\nLin - keeps the club budget" });
K.crewOf(form);                                  // one crewmate per line, with the page's archetype and job guess
K.verdictFor(K.crewOf(form));                    // {verdict: "seal", why: [...]} - the page's rule over its guesses
const built = K.buildInput(form);
const wire = K.toWire(built.input);              // POST this to /estimate and /run
// then: const reply = K.normalize(K.parseReply(job.output.output));
// K.check(reply, built).notes                   // the page's re-check of the reply
// K.md(built, reply)                            // the crew as Markdown

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://under-glass.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":"under-glass"}' | 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": "crew", "stay": "six months", "crew": "1. Rosa - reads the instructions twice and keeps the budget spreadsheet\n2. Ken - grows tomatoes and chillies on a tiny balcony\n3. Lou - full of plans for the next big trip\n4. Ade - fixes bikes and anything with a motor", "guess": "1 | executor | air\n2 | specialist | farmer\n3 | visionary | chronicler\n4 | specialist | fixer"}
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 lane and the input: a retry with the same key never bills twice. The page uses under-glass:<task>:<inputHash>:a<attempt> (the one task is crew), 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="under-glass:crew:$(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: under-glass:crew:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json