Run Part Fit from your own code
Both metered lanes are available over HTTP and each returns one JSON object. The division of labour: the page looks every interface up in the bundled standards, evaluates it at maximum material condition with the process clearance, budgets the process undersize, holds the process and the material to their limits, and maps every requirement to a gauge, then hands it all to the run as facts. The run writes the brief or the model; the page then holds it to its own facts (fitreply.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":"part-fit"}), 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, part, interfaces, facts) is missing. The page always sends all five of task, part, interfaces, 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 - before any geometry, then the parametric source:
brief- the process and material, every interface with a value that passes at maximum material condition, the unchecked interfaces, one gauge per requirement, and the test coupon.model- the build123d source: docstring, INTERFACE and DESIGN blocks, interfaces(), checks() and build(), plus the parameters to tune.
A missing or unknown task is answered as brief. Both lanes send the same five fields. The facts are the page's checks, one row per line, and a line starting "must:" is a rule the reply has to hold. The page's handoff carries the brief into carry for the model lane.
The input
| Field | Required | What |
|---|---|---|
task | yes | brief or model. If it is missing or neither, do brief and set "task": "brief". |
part | yes | what the part is and must do, the device family, the process and material, the fit, the service conditions and the design figures; "not given" means not given. |
interfaces | yes | each mating interface with its source, the standard's numbers, the intent, the clearance, the value and the page's result; the stated requirements with the gauge type the page read; the holes and bores. |
carry | send it (empty for brief) | model task only: the brief decisions, or a sentence saying none were carried over. |
facts | yes | the page's checks, one line each as area | id | status | evidence, and the standards in use. 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, @k-dense-ai/lab-hardware-cad - the only standard the run applies:
- The hard part of lab hardware is not the geometry: it is that the part must mate with equipment whose dimensions are fixed by a standard or a vendor drawing. Never write an interface dimension from memory - take it from the bundled standards or the facts, and where neither has it, ask for the vendor drawing or a measurement and name the dimension as unchecked.
- A pocket, bore or slot that receives a component is sized against the component's maximum material condition - nominal plus its plus-tolerance - and only then given clearance (the envelope intent). A feature that must itself conform sits inside the nominal band (the match intent). A clearance widens a band; it never shifts it. A pocket sized from nominal can jam on a conforming part.
- The receiving feature's own process undersize is budgeted separately: its smallest actual size must still exceed the mating part's maximum size plus the functional gap. Clearances by fit and process are starting points for a first article, not guarantees: print or cut a test coupon of the critical interface first.
- Process sets minimum wall, minimum feature and achievable tolerance. Printed holes come out undersize; a mill cannot cut a sharp internal corner, and a pocket fillet larger than the plate's minimum corner radius binds - use corner relief. Printed fine threads are a mistake: use a heat-set insert or a captive nut. A 25 mm metric grid and a 1 inch grid are not interchangeable. A laser-cut file states its kerf convention.
- Material compatibility governs: printed PLA, PETG, ABS and resins are not autoclavable; acrylic crazes on alcohols, including 70% ethanol; polycarbonate is attacked by many solvents; uncured resin is cytotoxic and a printed part should hold, not touch, cells; printed surfaces scatter and most resins autofluoresce.
- The model is parametric source: every dimension a user might change is a module-level named constant with units in the name; an INTERFACE block holds the standard-fixed numbers annotated with their standard id and a DESIGN block the free ones; every computed dimension is derived inside a function; interfaces() declares each standard interface with its intent and a non-negative clearance; checks() maps every requirement to a go/no-go gauge (a clear region, a material region, or a bounding-box limit) sized from the requirement's own numbers; build() returns the part. The docstring records the process, the material and every interface source.
House rules
- Use only the input and the standards' numbers the input gives. Never invent a dimension, a tolerance, a vendor or a standard. A value you choose (a clearance, a wall, a corrected pocket size) is derived from the page's numbers and shown so.
- The page's evaluations are settled: never call a failed interface fine, and never change a standard's number. Where an interface fails or is open, write the corrected value that passes; the failure itself belongs in a caveat.
- Ids are exactly as the facts give them: the standard ids, the dimension names, the family (labware, optomech, microfluidics, behavior), the process (fdm, sla, sls, cnc, laser), the material (pla, petg, abs, pp, nylon, peek, resin, bioresin, acrylic, pc, aluminium, steel, glass).
- No web links (http or https). Millimetres throughout; convert inch hardware in one named constant.
- Never use backticks in text fields, and never refer to the input itself as data. Terms of art from the rules above are free to use; never paste a whole sentence from this prompt.
- Never write a gendered pronoun for anyone.
- Reply with ONLY one JSON object - no prose before or after, no code fences.
The replies
brief
Write the design brief a fabricator and a reviewer work from.
summary- one sentence, at most 40 words: whether it fits as drawn, and the first change.family- the page's family id, andprocess-process(the page's id),material(the material id to use - the page's, or, when the page fails it, a replacement this process can make that survives the service, said why),why(one sentence).interfaces- one object per interface the input lists, in order:feature(as given),source(the standard id, or vendor drawing, measurement or user spec),intent(envelope or match),value_mm(a number that passes at maximum material condition and, for an envelope, still leaves the clearance after the process undersize - the given one when it does, the corrected one when it does not; null only for an unchecked interface with no value),why(one sentence: the standard's numbers and the clearance behind the value, or that it is unchecked and what would check it).unchecked- short strings: every interface no bundled standard covers, with what would check it;[]when none.gauges- one object per stated requirement, in order:requirement(as given),gauge(clear, material or bbox),how(one sentence: the region or limit, sized from the requirement's own numbers).coupon- one sentence: the test coupon of the critical interface to print or cut first.caveats- at most 4 short strings: the page's failures and the material and process cautions that remain;[]when there is nothing to say.
{
"task": "brief",
"summary": "one sentence",
"family": "labware",
"process": { "process": "fdm", "material": "petg", "why": "one sentence" },
"interfaces": [ { "feature": "plate pocket length", "source": "slas-microplate-footprint", "intent": "envelope", "value_mm": 129.4, "why": "one sentence" } ],
"unchecked": [ ],
"gauges": [ { "requirement": "as given", "gauge": "bbox", "how": "one sentence" } ],
"coupon": "one sentence",
"caveats": [ ]
}
The page holds it to its own facts (check() in fitreply.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; a summary of at most 40 words; the page's family and process; a replacement material when the page fails the given one; one interface per listed interface, in order, each standard one re-evaluated against its standard - at least maximum material condition plus clearance for an envelope, inside the band for a match - and through the process undersize; every unchecked interface named; one gauge per requirement, clear, material or bbox, sized from the requirement's own number; a test coupon.
model
Write the parametric build123d model.
summary- one sentence, at most 40 words.filename- the model file name, ending in _model.py.code- the complete Python source as one string with real newlines: the module docstring with Process and Interfaces lines; the INTERFACE and DESIGN constant blocks, lower-case names with units such as wall_t_mm - INTERFACE holds each standard's own nominal and tolerance as constants (never a corrected size), DESIGN the clearance, the undersize and the free choices, and a corrected pocket or bore size is computed from them in a function; every computed dimension derived inside a function from the named constants - no number in a function body except 0, 0.5, 1, 2 and angles, so a carried value becomes a named constant; geometry that carries each feature: a hole or screw lies in solid material, never in a pocket or cutout, and a pocket that must hold a part has a floor or a ledge under it unless the part is meant to pass through; interfaces() returning dicts with feature, standard, dimension, value, intent and clearance; checks() returning one dict per requirement, each with "feature" and one gauge: "clear" (no material may intrude) or "material" (material must be present), each a region {"cylinder": diameter, "axis": "x", "y" or "z", "at": [(a, b), ...]} - at is 2D in the plane perpendicular to the axis, with an optional "span": (lo, hi) along it - or {"box": (dx, dy, dz), "at": [(x, y, z), ...]}; a material gauge also sets "min_mm3" on the entry (the total over all its at positions), sized from the requirement; or a "bbox_x", "bbox_y" or "bbox_z" bound {"max": mm} (and "min" if needed); build() returning the part; only build123d and math imported; no file writes, no subprocess, no network.parameters- one object per DESIGN constant a user would tune:name(as in the code),value_mm(a number),why(one sentence).caveats- at most 4 short strings: what the generated geometry still needs (the snapshot to read, the coupon to print, an unchecked interface);[]when there is nothing to say.
{
"task": "model",
"summary": "one sentence",
"filename": "carrier_model.py",
"code": "\"\"\"Docstring.\"\"\"\nfrom build123d import *\n...",
"parameters": [ { "name": "wall_t_mm", "value_mm": 3.0, "why": "one sentence" } ],
"caveats": [ ]
}
The page requires: the reply answers the task it was sent; the same text rules; a _model.py file name; a module docstring with the process and the interface sources; an import from build123d; build(), interfaces() and checks() at module level; INTERFACE and DESIGN blocks; constants with units in their names; every standard id and every standard nominal the page uses present in the code; interfaces() declaring each standard interface with a non-negative clearance; checks() with a gauge per requirement; no bare numbers in build(); no file, process or network call and no export in the model; balanced brackets; each listed parameter a module-level constant.
Worked examples
Three runs from the page's free samples (invented companies): the brief for a plate carrier drawn near nominal and bound for an autoclave, the model for the same part carrying that brief, and the brief for a cage-to-breadboard adapter that is ready for a coupon. 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.
brief - A plate carrier drawn near nominal
The page read it as: it will not fit as drawn - fix the interfaces first; fdm in petg; standards slas-microplate-footprint; 3 of 6 gates; failing checks: envelope, undersize, corners, material.
Input (facts is 2282 characters; the first 1397 are shown):
{
"task": "brief",
"part": "Part: 96-well plate carrier for an incubator shelf\nWhat it must do: A carrier that holds one SLAS 96-well plate flat on an incubator shelf so it can be lifted out by two side tabs without touching the plate.\nFamily: labware (Labware adapters - plates, cuvettes, tubes, slides, dishes)\nProcess: FDM (about +/-0.3 mm, minimum wall 1.2 mm); material: PETG; fit: Free-sliding, 0.4 mm per side\nService: autoclave yes; cleaned with ethanol; cell contact no; optical or fluorescence path no\nDesign: wall 3 mm; smallest feature 1.2 mm; required tolerance +/-0.3 mm; threads none; pocket corners fillet (fillet 3 mm); table grid none",
"interfaces": "Interfaces (feature | source | standard numbers | intent | clearance | value | result):\n- plate pocket length | slas-microplate-footprint footprint_length | nominal 127.76 +0.5/-0.5 | envelope | clearance 0.8 | value 128.2 | fail headroom -0.86 | worst-case gap -0.36\n- plate pocket width | slas-microplate-footprint footprint_width | nominal 85.48 +0.5/-0.5 | envelope | clearance 0.8 | value 85.9 | fail headroom -0.88 | worst-case gap -0.38\n- shelf slot | measured (unchecked) | width | match | value 140\n\nRequirements (gauge | text):\n- bbox | under 20 mm tall to fit between shelves\n- clear | a plate at maximum material drops into the pocket\n- material | two side tabs stay at least 2 mm thick\n\nHoles and bores:\nnone",
"carry": "",
"facts": "The page's checks (4 fail, 1 warn, 3 pass, 2 n/a); verdict: it will not fit as drawn - fix the interfaces first:\n- Interfaces | family | na | Labware adapters - plates, cuvettes, tubes, slides, dishes\n- Interfaces | sources | pass | 2 dimensions against bundled standards, 1 named unchecked dimension\n- Interfaces | unchecked | warn | shelf slot (measured, 140 mm) - name each as unchecked in the report\n- Interfaces | envelope | fail | plate pocket length 128.2 mm is under 129.06 mm (max material 128.26 mm + clearance 0.8 mm); plate pocket width 85.9 mm is under 86.78 mm (max material 85.98 mm + clearance 0.8 mm)\n- Interfaces | undersize | fail | plate pocket length: 128.2 mm - FDM tolerance 0.3 = 127.9 mm against max material 128.26 mm leaves -0.36 mm - can jam; plate pocket width: 85.9 mm - FDM tolerance 0.3 = 85.6 mm against max material 85.98 mm leaves -0.38 mm - can jam\n- Process and geometry | process | pass | FDM: wall 3 mm, feature 1.2 mm within the planning figures\n- Process and geometry | corners | fail | a 3 mm fillet binds plates whose corners are sharper - keep it at or under 1.58 mm, or cut corner relief\n- Material and service | material | fail | PETG is not autoclavable at 121 C - offer chemical or gas sterilisation, or a verified high-temperature material\n- Requirements and gauges | gauges | pass | bbox: under 20 mm tall to fit between shelves; clear: a plate at\n\n[... 885 more characters cut from this display ...]"
}
Reply:
{
"task": "brief",
"summary": "It will not fit as drawn: widen the plate pocket to 129.36 by 87.08 mm so a maximum-material plate clears after FDM undersize, then switch from PETG to polypropylene for autoclaving.",
"family": "labware",
"process": {
"process": "fdm",
"material": "polypropylene",
"why": "PETG fails the 121 C autoclave cycle, so the carrier moves to an FDM-printable polypropylene that tolerates steam and ethanol wiping, to be verified on a coupon through one full autoclave cycle."
},
"interfaces": [
{
"feature": "plate pocket length",
"source": "slas-microplate-footprint",
"intent": "envelope",
"value_mm": 129.36,
"why": "Footprint_length 127.76 +0.5 gives 128.26 at maximum material, plus the 0.8 clearance is 129.06, plus the 0.3 FDM undersize budget is 129.36, replacing the failing 128.2."
},
{
"feature": "plate pocket width",
"source": "slas-microplate-footprint",
"intent": "envelope",
"value_mm": 87.08,
"why": "Footprint_width 85.48 +0.5 gives 85.98 at maximum material, plus the 0.8 clearance is 86.78, plus the 0.3 FDM undersize budget is 87.08, replacing the failing 85.9."
},
{
"feature": "shelf slot",
"source": "measurement",
"intent": "match",
"value_mm": 140,
"why": "The 140 mm shelf slot width is a measured, unchecked figure that no bundled standard covers; re-measure the incubator shelf at several points before cutting."
}
],
"unchecked": [
"shelf slot (measured, 140 mm): re-measure the incubator shelf width at several points or obtain the incubator vendor drawing"
],
"gauges": [
{
"requirement": "under 20 mm tall to fit between shelves",
"gauge": "bbox",
"how": "Limit the carrier's bounding box height to below 20 mm, tabs included."
},
{
"requirement": "a plate at maximum material drops into the pocket",
"gauge": "clear",
"how": "Keep a clear block of 128.26 by 85.98 mm, the footprint at maximum material, free of solid inside the pocket."
},
{
"requirement": "two side tabs stay at least 2 mm thick",
"gauge": "material",
"how": "Require a 2 mm thick solid region through each side tab across its full lifting area."
}
],
"coupon": "Print a thin open frame of the 129.36 by 87.08 mm pocket with relieved corners in polypropylene, autoclave it once, then check that a plate drops in and lifts out freely.",
"caveats": [
"As drawn the pocket jams: 128.2 and 85.9 mm sit 0.36 and 0.38 mm under a maximum-material plate after FDM undersize.",
"The 3 mm pocket fillet binds sharper plate corners: keep it at or under 1.58 mm, or cut corner relief.",
"PETG is not autoclavable at 121 C; confirm the polypropylene print survives the cycle without warping the pocket.",
"The shelf slot rests on one measurement and stays unchecked until confirmed."
]
}
What step 5 prints for it: plate pocket length: 129.36 | plate pocket width: 87.08 | shelf slot: 140
The page's check: kept - nothing to flag
model - A plate carrier drawn near nominal
The input carries the saved brief (R.laneForm fills carry from the brief reply), as the page's handoff does. Standards in use: slas-microplate-footprint.
Input (facts is 2333 characters; the first 1397 are shown):
{
"task": "model",
"part": "Part: 96-well plate carrier for an incubator shelf\nWhat it must do: A carrier that holds one SLAS 96-well plate flat on an incubator shelf so it can be lifted out by two side tabs without touching the plate.\nFamily: labware (Labware adapters - plates, cuvettes, tubes, slides, dishes)\nProcess: FDM (about +/-0.3 mm, minimum wall 1.2 mm); material: PETG; fit: Free-sliding, 0.4 mm per side\nService: autoclave yes; cleaned with ethanol; cell contact no; optical or fluorescence path no\nDesign: wall 3 mm; smallest feature 1.2 mm; required tolerance +/-0.3 mm; threads none; pocket corners fillet (fillet 3 mm); table grid none",
"interfaces": "Interfaces (feature | source | standard numbers | intent | clearance | value | result):\n- plate pocket length | slas-microplate-footprint footprint_length | nominal 127.76 +0.5/-0.5 | envelope | clearance 0.8 | value 128.2 | fail headroom -0.86 | worst-case gap -0.36\n- plate pocket width | slas-microplate-footprint footprint_width | nominal 85.48 +0.5/-0.5 | envelope | clearance 0.8 | value 85.9 | fail headroom -0.88 | worst-case gap -0.38\n- shelf slot | measured (unchecked) | width | match | value 140\n\nRequirements (gauge | text):\n- bbox | under 20 mm tall to fit between shelves\n- clear | a plate at maximum material drops into the pocket\n- material | two side tabs stay at least 2 mm thick\n\nHoles and bores:\nnone",
"carry": "Process: fdm in pp - PETG fails the 121 C autoclave cycle, so the carrier moves to an FDM-printable polypropylene that tolerates steam and ethanol wiping, to be verified on a coupon through one full autoclave cycle.\nInterfaces: plate pocket length 129.36 mm (slas-microplate-footprint, envelope); plate pocket width 87.08 mm (slas-microplate-footprint, envelope); shelf slot 140 mm (measurement, match)\nGauges: bbox - Limit the carrier's bounding box height to below 20 mm, tabs included; clear - Keep a clear block of 128.26 by 85.98 mm, the footprint at maximum material, free of solid inside the pocket; material - Require a 2 mm thick solid region through each side tab across its full lifting area\nCoupon: Print a thin open frame of the 129.36 by 87.08 mm pocket with relieved corners in polypropylene, autoclave it once, then check that a plate drops in and lifts out freely.",
"facts": "The page's checks (4 fail, 1 warn, 3 pass, 2 n/a); verdict: it will not fit as drawn - fix the interfaces first:\n- Interfaces | family | na | Labware adapters - plates, cuvettes, tubes, slides, dishes\n- Interfaces | sources | pass | 2 dimensions against bundled standards, 1 named unchecked dimension\n- Interfaces | unchecked | warn | shelf slot (measured, 140 mm) - name each as unchecked in the report\n- Interfaces | envelope | fail | plate pocket length 128.2 mm is under 129.06 mm (max material 128.26 mm + clearance 0.8 mm); plate pocket width 85.9 mm is under 86.78 mm (max material 85.98 mm + clearance 0.8 mm)\n- Interfaces | undersize | fail | plate pocket length: 128.2 mm - FDM tolerance 0.3 = 127.9 mm against max material 128.26 mm leaves -0.36 mm - can jam; plate pocket width: 85.9 mm - FDM tolerance 0.3 = 85.6 mm against max material 85.98 mm leaves -0.38 mm - can jam\n- Process and geometry | process | pass | FDM: wall 3 mm, feature 1.2 mm within the planning figures\n- Process and geometry | corners | fail | a 3 mm fillet binds plates whose corners are sharper - keep it at or under 1.58 mm, or cut corner relief\n- Material and service | material | fail | PETG is not autoclavable at 121 C - offer chemical or gas sterilisation, or a verified high-temperature material\n- Requirements and gauges | gauges | pass | bbox: under 20 mm tall to fit between shelves; clear: a plate at\n\n[... 936 more characters cut from this display ...]"
}
Reply (long strings shortened for display - see the cuts below):
{
"task": "model",
"summary": "Parametric polypropylene carrier with a 129.36 by 87.08 mm pocket that clears a maximum-material plate after FDM undersize, relieved corners instead of the binding fillet, and two 3 mm lifting tabs.",
"filename": "carrier_model.py",
"code": "\"\"\"96-well plate carrier for an incubator shelf.\n\nHolds one SLAS 96-well plate flat on an incubator shelf; two side tabs lift it out\nwithout touching the plate.\n\nProcess: fdm, material pp (polypropylene in place of PETG, which fails the [... 4928 more characters cut from this display]",
"parameters": [
{
"name": "pocket_clearance_mm",
"value_mm": 0.8,
"why": "The free-sliding fit of 0.4 mm per side added to the 128.26 by 85.98 mm maximum-material footprint."
},
{
"name": "process_undersize_mm",
"value_mm": 0.3,
"why": "The FDM tolerance budgeted so the smallest printed pocket still leaves the full clearance."
},
{
"name": "wall_t_mm",
"value_mm": 3,
"why": "The 3 mm design wall, well above the 1.2 mm FDM minimum, keeping the outer length at 135.36 mm inside the 140 mm shelf slot."
},
{
"name": "floor_t_mm",
"value_mm": 3,
"why": "Floor under the plate, matched to the wall so the part prints evenly."
},
{
"name": "rim_h_mm",
"value_mm": 10,
"why": "Pocket depth; with the floor it gives a 13 mm tall carrier, under the 20 mm shelf limit."
},
{
"name": "corner_relief_d_mm",
"value_mm": 3,
"why": "Corner relief bores replace the 3 mm fillet that binds plates with sharper corners."
},
{
"name": "tab_l_mm",
"value_mm": 40,
"why": "Length of each side tab, wide enough for a gloved grip."
},
{
"name": "tab_reach_mm",
"value_mm": 15,
"why": "How far each tab stands out from the rim so fingers never touch the plate."
},
{
"name": "tab_t_mm",
"value_mm": 3,
"why": "Tab thickness, above the 2 mm minimum the requirement sets."
}
],
"caveats": [
"Read the build snapshot to confirm the pocket, the corner relief bores and both tabs before slicing.",
"Print the open-frame coupon of the 129.36 by 87.08 mm pocket in polypropylene, autoclave it once, and check a plate drops in and lifts out.",
"The 140 mm shelf slot is a measurement and stays unchecked; re-measure the shelf before printing.",
"Polypropylene warps and bonds poorly to most beds; confirm the floor prints flat."
]
}
Cut from this display only (the saved reply and the check use the full text): code (5164 characters, 236 shown).
What step 5 prints for it: pocket_clearance_mm: 0.8 | process_undersize_mm: 0.3 | wall_t_mm: 3 | floor_t_mm: 3 | rim_h_mm: 10 | corner_relief_d_mm: 3 | tab_l_mm: 40 | tab_reach_mm: 15 | tab_t_mm: 3
The page's check: kept - nothing to flag
brief - A cage-to-breadboard adapter, ready
The page read it as: ready for a test coupon; cnc in aluminium; standards cage-system-30mm, optical-breadboard-metric; 6 of 6 gates; failing checks: none.
Input (facts is 1741 characters; the first 1396 are shown):
{
"task": "brief",
"part": "Part: 30 mm cage to metric breadboard adapter\nWhat it must do: A plate that carries a 30 mm cage system above a metric breadboard on two M6 screws, keeping the optical axis at 75 mm.\nFamily: optomech (Optomechanics - mounts, posts, breadboard and cage parts)\nProcess: CNC milling (about +/-0.05 mm, minimum wall 0.8 mm); material: Aluminium; fit: Located but removable, 0.05 mm per side\nService: autoclave no; cleaned with ethanol; cell contact no; optical or fluorescence path no\nDesign: wall 3 mm; smallest feature 2 mm; required tolerance +/-0.05 mm; threads none; pocket corners relief; table grid metric",
"interfaces": "Interfaces (feature | source | standard numbers | intent | clearance | value | result):\n- cage rod bores | cage-system-30mm rod_diameter | nominal 6 +0/-0 | envelope | clearance 0.1 | value 6.15 | pass headroom 0.05 | worst-case gap 0.1\n- rod bore spacing | cage-system-30mm rod_spacing | nominal 30 +0/-0 | match | clearance 0 | value 30 | pass\n- breadboard screw pitch | optical-breadboard-metric grid_pitch | nominal 25 +0/-0 | match | clearance 0 | value 25 | pass\n\nRequirements (gauge | text):\n- clear | two M6 socket screws pass through to the table\n- bbox | under 12 mm thick\n- material | keep material under the cage plate seat\n\nHoles and bores:\n- 6.6 mm | m6 clearance\n- 6.15 mm | cage rod bore",
"carry": "",
"facts": "The page's checks (0 fail, 0 warn, 7 pass, 2 n/a); verdict: ready for a test coupon:\n- Interfaces | family | na | Optomechanics - mounts, posts, breadboard and cage parts\n- Interfaces | sources | pass | 3 dimensions against bundled standards\n- Interfaces | envelope | pass | cage rod bores headroom +0.05 mm; rod bore spacing in band; breadboard screw pitch in band\n- Interfaces | undersize | pass | cage rod bores: 6.15 mm - CNC milling tolerance 0.05 = 6.1 mm against max material 6 mm leaves 0.1 mm\n- Process and geometry | process | pass | CNC milling: wall 3 mm, feature 2 mm within the planning figures\n- Process and geometry | grid | pass | a metric 25 mm grid - never mix it with the 25.4 mm one; the error over 5 hole centres would be 1.6 mm\n- Material and service | material | pass | Aluminium for ethanol cleaning\n- Requirements and gauges | gauges | pass | clear: two M6 socket screws pass through to the table; bbox: under 12 mm thick; material: keep material under the cage plate seat\n- Requirements and gauges | coupon | na | clearances are starting points for a first article, not guarantees - print or cut the critical interface first\nBundled standards in use: cage-system-30mm, optical-breadboard-metric. Unchecked interfaces: none.\nmust: family optomech; one interface row per listed interface, in order (cage rod bores; rod bore spacing; breadboard screw pitch), each with its\n\n[... 345 more characters cut from this display ...]"
}
Reply:
{
"task": "brief",
"summary": "It fits as drawn and is ready for a test coupon; the first step is cutting a coupon with the two 6.15 mm rod bores at 30 mm spacing and the M6 holes at 25 mm pitch.",
"family": "optomech",
"process": {
"process": "cnc",
"material": "aluminium",
"why": "Milled aluminium holds the 0.05 mm tolerance needed for rod bores and tolerates ethanol cleaning."
},
"interfaces": [
{
"feature": "cage rod bores",
"source": "cage-system-30mm",
"intent": "envelope",
"value_mm": 6.15,
"why": "Rod_diameter 6 +0 is the maximum material size, plus 0.1 clearance is 6.1, so 6.15 passes with 0.05 headroom and leaves 0.1 after the 0.05 milling undersize."
},
{
"feature": "rod bore spacing",
"source": "cage-system-30mm",
"intent": "match",
"value_mm": 30,
"why": "Rod_spacing is 30 +0/-0 with zero clearance, so the bore centres sit at 30 within the nominal band."
},
{
"feature": "breadboard screw pitch",
"source": "optical-breadboard-metric",
"intent": "match",
"value_mm": 25,
"why": "Grid_pitch is 25 +0/-0 with zero clearance, so the M6 holes sit at 25 on the metric grid."
}
],
"unchecked": [],
"gauges": [
{
"requirement": "two M6 socket screws pass through to the table",
"gauge": "clear",
"how": "Keep two 6.6 mm diameter cylinders, 25 mm apart, free of solid through the full plate thickness."
},
{
"requirement": "under 12 mm thick",
"gauge": "bbox",
"how": "Limit the plate's bounding box thickness to below 12 mm."
},
{
"requirement": "keep material under the cage plate seat",
"gauge": "material",
"how": "Require solid aluminium across the seat region between the two rod bores spaced 30 mm apart."
}
],
"coupon": "Mill a small aluminium coupon with two 6.15 mm rod bores at 30 mm centres and two 6.6 mm holes at 25 mm pitch, then slide in cage rods and bolt it to the breadboard.",
"caveats": [
"Keep the metric 25 mm grid; a 25.4 mm grid drifts 1.6 mm over 5 hole centres.",
"Milled internal corners need relief, since the cutter cannot leave a sharp corner."
]
}
What step 5 prints for it: cage rod bores: 6.15 | rod bore spacing: 30 | breadboard screw pitch: 25
The page's check: kept - nothing to flag
Build the input yourself
Load the engine in node (global.window = global; require("./fitkit.js"); require("./fitreply.js")), then:
const K = FitKit, R = FitReply;
const f = K.form(Object.assign(K.blank(), { label: "Plate carrier", process: "fdm", fit: "free",
interfaces: "plate pocket length | slas-microplate-footprint | footprint_length | 128.2 | envelope" }));
// f.ifaces (each interface with its evaluation), f.gates, f.reqRows (requirements as gauges), K.preview(f)
// K.evaluate("slas-microplate-footprint", "footprint_length", 129.06, "envelope", 0.8) - the skill's check, directly
const b = K.buildInput(f, "brief"); // or "model" (R.laneForm(form, "model", brief) fills carry)
const wire = K.toWire(b.input); // POST this to /estimate and /run
// then: const reply = R.normalize(R.parseReply(job.output.output), "brief");
// 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://part-fit.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://part-fit.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"]
// part-fit.mjs -- Node 18+ (global fetch)
const BASE = "https://api.skillsafe.ai/v1/app-api";
let TOKEN = "YOUR_TOKEN"; // from https://part-fit.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://part-fit.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
}
// PartFit.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 PartFit {
static final String BASE = "https://api.skillsafe.ai/v1/app-api";
static String token = "YOUR_TOKEN"; // from https://part-fit.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://part-fit.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://part-fit.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://part-fit.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":"part-fit"}' | 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": "part-fit"})["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: "part-fit" })).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": "part-fit"}, 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", "part-fit"), 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: "part-fit" })["token"])
// a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
$TOKEN = "";
$TOKEN = api("POST", "/guest", ["slug" => "part-fit"])["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 = "part-fit" }))!["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": "brief", "part": "Part: ... | What it must do: ... | Process: ...", "interfaces": "Interfaces (feature | source | ...) ... | Requirements ...", "carry": "", "facts": "Checks ... | must: ..."}
# - 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": "brief", "part": "Part: ... | What it must do: ... | Process: ...", "interfaces": "Interfaces (feature | source | ...) ... | Requirements ...", "carry": "", "facts": "Checks ... | must: ..."}
# - 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: "brief" or "model", part, interfaces, 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 part-fit:<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 brief reply carries summary, family, process, interfaces, unchecked, gauges, coupon and caveats; a model reply carries summary, filename, code, parameters and caveats. The code below prints the gist of either: each interface and its value, or each parameter and its value.
KEY="part-fit:$(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 == "model" then (.parameters[] | "\(.name): \(.value_mm)")
else (.interfaces[] | "\(.feature): \(.value_mm)") end' reply.json
import hashlib, time
key = "part-fit:" + 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"] == "model":
for x in reply["parameters"]:
print(x["name"], x["value_mm"])
else:
for x in reply["interfaces"]:
print(x["feature"], x["value_mm"])
import { createHash } from "node:crypto";
const key = `part-fit:${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 === "model") { for (const x of reply.parameters) console.log(x.name, x.value_mm); }
else { for (const x of reply.interfaces) console.log(x.feature, x.value_mm); }
sum := sha256.Sum256(raw) // add "crypto/sha256", "encoding/hex", "time" to the imports
key := "part-fit:" + 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 := "interfaces", "feature", "value_mm"
if reply["task"] == "model" {
key, a, b = "parameters", "name", "value_mm"
}
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 = "part-fit:" + 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("model")) {
for (JsonNode x : reply.path("parameters")) System.out.println(x.path("name").asText() + " " + x.path("value_mm").asText());
} else {
for (JsonNode x : reply.path("interfaces")) System.out.println(x.path("feature").asText() + " " + x.path("value_mm").asText());
}
require "digest"
key = "part-fit:#{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"] == "model"
reply["parameters"].each { |x| puts "#{x["name"]} #{x["value_mm"]}" }
else
reply["interfaces"].each { |x| puts "#{x["feature"]} #{x["value_mm"]}" }
end
$key = "part-fit:" . $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"] === "model") { foreach ($reply["parameters"] as $x) echo $x["name"], " ", $x["value_mm"], "\n"; }
else { foreach ($reply["interfaces"] as $x) echo $x["feature"], " ", $x["value_mm"], "\n"; }
var digest = System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(input.ToJsonString()));
var key = $"part-fit:{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 == "model") { foreach (var x in reply["parameters"]!.AsArray()) Console.WriteLine($"{x!["name"]} {x["value_mm"]}"); }
else { foreach (var x in reply["interfaces"]!.AsArray()) Console.WriteLine($"{x!["feature"]} {x["value_mm"]}"); }
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: part-fit:$(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": "brief", "part": "Part: ... | What it must do: ... | Process: ...", "interfaces": "Interfaces (feature | source | ...) ... | Requirements ...", "carry": "", "facts": "Checks ... | must: ..."}
# - 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": "part-fit:" + 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": `part-fit:${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", "part-fit:"+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", "part-fit:" + 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" => "part-fit:#{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: part-fit:" . 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", $"part-fit:{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);