Skip to content

Inbound Work Orders

Endpoint

text POST /api/v1/inbound/work-orders POST /api/v1/inbound/work-orders/test

POST /api/v1/inbound/work-orders accepts ERP work-order wrapper payloads, validates them, and persists accepted data to MongoDB. Behavior includes product upsert, project/production/stage merge, machine and employee resolution, and project-level partial processing. It is not validation-only.

POST /api/v1/inbound/work-orders/test is a public format-validation endpoint. It checks JSON/XML syntax and the request contract only. It does not require a Bearer token, does not use Idempotency-Key, does not read or write MongoDB, and does not create a request archive record.

The live endpoint’s authentication, idempotency, archive, and persistence behavior is unchanged.

See Work-Order Persistence for live merge and resolution rules.

Authentication

Live endpoint

When AUTH_ENABLED=true, live requests must include a JWT Bearer access token:

http Authorization: Bearer <access_token>

Obtain the token from POST /api/v1/auth/token. See Authentication.

When AUTH_ENABLED=false, the live endpoint remains open without a Bearer token for local development, but persistence still writes to MongoDB.

Missing tokens return 401 AUTHENTICATION_REQUIRED. Invalid or expired tokens return 401 INVALID_TOKEN. Authentication error responses honor JSON and XML Accept negotiation.

Test endpoint

POST /api/v1/inbound/work-orders/test is public. Authentication is not required even when AUTH_ENABLED=true. A token may be sent but is ignored for authorization.

Tenant fields (company_id, organization_id) on the live endpoint are taken from the request root or from configured environment defaults. They are not derived from token claims in this release. The test endpoint does not perform tenant environment fallback or database tenant checks; when present, tenant fields are validated only as contract strings (ObjectID hex format).

Contract references in the repository:

Content types

Request Content-Type

Value Description
application/json JSON payload
application/xml XML payload
text/xml XML payload

Parameters such as charset=utf-8 are supported.

Unsupported request Content-Type values return 415 Unsupported Media Type.

Response Accept

Value Description
application/json JSON response
application/xml XML response
text/xml XML response
*/* Match request format

When Accept is omitted, the response uses the same format as the request Content-Type.

Unsupported Accept values return 406 Not Acceptable.

Tenant fields

Root fields company_id and organization_id are an optional pair in the request body.

Situation Result
Both request values present Use the request pair
Neither request value present Use DEFAULT_COMPANY_ID and DEFAULT_ORGANIZATION_ID when both environment values are configured
Exactly one request value present 400 TENANT_CONTEXT_INCOMPLETE (environment defaults are not applied)
Neither request nor configured defaults 400 TENANT_CONTEXT_REQUIRED
Invalid ObjectID in a provided value 400 INVALID_TENANT_CONTEXT

When both fields are present in the request, each must be a 24-character hexadecimal MongoDB ObjectID string (^[A-Fa-f0-9]{24}$).

XML payloads follow the same rules. The XSD marks both elements as optional (minOccurs="0") because tenant resolution may use environment defaults when both are omitted.

See Configuration for DEFAULT_* startup validation.

Request examples

JSON

json { "company_id": "507f1f77bcf86cd799439011", "organization_id": "507f191e810c19729de860ea", "projects": [ { "project_name": "Proje Adı", "project_code": "PROJE-001", "start_date": "2026-08-06T08:00:00Z", "estimated_end_date": "2026-08-30T17:00:00Z", "description": "Proje açıklaması", "productions": [ { "production_name": "Üretim Emri Adı", "production_code": "URETIM-001", "planned_start_datetime": "2026-08-06T08:00:00Z", "planned_finish_datetime": "2026-08-10T17:00:00Z", "remarks": "Üretim emri açıklaması", "production_stages": [ { "production_stage_name": "Kesim", "production_stage_code": "ASAMA-001", "stage_number": 1, "stage_type": "yeni", "planning_start_time": "2026-08-06T08:00:00Z", "planning_end_time": "2026-08-07T17:00:00Z", "machines": [ { "machine_code": "MACHINE-001", "machine_name": "CNC Makinesi", "output_products": [ { "product_name": "Makine Çıktı Ürünü", "product_code": "URUN-001", "category": "Yarı Mamul", "unit": "adet", "quantity": 30, "ideal_cycle_time": 1700 } ] } ], "employees": [ { "username": "operator.user" } ], "input_products": [ { "product_name": "Aşama Girdi Ürünü", "product_code": "HAM-001", "category": "Hammadde", "unit": "adet", "quantity": 30, "ideal_cycle_time": 0 } ], "output_products": [ { "product_name": "Aşama Çıktı Ürünü", "product_code": "YARI-001", "category": "Yarı Mamul", "unit": "adet", "quantity": 30, "ideal_cycle_time": 1700 } ] } ], "input_products": [ { "product_name": "Üretim Girdi Ürünü", "product_code": "HAM-001", "category": "Hammadde", "unit": "adet", "quantity": 30, "ideal_cycle_time": 0 } ], "final_products": [ { "product_name": "Nihai Ürün", "product_code": "NIHAI-001", "category": "Nihai Mamul", "unit": "adet", "quantity": 30, "ideal_cycle_time": 1700 } ] } ] } ] }

XML

```xml

507f1f77bcf86cd799439011 507f191e810c19729de860ea Proje Adı PROJE-001 2026-08-06T08:00:00Z 2026-08-30T17:00:00Z Üretim Emri Adı URETIM-001 2026-08-06T08:00:00Z 2026-08-10T17:00:00Z Kesim ASAMA-001 1 yeni 2026-08-06T08:00:00Z 2026-08-07T17:00:00Z Aşama Girdi Ürünü HAM-001 Hammadde adet 30 0 Aşama Çıktı Ürünü YARI-001 Yarı Mamul adet 30 1700 Üretim Girdi Ürünü HAM-001 Hammadde adet 30 0 Nihai Ürün NIHAI-001 Nihai Mamul adet 30 1700 ```

Response statuses

HTTP data.status / error code When
200 success All received projects were processed
200 partial_success At least one project processed; one or more projects, productions, or stages skipped
409 NO_PROJECT_PROCESSED Structurally valid request, but no project could be persisted
409 INTEGRATION_ACTOR_USER_NOT_FOUND Configured integration actor user does not exist
409 INTEGRATION_ACTOR_USER_INACTIVE Configured integration actor user is not active
409 INTEGRATION_ACTOR_TENANT_MISMATCH Configured actor does not belong to the request tenant
503 DATABASE_UNAVAILABLE MongoDB connection, server selection, or timeout failure

JSON success / partial success

json { "success": true, "request_id": "uuid", "data": { "message": "Work-order payload processed.", "status": "partial_success", "summary": { "received_projects": 3, "processed_projects": 2, "skipped_projects": 1, "created_projects": 1, "updated_projects": 1, "received_productions": 4, "processed_productions": 3, "skipped_productions": 1, "received_stages": 6, "processed_stages": 5, "skipped_stages": 1, "product_occurrences": 20, "created_products": 2, "updated_products": 4, "resolved_machines": 3, "resolved_employees": 2, "unresolved_employees": 1 }, "results": [ { "project_code": "PRJ-001", "status": "processed", "action": "created", "warnings": [] }, { "project_code": "PRJ-002", "status": "skipped", "action": "none", "warnings": [ { "code": "PROJECT_NAME_CONFLICT", "project_code": "PRJ-002", "message": "The project_code exists with a different project_name." } ] } ] }, "error": null }

Per-project result fields

Field Values
status processed, processed_with_warnings, skipped
action created, updated, none
warnings Array of warning objects (code, optional context fields, message)

Persistence behavior (summary)

Area Behavior
Products Upsert by company_id + organization_id + product_code
Projects Create or merge by tenant + project_code; name mismatch skips the project
Productions / stages Merge by code within the parent; omitted existing children are preserved
Machines Read-only catalog resolution; missing machine skips the whole stage
Employees Read-only user resolution; missing username skips only that assignment
Partial processing Projects are independent; one skip does not stop others

Operational fields such as production_stage_operation, stage files, and production files are preserved on update.

Collection tenant type differences

Existing MongoDB schemas differ by collection and are preserved:

Collection company_id / organization_id type
projects, machines ObjectID
products, users string

Integration actor

Audit fields use INTEGRATION_ACTOR_USER_ID (24-character ObjectID hex of an existing active user). It must reference a real MongoDB user — never a generated random ObjectID. Config load checks required + format only. Live POST additionally checks exists, active, and tenant match. Responses never include the actor ObjectID. See Configuration and Error Codes.

Idempotency and archive

Live endpoint requires Idempotency-Key when idempotency is enabled/required. Same key + same canonical payload replays the stored logical response without re-running ApplyPlan. Same key + different payload returns 409 IDEMPOTENCY_KEY_CONFLICT.

Every live attempt is archived in api_request_archive when archive is enabled. The test endpoint writes business collections: 0, archive: 0, and never writes api_idempotency_records.

See:

Standalone MongoDB

Persistence is compatible with standalone MongoDB. Multi-document transactions are not required. Product master writes are independent of the project document write; if a project write fails after products were upserted, those product rows may remain.

Required fields

Level Required fields
Request company_id, organization_id, projects (min 1 item)
Project project_name, project_code, start_date, estimated_end_date, productions
Production production_name, production_code, planned_start_datetime, planned_finish_datetime, production_stages, input_products, final_products
Production stage production_stage_name, production_stage_code, stage_number, stage_type, planning_start_time, planning_end_time, input_products, output_products
Product product_name, product_code, category, unit, quantity, ideal_cycle_time
Machine (when present) machine_code, output_products (min 1 item)
Employee (when present) username

Optional fields

Field Notes
project.description Optional string
production.remarks Optional string
production_stage.description Optional string
machine.machine_name Optional string (informational; canonical name comes from MongoDB)
production_stage.machines Optional array; may be omitted or empty
production_stage.employees Optional array; may be omitted or empty

Array rules

Required arrays must contain at least one item:

  • projects, productions, production_stages
  • production.input_products, production.final_products
  • production_stage.input_products, production_stage.output_products
  • machine.output_products when a machine entry is present

machines and employees may be omitted or sent as empty arrays.

Single objects are not coerced into arrays; missing or empty required arrays return validation errors.

Within one request, duplicate structural codes are rejected (DUPLICATE_PROJECT_CODE, DUPLICATE_PRODUCTION_CODE, DUPLICATE_PRODUCTION_STAGE_CODE, DUPLICATE_MACHINE_CODE, DUPLICATE_EMPLOYEE_USERNAME). Repeated product_code across occurrences is allowed.

UTC Z date rule

All date-time fields must be RFC 3339 UTC values ending with Z.

Accepted examples:

  • 2026-08-06T08:30:00Z
  • 2026-08-06T08:30:00.123Z

Rejected examples:

  • 2026-08-06T11:30:00+03:00
  • 2026-08-06T08:30:00+00:00
  • 2026-08-06 08:30:00

Date ordering checks:

  • estimated_end_date must not be before start_date
  • planned_finish_datetime must not be before planned_start_datetime
  • planning_end_time must not be before planning_start_time

Equal start/end timestamps are allowed.

Integer rules

Field Rule
stage_number Integer, must be > 0
quantity Integer, must be >= 0
ideal_cycle_time Integer, must be >= 0

JSON floats, strings, booleans, and null values are rejected for integer fields.

stage_type normalization

Accepted values after normalization:

  • yeni
  • revize

Case variants such as YENİ or Revize are normalized using Turkish casing rules. Other values are rejected.

Strict unknown fields

  • JSON: unknown properties are rejected (DisallowUnknownFields).
  • XML: unknown elements are rejected with a field path in error details.
  • XML DOCTYPE/DTD declarations are rejected.

Work-order test endpoint

text POST /api/v1/inbound/work-orders/test

Public format-validation endpoint for the same JSON/XML request contract as the live endpoint.

Behavior Detail
Authentication Not required (public even when AUTH_ENABLED=true)
Idempotency-Key Not required; ignored if sent
MongoDB No reads and no writes
Request archive No archive document
Business checks No machine/employee/project existence checks
Formats Same Content-Type / Accept / body limit / strict unknown-field rules
Response status: valid, write_performed: false, database_accessed: false, archive_written: false

HTTP status meanings for format failures: 200 / 400 / 406 / 413 / 415. This endpoint does not return UNAUTHORIZED, idempotency archive/store codes, REQUEST_ARCHIVE_UNAVAILABLE, MACHINE_NOT_FOUND, EMPLOYEE_NOT_FOUND, or PROJECT_CONFLICT.

A 200 response means the payload format and contract are valid. It does not mean a later live write will succeed.

Postman / curl JSON example

http POST /api/v1/inbound/work-orders/test Content-Type: application/json Accept: application/json

No Authorization or Idempotency-Key header is required. Use the same JSON body shape as the live request examples above.

Postman / curl XML example

http POST /api/v1/inbound/work-orders/test Content-Type: application/xml Accept: application/xml

JSON test success example

json { "success": true, "request_id": "uuid", "data": { "message": "Work-order payload format is valid.", "status": "valid", "content_type": "application/json", "write_performed": false, "database_accessed": false, "archive_written": false }, "error": null }

Body limit

Configure with INBOUND_MAX_BODY_BYTES (default: 10 MiB / 10485760 bytes). Exceeding the limit returns 413 Payload Too Large.

Contract validation error details

JSON/XML syntax errors and contract errors are classified separately. Contract details always include a full nested path with array indexes so an ERP operator can locate the exact field, for example:

projects[0].productions[0].production_stages[0].machines[0].output_products[0].ideal_cycle_time

Invalid integer type (not malformed JSON)

json { "success": false, "request_id": "uuid", "data": null, "error": { "code": "INVALID_FIELD_TYPE", "message": "Request contains a field with an invalid type.", "details": [ { "field": "projects[0].productions[0].production_stages[0].machines[0].output_products[0].ideal_cycle_time", "code": "INVALID_TYPE", "message": "Field 'ideal_cycle_time' must be an integer.", "expected_type": "integer", "actual_type": "string", "received_value": "asd" } ] } }

Sending "ideal_cycle_time": "asd" must not return MALFORMED_JSON.

Other contract examples

Scenario Top-level code Detail code
True JSON syntax break ({"projects":[) MALFORMED_JSON JSON_SYNTAX_ERROR (+ line/column/byte_offset)
Unknown nested field UNKNOWN_FIELD UNKNOWN_FIELD
Missing required field REQUIRED_FIELD_MISSING REQUIRED
Invalid stage_type VALIDATION_ERROR INVALID_ENUM_VALUE
Invalid RFC3339 date VALIDATION_ERROR INVALID_DATETIME
Negative quantity VALIDATION_ERROR VALUE_OUT_OF_RANGE

Multiple contract errors in one syntactically valid body are returned together under VALIDATION_ERROR (capped at 100 details). received_value is limited to safe primitives and long strings are truncated.

Error and warning codes

Code HTTP Meaning
MALFORMED_JSON 400 JSON syntax cannot be parsed
MALFORMED_XML 400 XML syntax cannot be parsed or DOCTYPE rejected
INVALID_FIELD_TYPE 400 Field type mismatch (for example string instead of integer)
REQUIRED_FIELD_MISSING 400 One or more required fields are missing
UNKNOWN_FIELD 400 Unknown JSON property or XML element
VALIDATION_ERROR 400 One or more contract validation errors
VALIDATION_FAILED 400 Legacy alias retained for some validation failures
DUPLICATE_* 400 Duplicate structural codes inside one request (see Error Codes)
AUTHENTICATION_REQUIRED / INVALID_TOKEN 401 Auth failures when enabled (live endpoint)
NOT_ACCEPTABLE 406 Unsupported Accept header
NO_PROJECT_PROCESSED 409 No project could be persisted (live)
INTEGRATION_ACTOR_USER_NOT_FOUND 409 Configured actor user was not found (live)
INTEGRATION_ACTOR_USER_INACTIVE 409 Configured actor user is not active (live)
INTEGRATION_ACTOR_TENANT_MISMATCH 409 Configured actor does not belong to the request tenant (live)
PAYLOAD_TOO_LARGE 413 Body exceeds inbound limit
UNSUPPORTED_MEDIA_TYPE 415 Unsupported Content-Type
INTERNAL_ERROR 500 Unexpected server error
DATABASE_UNAVAILABLE 503 MongoDB unavailable (live)

Result/warning codes (in live results[].warnings or 409 details): PROJECT_NAME_CONFLICT, MACHINE_NOT_FOUND, EMPLOYEE_NOT_FOUND, ALL_STAGES_SKIPPED, ALL_PRODUCTIONS_SKIPPED.

See Error Codes for the full list.