Error Codes
Every error response uses the same envelope, carries a stable code, a human-readable message, a request_id you can send to support, and a doc_url linking back to this page.
The error envelope
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": { "fieldErrors": { "to_details": ["Required"] } },
"doc_url": "https://www.generateinvoice.io/docs/errors#validation_error",
"request_id": "req_a1b2c3d4e5f6a7b8c9d0"
}
}The same request_id is also returned as the X-Request-Id response header. Every error response carries it; on successes it is rolling out endpoint by endpoint (invoices first) and is always present in the JSON meta.request_id field.
All error codes
| Code | HTTP | Meaning | How to fix |
|---|---|---|---|
| UNAUTHORIZED | 401 | No API key was provided, or session auth failed. | Send your key in the Authorization header: Bearer gi_live_… (or X-API-Key). |
| INVALID_API_KEY | 401 | The key format is wrong or the key does not exist. | Keys start with gi_live_ or gi_test_. Copy the full key from Settings → API Keys. |
| API_KEY_EXPIRED | 401 | The key has an expiry date that has passed. | Create a new key in the dashboard and rotate your integration. |
| API_KEY_DISABLED | 401 | The key was deactivated. | Re-enable it in the dashboard or create a new key. |
| USER_NOT_FOUND | 401 | The account behind the key no longer exists. | Contact support@generateinvoice.io with your request_id. |
| VALIDATION_ERROR | 400 | The request body failed validation. | Inspect error.details for per-field messages and correct the payload. |
| USAGE_LIMIT_EXCEEDED | 403 | Your monthly document quota is used up. | Upgrade your plan, or wait for the next billing period. |
| RATE_LIMIT_EXCEEDED | 429 | Too many API requests this hour for your plan. | Honor the Retry-After header, then retry. See Rate Limits. |
| NOT_FOUND | 404 | The requested document or resource does not exist (or is not yours). | Check the id — resources are scoped to your account. |
| INVALID_STATE | 400/409 | The operation is not allowed in the document’s current status. | Check the document status first (e.g. only quotes in status "sent" can be accepted). |
| QUOTE_EXPIRED | 400 | The quote’s valid_until date has passed. | Create a new quote or extend valid_until before converting. |
| LIMIT_EXCEEDED | 403 | A plan limit other than documents (bulk jobs, rows, webhooks) was hit. | Reduce the request size or upgrade your plan. |
| INVALID_REFERENCE | 400 | A referenced resource (e.g. related_invoice_id) was not found. | Check error.details.field and pass the id of an existing resource you own. |
| QUEUE_ERROR | 500 | A bulk job could not be queued for processing. | Retry the request; if it persists, contact support with the request_id. |
| CANCEL_FAILED | 500 | A bulk job cancellation could not be processed. | Check the job status; jobs that already completed cannot be cancelled. |
| FEATURE_DISABLED | 404 | The endpoint exists but the feature is not available yet. | Nothing to fix — watch the changelog for availability. |
| INTERNAL_ERROR | 500 | Something failed on our side. | Retry with backoff. If it persists, email support with the request_id. |
Handling errors
Branch on error.code, never on the message text — messages can change, codes are stable. A minimal handler:
const res = await fetch('https://www.generateinvoice.io/api/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GENERATEINVOICE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
const body = await res.json();
if (!body.success) {
switch (body.error.code) {
case 'RATE_LIMIT_EXCEEDED': {
const retryAfter = Number(res.headers.get('Retry-After') ?? 60);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
return retry();
}
case 'VALIDATION_ERROR':
console.error('Fix these fields:', body.error.details);
break;
default:
// Log the request_id — support can trace the exact request with it.
console.error(body.error.code, body.error.request_id);
}
}Validation error details
VALIDATION_ERROR responses include a details object with fieldErrors (per-field messages) and formErrors (top-level messages). Line-item errors currently report at the line_items level; per-index paths are on the roadmap.