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
Theexternal_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 anexternal_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-CODEreturns 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
*_idfield 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, or404. - Your
external_idis a filter on the collection —GET /v2/product-instances?external_id=SKU-12345returns a page whosedataholds 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.
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 Emidatid (UUID) — never by your own code. There are two ways to obtain that UUID:
- For an entity you just created this run, use the
idfrom 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, whosedataholds at most one item with itsid. This works only for entities that have anexternal_id; one created without a code can be referenced only via theidreturned by its create response, or by walking the unfiltered list.
id wherever a reference is needed — for example as a BOM entry’s
ref: