Skip to main content

Errors

The error envelope, every error code with its HTTP status and fix, the details shapes, Retry-After, and the order of the checks.

The envelope#

Every refusal is JSON:

json
{ "error": { "code": "connection_not_active", "message": "The connection is paused.", "details": { "status": "paused" } }, "request_id": "…" }
  • Branch on code, never on message: messages are English and may change.
  • request_id equals the NK-Request-Id response header, which every response carries. Quote it when you write to partners@novikvadrat.rs.
  • Responses are application/json; charset=utf-8 with Cache-Control: no-store.

Every code#

Code HTTP You see it when Fix
ingest_disabled 503 Novi Kvadrat switched ingestion off retry later with backoff
body_too_large 413 the body is over 4 MiB (checked first) split the batch
signature_missing 401 a signing header is absent or malformed send all five headers
key_invalid 401 unknown or revoked key use an active key, or register a new one
key_expired 401 the key is past its 365 days register a new key
app_suspended 403 staff suspended the app write to partners@novikvadrat.rs
account_blocked 403 staff suspended or archived your organization write to partners@novikvadrat.rs
ip_allowlist_empty 403 a live app without addresses add addresses to the allowlist
ip_not_allowed 403 the caller is not listed, or its address can't be determined edit the allowlist
digest_mismatch 400 the body changed after hashing hash the exact bytes you send
timestamp_out_of_window 401 your clock is off by more than 300 s sync the clock (NTP)
signature_invalid 401 the base string or the key does not match compare with the test vector
rate_limited 429 over the app's per-minute budget (replays count too) wait Retry-After
nonce_reused 401 a nonce sent twice within 15 minutes a fresh random nonce per request
connection_not_found 404 unknown id, or another app's connection check the id and the key's environment
connection_not_active 409 details.status is pending, paused or revoked wait for the agency, or stop pushing
connection_item_limit 429 over the connection's hourly item budget wait Retry-After, spread pushes
connect_code_invalid 404 an unknown, expired, used or other app's code ask for a new code
agency_not_eligible 422 details.reason is register_number_missing or account_blocked the agency fixes its profile
validation_failed 422 the payload is invalid (details array) fix the fields at the pointers
body_invalid 400 the body is not valid JSON send valid JSON (valid JSON of the wrong shape, such as [], is 422 validation_failed)
duplicate_external_id 422 one external_id twice in a batch send each listing once
batch_too_large 422 more than max_batch_items, more than 50 to validate, more than 5,000 reconcile ids split
idempotency_in_progress 409 the same key while the first request runs (details.batch_id) poll the batch
idempotency_key_reused 422 the same key with a different body, within 7 days a new key per distinct body
mass_removal_requires_confirmation 409 a reconcile would remove too much check, then confirm
listing_not_found 404 no staged listing with that external_id on this connection —
batch_not_found 404 unknown batch id, or another app's batch —
place_not_found 404 unknown place id —
internal_error 500 something failed on our side retry with backoff; quote NK-Request-Id if it persists

You will not normally see connection_exists (409): it is what an agency's own portal answers when that agency is already connected to your app.

Details#

details is present only for some codes:

Code details
validation_failed, duplicate_external_id an array of { pointer, code, message }; pointer is a JSON Pointer into your request body (in a batch, rooted at /listings/{i}) and code a detail code
connection_not_active { status }
agency_not_eligible { reason } — register_number_missing or account_blocked
mass_removal_requires_confirmation { live, missing, threshold_ratio, threshold_min }
idempotency_in_progress { batch_id }

Retry-After#

  • 429 rate_limited carries Retry-After: <seconds to the next minute>.
  • 429 connection_item_limit carries Retry-After: <seconds to the next hour>.

The order of the checks#

Every request runs these checks in order and stops at the first refusal:

# Check Refusal
0 ingestion is on 503 ingest_disabled
1 body ≤ 4 MiB 413 body_too_large
2 the five headers present and well-formed 401 signature_missing
3 the key exists, is active and not expired 401 key_invalid / key_expired
4 the app is active, your organization is not blocked, a live app has an allowlist 403 app_suspended / account_blocked / ip_allowlist_empty
5 the client address is allowed 403 ip_not_allowed
6 digest, timestamp, signature 400 digest_mismatch / 401 timestamp_out_of_window / 401 signature_invalid
7 the app's per-minute budget 429 rate_limited
8 the nonce is new 401 nonce_reused
9 routes that name a connection: it belongs to your app and its state allows the call 404 connection_not_found / 409 connection_not_active
10 listing writes: the connection's hourly item budget 429 connection_item_limit

The rate counter runs before the nonce check, so a replayed request still counts against your budget. Writes need the connection active or certifying; reads (the connection itself and the reconcile view) also admit paused.

Last updated