Error Handling
Complete guide to handling API errors, HTTP status codes, and implementing retry logic.
HTTP Status Codes
SudoMock API uses standard HTTP status codes to indicate success or failure. All error responses include a JSON body with details.
Request successful. Response contains requested data.
Resource created successfully. Used for API key generation.
Request successful, no content returned. Used for delete operations.
Invalid request format, missing required fields, or malformed JSON.
Invalid or missing API key (X-API-KEY header) or Bearer token.
Resource not found. Check mockup_uuid, smart_object_uuid, or key_id.
Request body validation failed. Check field types and constraints.
Request limit exceeded. Check the Retry-After header and wait before retrying.
Unexpected server error. Safe to retry with backoff.
Error Response Format
Standard error responses (400, 401, 404, 500) follow this structure:
1{2 "success": false,3 "detail": "Human-readable error message"4}
Validation errors (422) have a different structure with detailed field information:
1{2 "detail": [3 {4 "loc": ["body", "field_name"],5 "msg": "Error message describing the issue",6 "type": "error_type"7 }8 ]9}
Error Examples by Endpoint
Upload PSD - POST /api/v1/psd/upload
400 Bad Request
Missing or invalid PSD file URL.
1// Request with invalid URL2POST /api/v1/psd/upload3{4 "psd_file_url": "not-a-valid-url"5}67// Response8{9 "success": false,10 "detail": "Invalid PSD file URL format"11}
401 Unauthorized
Missing or invalid X-API-KEY header.
1// Request without API key2POST /api/v1/psd/upload3X-API-KEY: (missing or invalid)45// Response6{7 "success": false,8 "detail": "Invalid or missing API key"9}
422 Validation Error
Request body validation failed.
1// Request missing required field2POST /api/v1/psd/upload3{4 "psd_name": "My Mockup"5 // Missing: psd_file_url (required)6}78// Response9{10 "detail": [11 {12 "loc": ["body", "psd_file_url"],13 "msg": "field required",14 "type": "value_error.missing"15 }16 ]17}
Render Mockup - POST /api/v1/renders
400 Bad Request
Invalid smart object configuration or asset URL.
1// Request with invalid asset URL2POST /api/v1/renders3{4 "mockup_uuid": "valid-uuid",5 "smart_objects": [{6 "uuid": "so-uuid",7 "asset": { "url": "not-accessible-url" }8 }]9}1011// Response12{13 "success": false,14 "detail": "Unable to fetch asset from provided URL"15}
404 Mockup Not Found
The specified mockup_uuid doesn't exist or doesn't belong to your account.
1// Request with invalid mockup_uuid2POST /api/v1/renders3{4 "mockup_uuid": "non-existent-uuid",5 "smart_objects": [...]6}78// Response9{10 "success": false,11 "detail": "Mockup not found"12}
422 Validation Error
Invalid field types or constraint violations.
1// Request with invalid field type2POST /api/v1/renders3{4 "mockup_uuid": "valid-uuid",5 "smart_objects": "should-be-array"6}78// Response9{10 "detail": [11 {12 "loc": ["body", "smart_objects"],13 "msg": "value is not a valid list",14 "type": "type_error.list"15 }16 ]17}1819// Request with invalid export options20POST /api/v1/renders21{22 "mockup_uuid": "valid-uuid",23 "smart_objects": [...],24 "export_options": {25 "image_size": 10000, // Max is 800026 "quality": 150 // Max is 10027 }28}2930// Response31{32 "detail": [33 {34 "loc": ["body", "export_options", "image_size"],35 "msg": "ensure this value is less than or equal to 8000",36 "type": "value_error.number.not_le"37 },38 {39 "loc": ["body", "export_options", "quality"],40 "msg": "ensure this value is less than or equal to 100",41 "type": "value_error.number.not_le"42 }43 ]44}
API Keys - /api/v1/api-keys
401 Unauthorized
Invalid or missing Bearer token (requires Supabase JWT).
1// Request without Bearer token2POST /api/v1/api-keys3Authorization: (missing)45// Response6{7 "success": false,8 "detail": "Invalid or missing Bearer token"9}
404 API Key Not Found
When deleting, updating, or regenerating a non-existent key.
1// Request to delete non-existent key2DELETE /api/v1/api-keys/non-existent-id34// Response5{6 "success": false,7 "detail": "API key not found"8}
422 Validation Error
Invalid API key name or expiration days.
1// Request with invalid expiration2POST /api/v1/api-keys3{4 "name": "My Key",5 "expires_in_days": 5000 // Max is 36506}78// Response9{10 "detail": [11 {12 "loc": ["body", "expires_in_days"],13 "msg": "ensure this value is less than or equal to 3650",14 "type": "value_error.number.not_le"15 }16 ]17}
500 Internal Server Error
Server errors are rare but can occur. They are safe to retry.
1// Response on server error2{3 "success": false,4 "detail": "An unexpected error occurred. Please try again."5}
Report Persistent Errors
Retry Strategy
Implement exponential backoff for failed requests:
1async function apiRequest(url, options, maxRetries = 3) {2 for (let attempt = 0; attempt < maxRetries; attempt++) {3 try {4 const response = await fetch(url, options);56 // Success7 if (response.ok) {8 return response.json();9 }1011 // Rate or concurrent limit exceeded - use Retry-After header12 if (response.status === 429) {13 const retryAfter = parseInt(response.headers.get('Retry-After') || '60');14 console.log(`Limit exceeded. Waiting ${retryAfter}s...`);15 await sleep(retryAfter * 1000);16 continue;17 }1819 // Server error - exponential backoff20 if (response.status >= 500) {21 const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s22 console.log(`Server error. Retrying in ${delay}ms...`);23 await sleep(delay);24 continue;25 }2627 // Client error - don't retry, throw immediately28 const error = await response.json();29 throw new Error(error.detail || JSON.stringify(error));3031 } catch (networkError) {32 // Network error - retry with backoff33 if (attempt < maxRetries - 1) {34 const delay = Math.pow(2, attempt) * 1000;35 await sleep(delay);36 continue;37 }38 throw networkError;39 }40 }4142 throw new Error('Max retries exceeded');43}4445function sleep(ms) {46 return new Promise(resolve => setTimeout(resolve, ms));47}4849// Optional: Check remaining requests before making calls50function checkRateLimitHeaders(response) {51 const limit = response.headers.get('RateLimit-Limit');52 const remaining = response.headers.get('RateLimit-Remaining');53 const reset = response.headers.get('RateLimit-Reset');5455 console.log(`Rate limit: ${remaining}/${limit} remaining, resets in ${reset}s`);5657 // Warning when running low58 if (remaining && parseInt(remaining) < 100) {59 console.warn('Rate limit running low, consider slowing down');60 }61}
When to Retry
- Always retry: 429, 500, 502, 503, 504, network errors
- Never retry: 400, 401, 404, 422 (fix the request first)
Handling Validation Errors
Parse 422 errors to identify exactly which field failed:
1async function handleRenderRequest(payload) {2 const response = await fetch('https://api.sudomock.com/api/v1/renders', {3 method: 'POST',4 headers: {5 'X-API-KEY': process.env.SUDOMOCK_API_KEY,6 'Content-Type': 'application/json'7 },8 body: JSON.stringify(payload)9 });1011 if (response.status === 422) {12 const error = await response.json();1314 // Parse validation errors15 for (const err of error.detail) {16 const fieldPath = err.loc.join('.');17 console.error(`Validation error at ${fieldPath}: ${err.msg}`);1819 // Example: "body.export_options.quality: ensure this value is less than or equal to 100"20 }2122 throw new Error('Validation failed: ' + error.detail.map(e => e.msg).join(', '));23 }2425 if (!response.ok) {26 const error = await response.json();27 throw new Error(error.detail);28 }2930 return response.json();31}
Parallel Limits by Plan
Field Constraints Reference
These are the validation constraints that can trigger 422 errors:
| Field | Type | Constraints |
|---|---|---|
| export_options.image_size | integer | 100 - 8000 |
| export_options.quality | integer | 1 - 100 |
| export_options.image_format | enum | png, jpg, webp |
| smart_objects[].asset.fit | enum | fill, contain, cover |
| smart_objects[].asset.rotate | integer | -360 to 360 |
| adjustment_layers.brightness | integer | -150 to 150 |
| adjustment_layers.contrast | integer | -100 to 100 |
| adjustment_layers.saturation | integer | -100 to 100 |
| adjustment_layers.vibrance | integer | -100 to 100 |
| adjustment_layers.opacity | integer | 0 - 100 |
| adjustment_layers.blur | integer | 0 - 100 |
| color.hex | string | Pattern: ^#[0-9A-Fa-f]{6}$ |
| api_key.name | string | 1 - 255 characters |
| api_key.expires_in_days | integer | 1 - 3650 (or null) |
Handling 429 Errors
A 429 response can occur for two reasons: rate limiting (too many requests per minute) or concurrent limiting (too many simultaneous operations). Check the error.type field to distinguish between them.
Rate Limit Exceeded
You've exceeded the request frequency limit (1000 requests/minute for authenticated users).
1{2 "detail": "Rate limit exceeded. Please slow down and try again.",3 "error": {4 "type": "rate_limit_exceeded",5 "code": "RATE_LIMIT_EXCEEDED",6 "limit": 1000,7 "remaining": 0,8 "retry_after": 429 }10}
Concurrent Limit Exceeded
You have too many simultaneous render or upload operations. Wait for current operations to complete.
1{2 "detail": "Max concurrent render requests exceeded. Your plan (Pro) allows 10 concurrent render request(s).",3 "error": {4 "type": "concurrent_limit_exceeded",5 "code": "CONCURRENT_LIMIT_EXCEEDED",6 "limit": 10,7 "current": 11,8 "remaining": 0,9 "resource": "concurrent-render"10 }11}
Response Headers (IETF Standard)
All responses include rate limit headers following the IETF standard:
| Header | Description | Example |
|---|---|---|
| RateLimit-Limit | Maximum requests per window | 1000 |
| RateLimit-Remaining | Requests remaining in current window | 487 |
| RateLimit-Reset | Seconds until window resets | 30 |
| Retry-After | Seconds to wait before retrying (429 only) | 5 |
Handling 429 in Code
1async function handle429(response) {2 const error = await response.json();34 // Get retry delay from header or error body5 const retryAfter = parseInt(6 response.headers.get('Retry-After') ||7 error.error?.retry_after ||8 '60'9 );1011 // Check error type12 if (error.error?.type === 'rate_limit_exceeded') {13 console.log(`Rate limited. Waiting ${retryAfter}s...`);14 } else if (error.error?.type === 'concurrent_limit_exceeded') {15 console.log(`Concurrent limit hit. Resource: ${error.error.resource}`);16 }1718 // Wait and retry19 await new Promise(r => setTimeout(r, retryAfter * 1000));20 return retry();21}
Always Respect Retry-After
Retry-After header and retrying immediately may result in extended rate limiting. Implement exponential backoff as a fallback.Need Higher Limits?
Contact us for enterprise parallel limits and dedicated support.