Skip to content

Error Handling

All Arky API errors follow a consistent structure to help you handle failures gracefully.

{
"message": "Human-readable error message",
"error": "ERROR.CODE",
"statusCode": 400,
"validationErrors": []
}
  • message: Detailed, human-readable description of the error
  • error: Machine-readable error code in DOMAIN.TYPE format
  • statusCode: HTTP status code (400, 401, 403, 404, 500, etc.)
  • validationErrors: Array of field-level validation errors (when applicable)

CodeMeaningWhen it occurs
400Bad RequestInvalid input, validation failed, business logic error
401UnauthenticatedMissing, invalid, or expired token
403ForbiddenAuthenticated but lacks permission for the resource
404Not FoundResource doesn’t exist
409ConflictTransaction conflict (optimistic locking, concurrent updates)
500Internal Server ErrorUnexpected server error

These errors can occur across all API endpoints:

Status: 400
Description: Malformed request or invalid parameters

{
"message": "Bad request: Invalid UUID format",
"error": "GENERAL.BAD_REQUEST",
"statusCode": 400,
"validationErrors": []
}

Status: 401
Description: No authentication token provided, or token is invalid/expired

{
"message": "Unauthenticated",
"error": "GENERAL.UNAUTHENTICATED",
"statusCode": 401,
"validationErrors": []
}

Common causes:

  • Missing Authorization header
  • Expired access token
  • Invalid JWT signature
  • Token refresh needed

Status: 403
Description: Authenticated but insufficient permissions

{
"message": "You are forbidden to access this resource.",
"error": "GENERAL.FORBIDDEN",
"statusCode": 403,
"validationErrors": []
}

Common causes:

  • User doesn’t have required role/permission
  • Resource belongs to different business
  • API key lacks necessary scopes

Status: 404
Description: Requested resource doesn’t exist

{
"message": "Not found",
"error": "GENERAL.NOT_FOUND",
"statusCode": 404,
"validationErrors": []
}

Status: 400
Description: Request validation failed (field-level errors)

{
"message": "Validation failed",
"error": "GENERAL.VALIDATION_FAILED",
"statusCode": 400,
"validationErrors": [
{
"field": "email",
"message": "Invalid email format"
},
{
"field": "password",
"message": "Password must be at least 8 characters"
}
]
}

Status: 500
Description: Unexpected server error

{
"message": "Internal server Error: Database connection failed",
"error": "GENERAL.INTERNAL_SERVER",
"statusCode": 500,
"validationErrors": []
}

Error CodeStatusDescription
USER.NOT_FOUND400User not found
USER.EMAIL_EXISTS400Email already registered
USER.PASSWORD_WRONG400Incorrect password
USER.PASSWORD_REQUIRED400Password field missing
USER.PASSWORD_INVALID400Password doesn’t meet requirements
USER.INVALID_ORIGIN_URI400OAuth origin not whitelisted
USER.INVALID_REDIRECT_URI400OAuth redirect not whitelisted
USER.FAILED_OAUTH_LOGIN400OAuth login failed
USER.FAILED_REFRESH_ACCESS_TOKEN400Token refresh failed
USER.UNAUTHORIZED403User not authorized for this action

Example:

{
"message": "UserError: email already exists",
"error": "USER.EMAIL_EXISTS",
"statusCode": 400,
"validationErrors": []
}
Error CodeStatusDescription
BUSINESS.NOT_FOUND400Business not found
BUSINESS.NAME_REQUIRED400Business name is required
BUSINESS.ALREADY_MEMBER400User is already a member
BUSINESS.INVITATION_PENDING400Invitation already sent
BUSINESS.FAILED_CREATE400Failed to create business
BUSINESS.FAILED_UPDATE400Failed to update business
BUSINESS.FAILED_DELETE400Failed to delete business
Error CodeStatusDescription
RESERVATION.NOT_FOUND400Reservation not found
RESERVATION.TIME_SLOT_NOT_AVAILABLE409Time slot already booked
RESERVATION.OUTSIDE_WORKING_HOURS400Booking outside provider schedule
RESERVATION.INVALID_TIME_RANGE400Invalid start/end time
RESERVATION.FAILED_CREATE400Failed to create reservation

Example:

{
"message": "ReservationError: Time slot not available",
"error": "RESERVATION.TIME_SLOT_NOT_AVAILABLE",
"statusCode": 409,
"validationErrors": []
}
Error CodeStatusDescription
PRODUCT.NOT_FOUND400Product not found
PRODUCT.INSUFFICIENT_STOCK400Not enough inventory
PRODUCT.FAILED_CREATE400Failed to create product
PRODUCT.FAILED_UPDATE400Failed to update product
Error CodeStatusDescription
ORDER.NOT_FOUND400Order not found
ORDER.INVALID_STATUS400Invalid order status transition
ORDER.PAYMENT_FAILED400Payment processing failed
ORDER.FAILED_CREATE400Failed to create order
Error CodeStatusDescription
PAYMENT.STRIPE_ERROR400Stripe API error
PAYMENT.INVALID_AMOUNT400Payment amount mismatch
PAYMENT.PROMO_CODE_INVALID400Invalid or expired promo code
PAYMENT.PROMO_CODE_LIMIT_REACHED400Promo code usage limit exceeded
Error CodeStatusDescription
MEDIA.NOT_FOUND400Media file not found
MEDIA.UPLOAD_FAILED400File upload failed
MEDIA.INVALID_FILE_TYPE400Unsupported file format
MEDIA.FILE_TOO_LARGE413File exceeds size limit
Error CodeStatusDescription
COLLECTION.NOT_FOUND400Collection not found
COLLECTION.FAILED_CREATE400Failed to create collection
FIELD.VALIDATION_FAILED400Block validation failed
FIELD.REQUIRED_MISSING400Required block missing
FIELD.TYPE_MISMATCH400Block value type mismatch
Error CodeStatusDescription
NEWSLETTER.NOT_FOUND400Newsletter not found
NEWSLETTER.ALREADY_SUBSCRIBED400Email already subscribed
NEWSLETTER.NOT_SUBSCRIBED400Email not subscribed
NEWSLETTER.PAYMENT_REQUIRED400Paid newsletter requires payment

The SDK automatically parses error responses and throws structured errors:

try {
const user = await sdk.user.register({
email: 'invalid-email',
password: '123',
});
} catch (error: any) {
// Access error details
console.error('Error code:', error.name); // "ApiError"
console.error('Status code:', error.statusCode); // 400
console.error('Error type:', error.error); // "GENERAL.VALIDATION_FAILED"
console.error('Message:', error.message); // "Validation failed"
// Handle validation errors
if (error.validationErrors?.length > 0) {
error.validationErrors.forEach((err) => {
console.error(`${err.field}: ${err.message}`);
});
}
// Handle specific error codes
if (error.error === 'USER.EMAIL_EXISTS') {
alert('This email is already registered. Please log in instead.');
}
}

Don’t rely solely on status codes—use the error field for precise handling:

try {
await sdk.reservation.create({ /* ... */ });
} catch (error: any) {
if (error.statusCode === 409) {
if (error.error === 'RESERVATION.TIME_SLOT_NOT_AVAILABLE') {
// Show time slot picker again
refreshAvailableSlots();
}
}
}

The message field is server-generated; consider translating error codes to localized messages:

const ERROR_MESSAGES = {
'USER.EMAIL_EXISTS': 'This email is already registered.',
'USER.PASSWORD_WRONG': 'Incorrect password. Please try again.',
'RESERVATION.TIME_SLOT_NOT_AVAILABLE': 'This time slot is no longer available.',
};
function getUserMessage(error: any): string {
return ERROR_MESSAGES[error.error] || 'An error occurred. Please try again.';
}

Implement retry logic for 500 errors and transaction conflicts:

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error: any) {
if (error.statusCode === 500 || error.statusCode === 409) {
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, 2 ** i * 1000));
continue;
}
}
throw error;
}
}
throw new Error('Max retries exceeded');
}

Error responses include x-request-id header for support:

try {
await sdk.eshop.products.create({ /* ... */ });
} catch (error: any) {
console.error('Request ID:', error.requestId); // Use when contacting support
console.error('Error:', error.error);
}