← Pillow Question
Pillow Question API
Tokens

Drive Pillow Question from your own code

Both metered lanes are available over HTTP: the evening seed (tonight's question kept or reshaped, an incubation phrase, three images, a short visualization, a bedside card, what to do on a night waking and the morning prompts) and the morning harvest (what the person remembered sorted into fragments that quote their own words, possible threads back to the question phrased as questions, journaling prompts and one adjustment for the next night). A natural use is a journaling app or a sleep diary that wants an incubation step: your code sends the question and the estimated night in the evening, and the recall in the morning.

The division of labour first: the page sketches and splits; the model writes; the page re-checks every quote and every time. The question's shape check, the estimated night (textbook 90-minute cycles from the bedtime and the alarm - an estimate, not a measurement), the optional night waking, the recall split into fragments and the words it shares with the question come from nightkit.js, which runs in node as well as in the browser. Its check function holds each reply to the person's own words: every harvest quote must appear in the recall, every clock time in a seed must be on the night's sketch, no internal field names in prose. Dreams are treated as material for reflection, never as predictions; not medical advice.

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":"pillow-question"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare: never wrap it as {"input": {…}}.

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or task / question 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: both lanes are metered and need 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 comes first

Every run sends task: seed or harvest. Both share the question fields; each lane adds its own. The model follows only the named task's contract.

TaskVerdictsReply fields
seedready (one open question the night can hold), reshape (yes/no, two questions, a prediction, about changing someone else, or too vague - one thread opened up)task, verdict, title, summary, question_kept, reshape_why, phrase (5-12 words), images (3 strings), visualization (60-110 words), cue_card (3-5 lines, the phrase first), if_you_wake, morning_prompts (3-5), cautions (0-2)
harvestecho (a fragment visibly touches the question), faint (only a mood or shape resembles it), quiet (little or no recall, or nothing connects)task, verdict, title, summary, fragments [{quote, image, feeling, thread, prompt}] (quotes copied exactly from the recall, at most 6), threads (0-3), prompts (3-5), tonight

The declared input schema carries scalars only, so object-valued fields travel JSON-encoded as strings (NightKit.toWire does this). The schema requires task and question.

Input fields

FieldLaneTypeNotes
taskbothstringseed or harvest.
questionbothstringThe question as typed; in the morning, the question as the seed reshaped it (if it did). Past 600 characters the middle is cut with a marker.
contextbothstringThe person's own words about the situation (may be empty); past 2,000 characters the middle is cut.
page_flags, page_verdictbothstring (JSON), string[{level, text}] with level fix / warn / info; the page's own reading - ready / reshape for a seed, echo / faint / quiet for a harvest.
bedtime, wakeseedstringHH:MM, 24-hour.
wbtbseedstringyes when the person plans to wake once and go back to sleep, else no.
nightseedstring (JSON){asleep_by, hours, cycles, dream_tides: [{from, to}], wake_window, last_tide, estimate: true} from NightKit.night.
phraseharveststringThe phrase used last night (may be empty).
dreamharveststringWhat the person remembered; past 6,000 characters the middle is cut and the start and end kept.
waking_moodharveststringOne word, or empty.
page_recallharveststring (JSON){words, fragments: ["1. ...", ...], shared_words, empty} from NightKit.prescan.
retry_notebothstringOnly on a reformat retry.

The reply

The job's output.output is a string holding one JSON object. Parse it with NightKit.parseReply, then normalize(reply, task), then check(reply, state, task), which returns {notes}: the page's notes (info, warn) shown under "The page's check".

Worked examples

Replies produced against the app's prompt for the three sample nights (made up; lists shortened; the full replies ship in example-result.js). Together they cover every verdict of both lanes. The page replays each of them free from its sample buttons.

task: seed - the studio - ready

Input:

{
 "task": "seed",
 "question": "What is really holding me back from opening my own pottery studio?",
 "context": "I've been throwing pots in a shared community studio for six years. There's a small unit for rent on Mercer Lane with ro ... (181 characters)",
 "bedtime": "22:45",
 "wake": "06:45",
 "wbtb": "no",
 "night": "{\"asleep_by\":\"23:00\",\"hours\":7.8,\"cycles\":5,\"dream_tides\":[{\"from\":\"00:20\",\"to\":\"00:30\"},{\"from\":\"01:45\",\"to\":\"02:00\"},{\"from\":\"03:10\",\"to\":\"03:30\"},{\"from\":\"04:35\",\"to\":\"05:00\"},{\"from\":\"06:00\",\"to\":\"06:30\"}],\"wake_window\":\"\",\"last_tide\":\"06:00-06:30\",\"estimate\":true}",
 "page_flags": "[]",
 "page_verdict": "ready"
}

Reply:

{
 "task": "seed",
 "verdict": "ready",
 "title": "What's Holding Back The Studio",
 "summary": "Tonight you're asking what is truly in the way of opening your own pottery studio, not just the practical steps. Hold the question loosely as you fall asleep, without trying to answer it outright.",
 "question_kept": "What is really holding me back from opening my own pottery studio?",
 "reshape_why": "",
 "phrase": "Tonight I dream of my own pottery studio, and I remember.",
 "images": [
  "The small unit on Mercer Lane, quiet and waiting.",
  "Room enough for a kiln, warm and quiet.",
  "Throwing pots in the shared studio after six years."
 ],
 "visualization": "Let your body sink heavier into the bed with each breath, shoulders loosening, jaw soft. Picture the small unit on Mercer Lane, quiet and waiting for you. Let i ...",
 "cue_card": [
  "Tonight I dream of my own pottery studio, and I remember.",
  "The unit on Mercer Lane, quiet and waiting.",
  "Room enough for a kiln, warm and quiet.",
  "..."
 ],
 "if_you_wake": "If you wake in the night, lie still for a moment before you move and let whatever is there settle. Notice any key words or pictures from what you were in, without reaching for your phone. Then say your phrase again - tonight I dream of my own pottery studio, and I remember - and let sleep take you back.",
 "morning_prompts": [
  "What images, people, places or feelings do you remember from the night, before anything else comes to mind?",
  "Did anything in the night touch on the pottery studio, the unit on Mercer Lane, or opening a space of your own?",
  "..."
 ],
 "cautions": [
  "Treat whatever you recall as one view among many, not the final word on the studio."
 ]
}

task: seed - two cities - reshape

Input:

{
 "task": "seed",
 "question": "Should I move to Lisbon for the new job or stay in Leeds near my sister, and will it work out either way?",
 "context": "The offer came last week and I have to answer by Friday. My sister just had her second baby. I love my flat but I've fel ... (139 characters)",
 "bedtime": "00:30",
 "wake": "07:30",
 "wbtb": "yes",
 "night": "{\"asleep_by\":\"00:45\",\"hours\":6.4,\"cycles\":4,\"dream_tides\":[{\"from\":\"02:05\",\"to\":\"02:15\"},{\"from\":\"03:30\",\"to\":\"03:45\"},{\"from\":\"04:55\",\"to\":\"05:15\"},{\"from\":\"06:40\",\"to\":\"07:05\"}],\"wake_window\":\"05:15\",\"last_tide\":\"06:40-07:05\",\"estimate\":true}",
 "page_flags": "[{\"level\":\"warn\",\"text\":\"A yes/no question - open it with what or how so the dream has room.\"},{\"level\":\"warn\",\"text\":\"Asks for a prediction - dreams are better at showing how something feels than what will happen.\"}]",
 "page_verdict": "reshape"
}

Reply:

{
 "task": "seed",
 "verdict": "reshape",
 "title": "Weighing Lisbon Against Staying In Leeds",
 "summary": "Tonight's question is about weighing the new job in Lisbon against staying in Leeds near your sister, not about predicting how either path turns out. Hold it tonight as something to feel your way through, not a verdict to wait for in your sleep.",
 "question_kept": "What would help me choose between the new job in Lisbon and staying in Leeds near my sister?",
 "reshape_why": "Your question was yes-or-no and asked the dream to predict how things would turn out, but dreams are better at showing how a choice feels than telling you what will happen. So I opened it into one question about what would help you weigh the Lisbon job against staying near your sister in Leeds.",
 "phrase": "Tonight I picture Lisbon and Leeds, and I remember.",
 "images": [
  "The new job waiting, far away in Lisbon.",
  "Your flat, quiet and familiar, after a stuck year.",
  "Your sister close by, with her second baby."
 ],
 "visualization": "You lie still and let your breath slow, body heavy and loose. Let your shoulders drop, let the day's weight settle and fade. Picture the new job waiting, far aw ...",
 "cue_card": [
  "Tonight I picture Lisbon and Leeds, and I remember.",
  "Hold the job, the flat, your sister near.",
  "Let the day go quiet.",
  "..."
 ],
 "if_you_wake": "If you wake near the estimated 05:15 window, lie still before you move and let what you were picturing linger. Notice any key words from the job, the flat, your sister, or the baby, then say your phrase again: tonight I picture Lisbon and Leeds, and I remember. Let that carry you back down.",
 "morning_prompts": [
  "What images, people, places or feelings do you remember from the night, before you check anything else?",
  "Did any part of the dream touch on the new job in Lisbon or on staying in Leeds?",
  "..."
 ],
 "cautions": [
  "This is a big decision - let the dream be one input among others, not the final answer."
 ]
}

task: seed - the chorus - ready on a short night

Input:

{
 "task": "seed",
 "question": "What does the chorus of my unfinished song want to say?",
 "context": "I wrote two verses about my grandmother's garden last month. The chorus melody is done but every set of words I try soun ... (128 characters)",
 "bedtime": "23:30",
 "wake": "05:30",
 "wbtb": "no",
 "night": "{\"asleep_by\":\"23:45\",\"hours\":5.8,\"cycles\":4,\"dream_tides\":[{\"from\":\"01:05\",\"to\":\"01:15\"},{\"from\":\"02:30\",\"to\":\"02:45\"},{\"from\":\"03:55\",\"to\":\"04:15\"},{\"from\":\"05:20\",\"to\":\"05:30\"}],\"wake_window\":\"\",\"last_tide\":\"05:20-05:30\",\"estimate\":true}",
 "page_flags": "[{\"level\":\"warn\",\"text\":\"A short night (about 5.8 hours) - the longest dream tides come late, so recall tends to be thin.\"}]",
 "page_verdict": "ready"
}

Reply:

{
 "task": "seed",
 "verdict": "ready",
 "title": "What the chorus wants to say",
 "summary": "Tonight's question is about letting the unfinished chorus find its own words, after two already-finished verses about your grandmother's garden. Hold it loosely as you drift off, the way you would hum a tune before all its words arrive.",
 "question_kept": "What does the chorus of my unfinished song want to say?",
 "reshape_why": "",
 "phrase": "Tonight I listen for what the chorus wants to say.",
 "images": [
  "The finished chorus melody waiting quietly for its words.",
  "Two verses about your grandmother's garden, settled and done.",
  "Words that keep sounding fake, softening in the dark."
 ],
 "visualization": "You settle into stillness, breath slowing, the day's edges going soft. Picture the chorus melody, already finished, waiting quietly for words it hasn't found ye ...",
 "cue_card": [
  "Tonight I listen for what the chorus wants to say.",
  "The melody is already finished.",
  "Let the words come soft and true.",
  "..."
 ],
 "if_you_wake": "If you wake in the night, lie still before you move. Let any image or words surface, and note the key ones in your mind. Then return to the phrase: tonight I listen for what the chorus wants to say.",
 "morning_prompts": [
  "What images, people, places, or feelings do you remember first?",
  "Did any words, sounds, or fragments feel close to the chorus you're waiting on?",
  "..."
 ],
 "cautions": [
  "Tonight is short, with the longest dreaming stretch right before you wake - recall may be thin, and that's normal."
 ]
}

task: harvest - the studio - echo

Input:

{
 "task": "harvest",
 "question": "What is really holding me back from opening my own pottery studio?",
 "context": "I've been throwing pots in a shared community studio for six years. There's a small unit for rent on Mercer Lane with ro ... (181 characters)",
 "phrase": "Tonight I dream of my own pottery studio, and I remember.",
 "dream": "I was in a big empty studio with white walls and the kiln was there but the door was locked from the inside. My old teac ... (345 characters)",
 "waking_mood": "unsettled",
 "page_recall": "{\"words\":70,\"fragments\":[\"1. I was in a big empty studio with white walls and the kiln was there but the door was locked from the inside.\",\"2. My old teacher was knocking on the window, smiling, and I couldn't find the key.\",\"3. Then the clay on the wheel turned into sand and kept spilling onto the floor.\",\"4. I felt panicky but also weirdly calm at the end when I just sat on the floor.\"],\"shared_words\":[\"studio\"],\"empty\":false}",
 "page_flags": "[]",
 "page_verdict": "echo"
}

Reply:

{
 "task": "harvest",
 "verdict": "echo",
 "title": "A locked door, a missing key",
 "summary": "This dream keeps circling the same studio and kiln that your question is about, with a door you couldn't open and a key you couldn't find - that's a clear echo of what you're asking yourself about starting your own place.",
 "fragments": [
  {
   "quote": "I was in a big empty studio with white walls and the kiln was there but the door was locked from the inside",
   "image": "kiln behind locked door",
   "feeling": "",
   "thread": "Could the door being locked from the inside be about something only you can open, not anyone else?",
   "prompt": "What does it mean that the studio and the kiln were right there, but the door kept you out?"
  },
  {
   "quote": "My old teacher was knocking on the window, smiling, and I couldn't find the key",
   "image": "teacher knocking, no key",
   "feeling": "",
   "thread": "Could not finding the key be about what feels missing before you'd let yourself start?",
   "prompt": "What would it be like to let your old teacher in, or to find the key yourself?"
  },
  "..."
 ],
 "threads": [
  "The locked door and the missing key both keep you just outside the studio - is that close to how waiting until spring feels?",
  "The clay turning to sand might be worth sitting with - is something starting to feel like it's slipping away the longer you wait?",
  "..."
 ],
 "prompts": [
  "What part of this dream feels closest to how you feel about the studio right now?",
  "If the door had opened, what do you imagine was on the other side?",
  "..."
 ],
 "tonight": "You could narrow tomorrow's question to what's behind the locked door and the missing key, since that's where this dream lingered longest."
}

task: harvest - two cities - faint

Input:

{
 "task": "harvest",
 "question": "What would help me choose between the new job in Lisbon and staying in Leeds near my sister?",
 "context": "The offer came last week and I have to answer by Friday. My sister just had her second baby. I love my flat but I've fel ... (139 characters)",
 "phrase": "Tonight I picture Lisbon and Leeds, and I remember.",
 "dream": "I was in a swimming pool at night with people from school. The water was cold but I didn't mind. Someone handed me a tic ... (210 characters)",
 "waking_mood": "foggy",
 "page_recall": "{\"words\":41,\"fragments\":[\"1. I was in a swimming pool at night with people from school.\",\"2. The water was cold but I didn't mind.\",\"3. Someone handed me a ticket and the writing on it kept changing.\",\"4. I was looking for a coat I'd forgotten somewhere.\"],\"shared_words\":[],\"empty\":false}",
 "page_flags": "[{\"level\":\"info\",\"text\":\"No words in common with your question - links may still be there in mood or shape.\"}]",
 "page_verdict": "faint"
}

Reply:

{
 "task": "harvest",
 "verdict": "faint",
 "title": "Swimming Pool and a Forgotten Coat",
 "summary": "You're a little foggy this morning, and nothing in the pool, the ticket that kept changing, or the missing coat shares a word with your question. That keeps this at a loose, faint echo rather than a clear one - the shape might still say something, even without a direct line to Lisbon or Leeds.",
 "fragments": [
  {
   "quote": "I was in a swimming pool at night with people from school.",
   "image": "swimming pool at night",
   "feeling": "",
   "thread": "Could the people from school in the pool be tied to what feels familiar about staying in Leeds?",
   "prompt": "What stands out now about being back with people from school in that pool?"
  },
  {
   "quote": "The water was cold but I didn't mind.",
   "image": "cold water",
   "feeling": "",
   "thread": "Could not minding the cold water be echoing some readiness for change already in you?",
   "prompt": "What lets you not mind the cold water, now that you think about it?"
  },
  "..."
 ],
 "threads": [
  "The people from school and the forgotten coat both reach back to something already behind you - could that be part of what staying in Leeds holds?",
  "The ticket's writing keeps shifting the way a decision between two cities might - could that be close to how open the choice still feels?",
  "..."
 ],
 "prompts": [
  "What is the first feeling you notice thinking about the dream now?",
  "Which image feels most alive this morning - the pool, the ticket, or the coat?",
  "..."
 ],
 "tonight": "Keep the same question tonight, but say the phrase with a couple more of your own words - like choose or stuck - since nothing in last night's dream touched the question's exact words."
}

task: harvest - the chorus - quiet

Input:

{
 "task": "harvest",
 "question": "What does the chorus of my unfinished song want to say?",
 "context": "I wrote two verses about my grandmother's garden last month. The chorus melody is done but every set of words I try soun ... (128 characters)",
 "phrase": "Tonight I listen for what the chorus wants to say.",
 "dream": "Nothing really. I woke up thirsty and the radiator was clanking.",
 "waking_mood": "foggy",
 "page_recall": "{\"words\":11,\"fragments\":[],\"shared_words\":[],\"empty\":true}",
 "page_flags": "[{\"level\":\"info\",\"text\":\"Little or no recall - that is common. The harvest still gives you recall prompts and an adjustment for tonight.\"}]",
 "page_verdict": "quiet"
}

Reply:

{
 "task": "harvest",
 "verdict": "quiet",
 "title": "Little recall, mostly waking details",
 "summary": "You woke foggy, and nothing from inside a dream came back - only the thirst and the clanking radiator from waking life. A quiet night like this is common, and it does not mean your question went unanswered.",
 "fragments": [],
 "threads": [],
 "prompts": [
  "Before you move or check your phone tomorrow, what is the very first feeling you notice?",
  "Is there any flicker of an image, a color, or a sound you can catch before it fades?",
  "..."
 ],
 "tonight": "Keep the same question and phrase tonight. The moment you surface from sleep, stay still and reach for any image or sound before you move or speak."
}

Build the input yourself

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

const K = NightKit;
const form = { question: "What is really holding me back from opening my own pottery studio?", context: "...",
  bedtime: "22:45", wake: "06:45", wbtb: "no" };
const st = K.state(form);                              // question flags, the estimated night, the recall split
console.log(st.Q.verdict, st.N.asleep_by, st.N.tides.map(t => t.from + "-" + t.to));
const wire = K.toWire(K.buildInput(st, "seed").input);  // POST this to /estimate and /run
// morning: set form.phrase = seedReply.phrase, form.kept = seedReply.verdict === "reshape" ? seedReply.question_kept : "",
// form.dream = "what you remember", then K.toWire(K.buildInput(K.state(form), "harvest").input)
// later: const reply = K.normalize(K.parseReply(job.output.output), "harvest");
// const { notes } = K.check(reply, K.state(form), "harvest");

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://pillow-question.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":"pillow-question"}' | 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": "seed", "question": "What is really holding me back from opening my own pottery studio?", "context": "...", "bedtime": "22:45", "wake": "06:45", "wbtb": "no", "night": "{...}", "page_flags": "[]", "page_verdict": "ready"}
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 pillow-question:<task>:<inputHash>:a<attempt> (the examples below use the seed lane), 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="pillow-question:seed:$(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: pillow-question:seed:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json