Errors
All API errors follow a consistent format:
{
"error": {
"type": "invalid_request",
"message": "Amount must be greater than 0",
"param": "amount",
"code": "amount_invalid"
}
}Error types
| HTTP Status | Type | Description |
|---|---|---|
400 | invalid_request | Bad or missing parameters |
401 | authentication_error | Invalid or missing API key |
404 | not_found | Resource not found (or belongs to different merchant) |
429 | rate_limit | Too many requests. See Rate Limits. |
500 | internal_error | Server-side failure. Contact support if persistent. |
Error fields
| Field | Type | Description |
|---|---|---|
type | string | Error category (always present) |
message | string | Human-readable description (always present) |
param | string | The parameter that caused the error (when applicable) |
code | string | Machine-readable error code (when applicable) |
Retired wallet key endpoints
Interactive wallet APIs use a merchant session rather than a payment API key. The retired key POST routes return HTTP 410 in their normal wallet error envelope:
{
"success": false,
"error": {
"code": "SIGN_WINDOW_REQUIRED",
"message": "Reload inzi and open the wallet action in inzi sign.",
"details": {
"migrationUrl": "https://docs.inzilink.com/docs/wallet-signing",
"signingSessionEndpoint": "/api/v1/signing-sessions",
"interactive": true
}
}
}Follow Wallet Signing to select the action and open its Sign window. Retrying a retired POST or changing its Origin does not restore it.
Common errors
Authentication
// Missing or invalid API key
{
"error": {
"type": "authentication_error",
"message": "Invalid API key provided"
}
}Validation
// Invalid amount
{
"error": {
"type": "invalid_request",
"message": "Amount must be greater than 0",
"param": "amount",
"code": "amount_invalid"
}
}
// Metadata too large
{
"error": {
"type": "invalid_request",
"message": "Metadata must be less than 4KB",
"param": "metadata",
"code": "metadata_too_large"
}
}Team
Only the account owner grants the admin role. An admin — or an API key,
which acts with the admin role — that invites with "role": "admin" is
refused before anything is created or emailed:
// POST /merchants/team/invite { "email": "ops@example.com", "role": "admin" }
{
"success": false,
"error": {
"code": "OWNER_INVITES_ADMINS",
"message": "Only the account owner can invite admins. You can invite this person as a viewer."
}
}The same caller may invite with the viewer role:
// POST /merchants/team/invite { "email": "ops@example.com", "role": "viewer" } → 201Plans
Returned by POST /merchants/me/plan (owner only) with 409: the request is
valid, the account state does not allow it.
// POST /merchants/me/plan { "plan": "scale" } → 409
{
"success": false,
"error": {
"code": "BILLING_UNAVAILABLE",
"message": "Paid plans cannot be bought online right now. Write to us and we will switch you over by hand."
}
}| Code | When |
|---|---|
BILLING_UNAVAILABLE | An invoice for the new plan cannot be issued right now. Nothing is left pending. |
UNPAID_INVOICE | An invoice is overdue. Settle it, then change the plan. |
CUSTOM_PLAN_NEEDS_A_CONVERSATION | The account has custom terms; changing them is done with us. |
PLAN_NOT_AVAILABLE | The plan is not on sale yet. |
An upgrade that is accepted answers 200 with "applied": "awaiting_payment"
and an invoiceUrl — see the changelog.
Actions that depend on the plan answer 403 with the reason in code, the
capability and plan in details, and the limit when there is one:
// POST /merchants/team/invite — the eleventh person on Team → 403
{
"success": false,
"error": {
"code": "QUOTA_EXCEEDED",
"message": "You have used everything your plan includes for this period.",
"details": {
"capability": "team_manage",
"plan": "scale",
"quota": { "metric": "teamMembers", "limit": 10, "used": 10, "remaining": 0, "resetAt": null }
}
}
}| Code | What to do |
|---|---|
PLAN_REQUIRED | The plan does not include this. Upgrade in Billing. |
QUOTA_EXCEEDED | The plan’s limit for the period is used up. |
SUBSCRIPTION_RESTRICTED | A subscription invoice is overdue. Pay it in Billing; data and withdrawals stay available. |
CONTRACT_TERMS_REQUIRED | Custom terms do not cover this yet. Talk to us. |
FEATURE_NOT_RELEASED | Not available yet. Paying does not unlock it. |
Payer consent
Returned by POST /api/v1/checkout/{short_code}/pay when the payer’s consent
to pay in stablecoin is missing or weaker than the merchant requires. See the
changelog entry for the full contract.
// 428 — consent missing
{
"success": false,
"error": {
"code": "CONSENT_REQUIRED",
"message": "This payment needs the payer's explicit consent to pay in stablecoin…",
"details": {
"field": "consent",
"expected": { "accepted": true, "locale": "en", "acceptanceMode": "checkbox", "version": "v3" }
}
}
}
// 400 — a tick-box is required for this merchant
{
"success": false,
"error": {
"code": "CONSENT_FORM_TOO_WEAK",
"message": "A tick-box is required for this merchant…",
"details": { "field": "consent.acceptanceMode", "expected": "checkbox" }
}
}Token and currency
Payments are one to one: a USD price is paid in USDC or USDT, a EUR price in EURC. A token that does not settle the link’s currency is refused before anything is recorded:
// 400
{
"success": false,
"error": {
"code": "TOKEN_CURRENCY_MISMATCH",
"message": "That token cannot pay this link: payments are one to one, so a USD price is paid in USDC or USDT and a EUR price in EURC. Pick one of the tokens listed in \"acceptedTokens\" of GET /checkout/{short_code}."
}
}Rate limiting
{
"error": {
"type": "rate_limit",
"message": "Rate limit exceeded. Retry after 42 seconds."
}
}See the X-RateLimit-* response headers for current limits. Details in Rate Limits.
Handling errors
const res = await fetch('https://api.inzilink.com/api/v1/checkouts', { ... })
if (!res.ok) {
const { error } = await res.json()
switch (error.type) {
case 'authentication_error':
// Check your API key
break
case 'invalid_request':
// Fix the request parameters
console.error(`Invalid param: ${error.param} — ${error.message}`)
break
case 'rate_limit':
// Back off and retry
break
case 'internal_error':
// Retry with exponential backoff
break
}
}