Error Handling
The Hydra API uses standard HTTP status codes and returns structured JSON error responses. Every error includes a machine-readable code and a human-readable message.
Error response format
{
"error": {
"code": "not_found",
"message": "Product does not exist."
}
}
| Field | Type | Description |
|---|---|---|
code |
string | Machine-readable error identifier (see table below). |
message |
string | Human-readable explanation. |
field |
string | The specific field that caused the error (when applicable). |
details |
array | Per-field validation errors (for validation_error only). |
Status codes
400 Bad Request
The request is malformed or contains invalid data.
invalid_request — A specific field or parameter is invalid:
{
"error": {
"code": "invalid_request",
"message": "Quantity must be greater than zero.",
"field": "quantity"
}
}
validation_error — Multiple fields failed validation:
{
"error": {
"code": "validation_error",
"message": "Request body has invalid fields.",
"details": [
{ "field": "title", "message": "Required" },
{ "field": "price", "message": "Must be a positive integer" }
]
}
}
401 Unauthorized
The API key is missing, malformed, or revoked.
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}
403 Forbidden
The API key doesn’t have permission for this operation. Common causes:
- Using a publishable key (
pk_*) on a write endpoint - Account suspended due to billing issues
{
"error": {
"code": "forbidden",
"message": "Key does not have permission for this action."
}
}
404 Not Found
The requested resource doesn’t exist, or it belongs to a different project/test mode.
{
"error": {
"code": "not_found",
"message": "Product does not exist."
}
}
409 Conflict
The request conflicts with the current state. For example, creating a product with a handle that already exists:
{
"error": {
"code": "conflict",
"message": "A product with this handle already exists."
}
}
429 Too Many Requests
Rate limit or monthly quota exceeded. Check the Retry-After header.
rate_limited — Per-minute rate limit exceeded:
{
"error": {
"code": "rate_limited",
"message": "Too many requests."
}
}
quota_exceeded — Monthly request quota exhausted:
{
"error": {
"code": "quota_exceeded",
"message": "Monthly API quota exceeded."
}
}
500 Internal Server Error
An unexpected error occurred on the server. If this persists, contact support.
{
"error": {
"code": "internal_error",
"message": "An unexpected error occurred."
}
}
Error codes reference
| Code | Status | When it occurs |
|---|---|---|
invalid_request |
400 | A field value is invalid or missing |
validation_error |
400 | Multiple request body fields failed validation |
unauthorized |
401 | Missing, malformed, or revoked API key |
forbidden |
403 | Insufficient permissions for this operation |
account_suspended |
403 | Account suspended due to failed payment |
domain_not_verified |
403 | Live publishable key requires domain verification |
not_found |
404 | Resource doesn’t exist or is in a different environment |
conflict |
409 | Unique constraint violation (duplicate handle, email, etc.) |
rate_limited |
429 | Per-minute request limit exceeded |
quota_exceeded |
429 | Monthly API request quota exhausted |
internal_error |
500 | Unexpected server error |
Handling errors in code
With the SDK
The SDK throws typed errors that you can catch and inspect:
import { Hydra } from '@gethydra/sdk';
const hydra = new Hydra({ secret_key: 'sk_live_...' });
try {
const product = await hydra.products.get('prod_nonexistent');
} catch (err) {
if (err.status === 404) {
console.log('Product not found');
} else if (err.status === 429) {
// Back off and retry
const retryAfter = err.headers?.get('Retry-After');
console.log(`Rate limited. Retry after ${retryAfter}s`);
} else {
throw err; // Re-throw unexpected errors
}
}
With fetch
const res = await fetch('https://api.hydrajs.dev/v1/products/prod_nonexistent', {
headers: { Authorization: `Bearer ${SECRET_KEY}` },
});
if (!res.ok) {
const { error } = await res.json();
console.error(`${error.code}: ${error.message}`);
if (error.code === 'rate_limited') {
const retryAfter = res.headers.get('Retry-After');
// Wait and retry
}
}
Idempotency
For POST requests that create resources, you can include an Idempotency-Key header to safely retry failed requests without creating duplicates:
curl -X POST https://api.hydrajs.dev/v1/orders \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Idempotency-Key: unique-request-id-123" \
-H "Content-Type: application/json" \
-d '{"checkout_id": "chk_abc123..."}'
If a request with the same idempotency key was already processed, the API returns the original response. Keys expire after 24 hours.
Best practices
- Check
error.code, noterror.message. Messages may change; codes are stable. - Handle
429with backoff. Respect theRetry-Afterheader to avoid cascading failures. - Use idempotency keys for any
POSTthat creates resources or processes payments. - Log the full error response including
fieldanddetailsfor debugging validation failures.