Error handling¶
Invora APIs use standard gRPC status codes, transcoded to HTTP status codes on the REST surface. The body is a google.rpc.Status: a numeric code, a human-readable message, and an optional details array carrying typed, @type-tagged payloads (most commonly google.rpc.BadRequest for field-level validation errors).
Status codes¶
| gRPC code | HTTP | When |
|---|---|---|
OK (0) |
200 | Success |
INVALID_ARGUMENT (3) |
400 | Missing required fields, invalid enum values, malformed input, business-rule validation failure |
UNAUTHENTICATED (16) |
401 | Missing or invalid access token |
PERMISSION_DENIED (7) |
403 | Missing scope, wrong tenant, or missing plan entitlement |
NOT_FOUND (5) |
404 | Document key, party, plan, etc. does not exist |
ALREADY_EXISTS (6) |
409 | Key collision on create |
ABORTED (10) |
409 | concurrencyStamp mismatch — another write happened |
FAILED_PRECONDITION (9) |
400 | Operation invalid for the current state (e.g. editing a frozen document) |
RESOURCE_EXHAUSTED (8) |
429 | Rate or plan limit reached |
UNAVAILABLE (14) |
503 | Upstream/temporary failure (e.g. a tax authority) — retry with backoff |
INTERNAL (13) |
500 | Unexpected server error |
Error body shape¶
A validation failure (INVALID_ARGUMENT) returns field-level detail in a google.rpc.BadRequest:
curl -X POST https://gateway.invora.app/api/v2/documents \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "changes": { "content": { "invoice": {} } } }'
{
"code": 3,
"message": "invalid argument",
"details": [
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{ "field": "changes.content", "description": "invoice content is required" }
]
}
]
}
codeis the numeric gRPC status (3=INVALID_ARGUMENT).messageis a short, human-readable summary.details[].fieldViolations[]lists each offendingfield(a path into your request) and adescriptionof what went wrong.
Other detail types may appear (e.g. google.rpc.ErrorInfo for machine-readable reason/domain), all tagged by their @type. Read details by their @type; do not parse the free-text message.
Authentication & authorization errors¶
A missing or expired token returns UNAUTHENTICATED (401); a valid token without the required scope or entitlement returns PERMISSION_DENIED (403). See Authentication for tokens and scopes.
Concurrency¶
A stale concurrencyStamp on a write returns ABORTED (409). Re-read the resource, take the fresh stamp, and retry once. See Entity versioning.
Retry strategy¶
| Error | Retry? | Strategy |
|---|---|---|
UNAVAILABLE (503) |
Yes | Exponential backoff (e.g. 1s, 2s, 4s, 8s) |
RESOURCE_EXHAUSTED (429) |
Yes | Back off and retry after a delay |
ABORTED (409, concurrency) |
Yes | Re-read, take the fresh stamp, retry once |
INTERNAL (500) |
Maybe | Retry once; if it persists, contact support |
INVALID_ARGUMENT, NOT_FOUND, PERMISSION_DENIED, … |
No | Fix the request and resubmit |
Related¶
- Authentication —
UNAUTHENTICATEDvsPERMISSION_DENIED. - Entity versioning —
ABORTEDon concurrency conflicts. - gRPC & JSON transcoding — how gRPC status maps to HTTP and JSON.
- List & filtering and Field masks — common sources of
INVALID_ARGUMENT.