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
Server-side correlation ID - include it when reporting an issue.
Some errors carry extra top-level fields (RFC 9457 §3.2 extension members). For example, a not-found error includes entity_type and entity_id. 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. Some validators also include a params array carrying positional arguments for i18n template substitution - your localised UI can render the message from code + params instead of trusting the English message.
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 collision (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.
ORGANIZATION_SUSPENDED
403
The organization is suspended.
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 no longer exists.
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
NOT_ELIGIBLE_FOR_WITHDRAWAL
422
The subscription is not in a state eligible for withdrawal.
CONSUMER_PROTECTION_VIOLATION
422
The operation would violate a consumer-protection rule.
CONFLICT
409
An agreement's early-termination gate blocks this cancel, quantity decrease or item removal. Retry with an override_reason - see Committed terms.
Committed terms
An agreement that commits a subscription to a term can refuse a cancellation, a quantity decrease or an item removal that lands inside it. The refusal is a 409 whose code is the generic CONFLICT, so the extension members are what tell it apart from a version mismatch or a duplicate key:
Code
{ "type": "https://api.kontier.eu/errors/CONFLICT", "title": "Conflict", "status": 409, "detail": "this subscription is committed until 2027-03-01 and the agreement blocks cancel before then; retry with an override reason to proceed anyway", "code": "CONFLICT", "request_id": "01HKZ…", "agreement_id": "0f9c…", "committed_until": "2027-03-01T00:00:00Z", "operation": "cancel", "gate": "BLOCK", "cotermination_group_size": 5, "early_termination_fee": "12000.00", "early_termination_currency": "EUR", "early_termination_explanation": "50% of the 24000.00 remaining on the term"}
Member
What it carries
agreement_id
The agreement that refused the request.
committed_until
When the term runs out (RFC 3339). The one fact that decides whether to wait or to override.
operation
What was refused: cancel, quantity_decrease or item_remove.
gate
The agreement's early_termination_gate. Only BLOCK refuses; NONE and WARN let the operation through.
cotermination_group_size
How many subscriptions are committed together, this one included. Present only when it is more than one. The request still affects one subscription; the others are untouched.
early_termination_fee
What leaving now costs, as a decimal string in major units. Present only when the agreement charges a non-zero fee, alongside early_termination_currency and early_termination_explanation.
These are top-level extension members (RFC 9457 §3.2), not a nested object. The server splices the structured detail straight into the problem document beside code and detail. There is no details key to unwrap.
Branch on agreement_id, not on code.CONFLICT is shared with optimistic-concurrency mismatches, duplicate keys and idempotency collisions, none of which are fixed by an override reason. Re-sending the request with override_reason set proceeds and records who overrode the commitment; re-sending it unchanged will be refused for as long as the term runs.
Invoices & billing milestones
Code
Status
Meaning
INVOICE_NOT_FOUND
404
The invoice does not exist.
INVOICE_ALREADY_FINALIZED
409
The invoice is finalized and can no longer be modified.
INVOICE_INVALID_TRANSITION
409
The requested invoice status change is not allowed.
LINE_ITEM_NOT_FOUND
404
The invoice line item does not exist.
MILESTONE_NOT_PENDING
409
The billing milestone is not in a pending state.
MILESTONE_ALREADY_INVOICED
409
The billing milestone has already been invoiced.
MILESTONE_PERCENTAGE_EXCEEDED
422
The milestone percentages would exceed 100%.
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_EXPIRED
409
The promotion has expired.
MAX_REDEMPTIONS_REACHED
409
The promotion's total redemption limit is reached.
MAX_REDEMPTIONS_PER_CUSTOMER_REACHED
409
The customer's redemption limit for this promotion is reached.
DUPLICATE_REDEMPTION
409
This redemption was already recorded.
TOKEN_NOT_FOUND
404
The promotion token does not exist.
TOKEN_NOT_OWNED
403
The promotion token belongs to a different customer.
TOKEN_EXHAUSTED
409
The promotion token has no remaining uses.
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 can no longer be modified.
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
403
The action is gated by an approval and cannot be decided by an API key; a person must decide.
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 no longer 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.
ROLE_HAS_MEMBERS
409
The role still has members and cannot be deleted.
LAST_ADMIN_GUARD
409
The operation would remove the last administrator.
LAST_AUTH_PATH_GUARD
409
The operation would remove the last remaining way to authenticate.
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.
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
POST requests must include an Idempotency-Key header - Kontier rejects POSTs without one. The key is a free-form string (use a UUID or your own request ID); it lives for 24 hours per organization.
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.
POST without an Idempotency-Key header at all → 400 VALIDATION with the missing header in fields[].
Body equivalence is checked via SHA-256 of the request body, so trivial whitespace changes count as a different body. Re-encode deterministically.
Keys are scoped per organization. Two different orgs can use the same string without colliding.