Reference
Errors
The API error format, the error code catalog and the Hacienda rejections Aster catches before sending.
Every API error uses the same format and a stable error.code. Code against error.code, not against the text in error.message, which may change.
Format
Section titled “Format”{ "success": false, "error": { "code": "validation_error", "message": "Validation failed", "errors": { "items": ["At least one item is required"] } }, "timestamp": "2026-03-06T12:00:00Z", "api_version": "1.0"}| Field | Description |
|---|---|
success |
Always false on an error |
error.code |
Stable code, in English and snake_case |
error.message |
Human-readable description |
error.errors |
Only on validation_error: an object with each field’s path and its list of problems |
timestamp |
Response time, UTC |
api_version |
API version |
Authentication, permission, rate-limit and idempotency errors also repeat the message in a top-level message field, for compatibility with older clients.
Language
Section titled “Language”error.message and the messages in error.errors come in Spanish or English depending on the request’s Accept-Language header. With Accept-Language: en you get:
{ "success": false, "error": { "code": "validation_error", "message": "CABYS code 4292199990299 is not in the Catálogo de Bienes y Servicios (Hacienda error -400).", "errors": { "items.0.cabys_code": [ "CABYS code 4292199990299 is not in the Catálogo de Bienes y Servicios (Hacienda error -400)." ] } }, "timestamp": "2026-10-08T16:20:11Z", "api_version": "1.0"}The paths in error.errors follow the structure of the JSON you sent: items.0.cabys_code is the cabys_code of the first line (counting from 0) and receiver.id is the receiver’s ID.
hacienda_message, on the other hand, carries Hacienda’s text exactly as Hacienda returned it, untranslated and unabridged.
Error codes
Section titled “Error codes”| HTTP | error.code |
When | What to do |
|---|---|---|---|
| 400 | invalid_request |
The body is missing or not valid JSON | Check the body and Content-Type |
| 400 | invalid_idempotency_key |
X-Idempotency-Key is longer than 255 characters |
Use a shorter key |
| 401 | unauthenticated |
The key is missing, malformed or unknown | Check the Authorization header |
| 401 | token_expired |
The key is past its expiry date | Create a new key |
| 401 | key_revoked |
The key was revoked | Use an active key |
| 401 | tenant_unresolved |
The key’s organization could not be loaded | Check that the organization exists |
| 402 | plan_required |
An aster_live_ key was used or created without a paid plan |
Choose a plan in the dashboard |
| 402 | quota_exceeded |
The plan’s monthly limit plus purchased packs was reached. Returned before signing | Upgrade your plan or add a pack at any time; see below |
| 402 | subscription_inactive |
The subscription is not in good standing (for example, a declined charge). Test keys keep working | Update the payment method in the dashboard |
| 402 | subscription_expired |
Both the trial and the subscription have ended | Renew the plan in the dashboard |
| 403 | forbidden |
The key lacks the permission the endpoint requires | Create a key with that permission |
| 403 | account_key_required |
The endpoint manages the account and was called with an organization key | Use an account key (created without an organization) |
| 403 | organization_limit_reached |
The plan allows no more organizations | Upgrade your plan |
| 403 | no_tenant, tenant_inactive, tenant_deleted |
The organization is not available | Contact support |
| 404 | not_found |
The resource doesn’t exist or doesn’t belong to your organization | Check the clave or identifier |
| 409 | duplicate_clave |
The clave was already used | Don’t retry with the same clave; see Consecutivo (in Spanish) |
| 409 | duplicate_consecutive |
The consecutivo was already used in that environment | The number is spent; use a new one |
| 409 | organization_exists |
An organization with that ID already exists in the account | Use the existing one |
| 409 | idempotency_in_progress |
Another request with the same X-Idempotency-Key is still running |
Wait and retry with the same key |
| 422 | validation_error |
One or more fields failed validation. Details in error.errors |
Fix the listed fields |
| 422 | idempotency_key_reused |
An X-Idempotency-Key was reused with a different body or path |
Use a new key for each distinct request |
| 422 | organization_mismatch |
The document’s issuer is not the key’s organization | Use the key of the issuing organization |
| 422 | invalid_clave_consecutive |
clave was sent without consecutive_number, or the other way around |
Send both or neither |
| 422 | invalid_clave_format, invalid_consecutive_format |
The clave is not 50 characters or the consecutivo is not 20 digits | See Clave (in Spanish) |
| 422 | clave_date_mismatch |
The date inside the clave doesn’t match document.date |
Build the clave with the issue date |
| 422 | fec_clave_id_mismatch |
On an FEC, the clave doesn’t carry the receiver’s ID | Use the buyer’s ID in the clave |
| 422 | missing_certificate, missing_credentials |
The certificate or Hacienda credentials for that environment are missing | Upload them in the dashboard |
| 422 | certificate_expired |
The signing certificate for that environment has expired | Upload a valid certificate |
| 422 | cannot_revoke_current_key |
A key tried to revoke itself | Revoke it with another account key |
| 429 | rate_limited |
The per-minute or per-day request limit was exceeded | Wait the seconds in Retry-After |
| 500 | internal_error |
Unexpected Aster error | Retry with the same X-Idempotency-Key |
None of these errors registers the document or spends the consecutivo, except duplicate_clave and duplicate_consecutive, which mean it was already spent. Billing errors (402) are returned before signing, so you can safely release the number in your system.
Plan limit reached
Section titled “Plan limit reached”{ "success": false, "error": { "code": "quota_exceeded", "message": "You have reached your plan's document limit for this month.", "upgrade_url": "https://aster.astranexo.com/app/billing/plan", "pack_url": "https://aster.astranexo.com/app/billing/packs" }, "message": "You have reached your plan's document limit for this month.", "timestamp": "2026-10-08T16:20:11Z", "api_version": "1.0"}Upgrade your plan or add a pack at any time with the links in error.upgrade_url and error.pack_url (always use the ones in the response). The change is charged to your saved payment method and invoicing resumes within a minute. Test mode has no usage limit. See what counts toward usage (in Spanish).
Rate limits
Section titled “Rate limits”Every authenticated response carries the headers of the limit that applies:
X-RateLimit-Limit: 300X-RateLimit-Remaining: 298X-RateLimit-Reset: 1791476460When you exceed it, the response is 429 rate_limited with Retry-After in seconds. Rejected requests don’t count toward the limit. See Límites (in Spanish).
Hacienda rejections Aster catches before sending
Section titled “Hacienda rejections Aster catches before sending”A Hacienda rejection spends the consecutivo and forces you to issue a replacement document. That’s why Aster checks the most common rejection causes before signing and answers 422 validation_error with Hacienda’s code in the message, without sending anything.
| Hacienda code | What Aster checks |
|---|---|
-107, -109, -111, -190, -290, -476, -487 |
Calculations: line amounts, subtotals, tax, exemption, net tax, tax assumed by the issuer, tax breakdown, services vs. goods classification and summary totals. Compared at 5 decimal places with Hacienda’s tolerance |
-38 |
The issuer’s and receiver’s IDs are registered with Hacienda |
-400 |
Each line’s CABYS code exists in the Catálogo de Bienes y Servicios |
-483, -485 |
An exempt CABYS code is invoiced with rate code 10 (Tarifa Exenta), not another 0% rate |
-491 |
Reference code 99 (Otros) carries reference.code_other_description |
-505 |
The rate code (rate_code) matches the percentage (rate) |
-509 |
Every CABYS code on a credit or debit note appears in the original document |
-512 |
A credit note’s total doesn’t exceed the original document’s total |
-17 |
A credit or debit note’s receiver is the same as the original document’s |
-29 |
The referenced document exists and was accepted by Hacienda |
-99 |
The clave was never used before (answers 409 duplicate_clave) |
Aster also validates the full version 4.4 structure: required fields per document type, lengths, ID formats (including alphanumeric corporate IDs), units of measure against the CABYS chapter, current reference codes and the IVA devuelto (returned VAT) rules.
The note checks (-509, -512, -17, -29) use the original document stored in Aster. If the original is not in Aster, those content checks are skipped and the verdict is left to Hacienda.
Warnings that are not rejections
Section titled “Warnings that are not rejections”Codes -37 and -410 are warnings: Hacienda accepts the document with minor observations. Aster marks it accepted and keeps the text in hacienda_message. See Ciclo de vida (in Spanish).
Rejections that come from Hacienda
Section titled “Rejections that come from Hacienda”If Hacienda rejects a document for a rule Aster cannot check beforehand, the document ends in rejected, you receive the document.rejected event and Hacienda’s detail is in hacienda_message. To fix it, issue a new document that replaces the rejected one (see Consecutivo, in Spanish).