Skip to content

Error Handling

Invora APIs use standard gRPC status codes augmented with domain-specific error details.

Wire format follows google.rpc.Status: top-level gRPC status carries code, message, and a details array of google.protobuf.Any-packed messages (for example google.rpc.ErrorInfo, google.rpc.BadRequest, or Invora-specific types such as invora.documents.v2.ValidationError). JSON transcoding maps these fields per gRPC JSON mapping.

gRPC Status Codes

Code Meaning When
OK (0) Success Request completed
INVALID_ARGUMENT (3) Bad request Missing required fields, invalid enum values, malformed input
NOT_FOUND (5) Resource missing Document key, party, plan code doesn't exist
ALREADY_EXISTS (6) Duplicate Key collision on create, duplicate link
PERMISSION_DENIED (7) Unauthorized Missing scope, wrong tenant, billing entitlement required
FAILED_PRECONDITION (9) State conflict Freeze a non-draft, update a frozen document, link non-customer party
ABORTED (10) Concurrency conflict concurrency_stamp mismatch (another write happened)
OUT_OF_RANGE (11) Pagination invalid Expired cursor, page_size > max
UNIMPLEMENTED (12) Not available RPC exists in proto but not yet implemented
UNAVAILABLE (14) Temporary failure Upstream timeout (ZATCA, billing provider), retry with backoff
RESOURCE_EXHAUSTED (8) Rate limited Too many requests — see Rate Limiting section

Error Detail Structure

Every error includes a google.rpc.Status with structured details in details field:

{
  "code": 3,
  "message": "Document validation failed",
  "details": [
    {
      "@type": "type.googleapis.com/invora.documents.v2.ValidationError",
      "field": "invoice_line[0].line_extension_amount",
      "code": "AMOUNT_MISMATCH",
      "message": "Line extension amount (100.00) does not equal quantity * price (90.00)",
      "severity": "ERROR"
    }
  ]
}

Domain Error Codes

Domain errors are returned as string codes in google.rpc.ErrorInfo details or as ValidationError messages. These are not proto enums — they are server-defined string constants returned in error responses.

Document State Errors

Code gRPC Status Trigger
DOCUMENT_NOT_DRAFT FAILED_PRECONDITION Attempting to Update/Delete a frozen document
DOCUMENT_NOT_FROZEN FAILED_PRECONDITION Attempting to Send a draft document
DOCUMENT_ALREADY_CANCELLED FAILED_PRECONDITION Cancel/WriteOff an already-cancelled document
CONCURRENCY_CONFLICT ABORTED concurrency_stamp doesn't match server version
MISSING_SUPPLIER INVALID_ARGUMENT Document has no supplier party
MISSING_CUSTOMER INVALID_ARGUMENT Document has no customer party
MISSING_LINE_ITEMS INVALID_ARGUMENT Document has zero line items

Regulation Errors

Code gRPC Status Trigger
REGULATION_NOT_ENABLED FAILED_PRECONDITION Freeze with regulation that isn't enabled for tenant
REGULATION_NOT_ONBOARDED FAILED_PRECONDITION Submit to ZATCA before completing onboarding
SUBMISSION_REJECTED ABORTED Regulatory authority rejected the document
SUBMISSION_TIMEOUT UNAVAILABLE Regulatory authority didn't respond in time

ZATCA-Specific Errors

Returned as string values in ZatcaOnboardingError.code and ZatcaOnboardingError.category within the InitiateOnboarding stream. These are server-defined string codes, not proto enums:

Category Code Meaning
OTP INVALID_OTP OTP expired or already used
OTP OTP_RATE_LIMITED Too many OTP attempts
CSR CSR_GENERATION_FAILED Certificate signing request generation failed
COMPLIANCE COMPLIANCE_CHECK_FAILED ZATCA rejected compliance invoice
COMPLIANCE CSID_NOT_ISSUED ZATCA did not issue compliance CSID
PRODUCTION PRODUCTION_CSID_FAILED Production CSID issuance failed

Billing Errors

Code gRPC Status Trigger
BILLING_NOT_ENABLED PERMISSION_DENIED Tenant plan doesn't include billing capability
CUSTOMER_NOT_FOUND NOT_FOUND external_id doesn't match any billing customer
PLAN_NOT_FOUND NOT_FOUND Plan code doesn't exist
SUBSCRIPTION_ALREADY_ACTIVE ALREADY_EXISTS Customer already has active subscription to this plan
WALLET_INSUFFICIENT_BALANCE FAILED_PRECONDITION Wallet balance too low for transaction

Party Errors

Code gRPC Status Trigger
PARTY_NOT_CUSTOMER_ROLE FAILED_PRECONDITION LinkBillingCustomer on a SUPPLIER-only party
PARTY_ALREADY_LINKED ALREADY_EXISTS Party already linked to a billing customer
BILLING_ENTITLEMENT_REQUIRED PERMISSION_DENIED Tenant plan doesn't include billing; can't link

Retry Strategy

Error Retry? Strategy
UNAVAILABLE Yes Exponential backoff: 1s, 2s, 4s, 8s, max 5 retries
RESOURCE_EXHAUSTED Yes Wait for retry-after header, or backoff 30s
ABORTED (concurrency) Yes Re-read resource, get fresh stamp, retry once
INTERNAL Maybe Report to Invora support if persistent
All others No Fix request and resubmit

Rate Limiting

Rate limits are enforced per tenant. When exceeded, the server returns RESOURCE_EXHAUSTED.

Limits vary by plan tier and endpoint category (read vs write vs bulk). Check response headers for current limits:

  • x-ratelimit-limit: max requests in current window
  • x-ratelimit-remaining: requests left in window
  • x-ratelimit-reset: UTC epoch seconds when window resets

When rate limited, back off and retry after the x-ratelimit-reset time. If no reset header is present, use exponential backoff starting at 30 seconds.

Contact support or check your plan details for specific rate limit quotas.