Skip to main content
The Emidat API is built so you can drive it with your own identifiers, which are considered external_id in the following. You attach your identifiers when you create writable entities, filter list endpoints by those same codes, and receive them back on every response. Read-only plants and production processes expose the codes configured in the Emidat UI. References between entities in request bodies use the Emidat id (UUID); you obtain each id from the create response or by resolving your code with GET …?external_id=.

Two kinds of identifier

Defining an external_id

The external_id field is required on create and full update requests, but its value can be null when you have no code. Omitting the field returns 422. For writable entities, you set it when you create the entity and can change it later through its PUT (see below). It is an arbitrary string you choose — no format rules and no length limit — so your existing ERP codes work as-is. Surrounding whitespace is trimmed, so "ART-1 " and "ART-1" are the same code, and a string that is empty or whitespace-only is treated as no code at all, exactly like null, rather than rejected. Plant and production process codes are managed in the Emidat UI instead. Within your manufacturer an external_id is unique among active entities of a given type. It is descriptive only — not an idempotency key. Creating another entity of the same type with a code that already exists is rejected with 409 (see idempotency below).

Changing an external_id

Every writable entity that carries an external_id lets you change it through its PUT endpoint. Plants and production processes are read-only in API v2, so their external_id must be changed in the Emidat UI. PUT is a full replacement of editable fields, so send external_id on every update, carrying either the code you want or the entity’s current one. Passing null clears the code. Immutable parent and type fields may be omitted or echoed from the current resource; a different value returns 422. Uniqueness is re-checked on update. A code held by another active entity of the same type is rejected with 409 err_external_id_exists; re-sending the entity’s own current code is not a conflict and succeeds unchanged. Three consequences worth planning for:
  • The old code stops resolving — GET …?external_id=OLD-CODE returns an empty page.
  • The freed code becomes available again to another entity of the same type.
  • References are unaffected. Other entities point at this one by its Emidat UUID, never by your code, so no BOM entry or *_id field needs updating.

Addressing an entity

One rule, applied to every entity:
  • A path parameter is always the Emidat UUID — GET /v2/product-instances/{id} returns that one instance, or 404.
  • Your external_id is a filter on the collection — GET /v2/product-instances?external_id=SKU-12345 returns a page whose data holds the single instance carrying that code, or is empty if none does. The filter is optional: omitting it lists every instance you can access, one page at a time. See Pagination.
Because uniqueness makes the filtered result at most one item, a code lookup and a full listing are the same endpoint — you never need a separate call shape for the two.

Idempotent creation

external_id is not an idempotency key — idempotency comes solely from the Idempotency-Key header. Idempotency-Key header. Generate a unique key of 1–255 characters (e.g. a UUID) per logical create and send it as Idempotency-Key: <key>. Empty keys or keys longer than 255 characters return 422. A replay carrying the same key returns the original response unchanged (200). Reusing a key with a different request body or create endpoint is rejected with 409 err_idempotency_key_reused. Bodies are compared byte for byte, including JSON whitespace and key order. Keys are scoped to your manufacturer and retained for 6 hours; after that a replay is treated as a new create. Successful responses are retained for six hours from completion; replaying a response does not extend that window. If creation fails before any database commit is attempted, the key is released so a corrected request can reuse it. A concurrent retry receives 409 err_idempotency_in_progress; retry the same request with backoff. Deduplication is best effort and relies on Redis: cache loss can end the window early. Redis unavailability, timeouts, or unreadable cache entries can allow creation without replay protection, so retries can create duplicates. If creation succeeds but caching the response fails or its reservation expires, the API still returns the created entity with 201. Redis waits share a two-second budget per request, excluding creation time. Reservation lookup uses at most one second, leaving time to cache the result or release a failed reservation. Exhausting that budget can leave a pending reservation even when creation succeeded. Incompatible replay-format upgrades start a new cache namespace. Replay protection does not carry across that boundary: retrying a request created under an older version can create another entity. Requests created under the new version are protected normally; old entries expire independently. Once a database commit is attempted, a failed create keeps any surviving reservation because the entity may already exist. This includes a lost commit response, interrupted request, or failed impact calculation or refresh. A failure to cache a successful response or a stopped process can also leave a reservation. These keys can remain reserved for up to six hours; shortening this window could allow a retry to create a duplicate. Reconcile the result (using external_id where available) before trying a new create. Every retry requires current permissions for the endpoint and the plants authorized during the original create. Changes to referenced entities do not invalidate the cached response: create-time reference validation runs only when creating. For product instances, the effective view:processes and edit:processes scopes must match the original request; either gaining or losing one returns 403. Unrelated scope changes do not prevent replay. So the status tells you what happened: 201 = created, 200 = a key replay returned the original entity. The request body is not re-applied on a 200 — to change an entity, use its PUT endpoint. Because external_id is unique per type, a POST carrying an external_id that already exists is rejected with 409 — it is never silently deduplicated.

Referencing other entities

When a request body points at another entity, you reference it by its Emidat id (UUID) — never by your own code. There are two ways to obtain that UUID:
  • For an entity you just created this run, use the id from its create response (the create response always returns it).
  • For an entity you don’t already have the UUID for, resolve it first with GET …?external_id=YOUR-CODE, whose data holds at most one item with its id. This works only for entities that have an external_id; one created without a code can be referenced only via the id returned by its create response, or by walking the unfiltered list.
Then use that id wherever a reference is needed — for example as a BOM entry’s ref: