← Integration Pulse
API
Tokens

Run Integration Pulse from your own code

Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page classifies the ticket pile against today's surface, splits every integration's errors by family against its own baseline, catches silent integrations, reads blame against the deploy timeline, ranks the breaking ones by installs, ranks the rungs and reads the gates, then hands it all to the run as facts. The run writes the visibility design or the outreach; the page then holds it to its own facts (ipreply.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":"integration-pulse"}), 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, platform, signals, facts) is missing. The page always sends all five of task, platform, signals, carry, facts; carry is an empty string for surface.
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 - what integrators see, then who breaks now:

A missing or unknown task is answered as surface. 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 design's decisions into carry for the outreach lane.

The input

FieldRequiredWhat
taskyessurface or outreach. If it is missing or neither, do surface and set "task": "surface".
platformyesthe platform, what integrators build, the segments, tagging, webhooks, the request ID today, provider failures, bodies, retention, time to notice, how fast partners lose money, alerting today, an outbound channel, deadline, mandate and effort; "not given" means not given.
signalsyesthe ticket pile by class against today's surface, our deploys, and every integration's calls and error rates split by family against its own baseline, the breaking ones ranked by installs with the page's cause.
carrysend it (empty for surface)outreach task only: the surface design's decisions, when the surface task ran first; may be empty.
factsyesthe page's checks, one line each as area | id | status | evidence, the verdict and the ranked rungs. 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/integration-error-observability - the only standard the run applies:

House rules

The replies

surface

Write the visibility design - what integrators will be able to see and when the platform reaches out. Choosing a retention, a cooldown or an alert threshold is a design decision; say what changes.

  1. summary - one sentence, at most 40 words: what integrators cannot see today, and the first build.
  2. audit - one object per failure class the facts list: class (the class id), gap (true or false, exactly as the page reads it), fix (one sentence: the surface that closes it - an event log or a volume-drop signal counts even though it is not one of the rungs - or why none is needed).
  3. rungs - the ten rungs in the facts' order: rung (delivery-log, request-log, replay, header-echo, searchable-id, propagation, digest, threshold-cooldown, state-change or fleet-view), use (exactly as the facts rank it), why (one sentence).
  4. log_fields - short strings: what each log row carries.
  5. correlation - header (the response header name), format (one sentence: the ID's form), searchable (true).
  6. buckets - exactly ["4xx", "5xx", "429"].
  7. notification - posture (digest or threshold, as the facts rank it), cooldown_minutes (a number when the posture is threshold, else 0), state_changes (short strings: the discrete events alerted at once).
  8. redaction - short strings: what is redacted before a body leaves, or that only metadata is shown.
  9. caveats - at most 4 short strings; [] when there is nothing to say.
{
  "task": "surface",
  "summary": "one sentence",
  "audit": [ { "class": "webhook", "gap": true, "fix": "one sentence" } ],
  "rungs": [ { "rung": "delivery-log", "use": "now", "why": "one sentence" } ],
  "log_fields": [ "timestamp" ],
  "correlation": { "header": "X-Request-Id", "format": "one sentence", "searchable": true },
  "buckets": [ "4xx", "5xx", "429" ],
  "notification": { "posture": "digest", "cooldown_minutes": 0, "state_changes": [ "credentials revoked" ] },
  "redaction": [ "a short string" ],
  "caveats": [ ]
}

The page holds it to its own facts (check() in ipreply.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; no reliability number promised; one audit row per class in the pile with the page's gap and a fix; the ten rungs as ranked, each with a reason; a log row with a timestamp, the endpoint, the status, the error code and the request ID (metadata only when bodies never leave); a named header and a searchable ID; buckets exactly 4xx, 5xx and 429; the ranked posture, a cooldown when it is threshold, state changes named; redaction rules.

outreach

Write the outreach for the integrations breaking now - who hears what, in blast-radius order.

  1. summary - one sentence, at most 40 words.
  2. contacts - one object per breaking integration, in the facts' order: integration (as written), cause (ours, theirs, ratelimit or silent, exactly as the page reads it), channel (named - a person reaching the owner, first for fleet partners and for our own breaks with many installs; alert when partners lose money within hours; state-change for a discrete event; digest otherwise), message (one or two sentences to the integrator; quote rates only by family; a theirs cause gets the evidence - the family, the date, their release - and where to look in the log, without blame), next (one sentence: what we or they do next).
  3. ours - short strings: what we fix on our side for every ours cause; [] when none.
  4. caveats - at most 4 short strings; [] when there is nothing to say.
{
  "task": "outreach",
  "summary": "one sentence",
  "contacts": [ { "integration": "Shopvarne app", "cause": "ours", "channel": "named", "message": "one or two sentences", "next": "one sentence" } ],
  "ours": [ "a short string" ],
  "caveats": [ ]
}

The page requires: the reply answers the task it was sent; the same text rules; exactly the breaking integrations in the page's order with the page's causes; a known channel, a message and a next step each; an ours cause owns the fault, a rate-limit cause points at the quota, a silent cause asks about the stop; a quoted rate always names its family; what we fix on our side when a cause is ours.

Worked examples

Three runs from the page's free samples (invented platforms): the visibility design for a shipping API whose partners hear last, the outreach for the same platform carrying that design, and the design for a no-code platform with silent mismatches. 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.

surface - A shipping API whose partners hear last

The page read it as: integrators are debugging in the dark; 20 tickets; classes with no surface: sync, webhook, silent; breaking: Shopvarne app (ours), Labelquint plugin (theirs), Shipmorrow agency (ratelimit), Northbrae Wholesale (silent); 1 of 5 gates; failing checks: coverage, volume, redaction, resolvable, retention, fatigue, ours.

Input (signals is 1712 characters; the first 1397 are shown; facts is 3205 characters; the first 1398 are shown):

{
 "task": "surface",
 "platform": "Platform: Parcelvane Shipping API\nWhat integrators build and what they see today: Label and tracking API used by merchants' own engineers and by partner apps installed across thousands of merchant accounts; webhooks carry tracking events.\nSegments: single-account integrators yes, fleet partners yes, no-code and agent builders no\nIdentity tagged at ingestion: yes; webhooks: yes (per-attempt outcomes recorded: yes); request ID: Request ID on error responses only, searchable by integrators: no; failures often come from our own providers: yes\nBodies: Bodies hold PII or secrets, not yet reviewed; retention 7 days; partners take 10 days to notice; Partners lose money within hours; alerting today: An alert per failure; an outbound partner channel exists: yes\nDeadline: no; a compounding surface; effort ceiling: medium; as-of: 2026-11-01",
 "signals": "Ticket pile by class (class | tickets | self-servable | surface today | covered):\n- sync | 5 | 4 | nothing | no\n- webhook | 8 | 7 | failure counts only | no\n- auth | 3 | 2 | a state-change alert | yes\n- ratelimit | 2 | 2 | rate-limit headers and a quota view | yes\n- silent | 2 | 1 | nothing | no\nOur deploys: 2026-10-28 label-rendering service v4.2; 2026-10-14 tracking webhooks batching\nIntegrations (split by family against their own baseline), breaking ones ranked by installs:\n- Shopvarne app | fleet | 1800 installs | calls 52000 (baseline 50000) | 4xx 0.6%, 5xx 8.1%, 429 0%; overall baseline 1% | failing since 2026-10-29 | their release - | BREAKING: ours - failing since 2026-10-29, within two days of our deploy 2026-10-28 (label-rendering service v4.2)\n- Labelquint plugin | fleet | 650 installs | calls 9000 (baseline 9500) | 4xx 16.7%, 5xx 0.2%, 429 0%; overall baseline 2% | failing since 2026-10-30 | their release 2026-10-29 | BREAKING: theirs - 4xx dominate, since their release 2026-10-29 - after our deploy 2026-10-28 too, so rule ours out first\n- Shipmorrow agency | fleet | 120 installs | calls 20000 (baseline 18000) | 4xx 0.5%, 5xx 0.3%, 429 15%; overall baseline 1% | failing since 2026-10-25 | their release - | BREAKING: ratelimit - 429 dominate - hand the quota question to the rate-limit policy\n- Northbrae Wholesale | single | 1 install | calls 40 (baseline 3000) | 4xx\n\n[... 315 more characters cut from this display ...]",
 "carry": "",
 "facts": "The page's checks (7 fail, 2 warn, 2 pass, 1 n/a); verdict: integrators are debugging in the dark:\n- What integrators can see | baseline | na | 16 of 20 tickets could have been answered by a surface you could ship\n- What integrators can see | coverage | fail | Synchronous API errors (5 tickets) - today nothing; Webhooks that never arrived (8 tickets) - today failure counts only; An integration that stopped calling (2 tickets) - today nothing\n- What integrators can see | volume | fail | an integration went silent and nothing alerts on absence\n- What integrators can see | counts | warn | Webhooks that never arrived - a count says something is wrong and gives no way to act\n- What integrators can see | redaction | fail | bodies carry PII or secrets with no review, and a log is going out\n- Correlation, aggregation and alerts | tagged | pass | API key, app or client ID on arrival\n- Correlation, aggregation and alerts | resolvable | fail | Request ID on error responses only; the ID is not searchable outside the platform\n- Correlation, aggregation and alerts | retention | fail | 7-day retention against 10 days to notice\n- Correlation, aggregation and alerts | fatigue | fail | an alert per failure trains partners to mute the channel\n- Correlation, aggregation and alerts | fleetpush | pass | An alert per failure\n- Who is breaking now | breaking | warn | Shopvarne app (ours), Labelquint\n\n[... 1807 more characters cut from this display ...]"
}

Reply:

{
 "task": "surface",
 "summary": "Integrators see nothing for failed calls, only counts for lost webhooks, and no warning when an integration goes quiet; ship the delivery-attempt log already recorded, then the request log.",
 "audit": [
  {
   "class": "sync",
   "gap": true,
   "fix": "A request log per call with status, error code and a searchable request ID."
  },
  {
   "class": "webhook",
   "gap": true,
   "fix": "Expose the per-attempt delivery log with replay, so a count becomes something to act on."
  },
  {
   "class": "auth",
   "gap": false,
   "fix": "The existing state-change alert already answers these tickets."
  },
  {
   "class": "ratelimit",
   "gap": false,
   "fix": "Rate-limit headers and the quota view already cover this class."
  },
  {
   "class": "silent",
   "gap": true,
   "fix": "A volume-drop signal against each integration's own baseline, since absence raises no error."
  }
 ],
 "rungs": [
  {
   "rung": "delivery-log",
   "use": "now",
   "why": "Every attempt is already recorded, so showing it is the cheapest win."
  },
  {
   "rung": "request-log",
   "use": "now",
   "why": "Failed synchronous calls sit in the ticket pile with nothing to look at."
  },
  {
   "rung": "replay",
   "use": "now",
   "why": "Integrators lose tracking events while their own endpoints are down."
  },
  {
   "rung": "header-echo",
   "use": "now",
   "why": "The ID reaches only error responses today; successes need it too."
  },
  {
   "rung": "searchable-id",
   "use": "now",
   "why": "An ID no integrator can look up resolves nothing, so it ships with the log."
  },
  {
   "rung": "propagation",
   "use": "now",
   "why": "Support keeps pointing at our providers, so the ID must travel to them."
  },
  {
   "rung": "digest",
   "use": "later",
   "why": "Money is lost within hours, so spike alerts come first and the digest after."
  },
  {
   "rung": "threshold-cooldown",
   "use": "now",
   "why": "Replace the per-failure alert with a spike alert held by a cooldown."
  },
  {
   "rung": "state-change",
   "use": "now",
   "why": "Revoked keys or a disabled integration need word at once, not in a batch."
  },
  {
   "rung": "fleet-view",
   "use": "now",
   "why": "Fleet partners span thousands of merchants, so we rank breakage by installs ourselves."
  }
 ],
 "log_fields": [
  "timestamp",
  "endpoint or event",
  "status",
  "error code",
  "request ID",
  "attempt number",
  "redacted payload"
 ],
 "correlation": {
  "header": "X-Request-Id",
  "format": "An opaque random ID per call plus a correlation ID per transaction, returned on every response.",
  "searchable": true
 },
 "buckets": [
  "4xx",
  "5xx",
  "429"
 ],
 "notification": {
  "posture": "threshold",
  "cooldown_minutes": 60,
  "state_changes": [
   "credentials revoked",
   "integration disabled",
   "subscription expired"
  ]
 },
 "redaction": [
  "Show metadata only until a redaction review clears bodies",
  "Strip PII and secrets before any body leaves"
 ],
 "caveats": [
  "Retention of 7 days misses partners who notice after 10; raise it to 30 days.",
  "A 60-minute cooldown trades some repeat alerts for a channel partners keep open."
 ]
}

What step 5 prints for it: delivery-log | request-log | replay | header-echo | searchable-id | propagation | threshold-cooldown | state-change | fleet-view

The page's check: kept - nothing to flag

outreach - A shipping API whose partners hear last

The input carries the saved design's decisions (R.laneForm fills carry from the design reply), as the page's handoff does. Breaking, by installs: Shopvarne app (ours), Labelquint plugin (theirs), Shipmorrow agency (ratelimit), Northbrae Wholesale (silent).

Input (signals is 1712 characters; the first 1397 are shown; facts is 2995 characters; the first 1398 are shown):

{
 "task": "outreach",
 "platform": "Platform: Parcelvane Shipping API\nWhat integrators build and what they see today: Label and tracking API used by merchants' own engineers and by partner apps installed across thousands of merchant accounts; webhooks carry tracking events.\nSegments: single-account integrators yes, fleet partners yes, no-code and agent builders no\nIdentity tagged at ingestion: yes; webhooks: yes (per-attempt outcomes recorded: yes); request ID: Request ID on error responses only, searchable by integrators: no; failures often come from our own providers: yes\nBodies: Bodies hold PII or secrets, not yet reviewed; retention 7 days; partners take 10 days to notice; Partners lose money within hours; alerting today: An alert per failure; an outbound partner channel exists: yes\nDeadline: no; a compounding surface; effort ceiling: medium; as-of: 2026-11-01",
 "signals": "Ticket pile by class (class | tickets | self-servable | surface today | covered):\n- sync | 5 | 4 | nothing | no\n- webhook | 8 | 7 | failure counts only | no\n- auth | 3 | 2 | a state-change alert | yes\n- ratelimit | 2 | 2 | rate-limit headers and a quota view | yes\n- silent | 2 | 1 | nothing | no\nOur deploys: 2026-10-28 label-rendering service v4.2; 2026-10-14 tracking webhooks batching\nIntegrations (split by family against their own baseline), breaking ones ranked by installs:\n- Shopvarne app | fleet | 1800 installs | calls 52000 (baseline 50000) | 4xx 0.6%, 5xx 8.1%, 429 0%; overall baseline 1% | failing since 2026-10-29 | their release - | BREAKING: ours - failing since 2026-10-29, within two days of our deploy 2026-10-28 (label-rendering service v4.2)\n- Labelquint plugin | fleet | 650 installs | calls 9000 (baseline 9500) | 4xx 16.7%, 5xx 0.2%, 429 0%; overall baseline 2% | failing since 2026-10-30 | their release 2026-10-29 | BREAKING: theirs - 4xx dominate, since their release 2026-10-29 - after our deploy 2026-10-28 too, so rule ours out first\n- Shipmorrow agency | fleet | 120 installs | calls 20000 (baseline 18000) | 4xx 0.5%, 5xx 0.3%, 429 15%; overall baseline 1% | failing since 2026-10-25 | their release - | BREAKING: ratelimit - 429 dominate - hand the quota question to the rate-limit policy\n- Northbrae Wholesale | single | 1 install | calls 40 (baseline 3000) | 4xx\n\n[... 315 more characters cut from this display ...]",
 "carry": "Building now: delivery-log, request-log, replay, header-echo, searchable-id, propagation, threshold-cooldown, state-change, fleet-view\nRequest ID: X-Request-Id, searchable true\nNotification: threshold with a 60-minute cooldown; state changes: credentials revoked, integration disabled, subscription expired\nGaps closed: sync - A request log per call with status, error code and a searchable request ID.; webhook - Expose the per-attempt delivery log with replay, so a count becomes something to act on.; silent - A volume-drop signal against each integration's own baseline, since absence raises no error.",
 "facts": "The page's checks (7 fail, 2 warn, 2 pass, 1 n/a); verdict: integrators are debugging in the dark:\n- What integrators can see | baseline | na | 16 of 20 tickets could have been answered by a surface you could ship\n- What integrators can see | coverage | fail | Synchronous API errors (5 tickets) - today nothing; Webhooks that never arrived (8 tickets) - today failure counts only; An integration that stopped calling (2 tickets) - today nothing\n- What integrators can see | volume | fail | an integration went silent and nothing alerts on absence\n- What integrators can see | counts | warn | Webhooks that never arrived - a count says something is wrong and gives no way to act\n- What integrators can see | redaction | fail | bodies carry PII or secrets with no review, and a log is going out\n- Correlation, aggregation and alerts | tagged | pass | API key, app or client ID on arrival\n- Correlation, aggregation and alerts | resolvable | fail | Request ID on error responses only; the ID is not searchable outside the platform\n- Correlation, aggregation and alerts | retention | fail | 7-day retention against 10 days to notice\n- Correlation, aggregation and alerts | fatigue | fail | an alert per failure trains partners to mute the channel\n- Correlation, aggregation and alerts | fleetpush | pass | An alert per failure\n- Who is breaking now | breaking | warn | Shopvarne app (ours), Labelquint\n\n[... 1597 more characters cut from this display ...]"
}

Reply:

{
 "task": "outreach",
 "summary": "Four integrations are breaking: our label-rendering deploy broke Shopvarne app, Labelquint plugin errors follow its own release, Shipmorrow agency is hitting its quota, and Northbrae Wholesale went quiet.",
 "contacts": [
  {
   "integration": "Shopvarne app",
   "cause": "ours",
   "channel": "named",
   "message": "Our deploy on 2026-10-28 of label-rendering service v4.2 broke your calls: 5xx at 8.1% since 2026-10-29, and the fault is ours, not yours.",
   "next": "We roll back or fix v4.2 and confirm once 5xx returns to baseline."
  },
  {
   "integration": "Labelquint plugin",
   "cause": "theirs",
   "channel": "named",
   "message": "Since your release on 2026-10-29, 4xx have risen to 16.7%, so requests your plugin now sends are being rejected.",
   "next": "We first rule out our 2026-10-28 deploy, then share the failing error codes so you can patch the release."
  },
  {
   "integration": "Shipmorrow agency",
   "cause": "ratelimit",
   "channel": "named",
   "message": "Since 2026-10-25, 429 have reached 15% as your traffic presses against its quota; this is the rate limit, not a bug.",
   "next": "Review your quota in the quota view and spread calls or request a higher limit."
  },
  {
   "integration": "Northbrae Wholesale",
   "cause": "silent",
   "channel": "alert",
   "message": "Your calls fell from about 3000 to 40 since 2026-10-27 with no errors on our side; did you stop on purpose?",
   "next": "If the stop was not intended, check your scheduler and credentials and reply so we can trace it together."
  }
 ],
 "ours": [
  "Fix or roll back label-rendering service v4.2, deployed 2026-10-28, which broke Shopvarne app"
 ],
 "caveats": [
  "Today's per-failure alerting should not carry these messages; send each once."
 ]
}

What step 5 prints for it: Shopvarne app ours | Labelquint plugin theirs | Shipmorrow agency ratelimit | Northbrae Wholesale silent

The page's check: kept - nothing to flag

surface - A no-code platform with silent mismatches

The page read it as: integrators are debugging in the dark; 20 tickets; classes with no surface: sync, mismatch; breaking: Sheetmarrow connector (theirs); 3 of 5 gates; failing checks: coverage, resolvable.

Input (facts is 2611 characters; the first 1396 are shown):

{
 "task": "surface",
 "platform": "Platform: Formulark\nWhat integrators build and what they see today: A no-code forms platform; flow builders send submissions into CRMs and spreadsheets through connectors, and a few customers call the API directly.\nSegments: single-account integrators yes, fleet partners no, no-code and agent builders yes\nIdentity tagged at ingestion: yes; webhooks: no (per-attempt outcomes recorded: no); request ID: Request ID on every response, searchable by integrators: no; failures often come from our own providers: no\nBodies: Bodies can never leave our tenancy; retention 30 days; partners take 3 days to notice; Noticed at the next reconciliation; alerting today: A periodic digest; an outbound partner channel exists: no\nDeadline: yes; a one-off fix; effort ceiling: low; as-of: 2026-11-01",
 "signals": "Ticket pile by class (class | tickets | self-servable | surface today | covered):\n- sync | 6 | 5 | failure counts only | no\n- auth | 3 | 3 | a state-change alert | yes\n- ratelimit | 2 | 1 | rate-limit headers and a quota view | yes\n- mismatch | 9 | 6 | nothing | no\nOur deploys: 2026-10-20 form renderer\nIntegrations (split by family against their own baseline), breaking ones ranked by installs:\n- Sheetmarrow connector | fleet | 400 installs | calls 30000 (baseline 31000) | 4xx 7%, 5xx 0.1%, 429 0%; overall baseline 2% | failing since 2026-10-30 | their release 2026-10-29 | BREAKING: theirs - 4xx dominate, since their release 2026-10-29\n- Tallowby CRM flow | nocode | 1 install | calls 900 (baseline 950) | 4xx 1%, 5xx 0.1%, 429 0%; overall baseline 1% | failing since - | their release -",
 "carry": "",
 "facts": "The page's checks (2 fail, 4 warn, 4 pass, 1 n/a); verdict: integrators are debugging in the dark:\n- What integrators can see | baseline | na | 15 of 20 tickets could have been answered by a surface you could ship\n- What integrators can see | coverage | fail | Synchronous API errors (6 tickets) - today failure counts only; Silent data mismatch (9 tickets) - today nothing\n- What integrators can see | volume | warn | absence produces no errors - design the signal separately\n- What integrators can see | counts | warn | Synchronous API errors - a count says something is wrong and gives no way to act\n- What integrators can see | redaction | pass | metadata only - payload detail is off the menu\n- Correlation, aggregation and alerts | tagged | pass | API key, app or client ID on arrival\n- Correlation, aggregation and alerts | resolvable | fail | the ID is not searchable outside the platform\n- Correlation, aggregation and alerts | retention | pass | 30-day retention against 3 days to notice\n- Correlation, aggregation and alerts | fatigue | pass | A periodic digest\n- Correlation, aggregation and alerts | plain | warn | detail that helps engineers reads as noise to flow builders\n- Who is breaking now | breaking | warn | Sheetmarrow connector (theirs)\nRungs by the skill's ranking, in order: delivery-log skip (no webhooks - nothing to expose); request-log now (no webhooks, so the request\n\n[... 1215 more characters cut from this display ...]"
}

Reply:

{
 "task": "surface",
 "summary": "Integrators see only failure counts for API errors and nothing for mismatched data, and request IDs cannot be looked up; first build is a metadata request log searchable by ID.",
 "audit": [
  {
   "class": "sync",
   "gap": true,
   "fix": "A request log per call with status, error code and a searchable request ID replaces bare counts."
  },
  {
   "class": "auth",
   "gap": false,
   "fix": "The existing state-change alert already answers these tickets."
  },
  {
   "class": "ratelimit",
   "gap": false,
   "fix": "Rate-limit headers and the quota view already cover this class."
  },
  {
   "class": "mismatch",
   "gap": true,
   "fix": "An event log showing what each submission changed, in plain language with a next step for flow builders."
  }
 ],
 "rungs": [
  {
   "rung": "delivery-log",
   "use": "skip",
   "why": "There are no webhooks, so no delivery attempts exist to show."
  },
  {
   "rung": "request-log",
   "use": "now",
   "why": "Without webhooks the request log is the default surface, and API errors fill the pile."
  },
  {
   "rung": "replay",
   "use": "skip",
   "why": "No webhooks means no deliveries to resend."
  },
  {
   "rung": "header-echo",
   "use": "done",
   "why": "Every response already carries the request ID."
  },
  {
   "rung": "searchable-id",
   "use": "now",
   "why": "The ID exists but nobody outside can look it up, so it lands with the log."
  },
  {
   "rung": "propagation",
   "use": "later",
   "why": "Failures rarely come from our providers yet."
  },
  {
   "rung": "digest",
   "use": "now",
   "why": "Problems surface at reconciliation, so a periodic summary per integration fits."
  },
  {
   "rung": "threshold-cooldown",
   "use": "later",
   "why": "No partner loses money within the hour today."
  },
  {
   "rung": "state-change",
   "use": "now",
   "why": "Revoked credentials or a disabled connector must be told at once."
  },
  {
   "rung": "fleet-view",
   "use": "later",
   "why": "There are no fleet partners to rank yet."
  }
 ],
 "log_fields": [
  "timestamp",
  "endpoint",
  "status",
  "error code",
  "request ID",
  "plain-language next step"
 ],
 "correlation": {
  "header": "X-Request-Id",
  "format": "An opaque random ID per call and a correlation ID per submission flow.",
  "searchable": true
 },
 "buckets": [
  "4xx",
  "5xx",
  "429"
 ],
 "notification": {
  "posture": "digest",
  "cooldown_minutes": 0,
  "state_changes": [
   "credentials revoked",
   "integration disabled",
   "subscription expired"
  ]
 },
 "redaction": [
  "Metadata only; bodies never leave our tenancy"
 ],
 "caveats": [
  "No outbound partner channel exists, so the digest needs a delivery route first."
 ]
}

What step 5 prints for it: request-log | searchable-id | digest | state-change

The page's check: kept - nothing to flag

Build the input yourself

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

const K = PulseKit, R = PulseReply;
const f = K.form(Object.assign(K.blank(), { name: "Acme", tickets: "webhook | yes\nsync | no", surfaces: "webhook | counts",
  stats: "Big app | fleet | 900 | 10000 | 10000 | 50 | 900 | 0 | 1 | 2026-10-29 |", deploys: "2026-10-28 | release 7" }));
// f.auditRows (each class with its surface and gap), f.statRows (rates split by family, cause, why), f.breaking, f.ladder, f.gate
// K.auditMd(f), K.fleetCsv(f) - the audit table and the fleet view
const b = K.buildInput(f, "surface");                        // or "outreach" (R.laneForm(form, "outreach", design) fills carry)
const wire = K.toWire(b.input);                              // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "surface");
// 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://integration-pulse.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":"integration-pulse"}' | 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": "surface", "platform": "Platform: Parcelvane Shipping API\nWhat integrators build and what they see today: ...", "signals": "Ticket pile by class (class | tickets | self-servable | surface today | covered):\n- sync | 5 | 4 | nothing | no ...", "carry": "", "facts": "The page's checks (8 fail, ...); verdict: integrators are debugging in the dark:\n- What integrators can see | coverage | 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 integration-pulse:<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 surface reply carries summary, audit, rungs, log_fields, correlation, buckets, notification, redaction and caveats; an outreach reply carries summary, contacts, ours and caveats. The code below prints the gist of either: each rung and its use, or each contact and its cause.

KEY="integration-pulse:$(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 == "outreach" then (.contacts[] | "\(.integration): \(.cause)") 
       else (.rungs[] | "\(.rung) \(.use)") 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: integration-pulse:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json