sailing Foudra MRP
Reference chevron_rightapi/index

The API

JSON over HTTPS on the company's own subdomain, authenticated with an API key as Authorization: Bearer fdr_…. A key's scope limits it: read keys may only GET, write keys may do everything except approve or send a purchase order, approve keys may do those too. Decimals (quantities, money) are strings — JSON numbers are floats and these are not. Errors are {"error": "…"} with a meaningful status.

Reading

Endpoint Returns
GET /api/parts · /api/parts/{id} Parts, paged (?page=&per=); ?category=504 narrows to a category, ?q= searches number, name and description; a part includes on_hand
GET /api/parts/{id}/bom The part's BOM lines: child part, quantity per, reference, routing step
GET /api/parts/{id}/routing The part's routing steps, with totals
GET /api/parts/{id}/images The part's images: id, filename, primary flag
GET /api/suppliers The suppliers
GET /api/parts/{id}/sourcing Who sells the part, as what, for how much
GET /api/demand?status=open&channel= Demand orders with channel, reference, quantity and shipped quantity
GET /api/channels The channels
GET /api/channels/{code}/listings Every part listed on the channel with its channel part number; null means not pushed yet
GET /api/stock One row per part per location
GET /api/sourcing?supplier=&supplier_part_number=&part_number= Which of our parts is a supplier's number, or the reverse. Case-insensitive
GET /api/purchase-orders?status=&number=PO-00004 Orders with supplier, subtotal and a link. number finds one the way people name it
GET /api/purchase-orders/{id} An order with its lines
GET /api/workstations · /api/workstations/{id} Stations with group and status
GET /api/workstations/{id}/next The job this station should do now, with its materials — null unless the station is idle and a step is ready. ?material=<part id> restricts to jobs consuming that part. The response carries the station's current_material and requires_material_change
GET /api/work-orders?status=&number=WO-00023&external_ref= · /api/work-orders/{id} Orders; a single order includes its steps and each step's materials and readiness
GET /api/{parts,workstations,work-orders}/{id}/custom The opaque JSON, or null

Writing

Endpoint Body Does
POST /api/purchase-orders {supplier, currency?, exchange_rate?, expected_at?, external_ref?, notes?, lines:[{supplier_part_number | part_id, quantity, unit_price?}]} Creates a draft; lines by their number are resolved through sourcing. An unknown number fails the whole request naming the line. A human approves
PUT /api/workstations/{id}/status {"status": "idle|busy|blocked|offline", "note"?} Mirrors a machine's state
POST /api/work-order-steps/{id}/start {"workstation_id"?} Takes a step
POST /api/work-order-steps/{id}/complete {"quantity": "9", "consumed"?: [{"part_id", "quantity"}]} Records units made; backflushes materials at BOM standard, or at the reported actual for any part named in consumed; last step receives output
POST /api/work-order-steps/{id}/scrap {"quantity": "9", "consumed"?: […], "note"?} A run that made nothing: materials consumed, no output, the step's count unchanged
POST /api/work-order-steps/{id}/pause A running step with quantity left goes back to pending, progress kept, station released — so the next job is chosen afresh. The print manager does this after every bed
PUT /api/workstations/{id}/material {"part_id": 934} or {"part_id": null} What the station is set up with — a printer's loaded filament
PUT /api/tasks/{source} [{key, priority, message, href, actions?: [{key, label, tone?}]}] Replaces this source's task set wholesale; anything omitted is resolved. Source names are lowercase slugs. A task may offer up to six actions — buttons a human can press; pressing one resolves the task with that key as its outcome, which the source reads back. A task with an outcome is never reopened: use a new key for a new occurrence
GET /api/tasks/{source} This source's open tasks and its recently resolved ones with an outcome — how an integration learns which button was pressed
PUT /api/{kind}/{id}/custom The JSON itself Replaces the opaque blob (64 KB max)
POST /api/parts {category_code, name, part_type, description?, notes?, unit_of_measure?, is_stocked?, minimum_stock?, lead_time_days?, custom?} Creates a part; the stock number is allocated in the category. Returns the part
POST /api/parts/{id}/bom/lines {child_part_id, quantity, reference?, notes?, routing_step_id?} Adds a BOM line; the cycle rules apply
PUT /api/bom/lines/{id} {quantity?, reference?, notes?} Edits a line
POST /api/parts/{id}/routing/steps {name, group_code?, seq?, setup_minutes?, run_minutes_per_unit?, batch_size?, instructions?} Adds a routing step; seq blank takes the next ten
POST /api/parts/{id}/images multipart, field file Uploads an image (12 MB max); the first becomes primary
PUT /api/parts/{id}/sourcing/{supplier} {supplier_part_number, unit_price?, currency?, product_url?, minimum_order_qty?, pack_size?, lead_time_days?, is_preferred?, notes?} Creates the supplier's row for the part (201) or updates it (200); fields not sent keep their value. A part's first source becomes preferred
POST /api/demand {channel, reference, part_id | part_number | channel_part_number, quantity, due_at?, kind?, notes?} Demand received. Idempotent on (channel, reference): a repeat while open updates quantity and date and answers 200; a first arrival answers 201. A closed or cancelled reference is left alone
POST /api/demand/{id}/ship {quantity?, location_id? | location_code?} Demand shipped. Issues that quantity (blank: the rest) from the location, or the part's biggest pile, as a ledger movement referencing the demand; closes the demand once everything has gone. 409 when there is not enough on hand
POST /api/demand/{id}/cancel Demand cancelled
PUT /api/channels/{code}/listings/{part_id} {channel_part_number?, is_listed?, notes?} Lists a part on the channel, or records the channel's number for it

To be told when something happens rather than asking, see webhooks.

For an assistant

What a chat agent needs to keep a person on track: one call for "what needs me", lookups by the names people use, and the buttons. Every item carries a url a human can click.

Endpoint Returns
GET /api/brief Everything that needs attention now: open tasks, workstation states, draft and open work orders, purchase orders awaiting approval, approved but unsent, and overdue, planned orders due to be firmed, transfers needed, shortages, and jobs the capacity check says will finish late. headline says the same in a few sentences, most urgent first
GET /api/tasks Every open task, most urgent first, with its actions
GET /api/planning The planning page: proposed and firmed planned orders (each says because of what), and each workstation group's schedule with its late jobs
GET /api/demand/{id}/progress "Where is my order?": the demand, the part's stock and inbound supply, the planning run's proposals for this order, the work orders making it, and which of those are scheduled late. Supply belongs to the part, not the order: two orders for one part see the same stock

Doing things

Endpoint Body Needs Does
POST /api/tasks/{id}/act {"action": "ready"} operator Presses one of the task's buttons
POST /api/tasks/{id}/snooze {"days": 2} operator Hides the task; a week when no number is given
POST /api/tasks/{id}/resolve operator Closes the task by hand
POST /api/work-orders {part_number | part_id, location, quantity?, external_ref?, due_at?, priority?, notes?, output_location?, release?} planner Raises a work order: a draft, or released when release is true. location is the code of where it draws parts (and puts output, unless output_location differs). external_ref is your name for what the job is about. Send an Idempotency-Key, or a retry raises a second order
POST /api/work-orders/{id}/issue {part_number | part_id, quantity?, note?} operator Draws a part from stores for the order, outside its BOM. 409 when there is not enough
POST /api/work-orders/{id}/release planner Draft to released. Answers with the order and each step's readiness
POST /api/work-orders/{id}/cancel planner Cancels an unfinished order
POST /api/planning/run planner Runs planning now. Answers with the proposals
POST /api/planning/firm {"ids": [12, 13], "location": "BHILL"} planner Proposals into draft work orders and purchase orders. location (or location_id) is needed when a make is among them
POST /api/purchase-orders/{id}/cancel planner Cancels the order
POST /api/purchase-orders/{id}/approve admin, approve key Draft to approved
POST /api/purchase-orders/{id}/send planner, approve key Records the order as sent. Foudra does not transmit it to the supplier

Four rules apply to every one of these:

Every answer has summary, a sentence fit to repeat to a human, data, and acted_as. Refusals: 403 for a key's scope or a user's role, 404, 409 when the thing is not in a state that allows it (the message says why), 422 for a bad action or a reused key.

The print-farm shape

Poll /next?material=<loaded> while idle — anything ready in what is loaded? If not, /next unfiltered says what the top job needs; push a change filament task and go blocked. The human fits the spool, sets Loaded material on the workstation page, taps Ready. Then start; print; complete with the quantity and the actual filament used; status idle again. A failed print is scrap, not a completion of zero. The MRP knows nothing about printers.

API writes are recorded against the user who created the key, unless X-On-Behalf-Of names someone else (see above).