Skip to content

API Documentation

Eryxon Flow is a 100% API-driven manufacturing execution system. Your ERP system pushes jobs, parts, and tasks via REST API. Eryxon sends completion events back via webhooks. The MCP server enables AI/automation integration.

[!TIP] Looking for automated integrations? See our MCP Server Guide or our API Sync Strategy.

  1. REST Standards & Best Practices
  2. HTTP Status Codes
  3. Response Formats
  4. Error Handling
  5. Validation Rules
  6. Authentication
  7. Core REST APIs
  8. Job Lifecycle APIs
  9. Operation Lifecycle APIs
  10. NCR (Non-Conformance Report) APIs
  11. Webhook Events
  12. MCP Server Integration
  13. Database Indexing

Eryxon Flow API follows REST best practices with comprehensive validation, clear error messages, and proper HTTP semantics.

Proper HTTP Status Codes - Meaningful status codes for every response ✅ Standardized Responses - Consistent success/error formats ✅ Comprehensive Validation - Field-level validation before database operations ✅ Batch Operations - Efficient foreign key validation ✅ Clear Error Messages - Actionable error details ✅ Type Safety - Schema validation for all requests


All API endpoints use proper REST status codes:

CodeMeaningUsage
200 OKSuccessGET requests, successful PATCH/DELETE
201 CreatedResource createdSuccessful POST requests
204 No ContentSuccess with no bodyAlternative for DELETE (not currently used)
CodeMeaningUsageExample
400 Bad RequestMalformed requestInvalid JSON, malformed query params{"error": "Invalid JSON in request body"}
401 UnauthorizedAuthentication failedInvalid/missing API key{"error": "Invalid or missing API key"}
402 Payment RequiredQuota exceededPlan limits reached{"error": "Job limit exceeded (50/50)"}
403 ForbiddenAccess deniedTenant isolation violation{"error": "Access denied to this resource"}
404 Not FoundResource doesn’t existJob/part/operation ID not found{"error": "Job with ID xxx not found"}
409 ConflictResource conflictDuplicate job_number, state violations{"error": "Job number JOB-001 already exists"}
422 Unprocessable EntityValidation errorWell-formed but invalid dataSee Validation Errors below
429 Too Many RequestsRate limit exceededToo many API calls{"error": "Rate limit exceeded"}
CodeMeaningUsage
500 Internal Server ErrorServer errorUnexpected errors, database failures
Request received
├─ Malformed JSON/query params? → 400 Bad Request
├─ Invalid/missing API key? → 401 Unauthorized
├─ Quota exceeded? → 402 Payment Required
├─ Wrong tenant? → 403 Forbidden
├─ Resource not found? → 404 Not Found
├─ Duplicate/conflict? → 409 Conflict
├─ Validation failed? → 422 Unprocessable Entity
├─ Rate limit hit? → 429 Too Many Requests
├─ Server error? → 500 Internal Server Error
└─ Success
├─ Created resource? → 201 Created
└─ Otherwise → 200 OK

All successful API responses follow this structure:

{
"success": true,
"data": { ... },
"meta": {
"pagination": {
"limit": 100,
"offset": 0,
"total": 250
}
}
}

Fields:

  • success - Always true for successful responses
  • data - The response payload (object or array)
  • meta - Optional metadata (pagination, filters, etc.)

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 - Always false for errors
  • error.code - Machine-readable error code
  • error.message - Human-readable summary
  • error.details - Array of specific errors (for validation)
  • error.statusCode - HTTP status code

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
}
}
ConstraintMeaningExample
NOT_NULLRequired field missingjob_number is required
FK_CONSTRAINTForeign key violationcell_id references non-existent cell
FK_REQUIREDRequired foreign key missingpart_id is required
UUID_FORMATInvalid UUID formatid must be a valid UUID
TYPE_MISMATCHWrong data typeExpected number, got string
MIN_VALUEValue too smallquantity must be >= 1
MAX_VALUEValue too largepriority must be <= 100
MIN_LENGTHString too shortjob_number must be at least 1 character
MAX_LENGTHString too longjob_number must be at most 255 characters
PATTERN_MISMATCHDoesn’t match patternInvalid format
ENUM_CONSTRAINTInvalid enum valuestatus must be one of: not_started, in_progress, completed
DATE_FORMATInvalid date formatdue_date must be ISO 8601 format
UNIQUE_CONSTRAINTDuplicate valueDuplicate part numbers found
CIRCULAR_REFERENCESelf-referential FKPart cannot be its own parent
CodeHTTPDescriptionExample
VALIDATION_ERROR422Field validation failedMissing required field
UNAUTHORIZED401Authentication failedInvalid API key
NOT_FOUND404Resource doesn’t existJob ID not found
CONFLICT409Resource conflictDuplicate job_number
QUOTA_EXCEEDED402Plan limit reachedJob limit: 50/50
BAD_REQUEST400Malformed requestInvalid JSON
FORBIDDEN403Access deniedWrong tenant
METHOD_NOT_ALLOWED405HTTP method not supportedPOST to GET-only endpoint
INTERNAL_ERROR500Server errorDatabase connection failed

Required Fields:

  • job_number (string, 1-255 chars, unique per tenant)
  • parts (array, min 1 part)

Optional Fields:

  • customer_name (string, max 255 chars)
  • due_date (ISO 8601 date string)
  • priority (integer, >= 0)
  • current_cell_id (UUID, must exist in cells)
  • status (enum: not_started, in_progress, on_hold, completed)
  • description (string)
  • metadata (JSON object)

Business Rules:

  • Job number must be unique within tenant
  • At least one part required
  • Status transitions must be valid (not_started → in_progress → completed)
  • All foreign keys must reference existing records in same tenant

Required Fields:

  • job_id (UUID, must exist)
  • part_number (string, unique within job)
  • quantity (integer, >= 1)
  • operations (array, min 1 operation)

Optional Fields:

  • material (string)
  • parent_part_id (UUID, must exist in same job, cannot be self)
  • current_cell_id (UUID, must exist)
  • material_id (UUID, must exist)
  • description (string)
  • drawing_url (string)
  • step_file_url (string)

Business Rules:

  • Part number unique within job
  • Parent part must belong to same job
  • Cannot be own parent (circular reference check)
  • Operations must have sequential sequence numbers (1, 2, 3…)

Required Fields:

  • part_id (UUID, must exist)
  • operation_name (string, 1-255 chars)
  • sequence (integer, >= 1, unique within part)

Optional Fields:

  • cell_id (UUID, must exist)
  • assigned_operator_id (UUID, must exist in profiles)
  • estimated_time_minutes (number, >= 0)
  • setup_time_minutes (number, >= 0)
  • instructions (string)
  • status (enum: not_started, in_progress, paused, completed)

Business Rules:

  • Sequence must be positive integer
  • Sequence unique within part
  • All FKs must belong to same tenant

Required Fields:

  • operation_id (UUID, must exist)
  • title (string, 1-255 chars)
  • description (string, min 1 char)

Optional Fields:

  • severity (enum: low, medium, high, critical)
  • status (enum: open, in_progress, resolved, closed)
  • issue_type (enum: general, ncr)
  • reported_by_id (UUID, must exist)
  • resolved_by_id (UUID, must exist)
  • verified_by_id (UUID, must exist)

NCR-Specific Fields (when issue_type = "ncr"):

  • ncr_number (auto-generated if not provided)
  • ncr_category (enum: material, process, equipment, design, supplier, documentation, other)
  • ncr_disposition (enum: use_as_is, rework, repair, scrap, return_to_supplier)
  • root_cause (string)
  • corrective_action (string)
  • preventive_action (string)
  • affected_quantity (integer)
  • verification_required (boolean)

All API endpoints require an API key in the Authorization header:

Authorization: Bearer ery_live_xxxxxxxxxx

API keys are managed through the admin interface and come in two types:

  • ery_live_* - Production keys
  • ery_test_* - Testing keys

Base URL: /functions/v1/api-jobs

Terminal window
GET /api-jobs?status=in_progress&customer=ACME&limit=100&offset=0

Query Parameters:

  • status - Filter by status: not_started, in_progress, completed, on_hold
  • customer - 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-2024-001",
"customer": "ACME Corp",
"status": "in_progress",
"due_date": "2024-12-31",
"started_at": "2024-01-15T10:00:00Z",
"notes": "Rush order",
"parts": [...]
}
],
"pagination": {
"limit": 100,
"offset": 0,
"total": 150
}
}
}
Terminal window
POST /api-jobs
Content-Type: application/json
{
"job_number": "JOB-2024-001",
"customer": "ACME Corp",
"due_date": "2024-12-31",
"notes": "Rush order",
"metadata": {"po_number": "PO-12345"},
"parts": [
{
"part_number": "PART-001",
"material": "Aluminum 6061",
"quantity": 10,
"file_paths": ["s3://drawings/part-001.pdf"],
"operations": [
{
"operation_name": "CNC Milling",
"cell_name": "Mill-01",
"estimated_time": 120,
"sequence": 1,
"notes": "Use 0.5\" end mill"
}
]
}
]
}

Response:

{
"success": true,
"data": {
"job_id": "uuid",
"job_number": "JOB-2024-001",
"parts": [
{
"part_id": "uuid",
"part_number": "PART-001",
"operations": [
{
"operation_id": "uuid",
"operation_name": "CNC Milling"
}
]
}
]
}
}

Webhook Triggered: job.created

Terminal window
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

Terminal window
DELETE /api-jobs?id=<job-id>

Base URL: /functions/v1/api-parts

Terminal window
GET /api-parts?job_id=<uuid>&status=in_progress&material=Aluminum

Query Parameters:

  • job_id - Filter by job
  • part_number - Filter by part number (partial match)
  • material - Filter by material
  • status - Filter by status
  • limit, offset - Pagination
Terminal window
POST /api-parts
{
"job_id": "uuid",
"part_number": "PART-002",
"material": "Steel 4140",
"quantity": 5,
"parent_part_number": "PART-001",
"notes": "Sub-assembly"
}

Base URL: /functions/v1/api-operations

Terminal window
GET /api-operations?part_id=<uuid>&status=in_progress&cell_name=Mill

Query Parameters:

  • part_id - Filter by part
  • job_id - Filter by job (finds parts first, then operations)
  • cell_id - Filter by cell
  • cell_name - Filter by cell name (partial match)
  • status - Filter by status
  • assigned_operator_id - Filter by assigned operator
  • search - Search operation names
  • sort_by - Sort field: sequence, created_at, estimated_time, actual_time, status
  • sort_order - asc or desc
  • include_count - Include total count (true/false)
Terminal window
POST /api-operations
{
"part_id": "uuid",
"cell_id": "uuid",
"operation_name": "Welding",
"estimated_time": 60,
"sequence": 2,
"notes": "TIG weld only"
}

Base URL: /functions/v1/api-job-lifecycle

Terminal window
POST /api-job-lifecycle/start?id=<job-id>

What it does:

  • Changes status from not_started or on_holdin_progress
  • Sets started_at timestamp (first time only)
  • Clears paused_at
  • Triggers job.started webhook
Terminal window
POST /api-job-lifecycle/stop?id=<job-id>

What it does:

  • Changes status from in_progresson_hold
  • Sets paused_at timestamp
  • Triggers job.stopped webhook
Terminal window
POST /api-job-lifecycle/complete?id=<job-id>

What it does:

  • Changes status from in_progresscompleted
  • Sets completed_at timestamp
  • Calculates and stores actual_duration (in minutes)
  • Triggers job.completed webhook
Terminal window
POST /api-job-lifecycle/resume?id=<job-id>

What it does:

  • Changes status from on_holdin_progress
  • Sets resumed_at timestamp
  • Clears paused_at
  • Triggers job.resumed webhook

Example Response:

{
"success": true,
"data": {
"job": {
"id": "uuid",
"job_number": "JOB-2024-001",
"status": "in_progress",
"started_at": "2024-01-15T10:00:00Z",
"completed_at": null
},
"operation": "start",
"previous_status": "not_started",
"new_status": "in_progress"
}
}

Base URL: /functions/v1/api-operation-lifecycle

Terminal window
POST /api-operation-lifecycle/start?id=<operation-id>&user_id=<user-id>

What it does:

  • Changes status to in_progress
  • Sets started_at timestamp
  • Creates time entry with start_time
  • Triggers operation.started webhook
Terminal window
POST /api-operation-lifecycle/pause?id=<operation-id>

What it does:

  • Changes status to on_hold
  • Sets paused_at timestamp
  • Ends active time entries (calculates duration)
  • Updates actual_time with cumulative time
  • Triggers operation.paused webhook
Terminal window
POST /api-operation-lifecycle/resume?id=<operation-id>&user_id=<user-id>

What it does:

  • Changes status to in_progress
  • Sets resumed_at timestamp
  • Creates new time entry
  • Triggers operation.resumed webhook
Terminal window
POST /api-operation-lifecycle/complete?id=<operation-id>

What it does:

  • Changes status to completed
  • Sets completed_at timestamp
  • Sets completion_percentage to 100
  • Ends all active time entries
  • Calculates final actual_time
  • Triggers operation.completed webhook

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-2024-001"
}
}
},
"operation_type": "complete",
"previous_status": "in_progress",
"new_status": "completed",
"time_entry_ended": true
}
}

Terminal window
POST /api-issues
Content-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",
"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",
"ncr_number": "NCR-2024-0001",
"title": "Dimensional Out of Tolerance",
"severity": "high",
"status": "open",
"issue_type": "ncr",
"ncr_category": "process",
"disposition": "rework",
"created_at": "2024-01-15T14:30:00Z"
}
}
}

Webhook Triggered: ncr.created

Required:

  • operation_id - Where the non-conformance occurred
  • title - Short summary
  • severity - low, medium, high, critical

NCR-Specific:

  • issue_type - Set to "ncr" (auto-generates NCR number)
  • ncr_category - material, process, equipment, design, supplier, documentation, other
  • affected_quantity - Number of parts affected
  • disposition - use_as_is, rework, repair, scrap, return_to_supplier
  • root_cause - Root cause analysis
  • corrective_action - Immediate action taken
  • preventive_action - Long-term prevention
  • verification_required - Requires verification after corrective action
  • verified_by_id - User who verified (auto-sets verified_at)
Terminal window
PATCH /api-issues?id=<ncr-id>
{
"status": "resolved",
"resolution_notes": "All parts re-machined and inspected. Tool tracking implemented.",
"verified_by_id": "uuid"
}
Terminal window
GET /api-issues?issue_type=ncr&severity=high&status=open

Base URL: /functions/v1/api-substeps

Terminal window
POST /api-substeps
{
"operation_id": "uuid",
"description": "Measure hole diameter with caliper",
"sequence": 1
}

Response:

{
"success": true,
"data": {
"substep": {
"id": "uuid",
"operation_id": "uuid",
"description": "Measure hole diameter with caliper",
"sequence": 1,
"completed": false
}
}
}

Webhook Triggered: step.added

Terminal window
PATCH /api-substeps?id=<substep-id>
{
"completed": true
}

Webhook Triggered: step.completed


Eryxon automatically sends webhooks to registered endpoints for the following events:

  • job.created - New job created via API
  • job.started - Job started
  • job.stopped - Job paused/stopped
  • job.resumed - Job resumed from pause
  • job.completed - Job completed
  • job.updated - Job fields updated
  • part.created - New part created
  • part.updated - Part fields updated
  • part.started - Work started on part
  • part.completed - Part completed
  • operation.started - Operation started (operator begins work)
  • operation.paused - Operation paused
  • operation.resumed - Operation resumed
  • operation.completed - Operation completed
  • issue.created - General issue created
  • ncr.created - NCR (Non-Conformance Report) created
  • ncr.verified - NCR corrective action verified
  • step.added - Substep added to operation
  • step.completed - Substep marked as completed
{
"event_type": "job.completed",
"timestamp": "2024-01-15T16:30:00Z",
"tenant_id": "uuid",
"data": {
"job_id": "uuid",
"job_number": "JOB-2024-001",
"customer": "ACME Corp",
"previous_status": "in_progress",
"new_status": "completed",
"started_at": "2024-01-15T10:00:00Z",
"completed_at": "2024-01-15T16:30:00Z",
"actual_duration": 390
}
}

All webhooks include HMAC-SHA256 signatures for authenticity verification:

Headers:

  • X-Eryxon-Signature - HMAC-SHA256 signature of the payload
  • X-Eryxon-Event - Event type (e.g., job.completed)
  • Content-Type: application/json

Verification (Node.js example):

const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const digest = hmac.update(payload).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(digest)
);
}

Create Webhook:

Terminal window
POST /api-webhooks
{
"url": "https://your-erp.com/webhooks/eryxon",
"events": ["job.created", "job.completed", "ncr.created"],
"active": true
}

List Webhooks:

Terminal window
GET /api-webhooks?event_type=job.completed&active=true

Update Webhook:

Terminal window
PATCH /api-webhooks?id=<webhook-id>
{
"active": false
}

The Model Context Protocol (MCP) server enables AI assistants and automation tools to interact with Eryxon Flow.

  • fetch_jobs - List jobs with filters
  • fetch_parts - List parts with filters
  • fetch_tasks - List tasks with filters
  • fetch_issues - List issues with filters
  • fetch_ncrs - List NCRs with filters
  • get_dashboard_stats - Get aggregated statistics
  • start_job - Start a job
  • stop_job - Stop/pause a job
  • complete_job - Complete a job
  • resume_job - Resume a paused job
  • update_job - Update job fields
  • create_job - Create new job
  • start_operation - Start an operation
  • pause_operation - Pause an operation
  • complete_operation - Complete an operation
  • update_operation - Update operation fields
  • create_ncr - Create Non-Conformance Report
  • fetch_ncrs - List NCRs with filtering
  • add_substep - Add substep to operation
  • complete_substep - Mark substep as completed
Terminal window
cd mcp-server
npm install
npm run build
export SUPABASE_URL="https://your-project.supabase.co"
export SUPABASE_SERVICE_KEY="your-service-key"
npm start
// Using Claude Desktop or other MCP client
{
"name": "start_job",
"arguments": {
"id": "uuid-of-job"
}
}
// Response
{
"content": [
{
"type": "text",
"text": "Job started successfully:\n{\n \"id\": \"uuid\",\n \"status\": \"in_progress\",\n \"started_at\": \"2024-01-15T10:00:00Z\"\n}"
}
]
}

All critical queries are optimized with database indexes:

Jobs:

  • (tenant_id, status) - Status filtering
  • (tenant_id, created_at DESC) - Recent jobs
  • (tenant_id, due_date) - Upcoming due dates
  • (tenant_id, customer) - Customer filtering
  • Full-text search on job_number, customer, notes

Parts:

  • (tenant_id, status) - Status filtering
  • (tenant_id, job_id) - Parts by job
  • (tenant_id, material) - Material filtering
  • (parent_part_id) - Sub-assemblies
  • Full-text search on part_number, material, notes

Operations:

  • (tenant_id, status) - Status filtering
  • (tenant_id, part_id) - Operations by part
  • (tenant_id, cell_id) - Operations by cell
  • (part_id, sequence) - Operation sequence
  • (assigned_operator_id) - Operator assignments
  • Full-text search on operation_name, notes

Issues/NCRs:

  • (tenant_id, status) - Status filtering
  • (tenant_id, severity) - Severity filtering
  • (tenant_id, issue_type) - NCR filtering
  • (tenant_id, ncr_number) - Unique NCR number
  • (operation_id) - Issues by operation
  • Full-text search on description, resolution_notes

Time Entries:

  • (operation_id) - Entries by operation
  • (user_id) - Entries by user
  • (operation_id, end_time) - Active entries

Webhooks:

  • (tenant_id, active) - Active webhooks
  • (webhook_id, created_at DESC) - Webhook logs
  • (event_type, created_at DESC) - Event filtering

All API endpoints return consistent error responses:

{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Job ID is required in query string (?id=xxx)"
}
}

Common Error Codes:

  • UNAUTHORIZED - Invalid or missing API key
  • VALIDATION_ERROR - Missing or invalid request parameters
  • NOT_FOUND - Resource not found
  • INVALID_STATE_TRANSITION - Illegal status change (e.g., completing a not-started job)
  • CONFLICT - Resource conflict (e.g., duplicate job number)
  • INTERNAL_ERROR - Server error