All API errors return a consistent JSON structure. The GlobalExceptionFilter normalizes every error -- including validation errors, domain exceptions, and unhandled crashes -- into this format.
When request body validation fails via class-validator, the GlobalExceptionFilter joins all validation messages into a single comma-separated string and forces the code to VALIDATION_ERROR:
{ "statusCode": 400, "code": "VALIDATION_ERROR", "message": "amount must be a positive number, side must be one of: YES, NO"}
The validation pipe is configured with whitelist: true (strips unknown properties), transform: true (auto-coerces types), and forbidNonWhitelisted: true (rejects unknown fields).
The frontend API client (lib/api/client.ts) throws an ApiClientError with the full error body. Handle errors by checking the code field:
try { await placeOrder(orderData);} catch (error) { if (error instanceof ApiClientError) { switch (error.body.code) { case 'ORDER_FAILED': showToast('Order failed: ' + error.body.message); break; case 'UNAUTHORIZED': // 401 retry is handled automatically by the client break; default: showToast('Something went wrong'); } }}
The API client automatically handles 401 errors by refreshing the access token and retrying the request. You do not need to handle UNAUTHORIZED errors manually in most cases.