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, malformed JSON, or PSD processing errors (file too large, invalid format, no smart objects).
Invalid or missing API key (x-api-key header), Bearer token, or Studio session token.
Insufficient credits. No subscription credits, extra credits, or PAYG available.
Access blocked due to suspicious activity, or resource not accessible with your authentication method.
Resource not found. Check mockup_uuid, smart_object_uuid, or key_id.
Request body validation failed. Check field types and constraints.
Rate limit or concurrent limit exceeded. Check the Retry-After header and wait before retrying.
Unexpected server error. Safe to retry with backoff.
Upstream image source error. The artwork URL you provided is unreachable or returned an error. Safe to retry.
Error Response Formats
The API uses five distinct response formats depending on the error type. Understanding which format to expect makes error handling more robust.
1. Standard Error (400, 401, 403, 404, 500, 502)
Most errors return this minimal structure. Raised by HTTPException handlers throughout the API.
1{2 "detail": "Human-readable error message",3 "success": false4}
2. PSD Processing Error (400, 500)
Returned by POST /api/v1/psd/upload when PSD parsing or processing fails. Includes a machine-readable error_code and a details object with a suggestion for how to fix the issue.
1{2 "error_code": "PSD_TOO_LARGE",3 "message": "PSD file too large (512.3MB)",4 "detail": "PSD file too large (512.3MB)",5 "details": {6 "file_size_mb": 512.3,7 "max_size_mb": 300.0,8 "suggestion": "Reduce file size to under 300.0MB or optimize layers"9 },10 "success": false11}
3. Validation Error (422)
Returned when request body validation fails. All validation messages are sanitized to prevent information disclosure. The errors array contains one entry per invalid field.
1{2 "detail": "Validation error",3 "errors": [4 {5 "field": "body -> psd_file_url",6 "message": "Missing required field: body -> psd_file_url"7 }8 ],9 "success": false10}
Sanitized Messages
4. Credit/Billing Error (402)
Returned when the user has no available credits and Pay-As-You-Go is not available. Includes actionable links and an optional credits_reset_at timestamp.
1{2 "error": "credits_exhausted",3 "message": "You've run out of credits for this billing period.",4 "actions": [5 {6 "label": "Enable Pay-As-You-Go",7 "url": "https://sudomock.com/dashboard/billing?action=enable-payg"8 },9 {10 "label": "Upgrade Plan",11 "url": "https://sudomock.com/pricing"12 }13 ],14 "credits_reset_at": "2026-04-01T00:00:00Z"15}
5. Rate Limit Error (429)
Returned when rate or concurrent limits are exceeded. The error.type field distinguishes between rate limiting and concurrent limiting.
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 "reset_seconds": 42,9 "retry_after": 42,10 "resource": "api"11 }12}
Note on PSD File Too Large
400 Bad Request with error code PSD_TOO_LARGE, not 413. This applies to all PSD validation errors including dimension limits.PSD Error Codes
The POST /api/v1/psd/upload endpoint returns structured PSD errors with a machine-readable error_code. Use this field for programmatic error handling.
| Error Code | HTTP | Description |
|---|---|---|
| PSD_DOWNLOAD_FAILED | 400 | Failed to download PSD file from URL. Check if the URL is accessible. |
| PSD_PARSE_FAILED | 400 | Invalid or corrupted PSD/PSB file. Ensure it was created with Adobe Photoshop. |
| PSD_TOO_LARGE | 400 | PSD file exceeds the 300MB size limit. Reduce file size or optimize layers. |
| DIMENSION_TOO_LARGE | 400 | PSD pixel dimensions exceed the maximum (10000x10000px). |
| NO_SMART_OBJECTS | 400 | PSD contains no Smart Object layers. Add at least one Smart Object in Photoshop. |
| UNSUPPORTED_FEATURE | 400 | PSD uses an unsupported Photoshop feature. Flatten or simplify the PSD. |
| UNSUPPORTED_SMART_OBJECT_FORMAT | 400 | Smart Object contains a vector format (AI, PDF, EPS, SVG). Rasterize the layer in Photoshop. |
| SMART_OBJECT_EXTRACTION_FAILED | 500 | Failed to extract Smart Object data. The Smart Object may be corrupted. |
| LAYER_RENDER_FAILED | 500 | Failed to render a layer. May contain unsupported effects or blend modes. |
| STORAGE_FAILED | 500 | Failed to store processed data. Retry the upload. |
| INTERNAL_ERROR | 500 | Unexpected processing error. Retry the upload or contact support. |
Each PSD error includes a details object with context-specific fields and a suggestion string:
1// Handling PSD error codes2if (response.status === 400 || response.status === 500) {3 const error = await response.json();45 if (error.error_code) {6 // PSD processing error7 switch (error.error_code) {8 case 'PSD_TOO_LARGE':9 console.error(`File is ${error.details.file_size_mb}MB, max is ${error.details.max_size_mb}MB`);10 break;11 case 'NO_SMART_OBJECTS':12 console.error(`Found ${error.details.total_layers} layers but no Smart Objects`);13 break;14 case 'UNSUPPORTED_SMART_OBJECT_FORMAT':15 console.error(`Layer "${error.details.layer_name}" uses ${error.details.format.toUpperCase()} format`);16 break;17 default:18 console.error(error.details?.suggestion || error.message);19 }20 }21}
Credit & Billing Errors (402)
When a request requires credits but none are available, the API returns 402 with an error field indicating the specific billing issue. Credits are only charged on successful requests. Failed requests (4xx, 5xx) are not charged.
| Error Type | Description | Suggested Action |
|---|---|---|
| credits_exhausted | Monthly subscription credits and extra credits are depleted, and PAYG is not enabled. | Enable Pay-As-You-Go or upgrade plan. |
| payment_required | A pending PAYG invoice could not be collected. API access is blocked until payment. | Pay the outstanding invoice or update payment method. |
| payg_limit_reached | PAYG spending limit for the current period has been reached. | Increase PAYG limit in billing settings or upgrade plan. |
| no_subscription | No active subscription found for this account. | Subscribe to a plan. |
1// Handling 402 responses2if (response.status === 402) {3 const error = await response.json();45 switch (error.error) {6 case 'credits_exhausted':7 // Show upgrade prompt or enable PAYG8 console.log('Credits depleted. Resets at:', error.credits_reset_at);9 break;10 case 'payment_required':11 // Redirect to billing - invoice needs payment12 console.error('Payment required:', error.message);13 break;14 case 'payg_limit_reached':15 // PAYG limit hit16 console.log('PAYG limit reached');17 break;18 case 'no_subscription':19 // No active plan20 console.log('No subscription found');21 break;22 }2324 // Actions array contains direct links25 for (const action of error.actions) {26 console.log(` ${action.label}: ${action.url}`);27 }28}
Error Examples by Endpoint
Upload PSD - POST /api/v1/psd/upload
400 Bad Request - PSD Validation Error
PSD file is too large, corrupted, or has no Smart Objects. Returns the PSDErrorResponse format.
1// Response - PSD too large2{3 "error_code": "PSD_TOO_LARGE",4 "message": "PSD file too large (512.3MB)",5 "detail": "PSD file too large (512.3MB)",6 "details": {7 "file_size_mb": 512.3,8 "max_size_mb": 300.0,9 "suggestion": "Reduce file size to under 300.0MB or optimize layers"10 },11 "success": false12}1314// Response - No Smart Objects15{16 "error_code": "NO_SMART_OBJECTS",17 "message": "PSD file contains no smart objects",18 "detail": "PSD file contains no smart objects",19 "details": {20 "total_layers": 12,21 "suggestion": "Add at least one Smart Object layer in Photoshop (Layer > Smart Objects > Convert to Smart Object)"22 },23 "success": false24}2526// Response - Vector Smart Object27{28 "error_code": "UNSUPPORTED_SMART_OBJECT_FORMAT",29 "message": "Smart object 'Logo' contains unsupported format: AI",30 "detail": "Smart object 'Logo' contains unsupported format: AI",31 "details": {32 "layer_name": "Logo",33 "format": "ai",34 "supported_formats": ["png", "jpg", "jpeg", "tif", "tiff", "gif", "bmp", "webp", "psd", "psb"],35 "suggestion": "Vector Smart Objects (AI) are not currently supported. To fix: In Photoshop, right-click the Smart Object layer > 'Rasterize Layer', then re-save your PSD."36 },37 "success": false38}
401 Unauthorized
Missing or invalid x-api-key header.
1// Response2{3 "detail": "Not authenticated",4 "success": false5}
422 Validation Error
Request body validation failed. Error messages are sanitized to prevent information disclosure.
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": "Validation error",11 "errors": [12 {"field": "body -> psd_file_url", "message": "Missing required field: body -> psd_file_url"}13 ],14 "success": false15}
Render Mockup - POST /api/v1/renders
404 Mockup Not Found
The specified mockup_uuid doesn't exist or doesn't belong to your account.
1// Response2{3 "detail": "Mockup not found",4 "success": false5}
422 Validation Error
Invalid field types or constraint violations. Messages are sanitized and do not expose internal validation details.
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": "Validation error",11 "errors": [12 {"field": "body -> smart_objects", "message": "Invalid type for field: body -> smart_objects"}13 ],14 "success": false15}1617// Request with invalid export options18POST /api/v1/renders19{20 "mockup_uuid": "valid-uuid",21 "smart_objects": [...],22 "export_options": {23 "image_size": 10000, // Max is 800024 "quality": 150 // Max is 10025 }26}2728// Response29{30 "detail": "Validation error",31 "errors": [32 {"field": "body -> export_options -> image_size", "message": "Invalid number for field: body -> export_options -> image_size"},33 {"field": "body -> export_options -> quality", "message": "Invalid number for field: body -> export_options -> quality"}34 ],35 "success": false36}
502 Bad Gateway - Image Source Error
The artwork URL you provided could not be fetched. The upstream image source returned an error or is unreachable. This is safe to retry.
1// Artwork URL returned 4042{3 "detail": "Image not found for 'Main Design'. example.com returned HTTP 404.",4 "success": false5}67// Artwork source is down8{9 "detail": "The image source for 'Main Design' is temporarily unavailable. cdn.example.com returned HTTP 503. Please try again later.",10 "success": false11}1213// Artwork URL is unreachable14{15 "detail": "Failed to download image for 'Main Design'. The image source may be unreachable.",16 "success": false17}
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 "detail": "Not authenticated",8 "success": false9}
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 "detail": "API key not found",7 "success": false8}
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": "Validation error",11 "errors": [12 {"field": "body -> expires_in_days", "message": "Invalid number for field: body -> expires_in_days"}13 ],14 "success": false15}
500 Internal Server Error
Server errors are rare but can occur. They are safe to retry with exponential backoff.
1// Generic server error (unhandled exception)2{3 "detail": "Internal server error",4 "success": false5}67// PSD processing server error (includes error_code)8{9 "error_code": "STORAGE_FAILED",10 "message": "Failed to store processed data",11 "detail": "Failed to store processed data",12 "details": {13 "reason": "Connection timeout",14 "suggestion": "Please try again. If the problem persists, contact support."15 },16 "success": false17}
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 or upstream error - exponential backoff20 if (response.status >= 500) {21 const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s22 console.log(`Server error ${response.status}. 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 || error.message || 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');54 const policy = response.headers.get('RateLimit-Policy');5556 console.log(`Rate limit: ${remaining}/${limit} remaining, resets in ${reset}s (policy: ${policy})`);5758 // Check concurrent usage59 const concurrentLimit = response.headers.get('X-Concurrent-Limit');60 const concurrentUsed = response.headers.get('X-Concurrent-Used');61 const concurrentRemaining = response.headers.get('X-Concurrent-Remaining');6263 if (concurrentLimit) {64 console.log(`Concurrent: ${concurrentUsed}/${concurrentLimit} in use, ${concurrentRemaining} remaining`);65 }6667 // Warning when running low68 if (remaining && parseInt(remaining) < 100) {69 console.warn('Rate limit running low, consider slowing down');70 }71}
When to Retry
- Always retry: 429, 500, 502, 503, 504, network errors
- Never retry: 400, 401, 402, 403, 404, 422 (fix the request or billing 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 sanitized validation errors15 for (const err of error.errors) {16 console.error(`Validation error at ${err.field}: ${err.message}`);1718 // Example message: "Invalid number for field: body -> export_options -> quality"19 }2021 throw new Error('Validation failed: ' + error.errors.map(e => e.message).join(', '));22 }2324 if (!response.ok) {25 const error = await response.json();26 throw new Error(error.detail || error.message);27 }2829 return response.json();30}
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. The API uses a token bucket algorithm with a sustained rate of 1,000 requests/minute and a burst capacity of 1,500 requests. Tokens refill at ~16.67/second, so short bursts above 1,000 RPM are allowed as long as the bucket has tokens.
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 "reset_seconds": 42,9 "retry_after": 42,10 "resource": "api"11 }12}
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
All authenticated API responses include rate limit headers following the IETF draft standard (RateLimit-*) and concurrent limit headers (X-Concurrent-*):
| 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 |
| RateLimit-Policy | Rate limit policy string (limit and window) | 1000;w=60 |
| X-Concurrent-Limit | Maximum concurrent requests allowed by your plan | 10 |
| X-Concurrent-Used | Number of concurrent requests currently in progress | 3 |
| X-Concurrent-Remaining | Concurrent request slots remaining | 7 |
| Retry-After | Seconds to wait before retrying (429 only) | 42 |
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. For rate limit errors, Retry-After is calculated dynamically based on token refill rate. For concurrent limit errors, it is a fixed 5 seconds. Implement exponential backoff as a fallback.Need Higher Limits?
Contact us for enterprise parallel limits and dedicated support.