Error Handling

The Storno.ro API uses standard HTTP status codes and returns structured error responses in JSON format.

Error Response Format

All error responses follow this structure:

{
  "error": "Short error description",
  "message": "Detailed human-readable explanation",
  "code": 400
}

Validation Errors

When a request fails validation, the response includes field-level error details:

{
  "error": "Validation failed",
  "message": "The request contains invalid data",
  "code": 400,
  "errors": {
    "issueDate": "This value should not be blank.",
    "lines": "At least one line item is required.",
    "lines[0].quantity": "This value should be greater than 0."
  }
}

HTTP Status Codes

Success Codes

CodeDescription
200Request succeeded
201Resource created successfully
204Request succeeded with no response body (e.g., DELETE)

Client Error Codes

CodeDescription
400Bad Request — Invalid request body, missing required fields, or validation errors
401Unauthorized — Missing, invalid, or expired JWT token
402Payment Required — Feature not available on current plan (see PLAN_LIMIT code)
403Forbidden — Valid token but insufficient permissions (e.g., wrong company, role restriction)
404Not Found — Resource doesn't exist or belongs to a different company
405Method Not Allowed — HTTP method not supported for this endpoint
409Conflict — Resource state conflict (e.g., issuing an already-issued invoice, duplicate IBAN)
422Unprocessable Entity — Request is well-formed but semantically invalid
429Too Many Requests — Rate limit exceeded

Server Error Codes

CodeDescription
500Internal Server Error — Unexpected server error
502Bad Gateway — ANAF service unreachable
503Service Unavailable — Server temporarily unavailable

Common Error Scenarios

Authentication Errors

// 401 — Expired token
{
  "code": 401,
  "message": "Expired JWT Token"
}

// 401 — Invalid credentials
{
  "code": 401,
  "message": "Invalid credentials."
}

Company Context Errors

// 403 — Missing X-Company header
{
  "error": "Company context required",
  "message": "The X-Company header is required for this endpoint.",
  "code": 403
}

// 403 — No access to company
{
  "error": "Access denied",
  "message": "You do not have access to this company.",
  "code": 403
}

Plan Limit Errors

// 402 — Feature not available on current plan
{
  "error": "Recurring invoices are not available on your plan.",
  "code": "PLAN_LIMIT"
}

// 402 — Monthly invoice limit reached
{
  "error": "Monthly invoice limit reached.",
  "code": "PLAN_LIMIT",
  "limit": 100
}

Plan limit errors apply equally to web, mobile, and API key authenticated requests. See Plans & Features for details on what each plan includes.

Resource State Errors

// 409 — Invoice already issued
{
  "error": "Invalid state transition",
  "message": "Cannot issue an invoice that is already issued.",
  "code": 409
}

// 409 — Cannot delete issued invoice
{
  "error": "Cannot delete",
  "message": "Cannot delete an issued invoice. Cancel it first.",
  "code": 409
}

Handling Errors

Retry Strategy

  • 401: Refresh your token and retry the request
  • 429: Wait for the duration specified in the Retry-After header
  • 500/502/503: Retry with exponential backoff (max 3 retries)
  • 402: Upgrade your plan — see Plans & Features
  • 400/403/404/409: Do not retry — fix the request

Example Error Handler

async function apiRequest(url, options = {}) {
  const response = await fetch(url, {
    ...options,
    headers: {
      'Authorization': `Bearer ${token}`,
      'X-Company': companyUuid,
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  if (!response.ok) {
    const error = await response.json();

    if (response.status === 401) {
      // Token expired — refresh and retry
      await refreshToken();
      return apiRequest(url, options);
    }

    throw new ApiError(error.message, response.status, error.errors);
  }

  return response.json();
}