← Part Fit
API
Tokens

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.

StatusCodeMeaning
400validation_errorThe 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.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: both lanes are metered and need a personal sign-in.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. 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:

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

FieldRequiredWhat
taskyesbrief or model. If it is missing or neither, do brief and set "task": "brief".
partyeswhat 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.
interfacesyeseach 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.
carrysend it (empty for brief)model task only: the brief decisions, or a sentence saying none were carried over.
factsyesthe 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_notenooptional; 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:

House rules

The replies

brief

Write the design brief a fabricator and a reviewer work from.

  1. summary - one sentence, at most 40 words: whether it fits as drawn, and the first change.
  2. family - the page's family id, and process - 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).
  3. 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).
  4. unchecked - short strings: every interface no bundled standard covers, with what would check it; [] when none.
  5. 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).
  6. coupon - one sentence: the test coupon of the critical interface to print or cut first.
  7. 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.

  1. summary - one sentence, at most 40 words.
  2. filename - the model file name, ending in _model.py.
  3. 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.
  4. parameters - one object per DESIGN constant a user would tune: name (as in the code), value_mm (a number), why (one sentence).
  5. 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"}
}

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')

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}' 

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}' 

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

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