Run Market Seed from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page runs the build gate on four evidenced triggers, ranks curation, take-rate and seeding with the moves the interview causes, and checks the governance rules, the economics and the seeding plan, then hands it all to the run as facts. The run writes the marketplace decision or the rulebook; the page then holds it to its own facts (msreply.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":"market-seed"}), so no slug header is needed afterwards. Send it as Authorization: Bearer YOUR_TOKEN. The run body is the input object, sent bare.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required key (task, platform, rules, facts) is missing. The page always sends all five of task, platform, rules, carry, facts; carry is an empty string for decision. |
| 401 | unauthorized | No token, or a stale one. Mint a guest token or sign in again. |
| 402 | insufficient_credits | The balance is under min_credits. Price with /estimate first. |
| 403 | forbidden | A guest token tried to run: both lanes are metered and need a personal sign-in. |
| 404 | not_found | Unknown job id. |
| 429 | rate_limited | Back off and retry. |
| 5xx | server_error | Transient. 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 - whether and which way, then the rules once the gate says build:
decision- build, buy, catalog or join: the four triggers with their evidence, what reopens build or which business case carries it, two or three candidates and a recommendation.rulebook- the operating rules for the chosen model: governance with owners and removal, the four market-failure causes, the take-rate with notice and enforcement, seeding and supply-first metrics. The page builds this input only when its gate says build.
A missing or unknown task is answered as decision. 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 decision into carry for the rulebook lane.
The input
| Field | Required | What |
|---|---|---|
task | yes | decision or rulebook. If it is missing or neither, do decision and set "task": "decision". |
platform | yes | the product and why a marketplace now, the ecosystem today, demand and supply evidence, staffing, purpose, data sensitivity, intent, curation, billing, the planned rate and notice, partner concentration, placement, the hard side, the atomic network and the re-ranking answers; "not given" means not given. |
rules | yes | every governance rule drafted so far with the question it answers, its owner, its removal path and the market failure it addresses. |
carry | send it (empty for decision) | rulebook task only: the decision's choices, when the decision task ran first; may be empty. |
facts | yes | the page's checks, one line each as area | id | status | evidence, the verdict and the ranked menus. A line starting "must:" applies to your reply. |
retry_note | no | optional; if present, your previous reply was unusable - follow it. |
The rules the model applies
From the source skill, @samber/connector-marketplace-strategy - the only standard the run applies:
- Most SaaS platforms should not build a marketplace, or not yet. Joining the marketplaces customers already trust is the pragmatic default; buying embedded iPaaS is the middle path when the need is many integrations fast rather than becoming a distribution hub; building is the most expensive and riskiest path. Build only when all four triggers hold with cited evidence: customer pull, a supply signal, repeated procurement friction from one-off integration requests, and a partner lifecycle staffed as a standing commitment. When some fail, a curated partner catalog or better bilateral integrations comes first, with the failed triggers as the conditions to revisit build. Name which business case carries a build: ecosystem and distribution value, or retention and expansion value.
- An operating model bundles a curation level, a partner mix, a take-rate and a seeding plan; candidates differ on the bundle, not on one dial, and each names the platform archetype it resembles, what it optimizes, what it costs and the failure it courts.
- Governance answers three questions in writing, each with an owner: who gets in, how value divides between platform, partner and customer, and how conflicts, enforcement and removal work. Every rule traces to a market-failure cause - information asymmetry, externalities, monopoly power, risk - and a cause with no rule is a gap. Delisting is the enforcement lever; plan for partner concentration by pooling scale for smaller partners; schedule governance review rather than freezing launch rules.
- Curation: hybrid by default - open admission behind a minimal automated bar plus a certified tier, where losing the badge never drops the listing; open admission when apps touch little sensitive data and supply is the constraint; a single hard review gate when apps read sensitive data; partnership-gated listing when resellers or co-selling are expected. Over-curation at cold start starves the hard side. Paid featured placement needs a disclosure label.
- Take-rate: zero while seeding - an architecture, not an embarrassment; volume-tiered once billing flows through the platform and some partners are visibly winning; a flat 15-25% plus fees only once the marketplace is the procurement gate - never on ambition alone. Every rate needs an anti-circumvention rule with a delisting path, and the strategy commits to a notice period for rate changes - six months, with unfavourable changes deferred.
- Seeding: name the hard side (for app stores, the developers who build) and the atomic network - one dense use case, not a broad thin catalog. First-party complements by default; a stand-alone tool the hard side wants when it has no reason to show up yet; subsidies when a hard date has budget behind it; hackathons and community layered on top for a compounding mandate with an existing community, never replacing the rest.
- Measure supply first - active partners, time to first listing, catalog freshness, partner revenue concentration - then demand (share of customers with an installed connector, integrations per account) and flywheel coupling (marketplace-influenced deals), baselined against your own first quarter.
House rules
- Use only the input. Never invent a customer, a partner, a platform, a figure or a date - except a value a field below asks you to propose, always labelled as proposed.
- The page's gate, rankings and checks are settled: never move a path or a rung, rank differently or call a failed gate fine.
- No web links (http or https).
- Write in the language of the input; keep the JSON keys and the path, curation, take-rate and seeding ids as given.
- Never use backticks and never write the input's field names as labels in your text. Words and terms of art from the rules above are free to use; what you never do is paste a whole sentence or quoted saying from this prompt, or state a rule with no fact of this platform in it.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
decision
Write the decision document: build, buy, catalog or join, and how.
summary- one sentence, at most 40 words: the path, and the evidence it rests on.path-choice(build,buy,catalogorjoin, exactly as the facts settle it),why(one sentence in this platform's terms).triggers- exactly four objects in this order - customer pull, supply signal, procurement friction, staffed lifecycle:trigger(its name),holds(true or false, as the facts read it),evidence(one sentence citing this platform's evidence, or what is missing).business_case- one sentence naming which case carries a build - distribution and ecosystem value, or retention and expansion value - for this platform; empty when the path is not build.revisit- short strings: each failing trigger, as the measurable condition in this platform's terms that would reopen build; empty when the path is build.candidates- two or three objects:name,archetype(the platform archetype it resembles, by type, not a company the input does not name),curation(hybrid,open,revieworpartnership),take_rate(zero,tieredorflat),seeding(firstparty,sequential,subsidiesorsocial),optimizes,costs,failure(one sentence each). When the path is build, every candidate bundles the three rungs, the bundles differ, and one carries the page's ranked rungs. When it is not build, the candidates are ways to carry out that path and the three rung ids are empty strings.recommendation-candidate(one candidate's name, exactly),why(one sentence tied to this platform's answers).caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "decision",
"summary": "one sentence",
"path": { "choice": "build", "why": "one sentence" },
"triggers": [ { "trigger": "customer pull", "holds": true, "evidence": "one sentence" } ],
"business_case": "one sentence",
"revisit": [ ],
"candidates": [ { "name": "a short name", "archetype": "a platform type", "curation": "hybrid", "take_rate": "zero", "seeding": "firstparty", "optimizes": "one sentence", "costs": "one sentence", "failure": "one sentence" } ],
"recommendation": { "candidate": "a candidate's name", "why": "one sentence" },
"caveats": [ ]
}
The page holds it to its own facts (check() in msreply.js; any warning makes the reply flagged): the reply answers the task it was sent; no web link; no backticks; a summary of at most 40 words; the gate's path; all four triggers in order, holding or failing as the page reads them, each with evidence; the business case for a build, or each failing trigger as a condition to reopen build; two or three candidates with what each optimizes, costs and courts - for a build each bundling the page's rung ids, the bundles differing and one carrying the ranked rungs; a recommendation naming one candidate; at most four caveats.
rulebook
Write the operating rules for the chosen model (the page offers this task only when the gate says build).
summary- one sentence, at most 40 words.governance-admission,value,conflict, each an object:rule(one or two sentences for this platform),owner(the role that owns it, as the input names it, else a role to appoint, said to be appointed),removal(the removal or delisting path; required for conflict).causes- four objects, one per market-failure cause:cause(information asymmetry, externalities, monopoly power or risk),rule(the rule that addresses it on this platform).economics-take_rate(zero,tieredorflat, exactly as the facts rank first),level(the percentage or threshold, as the input gives it or labelled as proposed),notice(the notice period for rate changes - at least six months),circumvention(the anti-circumvention rule with its delisting path),disclosure(the label rule for paid placement - written whenever paid placement is planned, even if no rule exists yet; empty only when none is planned).seeding-hard_side(who it is on this platform),atomic_network(the one dense use case),rung(firstparty,sequential,subsidiesorsocial, exactly as the facts rank first),promote_when(one sentence: the measurable condition that moves to the next rung).metrics-supply,demand,flywheel(short strings each; supply first).review- one sentence: by whom the governance rules are reviewed, and when - a cadence labelled proposed, or the events that trigger a review.caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "rulebook",
"summary": "one sentence",
"governance": { "admission": { "rule": "", "owner": "", "removal": "" }, "value": { "rule": "", "owner": "", "removal": "" }, "conflict": { "rule": "", "owner": "", "removal": "" } },
"causes": [ { "cause": "information asymmetry", "rule": "one sentence" } ],
"economics": { "take_rate": "zero", "level": "", "notice": "six months", "circumvention": "one sentence", "disclosure": "" },
"seeding": { "hard_side": "", "atomic_network": "", "rung": "firstparty", "promote_when": "one sentence" },
"metrics": { "supply": [ "a metric" ], "demand": [ "a metric" ], "flywheel": [ "a metric" ] },
"review": "one sentence",
"caveats": [ ]
}
The page requires: the reply answers the task it was sent; the same text rules; admission, value and conflict rules each with an owner, and conflict with a removal path; all four market-failure causes, each with its rule; the ranked take-rate, at least six months' notice and an anti-circumvention rule with delisting (and a label rule when paid placement is planned); the hard side, the atomic network, the ranked seeding rung and what promotes it; supply metrics; a governance review.
Worked examples
Three runs from the page's free samples (invented companies): the decision for a contract platform with real pull and partners, the rulebook for the same platform carrying that decision, and the decision for an HR SaaS whose board wants an app store. 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.
decision - A contract platform with real pull and partners
The page read it as: a marketplace you can build and run; gate path build (4 of 4 triggers); curation hybrid, open, review, partnership; take-rate zero; seeding firstparty, social, sequential; 5 of 5 gates; failing checks: none.
Input (facts is 2271 characters; the first 1399 are shown):
{
"task": "decision",
"platform": "Platform: Clausewren\nThe product and why a marketplace now: A contract-management platform; enterprise deals stall over missing CRM and e-signature integrations, and three vendors already ship unofficial connectors.\nEcosystem today: A partner directory or catalog; integrations live: 14; partners asking to build: 9; demand: Deals lost or stalled over missing integrations; procurement friction: yes; supply: Third parties already shipped unofficial integrations; partner lifecycle staffed: yes; many integrations needed fast: no\nPurpose: Retention and stickiness; data sensitivity: medium; team intent: build; planned curation: Hybrid: open admission plus a certified tier; resellers or co-selling expected: no\nBilling through the platform: no; partners visibly winning: no; marketplace is the procurement gate: no; planned take-rate: 0%; notice for rate changes: 6 months; top partner's share of partner revenue: 45%; paid featured placement: yes (disclosed: yes)\nHard side: developers; it has a reason to show up today: yes; atomic network: contract approval flows that start in a CRM and end in e-signature; subsidy budget: no; developer community: yes; hard date: no; a compounding asset; effort ceiling: medium; as-of: 2026-11-01",
"rules": "Governance rules (rule | answers | owner | removal path | market failure):\n- Open listing behind automated checks; certified badge for apps passing the security questionnaire and a 99% API success floor | answers: admission (who gets in, and who decides) | owner: Partner program manager | removal path: yes | against: information asymmetry, risk\n- Zero revenue share while seeding, with six months' notice before any change | answers: value (how value divides between platform, partner and customer) | owner: Head of partnerships | removal path: NO | against: information asymmetry\n- Apps routing contract billing around the platform are delisted after 30 days' notice | answers: conflict (disputes, enforcement and removal) | owner: Partner program manager | removal path: yes | against: externalities\n- Paid featured slots are labelled as sponsored | answers: value (how value divides between platform, partner and customer) | owner: Marketing lead | removal path: NO | against: information asymmetry\n- Shared analytics and co-marketing pool for partners under 10 installs | answers: value (how value divides between platform, partner and customer) | owner: Head of partnerships | removal path: NO | against: monopoly power",
"carry": "",
"facts": "The page's checks (0 fail, 0 warn, 6 pass, 1 n/a); verdict: a marketplace you can build and run:\n- The gate and the model | triggers | na | customer pull: holds - Deals lost or stalled over missing integrations; a supply signal: holds - Third parties already shipped unofficial integrations; repeated procurement friction: holds - one-off integration requests keep stalling procurement; a staffed partner lifecycle: holds - public API, docs, sandbox, review and partner support as a standing commitment - build our own marketplace\n- The gate and the model | evidence | pass | the intent (build) fits the gate (build)\n- Governance | governance | pass | all three questions answered, owned, with a delisting path\n- Governance | concentration | pass | the top partner holds 45% of partner revenue - pool scale (shared analytics and marketing) for the rest\n- Economics and seeding | curation | pass | planned Hybrid: open admission plus a certified tier; ranked: Hybrid: open admission plus a certified tier > Open admission > A single hard review gate > Partnership-gated listing\n- Economics and seeding | economics | pass | zero take, 6 months' notice committed; ranked: Zero take while seeding - billing does not flow through the platform - no share can be collected\n- Economics and seeding | seeding | pass | hard side: the developers who build the apps; atomic network: contract approval flows that\n\n[... 872 more characters cut from this display ...]"
}
Reply (long strings shortened for display - see the cuts below):
{
"task": "decision",
"summary": "Clausewren should build its own marketplace: stalled enterprise deals, nine partners asking to build, three unofficial connectors, repeated procurement friction and a staffed partner lifecycle clear all four triggers.",
"path": {
"choice": "build",
"why": "Every trigger holds for Clausewren, and the CRM and e-signature connectors that keep stalling enterprise contracts are already being built by outsiders without a home on the platform."
},
"triggers": [
{
"trigger": "customer pull",
"holds": true,
"evidence": "Enterprise deals are lost or stalled over missing CRM and e-signature integrations."
},
{
"trigger": "supply signal",
"holds": true,
"evidence": "Three vendors already ship unofficial Clausewren connectors and nine partners are asking to build."
},
{
"trigger": "procurement friction",
"holds": true,
"evidence": "One-off integration requests keep stalling enterprise procurement of Clausewren."
},
{
"trigger": "staffed lifecycle",
"holds": true,
"evidence": "A public API, docs, sandbox, review and partner support are staffed as a standing commitment."
}
],
"business_case": "Retention and expansion value carries the build: Clausewren's purpose is stickiness, and connectors that keep contract approvals flowing from the CRM to e-signature make accounts harder to leave rather than opening a new revenue line.",
"revisit": [],
"candidates": [
{
"name": "Certified contract-flow marketplace",
"archetype": "a large CRM's curated app directory with a certified tier",
"curation": "hybrid",
"take_rate": "zero",
"seeding": "firstparty",
"optimizes": "It lets the nine interested partners and the three unofficial connectors list quickly behind automated checks while the certified badge signals which apps meet the security questionnaire and the 99% API success floor.",
"costs": "Clausewren must keep its own CRM-to-e-signature connectors best in class and run the certification checks continuously.",
"failure": "If the badge tier grows lax, buyers stop trusting it and the certified apps lose their edge in enterprise procurement."
},
{
"name": "Open connector hub",
"archetype": "an open developer platform's self-serve app listing",
"curation": "open",
"take_rate": "zero",
"seeding": "sequential",
"optimizes": "It maximizes listings by admitting any app that passes automated checks and drawing builders in with the sandbox and tooling first.",
"costs": "It gives up the certified signal enterprise buyers rely on and pushes quality policing onto support and delisting.",
"failure": "With medium-sensitivity contract data, a weak listing can damage trust in the whole catalog before enforcement catches it."
},
{
"name": "Community-built certified catalog",
"archetype": "a developer-tools platform's community-driven extension gallery with a verified tier",
"curation": "hybrid",
"take_rate": "zero",
"seeding": "social",
"optimizes": "It leans on Clausewren's existing developer community and hackathons to fill the catalog behind the same open-plus-certified bar.",
"costs": "It needs steady community programming and event effort against a medium effort ceiling.",
"failure": "Leading with community events instead of first-party complements risks a broad, thin catalog that never makes the CRM-to-e-signature flow dense."
}
],
"recommendation": {
"candidate": "Certified contract-flow marketplace",
"why": "It carries the page's ranked rungs, matches the hybrid curation and zero take Clausewren already planned, and seeds the one dense use case, contract approvals from CRM to e-signature, with Clausewren's own connectors before layering community [... 13 more characters cut from this display]"
},
"caveats": [
"The top partner holds 45% of partner revenue, so the shared analytics and co-marketing pool for smaller partners needs to stay funded.",
"Paid featured slots must keep their sponsored label as the catalog grows.",
"The value-division rules have no removal path written; only the conflict rule names delisting."
]
}
Cut from this display only (the saved reply and the check use the full text): recommendation.why (251 characters, 238 shown).
What step 5 prints for it: build: Clausewren should build its own marketplace: stalled enterprise deals, nine partners asking to build, three unofficial connectors, repeated procurement friction and a staffed partner lifecycle clear all four triggers.
The page's check: kept - nothing to flag
rulebook - A contract platform with real pull and partners
The input carries the saved decision (R.laneForm fills carry from the decision reply), as the page's handoff does. Take-rate ranked: zero; seeding ranked: firstparty, social, sequential.
Input (facts is 2371 characters; the first 1399 are shown):
{
"task": "rulebook",
"platform": "Platform: Clausewren\nThe product and why a marketplace now: A contract-management platform; enterprise deals stall over missing CRM and e-signature integrations, and three vendors already ship unofficial connectors.\nEcosystem today: A partner directory or catalog; integrations live: 14; partners asking to build: 9; demand: Deals lost or stalled over missing integrations; procurement friction: yes; supply: Third parties already shipped unofficial integrations; partner lifecycle staffed: yes; many integrations needed fast: no\nPurpose: Retention and stickiness; data sensitivity: medium; team intent: build; planned curation: Hybrid: open admission plus a certified tier; resellers or co-selling expected: no\nBilling through the platform: no; partners visibly winning: no; marketplace is the procurement gate: no; planned take-rate: 0%; notice for rate changes: 6 months; top partner's share of partner revenue: 45%; paid featured placement: yes (disclosed: yes)\nHard side: developers; it has a reason to show up today: yes; atomic network: contract approval flows that start in a CRM and end in e-signature; subsidy budget: no; developer community: yes; hard date: no; a compounding asset; effort ceiling: medium; as-of: 2026-11-01",
"rules": "Governance rules (rule | answers | owner | removal path | market failure):\n- Open listing behind automated checks; certified badge for apps passing the security questionnaire and a 99% API success floor | answers: admission (who gets in, and who decides) | owner: Partner program manager | removal path: yes | against: information asymmetry, risk\n- Zero revenue share while seeding, with six months' notice before any change | answers: value (how value divides between platform, partner and customer) | owner: Head of partnerships | removal path: NO | against: information asymmetry\n- Apps routing contract billing around the platform are delisted after 30 days' notice | answers: conflict (disputes, enforcement and removal) | owner: Partner program manager | removal path: yes | against: externalities\n- Paid featured slots are labelled as sponsored | answers: value (how value divides between platform, partner and customer) | owner: Marketing lead | removal path: NO | against: information asymmetry\n- Shared analytics and co-marketing pool for partners under 10 installs | answers: value (how value divides between platform, partner and customer) | owner: Head of partnerships | removal path: NO | against: monopoly power",
"carry": "Path: Build our own marketplace - Every trigger holds for Clausewren, and the CRM and e-signature connectors that keep stalling enterprise contracts are already being built by outsiders without a home on the platform.\nBusiness case: Retention and expansion value carries the build: Clausewren's purpose is stickiness, and connectors that keep contract approvals flowing from the CRM to e-signature make accounts harder to leave rather than opening a new revenue line.\nChosen model: Certified contract-flow marketplace (curation hybrid, take-rate zero, seeding firstparty)\nWhy: It carries the page's ranked rungs, matches the hybrid curation and zero take Clausewren already planned, and seeds the one dense use case, contract approvals from CRM to e-signature, with Clausewren's own connectors before layering community work on top.",
"facts": "The page's checks (0 fail, 0 warn, 6 pass, 1 n/a); verdict: a marketplace you can build and run:\n- The gate and the model | triggers | na | customer pull: holds - Deals lost or stalled over missing integrations; a supply signal: holds - Third parties already shipped unofficial integrations; repeated procurement friction: holds - one-off integration requests keep stalling procurement; a staffed partner lifecycle: holds - public API, docs, sandbox, review and partner support as a standing commitment - build our own marketplace\n- The gate and the model | evidence | pass | the intent (build) fits the gate (build)\n- Governance | governance | pass | all three questions answered, owned, with a delisting path\n- Governance | concentration | pass | the top partner holds 45% of partner revenue - pool scale (shared analytics and marketing) for the rest\n- Economics and seeding | curation | pass | planned Hybrid: open admission plus a certified tier; ranked: Hybrid: open admission plus a certified tier > Open admission > A single hard review gate > Partnership-gated listing\n- Economics and seeding | economics | pass | zero take, 6 months' notice committed; ranked: Zero take while seeding - billing does not flow through the platform - no share can be collected\n- Economics and seeding | seeding | pass | hard side: the developers who build the apps; atomic network: contract approval flows that\n\n[... 972 more characters cut from this display ...]"
}
Reply (long strings shortened for display - see the cuts below):
{
"task": "rulebook",
"summary": "Clausewren runs a hybrid, zero-take marketplace seeded with its own CRM-to-e-signature connectors, where delisting enforces billing rules, sponsored slots carry a label and smaller partners share a scale pool.",
"governance": {
"admission": {
"rule": "Any developer can list a Clausewren app once it clears the automated checks; the certified badge goes only to apps that pass the security questionnaire and hold a 99% API success floor, and losing the badge never drops the listing.",
"owner": "Partner program manager",
"removal": "An app that falls below the 99% API success floor or fails a repeat security questionnaire loses its certified badge but stays listed; an app that fails the automated checks is unlisted until it passes again."
},
"value": {
"rule": "Clausewren takes no revenue share while seeding and gives six months' notice before any change, paid featured slots on the Clausewren marketplace are labelled as sponsored, and partners under 10 installs draw on a shared analytics and [... 19 more characters cut from this display]",
"owner": "Head of partnerships",
"removal": "Proposed: a paid featured slot shown without its sponsored label is pulled until the label is restored, and a partner leaves the shared pool once it passes 10 installs."
},
"conflict": {
"rule": "A Clausewren app that routes contract billing around the platform is in breach, and disputes over that breach are decided by the partner program manager.",
"owner": "Partner program manager",
"removal": "The app is delisted after 30 days' notice if the billing route is not brought back through Clausewren."
}
},
"causes": [
{
"cause": "information asymmetry",
"rule": "The certified badge, earned through the security questionnaire and the 99% API success floor, and the sponsored label on paid slots tell Clausewren buyers which connectors are vetted and which are paid placement."
},
{
"cause": "externalities",
"rule": "Apps that route contract billing around Clausewren are delisted after 30 days' notice, so one partner's workaround does not erode the marketplace for everyone else."
},
{
"cause": "monopoly power",
"rule": "With the top partner holding 45% of partner revenue, the shared analytics and co-marketing pool for partners under 10 installs gives smaller Clausewren partners scale they could not reach alone."
},
{
"cause": "risk",
"rule": "Because Clausewren connectors touch contract data of medium sensitivity, the certified tier requires the security questionnaire and the 99% API success floor, and an app that fails them loses its badge."
}
],
"economics": {
"take_rate": "zero",
"level": "0%",
"notice": "6 months, with any unfavourable change deferred until the notice runs out",
"circumvention": "A Clausewren app that routes contract billing around the platform is delisted after 30 days' notice, owned by the partner program manager.",
"disclosure": "Every paid featured slot on the Clausewren marketplace carries a sponsored label, owned by the marketing lead."
},
"seeding": {
"hard_side": "The developers who build Clausewren apps, including the three vendors already shipping unofficial connectors and the nine partners asking to build.",
"atomic_network": "Contract approval flows that start in a CRM and end in e-signature.",
"rung": "firstparty",
"promote_when": "Proposed: move to hackathons and community once Clausewren's own CRM and e-signature connectors are live and the unofficial third-party connectors have been listed on the marketplace."
},
"metrics": {
"supply": [
"active partners listing Clausewren apps",
"time to first listing for a new partner",
"catalog freshness of listed connectors",
"top partner's share of partner revenue, from 45% today"
],
"demand": [
"share of Clausewren customers with an installed connector",
"integrations per account"
],
"flywheel": [
"marketplace-influenced deals, baselined against the first quarter"
]
},
"review": "Proposed: the head of partnerships and the partner program manager review the Clausewren governance rules each quarter, starting from the first quarter's baseline.",
"caveats": [
"The value rules had no removal path in the draft; the ones above are proposed.",
"The top partner's 45% share of partner revenue makes the shared pool a rule to watch, not a formality.",
"Billing does not flow through Clausewren, so no share could be collected yet even if the rate changed."
]
}
Cut from this display only (the saved reply and the check use the full text): governance.value.rule (249 characters, 230 shown).
What step 5 prints for it: admission: Partner program manager | value: Head of partnerships | conflict: Partner program manager
The page's check: kept - nothing to flag
decision - An HR SaaS whose board wants an app store
The page read it as: not yet - a curated catalog first; gate path catalog (1 of 4 triggers); curation review, hybrid, open; take-rate zero; seeding sequential, firstparty; 0 of 1 gates; failing checks: evidence.
Input (facts is 2404 characters; the first 1398 are shown):
{
"task": "decision",
"platform": "Platform: Rotavane\nThe product and why a marketplace now: A shift-scheduling and HR SaaS for restaurants; the board wants an app store like the big HR suites have.\nEcosystem today: Bilateral integrations we built; integrations live: 6; partners asking to build: 0; demand: Recurring support and feature requests; procurement friction: no; supply: Neither; partner lifecycle staffed: no; many integrations needed fast: no\nPurpose: A revenue line; data sensitivity: high; team intent: build; planned curation: A single hard review gate; resellers or co-selling expected: no\nBilling through the platform: no; partners visibly winning: no; marketplace is the procurement gate: no; planned take-rate: 20%; notice for rate changes: not given; top partner's share of partner revenue: not given; paid featured placement: no (disclosed: no)\nHard side: developers; it has a reason to show up today: no; atomic network: not given; subsidy budget: no; developer community: no; hard date: no; a compounding asset; effort ceiling: low; as-of: 2026-11-01",
"rules": "Governance rules (rule | answers | owner | removal path | market failure):\n- Every app passes a security review before listing | answers: admission (who gets in, and who decides) | owner: Platform security lead | removal path: yes | against: risk",
"carry": "",
"facts": "The page's checks (1 fail, 4 warn, 0 pass, 2 n/a); verdict: not yet - a curated catalog first:\n- The gate and the model | triggers | na | customer pull: holds - Recurring support and feature requests; a supply signal: fails - Neither; repeated procurement friction: fails - not reported; a staffed partner lifecycle: fails - not staffed as a standing commitment - a curated partner catalog first\n- The gate and the model | evidence | fail | the team intends to build, but a supply signal, repeated procurement friction, a staffed partner lifecycle fail - an expensive partner directory waiting to happen; a curated partner catalog first now, with the failed triggers as the conditions to revisit build\n- Governance | governance | warn | once you build: no rule answers value (how value divides between platform, partner and customer), conflict (disputes, enforcement and removal)\n- Governance | causes | warn | once you build: no rule against information asymmetry, externalities, monopoly power\n- Economics and seeding | curation | na | once you build: planned A single hard review gate; ranked: A single hard review gate > Hybrid: open admission plus a certified tier > Open admission - every app can read sensitive customer data - a single hard review gate, despite its cost; a low effort ceiling rules out partnership-gated listing\n- Economics and seeding | economics | warn | once you build: a\n\n[... 1006 more characters cut from this display ...]"
}
Reply:
{
"task": "decision",
"summary": "Rotavane should run a curated catalog of its six security-reviewed integrations, not an app store: recurring requests show customer pull, but no partner has asked to build, procurement is not stalling and no partner team is staffed.",
"path": {
"choice": "catalog",
"why": "Only customer pull holds for Rotavane, so a reviewed catalog of the integrations it already runs answers the recurring requests without staffing a developer program that no partner has asked for."
},
"triggers": [
{
"trigger": "customer pull",
"holds": true,
"evidence": "Restaurant customers keep sending support tickets and feature requests for integrations."
},
{
"trigger": "supply signal",
"holds": false,
"evidence": "No partner has asked to build on Rotavane and no third party has shipped an unofficial integration; all six live integrations were built in-house."
},
{
"trigger": "procurement friction",
"holds": false,
"evidence": "Rotavane reports no deals stalling over one-off integration requests."
},
{
"trigger": "staffed lifecycle",
"holds": false,
"evidence": "No one is staffed to run partner onboarding, review and support as a standing commitment, and the effort ceiling is low."
}
],
"business_case": "",
"revisit": [
"Supply signal: at least one outside partner asks to build on Rotavane or ships an unofficial integration, up from zero today.",
"Procurement friction: restaurant deals are recorded as lost or stalled over a missing integration, where none are reported today.",
"Staffed lifecycle: a named owner and budget for partner onboarding, the security review and partner support are committed as standing work, not borrowed from the security lead."
],
"candidates": [
{
"name": "Reviewed integration catalog",
"archetype": "a vertical SaaS's in-product integrations page",
"curation": "",
"take_rate": "",
"seeding": "",
"optimizes": "It turns the six integrations Rotavane already runs into one findable, security-reviewed list that answers the recurring requests.",
"costs": "It costs a catalog page and keeping each entry's description and review current, which fits a low effort ceiling.",
"failure": "It can go stale if listings are never refreshed, leaving restaurants to read it as a dead directory."
},
{
"name": "Deeper bilateral six",
"archetype": "a point-solution SaaS's settings-page connectors",
"curation": "",
"take_rate": "",
"seeding": "",
"optimizes": "It ranks the recurring requests and spends the engineering on making the existing integrations cover them better, with no listing surface at all.",
"costs": "Every improvement stays on Rotavane's own engineering roadmap, with no outside builder sharing the load.",
"failure": "Customers never see the integrations in one place, so the board's app-store question returns with nothing to show."
},
{
"name": "Invite-only partner catalog",
"archetype": "a large HR suite's partner directory",
"curation": "",
"take_rate": "",
"seeding": "",
"optimizes": "It lists the in-house integrations plus a few invited partner integrations that pass the security review, testing whether supply exists.",
"costs": "Each invited partner needs the full security review and someone to chase them, which strains a low effort ceiling and an unstaffed partner function.",
"failure": "With no partners asking to build, invitations go unanswered and the catalog looks half-empty."
}
],
"recommendation": {
"candidate": "Reviewed integration catalog",
"why": "It answers the one trigger that holds, the recurring integration requests, within a low effort ceiling while keeping every listing behind the security review that high-sensitivity HR data needs."
},
"caveats": [
"The planned 20% share cannot be collected while billing does not flow through Rotavane, so the board's revenue-line purpose does not fit a catalog.",
"Only an admission rule is written; value division and conflict and removal rules are still missing for any later build.",
"No atomic network is named, so a future build has no dense use case to seed yet."
]
}
What step 5 prints for it: catalog: Rotavane should run a curated catalog of its six security-reviewed integrations, not an app store: recurring requests show customer pull, but no partner has asked to build, procurement is not stalling and no partner team is staffed.
The page's check: kept - nothing to flag
Build the input yourself
Load the engine in node (global.window = global; require("./mskit.js"); require("./msreply.js")), then:
const K = MarketKit, R = MarketReply;
const f = K.form(Object.assign(K.blank(), { name: "Acme", demand: "deals", friction: "yes", supply: "asking", staffed: "no",
rules: "Open listing behind automated checks | admission | Partner lead | yes | security risk" }));
// f.gatePath (the four triggers, the path, what failed), f.curationRank, f.rateRank, f.seedRank, f.ruleRows, f.gate
// K.gateMd(f), K.rulesCsv(f) - the gate memo and the rules table
const b = K.buildInput(f, "decision"); // "rulebook" only once the gate says build
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "decision");
// 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://market-seed.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"}
}
import json, urllib.request
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://market-seed.skillsafe.ai/tokens.html
def api(method, path, body=None, headers=None):
req = urllib.request.Request(BASE + path, method=method,
data=None if body is None else json.dumps(body).encode(),
headers={"Content-Type": "application/json", **({"Authorization": "Bearer " + TOKEN} if TOKEN else {}), **(headers or {})})
with urllib.request.urlopen(req) as r:
env = json.load(r)
if env.get("error"):
raise RuntimeError(env["error"])
return env["data"]
// market-seed.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://market-seed.skillsafe.ai/tokens.html
async function api(method, path, body, headers = {}) {
const r = await fetch(BASE + path, { method, headers: { "Content-Type": "application/json",
...(TOKEN ? { Authorization: "Bearer " + TOKEN } : {}), ...headers },
body: body === undefined ? undefined : JSON.stringify(body) });
const env = await r.json();
if (env.error) throw new Error(env.error.code + ": " + env.error.message);
return env.data;
}
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
var token = "YOUR_TOKEN" // from https://market-seed.skillsafe.ai/tokens.html
func api(method, path string, body any, headers map[string]string) (map[string]any, error) {
var buf bytes.Buffer
if body != nil { json.NewEncoder(&buf).Encode(body) }
req, _ := http.NewRequest(method, base+path, &buf)
req.Header.Set("Content-Type", "application/json")
if token != "" { req.Header.Set("Authorization", "Bearer "+token) }
for k, v := range headers { req.Header.Set(k, v) }
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
defer res.Body.Close()
var env struct { Data map[string]any; Error map[string]any }
json.NewDecoder(res.Body).Decode(&env)
if env.Error != nil { return nil, fmt.Errorf("%v", env.Error) }
return env.Data, nil
}
// Market Seed.java -- Java 17+, with com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.*;
import java.net.URI; import java.net.http.*; import java.util.*;
public class Market Seed {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://market-seed.skillsafe.ai/tokens.html
static final ObjectMapper M = new ObjectMapper();
static final HttpClient C = HttpClient.newHttpClient();
static JsonNode api(String method, String path, Object body, Map<String, String> headers) throws Exception {
var b = HttpRequest.newBuilder(URI.create(BASE + path)).header("Content-Type", "application/json");
if (!token.isEmpty()) b.header("Authorization", "Bearer " + token);
if (headers != null) headers.forEach(b::header);
b.method(method, body == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(M.writeValueAsString(body)));
JsonNode env = M.readTree(C.send(b.build(), HttpResponse.BodyHandlers.ofString()).body());
if (env.hasNonNull("error")) throw new RuntimeException(env.get("error").toString());
return env.get("data");
}
}
require "json"
require "net/http"
BASE = "https://api.skillsafe.ai/v1/app-api"
TOKEN = "YOUR_TOKEN" # from https://market-seed.skillsafe.ai/tokens.html
def api(method, path, body = nil, headers = {})
uri = URI(BASE + path)
req = Net::HTTPGenericRequest.new(method, !body.nil?, true, uri.request_uri,
{ "Content-Type" => "application/json" }.merge(TOKEN.empty? ? {} : { "Authorization" => "Bearer #{TOKEN}" }).merge(headers))
req.body = JSON.generate(body) unless body.nil?
env = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)
raise env["error"].to_s if env["error"]
env["data"]
end
<?php
const BASE = "https://api.skillsafe.ai/v1/app-api";
$TOKEN = "YOUR_TOKEN"; // from https://market-seed.skillsafe.ai/tokens.html
function api(string $method, string $path, $body = null, array $headers = []) {
global $TOKEN;
$h = ["Content-Type: application/json"];
if ($TOKEN) $h[] = "Authorization: Bearer $TOKEN";
foreach ($headers as $k => $v) $h[] = "$k: $v";
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $h, CURLOPT_RETURNTRANSFER => true]);
if ($body !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
$env = json_decode(curl_exec($ch), true);
if (!empty($env["error"])) throw new Exception(json_encode($env["error"]));
return $env["data"];
}
// Program.cs -- .NET 6+ console app
using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Nodes;
static class Api {
public const string Base = "https://api.skillsafe.ai/v1/app-api";
public static string Token = "YOUR_TOKEN"; // from https://market-seed.skillsafe.ai/tokens.html
static readonly HttpClient C = new();
public static async Task<JsonNode?> Call(string method, string path, object? body = null, Dictionary<string, string>? headers = null) {
var req = new HttpRequestMessage(new HttpMethod(method), Base + path);
if (Token != "") req.Headers.Add("Authorization", "Bearer " + Token);
if (headers != null) foreach (var h in headers) req.Headers.Add(h.Key, h.Value);
if (body != null) req.Content = JsonContent.Create(body);
var env = JsonNode.Parse(await (await C.SendAsync(req)).Content.ReadAsStringAsync())!;
if (env["error"] is JsonNode e && e.GetValueKind() != JsonValueKind.Null) throw new Exception(e.ToJsonString());
return env["data"];
}
}
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":"market-seed"}' | jq -r '.data.token')
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = ""
TOKEN = api("POST", "/guest", {"slug": "market-seed"})["token"]
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN = "";
TOKEN = (await api("POST", "/guest", { slug: "market-seed" })).token;
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = ""
g, err := api("POST", "/guest", map[string]string{"slug": "market-seed"}, nil)
if err != nil { panic(err) }
token = g["token"].(string)
// inside main: a guest token (enough for /me and /estimate); a run needs your personal token
token = "";
token = api("POST", "/guest", Map.of("slug", "market-seed"), null).get("token").asText();
# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN.replace("")
TOKEN.replace(api("POST", "/guest", { slug: "market-seed" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "market-seed"])["token"];
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
Api.Token = "";
Api.Token = (await Api.Call("POST", "/guest", new { slug = "market-seed" }))!["token"]!.GetValue<string>();
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}'
me = api("GET", "/me")
print(me["subject_type"], me.get("credits"))
const me = await api("GET", "/me");
console.log(me.subject_type, me.credits);
me, _ := api("GET", "/me", nil, nil)
fmt.Println(me["subject_type"], me["credits"])
JsonNode me = api("GET", "/me", null, null);
System.out.println(me.get("subject_type") + " " + me.get("credits"));
me = api("GET", "/me")
puts [me["subject_type"], me["credits"]].inspect
$me = api("GET", "/me");
echo $me["subject_type"], " ", $me["credits"] ?? "", "\n";
var me = await Api.Call("GET", "/me");
Console.WriteLine($"{me!["subject_type"]} {me["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": "decision", "platform": "Platform: Clausewren
The product and why a marketplace now: ...", "rules": "Governance rules (rule | answers | owner | removal path | market failure):
- Open listing behind automated checks ...", "carry": "", "facts": "The page's checks (...); verdict: a marketplace you can build and run:
- The gate and the model | triggers | na | ..."}
# - see the input table
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}'
inp = json.load(open("input.json")) # {"task": "decision", "platform": "Platform: Clausewren
The product and why a marketplace now: ...", "rules": "Governance rules (rule | answers | owner | removal path | market failure):
- Open listing behind automated checks ...", "carry": "", "facts": "The page's checks (...); verdict: a marketplace you can build and run:
- The gate and the model | triggers | na | ..."}
# - see the input table
est = api("POST", "/estimate", inp)
print(est["hold_credits"], est["min_credits"], est["model"], est.get("warnings"))
import { readFileSync } from "node:fs";
const input = JSON.parse(readFileSync("input.json", "utf8")); // {task: "decision" or "rulebook", platform, rules, carry, facts} (see the input table)
const est = await api("POST", "/estimate", input);
console.log(est.hold_credits, est.min_credits, est.model, est.warnings);
raw, _ := os.ReadFile("input.json") // add "os" to the imports
var input map[string]any
json.Unmarshal(raw, &input)
est, _ := api("POST", "/estimate", input, nil)
fmt.Println(est["hold_credits"], est["min_credits"], est["model"])
Map<String, Object> input = M.readValue(new java.io.File("input.json"), Map.class);
JsonNode est = api("POST", "/estimate", input, null);
System.out.println(est.get("hold_credits") + " " + est.get("min_credits") + " " + est.get("model"));
input = JSON.parse(File.read("input.json"))
est = api("POST", "/estimate", input)
puts [est["hold_credits"], est["min_credits"], est["model"]].inspect
$input = json_decode(file_get_contents("input.json"), true);
$est = api("POST", "/estimate", $input);
echo $est["hold_credits"], " ", $est["min_credits"], " ", $est["model"], "\n";
var input = JsonNode.Parse(File.ReadAllText("input.json"))!;
var est = await Api.Call("POST", "/estimate", input);
Console.WriteLine($"{est!["hold_credits"]} {est["min_credits"]} {est["model"]}");
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 market-seed:<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 decision reply carries summary, path, triggers, business_case, revisit, candidates, recommendation and caveats; a rulebook reply carries summary, governance, causes, economics, seeding, metrics, review and caveats. The code below prints the gist of either: the path and the summary, or the owner of each governance question.
KEY="market-seed:$(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 == "rulebook" then (.governance | to_entries[] | "\(.key): \(.value.owner)")
else "\(.path.choice): \(.summary)" end' reply.json
import hashlib, time
key = "market-seed:" + inp["task"] + ":" + hashlib.sha256(json.dumps(inp).encode()).hexdigest()[:16] + ":a1"
job = api("POST", "/run", inp, {"Idempotency-Key": key})
while True:
j = api("GET", "/jobs/" + job["job_id"])
if j["status"] in ("succeeded", "failed", "cancelled"):
break
time.sleep(2)
reply = json.loads(j["output"]["output"]) # the reply as one JSON object
print(j["charged_credits"], j.get("truncated"), reply["task"])
if reply["task"] == "rulebook":
for q, x in reply["governance"].items():
print(q, x["owner"])
else:
print(reply["path"]["choice"], reply["summary"])
import { createHash } from "node:crypto";
const key = `market-seed:${input.task}:${createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16)}:a1`;
const job = await api("POST", "/run", input, { "Idempotency-Key": key });
let j;
do { await new Promise((r) => setTimeout(r, 2000)); j = await api("GET", "/jobs/" + job.job_id); }
while (!["succeeded", "failed", "cancelled"].includes(j.status));
const reply = JSON.parse(j.output.output);
console.log(j.charged_credits, j.truncated, reply.task);
if (reply.task === "rulebook") { for (const [q, x] of Object.entries(reply.governance)) console.log(q, x.owner); }
else { console.log(reply.path.choice, reply.summary); }
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "market-seed:" + input["task"].(string) + ":" + hex.EncodeToString(sum[:8]) + ":a1"
job, _ := api("POST", "/run", input, map[string]string{"Idempotency-Key": key})
var j map[string]any
for {
time.Sleep(2 * time.Second)
j, _ = api("GET", "/jobs/"+job["job_id"].(string), nil, nil)
if s := j["status"]; s == "succeeded" || s == "failed" || s == "cancelled" { break }
}
var reply map[string]any
json.Unmarshal([]byte(j["output"].(map[string]any)["output"].(string)), &reply)
fmt.Println(j["charged_credits"], j["truncated"], reply["task"])
if reply["task"] == "rulebook" {
gv, _ := reply["governance"].(map[string]any)
for q, x := range gv {
fmt.Println(q, x.(map[string]any)["owner"])
}
} else {
p, _ := reply["path"].(map[string]any)
fmt.Println(p["choice"], reply["summary"])
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "market-seed:" + input.get("task") + ":" + HexFormat.of().formatHex(digest, 0, 8) + ":a1";
JsonNode job = api("POST", "/run", input, Map.of("Idempotency-Key", key)), j;
do { Thread.sleep(2000); j = api("GET", "/jobs/" + job.get("job_id").asText(), null, null); }
while (!Set.of("succeeded", "failed", "cancelled").contains(j.get("status").asText()));
JsonNode reply = M.readTree(j.get("output").get("output").asText());
System.out.println(j.get("charged_credits") + " " + reply.path("task").asText());
if (reply.path("task").asText().equals("rulebook")) {
reply.path("governance").fields().forEachRemaining(e -> System.out.println(e.getKey() + " " + e.getValue().path("owner").asText()));
} else {
System.out.println(reply.path("path").path("choice").asText() + " " + reply.path("summary").asText());
}
require "digest"
key = "market-seed:#{input["task"]}:#{Digest::SHA256.hexdigest(JSON.generate(input))[0, 16]}:a1"
job = api("POST", "/run", input, { "Idempotency-Key" => key })
j = nil
loop do
sleep 2
j = api("GET", "/jobs/#{job["job_id"]}")
break if %w[succeeded failed cancelled].include?(j["status"])
end
reply = JSON.parse(j["output"]["output"])
puts [j["charged_credits"], j["truncated"], reply["task"]].inspect
if reply["task"] == "rulebook"
reply["governance"].each { |q, x| puts "#{q} #{x["owner"]}" }
else
puts "#{reply["path"]["choice"]} #{reply["summary"]}"
end
$key = "market-seed:" . $input["task"] . ":" . substr(hash("sha256", json_encode($input)), 0, 16) . ":a1";
$job = api("POST", "/run", $input, ["Idempotency-Key" => $key]);
do { sleep(2); $j = api("GET", "/jobs/" . $job["job_id"]); }
while (!in_array($j["status"], ["succeeded", "failed", "cancelled"]));
$reply = json_decode($j["output"]["output"], true);
echo $j["charged_credits"], " ", $reply["task"], "\n";
if ($reply["task"] === "rulebook") { foreach ($reply["governance"] as $q => $x) echo $q, " ", $x["owner"], "\n"; }
else { echo $reply["path"]["choice"], " ", $reply["summary"], "\n"; }
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"market-seed:{input["task"]}:{Convert.ToHexString(digest, 0, 8).ToLowerInvariant()}:a1";
var job = await Api.Call("POST", "/run", input, new() { ["Idempotency-Key"] = key });
JsonNode j;
do { await Task.Delay(2000); j = (await Api.Call("GET", "/jobs/" + job!["job_id"]))!; }
while (!new[] { "succeeded", "failed", "cancelled" }.Contains(j["status"]!.GetValue<string>()));
var reply = JsonNode.Parse(j["output"]!["output"]!.GetValue<string>())!;
var task = (string?)reply["task"];
Console.WriteLine($"{j["charged_credits"]} {task}");
if (task == "rulebook") { foreach (var kv in reply["governance"]!.AsObject()) Console.WriteLine($"{kv.Key} {kv.Value!["owner"]}"); }
else { Console.WriteLine($"{reply["path"]!["choice"]} {reply["summary"]}"); }
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: market-seed:$(jq -r .task input.json):$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json
rev = json.load(open("input.json")) # the same run input as input.json: {"task": "decision", "platform": "Platform: Clausewren
The product and why a marketplace now: ...", "rules": "Governance rules (rule | answers | owner | removal path | market failure):
- Open listing behind automated checks ...", "carry": "", "facts": "The page's checks (...); verdict: a marketplace you can build and run:
- The gate and the model | triggers | na | ..."}
# - see the input table
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "market-seed:" + rev["task"] + ":" + hashlib.sha256(json.dumps(rev).encode()).hexdigest()[:16] + ":a1"})
with urllib.request.urlopen(req) as r:
for line in r:
line = line.decode().strip()
if line.startswith("data:"):
print(line[5:].strip()[:200])
const rev = JSON.parse(readFileSync("input.json", "utf8"));
const r = await fetch(BASE + "/run-stream", { method: "POST", body: JSON.stringify(rev), headers: {
"Content-Type": "application/json", Authorization: "Bearer " + TOKEN,
"Idempotency-Key": `market-seed:${rev.task}:${createHash("sha256").update(JSON.stringify(rev)).digest("hex").slice(0, 16)}:a1` } });
const dec = new TextDecoder();
for await (const chunk of r.body) for (const line of dec.decode(chunk).split("\n"))
if (line.startsWith("data:")) console.log(line.slice(5).trim().slice(0, 200));
rraw, _ := os.ReadFile("input.json") // add "bufio", "strings" to the imports
rs := sha256.Sum256(rraw)
req, _ := http.NewRequest("POST", base+"/run-stream", bytes.NewReader(rraw))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Idempotency-Key", "market-seed:"+input["task"].(string)+":"+hex.EncodeToString(rs[:8])+":a1")
res, _ := http.DefaultClient.Do(req)
sc := bufio.NewScanner(res.Body)
for sc.Scan() { if l := sc.Text(); strings.HasPrefix(l, "data:") { fmt.Println(l[5:]) } }
String rev = java.nio.file.Files.readString(java.nio.file.Path.of("input.json"));
var rd = java.security.MessageDigest.getInstance("SHA-256").digest(rev.getBytes());
var sreq = HttpRequest.newBuilder(URI.create(BASE + "/run-stream")).header("Content-Type", "application/json")
.header("Authorization", "Bearer " + token).header("Idempotency-Key", "market-seed:" + input.get("task") + ":" + HexFormat.of().formatHex(rd, 0, 8) + ":a1")
.POST(HttpRequest.BodyPublishers.ofString(rev)).build();
C.send(sreq, HttpResponse.BodyHandlers.ofLines()).body().filter(l -> l.startsWith("data:")).forEach(System.out::println);
rev = File.read("input.json")
uri = URI(BASE + "/run-stream")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer #{TOKEN}",
"Idempotency-Key" => "market-seed:#{JSON.parse(rev)["task"]}:#{Digest::SHA256.hexdigest(rev)[0, 16]}:a1")
req.body = rev
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
h.request(req) { |res| res.read_body { |chunk| chunk.each_line { |l| puts l if l.start_with?("data:") } } }
end
$rev = file_get_contents("input.json");
$ch = curl_init(BASE . "/run-stream");
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $rev, CURLOPT_HTTPHEADER => [
"Content-Type: application/json", "Authorization: Bearer $TOKEN",
"Idempotency-Key: market-seed:" . json_decode($rev, true)["task"] . ":" . substr(hash("sha256", $rev), 0, 16) . ":a1"],
CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { foreach (explode("\n", $chunk) as $l) if (str_starts_with($l, "data:")) echo $l, "\n"; return strlen($chunk); }]);
curl_exec($ch);
var rev = File.ReadAllText("input.json");
var rd = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(rev));
var sreq = new HttpRequestMessage(HttpMethod.Post, Api.Base + "/run-stream") { Content = new StringContent(rev, System.Text.Encoding.UTF8, "application/json") };
sreq.Headers.Add("Authorization", "Bearer " + Api.Token);
sreq.Headers.Add("Idempotency-Key", $"market-seed:{input["task"]}:{Convert.ToHexString(rd, 0, 8).ToLowerInvariant()}:a1");
using var http = new HttpClient();
using var res = await http.SendAsync(sreq, HttpCompletionOption.ResponseHeadersRead);
using var rdr = new StreamReader(await res.Content.ReadAsStreamAsync());
for (string? l; (l = await rdr.ReadLineAsync()) != null;) if (l.StartsWith("data:")) Console.WriteLine(l);