This reference document synthesizes the discoveries from preliminary API testing and aligns them with the Fusion 360 Tool Library Synchronization architectural goals. It serves as the master guide for developers interacting with the Grace Engineering Plex instance via the connect.plex.com REST API gateway.
Note: Grace Engineering runs Plex Classic, MES+ enabled, supporting Prime Archery and Montana Rifle Company.
Companion: Postman collections. Every endpoint documented in §3 below has a matching request in the Plex API — Datum Postman collection (see
Postman_Collections.mdfor the full catalog and naming convention). Postman is the recommended way to explore endpoints by hand; this file is the authoritative reference for what each endpoint actually returns.
All Plex APIs are routed through the developer portal. There is no session token or OAuth flow; a static subscription key is passed via request headers.
- Developer Portal:
https://developers.plex.com/ - Rate Limit: 200 API calls per minute across all endpoints.
- Base URL:
https://connect.plex.com(Production) /https://test.connect.plex.com(Test)
plex_api.py honors a PLEX_BASE_URL environment variable that overrides
both BASE_URL and PLEX_USE_TEST. Used by the write-validation
workflow in #92 to
point datum-sync at the local Plex-mimic mock
(tools/plex_mock/server.py) instead of connect.plex.com. Unset in
normal production operation.
Resolution order (first match wins):
- Explicit
base_url=kwarg toPlexClient()— tests and ad-hoc scripts PLEX_BASE_URLenv var — deployment-time override (the mock)PLEX_USE_TEST=1→test.connect.plex.com- Default →
connect.plex.com
Required Header:
X-Plex-Connect-Api-Key: <your_consumer_key>Warning
The API key must be in the Request Headers. Placing it as a URL parameter will result in a 401 Unauthorized error.
Important
All values below were verified empirically against connect.plex.com
(production) on the Grace tenant
(58f781ba-1691-4f32-b1db-381cdb21300c). Reproduce by running the
diagnostic at /api/diagnostics/tenant from the local UI, or by
running the Postman [AUTH] List All Tenants — Auth Canary request.
Verification history:
- 2026-04-07 — first full sweep with the
Fusion2PlexConsumer Key. Discovered that tools live atinventory/v1/inventory-definitions/supply-items(nottooling/v1/toolsas earlier docs claimed). - 2026-04-09 — re-verified with the rotated
DatumConsumer Key after the project rename. 23-request connectivity sweep + 6-request Get-by-ID chain test. Captured full field schemas + discovered that supply-items have no foreign key to suppliers, parts, locations, or any other resource (see §3.5 below). Also discovered thescheduling/v1/jobsendpoint (new — not in any earlier doc).
Plex uses two URL shapes for read endpoints:
- Master data — flat:
<namespace>/v1/<resource>Example:mdm/v1/parts,mdm/v1/suppliers,mdm/v1/operations - Definitions — nested:
<namespace>/v1/<namespace>-definitions/<resource>Example:production/v1/production-definitions/workcenters,inventory/v1/inventory-definitions/supply-items,inventory/v1/inventory-definitions/locations - Flat with sub-namespace (new 2026-04-09):
<namespace>/v1/<resource>— thescheduling/v1/jobsendpoint uses the first pattern but lives under a namespace that wasn't previously catalogued.
Both patterns are used in production. The bare <namespace>/v1 root
typically returns 404 (no resource at the root); the actual data lives
one level deeper.
Record counts are as of 2026-04-09 unless noted. Schemas captured 2026-04-09 by the Get-by-ID chain test.
| Status | Path | Records | Schema / Notes |
|---|---|---|---|
| 200 | mdm/v1/tenants |
1 | Single tenant: Grace. Auth canary — run first in any session. |
| 200 | mdm/v1/parts |
16,921 | +8 since 2026-04-07. 19.6 MB unfiltered. Tools are NOT here. Fields: buildingCode, createdById, createdDate, description, group, id, leadTimeDays, modifiedById, modifiedDate, name, note, number, productType, revision, source, status, type. type ∈ {Finished Good, Raw Material, Sub Assembly}. |
| 200 | mdm/v1/parts?status=Active |
— | 7.8 MB — only verified working filter. All other query params silently ignored. |
| 200 | mdm/v1/parts/{id} |
— | Same 17 fields as list view — no hidden detail. |
| 200 | mdm/v1/suppliers |
1,575 | 709 KB. Fields: category, code, contactNote, createdById, createdDate, id, language, modifiedById, modifiedDate, name, note, oldCode, parentSupplierId, status, type, webAddress. parentSupplierId is a self-referential FK. First record is a Carrier — list mixes material suppliers, carriers, etc. |
| 200 | mdm/v1/suppliers/{id} |
— | Same 16 fields as list view. |
| 200 | mdm/v1/customers |
109 | 96 KB. 35 fields. FKs to employees (assignedToId, assignedTo2Id, assignedTo3Id), contacts (contactResourceId), suppliers (defaultCarrierIds array, supplierCode). |
| 200 | mdm/v1/customers/{id} |
— | Same 35 fields as list view. |
| 200 | mdm/v1/contacts |
299 | 202 KB. |
| 200 | mdm/v1/buildings |
4 | 1.2 KB. Referenced from workcenters via buildingCode/buildingId. |
| 200 | mdm/v1/employees |
641 | 272 KB. UUIDs appear as createdById/modifiedById across essentially every other resource. |
| 200 | mdm/v1/operations |
122 | Minimal 4-field schema: code, id, inventoryType, type. No FK to tools, parts, or routings — the reason issue #5 is blocked. |
| 200 | mdm/v1/operations/{id} |
— | Same 4 fields as list view. |
| 200 | inventory/v1/inventory-definitions/supply-items |
2,516 | 614 KB. Full unfiltered. |
| 200 | inventory/v1/inventory-definitions/supply-items?category=Tools%20%26%20Inserts |
1,109 | TOOLS LIVE HERE. Fields: category, description, group, id, inventoryUnit, supplyItemNumber, type. No supplier FK. No cross-references of any kind. See §3.5. |
| 200 | inventory/v1/inventory-definitions/supply-items/{id} |
— | Same 7 fields as list view. |
| 200 | inventory/v1/inventory-definitions/locations |
1,270 | 279 KB. Not cross-referenced from supply-item. |
| 200 | production/v1/production-definitions/workcenters |
143 | 21 MILLs. workcenterId, not id. Fields: buildingCode, buildingId, ipAddress, name, plcName, productionLineId, tankSilo, workcenterCode, workcenterGroup, workcenterId, workcenterType. |
| 200 | production/v1/production-definitions/workcenters/{id} |
— | Same 11 fields as list view. |
| 200 | purchasing/v1/purchase-orders |
— | 44.2 MB unfiltered. Full PO history. ?updatedAfter= filter confirmed as a silent no-op on 2026-04-09 (byte-identical response). |
| 200 | inventory/v1-beta1/inventory-history/item-adjustments?ItemId=<uuid>&StartDate=<ISO>&EndDate=<ISO> |
varies | Supply-item adjustment log. 31/1,109 tools have non-empty history (2026-04-15). Fields: adjustmentDate, itemId, itemNo, location, locationId, quantity, transactionType. Summing quantity (already signed) gives running balance. Dates MUST be full ISO with Z (plain YYYY-MM-DD → 400 ARGUMENT_INVALID). See §3.6. |
| 200 | inventory/v1/inventory-tracking/containers |
10,676 | On-hand for parts (RAW/WIP/FG). Fields include quantity, partId, partNo, location, locationId, serialNo, lotId, inventoryType. Disjoint from supply-items (tools). |
| 200 | inventory/v1/inventory-history/container-adjustments?BeginDate=<ISO>&EndDate=<ISO> |
6,298 | Per-container adjustment log. Fields: adjustmentCode, adjustmentDate, location, partId, partNumber, quantity, serialNo, .... |
| 200 | scheduling/v1/jobs |
TBD | NEW — discovered 2026-04-09. Returns 200 but 15.8s response time, so the body is large. Schema, record count, and whether it carries tool/operation/workcenter FKs all TBD. Worth a deep-dive as follow-up to issue #5 (routing/operation linkage) — if jobs link to tools, we get the missing operation→tool mapping for free. |
All of the following were probed on 2026-04-09 and returned 404 RESOURCE_NOT_FOUND. Kept here so future sessions know they've been checked and don't waste a cycle re-testing them blindly. Re-probe periodically to detect subscription changes.
| Path | First checked | Notes |
|---|---|---|
tooling/v1/tools |
2026-04-07 (re-check 2026-04-09) | In original pre-Datum docs. Blocks #4. |
tooling/v1/tool-assemblies |
2026-04-07 (re-check 2026-04-09) | Blocks #4. |
tooling/v1/assemblies |
2026-04-09 | Alternate spelling, also 404. |
manufacturing/v1/routings |
2026-04-07 (re-check 2026-04-09) | Blocks #5. |
quality/v1/inspections |
2026-04-09 | Speculative probe. |
sales/v1/sales-orders |
2026-04-09 | Speculative probe. |
inventory/v1/on-hand |
2026-04-09 | Would have given tool stock levels. |
inventory/v1/containers |
2026-04-09 | |
inventory/v1/inventory-definitions/container-types |
2026-04-09 | |
mdm/v1/parts-buckets |
2026-04-09 | |
production/v1/production-definitions/assets |
2026-04-09 | |
production/v1/production-definitions/assemblies |
2026-04-09 | |
purchasing/v1/purchase-orders-lines |
2026-04-09 | Would have given supply-item → PO → supplier linkage. |
The inventory/v1/inventory-definitions/supply-items resource is identity-only. Its 7 fields are:
category(string, e.g."Tools & Inserts")description(free-text, human-readable)group(string, e.g."Machining")id(UUID — Plex primary key)inventoryUnit(string, e.g."Ea")supplyItemNumber(string — see below)type(string, e.g."SUPPLY")
There is no field on this resource that references another resource. Specifically:
- No
supplierIdorpreferredSupplierId— you cannot derive the vendor for a tool from Plex alone. - No
locationIdorwarehouseId— you cannot ask "where is this tool right now?" via this endpoint. - No
partId— supply-items are not linked tomdm/v1/parts. - No
workcenterId— supply-items are not assigned to machines. - No
operationId— supply-items are not linked to operations.
Implication for Datum sync architecture: vendor/supplier data for tools MUST live in Supabase as the source of truth. The Fusion JSON carries vendor and product-id, those get written to the tools table in Supabase, and when build_supply_item_payload() (issue #3) constructs the Plex POST body it uses only the 7 identity fields. Plex never learns who the vendor is, because Plex doesn't model that relationship for tools.
This finding also kills the hypothesis that PO lines could be used as a back-channel for the vendor link — purchasing/v1/purchase-orders-lines returned 404 on 2026-04-09.
Sample supplyItemNumber values captured 2026-04-09 (confirming the
"legacy free-text descriptions, not vendor part numbers" observation
from the 2026-04-08 Decision Log):
"Insert HM90 AXCR 150508 IC28""Screw Indexable Face Mill F75""Tap #8-32 H3 Spiral Point"
Fusion will insert clean vendor part numbers like "990910", so expect essentially zero collision with existing Plex records on first sync.
Endpoint: GET inventory/v1-beta1/inventory-history/item-adjustments
Required params: ItemId (supply-item UUID), StartDate, EndDate (full ISO with Z).
Key finding (probed 2026-04-15 across all 1,109 category="Tools & Inserts" supply-items): the quantity field is delivered pre-signed — positive for additions, negative for removals. You do NOT need to apply sign based on transactionType. Sum quantity directly to get the running balance.
The enumerated transactionType values across 2,005 real records:
| transactionType | records | qty_min | qty_max | quantity sign | interpretation |
|---|---|---|---|---|---|
PO Receipt |
1,479 | 1.0 | 100.0 | always + |
vendor received into stock |
Checkout |
326 | -75.0 | -1.0 | always - |
pulled from crib to production |
Correction |
125 | -6433.0 | 78.0 | either | manual count adjustment, signed |
Check In |
74 | 1.0 | 103.0 | always + |
returned to crib / physical recount up |
null |
1 | 19.0 | 19.0 | — | one record with missing transactionType; treat as data-quality issue, still sum the qty |
Implementation rule: running_balance = sum(r.quantity for r in records). No sign flip, no lookup table. If future records introduce a new transactionType, the pre-signed quantity contract should still hold — but the sync script should log any unknown transactionType values it encounters as a warning for review.
Of Grace's 1,109 tools, 31 (2.8%) have non-empty adjustment history. The remaining 1,078 have never been tracked in Plex inventory at all — a data-quality finding, not an API limitation. Datum distinguishes this in tools.qty_tracked: TRUE = ≥1 record, FALSE = linked but Plex has no history (display as "not tracked"), NULL = not yet checked by sync.
Cutting tools and inserts are inventory/v1/inventory-definitions/supply-items
records with category="Tools & Inserts". This is NOT what the original
Plex_API_Reference.md claimed — that file referenced tooling/v1/tools and
mdm/v1/parts, neither of which works for tooling on this app.
Verified empirically: 1,109 tools/inserts already exist in Plex Grace, mostly
in group="Machining" (1,039) and group="Tool Room" (104). The supply-item
schema is minimal — it tracks vendor part number identity, not geometry, so
the Fusion 360 sync will:
- Read existing tools via
GET inventory/v1/inventory-definitions/supply-items - Filter client-side or via query string to
category=Tools & Inserts - Match by
supplyItemNumber(vendor part number, e.g. Harvey Tool's990910) - Create new supply-items for Fusion tools that don't exist
- Update existing ones
Geometry (DC, OAL, NOF, holder details) stays in Fusion as the source of truth — Plex stores only the identity, description, and group/category.
The 21 MILL workcenter records map directly to physical Brother Speedio
machines via the workcenterCode field (which equals the machine number /
DNC IP last octet):
- Workcenter
879→ Brother Speedio 879 → FTP192.168.25.79 - Workcenter
880→ Brother Speedio 880 → FTP192.168.25.80
The full mill list: 814, 825, 827, 830, 834, 835, 836, 837, 839, 840, 841, 845, 848, 851, 865, 873, 879, 880, DEFLECT.
- 200 — success.
- 401
REQUEST_NOT_AUTHENTICATED— bad credentials OR a recognized namespace your app isn't subscribed to. Same wire response, indistinguishable from outside. - 404
RESOURCE_NOT_FOUND— Plex's gateway has no route at that path. Could mean unknown URL OR subscribed-but-no-resource. Same wire response. - 400 — Plex recognizes the path but the request is malformed (often treats a string as a UUID parameter and fails to parse).
- 403 — never observed in practice on this app.
The 401-vs-404 distinction is not a clean signal on its own. The only reliable way to disambiguate is to compare against a known-good client (Insomnia "Generate Code" output is the gold standard).
mdm/v1/parts and purchasing/v1/purchase-orders silently ignore the
limit query parameter. We learned this empirically — ?limit=1 returned
19.6 MB and 44 MB respectively. The only filter we've verified actually
works is ?status=Active on mdm/v1/parts (reduces 19.6 MB → 7.8 MB).
The typeName filter is also silently ignored. Always assume limit
does nothing and use real filters or accept the full DB pull.
Data flows from Fusion 360 to Plex via the REST API. The tooling/v1/* path namespace referenced in earlier drafts of this document does NOT exist on the Fusion2Plex app — see Section 3 and BRIEFING.md History §3 for the postmortem.
-
REST API Automation (Target State)
- A scheduled script parses the network share Fusion 360 tool library JSON files.
- Extracts
product-id,vendor,description, andgeometry. - Pre-sync validation gate runs via
validate_library.py(spec only, seevalidate_library_spec.md, implementation issue #25). - Pushes payloads to
inventory/v1/inventory-definitions/supply-itemswithcategory="Tools & Inserts",group="Machining", andsupplyItemNumber=<vendor part-id>as the dedup key. Read path verified (1,109 records); write logic in progress (issue #3). - Pushes payloads to
production/v1/production-definitions/workcenters/{id}utilizingpost-process.numberfor turret/pocket placement. Read path verified; write shape TBD (issue #6).
-
CSV Upload System (Historical Fallback)
- Prior to API access being verified, engineering used bulk CSV uploads.
- Sequence: Tool Assembly Upload ➔ Tool Inventory Upload ➔ Tool BOM Upload ➔ Routing Upload.
- The supply-items REST path above is the target state and supersedes this workflow once issues #3, #6, and #7 land.
Outside of the Plex database, NC programs and tool alignments require pushing to physical machines on the floor:
- Brother Speedio (879/880): Native FTP integration (
192.168.25.79,192.168.25.80). Scripts can push programs directly via standard FTP. - Citizen / Tsugami: Connected via Moxa NPort 5150/5250 converting RS-232 to TCP/IP.
- Haas VMCs: Native Ethernet on Sigma 5 boards.
Plex DCS acts as the source-of-truth for NC programs natively; DNC protocols transfer them to machines just-in-time.
- Supplier UUIDs: The
supplierIdin API responses is a UUID, NOT the supplier code (i.e. MSC is notMSC001). You must query the MDM endpoint to resolve vendor names to their internal UUIDs. - PO Filters: Filtering by
typestrings containing spaces (MRO SUPPLIES) requires proper URL encoding (%20). Undetected encoding issues will result in zero-record responses rather than explicit HTTP errors. - PowerShell Curl: Do not use the alias
curlin PowerShell scripts. UseInvoke-RestMethodto guarantee proper header passage and JSON native ingestion.