← Novel in November
Novel in November API
Tokens

Drive Novel in November from your own code

Both metered lanes are available over HTTP: the scene calendar (a logline, the acts, and one plotted scene with a writing prompt for every writing day of a 30-day novel, with the story's turning points on their days) and get unstuck (what the stall feels like, the story cause underneath and what the story needs, an earlier day to change on paper when the cause lies there, the next three writing days re-planned, a 15-minute sprint and a pace note). A natural use is a writing-tracker or a word-count bot that wants a plan before the month starts and a second read when a writer stalls.

The division of labour first: the page does the arithmetic; the model writes the story; the page re-checks the reply. The schedule (30 days from the start date, rest weekdays, a weekend boost, a target per writing day that sums exactly to the goal), the turning points (each beat on the first writing day whose running total reaches its share of the goal), the premise read and the pace from the word log come from novelkit.js, which runs in node as well as in the browser. Its check function holds each reply to the page: a scene for every writing day (the first seven for a rethink) and none on rest days, turning-point labels on the right days, the next days and the rewind day valid, word figures not on the page named, no internal field names in prose.

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":"novel-in-november"}), 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 a required field such as task 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: plan (the scene calendar) or unstick (get unstuck). Both share title, genre and premise; each lane adds its own fields. The model follows only the named task's contract.

TaskVerdictsReply fields
plango (who the story follows, what they want and what stands in the way are all stated), patch (one of the three is missing: the calendar assumes it and says so), rethink (two or three are missing: only a discovery week is planned, with questions for week two)task, verdict, title, summary, logline, assumptions, questions, acts [{name, days, purpose}], scenes [{day, beat, scene, prompt}]
unstickrewind (the cause is in an earlier day's material: change it on paper and write forward as if it were fixed), reroute (nothing earlier changes, but the next planned scenes no longer fit), keep_going (the story holds; the block is momentum or pace)task, verdict, title, summary, diagnosis {symptom, cause, need}, rewind {day, change, note_to_self} or null, next_scenes [{day, scene, prompt}], sprint {minutes, prompt}, pace_note

The declared input schema carries scalars only, so object-valued fields travel JSON-encoded as strings (NovelKit.toWire does this). Every value is a string.

Input fields: plan

FieldTypeNotes
taskstringplan.
title, genrestringMay be empty.
premisestringThe premise in the writer's words; past 2,400 characters the middle is cut with a marker.
protagonist, want, obstaclestringThe three pillars, each may be empty (the premise may carry them).
endingstringThe ending, if the writer knows it; may be empty.
templatestringThe story shape's name: Three acts, Seven points, Mystery or Four parts (kishotenketsu).
goalstringTotal words, digits only (default 50000).
page_daysstring (JSON)[{day, date, words}] for the writing days only: day 1-30 of the challenge, date like Sun Nov 1, words that day's target. The reply's scenes use these day numbers.
page_beatsstring (JSON)[{beat, day}]: the template's turning points on their days.
page_flagsstring (JSON)[{level, text}] with level fix / warn / info: schedule notes and missing pillars.
page_verdictstringThe page's own read from the pillar fields: go, patch or rethink.
retry_notestringOnly on a reformat retry.

Input fields: unstick

FieldTypeNotes
taskstringunstick.
title, genrestringAs for plan.
premisestringPast 800 characters the middle is cut.
stuck_notestringWhat is wrong, in the writer's words; past 1,500 characters the middle is cut.
last_passagestringThe end of what they wrote last (may be empty); past 2,500 characters the START is cut and the end kept, since the stall is where the passage stops.
planstringThe scene calendar from NovelKit.carryPlan: a logline line, then one line per writing day, Day N (date) [turning point]: scene | prompt. Past 6,000 characters, days far from today lose their prompt and then their detail first. May be empty.
page_pacestring (JSON){today_day, words_written, words_expected_by_today, words_ahead, words_behind, writing_days_left, words_per_day_to_finish, streak_days, days_logged, projected_total_at_this_pace, goal, status} from the word log.
upcoming_daysstring (JSON)[{day, date, words}]: the next writing days after today, up to 3. The reply's next_scenes use exactly these days.
page_flagsstring (JSON)[{level, text}]: no calendar carried, words behind, last day.
retry_notestringOnly on a reformat retry.

The reply

The job's output.output is a string holding one JSON object. Parse it with NovelKit.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" - writing days without a scene, scenes on rest days, turning-point labels on the wrong days, a verdict that differs from the page's read, next days or a rewind day that do not fit the calendar, word figures not on the page. NovelKit.applyNext(plan, unstick, state) folds the re-planned days into the calendar.

Worked examples

Replies produced against the app's prompt for the three sample novels (made-up premises, notes and word logs; long fields and 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: plan - lighthouse mystery - go

Input:

{
 "task": "plan",
 "title": "The Keeper's Log",
 "genre": "cozy mystery",
 "premise": "When her uncle, the last keeper of the Gullrock light, is arrested for the wreck of a fishing boat, Mara Quill comes bac ... (239 characters)",
 "protagonist": "Mara Quill, a hospital night-shift nurse who left the island at eighteen",
 "want": "clear her uncle's name before the inquiry closes the case",
 "obstacle": "Edwin Pell, the harbour master, who framed the keeper to hide his own smuggling, and an island that closes ranks around him",
 "ending": "Mara finds the torn page, Pell is exposed, and she stays on as the new keeper.",
 "template": "Mystery",
 "goal": "50000",
 "page_days": "[{\"day\":1,\"date\":\"Sun Nov 1\",\"words\":1670},{\"day\":2,\"date\":\"Mon Nov 2\",\"words\":1670},{\"day\":3,\"date\":\"Tue Nov 3\",\"words\":1670},{\"day\":4,\"date\":\"Wed Nov 4\",\"word ... (1303 characters)",
 "page_beats": "[{\"beat\":\"The crime\",\"day\":1},{\"beat\":\"The case opens\",\"day\":4},{\"beat\":\"First suspect falls\",\"day\":9},{\"beat\":\"Midpoint twist\",\"day\":15},{\"beat\":\"Sleuth in dan ... (235 characters)",
 "page_flags": "[]",
 "page_verdict": "go"
}

Reply:

{
 "task": "plan",
 "verdict": "go",
 "title": "The Keeper's Log, ready to draft",
 "summary": "All three pillars you need are already on the page - Mara, her want to clear Hugh, and Pell's frame-up - so you get a full month of scenes built straight through your calendar's beats. The thing to protect as you draft:  ...",
 "logline": "When her uncle is framed for a deadly wreck, hospital night-shift nurse Mara Quill returns to the island she fled at eighteen to prove he lit the lighthouse lamp before Edwin Pell's smuggling run can close the inquiry for good.",
 "assumptions": [],
 "questions": [],
 "acts": [
  {
   "name": "Return to Gullrock",
   "days": "1-4",
   "purpose": "Mara arrives to a harbour already certain of her uncle's guilt and commits to finding out why the lamp's only proof has vanished."
  },
  {
   "name": "Digging the Channel",
   "days": "5-15",
   "purpose": "Mara and her allies trace the wreck's real cause through suspects and shipping records until the pattern behind it comes into view."
  },
  "... (4 in all)"
 ],
 "scenes": [
  {
   "day": 1,
   "beat": "The crime",
   "scene": "Mara steps off the ferry into a harbour already full of talk about the wrecked Merle Jean, her uncle Hugh in a holding cell, and Edwin Pell telling everyone who'll listen that the lighthouse lamp was dark that night.",
   "prompt": "Three people ask if she's heard, before she's off the ramp."
  },
  {
   "day": 2,
   "beat": "",
   "scene": "In the cramped harbour-station office, Hugh swears to Mara that he climbed the tower and lit the lamp himself, but when she asks for his logbook, the page for that night has been cut out in one clean line.",
   "prompt": "A straight edge where a night should be."
  },
  {
   "day": 3,
   "beat": "",
   "scene": "Mara takes a room at Nan Pryce's guesthouse, and over cooling tea Nan tells her most of the island already believes Hugh fell asleep at his post, and that nobody crosses the harbourmaster twice.",
   "prompt": "A list of names not to ask."
  },
  "... (30 in all)"
 ]
}

task: plan - cargo-pilot story - patch

Input:

{
 "task": "plan",
 "title": "Long Haul to Ceres",
 "genre": "science fiction",
 "premise": "A slow cargo hauler three weeks out from Ceres answers a distress beacon from a mining relay station far off its route,  ... (168 characters)",
 "protagonist": "Ines Varga, a junior pilot on her first run as acting captain",
 "want": "bring all eleven survivors home to Ceres alive",
 "obstacle": "",
 "ending": "",
 "template": "Three acts",
 "goal": "50000",
 "page_days": "[{\"day\":2,\"date\":\"Mon Nov 2\",\"words\":1850},{\"day\":3,\"date\":\"Tue Nov 3\",\"words\":1850},{\"day\":4,\"date\":\"Wed Nov 4\",\"words\":1850},{\"day\":5,\"date\":\"Thu Nov 5\",\"word ... (1087 characters)",
 "page_beats": "[{\"beat\":\"Opening\",\"day\":2},{\"beat\":\"Inciting incident\",\"day\":4},{\"beat\":\"First turn\",\"day\":9},{\"beat\":\"Midpoint\",\"day\":16},{\"beat\":\"All is lost\",\"day\":23},{\"be ... (214 characters)",
 "page_flags": "[{\"level\":\"info\",\"text\":\"Not filled in: what stands in the way. The premise may say it; if not, the calendar will have to assume it.\"},{\"level\":\"info\",\"text\":\"N ... (263 characters)",
 "page_verdict": "patch"
}

Reply:

{
 "task": "plan",
 "verdict": "patch",
 "title": "Long Haul to Ceres, ready with one assumption",
 "summary": "Your protagonist and want are both clear - Ines Varga taking her first command, determined to bring all eleven survivors home - but nothing you gave names what stands in her way, so the calendar assumes it's the ship its ...",
 "logline": "When Ines Varga, a junior pilot thrown into her first command, pulls eleven stranded miners off a dying relay station, she has three weeks to stretch a hauler stocked for four crew across fifteen people - or watch the people she saved die of simple math before Ceres comes into view.",
 "assumptions": [
  "Assumed: the obstacle is the ship, not a person - a hauler stocked for a crew of four was never meant to carry fifteen all the way to Ceres, so the resource math is what stands in Ines's way.",
  "That shortfall runs through every act: what gets jettisoned, what gets rationed, and whose need gets argued for."
 ],
 "questions": [],
 "acts": [
  {
   "name": "Taking Them Aboard",
   "days": "2-9",
   "purpose": "Ines takes command of the hauler and pulls eleven survivors off the dying relay station, only to find the ship was never provisioned to carry them all the way to Ceres."
  },
  {
   "name": "The Long Ration",
   "days": "10-23",
   "purpose": "The rescue's cost comes due as supplies run thinner than planned, crew and survivors start pulling together, and just when Ines finds a way to ask Ceres for help, that help falls through."
  },
  "... (3 in all)"
 ],
 "scenes": [
  {
   "day": 2,
   "beat": "Opening",
   "scene": "Ines Varga, still finding her footing as acting captain after Captain Okonkwo's fall, hears a mining relay station's distress beacon crackle through the hauler's comms and turns the slow ship off its course to answer it.",
   "prompt": "A beacon's signal, faint and repeating, on an otherwise quiet bridge."
  },
  {
   "day": 3,
   "beat": "",
   "scene": "While the hauler closes the distance to the relay station, Ines and the engineer run the numbers on how much air, water and food the ship's four crew can spare for whoever is still alive over there, and the sums come up short before they've seen a single survivor.",
   "prompt": "A supply manifest that doesn't balance, no matter how it's added up."
  },
  {
   "day": 4,
   "beat": "Inciting incident",
   "scene": "Ines brings the hauler alongside the wrecked relay station, and her crew pulls eleven survivors through the airlock one by one, led by the station's foreman, Petra Nkemelu, who tells Ines flatly that the ship's stores were never meant to stretch across fifteen people for three weeks.",
   "prompt": "An airlock cycling open on eleven faces that weren't expecting rescue so soon."
  },
  "... (25 in all)"
 ]
}

task: plan - a lake and a feeling - rethink

Input:

{
 "task": "plan",
 "title": "",
 "genre": "literary fiction",
 "premise": "Something about a summer at a lake, and grief, and a house that has to be sold.",
 "protagonist": "",
 "want": "",
 "obstacle": "",
 "ending": "",
 "template": "Four parts (kishotenketsu)",
 "goal": "50000",
 "page_days": "[{\"day\":1,\"date\":\"Sun Nov 1\",\"words\":1920},{\"day\":2,\"date\":\"Mon Nov 2\",\"words\":1920},{\"day\":3,\"date\":\"Tue Nov 3\",\"words\":1920},{\"day\":5,\"date\":\"Thu Nov 5\",\"word ... (1129 characters)",
 "page_beats": "[{\"beat\":\"Introduction\",\"day\":1},{\"beat\":\"Development\",\"day\":8},{\"beat\":\"Twist\",\"day\":17},{\"beat\":\"Reconciliation\",\"day\":27}]",
 "page_flags": "[{\"level\":\"warn\",\"text\":\"Not filled in: who the story follows, what they want, what stands in the way. The premise may say it; if not, the calendar will have to ... (302 characters)",
 "page_verdict": "rethink"
}

Reply:

{
 "task": "plan",
 "verdict": "rethink",
 "title": "Lake house novel, discovery week ahead",
 "summary": "The premise gives you a lake house, a summer, and grief, but no one yet to carry the story — protagonist, want, and obstacle are all still open. This week's plan uses scenes built around choices rather than decisions alr ...",
 "logline": "Someone who comes back to the lake house for one last summer must decide what grief will let them keep before it's sold.",
 "assumptions": [],
 "questions": [
  "Whose summer is this on the page — are you writing as the person who has to deal with the lake house, or someone pulled into their orbit?",
  "What does that person want out of this summer — to keep the house, to finish something left undone, or to get away from someone?",
  "... (5 in all)"
 ],
 "acts": [
  {
   "name": "Discovery week",
   "days": "1-8",
   "purpose": "You'll spend this week finding out who this story belongs to, what they want, and what's standing in their way, through scenes built around choices instead of decisions already made for you."
  }
 ],
 "scenes": [
  {
   "day": 1,
   "beat": "Introduction",
   "scene": "Someone comes back to the lake house for what might be its last summer — a grown child, a widow, or someone else entirely; write whichever one walks up from the dock — and finds the rooms arranged exactly the way grief left them.",
   "prompt": "The screen door hasn't latched right in years, and nobody ever fixed it."
  },
  {
   "day": 2,
   "beat": "",
   "scene": "Alone in the house, that same person goes room by room and turns up a small, wrong-seeming object among the dead person's things, something that doesn't match the story they thought they knew.",
   "prompt": "What does the object weigh, and why has no one claimed it?"
  },
  {
   "day": 3,
   "beat": "",
   "scene": "At the house, a call or a knock brings the sale into the room — a realtor, a sibling, or a buyer already circling — and the person living with the grief pushes back, gently or not.",
   "prompt": "Someone says, 'We have to talk about the house,' before they've even put their bag down."
  },
  "... (7 in all)"
 ]
}

task: unstick - a reveal with no setup - rewind

Input:

{
 "task": "unstick",
 "title": "The Keeper's Log",
 "genre": "cozy mystery",
 "premise": "When her uncle, the last keeper of the Gullrock light, is arrested for the wreck of a fishing boat, Mara Quill comes bac ... (239 characters)",
 "stuck_note": "The unmasking on day 27 depends on the inquiry believing Davey, the surviving crewman. But that's not what I wrote. On day 5 I made Davey a  ... (337 characters)",
 "last_passage": "The reply from the inquiry panel was one line long. Mara read it twice at Nan's kitchen table, then folded it into her p ... (330 characters)",
 "plan": "Logline: When her uncle is framed for a deadly wreck, hospital night-shift nurse Mara Quill returns to the island she fled at eighteen to prove he lit the light ... (5934 characters)",
 "page_pace": "{\"today_day\":18,\"words_written\":27322,\"words_expected_by_today\":30060,\"words_ahead\":0,\"words_behind\":2738,\"writing_days_left\":12,\"words_per_day_to_finish\":1890,\"streak_days\":10,\"days_logged\":17,\"proje ... (262 characters)",
 "upcoming_days": "[{\"day\":19,\"date\":\"Thu Nov 19\",\"words\":1670},{\"day\":20,\"date\":\"Fri Nov 20\",\"words\":1670},{\"day\":21,\"date\":\"Sat Nov 21\",\"words\":1670}]",
 "page_flags": "[{\"level\":\"info\",\"text\":\"2,738 words behind the calendar.\"}]"
}

Reply:

{
 "task": "unstick",
 "verdict": "rewind",
 "title": "The Keeper's Log: Davey needs a window",
 "summary": "The unmasking doesn't actually fail because Davey's a passenger instead of crew - a witness can swear to what he saw either way. It fails because he was drunk below deck for the whole night, so he never actually saw the  ...",
 "diagnosis": {
  "symptom": "You're sure the unmasking can't hold up because Davey can't be trusted as a witness. Between him being a passenger who begged a ride, not crew, and spending the whole night drunk below deck on day 9, you wrote that nothing he swears to will hold up.",
  "cause": "You don't actually have a problem with the passenger detail - your calendar's own day 5 line still calls him 'the only crewman,' but crew or passenger, a witness can swear to what he saw. The real break is day 9: drunk below deck for the whole night, Davey never saw the boat cut off-channel or the second boat's lights, so there's nothing true behind what he's meant to sign on day 23 and swear to on day 27.",
  "need": "Give him a few sober, awake minutes on deck on day 9, before the drinking starts, so the one thing the inquiry needs from him on day 27 is something he actually witnessed."
 },
 "rewind": {
  "day": 9,
  "change": "Let Davey be topside long enough, before he goes below to drink, to see the Merle Jean turn off the marked channel on Pell's orders and catch the second boat's running lights - that's the one thing from that night he actually has to swear to later, and it's true before he ever touches a bottle.",
  "note_to_self": "Davey saw the turn and the second boat's lights before he went below and started drinking - that's the one true thing he can put his name to."
 },
 "next_scenes": [
  {
   "day": 19,
   "scene": "Past midnight, Mara lets herself into Pell's harbourside warehouse looking for anything that backs up what Davey has told her, and finds crates stacked under tarps that match nothing on a single one of his manifests.",
   "prompt": "A tarp that smells wrong before she even lifts it."
  },
  {
   "day": 20,
   "scene": "Behind a loose board, Mara finds a tin box of torn logbook pages going back years, and near the top, still curling at its cut edge, the page for the night of the wreck.",
   "prompt": "An edge that hasn't had time to flatten."
  },
  "... (3 in all)"
 ],
 "sprint": {
  "minutes": 15,
  "prompt": "Stay at Nan's table: have Mara go back through every name on her list out loud, crossing each one off for a reason, until Pell's is the only one left - then write the first line of what she decides to do about a man who never leaves anything behind."
 },
 "pace_note": "You're 2,738 words behind the calendar, but you've got a 10-day streak going - 1,890 words a day from here gets you to the 50,000-word goal."
}

task: unstick - a side character took over - reroute

Input:

{
 "task": "unstick",
 "title": "Long Haul to Ceres",
 "genre": "science fiction",
 "premise": "A slow cargo hauler three weeks out from Ceres answers a distress beacon from a mining relay station far off its route,  ... (168 characters)",
 "stuck_note": "Dov the engineer has completely taken over. Every scene I write turns into him patching the recyclers and arguing with Ines, and honestly it ... (361 characters)",
 "last_passage": "\"You don't get to be captain and also be nice about it,\" Dov said, elbow-deep in the scrubber housing. \"Pick one.\" Ines  ... (286 characters)",
 "plan": "Logline: When Ines Varga, a junior pilot thrown into her first command, pulls eleven stranded miners off a dying relay station, she has three weeks to stretch a ... (5985 characters)",
 "page_pace": "{\"today_day\":13,\"words_written\":19160,\"words_expected_by_today\":21280,\"words_ahead\":0,\"words_behind\":2120,\"writing_days_left\":14,\"words_per_day_to_finish\":2203,\"streak_days\":10,\"days_logged\":10,\"proje ... (262 characters)",
 "upcoming_days": "[{\"day\":14,\"date\":\"Sat Nov 14\",\"words\":2780},{\"day\":16,\"date\":\"Mon Nov 16\",\"words\":1850},{\"day\":17,\"date\":\"Tue Nov 17\",\"words\":1850}]",
 "page_flags": "[{\"level\":\"info\",\"text\":\"2,120 words behind the calendar.\"}]"
}

Reply:

{
 "task": "unstick",
 "verdict": "reroute",
 "title": "Long Haul to Ceres: Dov takes the wheel",
 "summary": "Dov has taken over the book's energy, and that's not the problem - the problem is that the next two scenes on your calendar leave him out and go quiet. Reroute them to carry his friction forward instead of parking Ines a ...",
 "diagnosis": {
  "symptom": "You said it yourself: Dov has taken the story over, and the two scenes waiting for you - the captain lecturing from his bunk, Ines messaging Ceres - feel like pure talk you don't want to sit down to.",
  "cause": "The real tension you've actually written lives in the room with Dov and Ines - \"You don't get to be captain and also be nice about it\" - while the bunk scene and the Ceres message on your calendar are staged as solo, talky beats with no one there to push back on her.",
  "need": "Your next few scenes need that same friction in the room: let Dov's pressure follow Ines into the next two decisions, instead of leaving her to talk to an empty bunk and a radio."
 },
 "rewind": null,
 "next_scenes": [
  {
   "day": 14,
   "scene": "Dov follows Ines to Okonkwo's bunk with the recycler numbers already run. When the captain insists from his bed that the cargo still matters more, Ines already has Dov's figures ready to back her refusal to move it.",
   "prompt": "An order from a bed he can't leave - and this time Dov already told her what to say back."
  },
  {
   "day": 16,
   "scene": "With Dov's jury-rigged filter and recycler numbers behind her, Ines overrides Okonkwo outright and commits the hauler fully to the fifteen people aboard over what's left of the cargo. She sends Ceres a message that tells the truth about how thin their margin has become, instead of the reassurance everyone back home is expecting.",
   "prompt": "A message to Ceres that finally tells the truth - and Dov has nothing left to argue with."
  },
  "... (3 in all)"
 ],
 "sprint": {
  "minutes": 15,
  "prompt": "Stay right here for fifteen minutes: Dov's back at the scrubber, elbow-deep, not meeting Ines's eye. Give him one more line that admits he heard her, without taking any of it back."
 },
 "pace_note": "You're 2,120 words behind the calendar right now, and it's 2,203 words a day the rest of the way - once you're back to scenes you want to write, that's an easy gap to close."
}

task: unstick - skipped a day - keep going

Input:

{
 "task": "unstick",
 "title": "",
 "genre": "literary fiction",
 "premise": "Something about a summer at a lake, and grief, and a house that has to be sold.",
 "stuck_note": "I wrote three days in a row and loved it, took my rest day, then skipped yesterday too and now I feel so far behind that it seems pointless  ... (154 characters)",
 "last_passage": "The dock had lost two boards over the winter and nobody had noticed, because nobody had been here to notice. She sat on  ... (182 characters)",
 "plan": "Logline: Someone who comes back to the lake house for one last summer must decide what grief will let them keep before it's sold.\nDay 1 (Sun Nov 1) [Introductio ... (2200 characters)",
 "page_pace": "{\"today_day\":6,\"words_written\":5230,\"words_expected_by_today\":9600,\"words_ahead\":0,\"words_behind\":4370,\"writing_days_left\":21,\"words_per_day_to_finish\":2132,\"streak_days\":0,\"days_logged\":4,\"projected_ ... (257 characters)",
 "upcoming_days": "[{\"day\":7,\"date\":\"Sat Nov 7\",\"words\":1920},{\"day\":8,\"date\":\"Sun Nov 8\",\"words\":1920},{\"day\":9,\"date\":\"Mon Nov 9\",\"words\":1920}]",
 "page_flags": "[{\"level\":\"info\",\"text\":\"4,370 words behind the calendar.\"}]"
}

Reply:

{
 "task": "unstick",
 "verdict": "keep_going",
 "title": "Lake house grief summer, winded not broken",
 "summary": "Your story hasn't broken - the page you stopped on sits right where your calendar means it to, a quiet beat just before something gives. What's actually stuck is the momentum a missed evening broke, not the plot. The one ...",
 "diagnosis": {
  "symptom": "After three strong days and your own rest day, missing yesterday too left you feeling - in your words - that \"it seems pointless to keep going.\"",
  "cause": "Nothing in the story itself has gone wrong - your last page stopped exactly where your calendar means it to, with her feet in the cold water as she \"counted the gaps\" in the dock. That's a quiet, held-breath beat with nothing yet happening to carry you into the next line, which is exactly the kind of place where a skipped evening turns into two.",
  "need": "Not to close the gap in one sitting - just to put one small thing back on that dock tonight, so the days after it (the confession that slips out during ordinary work, the sale that forces a choice) have somewhere to start from."
 },
 "rewind": null,
 "next_scenes": [
  {
   "day": 7,
   "scene": "She picks one ordinary task at the house - fixing that dock board, or clearing a single closet, whichever she's already circling - and keeps her hands busy long enough that the thing she hasn't said starts coming out sideways, to whoever is nearby to hear it.",
   "prompt": "What is she holding when the thing she hasn't said finally comes out sideways?"
  },
  {
   "day": 8,
   "scene": "The sale takes its next real step - papers, a phone call, or someone standing in the kitchen waiting on an answer, whichever has already shown up in her days at the house - and for once she can't just react: she has to choose a side, out loud.",
   "prompt": "What does she say that she can't take back?"
  },
  "... (3 in all)"
 ],
 "sprint": {
  "minutes": 15,
  "prompt": "Stay right there on the dock with her feet still in the cold water, and give her one small interruption - a sound from the house behind her, a splinter off the board, anything that makes her move - just to get a sentence past where she counted the gaps."
 },
 "pace_note": "You're 4,370 words behind, and it would take about 2,132 words a day from here to still hit goal - steep, but it only starts moving once you write again."
}

Build the input yourself

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

const K = NovelKit;
const form = { title: "The Keeper's Log", genre: "cozy mystery", premise: "When her uncle ... is arrested ...",
  protagonist: "Mara Quill", want: "clear her uncle's name", obstacle: "the harbour master who framed him",
  template: "mystery", start: "2026-11-01", goal: "50000", rest: "", boost: "1" };
const st = K.state(form);                                 // schedule, turning points, premise read, pace
console.log(st.sched.writing.map(d => d.target), st.beats, st.P.verdict);
const wire = K.toWire(K.buildInput(st, "plan").input);    // POST this to /estimate and /run
// then: const plan = K.normalize(K.parseReply(job.output.output), "plan");
// mid-month: form.carried = K.carryPlan(plan, st); form.log = JSON.stringify({1: 1702, 2: 1680});
// form.today_day = "18"; form.stuck = "the twist won't land"; K.toWire(K.buildInput(K.state(form), "unstick").input)
// const { notes } = K.check(reply, K.state(form), "unstick");

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://novel-in-november.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":"novel-in-november"}' | 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": "plan", "title": "The Keeper's Log", "genre": "cozy mystery", "premise": "When her uncle, the last keeper of the Gullrock light, is arrested ...", "protagonist": "Mara Quill ...", "want": "...", "obstacle": "...", "ending": "...", "template": "Mystery", "goal": "50000", "page_days": "[...]", "page_beats": "[...]", "page_flags": "[]", "page_verdict": "go"}
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 novel-in-november:<task>:<inputHash>:a<attempt> (the examples below use the plan lane; an unstick run differs only in its task and fields), 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="novel-in-november:plan:$(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: novel-in-november:plan:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json