← Beta Charter
API
Tokens

Run Beta Charter from your own code

Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page ranks the program shapes, reads every stage contract row, checks the wire, the time box and the freeze, holds every account to the entry criteria and the cap, reads the sign-offs and decides every beta that never ended, then hands it all to the run as facts. The run writes the program charter or the rescue; the page then holds it to its own facts (bcreply.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":"beta-charter"}), 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, program, register, facts) is missing. The page always sends all five of task, program, register, carry, facts; carry is an empty string for charter.
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 - before anyone enrolls, then the betas that never ended:

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

The input

FieldRequiredWhat
taskyescharter or rescue. If it is missing or neither, do charter and set "task": "charter".
programyesthe feature, what enters beta and why, the owner, partner hours, data and contracts, versioning and the mechanism, SDKs, detection, the dates, the release cycle, the activation floor and extensions; "not given" means not given.
registeryesthe stage contract by stage with empty rows marked, the cohort with the page's decision for each account, and the beta surfaces that never ended with the page's decision.
carrysend it (empty for charter)rescue task only: the charter's decisions, when the charter task ran first; may be empty.
factsyesthe page's checks, one line each as area | id | status | evidence, the verdict and the ranked shapes. 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-beta-program - the only standard the run applies:

House rules

The replies

charter

Write the program charter - what the beta promises, how it shows on the wire, how it ends and who gets in. Filling an empty contract row is a design decision: start that term with "Proposed: ". The precedence clause, when one is needed, goes in the access row.

  1. summary - one sentence, at most 40 words: the shape, and what must happen before the first account enrolls.
  2. shape - recommended and alternative (skip, private, hybrid, partners or public, exactly as the facts rank them; none when no owner is named), why (one sentence naming the moves).
  3. contract - one object per stage the register lists, in its order: stage (private or public), rows (the eleven rows in this order - access, availability, support, breaking, removal, production, pricing, data, limits, marking, discontinuation - each row and term: the register's term where one is written and settled, a proposed term where it is empty or breaks a rule above).
  4. wire - mechanism (suffix, path, header or channel, exactly as the facts read it), marking (short strings: how docs, SDKs, payloads and the console show the stage).
  5. exit - time_box_months (a number, at most 12), review (YYYY-MM-DD: the program's exit review date when the facts let it stand, otherwise a proposed date on or after the as-of date and on or before the GA target), freeze (one sentence: when the contract freezes against the review), activation_floor (a number of accounts; the program's floor when given), ga_criteria (short strings), kill_criteria (short strings), extension (one sentence: one extension, no longer than half the box).
  6. cohort - one object per account the register lists, in its order: account (as written), decision (enroll, wait or substitute, exactly as the register reads it), reason (one sentence; a substitute names what it gets instead of access).
  7. signoffs - five objects in this order: signoff (owner, design, security, legal, support), status (done or missing, as the facts read them).
  8. caveats - at most 4 short strings; [] when there is nothing to say.
{
  "task": "charter",
  "summary": "one sentence",
  "shape": { "recommended": "private", "alternative": "hybrid", "why": "one sentence" },
  "contract": [ { "stage": "private", "rows": [ { "row": "access", "term": "a short term" } ] } ],
  "wire": { "mechanism": "suffix", "marking": [ "a short string" ] },
  "exit": { "time_box_months": 6, "review": "2027-05-15", "freeze": "one sentence", "activation_floor": 3, "ga_criteria": [ "a short string" ], "kill_criteria": [ "a short string" ], "extension": "one sentence" },
  "cohort": [ { "account": "Hikeline", "decision": "enroll", "reason": "one sentence" } ],
  "signoffs": [ { "signoff": "owner", "status": "done" } ],
  "caveats": [ ]
}

The page holds it to its own facts (check() in bcreply.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; the ranked shape and alternative; exactly the page's stages, each with the eleven rows in order and no empty term; no SLO anywhere in the terms; test mode or synthetic data in the data row when the payload is personal data; a precedence clause when contracts promise all services; no SLA on a public preview whose shape is still moving; the page's mechanism and a marking list; a time box of at most twelve months, the page's activation floor, a freeze, at least three GA and two kill criteria, one extension no longer than half the box; every account with the page's decision and a reason; the five sign-offs as the page reads them.

rescue

Decide the beta surfaces that never ended - who hears what, and what nobody may change until they do.

  1. summary - one sentence, at most 40 words.
  2. surfaces - one object per surface, in the register's order: surface (as written), decision (ga, next or kill, exactly as the register reads it), owner (for the team, never quoted to callers: the owner as given, or unowned - name one when none is given), notice (one or two sentences to its production callers, sent privately before anything public: a ga relabels the surface as GA and hands it to the versioning policy; a next version names the migration; a kill gives months of notice, a data export and the alternative - name it when the input gives one, otherwise say the owner will name it before the notice period starts, never invent one), grandfather (one sentence: what stays unchanged for its callers between this notice and the decision taking effect).
  3. retroactive - short strings: the retroactive stage contract's key lines - from the carried charter's contract when one is carried, otherwise from the rules above - and the old label retired once every surface has a decision.
  4. caveats - at most 4 short strings; [] when there is nothing to say.
{
  "task": "rescue",
  "summary": "one sentence",
  "surfaces": [ { "surface": "/beta/payouts", "decision": "ga", "owner": "Priya Raman", "notice": "one or two sentences", "grandfather": "one sentence" } ],
  "retroactive": [ "a short string" ],
  "caveats": [ ]
}

The page requires: the reply answers the task it was sent; the same text rules; exactly the page's surfaces in its order with its decisions; every owner as given, or unowned - never a new name; a notice and a grandfathering line each; a kill with months of notice, a data export and a named alternative; a GA relabel; a next version naming the migration; at least three lines of retroactive contract; no SLO promised.

Worked examples

Three runs from the page's free samples (invented programs): the charter for a payments API with betas that never ended, the rescue for the same program carrying that charter, and the charter for a routing API whose shape is still open. 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.

charter - A payments API with betas that never ended

The page read it as: do not enroll anyone yet; shape private with hybrid as the alternative; cohort: Brightledger (enroll), Northwind Payroll (enroll), Covalla Freight (enroll), Merrowfen Health (substitute), Tessel Retail (enroll), Quayside Bank (enroll), Fernhill Media (wait); 0 of 5 gates; failing checks: contract, carveout, precedence, slo, wire, exit, gadate, signoff, cohort, production, perpetual, owners.

Input (register is 2158 characters; the first 1395 are shown; facts is 4131 characters; the first 1395 are shown):

{
 "task": "charter",
 "program": "Feature: Relaybank bulk export\nWhat enters beta and why: A bulk export endpoint for settled transactions on the Relaybank payments API; a sales deal wants it early, and the /beta endpoints from 2024 still carry production traffic.\nEntering: A new field or endpoint on an existing resource; additive on an existing pattern: no; shape still open: no; why a beta: A sales deal needs it\nOwner: Priya Raman; partner hours a week: 25, hours per active partner: 5\nPersonal or regulated data by nature: yes; contracts promise coverage on all services: yes; template for every beta: yes\nVersioning: Path versions; mechanism chosen: A separate base path; beta SDK methods: yes, pre-release channel: no; production-use detection: no; shape frozen: no\nAs of 2026-11-01; enrollment opens 2026-11-15; exit review 2027-12-15; contract freeze 2027-12-01; GA target 2027-06-30; partner release cycle 30 days; activation floor not given; extensions taken 0",
 "register": "Stage contract by stage (row | term):\nPrivate preview:\n- access | Invite only, allowlisted accounts\n- availability | No SLA; internal SLO 99.5% monthly\n- support | Shared channel, next business day\n- breaking | Allowed with 14 days of direct notice to each technical owner\n- removal | EMPTY\n- production | Unsupported\n- pricing | Free during preview; GA pricing announced 60 days before GA\n- data | Do not send personal data through this preview\n- limits | EMPTY\n- marking | A beta badge on the docs overview page\n- discontinuation | EMPTY\nCohort in the order given (account | decision | activated | traffic | production), cap 5:\n- Brightledger | enroll | activated yes | traffic 45% | production none\n- Northwind Payroll | enroll | activated no | traffic 20% | production none\n- Covalla Freight | enroll | activated yes | traffic 15% | production detected\n- Merrowfen Health | substitute (lacks a real integration in the window, a named technical owner, signed pre-GA terms) | activated no | traffic 0% | production none\n- Tessel Retail | enroll | activated no | traffic 10% | production none\n- Quayside Bank | enroll | activated no | traffic 10% | production none\n- Fernhill Media | wait | activated no | traffic 0% | production none\nBeta surfaces that never ended, by production accounts:\n- /beta/payouts | label beta | first shipped 2024-09-01 (26 months) | last shape change 2025-03-10 | 4200\n\n[... 763 more characters cut from this display ...]",
 "carry": "",
 "facts": "The page's checks (12 fail, 5 warn, 1 pass, 2 n/a); verdict: do not enroll anyone yet:\n- Shape and contract | owner | pass | Priya Raman\n- Shape and contract | shape | na | Private preview first, hybrid as the alternative - a sales deal changes nothing by itself - the deal account enters only if it passes the entry criteria\n- Shape and contract | contract | fail | Private preview: removal, limits, discontinuation - an empty row is a promise you will be held to anyway\n- Shape and contract | carveout | fail | the payload is personal data by nature, so 'do not send personal data' makes the beta unusable - offer test mode or synthetic data, live data only under a signed addendum\n- Shape and contract | precedence | fail | enterprise contracts promise coverage on all services and nothing in the contract overrides them - counsel signs the wording\n- Shape and contract | slo | fail | an internal SLO target sits in the contract - share it only as a non-contractual goal\n- Shape and contract | template | warn | this program is the template for every beta - write the stage contract before any feature-specific work\n- Wire, exit and sign-off | wire | fail | beta SDK methods and no pre-release channel - that blocks enrollment (raw-HTTP partners excepted)\n- Wire, exit and sign-off | mechanism | warn | A separate base path chosen; a version with a stability suffix fits path versions\n- Wire,\n\n[... 2736 more characters cut from this display ...]"
}

Reply:

{
 "task": "charter",
 "summary": "Run Relaybank bulk export as a private preview; before anyone enrolls, fill and fix the terms, ship a pre-release SDK channel, end the box before GA, and get owner, security and legal sign-off.",
 "shape": {
  "recommended": "private",
  "alternative": "hybrid",
  "why": "The shape is decided and the feature is built, so private preview leads with hybrid as the fallback, and the sales deal moves nothing by itself because the deal account still has to pass the entry criteria."
 },
 "contract": [
  {
   "stage": "private",
   "rows": [
    {
     "row": "access",
     "term": "Proposed: Invite only, allowlisted accounts; these pre-GA terms override any MSA or DPA that promises coverage on all services."
    },
    {
     "row": "availability",
     "term": "Proposed: No SLA; any internal availability target is shared only as a non-contractual goal and stays out of these terms."
    },
    {
     "row": "support",
     "term": "Shared channel, next business day"
    },
    {
     "row": "breaking",
     "term": "Allowed with 14 days of direct notice to each technical owner"
    },
    {
     "row": "removal",
     "term": "Proposed: at least 3 months of direct notice before any preview operation is removed, longer for accounts found running production (figure proposed)."
    },
    {
     "row": "production",
     "term": "Unsupported"
    },
    {
     "row": "pricing",
     "term": "Free during preview; GA pricing announced 60 days before GA"
    },
    {
     "row": "data",
     "term": "Proposed: Test mode or synthetic transaction data by default; live personal data only under a signed data addendum."
    },
    {
     "row": "limits",
     "term": "Proposed: Published per-account limits on export jobs and rows per day, set before enrollment and stated in the reference (figures to be proposed by the owner)."
    },
    {
     "row": "marking",
     "term": "Proposed: A stability suffix in the version, a beta badge on every operation in the reference, and a pre-release SDK channel."
    },
    {
     "row": "discontinuation",
     "term": "Proposed: If the preview is killed, at least 3 months of notice, a full export of results, and a named alternative in the notice (figure proposed)."
    }
   ]
  }
 ],
 "wire": {
  "mechanism": "suffix",
  "marking": [
   "Docs: a beta badge on every bulk export operation, not only the overview page",
   "SDKs: beta methods only in a pre-release channel, published before enrollment",
   "Payloads: the version with its stability suffix on every request and response",
   "Console: the account's beta enablement shown with the exit review date",
   "Server-side production-use detection on the beta version"
  ]
 },
 "exit": {
  "time_box_months": 6,
  "review": "2027-05-14",
  "freeze": "Proposed: freeze the contract at least one 30-day partner release cycle before an exit review that falls before the 2027-06-30 GA target.",
  "activation_floor": 3,
  "ga_criteria": [
   "At least the activation floor of accounts activated",
   "Production traffic on the final shape",
   "No open blocker",
   "Design re-review passed",
   "Support, docs and SDKs ready",
   "GA pricing set",
   "Internal availability target met for a sustained window"
  ],
  "kill_criteria": [
   "Activation below the floor at the review",
   "The problem disproved",
   "Cost to serve above what GA pricing can carry",
   "A better route found"
  ],
  "extension": "Proposed: one extension at most, no longer than 3 months (half the 6-month box); a second would be a deferred decision."
 },
 "cohort": [
  {
   "account": "Brightledger",
   "decision": "enroll",
   "reason": "Activated and qualified, but at 45% of beta traffic it sits above a third, so cap its share before others enroll."
  },
  {
   "account": "Northwind Payroll",
   "decision": "enroll",
   "reason": "Meets the entry criteria and enrolls, though it has not activated yet."
  },
  {
   "account": "Covalla Freight",
   "decision": "enroll",
   "reason": "Activated, but production use was detected with no exception, so open a conversation and agree an exception or longer notice."
  },
  {
   "account": "Merrowfen Health",
   "decision": "substitute",
   "reason": "Lacks a real integration in the window, a named technical owner and signed pre-GA terms, so it gets a design review under NDA, a synthetic-data sandbox, the GA and freeze dates and first place in the next wave instead of access."
  },
  {
   "account": "Tessel Retail",
   "decision": "enroll",
   "reason": "Meets the entry criteria and enrolls, though it has not activated yet."
  },
  {
   "account": "Quayside Bank",
   "decision": "enroll",
   "reason": "Meets the entry criteria and enrolls, though it has not activated yet."
  },
  {
   "account": "Fernhill Media",
   "decision": "wait",
   "reason": "The cap of 5 is full, so Fernhill Media leads the next wave."
  }
 ],
 "signoffs": [
  {
   "signoff": "owner",
   "status": "missing"
  },
  {
   "signoff": "design",
   "status": "done"
  },
  {
   "signoff": "security",
   "status": "missing"
  },
  {
   "signoff": "legal",
   "status": "missing"
  },
  {
   "signoff": "support",
   "status": "done"
  }
 ],
 "caveats": [
  "The 13-month box and a review after GA make this a perpetual beta until the dates move.",
  "No activation floor was given; 3 accounts is a proposal.",
  "This program is the template for every beta, so write the stage contract first.",
  "The three old beta surfaces need a retroactive contract before any relabel."
 ]
}

What step 5 prints for it: Brightledger enroll | Northwind Payroll enroll | Covalla Freight enroll | Merrowfen Health substitute | Tessel Retail enroll | Quayside Bank enroll | Fernhill Media wait

The page's check: kept - nothing to flag

rescue - A payments API with betas that never ended

The input carries the saved charter's decisions (R.laneForm fills carry from the charter reply), as the page's handoff does. Surfaces, by production accounts: /beta/payouts (ga), /beta/disputes (next), /beta/fx-quotes (kill).

Input (register is 2158 characters; the first 1395 are shown; facts is 3687 characters; the first 1395 are shown):

{
 "task": "rescue",
 "program": "Feature: Relaybank bulk export\nWhat enters beta and why: A bulk export endpoint for settled transactions on the Relaybank payments API; a sales deal wants it early, and the /beta endpoints from 2024 still carry production traffic.\nEntering: A new field or endpoint on an existing resource; additive on an existing pattern: no; shape still open: no; why a beta: A sales deal needs it\nOwner: Priya Raman; partner hours a week: 25, hours per active partner: 5\nPersonal or regulated data by nature: yes; contracts promise coverage on all services: yes; template for every beta: yes\nVersioning: Path versions; mechanism chosen: A separate base path; beta SDK methods: yes, pre-release channel: no; production-use detection: no; shape frozen: no\nAs of 2026-11-01; enrollment opens 2026-11-15; exit review 2027-12-15; contract freeze 2027-12-01; GA target 2027-06-30; partner release cycle 30 days; activation floor not given; extensions taken 0",
 "register": "Stage contract by stage (row | term):\nPrivate preview:\n- access | Invite only, allowlisted accounts\n- availability | No SLA; internal SLO 99.5% monthly\n- support | Shared channel, next business day\n- breaking | Allowed with 14 days of direct notice to each technical owner\n- removal | EMPTY\n- production | Unsupported\n- pricing | Free during preview; GA pricing announced 60 days before GA\n- data | Do not send personal data through this preview\n- limits | EMPTY\n- marking | A beta badge on the docs overview page\n- discontinuation | EMPTY\nCohort in the order given (account | decision | activated | traffic | production), cap 5:\n- Brightledger | enroll | activated yes | traffic 45% | production none\n- Northwind Payroll | enroll | activated no | traffic 20% | production none\n- Covalla Freight | enroll | activated yes | traffic 15% | production detected\n- Merrowfen Health | substitute (lacks a real integration in the window, a named technical owner, signed pre-GA terms) | activated no | traffic 0% | production none\n- Tessel Retail | enroll | activated no | traffic 10% | production none\n- Quayside Bank | enroll | activated no | traffic 10% | production none\n- Fernhill Media | wait | activated no | traffic 0% | production none\nBeta surfaces that never ended, by production accounts:\n- /beta/payouts | label beta | first shipped 2024-09-01 (26 months) | last shape change 2025-03-10 | 4200\n\n[... 763 more characters cut from this display ...]",
 "carry": "Shape: private (alternative hybrid)\nMechanism: suffix\nExit: a 6-month time box, review 2027-05-14, activation floor 3; Proposed: one extension at most, no longer than 3 months (half the 6-month box); a second would be a deferred decision.\nStage contract: private - access: Proposed: Invite only, allowlisted accounts; these pre-GA terms override any MSA or DPA that promises coverage on all services.; availability: Proposed: No SLA; any internal availability target is shared only as a non-contractual goal and stays out of these terms.; support: Shared channel, next business day; breaking: Allowed with 14 days of direct notice to each technical owner; removal: Proposed: at least 3 months of direct notice before any preview operation is removed, longer for accounts found running production (figure proposed).; production: Unsupported; pricing: Free during preview; GA pricing announced 60 days before GA; data: Proposed: Test mode or synthetic transaction data by default; live personal data only under a signed data addendum.; limits: Proposed: Published per-account limits on export jobs and rows per day, set before enrollment and stated in the reference (figures to be proposed by the owner).; marking: Proposed: A stability suffix in the version, a beta badge on every operation in the reference, and a pre-release SDK channel.; discontinuation: Proposed: If the preview is killed, at least 3 months of notice, a full export of results, and a named alternative in the notice (figure proposed).",
 "facts": "The page's checks (12 fail, 5 warn, 1 pass, 2 n/a); verdict: do not enroll anyone yet:\n- Shape and contract | owner | pass | Priya Raman\n- Shape and contract | shape | na | Private preview first, hybrid as the alternative - a sales deal changes nothing by itself - the deal account enters only if it passes the entry criteria\n- Shape and contract | contract | fail | Private preview: removal, limits, discontinuation - an empty row is a promise you will be held to anyway\n- Shape and contract | carveout | fail | the payload is personal data by nature, so 'do not send personal data' makes the beta unusable - offer test mode or synthetic data, live data only under a signed addendum\n- Shape and contract | precedence | fail | enterprise contracts promise coverage on all services and nothing in the contract overrides them - counsel signs the wording\n- Shape and contract | slo | fail | an internal SLO target sits in the contract - share it only as a non-contractual goal\n- Shape and contract | template | warn | this program is the template for every beta - write the stage contract before any feature-specific work\n- Wire, exit and sign-off | wire | fail | beta SDK methods and no pre-release channel - that blocks enrollment (raw-HTTP partners excepted)\n- Wire, exit and sign-off | mechanism | warn | A separate base path chosen; a version with a stability suffix fits path versions\n- Wire,\n\n[... 2292 more characters cut from this display ...]"
}

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

{
 "task": "rescue",
 "summary": "Three 2024-2025 betas get decisions: /beta/payouts goes GA as-is, /beta/disputes moves to a new version with migration, /beta/fx-quotes is killed; production callers hear privately first and nothing changes before then.",
 "surfaces": [
  {
   "surface": "/beta/payouts",
   "decision": "ga",
   "owner": "Priya Raman",
   "notice": "The payouts endpoint your 38 production accounts call has been stable since March 2025 and is being relabeled GA exactly as it is, with no code change on your side. From the relabel on it falls under our versioning policy, so GA [... 48 more characters cut from this display]",
   "grandfather": "Every request and response on /beta/payouts stays exactly as it is today until the GA relabel lands."
  },
  {
   "surface": "/beta/disputes",
   "decision": "next",
   "owner": "unowned - name one",
   "notice": "Two required fixes to the disputes endpoint will ship in a new version, and the 6 production accounts on it will be asked to migrate their calls to that version, with a migration guide listing what changes. The current /beta/disputes keeps [... 92 more characters cut from this display]",
   "grandfather": "Nothing on /beta/disputes changes for its callers until the new version ships and they have migrated."
  },
  {
   "surface": "/beta/fx-quotes",
   "decision": "kill",
   "owner": "Marco Bell",
   "notice": "The fx-quotes endpoint, shown under an undefined labs badge, will be discontinued: enrollment closes now, and we propose at least 3 months of notice before removal, with a full export of your results. A named alternative will be given in [... 39 more characters cut from this display]",
   "grandfather": "/beta/fx-quotes keeps answering as it does today for its 2 recent callers until the notice period ends."
  }
 ],
 "retroactive": [
  "Every /beta surface is a private preview: invite only, no SLA, unsupported in production, free until GA.",
  "These pre-GA terms override any MSA or DPA that promises coverage on all services.",
  "Breaking changes only with 14 days of direct notice to each technical owner; proposed: at least 3 months of notice before any removal.",
  "Discontinuation brings at least 3 months of notice, a full export and a named alternative (proposed).",
  "The beta label retires once every surface has a decision, and labs is never adopted as a stage."
 ],
 "caveats": [
  "/beta/disputes has no owner - name one before the new version is planned.",
  "No alternative to fx-quotes is named yet; the kill notice needs one before it goes out.",
  "The 3-month notice figures are proposals for the owner to confirm."
 ]
}

Cut from this display only (the saved reply and the check use the full text): surfaces[0].notice (276 characters, 228 shown); surfaces[1].notice (331 characters, 239 shown); surfaces[2].notice (276 characters, 237 shown).

What step 5 prints for it: /beta/payouts ga | /beta/disputes next | /beta/fx-quotes kill

The page's check: kept - nothing to flag

charter - A routing API whose shape is still open

The page read it as: ready to enroll; shape partners with hybrid as the alternative; cohort: Hikeline (enroll), Courier Loop (enroll), Openfield Realty (enroll), Atlorra Transit (substitute); 5 of 5 gates; failing checks: none.

Input (register is 2023 characters; the first 1394 are shown; facts is 2005 characters; the first 1388 are shown):

{
 "task": "charter",
 "program": "Feature: Trailmap isochrones\nWhat enters beta and why: A new isochrone resource family on the Trailmap routing API - reachable-area polygons by travel time; the team is not sure whether polygons or grids are the right shape.\nEntering: A new resource family; additive on an existing pattern: no; shape still open: yes; why a beta: The team is unsure of the shape\nOwner: Rui Calvaro; partner hours a week: 20, hours per active partner: 4\nPersonal or regulated data by nature: no; contracts promise coverage on all services: no; template for every beta: no\nVersioning: Dated versions; mechanism chosen: A version with a stability suffix; beta SDK methods: yes, pre-release channel: yes; production-use detection: yes; shape frozen: no\nAs of 2026-11-01; enrollment opens 2026-11-15; exit review 2027-05-15; contract freeze 2027-04-01; GA target 2027-09-01; partner release cycle 30 days; activation floor 3; extensions taken 0",
 "register": "Stage contract by stage (row | term):\nPrivate preview:\n- access | Invite only, signed pre-GA terms per account\n- availability | No SLA; not covered by the status page\n- support | Private channel answered within one business day by the routing team\n- breaking | Allowed, batched into a new dated preview version, with 21 days of direct notice\n- removal | 60 days of notice before the surface goes away\n- production | Unsupported unless a signed production exception covers the account\n- pricing | Free during the preview; GA pricing announced at the freeze\n- data | Location queries only; no personal data expected, standard DPA terms do not extend to the preview\n- limits | 10 requests per second and 50,000 polygons a day per account\n- marking | A preview version suffix, a preview sub-package in each SDK, a badge on every operation in the reference\n- discontinuation | Polygons stay exportable for 90 days; a named alternative in the matrix API\nCohort in the order given (account | decision | activated | traffic | production), cap 5:\n- Hikeline | enroll | activated yes | traffic 40% | production exception\n- Courier Loop | enroll | activated no | traffic 35% | production none\n- Openfield Realty | enroll | activated no | traffic 25% | production none\n- Atlorra Transit | substitute (lacks evidence of the exact problem) | activated no | traffic 0% | production none\nBeta surfaces that never\n\n[... 629 more characters cut from this display ...]",
 "carry": "",
 "facts": "The page's checks (0 fail, 1 warn, 7 pass, 2 n/a); verdict: ready to enroll:\n- Shape and contract | owner | pass | Rui Calvaro\n- Shape and contract | shape | na | Design partners first, hybrid as the alternative - the team is unsure of the shape - a frozen wrong shape costs a version bump, so design partners lead\n- Shape and contract | contract | pass | every row written for private preview\n- Wire, exit and sign-off | wire | pass | A version with a stability suffix\n- Wire, exit and sign-off | exit | pass | a 5.9-month time box; the freeze is 44 days before the review; activation floor 3\n- Wire, exit and sign-off | signoff | pass | owner, design, security, legal and support signed\n- The cohort | cohort | pass | 3 accounts enroll\n- The cohort | cap | pass | cap 5 (20 partner hours a week at 4 per active partner)\n- The cohort | entry | warn | Atlorra Transit lacks evidence of the exact problem - no access; offer a substitute instead (a design review under NDA, a synthetic-data sandbox, the GA and freeze dates, first place in the next wave)\n- Betas that never ended | inventory | na | Elevation preview: ga as-is; /v2beta/matrix: next version with migration\nShapes by the skill's ranking, in order: partners, hybrid, private, public (moved by: the team is unsure of the shape - a frozen wrong shape costs a version bump, so design partners lead).\nmust: shape recommended partners,\n\n[... 617 more characters cut from this display ...]"
}

Reply:

{
 "task": "charter",
 "summary": "Run Trailmap isochrones with design partners on a preview version suffix; the contract, exit and every sign-off are in place, so enrollment can open as planned.",
 "shape": {
  "recommended": "partners",
  "alternative": "hybrid",
  "why": "The team is unsure whether polygons or grids are right and isochrones are a new resource family, so design partners lead with hybrid as the fallback, since freezing a wrong shape costs a version bump."
 },
 "contract": [
  {
   "stage": "private",
   "rows": [
    {
     "row": "access",
     "term": "Invite only, signed pre-GA terms per account"
    },
    {
     "row": "availability",
     "term": "No SLA; not covered by the status page"
    },
    {
     "row": "support",
     "term": "Private channel answered within one business day by the routing team"
    },
    {
     "row": "breaking",
     "term": "Allowed, batched into a new dated preview version, with 21 days of direct notice"
    },
    {
     "row": "removal",
     "term": "60 days of notice before the surface goes away"
    },
    {
     "row": "production",
     "term": "Unsupported unless a signed production exception covers the account"
    },
    {
     "row": "pricing",
     "term": "Free during the preview; GA pricing announced at the freeze"
    },
    {
     "row": "data",
     "term": "Location queries only; no personal data expected, standard DPA terms do not extend to the preview"
    },
    {
     "row": "limits",
     "term": "10 requests per second and 50,000 polygons a day per account"
    },
    {
     "row": "marking",
     "term": "A preview version suffix, a preview sub-package in each SDK, a badge on every operation in the reference"
    },
    {
     "row": "discontinuation",
     "term": "Polygons stay exportable for 90 days; a named alternative in the matrix API"
    }
   ]
  }
 ],
 "wire": {
  "mechanism": "suffix",
  "marking": [
   "Docs: a badge on every isochrone operation in the reference",
   "SDKs: a preview sub-package in each SDK",
   "Payloads: the dated version with its preview suffix",
   "Console: the account's preview enablement and exit review date",
   "Server-side production-use detection on the preview version"
  ]
 },
 "exit": {
  "time_box_months": 6,
  "review": "2027-05-15",
  "freeze": "The contract freezes on 2027-04-01, 44 days and more than one 30-day partner release cycle before the 2027-05-15 exit review.",
  "activation_floor": 3,
  "ga_criteria": [
   "At least 3 accounts activated",
   "Production traffic on the final shape",
   "No open blocker",
   "Design re-review passed",
   "Support, docs and SDKs ready",
   "GA pricing set",
   "Internal availability target met for a sustained window"
  ],
  "kill_criteria": [
   "Fewer than 3 accounts activated at the review",
   "The problem disproved",
   "Cost to serve above what GA pricing can carry",
   "A better route found"
  ],
  "extension": "One extension at most, no longer than 3 months (half the 6-month box), and it still has to land before the 2027-09-01 GA target."
 },
 "cohort": [
  {
   "account": "Hikeline",
   "decision": "enroll",
   "reason": "Activated and running production under a signed exception, so it enrolls as a design partner."
  },
  {
   "account": "Courier Loop",
   "decision": "enroll",
   "reason": "Meets the entry criteria and enrolls, though it has not activated yet."
  },
  {
   "account": "Openfield Realty",
   "decision": "enroll",
   "reason": "Meets the entry criteria and enrolls, though it has not activated yet."
  },
  {
   "account": "Atlorra Transit",
   "decision": "substitute",
   "reason": "Lacks evidence of the exact problem, so it gets a design review under NDA, a synthetic-data sandbox, the GA and freeze dates and first place in the next wave instead of access."
  }
 ],
 "signoffs": [
  {
   "signoff": "owner",
   "status": "done"
  },
  {
   "signoff": "design",
   "status": "done"
  },
  {
   "signoff": "security",
   "status": "done"
  },
  {
   "signoff": "legal",
   "status": "done"
  },
  {
   "signoff": "support",
   "status": "done"
  }
 ],
 "caveats": [
  "With three enrolled accounts, treat early findings as anecdotal.",
  "Elevation preview and /v2beta/matrix still need their own decisions communicated to production callers."
 ]
}

What step 5 prints for it: Hikeline enroll | Courier Loop enroll | Openfield Realty enroll | Atlorra Transit substitute

The page's check: kept - nothing to flag

Build the input yourself

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

const K = CharterKit, R = CharterReply;
const f = K.form(Object.assign(K.blank(), { name: "Acme export", owner: "Sam", asof: "2026-11-01", contract: "private | access | Invite only\nprivate | removal | tbd",
  cohort: "Bigco | yes | yes | Lee | yes | yes | no | 30 | none", surfaces: "/beta/old | beta | 2024-01-01 | 2024-06-01 | 900 | 12 | Sam | 0 | yes" }));
// f.rank (the ranked shapes and their moves), f.grid (each stage's eleven rows, holes marked), f.cohortRows (each decision), f.surfaceRows (ga, next or kill), f.gate
// K.contractMd(f), K.rescueCsv(f) - the contract table and the rescue view
const b = K.buildInput(f, "charter");                        // or "rescue" (R.laneForm(form, "rescue", charter) fills carry)
const wire = K.toWire(b.input);                              // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "charter");
// 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://beta-charter.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":"beta-charter"}' | 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": "charter", "program": "Feature: Relaybank bulk export
What enters beta and why: ...", "register": "Stage contract by stage (row | term):
Private preview:
- access | Invite only, allowlisted accounts ...", "carry": "", "facts": "The page's checks (12 fail, ...); verdict: do not enroll anyone yet:
- Shape and contract | owner | pass | ..."}
# - 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 beta-charter:<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 charter reply carries summary, shape, contract, wire, exit, cohort, signoffs and caveats; a rescue reply carries summary, surfaces, retroactive and caveats. The code below prints the gist of either: each account and its decision, or each surface and its decision.

KEY="beta-charter:$(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 == "rescue" then (.surfaces[] | "\(.surface): \(.decision)") 
       else (.cohort[] | "\(.account): \(.decision)") 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: beta-charter:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json