Drive Flex Call from your own code
Both metered steps - the injury triage and the close calls - are available over HTTP. A natural use is a league tool or a group-chat bot that already knows a roster and its projections: your code sends the board and shows the cited calls.
The split first: the page does the arithmetic; the model gives the judgement; the page checks it. Parsing, the tag discount, the slot filler, the close calls and the flags come from flexkit.js, which runs in node as well as in the browser. Its reconcile function then checks that every tagged player or close call is answered exactly once, that O / IR / bye players are pivoted, that every pivot is on the roster, playable and able to fill the slot, that every start is one of the two names in its close call, that the verdict matches the calls, and that every cited id and every decimal number is on the board.
Base URL and the envelope
Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response uses one envelope:
{ "ok": true, "data": { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "status": 402, "details": { ... } } }
The token is minted for this app (the guest call takes {"slug":"flex-call"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object, sent bare: never wrap it as {"input": {…}}.
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error | The body is not a JSON object, or a required field is missing. |
| 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 tasks are metered and need a personal token. |
| 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
Every run sends task, which routes the one system prompt to one of two contracts:
| task | What it returns | Verdict field |
|---|---|---|
triage | A title, a summary; players [{player_id, verdict, pivot_to, check, why, cite}] - one per player in board.flagged, verdict play, monitor or pivot, pivot_to a roster id or null; 1–3 depth lines; caveats. | verdict: all_clear, watch_list, scramble |
calls | A title, a summary; calls [{decision_id, start, confidence, why, cite}] - one per close call, start its starter or challenger id, confidence high, medium or low; a stack_note line or null; 1–3 risks; caveats. | verdict: keep, tweak, rework |
A line is always {text, cite}. Cites are the board's ids: players P1 P2…, slots S1 S2…, close calls D1 D2…, flags F-empty F-hurt-start F-bye F-out F-noproj F-versus F-stack F-lock F-thin, and T-total T-bench L-scoring. The top-level verdict is fixed by the calls: triage is scramble when a starter (or a would-be starter) is pivoted, else watch_list when anyone is monitor, else all_clear; calls are keep with no swaps, tweak with one, rework with two or more.
The declared input schema carries scalars only, so object-valued fields travel JSON-encoded as strings (FlexKit.toWire does this). task, league, roster and board are required.
Input fields
| Field | Type | Notes |
|---|---|---|
task | string | triage or calls. |
league | string (JSON) | {scoring, scoring_name, preset, week, slots[{id, label, eligible[]}], status_rule}. |
roster | string (JSON) | [{id, name, pos, team, opp, proj, board, status, slot, floor?, ceil?, note?}]; notes clipped to 160 characters. |
board | string (JSON) | {lineup[], total, bench_best, decisions[], flags[], flagged[]} from FlexKit.analyze. |
triage | string (JSON) | calls only, optional: {verdict, summary, players[{player_id, verdict, pivot_to}]} from an earlier triage; bench its pivots first (FlexKit.applyTriage). |
note | string | Optional, the manager's situation; over 800 characters keeps the start and the end around a marker. |
retry_note | string | Only on a reformat retry. |
The reply
The job's output.output is a string holding one JSON object. Parse it with FlexKit.parseReply, then normalize, then check it with reconcile(reply, {stats}). applyTriage(benched, reply, stats) returns the kept-out list with the pivots added; applyCalls(locks, reply, stats) pins each call's starter into its slot.
Worked examples
Replies produced against the app's prompt for the three sample rosters (players and teams invented; lists shortened; the full replies ship in example-result.js). The page replays each of them free from its sample buttons. Inputs are shown with the roster and the board folded.
task: triage - Bye-week scramble (sample roster) (scramble)
Input:
{
"task": "triage",
"league": "{\"scoring\":\"ppr\",\"scoring_name\":\"full PPR\",\"preset\":\"standard\",\"week\":7,\"slots\":[{\"id\":\"S1\",\"label\":\"QB\",\"eligible\":[\"QB\"]},{\"id\":\"S2\",\"label\":\"RB1\",\"eligible\":[\"RB\"]},\"...\"],\"status_rule\":\"board points = your projection x 1 (healthy), 0.85 (Q), 0.35 (D), 0 (O, IR, bye)\"}",
"roster": "[{\"id\":\"P1\",\"name\":\"Cal Ostrander\",\"pos\":\"QB\",\"team\":\"HBR\",\"opp\":\"vs MTN\",\"proj\":19.4,\"board\":19.4,\"status\":\"healthy\",\"slot\":\"S1\",\"floor\":13,\"ceil\":26},{\"id\":\"P2\",\"name\":\"Dez Parrow\",\"pos\":\"QB\",\"team\":\"RVR\",\"opp\":\"@CPT\",\"proj\":15.1,\"board\":15.1,\"status\":\"healthy\",\"slot\":\"bench\",\"floor\":9,\"ceil\":22},\"... 13 more players\"]",
"board": "{\"lineup\":[{\"slot\":\"S1\",\"label\":\"QB\",\"player\":\"P1\",\"board\":19.4,\"locked\":false},{\"slot\":\"S2\",\"label\":\"RB1\",\"player\":\"P4\",\"board\":13.6,\"locked\":false},\"...\"],\"total\":103.1,\"bench_best\":{\"player\":\"P2\",\"adj\":15.1},\"decisions\":[{\"id\":\"D1\",\"slot\":\"S3\",\"label\":\"RB2\",\"starter\":\"P5\",\"challenger\":\"P6\",\"starter_board\":10.9,\"challenger_board\":10.4,\"gap\":0.5},{\"id\":\"D2\",\"slot\":\"S7\",\"label\":\"FLEX\",\"starter\":\"P10\",\"challenger\":\"P11\",\"starter_board\":11.6,\"challenger_board\":11,\"gap\":0.6}],\"flags\":[{\"id\":\"F-hurt-start\",\"text\":\"Rook Delacroix (questionable) is in the lineup - counted at a discount.\",\"players\":[\"P8\"]},\"...\"],\"flagged\":[{\"player\":\"P3\",\"status\":\"BYE\",\"starting\":false,\"would_start\":true},{\"player\":\"P7\",\"status\":\"O\",\"starting\":false,\"would_start\":true},{\"player\":\"P8\",\"status\":\"Q\",\"starting\":true,\"would_start\":true},{\"player\":\"P12\",\"status\":\"D\",\"starting\":false,\"would_start\":true}]}",
"note": "I'm projected to lose by about 20 this week, so I need upside more than a safe floor. Four of my guys are banged up or on bye."
}
Reply:
{
"task": "triage",
"verdict": "scramble",
"title": "Bye, an out, and a trending-up Q - lineup holds, but RB/WR depth is thin",
"summary": "Marcus Veil (bye) and Isaiah Crane (out) are already correctly left off your lineup, and Grady Lunt (doubtful) stays behind Noel Fairbanks at tight end, so none of them cost you a current start. Rook Delacroix looks like he is trending toward playing after a full Friday practice, so he stays in as your WR1 and keeps the Ostrander stack's ceiling. Since you are projected to trail by about 20, lean on Jalen Brooks-Tate and Price Amari - your best bench upside - if a late scratch opens a spot.",
"players": [
{
"player_id": "P3",
"verdict": "pivot",
"pivot_to": "P6",
"check": "nothing to check",
"why": "Marcus Veil is on bye this week, so his board number is 0 and he can't help you no matter how good his 17.8 projection looks. Jalen Brooks-Tate is your best healthy bench running back if a starter gets scratched.",
"cite": [
"P3",
"P6",
"F-bye"
]
},
{
"player_id": "P7",
"verdict": "pivot",
"pivot_to": "P11",
"check": "nothing to check",
"why": "Isaiah Crane is ruled out with a hamstring injury and is already at board 0 - his 16.2 projection does not matter this week. Price Amari is your best healthy bench receiver if you need a wide receiver later.",
"cite": [
"P7",
"P11",
"F-out"
]
},
"..."
],
"depth": [
{
"text": "Running back is thin behind your starters: Jalen Brooks-Tate is your only healthy bench running back if Tobias Rennick or Andre Holloway is scratched.",
"cite": [
"P6",
"P4",
"P5"
]
},
{
"text": "Wide receiver has one healthy bench option: Price Amari is your lone backup if Rook Delacroix, Kenji Morrow, or Sol Whitaker comes out.",
"cite": [
"P11",
"P8",
"P9",
"P10"
]
},
"..."
],
"caveats": [
"This reply has no live news, injury updates, or inactive lists beyond what's in your roster notes.",
"Confirm every questionable or doubtful player's final status before your lineup locks.",
"..."
]
}
task: calls - Bye-week scramble (sample roster) (rework)
Input:
{
"task": "calls",
"league": "{\"scoring\":\"ppr\",\"scoring_name\":\"full PPR\",\"preset\":\"standard\",\"week\":7,\"slots\":[{\"id\":\"S1\",\"label\":\"QB\",\"eligible\":[\"QB\"]},{\"id\":\"S2\",\"label\":\"RB1\",\"eligible\":[\"RB\"]},\"...\"],\"status_rule\":\"board points = your projection x 1 (healthy), 0.85 (Q), 0.35 (D), 0 (O, IR, bye)\"}",
"roster": "[{\"id\":\"P1\",\"name\":\"Cal Ostrander\",\"pos\":\"QB\",\"team\":\"HBR\",\"opp\":\"vs MTN\",\"proj\":19.4,\"board\":19.4,\"status\":\"healthy\",\"slot\":\"S1\",\"floor\":13,\"ceil\":26},{\"id\":\"P2\",\"name\":\"Dez Parrow\",\"pos\":\"QB\",\"team\":\"RVR\",\"opp\":\"@CPT\",\"proj\":15.1,\"board\":15.1,\"status\":\"healthy\",\"slot\":\"bench\",\"floor\":9,\"ceil\":22},\"... 13 more players\"]",
"board": "{\"lineup\":[{\"slot\":\"S1\",\"label\":\"QB\",\"player\":\"P1\",\"board\":19.4,\"locked\":false},{\"slot\":\"S2\",\"label\":\"RB1\",\"player\":\"P4\",\"board\":13.6,\"locked\":false},\"...\"],\"total\":103.1,\"bench_best\":{\"player\":\"P2\",\"adj\":15.1},\"decisions\":[{\"id\":\"D1\",\"slot\":\"S3\",\"label\":\"RB2\",\"starter\":\"P5\",\"challenger\":\"P6\",\"starter_board\":10.9,\"challenger_board\":10.4,\"gap\":0.5},{\"id\":\"D2\",\"slot\":\"S7\",\"label\":\"FLEX\",\"starter\":\"P10\",\"challenger\":\"P11\",\"starter_board\":11.6,\"challenger_board\":11,\"gap\":0.6}],\"flags\":[{\"id\":\"F-hurt-start\",\"text\":\"Rook Delacroix (questionable) is in the lineup - counted at a discount.\",\"players\":[\"P8\"]},\"...\"],\"flagged\":[{\"player\":\"P3\",\"status\":\"BYE\",\"starting\":false,\"would_start\":true},{\"player\":\"P7\",\"status\":\"O\",\"starting\":false,\"would_start\":true},{\"player\":\"P8\",\"status\":\"Q\",\"starting\":true,\"would_start\":true},{\"player\":\"P12\",\"status\":\"D\",\"starting\":false,\"would_start\":true}]}",
"note": "I'm projected to lose by about 20 this week, so I need upside more than a safe floor. Four of my guys are banged up or on bye.",
"triage": "{\"verdict\":\"scramble\",\"summary\":\"Marcus Veil (bye) and Isaiah Crane (out) are already correctly left off your lineup, and Grady Lunt (doubtful) stays behind Noel Fairbanks at tight end, so none of them cost you a current start. Rook Delacroix looks like he is trending toward playing after a full Friday practice, so he stays in as your WR1 and keeps the Ostrander stack's ceiling. Since you are projected to trail by about 20, lean on Jalen Brooks-Tate and Price Amari - your best bench upside - if a late scratch opens a spot.\",\"players\":[{\"player_id\":\"P3\",\"verdict\":\"pivot\",\"pivot_to\":\"P6\"},{\"player_id\":\"P7\",\"verdict\":\"pivot\",\"pivot_to\":\"P11\"},{\"player_id\":\"P8\",\"verdict\":\"play\",\"pivot_to\":null},{\"player_id\":\"P12\",\"verdict\":\"pivot\",\"pivot_to\":\"P13\"}]}"
}
Reply:
{
"task": "calls",
"verdict": "rework",
"title": "Chase the ceiling at both close calls",
"summary": "You're projected to trail by about 20, so both close calls go to the higher-ceiling bench option. Brooks-Tate's 10.4 board points are basically even with Holloway's 10.9, but his 22-point ceiling and lead, pass-catching role fit a comeback better. Amari's 11 is a near wash with Whitaker's 11.6, and his 25-point ceiling and history of big games give you the spike you need at FLEX.",
"calls": [
{
"decision_id": "D1",
"start": "P6",
"confidence": "medium",
"why": "Holloway's 10.9 board points edge Brooks-Tate's 10.4 by only half a point, but trailing by 20 you need the upside: Brooks-Tate's ceiling is 22 against Holloway's 14, and he has been the lead, pass-catching back since week 5 while Holloway still splits carries with a rookie.",
"cite": [
"D1",
"P5",
"P6"
]
},
{
"decision_id": "D2",
"start": "P11",
"confidence": "medium",
"why": "Whitaker's 11.6 barely tops Amari's 11, but Amari's 25-point ceiling dwarfs Whitaker's 14 and he already has two 30-point games this year - the kind of spike you need down 20, even accepting his 2-point floor against Whitaker's steady 9.",
"cite": [
"D2",
"P10",
"P11"
]
}
],
"stack_note": {
"text": "Your Ostrander-to-Delacroix connection (F-stack) is still in the lineup, keeping that higher ceiling and lower floor together, and Oakshire's defense still faces Kenji Morrow (F-versus), so his points still cut against your own DST.",
"cite": [
"F-stack",
"P1",
"P8",
"F-versus",
"P9",
"P15"
]
},
"risks": [
{
"text": "Delacroix is only questionable and counted at 12.7 of his 14.9 projection - a further downgrade before kickoff would leave WR1 near zero.",
"cite": [
"P8",
"F-hurt-start"
]
},
{
"text": "Brooks-Tate's floor is just 3 compared with Holloway's 7, so a quiet workload share at RB2 could cost you more than the 0.5-point edge you gave up.",
"cite": [
"P6",
"P5"
]
},
"..."
],
"caveats": [
"These are your own projections and board points, not independent numbers.",
"I have no live news, inactives list, or weather - confirm every status, especially Delacroix, before lock."
]
}
task: triage - Superflex QB question (sample roster) (watch_list)
Input:
{
"task": "triage",
"league": "{\"scoring\":\"half\",\"scoring_name\":\"half PPR\",\"preset\":\"superflex\",\"week\":7,\"slots\":[{\"id\":\"S1\",\"label\":\"QB\",\"eligible\":[\"QB\"]},{\"id\":\"S2\",\"label\":\"RB1\",\"eligible\":[\"RB\"]},\"...\"],\"status_rule\":\"board points = your projection x 1 (healthy), 0.85 (Q), 0.35 (D), 0 (O, IR, bye)\"}",
"roster": "[{\"id\":\"P1\",\"name\":\"Bram Okafor\",\"pos\":\"QB\",\"team\":\"SKY\",\"opp\":\"vs RVR\",\"proj\":21,\"board\":21,\"status\":\"healthy\",\"slot\":\"S1\",\"floor\":15,\"ceil\":28},{\"id\":\"P2\",\"name\":\"Theo Vance\",\"pos\":\"QB\",\"team\":\"CPT\",\"opp\":\"@DUN\",\"proj\":17,\"board\":14.5,\"status\":\"Q\",\"slot\":\"S8\",\"floor\":12,\"ceil\":28,\"note\":\"right thumb; limited Thursday; full Friday; plays in the 4:25 window\"},\"... 13 more players\"]",
"board": "{\"lineup\":[{\"slot\":\"S1\",\"label\":\"QB\",\"player\":\"P1\",\"board\":21,\"locked\":false},{\"slot\":\"S2\",\"label\":\"RB1\",\"player\":\"P4\",\"board\":15.2,\"locked\":false},\"...\"],\"total\":122.3,\"bench_best\":{\"player\":\"P3\",\"adj\":13.9},\"decisions\":[{\"id\":\"D1\",\"slot\":\"S10\",\"label\":\"DST\",\"starter\":\"P15\",\"challenger\":\"P14\",\"starter_board\":6.3,\"challenger_board\":5.9,\"gap\":0.4},{\"id\":\"D2\",\"slot\":\"S6\",\"label\":\"TE\",\"starter\":\"P11\",\"challenger\":\"P12\",\"starter_board\":8,\"challenger_board\":7.4,\"gap\":0.6},{\"id\":\"D3\",\"slot\":\"S8\",\"label\":\"SUPERFLEX\",\"starter\":\"P2\",\"challenger\":\"P3\",\"starter_board\":14.5,\"challenger_board\":13.9,\"gap\":0.6},{\"id\":\"D4\",\"slot\":\"S3\",\"label\":\"RB2\",\"starter\":\"P5\",\"challenger\":\"P6\",\"starter_board\":10.9,\"challenger_board\":9.1,\"gap\":1.8}],\"flags\":[{\"id\":\"F-hurt-start\",\"text\":\"Kai Lindqvist (questionable), Theo Vance (questionable) are in the lineup - counted at a discount.\",\"players\":[\"P5\",\"P2\"]},\"...\"],\"flagged\":[{\"player\":\"P2\",\"status\":\"Q\",\"starting\":true,\"would_start\":true},{\"player\":\"P5\",\"status\":\"Q\",\"starting\":true,\"would_start\":true}]}",
"note": "I'm the favourite by about 15 this week, so I would rather lock in safe floors than chase ceilings."
}
Reply:
{
"task": "triage",
"verdict": "watch_list",
"title": "Lindqvist good to go, watch Vance before lock",
"summary": "You're favored by about 15, so this leans toward protecting your floor rather than chasing upside. Kai Lindqvist's calf has a full Friday practice behind it, so he starts as planned. Theo Vance is trending the same way but his week was mixed and he plays in the late 4:25 window, so keep Milo Garrity ready in case you need the fallback.",
"players": [
{
"player_id": "P2",
"verdict": "monitor",
"pivot_to": "P3",
"check": "Decide before his 4:25 kickoff locks S8 - after that you can't swap in Milo Garrity.",
"why": "His thumb tag drops him from a 17 projection to a 14.5 board number, and the week was mixed - limited Thursday, full Friday - ahead of a 4:25 kickoff that leaves little time to react if he's downgraded. Milo Garrity's 13.9 is the bench fallback if you want the safer floor.",
"cite": [
"P2",
"P3",
"F-hurt-start"
]
},
{
"player_id": "P5",
"verdict": "play",
"pivot_to": null,
"check": "",
"why": "His calf tag discounts him from 12.8 to a 10.9 board number, but a full Friday practice is a concrete sign he's good to go, so he starts as planned.",
"cite": [
"P5",
"F-hurt-start"
]
}
],
"depth": [
{
"text": "Backup depth covers every spot but kicker: superflex has Milo Garrity's 13.9 (D3), RB has Omar Pettis's 9.1 (D4), tight end has Arlo Penn's 7.4 (D2), and D/ST is close with Riverton's 5.9 against Glenhaven's 6.3 (D1).",
"cite": [
"D3",
"D4",
"D2",
"D1"
]
},
{
"text": "Kicker is the thin spot - Bastian Kroll is your only one, so there's no fallback at S9 if he's scratched.",
"cite": [
"P13"
]
}
],
"caveats": [
"No live news, injury reports, or inactive lists beyond this input.",
"Confirm Vance's and Lindqvist's final status before your lineup locks.",
"..."
]
}
task: calls - Superflex QB question (sample roster) (tweak)
Input:
{
"task": "calls",
"league": "{\"scoring\":\"half\",\"scoring_name\":\"half PPR\",\"preset\":\"superflex\",\"week\":7,\"slots\":[{\"id\":\"S1\",\"label\":\"QB\",\"eligible\":[\"QB\"]},{\"id\":\"S2\",\"label\":\"RB1\",\"eligible\":[\"RB\"]},\"...\"],\"status_rule\":\"board points = your projection x 1 (healthy), 0.85 (Q), 0.35 (D), 0 (O, IR, bye)\"}",
"roster": "[{\"id\":\"P1\",\"name\":\"Bram Okafor\",\"pos\":\"QB\",\"team\":\"SKY\",\"opp\":\"vs RVR\",\"proj\":21,\"board\":21,\"status\":\"healthy\",\"slot\":\"S1\",\"floor\":15,\"ceil\":28},{\"id\":\"P2\",\"name\":\"Theo Vance\",\"pos\":\"QB\",\"team\":\"CPT\",\"opp\":\"@DUN\",\"proj\":17,\"board\":14.5,\"status\":\"Q\",\"slot\":\"S8\",\"floor\":12,\"ceil\":28,\"note\":\"right thumb; limited Thursday; full Friday; plays in the 4:25 window\"},\"... 13 more players\"]",
"board": "{\"lineup\":[{\"slot\":\"S1\",\"label\":\"QB\",\"player\":\"P1\",\"board\":21,\"locked\":false},{\"slot\":\"S2\",\"label\":\"RB1\",\"player\":\"P4\",\"board\":15.2,\"locked\":false},\"...\"],\"total\":122.3,\"bench_best\":{\"player\":\"P3\",\"adj\":13.9},\"decisions\":[{\"id\":\"D1\",\"slot\":\"S10\",\"label\":\"DST\",\"starter\":\"P15\",\"challenger\":\"P14\",\"starter_board\":6.3,\"challenger_board\":5.9,\"gap\":0.4},{\"id\":\"D2\",\"slot\":\"S6\",\"label\":\"TE\",\"starter\":\"P11\",\"challenger\":\"P12\",\"starter_board\":8,\"challenger_board\":7.4,\"gap\":0.6},{\"id\":\"D3\",\"slot\":\"S8\",\"label\":\"SUPERFLEX\",\"starter\":\"P2\",\"challenger\":\"P3\",\"starter_board\":14.5,\"challenger_board\":13.9,\"gap\":0.6},{\"id\":\"D4\",\"slot\":\"S3\",\"label\":\"RB2\",\"starter\":\"P5\",\"challenger\":\"P6\",\"starter_board\":10.9,\"challenger_board\":9.1,\"gap\":1.8}],\"flags\":[{\"id\":\"F-hurt-start\",\"text\":\"Kai Lindqvist (questionable), Theo Vance (questionable) are in the lineup - counted at a discount.\",\"players\":[\"P5\",\"P2\"]},\"...\"],\"flagged\":[{\"player\":\"P2\",\"status\":\"Q\",\"starting\":true,\"would_start\":true},{\"player\":\"P5\",\"status\":\"Q\",\"starting\":true,\"would_start\":true}]}",
"note": "I'm the favourite by about 15 this week, so I would rather lock in safe floors than chase ceilings.",
"triage": "{\"verdict\":\"watch_list\",\"summary\":\"You're favored by about 15, so this leans toward protecting your floor rather than chasing upside. Kai Lindqvist's calf has a full Friday practice behind it, so he starts as planned. Theo Vance is trending the same way but his week was mixed and he plays in the late 4:25 window, so keep Milo Garrity ready in case you need the fallback.\",\"players\":[{\"player_id\":\"P2\",\"verdict\":\"monitor\",\"pivot_to\":\"P3\"},{\"player_id\":\"P5\",\"verdict\":\"play\",\"pivot_to\":null}]}"
}
Reply:
{
"task": "calls",
"verdict": "tweak",
"title": "Floor over ceiling: one swap at TE",
"summary": "You're the favourite by about 15 and want safe floors over chasing ceilings, so I'm moving off Haskell Drum's touchdown-or-bust usage at TE and starting Arlo Penn's steadier target share instead - that also breaks the Vance-Drum stack's extra ceiling-for-floor trade you don't want this week. Everywhere else the board's starters already carry the better floor, so Theo Vance, Kai Lindqvist, and Glenhaven D/ST all stay in.",
"calls": [
{
"decision_id": "D1",
"start": "P15",
"confidence": "low",
"why": "Glenhaven's floor (2) edges Riverton's (0) on a 0.4-point gap with a similar ceiling (11 vs 12), so the safer floor pick - and the board's starter - wins this coin flip.",
"cite": [
"D1",
"P15",
"P14"
]
},
{
"decision_id": "D2",
"start": "P12",
"confidence": "medium",
"why": "Arlo Penn's 6-plus-target floor (6) doubles Haskell Drum's touchdown-or-bust floor (3) for only 0.6 fewer board points, and with you favouring floors over ceilings this week that outweighs Drum's extra upside from the Vance stack.",
"cite": [
"D2",
"P11",
"P12",
"F-stack-P2"
]
},
"..."
],
"stack_note": null,
"risks": [
{
"text": "Vance is still questionable and plays in the late 4:25 window, leaving little time to pivot to Garrity if he's downgraded.",
"cite": [
"P2",
"F-hurt-start"
]
},
{
"text": "Lindqvist's calf is already discounted to 10.9 on the board; it could still limit him in-game.",
"cite": [
"P5",
"F-hurt-start"
]
},
"..."
],
"caveats": [
"These are your own projections and board numbers; I only compared them.",
"I have no live news, so confirm Vance's and Lindqvist's statuses before your early games lock."
]
}
task: triage - Healthy favourite (sample roster) (all_clear)
Input:
{
"task": "triage",
"league": "{\"scoring\":\"std\",\"scoring_name\":\"standard\",\"preset\":\"three_wr\",\"week\":7,\"slots\":[{\"id\":\"S1\",\"label\":\"QB\",\"eligible\":[\"QB\"]},{\"id\":\"S2\",\"label\":\"RB1\",\"eligible\":[\"RB\"]},\"...\"],\"status_rule\":\"board points = your projection x 1 (healthy), 0.85 (Q), 0.35 (D), 0 (O, IR, bye)\"}",
"roster": "[{\"id\":\"P1\",\"name\":\"Ezra Coyle\",\"pos\":\"QB\",\"team\":\"DUN\",\"opp\":\"vs IRN\",\"proj\":18.6,\"board\":18.6,\"status\":\"healthy\",\"slot\":\"S1\",\"floor\":13,\"ceil\":25},{\"id\":\"P2\",\"name\":\"Hollis Grant\",\"pos\":\"QB\",\"team\":\"OKS\",\"opp\":\"vs TDE\",\"proj\":14.2,\"board\":14.2,\"status\":\"healthy\",\"slot\":\"bench\",\"floor\":8,\"ceil\":21},\"... 12 more players\"]",
"board": "{\"lineup\":[{\"slot\":\"S1\",\"label\":\"QB\",\"player\":\"P1\",\"board\":18.6,\"locked\":false},{\"slot\":\"S2\",\"label\":\"RB1\",\"player\":\"P3\",\"board\":14.8,\"locked\":false},\"...\"],\"total\":109,\"bench_best\":{\"player\":\"P2\",\"adj\":14.2},\"decisions\":[{\"id\":\"D1\",\"slot\":\"S8\",\"label\":\"FLEX\",\"starter\":\"P9\",\"challenger\":\"P5\",\"starter_board\":8.1,\"challenger_board\":7.9,\"gap\":0.2}],\"flags\":[{\"id\":\"F-hurt-start\",\"text\":\"Corbin Lyle (questionable) is in the lineup - counted at a discount.\",\"players\":[\"P7\"]},\"...\"],\"flagged\":[{\"player\":\"P7\",\"status\":\"Q\",\"starting\":true,\"would_start\":true}]}",
"note": "Healthy team and a comfortable favourite. Just want a second opinion before kickoff - and on a true coin flip I would rather stick with the board."
}
Reply:
{
"task": "triage",
"verdict": "all_clear",
"title": "Clean lineup - one Q tag trending toward a start",
"summary": "Your lineup is set with just one flagged player: Corbin Lyle (hip, Q). His full Friday practice and being listed as expected to play are good signs, so the board's call to start him holds. Everything else on your roster is healthy, so this is an all-clear with one tag to keep an eye on before kickoff.",
"players": [
{
"player_id": "P7",
"verdict": "play",
"pivot_to": null,
"check": "Glance at the inactive list before kickoff to confirm he's active as expected.",
"why": "Corbin Lyle's hip tag drops his board number from 11.3 to 9.6 (the Q discount), but his full practice Friday and being listed as expected to play both support starting him as the board has him.",
"cite": [
"P7",
"F-hurt-start"
]
}
],
"depth": [
{
"text": "Your bench QB, Hollis Grant, is close to a starter-level number (14.2 board) - a scratch there would barely cost you.",
"cite": [
"P2",
"T-bench"
]
},
{
"text": "RB and WR each have one healthy bench body (Tariq Wells, Quinn Harlow) to plug in, but your bench tight end is a big step down from your starter (5.2 vs 8.8 board).",
"cite": [
"P5",
"P10",
"P12",
"P11"
]
},
"..."
],
"caveats": [
"I have no live news, injury updates, or inactive lists beyond what's in your roster and notes.",
"Confirm Corbin Lyle is officially active before lineups lock.",
"..."
]
}
task: calls - Healthy favourite (sample roster) (keep)
Input:
{
"task": "calls",
"league": "{\"scoring\":\"std\",\"scoring_name\":\"standard\",\"preset\":\"three_wr\",\"week\":7,\"slots\":[{\"id\":\"S1\",\"label\":\"QB\",\"eligible\":[\"QB\"]},{\"id\":\"S2\",\"label\":\"RB1\",\"eligible\":[\"RB\"]},\"...\"],\"status_rule\":\"board points = your projection x 1 (healthy), 0.85 (Q), 0.35 (D), 0 (O, IR, bye)\"}",
"roster": "[{\"id\":\"P1\",\"name\":\"Ezra Coyle\",\"pos\":\"QB\",\"team\":\"DUN\",\"opp\":\"vs IRN\",\"proj\":18.6,\"board\":18.6,\"status\":\"healthy\",\"slot\":\"S1\",\"floor\":13,\"ceil\":25},{\"id\":\"P2\",\"name\":\"Hollis Grant\",\"pos\":\"QB\",\"team\":\"OKS\",\"opp\":\"vs TDE\",\"proj\":14.2,\"board\":14.2,\"status\":\"healthy\",\"slot\":\"bench\",\"floor\":8,\"ceil\":21},\"... 12 more players\"]",
"board": "{\"lineup\":[{\"slot\":\"S1\",\"label\":\"QB\",\"player\":\"P1\",\"board\":18.6,\"locked\":false},{\"slot\":\"S2\",\"label\":\"RB1\",\"player\":\"P3\",\"board\":14.8,\"locked\":false},\"...\"],\"total\":109,\"bench_best\":{\"player\":\"P2\",\"adj\":14.2},\"decisions\":[{\"id\":\"D1\",\"slot\":\"S8\",\"label\":\"FLEX\",\"starter\":\"P9\",\"challenger\":\"P5\",\"starter_board\":8.1,\"challenger_board\":7.9,\"gap\":0.2}],\"flags\":[{\"id\":\"F-hurt-start\",\"text\":\"Corbin Lyle (questionable) is in the lineup - counted at a discount.\",\"players\":[\"P7\"]},\"...\"],\"flagged\":[{\"player\":\"P7\",\"status\":\"Q\",\"starting\":true,\"would_start\":true}]}",
"note": "Healthy team and a comfortable favourite. Just want a second opinion before kickoff - and on a true coin flip I would rather stick with the board.",
"triage": "{\"verdict\":\"all_clear\",\"summary\":\"Your lineup is set with just one flagged player: Corbin Lyle (hip, Q). His full Friday practice and being listed as expected to play are good signs, so the board's call to start him holds. Everything else on your roster is healthy, so this is an all-clear with one tag to keep an eye on before kickoff.\",\"players\":[{\"player_id\":\"P7\",\"verdict\":\"play\",\"pivot_to\":null}]}"
}
Reply:
{
"task": "calls",
"verdict": "keep",
"title": "Board holds on the FLEX coin flip",
"summary": "You're healthy and a comfortable favourite, so I weighed floor over ceiling on the FLEX call and kept Silas Mbeki over Tariq Wells \u2014 the board numbers are almost even (8.1 to 7.9) and you told me to stick with the board on a true coin flip like this. Nothing else changes: your lineup stands as set, with Corbin Lyle's questionable tag the only item to watch before kickoff.",
"calls": [
{
"decision_id": "D1",
"start": "P9",
"confidence": "low",
"why": "Silas Mbeki edges Tariq Wells on the board (8.1 to 7.9, a 0.2 gap) and on floor (3 to 2), but the gap is well inside coin-flip range. You said to stick with the board on a true coin flip, so Mbeki keeps the FLEX spot.",
"cite": [
"D1",
"P9",
"P5"
]
}
],
"stack_note": {
"text": "Your DST (Capeton) plays against Rafe Moreno, so a big game from him works against your own defence's score.",
"cite": [
"F-versus",
"P14",
"P3"
]
},
"risks": [
{
"text": "Corbin Lyle is starting at a discount for his hip tag; a downgrade before kickoff would cost your WR3 spot.",
"cite": [
"P7",
"F-hurt-start"
]
},
{
"text": "The FLEX call was a near coin flip; if Tariq Wells's game script turns in his favour, the bench option would have outscored the board's pick.",
"cite": [
"D1",
"P9",
"P5"
]
}
],
"caveats": [
"These are your own projections and floors/ceilings - I only compare the numbers already on the board.",
"I have no live news, inactives, or injury updates - confirm statuses before lock."
]
}
Build the input yourself
Load the script in node (global.window = {}; require("./flexkit.js")), then:
const K = FlexKit;
const { players } = K.parseRoster("Cal Ostrander, QB, HBR, vs MTN, 19.4, 13, 26\n...");
let stats = K.analyze({ scoring: "ppr", preset: "standard", week: 7, players }); // lineup, close calls, flags
const { input } = K.buildInput("triage", { stats, note: "" });
const wire = K.toWire(input); // POST this to /estimate and /run
// later: const triage = K.normalize(K.parseReply(job.output.output));
// K.reconcile(triage, { stats }) -> [{level, code, text}]
// const benched = K.applyTriage([], triage, stats);
// stats = K.analyze({ ..., players, benched }); then K.buildInput("calls", { stats, triage })
Step 1 — a tiny client helper
One function that sends JSON and unwraps the envelope. 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="paste-your-token-here" # from https://flex-call.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 = "paste-your-token-here" # from https://flex-call.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"]
// flex-call.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "paste-your-token-here"; // from https://flex-call.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 = "paste-your-token-here" // from https://flex-call.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
}
// CueScroll.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 CueScroll {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "paste-your-token-here"; // from https://flex-call.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 = "paste-your-token-here" # from https://flex-call.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 = "paste-your-token-here"; // from https://flex-call.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 = "paste-your-token-here"; // from https://flex-call.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":"flex-call"}' | 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": "flex-call"})["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: "flex-call" })).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": "flex-call"}, 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", "flex-call"), 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: "flex-call" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "flex-call"])["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 = "flex-call" }))!["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": "triage", "league": "{...}", "roster": "[...]", "board": "{...}"}
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": "triage", "league": "{...}", "roster": "[...]", "board": "{...}"}
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, league, roster, board, note} (calls: + triage)
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 flex-call:<task>:<inputHash>:a<attempt>, and its one reformat retry (with retry_note added) is attempt 2, a new key and a second run. The job's output.output is the reply text; truncated: true means the balance cut it short.
KEY="flex-call:triage:$(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}'
import hashlib, time
key = "flex-call:" + 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 lane's JSON object
print(j["charged_credits"], j.get("truncated"), reply.get("verdict"))
import { createHash } from "node:crypto";
const key = `flex-call:${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.verdict);
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "flex-call:" + 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 }
}
fmt.Println(j["charged_credits"], j["truncated"], j["output"].(map[string]any)["output"])
var digest = java.security.MessageDigest.getInstance("SHA-256").digest(M.writeValueAsBytes(input));
String key = "flex-call:" + 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);
require "digest"
key = "flex-call:#{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["verdict"]].inspect
$key = "flex-call:" . $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["verdict"], "\n";
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"flex-call:{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>());
Console.WriteLine($"{j["charged_credits"]} {reply}");
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: flex-call:triage:$(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": "triage", "league": "{...}", "roster": "[...]", "board": "{...}"}
req = urllib.request.Request(BASE + "/run-stream", method="POST", data=json.dumps(rev).encode(),
headers={"Content-Type": "application/json", "Authorization": "Bearer " + TOKEN,
"Idempotency-Key": "flex-call:triage:" + 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": `flex-call:triage:${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", "flex-call:triage:"+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", "flex-call:triage:" + 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" => "flex-call:triage:#{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: flex-call:triage:" . 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", $"flex-call:triage:{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);