REST API Reference
Overview
Section titled “Overview”The Eryxon Flow REST API lets an ERP or integration create and update jobs, parts and operations. Eryxon emits committed production changes through signed webhooks. The optional MCP server exposes supported production and configuration actions to approved automation.
Most integration endpoints use Eryxon API keys (ery_live_... or ery_test_...). Admin browser endpoints such as api-export use the signed-in Supabase user session token.
HTTP Status Codes
Section titled “HTTP Status Codes”All API endpoints use proper REST status codes:
Success Codes (2xx)
Section titled “Success Codes (2xx)”| Code | Meaning | Usage |
|---|---|---|
| 200 OK | Success | GET requests, successful PATCH/DELETE |
| 201 Created | Resource created | Successful POST requests |
| 204 No Content | Success with no body | Alternative for DELETE (not currently used) |
Client Error Codes (4xx)
Section titled “Client Error Codes (4xx)”| Code | Meaning | Usage | Example |
|---|---|---|---|
| 400 Bad Request | Malformed request | Invalid JSON, malformed query params | {"error": "Invalid JSON in request body"} |
| 401 Unauthorized | Authentication failed | Invalid/missing API key | {"error": "Invalid or missing API key"} |
| 402 Payment Required | Quota exceeded | Plan limits reached | {"error": "Job limit exceeded (50/50)"} |
| 403 Forbidden | Access denied | Tenant isolation violation | {"error": "Access denied to this resource"} |
| 404 Not Found | Resource doesn’t exist | Job/part/operation ID not found | {"error": "Job with ID xxx not found"} |
| 409 Conflict | Resource conflict or refused transition | Duplicate job_number; a production rule refused the change (message is the rule text) | {"error": {"code": "CONFLICT", "message": "Stop active work before starting another operation"}} |
| 422 Unprocessable Entity | Validation error | Well-formed but invalid data | See Validation Errors below |
| 429 Too Many Requests | Rate limit exceeded | Too many API calls | {"error": "Rate limit exceeded"} |
Server Error Codes (5xx)
Section titled “Server Error Codes (5xx)”| Code | Meaning | Usage |
|---|---|---|
| 500 Internal Server Error | Server error | Unexpected errors, database failures |
Response Formats
Section titled “Response Formats”Success Response
Section titled “Success Response”All successful API responses follow this structure:
{ "success": true, "data": { "records": [ ... ], "pagination": { "limit": 100, "offset": 0, "total": 250 } }}Fields:
success- Alwaystruefor successful responsesdata- The response payload. List endpoints put pagination next to the returned records inside this object.
Error Response
Section titled “Error Response”All error responses follow this structure:
{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": [ { "field": "job_number", "message": "Job number is required", "constraint": "NOT_NULL", "entityType": "job", "entityIndex": 0 } ], "statusCode": 422 }}Fields:
success- Alwaysfalsefor errorserror.code- Machine-readable error codeerror.message- Human-readable summaryerror.details- Array of specific errors (for validation)error.statusCode- HTTP status code
Error Handling Reference
Section titled “Error Handling Reference”Validation Error Format
Section titled “Validation Error Format”When validation fails (422), you’ll receive detailed field-level errors:
{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "✗ 3 validation error(s) in job", "details": [ { "field": "job_number", "message": "Missing required field: job_number", "value": null, "constraint": "NOT_NULL", "entityType": "job", "entityIndex": 0 }, { "field": "parts[0].quantity", "message": "Part quantity must be >= 1", "value": 0, "constraint": "MIN_VALUE", "entityType": "part", "entityIndex": 0 }, { "field": "parts[0].operations[0].cell_id", "message": "Foreign key cell_id references non-existent record: abc-123", "value": "abc-123", "constraint": "FK_CONSTRAINT", "entityType": "operation", "entityIndex": 0 } ], "statusCode": 422 }}Validation Constraint Types
Section titled “Validation Constraint Types”| Constraint | Meaning | Example |
|---|---|---|
NOT_NULL | Required field missing | job_number is required |
FK_CONSTRAINT | Foreign key violation | cell_id references non-existent cell |
FK_REQUIRED | Required foreign key missing | part_id is required |
UUID_FORMAT | Invalid UUID format | id must be a valid UUID |
TYPE_MISMATCH | Wrong data type | Expected number, got string |
MIN_VALUE | Value too small | quantity must be >= 1 |
MAX_VALUE | Value too large | A numeric field exceeds its maximum |
MIN_LENGTH | String too short | job_number must be at least 1 character |
MAX_LENGTH | String too long | job_number must be at most 255 characters |
PATTERN_MISMATCH | Doesn’t match pattern | Invalid format |
ENUM_CONSTRAINT | Invalid enum value | status must be one of: not_started, in_progress, completed |
DATE_FORMAT | Invalid date format | due_date must be ISO 8601 format |
UNIQUE_CONSTRAINT | Duplicate value | Duplicate part numbers found |
CIRCULAR_REFERENCE | Self-referential FK | Part cannot be its own parent |
Common Error Codes
Section titled “Common Error Codes”| Code | HTTP | Description | Example |
|---|---|---|---|
VALIDATION_ERROR | 422 | Field validation failed | Missing required field |
UNAUTHORIZED | 401 | Authentication failed | Invalid API key |
NOT_FOUND | 404 | Resource doesn’t exist in your workshop | Operation not found |
CONFLICT | 409 | Duplicate record or a production rule refused the transition | Previous operation must be completed first |
QUOTA_EXCEEDED | 402 | Plan limit reached | Job limit: 50/50 |
BAD_REQUEST | 400 | Malformed request | Invalid JSON |
FORBIDDEN | 403 | Access denied | Wrong tenant |
METHOD_NOT_ALLOWED | 405 | HTTP method not supported | POST to GET-only endpoint |
INTERNAL_ERROR | 500 | Server error | Database connection failed |
Writable fields and validation
Section titled “Writable fields and validation”The payload reference lists the current writable fields and copy-paste request bodies. Unknown write fields are rejected. UUID references are checked against the authenticated workshop before a service-role write reaches the database.
Authentication
Section titled “Authentication”Most integration endpoints require an API key passed as a Bearer token:
Required header
Authorization: Bearer ery_live_xxxxxxxxxxAPI keys are managed through the admin interface and come in two types:
ery_live_*- Production keysery_test_*- Testing keys
Security: API keys are verified using SHA-256 hashing against the api_keys table and compared with constant-time matching in the edge auth helper. Never expose your API keys in client-side code or public repositories.
Admin-only endpoints used from the web app, such as api-export, use the signed-in Supabase user session token instead of an Eryxon API key.
Core REST APIs
Section titled “Core REST APIs”Jobs API
Section titled “Jobs API”Base URL: /functions/v1/api-jobs
GET - List Jobs
Section titled “GET - List Jobs”GET /api-jobs?status=in_progress&customer=Example&limit=100&offset=0Query Parameters:
status- Filter by status:not_started,in_progress,completed,on_holdcustomer- Filter by customer name (partial match)job_number- Filter by job number (partial match)limit- Results per page (default: 100, max: 1000)offset- Pagination offset (default: 0)
Response:
{ "success": true, "data": { "jobs": [ { "id": "uuid", "job_number": "JOB-2026-001", "customer": "Example Fabrication", "status": "in_progress", "due_date": "2026-12-31", "started_at": "2026-01-15T10:00:00Z", "notes": "Rush order", "parts": [...] } ], "pagination": { "limit": 100, "offset": 0, "total": 150 } }}POST - Create Job
Section titled “POST - Create Job”POST /api-jobsContent-Type: application/json
{ "job_number": "JOB-2026-001", "customer": "Example Fabrication", "due_date": "2026-12-31", "notes": "Rush order", "metadata": {"po_number": "PO-12345"}, "parts": [ { "part_number": "PART-001", "material": "Aluminum 6061", "quantity": 10, "file_paths": ["tenant-id/jobs/JOB-2026-001/part-001.pdf"], "operations": [ { "operation_name": "CNC Milling", "cell_id": "uuid", "estimated_time": 120, "sequence": 1, "notes": "Use 0.5\" end mill" } ] } ]}Response:
{ "success": true, "data": { "job": { "id": "uuid", "job_number": "JOB-2026-001", "parts": [ { "id": "uuid", "part_number": "PART-001", "operations": [ { "id": "uuid", "operation_name": "CNC Milling" } ] } ] } }}Webhook Triggered: job.created
PATCH - Update Job
Section titled “PATCH - Update Job”PATCH /api-jobs?id=<job-id>Content-Type: application/json
{ "status": "completed", "notes": "Finished ahead of schedule"}Allowed Fields: status, customer, due_date, due_date_override, notes, metadata
DELETE - Delete Job
Section titled “DELETE - Delete Job”DELETE /api-jobs?id=<job-id>Parts API
Section titled “Parts API”Base URL: /functions/v1/api-parts
GET - List Parts
Section titled “GET - List Parts”GET /api-parts?job_id=<uuid>&status=in_progress&material=AluminumQuery Parameters:
job_id- Filter by jobpart_number- Filter by part number (partial match)material- Filter by materialstatus- Filter by statuslimit,offset- Pagination
POST - Create Part
Section titled “POST - Create Part”POST /api-parts{ "job_id": "uuid", "part_number": "PART-002", "material": "Steel 4140", "quantity": 5, "parent_part_number": "PART-001", "notes": "Sub-assembly"}Operations API
Section titled “Operations API”Base URL: /functions/v1/api-operations
GET - List Operations
Section titled “GET - List Operations”GET /api-operations?part_id=<uuid>&status=in_progress&cell_id=<uuid>&search=MillQuery Parameters:
part_id- Filter by partcell_id- Filter by cellstatus- Filter by statussearch- Search operation namessort- Sort field:sequence,operation_name,created_at, orstatusorder-ascordesclimit,offset- Pagination
POST - Create Operation
Section titled “POST - Create Operation”POST /api-operations{ "part_id": "uuid", "cell_id": "uuid", "operation_name": "Welding", "estimated_time": 60, "sequence": 2, "notes": "TIG weld only"}Job and part status
Section titled “Job and part status”Job and part status (not_started, in_progress, completed) and their current cell are derived from the operations by the database (refresh_production_job) after every operation transition. There is no endpoint that sets them; drive the operations below and read the job back.
Operation Lifecycle APIs
Section titled “Operation Lifecycle APIs”Base URL: /functions/v1/api-operation-lifecycle
Start Operation
Section titled “Start Operation”POST /api-operation-lifecycle/start?id=<operation-id>&user_id=<user-id>What it does:
- Changes status to
in_progress - Sets
started_attimestamp - Creates time entry with
start_time - Triggers
operation.startedwebhook
Pause Operation
Section titled “Pause Operation”POST /api-operation-lifecycle/pause?id=<operation-id>What it does:
- Changes status to
on_hold - Sets
paused_attimestamp - Ends active time entries (calculates duration)
- Updates
actual_timewith cumulative time - Triggers
operation.pausedwebhook
Resume Operation
Section titled “Resume Operation”POST /api-operation-lifecycle/resume?id=<operation-id>&user_id=<user-id>What it does:
- Changes status to
in_progress - Sets
resumed_attimestamp - Creates new time entry
- Triggers
operation.resumedwebhook
Complete Operation
Section titled “Complete Operation”POST /api-operation-lifecycle/complete?id=<operation-id>What it does:
- Changes status to
completed - Sets
completed_attimestamp - Sets
completion_percentageto 100 - Ends all active time entries
- Calculates final
actual_time - Triggers
operation.completedwebhook
Rules (enforced by transition_operation; a refusal is 409 CONFLICT with the rule as error.message):
- An operator can run one timer at a time:
Stop active work before starting another operation. - An open standstill issue blocks a start:
Resolve the active standstill before restarting. - With the workshop setting Sequential release on:
Previous operation must be completed first. completerequires the operation to be started and its timers stopped;pauseneeds an operation in progress;resumeneeds a paused one.- Unknown operation:
404 NOT_FOUND.
Every response carries previous_status and new_status; the operation object reflects the committed state.
Example Response:
{ "success": true, "data": { "operation": { "id": "uuid", "operation_name": "CNC Milling", "status": "completed", "estimated_time": 120, "actual_time": 135, "completion_percentage": 100, "part": { "part_number": "PART-001", "job": { "job_number": "JOB-2026-001" } } }, "operation_type": "complete", "previous_status": "in_progress", "new_status": "completed", "time_entry_ended": true }}Batch APIs
Section titled “Batch APIs”Batches API
Section titled “Batches API”Base URL: /functions/v1/api-batches
GET - List Batches
Section titled “GET - List Batches”GET /api-batches?status=ready&batch_type=laser_nesting&limit=50Query Parameters:
status- Filter bydraft,ready,in_progress,completed, orcancelledbatch_type- Filter bylaser_nesting,tube_batch,saw_batch,finishing_batch, orgeneralcell_id- Filter by production cellmaterial- Filter by material labelsearch- Searchbatch_numberandmaterial
POST - Create Batch
Section titled “POST - Create Batch”POST /api-batches{ "batch_number": "NEST-2026-001", "batch_type": "laser_nesting", "cell_id": "uuid", "material": "SS304", "thickness_mm": 2, "operation_ids": ["uuid", "uuid"]}cell_id, parent_batch_id, and operation_ids are validated against the authenticated tenant. Operations must not already be assigned to another batch.
PATCH - Update Batch
Section titled “PATCH - Update Batch”PATCH /api-batches?id=<batch-id>{ "notes": "Updated nesting notes", "material": "SS304"}Use api-batch-lifecycle/add-operations to append operations.
Batch Lifecycle API
Section titled “Batch Lifecycle API”Base URL: /functions/v1/api-batch-lifecycle
POST /api-batch-lifecycle/start?id=<batch-id>POST /api-batch-lifecycle/stop?id=<batch-id>POST /api-batch-lifecycle/add-operations?id=<batch-id>Start moves a draft or ready batch to in_progress. Stop completes the batch and distributes active tracked time across operations. Add-operations appends tenant-validated operations while the batch is still draft or ready.
Webhook Triggered: batch.started, batch.completed
NCR APIs
Section titled “NCR APIs”Create NCR (Non-Conformance Report)
Section titled “Create NCR (Non-Conformance Report)”POST /api-issuesContent-Type: application/json
{ "operation_id": "uuid", "title": "Dimensional Out of Tolerance", "description": "Part hole diameter measured 0.505\", spec is 0.500\" ±0.002\"", "severity": "high", "issue_type": "ncr", "ncr_category": "process_error", "affected_quantity": 5, "disposition": "rework", "root_cause": "Tool wear - end mill exceeded replacement interval", "corrective_action": "Replaced tool, re-machined 5 parts", "preventive_action": "Implemented tool life tracking in system", "verification_required": true, "reported_by_id": "uuid"}Response:
{ "success": true, "data": { "issue": { "id": "uuid", "title": "Dimensional Out of Tolerance", "severity": "high", "status": "pending", "issue_type": "ncr", "ncr_category": "process_error", "disposition": "rework", "created_at": "2026-01-15T14:30:00Z" } }}Webhook Triggered: ncr.created
NCR Fields
Section titled “NCR Fields”Required:
operation_id- Where the non-conformance occurredtitle- Short summarydescription- What was observedseverity-low,medium,high,critical
NCR-Specific:
issue_type- Set to"ncr"ncr_category-material_defect,dimensional,surface_finish,process_error,otheraffected_quantity- Number of parts affecteddisposition-scrap,rework,use_as_is,return_to_supplierroot_cause- Root cause analysiscorrective_action- Immediate action takenpreventive_action- Long-term preventionverification_required- Whether a follow-up verification is required
Update NCR
Section titled “Update NCR”PATCH /api-issues?id=<ncr-id>{ "status": "closed", "resolution_notes": "All parts re-machined and inspected. Tool tracking implemented."}List NCRs
Section titled “List NCRs”GET /api-issues?issue_type=ncr&severity=high&status=pendingSubsteps API
Section titled “Substeps API”Base URL: /functions/v1/api-substeps
Add Substep
Section titled “Add Substep”POST /api-substeps{ "operation_id": "uuid", "name": "Measure hole diameter with caliper", "sequence": 1}Response:
{ "success": true, "data": { "substep": { "id": "uuid", "operation_id": "uuid", "name": "Measure hole diameter with caliper", "sequence": 1, "status": "not_started" } }}Webhook Triggered: step.added
Complete Substep
Section titled “Complete Substep”PATCH /api-substeps?id=<substep-id>{ "status": "completed", "completed_at": "2026-09-19T10:30:00Z", "completed_by": "uuid"}Webhook Triggered: step.completed
Webhook Events
Section titled “Webhook Events”Webhooks are configured with api-webhooks (name, HTTPS url, events, secret_key, active). Events are named <entity>.<action> and derived in the database: job, part, operation, batch, issue × created / updated / started / paused / resumed / completed / resolved / deleted where applicable, plus production.reported, production.deleted and sync.*.completed. Every delivery carries X-Eryxon-Event and X-Eryxon-Signature: t=<unix>,v1=<hmac> (HMAC-SHA256 of <t>.<body> with the secret). Delivery records are readable through api-webhook-deliveries.
The full catalogue, payload shape, signature verification and retry behaviour are on Webhooks.
MCP Server Integration
Section titled “MCP Server Integration”The MCP server covers the REST resources and production actions for approved automation. Lifecycle tools use the same database functions as the terminal, while all access is pinned to one workshop. See the MCP Server Reference for the tool list and the MCP Server Setup Guide for deployment.
Rate Limits
Section titled “Rate Limits”Self-hosted: The application does not impose hosted API quotas. You control the infrastructure.
Hosted trial (eryxon.eu): API requests have a daily limit. The current allowance and usage are shown in the app; commercial hosting terms are agreed separately.
When you exceed your rate limit, the API returns 429 Too Many Requests.