Run SDK Portfolio from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page reads every language against the add-threshold and the vertical's map, gives each one action, ranks the build model, checks the versioning split and holds every end-of-life to its evidence, then hands it all to the run as facts. The run writes the portfolio strategy or the end-of-life cases; the page then holds it to its own facts (spreply.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":"sdk-portfolio"}), 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, languages, facts) is missing. The page always sends all five of task, platform, languages, carry, facts; carry is an empty string for strategy. |
| 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 - which SDKs exist, then which end:
strategy- the portfolio strategy: every language with the page's action, the build model with its exit plan and guardrails, the tiers and the promotion gate, three versioning policies, the self-set threshold and the community policy.sunset- the end-of-life cases: each official SDK below the threshold, with the evidence it needs, the successor, final release to archive, the windows and the notice.
A missing or unknown task is answered as strategy. 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 strategy's decisions into carry for the sunset lane.
The input
| Field | Required | What |
|---|---|---|
task | yes | strategy or sunset. If it is missing or neither, do strategy and set "task": "strategy". |
platform | yes | the API, the vertical, the surface decision, telemetry, the threshold, the spec, procurement and self-hosting, the core-product answer, versioning, cadence, community policy, deadline, mandate and effort; "not given" means not given. |
languages | yes | every language with its share, deal requests, fluency, today's status, build, downloads, exclusive accounts, revenue at risk and the page's action, then the vertical's map. |
carry | send it (empty for strategy) | sunset task only: the strategy's decisions, when the strategy task ran first; may be empty. |
facts | yes | the page's checks, one line each as area | id | status | evidence, the verdict, the ranked build models and the shape. 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/sdk-portfolio-strategy - the only standard the run applies:
- A portfolio is one written, approved strategy - not a pile of per-SDK improvisations - and it starts only once SDKs were chosen as an integration surface.
- Language order follows the integrating vertical, not a popularity chart: fintech and enterprise platforms front-load Java, C#, Ruby and Go; AI and dev-tools platforms front-load Python and TypeScript. No vendor decides from a named ranking.
- Coverage rungs per language: official SDK, community tier with a promotion gate, spec-driven self-service. The default is self-service for every language and official SDKs only for the 2-5 languages the vertical map and telemetry confirm. The add-threshold - a sustained double-digit share of traffic, or repeated named deal requests - is self-set; label it so. A team already fluent in a language gets its official SDK nearly free.
- Tiers: two-tier (official with a support commitment, community with an explicit non-guarantee) is the default; numbered tiers when "official" stops discriminating. Encode the quality level in the version number and package metadata. Write the promotion gate before you need it; promotion turns on namespace transfer and maintainer cooperation.
- Build model, one decision portfolio-wide with at most one flagship exception: a commercial generator driven by the spec is the default; the free OpenAPI Generator when procurement rules out vendors or self-hosting is required (it is fully official-grade); a handwritten flagship only when the API is the core product and one language carries the bulk of revenue traffic - handwrite that one, generate the rest. Keep the spec, not a vendor DSL, as the source of truth; fix a drifting spec first; prefer self-hostable generators; write the exit plan before signing. Official is the support commitment, not the build method.
- Versioning: three policies, never merged - SDK package SemVer (a major only when the SDK's own surface breaks, never because the API released a version), the API's own scheme, and the language-runtime support window tied to upstream lifecycles. Publish a mapping table from SDK major to API versions. Cadence (a monthly train or continuous release-on-change) is a predictability promise, not a productivity metric.
- End-of-life of a whole language SDK: build the case before announcing anything - downloads, usage, and the decisive number, revenue at risk from accounts using that SDK exclusively. Then final release, announcement (a pinned deprecation issue naming the why, the successor and the timeline; registry markers; docs banners; support told), archive. Clean up 6-12 months after archive. The observed announcement-to-archive range is 6-24 months. Small is not safe to drop.
- Community policy: list community SDKs with an explicit non-guarantee, route their support to their repos, and invoke the promotion gate when one's language crosses the threshold rather than building a competitor.
House rules
- Use only the input. Never invent a language share, a deal, an account count, a revenue figure, a date or a vendor fact.
- The page's checks, actions and rankings are settled: never move an action, rank a build model differently or call a failed gate fine.
- No URL at all.
- Write in the language of the input; keep the JSON keys, action ids, build ids, shape ids and language names as given.
- Never use backticks, never write the input's field names as labels in your text, and never copy this prompt's wording into a reply.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
strategy
Write the portfolio strategy - which SDKs exist, built how, supported how, versioned how. A number the input does not give (a runtime grace window, a cadence) is a proposal: start it with "Proposed: ".
summary- one sentence, at most 40 words: the official set, the build model, and what must be fixed first.shape-lean,broadorflagship, exactly as the facts read it.languages- one object per language, in the input's order:language(as written),action(add,keep,promote,community,selfservice,evidenceorretire, exactly as the facts read it),why(one sentence).build-model(commercial,freeorflagship, exactly as the facts rank it),flagship(the handwritten language when the model is flagship, else empty),exit_plan(one sentence: who regenerates the SDKs if the vendor or pipeline goes away),guardrails(short strings: the spec as the source of truth, self-hosting, fixing a drifting spec first).tiers-shape(two-tierornumbered),official(one sentence: what official buys),community(one sentence: the explicit non-guarantee and where support routes),promotion_gate(short strings: checkable criteria).versioning-sdk(one sentence: SemVer per package, a major only when the SDK's surface breaks),api(one sentence: the API's scheme, owned by its own policy),runtime(one sentence: the language-runtime window),mapping(one sentence: the SDK-major to API-version table),cadence(one sentence).threshold- one sentence: the add-threshold, labelled self-set.community_policy- one sentence.caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "strategy",
"summary": "one sentence",
"shape": "lean",
"languages": [ { "language": "Java", "action": "keep", "why": "one sentence" } ],
"build": { "model": "commercial", "flagship": "", "exit_plan": "one sentence", "guardrails": [ "a short string" ] },
"tiers": { "shape": "two-tier", "official": "one sentence", "community": "one sentence", "promotion_gate": [ "a short string" ] },
"versioning": { "sdk": "one sentence", "api": "one sentence", "runtime": "one sentence", "mapping": "one sentence", "cadence": "one sentence" },
"threshold": "one sentence",
"community_policy": "one sentence",
"caveats": [ ]
}
The page holds it to its own facts (check() in spreply.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 page's shape; exactly the page's languages in order with their actions and a reason each; the ranked build model, the named flagship, an exit plan and spec-first guardrails; two-tier or numbered tiers with what official buys, the community non-guarantee and a promotion gate; SemVer SDKs never tied to API versions, the API scheme, a runtime window, a mapping table and a cadence; a self-set threshold; no popularity chart; a community policy.
sunset
Write the end-of-life case for every official SDK the page found below the threshold.
summary- one sentence, at most 40 words.cases- one object per case, in the input's order:language(as written),action(evidenceorretire, exactly as the facts read it),evidence(short strings: for an evidence case, the counts to gather - exclusive-usage accounts, revenue at risk, downloads; for a retire case, the counts that make it),successor(for a retire case, an official language from the strategy or spec-driven self-service; empty for an evidence case),steps(short strings in order: an evidence case gathers and decides, announcing nothing; a retire case runs final release, then announcement, then archive),announce_to_archive_months(a number from 6 to 24 for a retire case; 0 for an evidence case),cleanup_months(6 to 12 for a retire case; 0 for an evidence case),message(one or two sentences: for a retire case, the pinned deprecation notice naming the why, the successor and the timeline; for an evidence case, the internal note on what to gather - never a retirement announcement).caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "sunset",
"summary": "one sentence",
"cases": [ { "language": "Ruby", "action": "retire", "evidence": [ "a short string" ], "successor": "spec-driven self-service", "steps": [ "final release", "announcement", "archive" ], "announce_to_archive_months": 12, "cleanup_months": 6, "message": "one or two sentences" } ],
"caveats": [ ]
}
The page requires: the reply answers the task it was sent; the same text rules; exactly the page's cases in order; an evidence case naming the counts to gather, with no window and no announcement; a retire case running final release, announcement and archive in order, citing the exclusive-usage count, naming a successor from the official set or self-service, a 6-24 month window and 6-12 months of cleanup; a message each.
Worked examples
Three runs from the page's free samples (invented APIs): the strategy for a payments API with a drifting spec, the sunset for the same API carrying that strategy, and the strategy for an infrastructure API with a Go flagship. 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.
strategy - A payments API with a drifting spec
The page read it as: a portfolio with open policies; shape lean; build commercial; languages: Java (keep), Python (keep), TypeScript (promote), C# (add), Go (add), Ruby (retire), PHP (evidence), Elixir (community); 2 of 5 gates; failing checks: promote, spec, versioning, evidence, community.
Input (languages is 2078 characters; the first 1394 are shown; facts is 2412 characters; the first 1391 are shown):
{
"task": "strategy",
"platform": "Platform: Paxdrel API\nThe API and its integrators: A payments and ledger API; integrators are bank and fintech backend teams, plus a growing set of commerce platforms.\nVertical: Fintech or enterprise backend; SDKs chosen as a surface: yes; per-language telemetry: yes; add-threshold 10% of traffic or 3 named deal requests (self-set)\nSpec: A spec that drifts from the API; a hosted generator vendor acceptable: yes; self-hosted generation required: no; the API is the core product: no\nVersioning: SDK majors track API versions: yes; API scheme: dated API versions; mapping table: no; runtime support window: no; cadence: No stated cadence\nCommunity: No written policy; promotion gate written: [object Object]; deadline: no; a compounding policy; effort ceiling: medium; as-of: 2026-11-01",
"languages": "Languages in the order given (language | share | deals | fluent | today | downloads | exclusive accounts | revenue at risk | action):\n- Java | 34% | 5 deal requests | fluent yes | today official, generated | downloads up | exclusive accounts 120 | revenue at risk $2100000 | KEEP: 34% of API traffic, at or over the 10% add-threshold; 5 named deal requests, at or over 3\n- Python | 22% | 1 deal request | fluent yes | today official, handwritten | downloads up | exclusive accounts 40 | revenue at risk $600000 | KEEP: 22% of API traffic, at or over the 10% add-threshold\n- TypeScript | 18% | 0 deal requests | fluent no | today community | downloads up | exclusive accounts unknown | revenue at risk unknown | PROMOTE: 18% of API traffic, at or over the 10% add-threshold - a community SDK crossing the threshold goes through the promotion gate, not a competing build\n- C# | 6% | 4 deal requests | fluent no | today none | downloads - | exclusive accounts unknown | revenue at risk unknown | ADD: 4 named deal requests, at or over 3\n- Go | 8% | 0 deal requests | fluent yes | today none | downloads - | exclusive accounts unknown | revenue at risk unknown | ADD: the team already maintains it fluently and the vertical map ranks it top four\n- Ruby | 3% | 0 deal requests | fluent no | today official, handwritten | downloads down | exclusive accounts 0 | revenue at risk $0 | RETIRE: below the\n\n[... 684 more characters cut from this display ...]",
"carry": "",
"facts": "The page's checks (5 fail, 3 warn, 3 pass, 1 n/a); verdict: a portfolio with open policies:\n- The language order | surface | pass | the surface decision is made\n- The language order | coverage | pass | every language over 10% of traffic or 3 deal requests has an official decision\n- The language order | order | pass | Java, Python, TypeScript, C#, Go (5 official SDKs)\n- The language order | promote | fail | TypeScript - no promotion gate written; write it before building a competitor\n- The build model | build | na | A commercial generator driven by the spec first, the free OpenAPI Generator as the alternative - the spec drifts from the API - fix it before any generated rung\n- The build model | spec | fail | A spec that drifts from the API - fix the spec first\n- The build model | exit | warn | who regenerates the SDKs if the vendor disappears? Keep the spec, not a vendor DSL, as the source of truth, and prefer a generator that can self-host\n- The build model | handwritten | warn | Python, Ruby are handwritten - one decision portfolio-wide, at most one flagship exception\n- Versioning, tiers and EOL | versioning | fail | SDK majors track API versions - a breaking release for integrators whose code did not break; no SDK-major to API-version mapping table; no language-runtime support window\n- Versioning, tiers and EOL | cadence | warn | cadence is a predictability promise to\n\n[... 1021 more characters cut from this display ...]"
}
Reply:
{
"task": "strategy",
"summary": "Paxdrel ships five official SDKs - Java, Python, TypeScript, C# and Go - from a commercial spec-driven generator, but the drifting spec must be fixed before any generated release.",
"shape": "lean",
"languages": [
{
"language": "Java",
"action": "keep",
"why": "Java carries 34% of traffic with 5 named deal requests, 120 exclusive accounts and $2100000 of revenue at risk, well over the 10% add-threshold."
},
{
"language": "Python",
"action": "keep",
"why": "Python carries 22% of traffic with 40 exclusive accounts and $600000 of revenue at risk, over the add-threshold."
},
{
"language": "TypeScript",
"action": "promote",
"why": "The community TypeScript SDK carries 18% of traffic, so it goes through a written promotion gate rather than a competing in-house build."
},
{
"language": "C#",
"action": "add",
"why": "C# has only 6% of traffic but 4 named deal requests, over the 3-request threshold, and the fintech map ranks it early."
},
{
"language": "Go",
"action": "add",
"why": "Go holds 8% of traffic, the team already maintains it fluently and the fintech map ranks it in the top four, so its official SDK comes nearly free."
},
{
"language": "Ruby",
"action": "retire",
"why": "Ruby sits at 3% of traffic with falling downloads, 0 exclusive accounts and $0 of revenue at risk, so the retirement case holds."
},
{
"language": "PHP",
"action": "evidence",
"why": "PHP is an official SDK at 4% of traffic with no exclusive-usage count or revenue figure, so the case must be built before any end-of-life announcement."
},
{
"language": "Elixir",
"action": "community",
"why": "Elixir carries 1% of traffic with flat downloads, so it stays a listed community SDK with an explicit non-guarantee."
}
],
"build": {
"model": "commercial",
"flagship": "",
"exit_plan": "Before signing, the contract and runbook name the free OpenAPI Generator, run in-house CI from the same spec, as the pipeline that regenerates every SDK if the vendor goes away.",
"guardrails": [
"Fix the drifting spec first and gate generation on spec-versus-API contract tests",
"Keep the OpenAPI spec, not a vendor DSL, as the single source of truth",
"Prefer a generator that can self-host so builds survive the vendor",
"Fold the handwritten Python SDK into the generated pipeline - one portfolio-wide build decision"
]
},
"tiers": {
"shape": "two-tier",
"official": "Official buys a support commitment: maintained releases, security fixes, the documented runtime window and tickets answered by the Paxdrel team, marked in the version number and package metadata.",
"community": "Community SDKs carry an explicit non-guarantee - no support, security or compatibility promise from Paxdrel - and their support routes to each project's own repository.",
"promotion_gate": [
"The language sustains 10% of traffic or 3 named deal requests",
"The package namespace transfers to the Paxdrel organisation",
"The maintainers agree to cooperate through handover",
"Feature-complete against the current spec and passing the contract test suite",
"Releases can move onto the official generated pipeline"
]
},
"versioning": {
"sdk": "Each SDK package follows its own SemVer and bumps a major only when that SDK's own surface breaks, never because the API released a new dated version.",
"api": "The API keeps its dated versions under its own policy, separate from SDK package numbers.",
"runtime": "Proposed: each SDK supports every language runtime still under upstream maintenance and drops one only in a minor release announced 6 months after its upstream end of life.",
"mapping": "Proposed: publish a table mapping each SDK major to the dated API versions it supports.",
"cadence": "Proposed: a monthly release train for all official SDKs, with security fixes shipped out of band."
},
"threshold": "An official SDK is added at a sustained 10% of traffic or 3 named deal requests, a self-set threshold the team owns and may revise.",
"community_policy": "Write and publish a policy that lists community SDKs with an explicit non-guarantee, routes their support to their own repositories and invokes the promotion gate when a language crosses the threshold instead of building a competitor.",
"caveats": [
"No generated release ships until the spec stops drifting from the API",
"Decoupling SDK majors from API versions changes current practice and needs an announcement",
"PHP needs exclusive-usage and revenue counts before any decision"
]
}
What step 5 prints for it: Java keep | Python keep | TypeScript promote | C# add | Go add | Ruby retire | PHP evidence | Elixir community
The page's check: kept - nothing to flag
sunset - A payments API with a drifting spec
The input carries the saved strategy's decisions (R.laneForm fills carry from the strategy reply), as the page's handoff does. The end-of-life cases: Ruby (retire), PHP (evidence).
Input (languages is 2078 characters; the first 1394 are shown; facts is 2301 characters; the first 1391 are shown):
{
"task": "sunset",
"platform": "Platform: Paxdrel API\nThe API and its integrators: A payments and ledger API; integrators are bank and fintech backend teams, plus a growing set of commerce platforms.\nVertical: Fintech or enterprise backend; SDKs chosen as a surface: yes; per-language telemetry: yes; add-threshold 10% of traffic or 3 named deal requests (self-set)\nSpec: A spec that drifts from the API; a hosted generator vendor acceptable: yes; self-hosted generation required: no; the API is the core product: no\nVersioning: SDK majors track API versions: yes; API scheme: dated API versions; mapping table: no; runtime support window: no; cadence: No stated cadence\nCommunity: No written policy; promotion gate written: [object Object]; deadline: no; a compounding policy; effort ceiling: medium; as-of: 2026-11-01",
"languages": "Languages in the order given (language | share | deals | fluent | today | downloads | exclusive accounts | revenue at risk | action):\n- Java | 34% | 5 deal requests | fluent yes | today official, generated | downloads up | exclusive accounts 120 | revenue at risk $2100000 | KEEP: 34% of API traffic, at or over the 10% add-threshold; 5 named deal requests, at or over 3\n- Python | 22% | 1 deal request | fluent yes | today official, handwritten | downloads up | exclusive accounts 40 | revenue at risk $600000 | KEEP: 22% of API traffic, at or over the 10% add-threshold\n- TypeScript | 18% | 0 deal requests | fluent no | today community | downloads up | exclusive accounts unknown | revenue at risk unknown | PROMOTE: 18% of API traffic, at or over the 10% add-threshold - a community SDK crossing the threshold goes through the promotion gate, not a competing build\n- C# | 6% | 4 deal requests | fluent no | today none | downloads - | exclusive accounts unknown | revenue at risk unknown | ADD: 4 named deal requests, at or over 3\n- Go | 8% | 0 deal requests | fluent yes | today none | downloads - | exclusive accounts unknown | revenue at risk unknown | ADD: the team already maintains it fluently and the vertical map ranks it top four\n- Ruby | 3% | 0 deal requests | fluent no | today official, handwritten | downloads down | exclusive accounts 0 | revenue at risk $0 | RETIRE: below the\n\n[... 684 more characters cut from this display ...]",
"carry": "Official: Java, Python, TypeScript, C#, Go\nBuild: commercial\nTiers: two-tier - official: Official buys a support commitment: maintained releases, security fixes, the documented runtime window and tickets answered by the Paxdrel team, marked in the version number and package metadata.; community: Community SDKs carry an explicit non-guarantee - no support, security or compatibility promise from Paxdrel - and their support routes to each project's own repository.\nVersioning: SDK Each SDK package follows its own SemVer and bumps a major only when that SDK's own surface breaks, never because the API released a new dated version.; runtime Proposed: each SDK supports every language runtime still under upstream maintenance and drops one only in a minor release announced 6 months after its upstream end of life.; cadence Proposed: a monthly release train for all official SDKs, with security fixes shipped out of band.",
"facts": "The page's checks (5 fail, 3 warn, 3 pass, 1 n/a); verdict: a portfolio with open policies:\n- The language order | surface | pass | the surface decision is made\n- The language order | coverage | pass | every language over 10% of traffic or 3 deal requests has an official decision\n- The language order | order | pass | Java, Python, TypeScript, C#, Go (5 official SDKs)\n- The language order | promote | fail | TypeScript - no promotion gate written; write it before building a competitor\n- The build model | build | na | A commercial generator driven by the spec first, the free OpenAPI Generator as the alternative - the spec drifts from the API - fix it before any generated rung\n- The build model | spec | fail | A spec that drifts from the API - fix the spec first\n- The build model | exit | warn | who regenerates the SDKs if the vendor disappears? Keep the spec, not a vendor DSL, as the source of truth, and prefer a generator that can self-host\n- The build model | handwritten | warn | Python, Ruby are handwritten - one decision portfolio-wide, at most one flagship exception\n- Versioning, tiers and EOL | versioning | fail | SDK majors track API versions - a breaking release for integrators whose code did not break; no SDK-major to API-version mapping table; no language-runtime support window\n- Versioning, tiers and EOL | cadence | warn | cadence is a predictability promise to\n\n[... 910 more characters cut from this display ...]"
}
Reply (long strings shortened for display - see the cuts below):
{
"task": "sunset",
"summary": "Retire the handwritten Ruby SDK on its evidence, with spec-driven self-service as the successor once the drifting spec is fixed; hold PHP until its exclusive-usage case is built, announcing nothing.",
"cases": [
{
"language": "Ruby",
"action": "retire",
"evidence": [
"0 accounts use the Ruby SDK exclusively",
"$0 revenue at risk",
"downloads trending down",
"3% of API traffic, below the self-set 10% add-threshold, with 0 deal requests"
],
"successor": "spec-driven self-service",
"steps": [
"Ship the final release of the Ruby SDK with current fixes",
"Announce: pinned deprecation issue naming the why, the successor and the timeline; deprecation markers on the registry; banners on the Ruby docs; support told",
"Archive the Ruby repository at the end of the window",
"Clean up packages, docs and samples after archive"
],
"announce_to_archive_months": 12,
"cleanup_months": 6,
"message": "The official Ruby SDK is deprecated: it carries 3% of traffic, downloads are falling and no account depends on it alone, so effort moves to the official set. Ruby integrators should generate a client from the published spec; the repository [... 46 more characters cut from this display]"
},
{
"language": "PHP",
"action": "evidence",
"evidence": [
"accounts that use the PHP SDK exclusively",
"revenue at risk from those exclusive accounts",
"downloads trend over time (flat today)",
"PHP traffic share against the 10% threshold (4% today)"
],
"successor": "",
"steps": [
"Count accounts that call the API only through the PHP SDK",
"Sum the revenue at risk from those accounts",
"Pull the downloads trend",
"Decide retire or keep from those counts; announce nothing until then"
],
"announce_to_archive_months": 0,
"cleanup_months": 0,
"message": "Internal: before any decision on the PHP SDK, gather its exclusive-usage account count, the revenue at risk from those accounts and its downloads trend. Nothing is announced until the case is built."
}
],
"caveats": [
"Proposed: the 12-month announcement-to-archive window and 6-month cleanup; no timeline was given.",
"The spec drifts from the API; fix it before pointing Ruby integrators at self-service.",
"PHP is on the vertical map, so a small share alone is not a reason to drop it."
]
}
Cut from this display only (the saved reply and the check use the full text): cases[0].message (285 characters, 239 shown).
What step 5 prints for it: Ruby retire | PHP evidence
The page's check: kept - nothing to flag
strategy - An infrastructure API with a Go flagship
The page read it as: a portfolio ready to approve; shape flagship; build flagship; languages: Go (keep), Python (keep), TypeScript (keep), Java (community), Rust (selfservice), Perl (retire); 5 of 5 gates; failing checks: none.
Input (languages is 1510 characters; the first 1399 are shown; facts is 1763 characters; the first 1398 are shown):
{
"task": "strategy",
"platform": "Platform: Routhane\nThe API and its integrators: A routing and fleet API sold to infrastructure teams; the API is the product, and Go services carry most of the traffic.\nVertical: Infrastructure or cloud; SDKs chosen as a surface: yes; per-language telemetry: yes; add-threshold 10% of traffic or 3 named deal requests (self-set)\nSpec: A linted spec, the source of truth in CI; a hosted generator vendor acceptable: yes; self-hosted generation required: no; the API is the core product: yes\nVersioning: SDK majors track API versions: no; API scheme: dated API versions; mapping table: yes; runtime support window: yes; cadence: Continuous release-on-change\nCommunity: Listed with an explicit non-guarantee; promotion gate written: [object Object]; deadline: no; a compounding policy; effort ceiling: high; as-of: 2026-11-01",
"languages": "Languages in the order given (language | share | deals | fluent | today | downloads | exclusive accounts | revenue at risk | action):\n- Go | 62% | 2 deal requests | fluent yes | today official, handwritten | downloads up | exclusive accounts 310 | revenue at risk $4800000 | KEEP: 62% of API traffic, at or over the 10% add-threshold\n- Python | 18% | 1 deal request | fluent yes | today official, generated | downloads up | exclusive accounts 25 | revenue at risk $300000 | KEEP: 18% of API traffic, at or over the 10% add-threshold\n- TypeScript | 11% | 0 deal requests | fluent no | today official, generated | downloads up | exclusive accounts 8 | revenue at risk $90000 | KEEP: 11% of API traffic, at or over the 10% add-threshold\n- Java | 4% | 0 deal requests | fluent no | today community | downloads flat | exclusive accounts unknown | revenue at risk unknown | COMMUNITY: below the threshold - list it with an explicit non-guarantee; it staffs the long tail\n- Rust | 2% | 0 deal requests | fluent no | today none | downloads - | exclusive accounts unknown | revenue at risk unknown | SELFSERVICE: below the threshold - publish the spec and point it at open-source generators\n- Perl | 0.3% | 0 deal requests | fluent no | today official, generated | downloads down | exclusive accounts 0 | revenue at risk $0 | RETIRE: below the threshold, downloads down, no account uses it exclusively and no\n\n[... 111 more characters cut from this display ...]",
"carry": "",
"facts": "The page's checks (0 fail, 0 warn, 7 pass, 2 n/a); verdict: a portfolio ready to approve:\n- The language order | surface | pass | the surface decision is made\n- The language order | coverage | pass | every language over 10% of traffic or 3 deal requests has an official decision\n- The language order | order | pass | Go, Python, TypeScript (3 official SDKs)\n- The build model | build | na | A handwritten flagship, generated rest first, a commercial generator driven by the spec as the alternative - the API is the core product and Go carries 62% of traffic - handwrite that one, generate the rest\n- The build model | spec | pass | A linted spec, the source of truth in CI\n- The build model | exit | na | no hosted vendor in the default rung\n- Versioning, tiers and EOL | versioning | pass | SDK SemVer, API dated API versions and runtime windows written separately, with a mapping table\n- Versioning, tiers and EOL | evidence | pass | Perl (0 exclusive accounts) - the case holds\n- Versioning, tiers and EOL | community | pass | Listed with an explicit non-guarantee\nBuild models by the skill's ranking, in order: flagship, commercial, free (moved by: the API is the core product and Go carries 62% of traffic - handwrite that one, generate the rest); portfolio shape: flagship.\nmust: shape flagship; languages lists exactly Go keep, Python keep, TypeScript keep, Java community, Rust selfservice,\n\n[... 365 more characters cut from this display ...]"
}
Reply:
{
"task": "strategy",
"summary": "Routhane ships three official SDKs - a handwritten Go flagship with generated Python and TypeScript - and only needs to run the evidenced Perl retirement; nothing blocks approval.",
"shape": "flagship",
"languages": [
{
"language": "Go",
"action": "keep",
"why": "Go carries 62% of traffic with 310 exclusive accounts and $4800000 of revenue at risk, making it the handwritten flagship."
},
{
"language": "Python",
"action": "keep",
"why": "Python carries 18% of traffic with 25 exclusive accounts and $300000 of revenue at risk, over the 10% add-threshold."
},
{
"language": "TypeScript",
"action": "keep",
"why": "TypeScript carries 11% of traffic with 8 exclusive accounts and $90000 of revenue at risk, just over the add-threshold."
},
{
"language": "Java",
"action": "community",
"why": "Java holds 4% of traffic with flat downloads, so it stays a listed community SDK with an explicit non-guarantee."
},
{
"language": "Rust",
"action": "selfservice",
"why": "Rust holds 2% of traffic with no deal requests, so the published spec and open-source generators cover it."
},
{
"language": "Perl",
"action": "retire",
"why": "Perl holds 0.3% of traffic with falling downloads, 0 exclusive accounts and $0 of revenue at risk, so the retirement case holds."
}
],
"build": {
"model": "flagship",
"flagship": "Go",
"exit_plan": "The Go team owns the handwritten flagship, and the generated SDKs are rebuilt from the spec in CI with a self-hostable generator so the platform team can regenerate them if any vendor or pipeline goes away.",
"guardrails": [
"Keep the linted spec, not a vendor DSL, as the source of truth, including for the handwritten Go SDK",
"Prefer a self-hostable generator for Python and TypeScript",
"Contract-test the Go SDK against the spec so a drift is caught and the spec fixed first",
"No second handwritten exception"
]
},
"tiers": {
"shape": "two-tier",
"official": "Official buys an Routhane support commitment: maintained releases, security fixes, the published runtime window and team support, marked in version and package metadata.",
"community": "Community SDKs such as Java carry an explicit non-guarantee of support or compatibility, and their support routes to their own repositories.",
"promotion_gate": [
"A sustained 10% of traffic or 3 named deal requests",
"The package namespace transfers to Routhane",
"The maintainers agree to cooperate through handover",
"Feature-complete against the current spec and passing the contract tests"
]
},
"versioning": {
"sdk": "Each SDK package keeps its own SemVer and bumps a major only when its own surface breaks, never because the API released a version.",
"api": "The API keeps its dated versions under its own policy.",
"runtime": "Keep the written language-runtime support window, tied to each runtime's upstream lifecycle.",
"mapping": "Keep publishing the table mapping each SDK major to the dated API versions it supports.",
"cadence": "Continuous release-on-change: each spec or SDK change ships a release."
},
"threshold": "An official SDK is added at a sustained 10% of traffic or 3 named deal requests, a self-set threshold the team owns.",
"community_policy": "Keep listing community SDKs with an explicit non-guarantee, route their support to their repositories and invoke the promotion gate when a language crosses the threshold rather than building a competitor.",
"caveats": [
"TypeScript at 11% sits close to the threshold; watch it before any change",
"Perl retirement still runs final release, announcement and archive in order"
]
}
What step 5 prints for it: Go keep | Python keep | TypeScript keep | Java community | Rust selfservice | Perl retire
The page's check: kept - nothing to flag
Build the input yourself
Load the engine in node (global.window = global; require("./spkit.js"); require("./spreply.js")), then:
const K = PortfolioKit, R = PortfolioReply;
const f = K.form(Object.assign(K.blank(), { name: "Acme", vertical: "fintech", telemetry: "yes", threshold: "10", deals: "3",
languages: "Java | 40 | 2 | yes | official\nRuby | 2 | 0 | no | official | handwritten | down | 0 | $0" }));
// f.langRows (each language with its action and why), f.build (the ranked build models), f.shape, f.cases (the end-of-life cases), f.gate
// K.portfolioMd(f), K.languagesCsv(f) - the portfolio table and the languages view
const b = K.buildInput(f, "strategy"); // or "sunset" (R.laneForm(form, "sunset", strategy) fills carry)
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "strategy");
// 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://sdk-portfolio.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://sdk-portfolio.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"]
// sdk-portfolio.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://sdk-portfolio.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://sdk-portfolio.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
}
// SDK Portfolio.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 SDK Portfolio {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://sdk-portfolio.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://sdk-portfolio.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://sdk-portfolio.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://sdk-portfolio.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":"sdk-portfolio"}' | 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": "sdk-portfolio"})["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: "sdk-portfolio" })).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": "sdk-portfolio"}, 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", "sdk-portfolio"), 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: "sdk-portfolio" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "sdk-portfolio"])["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 = "sdk-portfolio" }))!["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": "strategy", "platform": "Platform: Paxdrel API
The API and its integrators: ...", "languages": "Languages in the order given (language | share | deals | ...):
- Java | 34% | 5 deal requests | ... | KEEP: ...", "carry": "", "facts": "The page's checks (...); verdict: a portfolio with open policies:
- The language order | surface | pass | ..."}
# - 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": "strategy", "platform": "Platform: Paxdrel API
The API and its integrators: ...", "languages": "Languages in the order given (language | share | deals | ...):
- Java | 34% | 5 deal requests | ... | KEEP: ...", "carry": "", "facts": "The page's checks (...); verdict: a portfolio with open policies:
- The language order | surface | pass | ..."}
# - 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: "strategy" or "sunset", platform, languages, 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 sdk-portfolio:<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 strategy reply carries summary, shape, languages, build, tiers, versioning, threshold, community_policy and caveats; a sunset reply carries summary, cases and caveats. The code below prints the gist of either: each language and its action, or each case and its action.
KEY="sdk-portfolio:$(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 == "sunset" then (.cases[] | "\(.language): \(.action)")
else (.languages[] | "\(.language): \(.action)") end' reply.json
import hashlib, time
key = "sdk-portfolio:" + 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"] == "sunset":
for x in reply["cases"]:
print(x["language"], x["action"])
else:
for x in reply["languages"]:
print(x["language"], x["action"])
import { createHash } from "node:crypto";
const key = `sdk-portfolio:${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 === "sunset") { for (const x of reply.cases) console.log(x.language, x.action); }
else { for (const x of reply.languages) console.log(x.language, x.action); }
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "sdk-portfolio:" + 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"])
key, name, field := "languages", "language", "action"
if reply["task"] == "sunset" {
key, name, field = "cases", "language", "action"
}
ls, _ := reply[key].([]any)
for _, x := range ls {
xx := x.(map[string]any)
fmt.Println(xx[name], xx[field])
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "sdk-portfolio:" + 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("sunset")) {
for (JsonNode x : reply.path("cases")) System.out.println(x.path("language").asText() + " " + x.path("action").asText());
} else {
for (JsonNode x : reply.path("languages")) System.out.println(x.path("language").asText() + " " + x.path("action").asText());
}
require "digest"
key = "sdk-portfolio:#{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"] == "sunset"
reply["cases"].each { |x| puts "#{x["language"]} #{x["action"]}" }
else
reply["languages"].each { |x| puts "#{x["language"]} #{x["action"]}" }
end
$key = "sdk-portfolio:" . $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"] === "sunset") { foreach ($reply["cases"] as $x) echo $x["language"], " ", $x["action"], "\n"; }
else { foreach ($reply["languages"] as $x) echo $x["language"], " ", $x["action"], "\n"; }
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"sdk-portfolio:{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 == "sunset") { foreach (var x in reply["cases"]!.AsArray()) Console.WriteLine($"{x!["language"]} {x["action"]}"); }
else { foreach (var x in reply["languages"]!.AsArray()) Console.WriteLine($"{x!["language"]} {x["action"]}"); }
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: sdk-portfolio:$(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": "strategy", "platform": "Platform: Paxdrel API
The API and its integrators: ...", "languages": "Languages in the order given (language | share | deals | ...):
- Java | 34% | 5 deal requests | ... | KEEP: ...", "carry": "", "facts": "The page's checks (...); verdict: a portfolio with open policies:
- The language order | surface | pass | ..."}
# - 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": "sdk-portfolio:" + 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": `sdk-portfolio:${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", "sdk-portfolio:"+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", "sdk-portfolio:" + 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" => "sdk-portfolio:#{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: sdk-portfolio:" . 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", $"sdk-portfolio:{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);