Skip to content

Commit 5f8713c

Browse files
committed
feat: expose documented API error codes
1 parent 9a53e6b commit 5f8713c

7 files changed

Lines changed: 318 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.4.0] - 2026-07-16
9+
10+
### Added
11+
12+
- Add `ApiErrorCodes`, grouped constants for documented API root codes and Facturapi-owned validation detail codes.
13+
- Add `FacturapiException.getApiErrorCode()` for the string V2 root error code while preserving `getErrorCode()` compatibility.
14+
815
## [1.3.0] - 2026-06-07
916

1017
### Added

README.es.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ Maven:
2929
<dependency>
3030
<groupId>io.facturapi</groupId>
3131
<artifactId>facturapi-java</artifactId>
32-
<version>1.1.0</version>
32+
<version>1.4.0</version>
3333
</dependency>
3434
```
3535

3636
Gradle:
3737

3838
```gradle
39-
implementation("io.facturapi:facturapi-java:1.1.0")
39+
implementation("io.facturapi:facturapi-java:1.4.0")
4040
```
4141

4242
## Inicio rápido
@@ -84,13 +84,16 @@ var updated = facturapi.organizations().uploadCertificate(
8484

8585
```java
8686
import io.facturapi.FacturapiException;
87+
import io.facturapi.constants.ApiErrorCodes;
8788

8889
try {
8990
facturapi.customers().retrieve("cus_123");
9091
} catch (FacturapiException e) {
9192
System.out.println(e.getMessage());
9293
System.out.println(e.getStatusCode());
93-
System.out.println(e.getErrorCode());
94+
if (ApiErrorCodes.RequestErrorCode.RATE_LIMIT_EXCEEDED.equals(e.getApiErrorCode())) {
95+
System.out.println(e.getHeaders().get("retry-after"));
96+
}
9497
System.out.println(e.getErrorPath());
9598
}
9699
```

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ Maven:
2929
<dependency>
3030
<groupId>io.facturapi</groupId>
3131
<artifactId>facturapi-java</artifactId>
32-
<version>1.1.0</version>
32+
<version>1.4.0</version>
3333
</dependency>
3434
```
3535

3636
Gradle:
3737

3838
```gradle
39-
implementation("io.facturapi:facturapi-java:1.1.0")
39+
implementation("io.facturapi:facturapi-java:1.4.0")
4040
```
4141

4242
## Quickstart
@@ -84,13 +84,16 @@ var updated = facturapi.organizations().uploadCertificate(
8484

8585
```java
8686
import io.facturapi.FacturapiException;
87+
import io.facturapi.constants.ApiErrorCodes;
8788

8889
try {
8990
facturapi.customers().retrieve("cus_123");
9091
} catch (FacturapiException e) {
9192
System.out.println(e.getMessage());
9293
System.out.println(e.getStatusCode());
93-
System.out.println(e.getErrorCode());
94+
if (ApiErrorCodes.RequestErrorCode.RATE_LIMIT_EXCEEDED.equals(e.getApiErrorCode())) {
95+
System.out.println(e.getHeaders().get("retry-after"));
96+
}
9497
System.out.println(e.getErrorPath());
9598
}
9699
```

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>io.facturapi</groupId>
77
<artifactId>facturapi-java</artifactId>
8-
<version>1.3.0</version>
8+
<version>1.4.0</version>
99
<name>facturapi-java</name>
1010
<description>Official Java SDK for Facturapi</description>
1111
<url>https://github.com/facturapi/facturapi-java</url>

src/main/java/io/facturapi/FacturapiException.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ public Object getErrorCode() {
6161
return errorCode;
6262
}
6363

64+
/**
65+
* Returns the documented V2 API root error code when the response contains one.
66+
*
67+
* <p>{@link #getErrorCode()} remains available for compatibility with older API responses.</p>
68+
*/
69+
public String getApiErrorCode() {
70+
return errorCode instanceof String ? (String) errorCode : null;
71+
}
72+
6473
public String getErrorPath() {
6574
return errorPath;
6675
}
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
package io.facturapi.constants;
2+
3+
/**
4+
* Documented Facturapi API error codes.
5+
*
6+
* <p>External PAC and SAT codes are returned in an error's {@code errors} detail array and are
7+
* intentionally not included in this catalog.</p>
8+
*/
9+
public final class ApiErrorCodes {
10+
private ApiErrorCodes() {
11+
}
12+
13+
public static final class CommonErrorCode {
14+
public static final String CONFLICT = "conflict";
15+
public static final String FORBIDDEN = "forbidden";
16+
public static final String INTERNAL_ERROR = "internal_error";
17+
public static final String NOT_FOUND = "not_found";
18+
public static final String UNAUTHORIZED = "unauthorized";
19+
private CommonErrorCode() { }
20+
}
21+
22+
public static final class AuthErrorCode {
23+
public static final String API_KEY_INVALID = "api_key_invalid";
24+
public static final String API_KEY_NOT_ALLOWED = "api_key_not_allowed";
25+
public static final String FEATURE_NOT_AVAILABLE = "feature_not_available";
26+
public static final String LIVE_API_KEY_REQUIRED = "live_api_key_required";
27+
public static final String MCP_PERMISSION_DENIED = "mcp_permission_denied";
28+
public static final String MISSING_CREDENTIALS = "missing_credentials";
29+
public static final String ORGANIZATION_INCOMPLETE = "organization_incomplete";
30+
public static final String SUBSCRIPTION_REQUIRED = "subscription_required";
31+
public static final String SUBSCRIPTION_LIVE_ACCESS_REQUIRED = "subscription_live_access_required";
32+
public static final String USER_KEY_INVALID = "user_key_invalid";
33+
public static final String USER_SUSPENDED = "user_suspended";
34+
private AuthErrorCode() { }
35+
}
36+
37+
public static final class RequestErrorCode {
38+
public static final String IDEMPOTENCY_KEY_IN_USE = "idempotency_key_in_use";
39+
public static final String RATE_LIMIT_EXCEEDED = "rate_limit_exceeded";
40+
public static final String DATE_RANGE_TOO_LARGE = "date_range_too_large";
41+
public static final String IMAGE_FILE_REQUIRED = "image_file_required";
42+
public static final String INVALID_COUNTRY_CODE = "invalid_country_code";
43+
public static final String INVALID_DATE = "invalid_date";
44+
public static final String INVALID_DATE_RANGE = "invalid_date_range";
45+
public static final String INVALID_IMAGE_FILE = "invalid_image_file";
46+
public static final String INVALID_JSON = "invalid_json";
47+
public static final String INVALID_REQUEST = "invalid_request";
48+
public static final String INVALID_MULTIPART_FORM_DATA = "invalid_multipart_form_data";
49+
public static final String INVALID_STATE_CODE = "invalid_state_code";
50+
public static final String INVALID_TIMEZONE = "invalid_timezone";
51+
public static final String MULTIPART_LIMIT_EXCEEDED = "multipart_limit_exceeded";
52+
public static final String PAGE_TOO_LARGE = "page_too_large";
53+
public static final String PAYLOAD_TOO_LARGE = "payload_too_large";
54+
private RequestErrorCode() { }
55+
}
56+
57+
public static final class CustomerErrorCode {
58+
public static final String CUSTOMER_COULD_NOT_BE_RESOLVED = "customer_could_not_be_resolved";
59+
public static final String CUSTOMER_EDIT_LINK_NOT_FOUND = "customer_edit_link_not_found";
60+
public static final String CUSTOMER_EDIT_LINK_UNAVAILABLE = "customer_edit_link_unavailable";
61+
public static final String CUSTOMER_EMAIL_REQUIRED = "customer_email_required";
62+
public static final String CUSTOMER_HAS_INVOICES = "customer_has_invoices";
63+
public static final String CUSTOMER_NOT_FOUND = "customer_not_found";
64+
public static final String CUSTOMER_TAX_INFO_UNAVAILABLE = "customer_tax_info_unavailable";
65+
private CustomerErrorCode() { }
66+
}
67+
68+
public static final class TaxInfoValidationCode {
69+
public static final String LEGAL_NAME_MISMATCH = "legal_name_mismatch";
70+
public static final String TAX_ADDRESS_ZIP_MISMATCH = "tax_address_zip_mismatch";
71+
public static final String TAX_ID_NOT_FOUND = "tax_id_not_found";
72+
public static final String TAX_SYSTEM_NOT_ALLOWED_FOR_TAX_ID = "tax_system_not_allowed_for_tax_id";
73+
public static final String TAX_SYSTEM_NOT_IN_CATALOG = "tax_system_not_in_catalog";
74+
private TaxInfoValidationCode() { }
75+
}
76+
77+
public static final class ProductErrorCode {
78+
public static final String PRODUCT_NOT_FOUND = "product_not_found";
79+
private ProductErrorCode() { }
80+
}
81+
82+
public static final class SupplierErrorCode {
83+
public static final String SUPPLIER_NOT_FOUND = "supplier_not_found";
84+
private SupplierErrorCode() { }
85+
}
86+
87+
public static final class CatalogErrorCode {
88+
public static final String PRODUCT_KEY_NOT_FOUND = "product_key_not_found";
89+
public static final String TARIFF_CODE_NOT_FOUND = "tariff_code_not_found";
90+
public static final String UNIT_KEY_NOT_FOUND = "unit_key_not_found";
91+
private CatalogErrorCode() { }
92+
}
93+
94+
public static final class InvoiceErrorCode {
95+
public static final String INVOICE_ALREADY_STAMPED = "invoice_already_stamped";
96+
public static final String INVOICE_NOT_DRAFT = "invoice_not_draft";
97+
public static final String INVOICE_NOT_FOUND = "invoice_not_found";
98+
public static final String INVOICE_NOT_STAMPED = "invoice_not_stamped";
99+
private InvoiceErrorCode() { }
100+
}
101+
102+
public static final class InvoiceDraftErrorCode {
103+
public static final String DRAFT_NOT_READY_TO_STAMP = "draft_not_ready_to_stamp";
104+
public static final String DRAFT_UPDATE_IN_PROGRESS = "draft_update_in_progress";
105+
private InvoiceDraftErrorCode() { }
106+
}
107+
108+
public static final class InvoiceStampingErrorCode {
109+
public static final String INVOICE_STAMPING_FAILED = "invoice_stamping_failed";
110+
public static final String INVOICE_STAMPING_SERVICE_UNAVAILABLE = "invoice_stamping_service_unavailable";
111+
public static final String INVOICE_STAMPING_VALIDATION_ERROR = "invoice_stamping_validation_error";
112+
public static final String MANIFESTO_SIGNATURE_FAILED = "manifesto_signature_failed";
113+
public static final String STAMPING_IN_PROGRESS = "stamping_in_progress";
114+
private InvoiceStampingErrorCode() { }
115+
}
116+
117+
public static final class InvoiceDeliveryErrorCode {
118+
public static final String INVOICE_EMAIL_DELIVERY_FAILED = "invoice_email_delivery_failed";
119+
public static final String INVOICE_EMAIL_RECIPIENT_REQUIRED = "invoice_email_recipient_required";
120+
public static final String INVOICE_EMAIL_STATUS_NOT_ALLOWED = "invoice_email_status_not_allowed";
121+
private InvoiceDeliveryErrorCode() { }
122+
}
123+
124+
public static final class InvoiceCancellationErrorCode {
125+
public static final String INVOICE_CANCELLATION_IN_PROGRESS = "invoice_cancellation_in_progress";
126+
public static final String INVOICE_CANCELLATION_RECEIPT_UNAVAILABLE = "invoice_cancellation_receipt_unavailable";
127+
public static final String INVOICE_CANCELLATION_FAILED = "invoice_cancellation_failed";
128+
public static final String INVOICE_CANCELLATION_NOT_ALLOWED = "invoice_cancellation_not_allowed";
129+
public static final String INVOICE_CANCELLATION_NOT_FOUND = "invoice_cancellation_not_found";
130+
public static final String INVOICE_CANCELLATION_RFC_MISMATCH = "invoice_cancellation_rfc_mismatch";
131+
public static final String INVOICE_CANCELLATION_SERVICE_UNAVAILABLE = "invoice_cancellation_service_unavailable";
132+
public static final String INVOICE_NOT_CANCELABLE = "invoice_not_cancelable";
133+
public static final String INVOICE_NOT_CANCELABLE_BY_SAT = "invoice_not_cancelable_by_sat";
134+
public static final String SUBSTITUTION_INVOICE_CANCELED = "substitution_invoice_canceled";
135+
public static final String SUBSTITUTION_INVOICE_NOT_FOUND = "substitution_invoice_not_found";
136+
public static final String SUBSTITUTION_INVOICE_REQUIRED = "substitution_invoice_required";
137+
public static final String SUBSTITUTION_INVOICE_STATUS_NOT_ALLOWED = "substitution_invoice_status_not_allowed";
138+
private InvoiceCancellationErrorCode() { }
139+
}
140+
141+
public static final class ReceiptErrorCode {
142+
public static final String RECEIPT_EXPIRED = "receipt_expired";
143+
public static final String RECEIPT_NOT_FOUND = "receipt_not_found";
144+
public static final String RECEIPT_NOT_OPEN = "receipt_not_open";
145+
private ReceiptErrorCode() { }
146+
}
147+
148+
public static final class ReceiptInvoicingErrorCode {
149+
public static final String RECEIPT_INVOICING_ADDRESS_MISMATCH = "receipt_invoicing_address_mismatch";
150+
public static final String RECEIPT_INVOICING_CUSTOMER_MISMATCH = "receipt_invoicing_customer_mismatch";
151+
public static final String RECEIPT_INVOICING_TOO_MANY_ITEMS = "receipt_invoicing_too_many_items";
152+
public static final String RECEIPT_KEYS_NOT_FOUND = "receipt_keys_not_found";
153+
private ReceiptInvoicingErrorCode() { }
154+
}
155+
156+
public static final class ReceiptGlobalInvoiceErrorCode {
157+
public static final String GLOBAL_INVOICE_TOO_MANY_ITEMS = "global_invoice_too_many_items";
158+
public static final String INVALID_GLOBAL_INVOICE_PERIOD = "invalid_global_invoice_period";
159+
private ReceiptGlobalInvoiceErrorCode() { }
160+
}
161+
162+
public static final class RetentionErrorCode {
163+
public static final String INVALID_RETENTION_COMPLEMENT = "invalid_retention_complement";
164+
public static final String RETENTION_NOT_FOUND = "retention_not_found";
165+
public static final String RETENTION_NOT_STAMPED = "retention_not_stamped";
166+
public static final String RETENTION_NOT_CANCELABLE = "retention_not_cancelable";
167+
private RetentionErrorCode() { }
168+
}
169+
170+
public static final class RetentionDeliveryErrorCode {
171+
public static final String RETENTION_EMAIL_DELIVERY_FAILED = "retention_email_delivery_failed";
172+
public static final String RETENTION_EMAIL_RECIPIENT_REQUIRED = "retention_email_recipient_required";
173+
public static final String RETENTION_EMAIL_STATUS_NOT_ALLOWED = "retention_email_status_not_allowed";
174+
private RetentionDeliveryErrorCode() { }
175+
}
176+
177+
public static final class RetentionCancellationErrorCode {
178+
public static final String RETENTION_CANCELLATION_FAILED = "retention_cancellation_failed";
179+
public static final String RETENTION_CANCELLATION_SERVICE_UNAVAILABLE = "retention_cancellation_service_unavailable";
180+
private RetentionCancellationErrorCode() { }
181+
}
182+
183+
public static final class RetentionStampingErrorCode {
184+
public static final String RETENTION_STAMPING_FAILED = "retention_stamping_failed";
185+
public static final String RETENTION_STAMPING_SERVICE_UNAVAILABLE = "retention_stamping_service_unavailable";
186+
public static final String RETENTION_STAMPING_VALIDATION_ERROR = "retention_stamping_validation_error";
187+
private RetentionStampingErrorCode() { }
188+
}
189+
190+
public static final class OrganizationErrorCode {
191+
public static final String INVALID_OPERATION = "invalid_operation";
192+
public static final String INVALID_USER_ID = "invalid_user_id";
193+
public static final String ORGANIZATION_NOT_FOUND = "organization_not_found";
194+
public static final String SUBSCRIPTION_ACTIVE_REQUIRED = "subscription_active_required";
195+
private OrganizationErrorCode() { }
196+
}
197+
198+
public static final class OrganizationSettingsErrorCode {
199+
public static final String CERTIFICATE_EXPIRED = "certificate_expired";
200+
public static final String CERTIFICATE_FILE_REQUIRED = "certificate_file_required";
201+
public static final String CERTIFICATE_FILES_INVALID = "certificate_files_invalid";
202+
public static final String CERTIFICATE_FILES_REQUIRED = "certificate_files_required";
203+
public static final String CERTIFICATE_FIEL_RFC_MISMATCH = "certificate_fiel_rfc_mismatch";
204+
public static final String CERTIFICATE_INVALID = "certificate_invalid";
205+
public static final String CERTIFICATE_NOT_YET_VALID = "certificate_not_yet_valid";
206+
public static final String CERTIFICATE_PREVIOUS_RFC_MISMATCH = "certificate_previous_rfc_mismatch";
207+
public static final String CSD_REQUIRED = "csd_required";
208+
public static final String FIEL_INVALID = "fiel_invalid";
209+
public static final String FIEL_RFC_MISMATCH = "fiel_rfc_mismatch";
210+
public static final String FIEL_REQUIRED = "fiel_required";
211+
public static final String ORGANIZATION_DOMAIN_CHANGE_NOT_ALLOWED = "organization_domain_change_not_allowed";
212+
public static final String ORGANIZATION_DOMAIN_UNAVAILABLE = "organization_domain_unavailable";
213+
public static final String ORGANIZATION_SETTINGS_INVALID = "organization_settings_invalid";
214+
public static final String ORGANIZATION_SUPPORT_EMAIL_REQUIRED = "organization_support_email_required";
215+
public static final String ORGANIZATION_TAX_INFO_INVALID = "organization_tax_info_invalid";
216+
public static final String PRIVATE_KEY_CERTIFICATE_MISMATCH = "private_key_certificate_mismatch";
217+
public static final String PRIVATE_KEY_FILE_REQUIRED = "private_key_file_required";
218+
public static final String PRIVATE_KEY_PASSWORD_INCORRECT = "private_key_password_incorrect";
219+
private OrganizationSettingsErrorCode() { }
220+
}
221+
222+
public static final class OrganizationInviteErrorCode {
223+
public static final String INVITE_EMAIL_DELIVERY_FAILED = "invite_email_delivery_failed";
224+
public static final String INVITE_EMAIL_MISMATCH = "invite_email_mismatch";
225+
public static final String INVITE_EXPIRED = "invite_expired";
226+
public static final String INVITE_NOT_FOUND = "invite_not_found";
227+
public static final String INVITE_ROLE_UNAVAILABLE = "invite_role_unavailable";
228+
public static final String USER_ALREADY_IN_ORGANIZATION = "user_already_in_organization";
229+
private OrganizationInviteErrorCode() { }
230+
}
231+
232+
public static final class OrganizationAccessErrorCode {
233+
public static final String ORGANIZATION_ADMIN_ACCESS_CANNOT_BE_REMOVED = "organization_admin_access_cannot_be_removed";
234+
public static final String ORGANIZATION_ADMIN_ASSIGNMENT_CANNOT_BE_EDITED = "organization_admin_assignment_cannot_be_edited";
235+
public static final String ORGANIZATION_ADMIN_ROLE_CANNOT_BE_DELETED = "organization_admin_role_cannot_be_deleted";
236+
public static final String ORGANIZATION_ADMIN_ROLE_CANNOT_BE_EDITED = "organization_admin_role_cannot_be_edited";
237+
public static final String ORGANIZATION_ADMIN_ROLE_REQUIRED = "organization_admin_role_required";
238+
public static final String ORGANIZATION_ID_NOT_ALLOWED = "organization_id_not_allowed";
239+
public static final String ORGANIZATION_ID_REQUIRED = "organization_id_required";
240+
public static final String OWNER_ACCESS_CANNOT_BE_REASSIGNED = "owner_access_cannot_be_reassigned";
241+
public static final String OWNER_ACCESS_CANNOT_BE_REMOVED = "owner_access_cannot_be_removed";
242+
public static final String ROLE_HAS_ASSIGNED_USERS = "role_has_assigned_users";
243+
public static final String ROLE_TEMPLATE_NOT_FOUND = "role_template_not_found";
244+
public static final String ROLE_NOT_FOUND = "role_not_found";
245+
public static final String USER_ACCESS_NOT_FOUND = "user_access_not_found";
246+
private OrganizationAccessErrorCode() { }
247+
}
248+
249+
public static final class OrganizationSeriesErrorCode {
250+
public static final String SERIES_ALREADY_EXISTS = "series_already_exists";
251+
public static final String SERIES_NOT_FOUND = "series_not_found";
252+
private OrganizationSeriesErrorCode() { }
253+
}
254+
255+
public static final class WebhookErrorCode {
256+
public static final String WEBHOOK_DELIVERY_ATTEMPT_NOT_FOUND = "webhook_delivery_attempt_not_found";
257+
public static final String WEBHOOK_NOT_FOUND = "webhook_not_found";
258+
public static final String WEBHOOK_SIGNATURE_INVALID = "webhook_signature_invalid";
259+
private WebhookErrorCode() { }
260+
}
261+
262+
public static final class ToolErrorCode {
263+
public static final String TAX_ID_VALIDATION_FAILED = "tax_id_validation_failed";
264+
public static final String TAX_ID_VALIDATION_SERVICE_UNAVAILABLE = "tax_id_validation_service_unavailable";
265+
private ToolErrorCode() { }
266+
}
267+
268+
/** Facturapi-owned values for {@code errors[].code} validation details. */
269+
public static final class ValidationErrorCode {
270+
public static final String AMOUNT_EXCEEDS_RELATED_DOCUMENT_BALANCE = "amount_exceeds_related_document_balance";
271+
public static final String EXCHANGE_RATE_TOO_LARGE = "exchange_rate_too_large";
272+
public static final String EXCHANGE_RATE_TOO_SMALL = "exchange_rate_too_small";
273+
public static final String INVALID_FORMAT = "invalid_format";
274+
public static final String INVALID_LENGTH = "invalid_length";
275+
public static final String INVALID_TYPE = "invalid_type";
276+
public static final String NOT_FOUND = "not_found";
277+
public static final String NOT_ALLOWED = "not_allowed";
278+
public static final String REQUIRED = "required";
279+
public static final String TOO_LARGE = "too_large";
280+
public static final String TOO_SMALL = "too_small";
281+
public static final String UNKNOWN_FIELD = "unknown_field";
282+
public static final String INVALID_VALUE = "invalid_value";
283+
private ValidationErrorCode() { }
284+
}
285+
}

0 commit comments

Comments
 (0)