Every error response is a RFC 9457 Problem Details document at Content-Type: application/problem+json. The HTTP status code carries the broad category; the code field carries the specific, machine-readable reason.
Status codes
Code
When it happens
400
Malformed request - bad JSON, missing required field, unknown enum value
401
Missing or invalid Authorization: Bearer … header
403
Caller lacks the permission this endpoint requires
404
Resource doesn't exist (or belongs to a different organization)
409
Conflict - duplicate, version mismatch, invalid state transition
422
Validation passed at the schema layer but business rules rejected the request
429
Per-organization rate limit hit - see headers + retry below
500
Internal server error - open an issue with the request_id
503
A downstream dependency (payment gateway, e-invoice service, identity provider) is unavailable
URI identifying the error class. Stable; safe to use for documentation links.
title
One-line human summary.
status
The HTTP status code, repeated in the body (RFC 9457 requirement).
detail
Free-form explanation of this occurrence - usually safe to surface to end users.
instance
The URI of the request that produced the error.
code
Switch on this. Short SCREAMING_SNAKE identifier, stable across versions.
request_id
Correlation ID of the failed request, the same value as the X-Request-Id response header - include it when reporting an issue.
Some errors carry extra top-level fields (RFC 9457 §3.2 extension members). For example, a conflict can name the record it collided with in existing_id, or the entity in entity_type and entity_id; a refused invoice draft lists every reason in refusals. Treat unknown fields as ignorable.
Always switch on code, not on title or detail. The code is part of the API contract; the prose can change without notice.
Validation errors
Field-level validation errors carry a fields array so a UI can highlight every bad field at once:
Code
{ "type": "https://api.kontier.eu/errors/VALIDATION", "title": "Validation Error", "status": 400, "detail": "The request input is invalid.", "code": "VALIDATION", "request_id": "01HKZ…", "fields": [ { "field": "email", "code": "REQUIRED", "message": "email is required" }, { "field": "preferred_currency", "code": "INVALID_FORMAT", "message": "preferred_currency must be a three-letter uppercase ISO 4217 code (got \"eur\")" } ]}
Each entry carries field, code, and message. message is a developer-readable explanation, not display text. Many entries also carry a reason (missing, format, range, not_allowed, too_long, too_short, mismatch) and the typed parameters that go with it (min/max, min_length/max_length, example), so your localised UI can word its own sentence instead of showing the English message. Some validators also include a params array of positional arguments.
Common error codes
Every endpoint documents the specific error codes it can return in the API reference. The generic codes below apply across most endpoints:
Code
Status
Meaning
VALIDATION
400
Schema-level field validation failed (fields array)
Resource is in a state that blocks the operation, optimistic-concurrency mismatch, or an Idempotency-Key whose first request is still being processed or that was used with a different body (the detail text disambiguates)
DUPLICATE
409
Natural key collision (e.g. an existing email on customer create)
INSUFFICIENT_BALANCE
422
Wallet debit / hold would exceed the available amount
RATE_LIMIT
429
Per-organization request budget exhausted
Domain-specific codes are grouped by area in the full catalog below. Each endpoint's error responses in the API reference list the subset it can actually return.
Error code catalog
Every code the API can return, grouped by area. Codes are stable across versions - switch on them. The HTTP status is the one paired with the code; a handful of codes (marked) can also appear inside the fields array of a VALIDATION error.
Transport & generic
Code
Status
Meaning
VALIDATION
400
The request failed schema validation. See the fields array.
UNAUTHORIZED
401
Authentication is missing or invalid.
FORBIDDEN
403
Authenticated, but not allowed to perform this operation.
NOT_FOUND
404
The addressed entity does not exist in this organization.
METHOD_NOT_ALLOWED
405
The HTTP method is not supported on this path.
NOT_ACCEPTABLE
406
No response representation satisfies the Accept header.
REQUEST_TIMEOUT
408
The request body was not received in time.
CONFLICT
409
The resource's current state blocks the operation.
DUPLICATE
409
A record with the same natural key already exists.
INVALID_TRANSITION
409
The requested state change is not allowed from the current state.
INVALID_OPERATION
409
The operation is not legal for this resource in its current state.
REQUEST_ENTITY_TOO_LARGE
413
The request body exceeds the 1 MB cap.
UNSUPPORTED_MEDIA_TYPE
415
The request Content-Type is not supported.
INDETERMINATE
422
A value a billing decision depends on could not be resolved and no safe default exists; the operation is refused rather than risk misbilling.
RATE_LIMIT
429
The per-organization request budget is exhausted. See Rate limiting.
CLIENT_CLOSED_REQUEST
499
The client closed the connection before the response completed.
INTERNAL_ERROR
500
Unexpected server error. Safe to retry; report the request_id.
NOT_IMPLEMENTED
501
The endpoint is recognized but not yet available.
BAD_GATEWAY
502
An upstream provider returned an invalid or failed response.
SERVICE_UNAVAILABLE
503
A dependency this operation needs is unavailable.
TIMEOUT
504
The request exceeded its processing deadline.
Field validation
These appear as the code on each entry of a VALIDATION error's fields array, and can also be the top-level code (400) when a single value is rejected.
Code
Status
Meaning
REQUIRED
400
A required field was missing.
INVALID_FORMAT
400
The value does not match the expected format.
INVALID_TYPE
400
The value is the wrong JSON type.
INVALID_ENUM
400
The value is not one of the accepted options.
PATTERN_MISMATCH
400
The value does not match the required pattern.
TOO_SHORT
400
The value is shorter than the allowed minimum.
TOO_LONG
400
The value is longer than the allowed maximum.
BELOW_MINIMUM
400
The numeric value is below the allowed minimum.
ABOVE_MAXIMUM
400
The numeric value is above the allowed maximum.
INVALID_DATE
400
Not a valid date.
INVALID_DATETIME
400
Not a valid RFC 3339 date-time.
INVALID_URL
400
Not a valid URL.
INVALID_EMAIL
400
Not a valid email address.
CURRENCY_NOT_ALLOWED
400
The currency is not enabled for billing (or for catalog authoring) in this workspace. See Currency policy.
Authentication & access
Code
Status
Meaning
INVALID_TOKEN
401
The bearer token could not be verified.
INVALID_CLAIMS
401
The token's claims are missing or invalid.
PERMISSION_DENIED
403
The caller's role does not grant the required permission. See Roles.
POLICY_DENIED
403
An organization security policy blocked the request.
ORGANIZATION_FORBIDDEN
403
The caller is not a member of the target organization.
NO_ACTIVE_ORGANIZATION
403
The request has no active organization context.
DOMAIN_NOT_ALLOWED
403
The caller's email domain is not permitted by organization policy.
Customers
Code
Status
Meaning
CUSTOMER_SUSPENDED
409
The customer is suspended and cannot be billed.
CUSTOMER_UNCLASSIFIED
409
The customer's buyer type is still unknown; classify it before billing.
BUSINESS_REQUIRES_TAX_ID
400
A business customer must have a tax ID.
MERGE_SELF
422
A customer cannot be merged into itself.
MERGE_CONFLICT
409
The two customers cannot be merged because of conflicting data.
MERGE_SOURCE_DELETED
422
The source customer of the merge has been deleted.
CONSENT_PROOF_REQUIRED
422
Recording an express opt-in requires the consent statement the recipient agreed to.
Products & pricing
Code
Status
Meaning
PRODUCT_NOT_FOUND
404
The product does not exist.
PRODUCT_IN_USE
409
The product is referenced by a plan or subscription and cannot be removed.
FORMULA_EVALUATION_ERROR
422
A pricing formula failed to evaluate for the request.
MISSING_FORMULA_VARIABLES
422
The pricing formula references variables the request did not supply.
MIXED_COST_CURRENCY
400
Cost lines mix currencies within a single operation.
Plans & subscriptions
Code
Status
Meaning
SUBSCRIPTION_EXIT_BLOCKED
409
A phase's exit rule blocks the cancellation: the phase commits the subscription until a later phase ends. See Committed terms.
SUBSCRIPTION_STATUS_TRANSITION
409
The status change is not allowed from the subscription's current status.
SUBSCRIPTION_NOT_DRAFT
409
Only a DRAFT subscription can be activated.
SUBSCRIPTION_NO_DRAFT
409
The operation needs an open draft revision and there is none.
SUBSCRIPTION_DRAFT_OPEN
409
A draft revision is already open.
SUBSCRIPTION_NOT_BILLABLE
422
The deal cannot be billed as authored, so it is not made live; detail says why.
The revision routes have many more SUBSCRIPTION_* codes, one per rule a revision can break; each operation in the API reference lists its own.
Committed terms
A phase can commit a subscription: its exit rule (exit.gate with not_before) blocks leaving until a named phase ends. A cancellation (now, scheduled, or a scheduled stop) that lands inside that commitment is refused with 409 SUBSCRIPTION_EXIT_BLOCKED; detail names the phase that commits and the phase it commits until. Nothing is changed. Wait until the commitment ends, or cancel with a when after it.
Invoices
Code
Status
Meaning
INVOICE_NOT_FOUND
404
The invoice does not exist.
INVOICE_INVALID_TRANSITION
409
The requested invoice status change is not allowed.
Payments
Code
Status
Meaning
PAYMENT_METHOD_NOT_FOUND
404
The payment method does not exist.
PAYMENT_METHOD_NOT_ELIGIBLE
422
The payment method cannot be used for this operation.
OVERPAYMENT
422
The payment amount exceeds the outstanding balance.
Wallets & holds
Code
Status
Meaning
INSUFFICIENT_BALANCE
422
The debit or hold would exceed the available balance.
BUDGET_EXCEEDED
422
The operation would exceed a configured budget.
INELIGIBLE
422
The wallet or account is not eligible for this operation.
WALLET_LIMIT_EXCEEDED
409
A wallet limit (balance or count) would be exceeded.
WALLET_TOPUP_DISALLOWED
400
Top-ups are not permitted for this wallet.
DUPLICATE_CURRENCY
409
A wallet already exists for this currency.
SAME_WALLET
400
The source and destination wallets are the same.
CURRENCY_MISMATCH_NO_RATE
400
The currencies differ and no exchange rate is available.
ALREADY_PROCESSED
409
The request was already processed; treat this as an idempotent no-op.
CREDIT_EXCEEDS_MAX_BALANCE
422
The credit would push the wallet over its maximum balance.
CREDIT_EXCEEDS_MAX_SINGLE
422
The credit exceeds the maximum single-credit limit.
HOLD_REQUIRED
409
The operation requires an active hold.
HOLD_INSUFFICIENT_BALANCE
422
The hold would exceed the available balance.
HOLD_ALREADY_CAPTURED
409
The hold has already been captured.
HOLD_ALREADY_VOIDED
409
The hold has already been voided.
HOLD_EXPIRED
409
The hold has expired.
HOLD_CURRENCY_MISMATCH
400
The hold currency does not match the wallet currency.
Promotions
Code
Status
Meaning
PROMOTION_NOT_ELIGIBLE
422
The customer is not eligible for the promotion.
PROMOTION_LIMIT_REACHED
409
A redemption limit or ceiling on the promotion is reached.
PROMOTION_ALREADY_HELD
409
The customer already holds this promotion.
PROMOTION_WRONG_CODE
422
The coupon code does not match the promotion.
PROMOTION_WINDOW_CLOSED
409
The promotion's window is not open.
PROMOTION_NOT_LIVE
409
The promotion is not live.
PROMOTION_NOT_DRAFT
409
The edit is only allowed while the promotion is a draft.
PROMOTION_CODE_TAKEN
409
Another promotion already uses this coupon code.
See the API reference for the complete list of PROMOTION_* codes.
Quotes
Code
Status
Meaning
QUOTE_INVALID_TRANSITION
409
The requested quote status change is not allowed.
QUOTE_EXPIRED
409
The quote has expired.
QUOTE_ALREADY_SIGNED
409
The quote has already been signed.
QUOTE_IMMUTABLE
409
The quote is in a state that does not allow changes.
QUOTE_DECLINED_REASON_REQUIRED
400
Declining a quote requires a reason.
QUOTE_PRICE_CHANGED
409
The quoted price changed; re-confirm before completing.
QUOTE_NOT_PROVISIONABLE
409
The quote cannot be provisioned (e.g. the customer already holds a live subscription for the quoted plan).
QUOTE_ORCHESTRATION_NOT_RETRYABLE
409
The provisioning retry would be a no-op or a duplicate.
QUOTE_OTP_INVALID
400
The one-time passcode is invalid.
QUOTE_OTP_RATE_LIMITED
429
Too many one-time-passcode attempts; wait before retrying.
Approvals
Code
Status
Meaning
APPROVAL_REQUIRED_HUMAN
409
The action is gated by an approval checkpoint and an API key cannot raise or decide the request; a signed-in person must perform it. See Approvals.
APPROVAL_NOT_PENDING
409
The approval request has already been decided, expired, or cancelled.
APPROVAL_NOT_VISIBLE
403
The caller lacks read access to the approval request.
NOT_APPROVAL_DECIDER
403
The caller is not permitted to decide this approval.
SELF_APPROVAL
403
The caller raised the request and cannot approve it.
MISSING_DOMAIN_PERMISSION
403
The decider could not perform the operation they are being asked to approve.
NOT_IN_APPROVER_ROLE
403
The caller is not in the configured approver role.
NO_ELIGIBLE_APPROVERS
422
Enabling this checkpoint would leave too few eligible approvers to ever decide it.
ROLE_LACKS_PERMISSION
422
The selected approver role lacks the permission the checkpoint requires.
REJECTION_COMMENT_REQUIRED
422
Rejecting an approval requires a comment.
CHECKPOINT_UNAVAILABLE
409
The approval's checkpoint is not available; its history stays readable but it cannot be decided.
APPLY_NOT_RETRYABLE
409
The approval's apply step is not in a failed state, so it cannot be retried.
Roles & members
Code
Status
Meaning
ROLE_BUILTIN_IMMUTABLE
409
Built-in roles cannot be modified.
LAST_ADMIN_GUARD
409
The operation would remove the last administrator.
Scheduled changes & publishing
Code
Status
Meaning
ONE_PENDING_PER_ENTITY
409
The entity already has a pending scheduled change.
PAST_SCHEDULE
422
The requested schedule time is in the past.
DEPENDENCY_NOT_RELEASED
422
A dependency of this change has not been released yet.
ROLLBACK_WINDOW_EXPIRED
422
The rollback window for this change has passed.
Webhooks (inbound)
Code
Status
Meaning
WEBHOOK_SIGNATURE_INVALID
401
The inbound webhook signature could not be verified.
WEBHOOK_TIMESTAMP_EXPIRED
403
The inbound webhook timestamp is outside the accepted window.
WEBHOOK_SECRET_NOT_FOUND
404
No signing secret is configured for the matching gateway.
Dunning & settings
Code
Status
Meaning
DUNNING_POLICY_EXISTS
409
A dunning policy already exists for this scope.
DUNNING_NOT_CONFIGURED
404
No dunning policy is configured.
SETTINGS_NOT_FOUND
404
The requested settings do not exist.
SETTINGS_INVALID_CATALOG
422
The settings reference an invalid catalog configuration.
SETTING_LOCKED_ABOVE
409
A level above locked this setting; change it there.
SETTING_OUT_OF_BOUNDS
409
The value falls outside the bound a level above set.
SETTING_CONFLICTS_BELOW
409
A proposed lock or bound conflicts with values levels below already hold; the error lists them.
SETTING_LOOSER_THAN_ABOVE
409
A sign-in setting would be looser than what an organization above requires.
SETTING_ALLOWS_NOTHING_BELOW
409
A narrowed allow-list would leave an organization below allowing nothing; resend with confirm_empty_descendants: true to proceed.
SETTING_LEVEL_NOT_ALLOWED
422
The setting cannot be set at the level you named.
SETTING_SANDBOX_CANNOT_SET_ORGANIZATION
422
A sandbox cannot write at organization level.
SETTING_POLICY_NOT_ALLOWED
422
The setting cannot carry that policy (for example, a boolean cannot be bounded).
SETTING_VALUE_INVALID
422
The value is not valid for the setting.
SETTING_GRANTED_BY_PARENT
422
The setting is a capability your parent organization grants; only it can change it.
Rate limiting
Limits are enforced per organization, separately for reads (GET/HEAD/OPTIONS) and writes (POST/PUT/PATCH/DELETE). When the budget is exhausted you get a 429 with the standard problem document plus headers:
Header
Meaning
X-RateLimit-Limit
Requests allowed in the current window
X-RateLimit-Remaining
Requests remaining in the current window
X-RateLimit-Reset
Unix timestamp when the window resets
Retry-After
Seconds to wait before retrying (always present on 429)
Use exponential backoff with jitter when retrying. The window is one minute.
Idempotency
Every POST request must include an Idempotency-Key header; a POST without one is refused with 400 VALIDATION and the missing header named in fields[]. PUT, PATCH and DELETE do not take one. The key is a free-form string - use a UUID per logical operation.
Same key, same body, retry within 24h → the original response is replayed (status code + body), nothing is re-executed.
Same key, different body → 409 CONFLICT with detail: "Idempotency-Key was already used with a different request body." This catches client-side bugs where the key wasn't regenerated for a new request.
Same key, request still in-flight → 409 CONFLICT with detail: "A request with this Idempotency-Key is already being processed." Wait and retry.
The first attempt answered 5xx → nothing is stored, and the key is released at once. Retry with the same key; the request is executed again.
4xx responses are stored like successes. Retrying a refused request with the same key replays the refusal; fix the request and send it with a new key.
Body equivalence is checked via SHA-256 of the raw request body, so trivial whitespace changes count as a different body. Re-encode deterministically.
On operations that move or document money (credits, charges, refunds, credit notes), Kontier never runs the request without deduplication: if the idempotency store is briefly unavailable, it answers 503 SERVICE_UNAVAILABLE with Retry-After instead. Retry with the same key.
Keys are scoped to the caller: the organization plus the API key (or the signed-in user) that sent the request. Two API keys can use the same string without colliding, and a replay is only ever served to the caller that made the first request.