Error code reference

Every VINR error code, its meaning, and how to resolve it.

View as MarkdownInstall skills

Every VINR API error returns a stable machine-readable code, a human-readable message, and the HTTP status that classifies it. This page is the canonical reference: what each code means, why it fires, and how to resolve it.

Anatomy of an errorAsk

Errors come back as a JSON object under an error key. The code is stable across versions and safe to branch on; the message is for humans and may change.

{
  "error": {
    "code": "card_declined",
    "message": "The card was declined by the issuer.",
    "type": "payment_error",
    "param": "payment_method",
    "doc_url": "https://docs.vinr.com/docs/troubleshooting/error-codes#card_declined",
    "request_id": "req_8aQ2xL0fK"
  }
}

Prop

Type

HTTP status mappingAsk

StatusTypeMeaning
400validation_errorThe request was malformed or missing required fields.
401authentication_errorThe API key is missing, invalid, or revoked.
402payment_errorThe payment could not be processed (declines, etc.).
404validation_errorThe referenced resource does not exist.
409billing_errorThe request conflicts with current resource state.
422billing_errorThe request was well-formed but semantically rejected.
429rate_limit_errorToo many requests; back off and retry.
5xxapi_errorA problem on VINR's side. Safe to retry with the same idempotency key.

The SDK throws a typed VinrError with these fields, so you rarely parse JSON yourself.

import { Vinr, VinrError } from '@vinr/sdk';

const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

try {
  await vinr.payments.create({ amount: 1000, currency: 'EUR', customer: 'cust_123' });
} catch (err) {
  if (err instanceof VinrError) {
    switch (err.code) {
      case 'card_declined':
        return showRetryWithDifferentCard();
      case 'rate_limited':
        return scheduleRetry(err.retryAfter);
      default:
        console.error(`[${err.requestId}] ${err.code}: ${err.message}`);
    }
  }
  throw err;
}

Authentication errorsAsk

CodeHTTPResolution
missing_api_key401Send the secret key in the X-Api-Key header.
invalid_api_key401The key is malformed or was rotated. Reissue from the dashboard.
key_revoked401The key was revoked. Generate a new one and redeploy.
mode_mismatch401A sandbox key was used against api.vinr.com (or vice versa). Match key mode to the base URL.
insufficient_permissions403The key's role lacks scope for this endpoint. Use a key with broader permissions.

Never embed a secret key in client-side code. If a key leaks, revoke it immediately — key_revoked will then protect you.

Validation errorsAsk

These mean the request itself is wrong. They are deterministic: retrying without changing the request will fail identically.

CodeHTTPResolution
missing_required_field400Add the field named in param.
invalid_amount400Amounts are positive integers in minor units (1000 = EUR 10.00).
invalid_currency400Use a supported ISO 4217 code; defaults to EUR.
resource_not_found404The ID in param does not exist or belongs to another mode.
invalid_id_prefix400An ID was passed to the wrong field (e.g. a sub_ where a pay_ was expected).

Payment errorsAsk

Payment errors map issuer and rail outcomes. Branch on code, not on the message. The decline_code field carries the issuer's reason when present.

CodeHTTPRetryableResolution
card_declined402NoGeneric issuer decline. Prompt for a different method.
insufficient_funds402MaybeCustomer lacks funds. Retry later or use another method.
expired_card402NoCollect new card details.
incorrect_cvc402NoRe-prompt for the security code.
authentication_required402YesComplete 3D Secure; see the payment lifecycle.
processing_error402YesTransient rail error. Retry with the same idempotency key.
payment_already_captured409NoThe payment is already completed; do not re-capture.

Reproduce each path in sandbox with the test cards: 4242 4242 4242 4242 succeeds, 4000 0000 0000 0002 returns card_declined, and 4000 0000 0000 3220 triggers authentication_required.

Billing errorsAsk

These arise from subscription and invoice state machines.

CodeHTTPResolution
subscription_not_active409The sub_ is paused or cancelled. Reactivate before modifying.
invoice_already_paid409The inv_ is settled. Issue a refund instead of recharging.
price_currency_mismatch422All line items on an invoice must share one currency.
usage_record_out_of_window422The mbu_ timestamp falls outside the open billing period.
proration_not_allowed422The plan forbids mid-cycle proration. Schedule the change for period end.

Rate-limit & server errorsAsk

CodeHTTPResolution
rate_limited429Back off using the Retry-After header (seconds).
concurrent_request_limit429Reduce parallelism for the same resource.
internal_error500Transient. Retry with the same idempotency key.
service_unavailable503VINR is degraded. Retry with exponential backoff.

For 429 and 5xx, retry with exponential backoff and jitter, capped at around 5 attempts. Always reuse your original idempotency key so a retry never double-charges.

async function withRetry<T>(fn: () => Promise<T>, attempts = 5): Promise<T> {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (err) {
      const retryable =
        err instanceof VinrError &&
        ['rate_limited', 'internal_error', 'service_unavailable', 'processing_error'].includes(err.code);
      if (!retryable || i === attempts - 1) throw err;
      const wait = err.retryAfter ?? Math.min(2 ** i * 200, 5000) + Math.random() * 200;
      await new Promise((r) => setTimeout(r, wait));
    }
  }
  throw new Error('unreachable');
}

Next stepsAsk

Was this page helpful?
Edit on GitHub

Last updated on

On this page