Before diving into individual resources, it's useful to understand the patterns that are shared across the whole API.
Identifiers
- All resource identifiers are UUID v4 strings (e.g.
3f2b9a1c-4d2e-4b6e-9c1a-6f5b8e2d7c10). - Clients never generate IDs — they are always assigned by the server on creation.
- Path parameters like
{id}and{stageId}always expect a UUID.
Field casing
All JSON request and response bodies use camelCase field names:
{"orderNumber": "PO-2026-0001","supplierId": "…","reachCompliant": true,"createdAt": "2026-04-21T10:00:00.000Z"}
Dates & times
- All timestamps use ISO 8601 format in UTC:
2026-04-21T10:00:00.000Z. - Fields ending in
At(createdAt,updatedAt,verifiedAt,changedAt) are set by the server and are read-only. - Business dates (
orderDate,expectedDelivery,actualDelivery,certificateExpiry) are client-supplied.
Monetary values
Monetary amounts (unitPrice, totalAmount, pricePerUnit) are returned as strings to preserve exact decimal precision:
{ "totalAmount": "1249.50", "currency": "EUR" }
Parse them with a decimal library on the client side — not Number / Float — whenever you need to perform arithmetic on them.
Enum fields
Enum fields are documented per endpoint. The API returns 400 Bad Request when you send a value that is not in the allowed set.
Commonly shared enums:
| Field | Allowed values |
|---|---|
unitOfMeasure | KG, G, T, L, ML, M3, M, CM, MM, M2, PCS, ROLL, BOX, PALLET |
Purchase status | DRAFT, CONFIRMED, IN_TRANSIT, DELIVERED, CANCELLED |
Chemical zdhcLevel | NOT_REGISTERED, LEVEL_1, LEVEL_2, LEVEL_3 |
Textile textileCategory | FIBER, YARN, FABRIC |
Yarn yarnCountUnit | NM, NE, TEX, DTEX, DEN |
Stage transportMode | TRUCK, SHIP, TRAIN, PLANE |
Stage companyType | SUPPLIER, COMPANY |
Country codes
Fields named country, productOriginCountry, rawMaterialOriginCountry expect ISO 3166-1 alpha-2 codes (IT, DE, CN, IN, …).
List endpoints
Collection endpoints use one of three response shapes. The reference page for each resource states the exact shape.
Plain arrays are used by simple catalog endpoints:
[{ "id": "…", "name": "…" },{ "id": "…", "name": "…" }]
Some newer endpoints return items plus paging fields:
{"items": [{ "id": "..." }],"total": 42,"page": 1,"pageSize": 20}
Product materials and product traceability lists return data, meta and facets:
{"data": [{ "id": "..." }],"meta": {"page": 1,"limit": 20,"total": 42,"totalPages": 3,"hasNextPage": true,"hasPreviousPage": false},"facets": {}}
Pagination parameters are 1-based where present (page=1). limit and pageSize maximums are documented per endpoint.
Partial updates
All update endpoints use PATCH with a partial body: include only the fields you want to change. Fields omitted from the request are left untouched.
curl -X PATCH {{BASE_URL}}/api/v1/suppliers/{id} \-H "Content-Type: application/json" \-d '{ "reachCompliant": true }'
Deletion
DELETE endpoints return 204 No Content on success. Delete operations are typically cascading: deleting a supplier removes its certifications; deleting a traceability journey removes its processing stages. Cascading behavior is noted per endpoint.
Custom metadata
Most core entities expose an optional metadata object — an arbitrary JSON map you can use to attach your own keys (ERP codes, external IDs, tags) without schema changes:
{"metadata": {"erpCode": "SUP-042","internalOwner": "procurement-team"}}
Keep metadata small (avoid storing large blobs), and do not store secrets or PII in it.
Document uploads
Document endpoints use two upload patterns:
| Pattern | Content type | Use it when |
|---|---|---|
| Direct multipart upload | multipart/form-data | The endpoint accepts a file field and, sometimes, a category field |
| Presigned upload | application/json then direct storage upload | The file should be uploaded directly to storage before calling complete |
The presigned flow is always:
POST .../presignwith file metadata.PUTthe bytes to the returnedpresignedUrlusing the returned storage instructions.POST .../{documentId}/completewithchecksumSha256.
Document status is PENDING_UPLOAD, READY or FAILED.
Idempotency
Raw-material purchase creation requires an idempotency-key header on:
POST /api/v1/product/materials/{kind}/{id}/purchasesPOST /api/v1/product/materials/{kind}/{id}/purchases/with-documents
Reuse the same key only when retrying the same logical request. A conflicting replay can return 409 Conflict.
Error format
Every error response uses a consistent envelope:
{"code": "VALIDATION_FAILED","message": "category is required","details": { "field": "category" },"requestId": "req_01HXYZ…"}
| Field | Meaning |
|---|---|
code | Stable machine-readable error code — use in your error branching |
message | Human-readable explanation |
details | Optional structured context (varies per error) |
requestId | Correlation id — quote it when opening a support ticket |
Common status codes
| Status | Meaning |
|---|---|
200 OK | Successful GET / PATCH |
201 Created | Successful POST creating a resource |
204 No Content | Successful DELETE |
400 Bad Request | Validation failed — see details |
401 Unauthorized | Missing or invalid credentials |
403 Forbidden | Tenant is not entitled to the resource/module |
404 Not Found | Resource does not exist or does not belong to your tenant |
409 Conflict | Business-rule violation (e.g. invalid purchase status transition) |
429 Too Many Requests | Rate limit hit — back off and retry |
5xx | Server error — retry with exponential backoff |
Always log the requestId returned on failures — it's the fastest way for Conforma support to diagnose an issue.