Run Platform Path from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page reads who the target role serves, resolves the track and archetype, audits the record against what the track is screened on, ranks the steps, plans the loop and checks the offer, then hands it all to the run as facts. The run writes the plan or the prep; the page then holds it to its own facts (ppreply.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":"platform-path"}), 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, candidate, evidence, facts) is missing. The page always sends all five of task, candidate, evidence, carry, facts; carry is an empty string for plan. |
| 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 - choose and break in, then pass the loop and weigh the offer:
plan- who the role serves, the target track and archetype, the steps in the page's order (the internal-API route labelled a hypothesis) and one portfolio artefact for each gap.prep- each loop round with what it judges and a drill, practice prompts from the posting, and the offer stance on the general ladder.
A missing or unknown task is answered as plan; an internal-developer-platform target is refused before it runs. Both lanes send the same five fields. The facts are the page's checks, steps and loop, one row per line, and a line starting "must:" is a rule the reply has to hold. The page's handoff carries the plan into carry for the prep lane.
The input
| Field | Required | What |
|---|---|---|
task | yes | plan or prep. If it is missing or neither, do plan and set "task": "plan". |
candidate | yes | where the person stands, the target track and archetype, the ecosystem, the deadline, one-off or compounding, portfolio hours, any offer and how it was benchmarked, and the plan date; "not given" means not given. |
evidence | yes | the target posting and the track record, one item per line as kind | item | describable without confidential detail. |
carry | send it (empty for plan) | prep task only: the plan decisions when the plan task ran first, or a sentence saying no plan was carried over. Either way, never mention this input or any input by its name in your reply. |
facts | yes | the page's checks, one line each as area | id | status | evidence, the track, the archetype and what is missing, the steps as step | what | note, and the loop as round | name | judges | drill. 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/developer-platform-career - the only standard the run applies:
- "Platform engineer" overwhelmingly means internal developer platforms - self-service infrastructure, CI/CD, Kubernetes abstractions, golden paths for a company's own engineers. This track is the other population: engineers and PMs who design and operate a company's public or external API and integration surface. Getting this wrong sources the wrong community, benchmarks the wrong band and reads the wrong postings.
- Archetypes do not predict each other. At an infrastructure or API-first company the API is the product and the bar is API design quality, reliability and contract stability; a company bolting a public API onto its product rarely has a dedicated team and folds API design into general backend work; an enterprise partner ecosystem has a named partner-engineering function measured on partners supported and integrations shipped. Most API teams are one to nine people: expect breadth.
- No standardized cross-industry ladder exists for these roles. GitLab's Partner Integration Engineer ladder (associate, full, manager - with open-source history required even at entry) and Stripe's Staff Software Engineer, API Platform posting (leading teams on API design, abstractions, frameworks or client libraries) are the sourced examples - never universal standards.
- No degree or bootcamp path exists; the track is layered on a general engineering or PM base. RFCs and design docs describable without confidential detail are a real, checkable artefact. Certifications such as apiMasters are informational at best, never a hiring gate. Building internal APIs first is a plausible lower-stakes practice ground - a hypothesis, not an established route.
- API design is its own interview, apart from system design: judged on clarity, predictability and misuse-resistance of an interface developers consume, not raw scale. PM candidates are judged on scoping, structuring and communicating design thinking, sometimes with a technical conversation but no code. A take-home designing an SDK or API surface is plausible, not confirmed practice.
- Compensation rides the general engineering or PM ladder at the equivalent level, not a bespoke platform-title search, which returns sparse crowdsourced data. Never present an undated figure as verified.
House rules
- Use only the input. Never invent an employer, a person, a figure, a salary, a date or a source - except a value a field below asks you to propose (a deadline for a step, a drill), labelled proposed when the input does not give it.
- The page's population reading, track, archetype, steps, loop and checks are settled: never reorder a step or a round, change the track, or call a failed gate fine. Where the record shows a gap, write what replaces it; the gap itself belongs in a caveat.
- Ids are exactly as the facts give them. Track ids: apipm, platform, partner, integration. Archetype ids: apifirst, bolton, ecosystem. Step ids: population, leadership, internal, portfolio, oss, loop, offer. Round ids: recruiter, manager, apidesign, system, scoping, techconv, written, team, exec.
- Speak to the person as you; never write a gendered pronoun for anyone.
- No web links (http or https).
- Write in the language of the input; keep the JSON keys and the ids as given.
- Never use backticks, and never refer to the input itself - no "the posting field", "the carry", "the facts say". Ordinary words are free: evidence, record or posting used in their everyday sense are fine; what is banned is talking about the input as data. Terms of art from the rules above are free to use; what you never do is paste a whole sentence from this prompt, or state a rule with no fact of this person in it.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
plan
Write the career plan.
summary- one sentence, at most 40 words: the target, and the first thing to do.population- one sentence: whether the target role is the internal-platform or the external-API population, and what in the posting or the answers shows it.target-track(the page's id),archetype(the page's id),why(one sentence: what a hire there is measured on, for this person).steps- one object per step the facts list, in that order, no others:step(its id),action(one sentence: what to do, in this person's terms),done_when(one sentence: the observable result),hypothesis(true for the internal-API step, false otherwise).portfolio- one object per missing artefact kind the facts name (one to keep current when nothing is missing):kind(rfc, api, oss, partner or pm),artefact(one sentence: the specific thing to make, from this person's own work),proves(one sentence: what a screener reads in it).caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "plan",
"summary": "one sentence",
"population": "one sentence",
"target": { "track": "platform", "archetype": "apifirst", "why": "one sentence" },
"steps": [ { "step": "portfolio", "action": "one sentence", "done_when": "one sentence", "hypothesis": false } ],
"portfolio": [ { "kind": "api", "artefact": "one sentence", "proves": "one sentence" } ],
"caveats": [ ]
}
The page holds it to its own facts (check() in ppreply.js; any warning makes the reply flagged): the reply answers the task it was sent; no web link; no backticks; no gendered pronoun; no input field named; no figure the input does not give; a summary of at most 40 words; a population sentence naming both the internal-platform and external-API populations; the page's track and archetype with what a hire is measured on; the page's steps in order, each with an action and a done-when, the internal-API step - and only it - labelled a hypothesis; one artefact per missing kind; no certification treated as a gate; no ladder presented as universal; PlatformCon never offered for this track.
prep
Write the interview preparation and the offer stance.
summary- one sentence, at most 40 words.rounds- one object per round the facts list, in that order, no others:round(its id),judges(one sentence: what it decides, for this posting),drill(one sentence: the practice to do before it, from this person's own work).practice- at least three short strings: practice prompts drawn from the target posting.offer-anchor(one sentence: the general engineering or product-management ladder and the equivalent level),source(the named, dated source as the input gives it, or "needs live verification" and say so),verdict(one sentence: hold, negotiate or re-benchmark, and why; when there is no offer, how to benchmark one when it comes).caveats- at most 4 short strings;[]when there is nothing to say.
{
"task": "prep",
"summary": "one sentence",
"rounds": [ { "round": "apidesign", "judges": "one sentence", "drill": "one sentence" } ],
"practice": [ "a prompt" ],
"offer": { "anchor": "one sentence", "source": "a named, dated source", "verdict": "one sentence" },
"caveats": [ ]
}
The page requires: the reply answers the task it was sent; the same text rules; the page's rounds in order, each with what it judges and a drill, no coding drill for the PM track; at least three practice prompts; a take-home SDK or API design called plausible, not confirmed; the offer anchored to the general engineering or PM ladder at the equivalent level, never a platform-title salary search, with a dated source or flagged for live verification; when the offer was not benchmarked on the ladder, a verdict to re-benchmark it.
Worked examples
Three runs from the page's free samples (invented companies): the plan for a backend engineer who builds internal APIs today, the prep for the same person carrying that plan, and the plan for a PM interviewing now with an offer benchmarked the wrong way. 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.
plan - Internal APIs to a public API platform
The page read it as: a platform move with gaps to close; track platform at apifirst; steps internal, portfolio, loop; 3 of 4 gates; failing checks: none.
Input (facts is 3024 characters; the first 1392 are shown):
{
"task": "plan",
"candidate": "Label: Backend engineer, internal APIs\nWhere they stand: Building internal APIs today; today: Backend engineer on an order-management team; I design the internal REST APIs three other teams call.; building internal APIs: yes\nTarget: Platform Engineer / Architect (read from the posting) at an infrastructure or API-first company (read from the posting); ecosystem to be credible in: rest\nDeadline: one to three months; a compounding move across employers; portfolio hours per week: 4\nOffer: none yet; plan date: 2026-11-02",
"evidence": "Target posting:\nPlatform Engineer, API Platform - Zindrel Pay\nThe API is the product at Zindrel Pay. You will design and run the public API and SDKs that external developers use to take card payments.\nYou will own versioning, idempotency and error design across the API surface, and review design documents with the team.\n\nTrack record (kind | item | describable without confidential detail):\n- an RFC or design doc | Design doc - internal order-events API, describable without confidential detail | yes\n- an RFC or design doc | Design doc - retry policy for the fulfilment service, can describe it | yes",
"carry": "",
"facts": "The page's checks (0 fail, 1 warn, 3 pass, 5 n/a); verdict: a platform move with gaps to close:\n- The target | stage | na | Building internal APIs today; building internal APIs today\n- The target | population | pass | external consumers - the posting names public api, sdks, external developers\n- The target | target | pass | Platform Engineer / Architect (read from the posting) at an infrastructure or API-first company (read from the posting) - measured on API design quality, infra reliability, contract stability\n- The track record | portfolio | warn | has an RFC or design doc; missing an API or schema design shipped\n- The track record | certs | pass | no certification listed\n- The loop | loop | na | Recruiter call, Hiring-manager interview, API-design interview, System-design interview, Team interviews (two to five) (the executive round only for senior hires)\n- The loop | split | na | judged on developer-facing usability and misuse-resistance, apart from general system design\n- The offer | offer | na | no offer yet\n- The page's plan | steps | na | 1. internal, 2. portfolio, 3. loop\nTrack: platform; archetype: apifirst; what this track is screened on: api, rfc; missing: api.\nThe steps (step | what | note):\n- internal | Use internal API work as the practice ground | a hypothesis, not an established route: you already build internal APIs - write them up as practice for the\n\n[... 1632 more characters cut from this display ...]"
}
Reply:
{
"task": "plan",
"summary": "Target the Platform Engineer, API Platform role at Zindrel Pay; first, rewrite your order-events API design doc as a public-facing API design with versioning, idempotency and error choices spelled out.",
"population": "This is the external-API population, not the internal-platform one: the Zindrel Pay posting says the API is the product and names a public API and SDKs used by external developers taking card payments.",
"target": {
"track": "platform",
"archetype": "apifirst",
"why": "At an API-first payments company you would be measured on API design quality, reliability and contract stability, which is exactly the versioning, idempotency and error design the posting asks you to own."
},
"steps": [
{
"step": "internal",
"action": "Treat the internal REST APIs that three other teams call as your practice ground, writing up how you handled versioning, retries and errors for those consumers - a hypothesis, not an established route into the external track.",
"done_when": "You have a short write-up of one internal API showing the consumer-facing decisions you made and what you would change for outside developers.",
"hypothesis": true
},
{
"step": "portfolio",
"action": "Using your four weekly hours, turn the order-events API into a shipped API or schema design you can show: a published spec with resource shapes, idempotency keys and a documented error model.",
"done_when": "A spec you can walk a screener through exists alongside your two design docs, with every example free of confidential detail.",
"hypothesis": false
},
{
"step": "loop",
"action": "Prepare the recruiter, hiring-manager, API-design, system-design and team rounds, practising a card-payments API design separately from the service sketch behind it.",
"done_when": "You can design a payments API with actionable errors in one session and then sketch the service behind it in a second, each judged on its own terms.",
"hypothesis": false
}
],
"portfolio": [
{
"kind": "api",
"artefact": "A public-style API spec for your order-events API, with versioning rules, idempotent create calls and an error catalogue drawn from your retry-policy work on the fulfilment service.",
"proves": "That you can design an interface outside developers consume that is clear, predictable and hard to misuse, not just one that scales."
}
],
"caveats": [
"Your record has design docs but no shipped API or schema design yet; the spec above is what closes that gap.",
"Using internal APIs as practice is a hypothesis, not a proven route into external API work.",
"With a one-to-three-month window and four hours a week, keep the spec to one API rather than several."
]
}
What step 5 prints for it: internal: You have a short write-up of one internal API showing the consumer-facing decisions you made and what you would change for outside developers. | portfolio: A spec you can walk a screener through exists alongside your two design docs, with every example free of confidential detail. | loop: You can design a payments API with actionable errors in one session and then sketch the service behind it in a second, each judged on its own terms.
The page's check: kept - nothing to flag
prep - Internal APIs to a public API platform
The input carries the saved plan (R.laneForm fills carry from the plan reply), as the page's handoff does. The page's loop: recruiter, manager, apidesign, system, team.
Input (facts is 2974 characters; the first 1392 are shown):
{
"task": "prep",
"candidate": "Label: Backend engineer, internal APIs\nWhere they stand: Building internal APIs today; today: Backend engineer on an order-management team; I design the internal REST APIs three other teams call.; building internal APIs: yes\nTarget: Platform Engineer / Architect (read from the posting) at an infrastructure or API-first company (read from the posting); ecosystem to be credible in: rest\nDeadline: one to three months; a compounding move across employers; portfolio hours per week: 4\nOffer: none yet; plan date: 2026-11-02",
"evidence": "Target posting:\nPlatform Engineer, API Platform - Zindrel Pay\nThe API is the product at Zindrel Pay. You will design and run the public API and SDKs that external developers use to take card payments.\nYou will own versioning, idempotency and error design across the API surface, and review design documents with the team.\n\nTrack record (kind | item | describable without confidential detail):\n- an RFC or design doc | Design doc - internal order-events API, describable without confidential detail | yes\n- an RFC or design doc | Design doc - retry policy for the fulfilment service, can describe it | yes",
"carry": "Population: This is the external-API population, not the internal-platform one: the Zindrel Pay posting says the API is the product and names a public API and SDKs used by external developers taking card payments.\nTarget: Platform Engineer / Architect at an infrastructure or API-first company - At an API-first payments company you would be measured on API design quality, reliability and contract stability, which is exactly the versioning, idempotency and error design the posting asks you to own.\nSteps: internal - Treat the internal REST APIs that three other teams call as your practice ground, writing up how you handled versioning, retries and errors for those consumers - a hypothesis, not an established route into the external track.; portfolio - Using your four weekly hours, turn the order-events API into a shipped API or schema design you can show: a published spec with resource shapes, idempotency keys and a documented error model.; loop - Prepare the recruiter, hiring-manager, API-design, system-design and team rounds, practising a card-payments API design separately from the service sketch behind it.\nPortfolio: api - A public-style API spec for your order-events API, with versioning rules, idempotent create calls and an error catalogue drawn from your retry-policy work on the fulfilment service.",
"facts": "The page's checks (0 fail, 1 warn, 3 pass, 5 n/a); verdict: a platform move with gaps to close:\n- The target | stage | na | Building internal APIs today; building internal APIs today\n- The target | population | pass | external consumers - the posting names public api, sdks, external developers\n- The target | target | pass | Platform Engineer / Architect (read from the posting) at an infrastructure or API-first company (read from the posting) - measured on API design quality, infra reliability, contract stability\n- The track record | portfolio | warn | has an RFC or design doc; missing an API or schema design shipped\n- The track record | certs | pass | no certification listed\n- The loop | loop | na | Recruiter call, Hiring-manager interview, API-design interview, System-design interview, Team interviews (two to five) (the executive round only for senior hires)\n- The loop | split | na | judged on developer-facing usability and misuse-resistance, apart from general system design\n- The offer | offer | na | no offer yet\n- The page's plan | steps | na | 1. internal, 2. portfolio, 3. loop\nTrack: platform; archetype: apifirst; what this track is screened on: api, rfc; missing: api.\nThe steps (step | what | note):\n- internal | Use internal API work as the practice ground | a hypothesis, not an established route: you already build internal APIs - write them up as practice for the\n\n[... 1582 more characters cut from this display ...]"
}
Reply:
{
"task": "prep",
"summary": "Prepare the Zindrel Pay loop by turning your order-events design doc and fulfilment retry policy into a card-payments API story, and practise the API-design round apart from the system-design round.",
"rounds": [
{
"round": "recruiter",
"judges": "Whether your move from internal order-management APIs to a public payments API reads as motivated and at the right level for Zindrel Pay.",
"drill": "Tell, in two minutes, how you design the internal REST APIs three other teams call and why you now want external developers taking card payments as your consumers."
},
{
"round": "manager",
"judges": "Whether your scope fits a team that owns versioning, idempotency and error design across a public API and its SDKs.",
"drill": "Pick one decision in your order-events design doc you would reverse now that the consumers would be outside developers, and explain why."
},
{
"round": "apidesign",
"judges": "How clear, predictable and hard to misuse a card-payments interface you design is for developers integrating it, not how far it scales.",
"drill": "Design a create-payment call with an idempotency key, a versioning rule and an error catalogue whose codes tell a developer what to fix, borrowing from your fulfilment retry policy."
},
{
"round": "system",
"judges": "Whether you can architect the distributed service behind the payments API you just designed.",
"drill": "Sketch the service behind your create-payment call, showing where idempotency keys are stored and how retries from your fulfilment work would avoid double charges."
},
{
"round": "team",
"judges": "How you work with the team and judge an integration that is going wrong, including in design-document reviews.",
"drill": "Review a failing integration in which a developer gets duplicate charges after a timeout, and say what you would ask first before touching the API."
}
],
"practice": [
"Design the versioning policy for a public card-payments API so that an older SDK keeps working after a field is added.",
"Walk through how an idempotency key on a payment request behaves when the client retries after a network timeout.",
"Draft the error model for a declined card versus an invalid request, so a developer knows which one to retry.",
"Review a design document that proposes renaming a response field across the API and the SDKs, and say what you would push back on."
],
"offer": {
"anchor": "Benchmark against the general software-engineering ladder at the level Zindrel Pay places you, not against a platform-specific title.",
"source": "needs live verification - no offer or dated ladder source exists yet, so find a named, dated source for that level once an offer arrives.",
"verdict": "No offer yet: when one comes, re-benchmark it against a named, dated general-engineering band at its stated level before deciding to hold or negotiate."
},
"caveats": [
"Your record has design docs but no shipped API or schema design yet, so the order-events spec is the gap to close before the API-design round.",
"A take-home designing an SDK or API surface is plausible here, not confirmed.",
"Treating your internal APIs as practice for the external track is a hypothesis, not an established route."
]
}
What step 5 prints for it: recruiter: Tell, in two minutes, how you design the internal REST APIs three other teams call and why you now want external developers taking card payments as your consumers. | manager: Pick one decision in your order-events design doc you would reverse now that the consumers would be outside developers, and explain why. | apidesign: Design a create-payment call with an idempotency key, a versioning rule and an error catalogue whose codes tell a developer what to fix, borrowing from your fulfilment retry policy. | system: Sketch the service behind your create-payment call, showing where idempotency keys are stored and how retries from your fulfilment work would avoid double charges. | team: Review a failing integration in which a developer gets duplicate charges after a timeout, and say what you would ask first before touching the API.
The page's check: kept - nothing to flag
plan - A PM with talks and an offer in hand
The page read it as: a platform move with gaps to close; track apipm at bolton; steps loop, offer, internal, portfolio; 2 of 5 gates; failing checks: portfolio, certs, offer.
Input (facts is 3978 characters; the first 1398 are shown):
{
"task": "plan",
"candidate": "Label: Product manager, first API role\nWhere they stand: Not in platform work yet; today: Product manager on a scheduling product; I give talks about developer experience and write a product newsletter.; building internal APIs: no\nTarget: API Product Manager (your answer) at a company bolting a public API onto its product; ecosystem to be credible in: webhooks\nDeadline: interviewing now; a one-off move - the next role fast; portfolio hours per week: 2\nOffer: API Product Manager; Senior PM; $172k base; benchmarked by: a platform-title salary search; source: a crowdsourced search for API product manager salaries; plan date: 2026-11-02",
"evidence": "Target posting:\nAPI Product Manager - Plimsodd\nPlimsodd is adding its first public API so partners can build on our scheduling product.\nYou will own the API roadmap, pricing for API access, and the developer portal external developers use.\n\nTrack record (kind | item | describable without confidential detail):\n- talks, posts or community work | Talk - developer experience at a regional product meetup | yes\n- talks, posts or community work | Blog - five posts on onboarding friction | yes\n- a certification | Certification - an API product certificate | yes\n- talks, posts or community work | Newsletter - monthly product newsletter | yes",
"carry": "",
"facts": "The page's checks (3 fail, 1 warn, 2 pass, 4 n/a); verdict: a platform move with gaps to close:\n- The target | stage | na | Not in platform work yet\n- The target | population | pass | external consumers - the posting names public api, developer portal, external developers\n- The target | target | pass | API Product Manager at a company bolting a public API onto its product - measured on general engineering delivery, with API design as one competency among several\n- The track record | portfolio | fail | has talks, posts or community work, a certification; missing API product, roadmap or pricing work and an RFC or design doc\n- The track record | certs | fail | Certification - the record leans on certification alone; no evidence shows employers weight it\n- The track record | devrel | warn | talks, posts and community work outnumber design artefacts - this track screens on API and system design judgment; advocate and educator roles are a different career\n- The loop | loop | na | Recruiter call, Hiring-manager interview, Scoping and design-thinking round, Technical conversation with an engineer, Written product exercise, Team interviews (two to five) (the executive round only for senior hires)\n- The loop | pmbar | na | scoping and communicating design thinking, not deep implementation; some loops add a technical conversation with an engineer, without code\n- The offer | offer | fail\n\n[... 2580 more characters cut from this display ...]"
}
Reply:
{
"task": "plan",
"summary": "Pursue the API Product Manager role at Plimsodd; start by drilling the scoping round, then re-benchmark your Senior PM offer against the general product-management ladder before any portfolio work.",
"population": "This is the external-API population, not the internal-platform one: the Plimsodd posting names a first public API for partners, a developer portal and external developers building on the scheduling product.",
"target": {
"track": "apipm",
"archetype": "bolton",
"why": "At a company bolting its first public API onto a scheduling product, you would be measured on general delivery with API design as one competency, so your roadmap, pricing and portal judgment carry the screen."
},
"steps": [
{
"step": "loop",
"action": "Since you are interviewing now, rehearse the recruiter, hiring-manager, scoping, technical-conversation, written-exercise and team rounds, starting with scoping a first public API for a scheduling product out loud.",
"done_when": "You can scope a first public API in ten minutes, walk an engineer through one API call end to end without code, and hand over a one-page breaking-change memo.",
"hypothesis": false
},
{
"step": "offer",
"action": "Re-benchmark the Senior PM offer at $172k base against the general product-management ladder at the Senior PM level, replacing the crowdsourced API product manager salary search with a named, dated source.",
"done_when": "You hold a named, dated product-management ladder source for the Senior PM level and know whether $172k base sits inside that band.",
"hypothesis": false
},
{
"step": "internal",
"action": "Take on an internal API at your current scheduling job as a lower-stakes first run at API product work - a hypothesis, not an established route.",
"done_when": "You have owned the scope or roadmap of one internal API and can describe the decisions without confidential detail.",
"hypothesis": true
},
{
"step": "portfolio",
"action": "With your two weekly hours, produce one artefact that combines API product thinking and a design doc, rather than another talk or post.",
"done_when": "One describable document covering an API roadmap or pricing decision and its design reasoning is ready to share with a screener.",
"hypothesis": false
}
],
"portfolio": [
{
"kind": "pm",
"artefact": "A proposed roadmap and API-access pricing outline for a public scheduling API, building on your five posts about onboarding friction.",
"proves": "That you can scope an API as a product, decide what partners get first and reason about how access is priced."
},
{
"kind": "rfc",
"artefact": "A short design doc for one scheduling endpoint and its webhook events, covering what partners receive, retries and how a breaking change would be handled.",
"proves": "That you can structure and communicate API design thinking in writing, the judgment this track screens on."
}
],
"caveats": [
"Your record has no API product, roadmap or pricing work and no design doc yet; the two artefacts above replace that gap.",
"Your API product certificate is informational at best; no employer is shown to weight it, so lead with design work instead.",
"Talks, posts and a newsletter point toward advocate or educator roles, which are a different career from this one.",
"A platform-title salary search gives sparse crowdsourced data, so the $172k base is not yet benchmarked."
]
}
What step 5 prints for it: loop: You can scope a first public API in ten minutes, walk an engineer through one API call end to end without code, and hand over a one-page breaking-change memo. | offer: You hold a named, dated product-management ladder source for the Senior PM level and know whether $172k base sits inside that band. | internal: You have owned the scope or roadmap of one internal API and can describe the decisions without confidential detail. | portfolio: One describable document covering an API roadmap or pricing decision and its design reasoning is ready to share with a screener.
The page's check: kept - nothing to flag
Build the input yourself
Load the engine in node (global.window = global; require("./ppkit.js"); require("./ppreply.js")), then:
const K = PathKit, R = PathReply;
const f = K.form(Object.assign(K.blank(), { label: "Me", stage: "internal", archetype: "apifirst", track: "platform",
posting: "Design the public API and SDKs external developers use.", record: "Design doc | internal events API, describable" }));
// f.popRead (who the role serves), f.trackUse, f.archUse, f.gaps, f.steps, f.loop, f.gates
// K.selfCheck(f), K.loopCsv(f) - the self-check and the loop prep sheet
const b = K.buildInput(f, "plan"); // or "prep" (R.laneForm(form, "prep", plan) fills carry)
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "plan");
// 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://platform-path.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://platform-path.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"]
// platform-path.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://platform-path.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://platform-path.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
}
// Platform Path.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 Platform Path {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://platform-path.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://platform-path.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://platform-path.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://platform-path.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":"platform-path"}' | 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": "platform-path"})["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: "platform-path" })).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": "platform-path"}, 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", "platform-path"), 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: "platform-path" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "platform-path"])["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 = "platform-path" }))!["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": "plan", "candidate": "Label: Backend engineer, internal APIs
Where they stand: ...", "evidence": "Target posting:
Platform Engineer, API Platform ...", "carry": "", "facts": "The page's checks (...); verdict: a platform move with gaps to close:
- The track record | portfolio | warn | ..."}
# - 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": "plan", "candidate": "Label: Backend engineer, internal APIs
Where they stand: ...", "evidence": "Target posting:
Platform Engineer, API Platform ...", "carry": "", "facts": "The page's checks (...); verdict: a platform move with gaps to close:
- The track record | portfolio | warn | ..."}
# - 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: "plan" or "prep", candidate, evidence, 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 platform-path:<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 plan reply carries summary, population, target, steps, portfolio and caveats; a prep reply carries summary, rounds, practice, offer and caveats. The code below prints the gist of either: each step and when it is done, or each round and its drill.
KEY="platform-path:$(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 == "prep" then (.rounds[] | "\(.round): \(.drill)")
else (.steps[] | "\(.step): \(.done_when)") end' reply.json
import hashlib, time
key = "platform-path:" + 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"] == "prep":
for x in reply["rounds"]:
print(x["round"], x["drill"])
else:
for x in reply["steps"]:
print(x["step"], x["done_when"])
import { createHash } from "node:crypto";
const key = `platform-path:${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 === "prep") { for (const x of reply.rounds) console.log(x.round, x.drill); }
else { for (const x of reply.steps) console.log(x.step, x.done_when); }
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "platform-path:" + 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, a, b := "steps", "step", "done_when"
if reply["task"] == "prep" {
key, a, b = "rounds", "round", "drill"
}
xs, _ := reply[key].([]any)
for _, x := range xs {
xx := x.(map[string]any)
fmt.Println(xx[a], xx[b])
}
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "platform-path:" + 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("prep")) {
for (JsonNode x : reply.path("rounds")) System.out.println(x.path("round").asText() + " " + x.path("drill").asText());
} else {
for (JsonNode x : reply.path("steps")) System.out.println(x.path("step").asText() + " " + x.path("done_when").asText());
}
require "digest"
key = "platform-path:#{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"] == "prep"
reply["rounds"].each { |x| puts "#{x["round"]} #{x["drill"]}" }
else
reply["steps"].each { |x| puts "#{x["step"]} #{x["done_when"]}" }
end
$key = "platform-path:" . $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"] === "prep") { foreach ($reply["rounds"] as $x) echo $x["round"], " ", $x["drill"], "\n"; }
else { foreach ($reply["steps"] as $x) echo $x["step"], " ", $x["done_when"], "\n"; }
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"platform-path:{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 == "prep") { foreach (var x in reply["rounds"]!.AsArray()) Console.WriteLine($"{x!["round"]} {x["drill"]}"); }
else { foreach (var x in reply["steps"]!.AsArray()) Console.WriteLine($"{x!["step"]} {x["done_when"]}"); }
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: platform-path:$(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": "plan", "candidate": "Label: Backend engineer, internal APIs
Where they stand: ...", "evidence": "Target posting:
Platform Engineer, API Platform ...", "carry": "", "facts": "The page's checks (...); verdict: a platform move with gaps to close:
- The track record | portfolio | warn | ..."}
# - 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": "platform-path:" + 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": `platform-path:${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", "platform-path:"+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", "platform-path:" + 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" => "platform-path:#{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: platform-path:" . 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", $"platform-path:{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);