Errors

Every error has a stable code, a type that maps to an HTTP status, and a request id to quote when you contact support.

Errors share one envelope. type tells you the class of problem and maps to the HTTP status; code is a stable identifier to branch on; message is for humans and may change; param names the offending field when there is one.

Error responsejson
{
  "error": {
    "type": "invalid_request_error",
    "code": "redirect_url_not_allowed",
    "message": "Domain \"shop.example\" is not in the merchant's allowed redirect domains.",
    "param": "return_url",
    "doc_url": "https://docs.borga.is/errors/redirect_url_not_allowed",
    "request_id": "req_95ef3682c9474dcd99cc31cab949a5e2"
  }
}

Each code has a page at /errors/<code>, which is what doc_url points to. The request_id is also sent as the X-Request-Id response header on every request; include it when writing to support.

Error types

typeHTTPMeaningRetry?
invalid_request_error400The request was malformed or refers to an object that does not exist.No. Fix the request.
authentication_error401The API key is missing, malformed or revoked.No. Check the key.
permission_error403The key or role is not allowed to do this, for example a live key before live mode is enabled.No.
payment_error402The card or processor declined.Depends on the code.
idempotency_error409An Idempotency-Key was reused incorrectly or is still in flight.No. Use a new key.
rate_limit_error429Too many requests.Yes, after Retry-After.
api_error500Something failed on Borga's side or at a provider.Yes, with backoff.

Handling errors in code

The Node SDK throws BorgaError for every non-2xx response and for network failures, with the envelope's fields as properties. It retries 429 and 5xx responses and network errors up to three times with exponential backoff before throwing.

Node.jsts
import { Borga, BorgaError } from "@borga/node";

const borga = new Borga({ apiKey: process.env.BORGA_SECRET_KEY! });

try {
  await borga.paymentSessions.create({
    amount: 1990,
    currency: "ISK",
    return_url: "https://yoursite.is/complete",
    cancel_url: "https://yoursite.is/cart",
  });
} catch (err) {
  if (err instanceof BorgaError) {
    // err.code is stable and safe to branch on; err.message is for humans.
    if (err.code === "redirect_url_not_allowed") {
      console.error(`Add the domain in the dashboard (${err.param})`);
    } else if (err.status === 429) {
      // The SDK already retried; back off further.
    }
    console.error(`[${err.requestId}] ${err.type}/${err.code}: ${err.message}`);
  } else {
    throw err;
  }
}

Branch on code, never on message. Treat unknown codes as failures of their type: new codes are added over time.

Validation errors

Request bodies are validated before anything else runs. Unknown fields are stripped silently; wrong types or constraint violations return validation_failed with the first failing rule in message and the field in param.

All error codes

codeHTTPMeaning
accounting_link_required400Bank invoices require a connected accounting provider. Connect PayDay under Settings → Accounting system.
accounting_not_connected400The merchant has no connected accounting provider.
already_applied400A live-mode application is already pending.
already_live400Live mode is already enabled for this merchant.
already_member400The invited user already belongs to this merchant.
amount_too_large400The amount exceeds what is allowed, for example a refund larger than the refundable balance.
amount_too_small400The amount is below the minimum for this operation, for example a refund of 0.
api_key_mode_mismatch403The key's mode (test or live) does not match the resource.
apply_live_cooldown400A live-mode application was submitted less than 15 minutes ago.
bank_invoice_failed400The accounting provider failed to register the bank claim.
bank_invoice_hosted_only400Bank invoices are only available in hosted checkout, not embedded.
bank_invoice_not_enabled400enabled_methods includes bank_invoice but the merchant has not enabled bank invoices. Turn it on under Settings → Bank invoice.
business_not_verified400Business verification has not completed, so live mode is not available yet.
cannot_pause400Only active or trialing subscriptions can be paused.
cannot_remove_owner400The merchant owner cannot be removed.
cannot_uncancel400The subscription is not scheduled to cancel at period end.
card_declined402The card issuer declined the payment.
card_payment_reverse_failed400The card processor definitively rejected the refund. The Refund is marked failed.
card_payment_reverse_uncertain502The processor did not confirm the refund. The Refund stays pending; do not retry blindly. Poll the refund or wait for payment.refunded.
card_payment_session_failed400The card processor rejected the session. Rare; retry, and contact Borga if it persists.
company_kennitala_mismatch400The company kennitala in the token does not match company_kennitala.
company_token_identity_mismatch400The Kenni company token was issued to a different user than the one signed in.
company_token_missing_name400The Kenni company token does not carry a company name.
connection_failed400The accounting provider rejected the credentials.
currency_mismatch400The item's currency differs from the subscription's currency.
customer_email_locked409The payer email was locked by the merchant or at checkout and cannot change.
database_error500A transient storage error. Retry with backoff.
expired_card402The card has expired.
expired_session401The dashboard session token has expired.
future_timestamp400A usage event timestamp is more than 60 seconds in the future.
idempotency_key_in_use409A request with this Idempotency-Key is still being processed. Wait for the original request to finish and retry with the same key, or use a new key for a different request.
idempotency_key_payload_mismatch409This Idempotency-Key was already used with a different request body or route. Use a fresh key for each distinct request.
incorrect_cvc402The card's security code was rejected.
insufficient_funds402The issuer reported insufficient funds.
insufficient_permissions403Your role on this merchant does not allow the action.
internal_error500Unexpected server error. Retry with backoff and report request_id if it persists.
invalid_amount400The amount is not a positive integer in the smallest currency unit.
invalid_api_key401The API key is malformed, unknown or revoked.
invalid_client_secret401The client_secret does not match this session.
invalid_company_id_token400The Kenni company token could not be verified.
invalid_currency400The currency is not supported. Use one of the supported currencies.
invalid_domain400A domain in allowed_redirect_domains is not a valid hostname.
invalid_embed_origin400The embedding origin is not a valid https://host origin.
invalid_field400A field failed validation. Check param and message.
invalid_kennitala400The kennitala failed the mod-11 checksum. Kennitala is 10 digits, optionally written as 000000-0000.
invalid_origin400An origin must be scheme and host only, for example https://shop.is, with no path.
invalid_param400A parameter is out of range. Check param.
invalid_period400period_start or period_end is not a valid ISO 8601 timestamp.
invalid_price_type400A price used for a subscription must have type: "recurring".
invalid_redirect_url400return_url or cancel_url is not a valid absolute URL.
invalid_request400The request is not valid for this key type.
invalid_session401The dashboard session token is invalid.
invalid_subscription400The items are not a valid subscription: at least one recurring price, and all items must share interval and currency.
invalid_timestamp400timestamp is not a valid ISO 8601 timestamp.
invalid_token401The dashboard session token could not be verified.
invalid_url400Webhook URLs must be public HTTPS URLs; localhost and private addresses are rejected.
kennitala_locked409The payer kennitala was pre-supplied by the merchant and cannot change.
live_mode_not_enabled403A live key was used before the merchant was approved for live mode. Use test keys until Settings → Live environment shows live mode as enabled.
mismatch400The subscription does not belong to customer.
missing_aggregate_property400aggregate_property is required for every aggregation except count.
missing_amount400Provide amount (to create a Payment) or payment (to attach an existing one).
missing_api_key401No Authorization: Bearer header was sent. Send your secret key as Authorization: Bearer sk_test_….
missing_client_secret401An embedded session was fetched without the X-Client-Secret header.
missing_company_id_token400Applying for live mode requires a fresh Kenni company authentication token.
missing_company_kennitala400Applying for live mode requires the company's kennitala.
missing_customer400customer is required for this query.
missing_field400A required field is absent. Check param for the field name.
missing_merchant400A dashboard request did not include the X-Merchant-Id header.
missing_meter400A metered recurring price needs meter.
missing_origin400An embedded session created with a secret key needs origin, the https://host of the page that opens checkout.
missing_processor_reference400The payment has no processor reference to refund against. Contact Borga.
missing_redirect_urls400A hosted session needs both return_url and cancel_url.
no_checkout_reference404The session has no card checkout to poll.
no_external_invoice400The invoice has not been created in the accounting provider yet.
no_merchant_access401The signed-in user does not belong to the merchant in X-Merchant-Id.
not_authenticated401A dashboard endpoint was called without a session token.
not_paused400The subscription is not paused.
not_retryable400Only invoices in status sync_failed can be re-synced.
origin_not_allowed400The browser's Origin is not in the publishable key's allowed_origins. Add the origin to the key under Developers in the dashboard.
payment_intent_authentication_failure4023-D Secure authentication failed or was abandoned.
payment_method_inactive400The payment method is detached, expired or failed and cannot be the default.
payment_not_available400The Payment referenced by payment is not in status created, so it cannot get a new session.
payment_not_refundable400Only payments in status succeeded or processing can be refunded.
pdf_not_supported400The connected accounting provider does not expose invoice PDFs.
poll_disabled403Client-side status polling is a development-only fallback and is off in production.
portal_consumed403A single-use customer portal link was already opened.
portal_expired403The customer portal link has expired.
portal_revoked403The customer portal link was revoked.
processing_error402The card processor returned a processing error. Safe to retry once.
provider_error400The accounting provider returned an error.
provider_no_bank_invoice400The connected accounting provider cannot issue bank claims. PayDay is required.
publishable_key_not_allowed403A publishable key was used for something only secret keys may do. Publishable keys can only create embedded sessions. Use a secret key from your server.
rate_limit_exceeded429Too many requests in the current window. Wait for Retry-After seconds, then retry.
redirect_url_not_allowed400return_url or cancel_url is not under an allowed redirect domain, or is not HTTPS. Add the domain under Settings → Account in the dashboard. In test mode http://localhost is always allowed.
resource_already_exists400An object with the same unique attribute already exists.
resource_in_wrong_state400The object cannot perform this action in its current status.
resource_missing400The starting_after cursor does not reference an object in this list. Use the id of the last object from the previous page.
resource_not_found400The referenced object does not exist in this merchant and mode. Check the id and that you are using a key for the right mode (test vs. live).
session_not_open400The payment session is no longer open; it completed or expired.
stale_company_id_token400The Kenni company token is older than five minutes. Authenticate again.
subscription_requires_save_payment_method400A session with subscription cannot set save_payment_method: false; subscriptions always save the card.
sync_failed400Re-syncing the invoice to the accounting provider failed again.
terms_not_accepted400The current Terms of Service must be accepted before this action.
unsupported_payment_method400A value in enabled_methods is not a payment method Borga supports.
upstream_unavailable500A provider Borga depends on (card processor, accounting system) is unavailable. Retry later.
validation_failed400Request validation failed. The message names the first failing constraint and param the field.
wrong_auth_method401An API key was sent to a dashboard-only endpoint. Dashboard endpoints (webhook endpoints, API keys, merchant settings) are managed in the dashboard UI.
wrong_connection_method400This provider connects with OAuth, not a pasted token (or vice versa).