The API uses standard HTTP status codes and returns errors in a consistent JSON format.
| Status Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Resource created successfully |
| 400 | Bad request (malformed request) |
| 401 | Missing API key |
| 403 | Invalid or revoked API key |
| 404 | Resource not found |
| 422 | Validation error |
| 429 | Rate limit exceeded |
| 500 | Server error |
| 503 | Service unavailable |
All errors follow this structure:
{
"success": false,
"errors": {
"<field_name|__all__>": ["error message"]
}
}errors.__all__: General errors not specific to a fielderrors.<field_name>: Errors specific to a particular field
{
"success": false,
"errors": {
"__all__": ["API key authentication failed"]
}
}{
"success": false,
"errors": {
"customer_id": ["Customer with this ID does not exist"],
"date": ["Invalid date format. Use ISO 8601 format (YYYY-MM-DD)"],
"lines": ["At least one line item is required"]
}
}{
"success": false,
"errors": {
"__all__": ["Invoice matching query does not exist."]
}
}{
"success": false,
"errors": {
"__all__": ["Rate limit exceeded. Please try again later."]
}
}Best Practices:
- Always check HTTP status codes
- Parse the
errorsobject for field-specific details - Implement retry logic for rate limits (429) and server errors (5xx)
- Log error responses for debugging
response = requests.post(url, headers=headers, json=data)
if response.status_code == 201:
# Success
result = response.json()
elif response.status_code == 422:
# Validation errors
errors = response.json()['errors']
for field, messages in errors.items():
print(f"{field}: {messages}")
elif response.status_code == 429:
# Rate limited - wait and retry
time.sleep(60)
response = requests.post(url, headers=headers, json=data)
else:
# Other error
print(f"Error {response.status_code}: {response.json()}")