Skip to content
Sandbox

Validation

The request schema is closed: every validation refusal, its code, its exact message, and the rules that most often surprise (upper case, null, UTC, unknown fields).

View as Markdown

A request body is validated against a closed schema: an unknown field is never ignored, it makes the request fail. This strictness makes the comparison of two requests total: a field silently tolerated would make two requests differ that are identical in the engine's eyes, and would produce an unjustified 409.

Every body validation error returns 400 with the code invalid_request. The detail field says which rule failed; the first failing rule stops validation.

{
  "type": "https://developer.noryo.app/errors/invalid_request",
  "title": "Invalid request",
  "status": 400,
  "code": "invalid_request",
  "detail": "currency must be an ISO 4217 code",
  "request_id": "req_…"
}
JSON
Warning

Build your logic on code, not on detail. The detail text is written for a human, in English, and may be reworded without a version change.

The detail messages

detailCause
body must be a JSON objectThe body is an array, a string, a number or null
unknown field: <name>A field outside the schema is present
descriptor is requireddescriptor missing, empty, whitespace only, or not a string
descriptor is too longMore than 512 characters
amount must be a finite numberamount is not a JSON number (a string "12.40", null)
currency must be an ISO 4217 codeNot exactly three upper-case letters (eur, EURO, null)
occurred_at must be ISO 8601 UTCNot a string shaped YYYY-MM-DDTHH:MM:SS followed by an optional fraction and Z, or a date that does not exist
country must be ISO 3166-1 alpha-2Not exactly two upper-case letters
mcc must be four digitsNot a string of exactly four digits (a number such as 5814 included)
external_transaction_id must be 1..255 charactersEmpty, too long, or not a string

The rules that surprise

Unknown fields are refused. A typo (occured_at) or a field from your internal model sent by mistake returns unknown field: occured_at. Some names are reserved for the server and refused the same way if they appear in the body: status, confidence, billable, units, environment, source, customized, enrichment_id, request_id, among others. The client can never declare what the server computes.

null is not an absence. For an optional field you do not have, omit the key. "amount": null is refused.

Upper case is required. currency and country are compared verbatim; the validator refuses eur rather than correcting it. See Formats.

occurred_at must be in UTC, with Z. 2026-09-06T13:42:00+02:00 is refused: convert it to 2026-09-06T11:42:00Z. A well-shaped date that does not exist (2026-02-30T10:00:00Z, an hour of 24, a second of 60) is refused the same way, with 400.

mcc is a string. Send "5814", not 5814: a numeric 5814 is refused with 400 and mcc must be four digits.

The body of a refresh

The body of POST …/refresh accepts the six economic fields and nothing else. An external_transaction_id is refused there (unknown field: external_transaction_id): the refresh targets the enrichment named in the URL, and accepting a second identifier would let one request designate two transactions. See Refresh.

Refusals that come before body validation

CodeStatusCause
unsupported_media_type415Content-Type missing or other than application/json
request_entity_too_large413Body over 16 KiB
invalid_json400Body that is not valid JSON, or an empty body
invalid_idempotency_key400Malformed Idempotency-Key header

Other route-specific refusals

CodeStatusCause
insufficient_scope403The key lacks enrich:write
identifier_required400Create call with neither Idempotency-Key nor external_transaction_id, or refresh without Idempotency-Key

On create, the order is: body, scope, identifier. On refresh: path identifier, Idempotency-Key, scope, then body. The complete order of checks is described in Requests.

Published on 2026-10-09 10:18 UTC (version 2).

Was this page helpful?