← Status Cadence
API
Tokens

Run Status Cadence from your own code

Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page parses the timeline, holds every next-update promise to the next post, measures the lag behind the alert, checks the wording and the components, and ranks the channels and the postmortem depth, then hands it all to the run as facts. The run rewrites the timeline or drafts the postmortem; the page then holds it to its own facts (screply.js).

Base URL and the response shape

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

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

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

StatusCodeMeaning
400validation_errorThe body is not a JSON object, or a required key (task, incident, timeline, facts) is missing. The page always sends all five of task, incident, timeline, carry, facts; carry is an empty string for rewrite.
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 sign-in.
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

Two lanes, chosen by task, in the order the work runs - the updates as they should have read, then the write-up after it is resolved:

A missing or unknown task is answered as rewrite. Both lanes send the same five fields. The facts are the page's checks, one row per line, and a line starting "must:" is a rule the reply has to hold. The page's handoff carries the rewrite into carry for the postmortem lane.

The input

FieldRequiredWhat
taskyesrewrite or postmortem. If it is missing or neither, do rewrite and set "task": "rewrite".
incidentyesthe service, the title as posted, the readers, the interview's answers, the channels in place, the alert and third-party times, and the components as name | state | scope; "not given" means not given.
timelineyesthe posted updates oldest first, one per line as #n | time | status | message.
carrysend it (empty for rewrite)postmortem task only: the rewritten timeline, when the rewrite task ran first; may be empty.
factsyesthe page's checks, one line each as area | id | status | evidence, the issues in fix order and each update's promise outcome. A line starting "must:" applies to your reply.
retry_notenooptional; if present, your previous reply was unusable - follow it.

The rules the model applies

From the source skill, @samber/api-status-communication - the only standard the run applies:

House rules

The replies

rewrite

Rewrite every posted update as it should have read - same moment, same status, same facts - so the timeline passes the five practices.

  1. summary - one sentence, at most 40 words: what was wrong with the timeline as posted and what the rewrite changes.
  2. title - the incident title in plain language: the symptom, the affected component a consumer recognizes and the scope (a region when only a region was hit). No codename.
  3. updates - exactly one object per posted update, in order: n (the update's number), status (its posted status, unchanged: investigating, identified, monitoring, resolved or update), text (two to four sentences). Each text says what is affected and for whom, what is confirmed (only what that update or an earlier one establishes - never a later update's knowledge), and what is being done. Every update that is not resolved ends with its next-update promise, exactly as the "must:" line gives it, in the form "Next update by HH:MM ZONE.". An update that had nothing new says so plainly - silence is the failure, not "nothing new". The resolved update states when service returned to normal if the input gives it, acknowledges the impact and apologizes, and says whether a postmortem follows. Scope by the components: a regional break reads as regional, never "the API is down".
  4. caveats - at most 4 short strings: facts the posted updates lacked that the team should have stated (a missing stage, an unknown start time); [] when there is nothing to say.
{
  "task": "rewrite",
  "summary": "one sentence",
  "title": "Elevated error rates on the Payments API in eu-west",
  "updates": [ { "n": 1, "status": "investigating", "text": "two to four sentences. Next update by 10:35 UTC." } ],
  "caveats": [ ]
}

The page holds it to its own facts (check() in screply.js; any warning makes the reply flagged): the reply answers the task it was sent; no URL; no backticks; a summary of at most 40 words; a plain-language title naming the symptom with no internal name; no person named and no vendor blamed; exactly one update per posted update, in order, each with its posted status; every open update carrying the next-update time from the must line; a zone on every clock time; no speculation, no internal names, no blanket outage claim the components contradict; the resolved update acknowledging the impact.

postmortem

Draft the public postmortem at the rung the page ranked - from the carried rewrite when there is one, else from the timeline.

  1. summary - one sentence, at most 40 words: what happened, for how long, to whom.
  2. rung - blog, summary or note, exactly as the facts rank it.
  3. title - a plain-language title, as in the rewrite task.
  4. impact - two or three sentences: the window in minutes exactly as the facts give it (for example "156 minutes"), the start and end times with their zone, the affected components and their scope, and what consumers saw.
  5. timeline - objects in time order: time (a time the input gives, with its zone) and event (one sentence). At least four entries for blog, at least three for summary, may be empty for note. Include the alert, the first public post and the resolution when the input gives them.
  6. cause - why did this happen: the cause as the input establishes it, as our own system's failure; "not yet established" when it does not.
  7. worse - could it have been worse: what limited the blast radius, or what nearly widened it - only from the input; "not established from the timeline" when the input does not say.
  8. fix - what restored service, from the input.
  9. prevention - objects: action (one sentence: a concrete change that addresses this cause or this incident's communication gaps the facts list), owner (a role - never a person's name). At least two for blog, at least one for summary, may be empty for note.
  10. caveats - at most 4 short strings: what the team must confirm before publishing (a cause still unverified, a customer count unknown); [] when there is nothing to say.
{
  "task": "postmortem",
  "summary": "one sentence",
  "rung": "summary",
  "title": "a plain title",
  "impact": "two or three sentences",
  "timeline": [ { "time": "09:02 UTC", "event": "one sentence" } ],
  "cause": "two or three sentences",
  "worse": "one or two sentences",
  "fix": "one or two sentences",
  "prevention": [ { "action": "one sentence", "owner": "a role" } ],
  "caveats": [ ]
}

The page requires: the reply answers the task it was sent; the same text rules; the rung the page ranked; the impact window in minutes exactly as the facts give it; every timeline time one the input gives, with its zone; at least four timeline entries and two prevention items for the blog rung, three and one for the summary rung; an owner on every prevention item; the why, worse and fix questions answered; an unestablished cause written as not yet established.

Worked examples

Three runs from the page's free samples (invented incidents): the rewrite for a payments API whose page trailed the outage, the postmortem for the same incident carrying that rewrite, and the rewrite for a geocoding outage run by the book. Each input is exactly what the page sends; each reply is the saved reply the sample replays, shipped unedited - but the facts and long strings are shortened in this display only, and every cut is listed under the example. The last line is the page's own check of the full reply.

rewrite - A payments API whose page trailed the outage

The page read it as: status theater - the page trailed the outage; first post 39 minutes after the alert; 156 minutes long; postmortem rung blog; 0 of 5 gates; failing checks: disclosed, lifecycle, promises, plain, owned, breadth, statusBy.

Input (facts is 4323 characters; the first 1394 are shown):

{
 "task": "rewrite",
 "incident": "Service: Brindlequay API\nIncident title as posted: INC-4471 Falcon gateway degradation\nReaders: A mix of both; enterprise customers: yes; contractual SLAs with consequences: yes\nStatus set by: a person; the status page shares infrastructure with the platform: yes; consumers visibly felt it: yes; an active engineering blog: no\nChannels in place: email and RSS yes, webhook no, SMS no; deadline: no; a compounding trust asset; effort ceiling: medium\nMonitoring alert fired: 2026-10-28 09:02 UTC; first third-party report: 2026-10-28 09:20 UTC\nComponents (name | state | scope):\n- Payments API | Major outage | eu-west only\n- Dashboard | Operational | all users\n- Webhooks | Degraded performance | eu-west only",
 "timeline": "Updates oldest first (#n | time | status | message):\n#1 | 2026-10-28 09:41 UTC | Investigating | We are investigating reports of issues with the API. We think it might be related to a recent change. We will provide another update in 30 minutes.\n#2 | 2026-10-28 10:32 UTC | Investigating | The API is down. This is due to an outage at our cloud provider in eu-west-1 and is outside our control. Engineer Tamsin Quarrow is working with the provider on it.\n#3 | 2026-10-28 11:05 UTC | Monitoring | Payments are processing again. We are monitoring. Next update by 11:45.\n#4 | 2026-10-28 11:38 UTC | Resolved | This incident has been resolved.",
 "carry": "",
 "facts": "The page's checks (7 fail, 4 warn, 2 pass, 3 n/a); verdict: status theater - the page trailed the outage:\n- The lifecycle and the cadence | disclosed | fail | first post 39 minutes after the alert; third party first, by 21 minutes\n- The lifecycle and the cadence | lifecycle | fail | missing Identified\n- The lifecycle and the cadence | promises | fail | 1 update (#2) names no next-update time; #1 promised in 30 minutes and the next post came 21 minutes late\n- The lifecycle and the cadence | cadence | pass | 51 minutes between #1 and #2\n- The wording | plain | fail | the title: internal name 'INC-4471'; #3 promises 'by 11:45' with no zone\n- The wording | owned | fail | deflects - #2: \"This is due to an outage at our cloud provider in eu-west-1 and is outside our control.\"; names 'Tamsin Quarrow' - a public update never names a person\n- The wording | precise | warn | #1 ('We think') - state what is confirmed, not what is suspected\n- The wording | scope | warn | #2 says the whole thing is down, while the components say Payments API major outage (eu-west only), Webhooks degraded performance (eu-west only)\n- The wording | empathy | warn | no update acknowledges the impact or apologizes\n- The components | worst | na | Major outage - Payments API (eu-west only), Webhooks (eu-west only)\n- The components | breadth | fail | Payments API is major outage for eu-west only - broken for a\n\n[... 2929 more characters cut from this display ...]"
}

Reply (long strings shortened for display - see the cuts below):

{
 "task": "rewrite",
 "summary": "The posted updates used an internal ticket title, guessed at a cause, blamed our cloud provider, named an engineer and broke their own promises; the rewrite scopes the outage to eu-west, owns the cause and keeps every next-update time.",
 "title": "Payments API outage and delayed webhooks in eu-west",
 "updates": [
  {
   "n": 1,
   "status": "investigating",
   "text": "Since 09:02 UTC, payment requests to the Payments API in eu-west are failing, and webhook delivery in eu-west is delayed. The Dashboard and other regions are working normally. Our on-call team is investigating and the cause is not yet [... 39 more characters cut from this display]"
  },
  {
   "n": 2,
   "status": "investigating",
   "text": "The Payments API in eu-west is still unavailable and webhook delivery in eu-west is still delayed; the Dashboard and other regions remain unaffected. We have confirmed the failure is in the cloud infrastructure our platform depends on in [... 143 more characters cut from this display]"
  },
  {
   "n": 3,
   "status": "monitoring",
   "text": "As of 11:05 UTC, payments are processing again through the Payments API in eu-west. We are monitoring payment processing and webhook delivery in eu-west to confirm the recovery holds. We are sorry for the disruption this has caused. Next [... 21 more characters cut from this display]"
  },
  {
   "n": 4,
   "status": "resolved",
   "text": "This incident is resolved as of 11:38 UTC; payments have been processing normally in eu-west since 11:05 UTC. From 09:02 UTC, payments through the Payments API in eu-west failed and webhook delivery in eu-west was delayed, and we apologize [... 169 more characters cut from this display]"
  }
 ],
 "caveats": [
  "No Identified update was posted once the cause was confirmed.",
  "The first public post came 39 minutes after our alert fired at 09:02 UTC, and 21 minutes after a third party reported it.",
  "Update #2 named no next-update time, and #1's promised update came 21 minutes late."
 ]
}

Cut from this display only (the saved reply and the check use the full text): updates[0].text (273 characters, 234 shown); updates[1].text (380 characters, 237 shown); updates[2].text (258 characters, 237 shown); updates[3].text (408 characters, 239 shown).

What step 5 prints for it: 1 investigating | 2 investigating | 3 monitoring | 4 resolved

The page's check: kept - nothing to flag

postmortem - A payments API whose page trailed the outage

The input carries the saved rewrite (R.laneForm fills carry from the rewrite reply), as the page's handoff does. The rung: blog; the issues in fix order: disclosure, lifecycle, promises, timezone, title, deflect, blame, precise, breadth, scope, empathy, channels.

Input (facts is 4208 characters; the first 1394 are shown; carry is 1482 characters; the first 1390 are shown):

{
 "task": "postmortem",
 "incident": "Service: Brindlequay API\nIncident title as posted: INC-4471 Falcon gateway degradation\nReaders: A mix of both; enterprise customers: yes; contractual SLAs with consequences: yes\nStatus set by: a person; the status page shares infrastructure with the platform: yes; consumers visibly felt it: yes; an active engineering blog: no\nChannels in place: email and RSS yes, webhook no, SMS no; deadline: no; a compounding trust asset; effort ceiling: medium\nMonitoring alert fired: 2026-10-28 09:02 UTC; first third-party report: 2026-10-28 09:20 UTC\nComponents (name | state | scope):\n- Payments API | Major outage | eu-west only\n- Dashboard | Operational | all users\n- Webhooks | Degraded performance | eu-west only",
 "timeline": "Updates oldest first (#n | time | status | message):\n#1 | 2026-10-28 09:41 UTC | Investigating | We are investigating reports of issues with the API. We think it might be related to a recent change. We will provide another update in 30 minutes.\n#2 | 2026-10-28 10:32 UTC | Investigating | The API is down. This is due to an outage at our cloud provider in eu-west-1 and is outside our control. Engineer Tamsin Quarrow is working with the provider on it.\n#3 | 2026-10-28 11:05 UTC | Monitoring | Payments are processing again. We are monitoring. Next update by 11:45.\n#4 | 2026-10-28 11:38 UTC | Resolved | This incident has been resolved.",
 "carry": "Rewritten title: Payments API outage and delayed webhooks in eu-west\nRewritten updates:\n#1 (investigating): Since 09:02 UTC, payment requests to the Payments API in eu-west are failing, and webhook delivery in eu-west is delayed. The Dashboard and other regions are working normally. Our on-call team is investigating and the cause is not yet established. Next update by 10:35 UTC.\n#2 (investigating): The Payments API in eu-west is still unavailable and webhook delivery in eu-west is still delayed; the Dashboard and other regions remain unaffected. We have confirmed the failure is in the cloud infrastructure our platform depends on in eu-west, and that dependency is our responsibility. Our team is working to restore payment processing in the region. Next update by 11:05 UTC.\n#3 (monitoring): As of 11:05 UTC, payments are processing again through the Payments API in eu-west. We are monitoring payment processing and webhook delivery in eu-west to confirm the recovery holds. We are sorry for the disruption this has caused. Next update by 11:45 UTC.\n#4 (resolved): This incident is resolved as of 11:38 UTC; payments have been processing normally in eu-west since 11:05 UTC. From 09:02 UTC, payments through the Payments API in eu-west failed and webhook delivery in eu-west was delayed, and we apologize to everyone whose payments and integrations were affected. We will publish a\n\n[... 92 more characters cut from this display ...]",
 "facts": "The page's checks (7 fail, 4 warn, 2 pass, 3 n/a); verdict: status theater - the page trailed the outage:\n- The lifecycle and the cadence | disclosed | fail | first post 39 minutes after the alert; third party first, by 21 minutes\n- The lifecycle and the cadence | lifecycle | fail | missing Identified\n- The lifecycle and the cadence | promises | fail | 1 update (#2) names no next-update time; #1 promised in 30 minutes and the next post came 21 minutes late\n- The lifecycle and the cadence | cadence | pass | 51 minutes between #1 and #2\n- The wording | plain | fail | the title: internal name 'INC-4471'; #3 promises 'by 11:45' with no zone\n- The wording | owned | fail | deflects - #2: \"This is due to an outage at our cloud provider in eu-west-1 and is outside our control.\"; names 'Tamsin Quarrow' - a public update never names a person\n- The wording | precise | warn | #1 ('We think') - state what is confirmed, not what is suspected\n- The wording | scope | warn | #2 says the whole thing is down, while the components say Payments API major outage (eu-west only), Webhooks degraded performance (eu-west only)\n- The wording | empathy | warn | no update acknowledges the impact or apologizes\n- The components | worst | na | Major outage - Payments API (eu-west only), Webhooks (eu-west only)\n- The components | breadth | fail | Payments API is major outage for eu-west only - broken for a\n\n[... 2814 more characters cut from this display ...]"
}

Reply (long strings shortened for display - see the cuts below):

{
 "task": "postmortem",
 "summary": "For 156 minutes on 2026-10-28, payments through the Payments API in eu-west failed and webhook delivery in eu-west was delayed, because the cloud infrastructure our platform depends on in that region failed.",
 "rung": "blog",
 "title": "Payments API outage and delayed webhooks in eu-west",
 "impact": "For 156 minutes, from 2026-10-28 09:02 UTC to 2026-10-28 11:38 UTC, the Payments API in eu-west was unavailable and webhook delivery in eu-west was degraded. Consumers in eu-west saw payment requests fail and webhooks arrive late, while [... 148 more characters cut from this display]",
 "timeline": [
  {
   "time": "09:02 UTC",
   "event": "Our monitoring alert fired, the start of the impact window."
  },
  {
   "time": "09:20 UTC",
   "event": "A third party reported the problem publicly before our status page did."
  },
  {
   "time": "09:41 UTC",
   "event": "We posted our first public update, Investigating, 39 minutes after the alert."
  },
  {
   "time": "10:32 UTC",
   "event": "We confirmed the failure was in the cloud infrastructure our platform depends on in eu-west, in an update that named no next-update time."
  },
  {
   "time": "11:05 UTC",
   "event": "Payments began processing again in eu-west and we moved to Monitoring."
  },
  {
   "time": "11:38 UTC",
   "event": "We confirmed the recovery held and resolved the incident."
  }
 ],
 "cause": "The Payments API in eu-west failed because the cloud infrastructure it runs on in that region failed. That infrastructure is our dependency, and how the Payments API relies on it is our responsibility. The deeper cause of the [... 120 more characters cut from this display]",
 "worse": "The failure stayed in eu-west: the Dashboard and every other region kept working, and webhooks in eu-west were slowed rather than stopped. It could have been worse for communication, because our status page shares infrastructure with the [... 73 more characters cut from this display]",
 "fix": "Payments resumed in eu-west at 11:05 UTC and we monitored until 11:38 UTC; the specific action that restored service is not yet established in our timeline.",
 "prevention": [
  {
   "action": "Open an Investigating post on the status page automatically when the monitoring alert fires, so the page is never 39 minutes behind the alert again.",
   "owner": "Platform reliability team"
  },
  {
   "action": "Review how the Payments API in eu-west depends on a single region of cloud infrastructure and plan how it keeps processing payments when that region fails.",
   "owner": "Payments engineering lead"
  },
  {
   "action": "Require every non-resolved update to name its next-update time with a time zone, and post an Identified update as soon as a cause is confirmed.",
   "owner": "Incident commander on call"
  },
  {
   "action": "Move the status page off our own infrastructure and add SMS or phone-grade notifications alongside email and RSS.",
   "owner": "Developer relations team"
  }
 ],
 "caveats": [
  "Confirm the root cause of the infrastructure failure before publishing anything beyond our dependency on it.",
  "Confirm what action restored payments at 11:05 UTC.",
  "The number of affected customers and failed payments is not given."
 ]
}

Cut from this display only (the saved reply and the check use the full text): impact (384 characters, 236 shown); cause (345 characters, 225 shown); worse (310 characters, 237 shown).

What step 5 prints for it: 09:02 UTC | 09:20 UTC | 09:41 UTC | 10:32 UTC | 11:05 UTC | 11:38 UTC

The page's check: kept - nothing to flag

rewrite - A geocoding outage run by the book

The page read it as: a timeline readers can trust; first post 3 minutes after the alert; 97 minutes long; postmortem rung blog; 5 of 5 gates; failing checks: none.

Input (facts is 2604 characters; the first 1399 are shown):

{
 "task": "rewrite",
 "incident": "Service: Mossroute Geocoding API\nIncident title as posted: Elevated 503 errors on the Geocoding API in all regions\nReaders: Mostly integrating developers; enterprise customers: no; contractual SLAs with consequences: no\nStatus set by: monitoring; the status page shares infrastructure with the platform: no; consumers visibly felt it: yes; an active engineering blog: yes\nChannels in place: email and RSS yes, webhook yes, SMS no; deadline: no; a compounding trust asset; effort ceiling: high\nMonitoring alert fired: 2026-10-30 14:02 UTC; first third-party report: not given\nComponents (name | state | scope):\n- Geocoding API | Major outage | all users\n- Routing API | Operational | all users\n- Dashboard | Operational | all users\n- Docs | Operational | all users",
 "timeline": "Updates oldest first (#n | time | status | message):\n#1 | 2026-10-30 14:05 UTC | Investigating | Our monitoring detected elevated 503 errors on the Geocoding API in all regions. Forward and reverse geocoding requests are failing for most callers. We are investigating and will post the next update by 14:35 UTC.\n#2 | 2026-10-30 14:31 UTC | Identified | We have identified the cause: a configuration change to the geocoding index rolled out at 13:58 UTC left the serving fleet without a valid index. We are rolling the change back region by region. Next update by 15:00 UTC.\n#3 | 2026-10-30 14:58 UTC | Identified | The rollback is complete in us-east and eu-west; error rates there are back to normal. ap-south is still rolling back. Next update by 15:30 UTC.\n#4 | 2026-10-30 15:24 UTC | Monitoring | The rollback is complete in every region and 503 rates are at baseline. We are monitoring for 15 minutes before resolving. Next update by 15:45 UTC.\n#5 | 2026-10-30 15:39 UTC | Resolved | Geocoding requests have succeeded normally since 15:22 UTC. We are sorry for the disruption this caused to your applications; a full incident report will follow on our engineering blog.",
 "carry": "",
 "facts": "The page's checks (0 fail, 0 warn, 11 pass, 3 n/a); verdict: a timeline readers can trust:\n- The lifecycle and the cadence | disclosed | pass | first post 3 minutes after the alert\n- The lifecycle and the cadence | lifecycle | pass | investigating, identified, monitoring and resolved, in order\n- The lifecycle and the cadence | promises | pass | 4 promises kept, every open update named its next time\n- The lifecycle and the cadence | cadence | pass | 27 minutes between #2 and #3\n- The wording | plain | pass | 'Elevated 503 errors on the Geocoding API in all regions' states the problem; every time carries its zone\n- The wording | owned | pass | no update blames a vendor or names a person\n- The wording | empathy | pass | an update acknowledges the impact\n- The components | worst | na | Major outage - Geocoding API (all users)\n- The components | breadth | pass | every state matches its scope\n- The components | granularity | pass | 4 components, consumer-facing names\n- The practice | statusBy | pass | the alert opens Investigating; people add substance, scope and cadence\n- The practice | channels | pass | Status page with email and RSS subscription > Webhook push > Out-of-band redundancy (page off your own infrastructure); deleted: SMS or phone-grade push; the first pass is in place\n- The practice | depth | na | Engineering-blog postmortem - a major outage - P1-class incidents get a\n\n[... 1205 more characters cut from this display ...]"
}

Reply (long strings shortened for display - see the cuts below):

{
 "task": "rewrite",
 "summary": "The posted timeline was already sound: prompt, owned, every promise kept. The rewrite keeps its facts and promise times, and adds what stayed available and which region was still failing.",
 "title": "Elevated 503 errors on the Geocoding API in all regions",
 "updates": [
  {
   "n": 1,
   "status": "investigating",
   "text": "Our monitoring detected elevated 503 errors on the Geocoding API in all regions at 14:02 UTC. Forward and reverse geocoding requests are failing for most callers; the Routing API, the Dashboard and the Docs are working normally. We are [... 41 more characters cut from this display]"
  },
  {
   "n": 2,
   "status": "identified",
   "text": "We have identified the cause: a configuration change to the geocoding index, rolled out at 13:58 UTC, left the servers that answer geocoding requests without a valid index. Forward and reverse geocoding are still failing for most callers [... 91 more characters cut from this display]"
  },
  {
   "n": 3,
   "status": "identified",
   "text": "The rollback is complete in us-east and eu-west, and Geocoding API error rates there are back to normal. Callers in ap-south are still seeing 503 errors while the rollback finishes in that region. Next update by 15:30 UTC."
  },
  {
   "n": 4,
   "status": "monitoring",
   "text": "The rollback is complete in every region, and Geocoding API 503 rates are back at their normal baseline. We are watching error rates for 15 minutes before we mark this resolved. Next update by 15:45 UTC."
  },
  {
   "n": 5,
   "status": "resolved",
   "text": "Geocoding requests have succeeded normally in all regions since 15:22 UTC. Forward and reverse geocoding failed for most callers during this incident, and we are sorry for the disruption this caused to your applications. A full incident [... 44 more characters cut from this display]"
  }
 ],
 "caveats": [
  "No posted update said that the Routing API, the Dashboard and the Docs stayed available.",
  "No posted update stated when callers first saw errors; the input gives only the 13:58 UTC change and the 14:02 UTC alert."
 ]
}

Cut from this display only (the saved reply and the check use the full text): updates[0].text (276 characters, 235 shown); updates[1].text (328 characters, 237 shown); updates[4].text (280 characters, 236 shown).

What step 5 prints for it: 1 investigating | 2 identified | 3 identified | 4 monitoring | 5 resolved

The page's check: kept - nothing to flag

Build the input yourself

Load the engine in node (global.window = global; require("./sckit.js"); require("./screply.js")), then:

const K = CadenceKit, R = CadenceReply;
const f = K.form(Object.assign(K.blank(), { service: "Acme API", title: "INC-12 latency", alert: "2026-10-28 09:00 UTC",
  updates: "2026-10-28 09:20 UTC | Investigating | We are looking into it. Next update in 30 minutes.\n2026-10-28 10:05 UTC | Resolved | Fixed." }));
// f.updateRows (each with its promise and kept/broken), f.lag, f.duration, f.issues (in fix order), f.gate, f.channels, f.depth
// K.timelineMd(f), K.timelineCsv(f) - the cadence table and the timeline
const b = K.buildInput(f, "rewrite");                        // or "postmortem" (R.laneForm(form, "postmortem", rewrite) fills carry)
const wire = K.toWire(b.input);                              // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "rewrite");
// R.check(reply, b)                                         // {status: kept or flagged, notes}

Get a token for the steps below from /tokens.html.

Step 1 — a tiny client helper

One function that sends JSON and unwraps the response. 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="YOUR_TOKEN"   # from https://status-cadence.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":"status-cadence"}' | 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": "rewrite", "incident": "Service: Brindlequay API
Incident title as posted: ...", "timeline": "Updates oldest first (#n | time | status | message):
#1 | 2026-10-28 09:41 UTC | Investigating | ...", "carry": "", "facts": "The page's checks (...); verdict: status theater - the page trailed the outage:
- The lifecycle and the cadence | disclosed | fail | ..."}
# - see the input table
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 status-cadence:<task>:<inputHash>:a<attempt> - the lane is part of the key, retry_note is left out of the hash - and its one automatic reformat retry is a new attempt number, named in the page as one extra run. A rewrite reply carries summary, title, updates and caveats; a postmortem reply carries summary, rung, title, impact, timeline, cause, worse, fix, prevention and caveats. The code below prints the gist of either: each rewritten update, or each timeline entry.

KEY="status-cadence:$(jq -r .task input.json):$(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}'
jq -r 'if .task == "postmortem" then (.timeline[] | "\(.time): \(.event)") 
       else (.updates[] | "#\(.n) \(.status): \(.text)") end' reply.json

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: status-cadence:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json