Skip to content
Sandbox

The two identifiers

Idempotency-Key protects the transport, external_transaction_id protects the invoice. What each one does, why at least one is required, and why you should send both.

View as Markdown

Noryo Merchant API recognises two identifiers. They are not the same thing, and confusing them is the most expensive mistake available on this API.

Idempotency-Keyexternal_transaction_id
WhereRequest headerBody field (create only)
What it protectsThe transport: a network retryThe economic identity: the same transaction
Lifetime24 hoursNo expiry
Same value, same requestReplays the memorised response, Noryo-Idempotency-Replayed: trueReturns the transaction's current result, deduplicated: true, not billed
Same value, different request409 idempotency_key_reuse409 transaction_id_conflict
ScopeWorkspace and environmentWorkspace and environment

At least one is required

On POST /merchant/v1/enrichments, a request with neither Idempotency-Key nor external_transaction_id is refused:

{
  "type": "https://developer.noryo.app/errors/identifier_required",
  "title": "Identifier required",
  "status": 400,
  "code": "identifier_required",
  "detail": "provide an Idempotency-Key or an external_transaction_id",
  "request_id": "req_…"
}
JSON

Without either, the deduplication branch would be skipped entirely, and two identical calls would become two billed events that nothing can tell apart. The API refuses rather than let that happen. On refresh, the Idempotency-Key is required in every case.

Send both

The key protects the retry, the transaction identifier protects the invoice. They cover different failures:

  • A retry after a timeout, a few seconds later: the Idempotency-Key returns the memorised body, byte for byte.
  • The same statement re-imported three weeks later: the idempotency key expired long ago, but the external_transaction_id recognises the transaction and returns its result without billing it again.
  • Refreshing a result: only possible if the original call carried an external_transaction_id. See Refresh.

external_transaction_id: economic deduplication

The external_transaction_id is your identifier for the transaction: a string of 1 to 255 characters, opaque to Noryo and compared verbatim. The first call carrying it, in a given workspace and environment, records the transaction and its economic input.

Later call with the same identifierResponse
Same economic input200 with the transaction's current result: deduplicated: true, deduplicated_by: "external_transaction_id", billable: false, units: 0
Different economic input409 transaction_id_conflict
Result content expired (over 90 days)410 result_content_expired. See Retention and 410.

The "current result" is the transaction's first result, replaced only by a refresh that succeeds (refresh_recalculated). A partial or not_found refresh does not replace it.

Two details:

  • "Same input" is judged on the canonical form of the six economic fields, not on bytes: a difference of case or whitespace in descriptor is not a difference, a difference of amount is. See Formats.
  • An engine failure does not record the transaction. A 503 engine_error does not claim the identifier: the next retry is processed as a first call.

Idempotency-Key: transport protection

1 to 255 bytes of visible ASCII (0x21 to 0x7E). Noryo applies no normalisation: no trimming, no case folding, no Unicode processing. KEY-1 and key-1 are two different keys. The only whitespace handling is what the HTTP stack itself strips around any header value, which is outside our control and outside yours.

Replay window: 24 hours. Inside it, the same key with the same request returns the memorised response, with Noryo-Idempotency-Replayed: true. The detailed cases (concurrency, refusals, expiry) are in Idempotency.

Published on 2026-10-08 21:04 UTC (version 1).

Was this page helpful?