SudoMock
API Reference

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.

200OK

Request successful. Response contains requested data.

201Created

Resource created successfully. Used for API key generation.

204No Content

Request successful, no content returned. Used for delete operations.

400Bad Request

Invalid request format, missing required fields, malformed JSON, or PSD processing errors (file too large, invalid format, no smart objects).

401Unauthorized

Invalid or missing API key (x-api-key header), Bearer token, or Studio session token.

402Payment Required

Insufficient credits. No subscription credits, extra credits, or PAYG available.

403Forbidden

Access blocked due to suspicious activity, or resource not accessible with your authentication method.

404Not Found

Resource not found. Check mockup_uuid, smart_object_uuid, or key_id.

422Validation Error

Request body validation failed. Check field types and constraints.

429Too Many Requests

Rate limit or concurrent limit exceeded. Check the Retry-After header and wait before retrying.

500Internal Server Error

Unexpected server error. Safe to retry with backoff.

502Bad Gateway

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.

ErrorResponse
1{
2 "detail": "Human-readable error message",
3 "success": false
4}

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.

PSDErrorResponse
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": false
11}

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.

ValidationErrorResponse (422)
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": false
10}

Sanitized Messages

Validation messages follow a fixed set of patterns: "Missing required field", "Invalid value for field", "Invalid type for field", "Invalid string format for field", "Invalid number for field", or "Validation error in field". Raw Pydantic error messages are never exposed.

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.

CreditErrorResponse (402)
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.

RateLimitErrorResponse (429)
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

PSD files exceeding the size limit (300MB) return 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 CodeHTTPDescription
PSD_DOWNLOAD_FAILED400Failed to download PSD file from URL. Check if the URL is accessible.
PSD_PARSE_FAILED400Invalid or corrupted PSD/PSB file. Ensure it was created with Adobe Photoshop.
PSD_TOO_LARGE400PSD file exceeds the 300MB size limit. Reduce file size or optimize layers.
DIMENSION_TOO_LARGE400PSD pixel dimensions exceed the maximum (10000x10000px).
NO_SMART_OBJECTS400PSD contains no Smart Object layers. Add at least one Smart Object in Photoshop.
UNSUPPORTED_FEATURE400PSD uses an unsupported Photoshop feature. Flatten or simplify the PSD.
UNSUPPORTED_SMART_OBJECT_FORMAT400Smart Object contains a vector format (AI, PDF, EPS, SVG). Rasterize the layer in Photoshop.
SMART_OBJECT_EXTRACTION_FAILED500Failed to extract Smart Object data. The Smart Object may be corrupted.
LAYER_RENDER_FAILED500Failed to render a layer. May contain unsupported effects or blend modes.
STORAGE_FAILED500Failed to store processed data. Retry the upload.
INTERNAL_ERROR500Unexpected processing error. Retry the upload or contact support.

Each PSD error includes a details object with context-specific fields and a suggestion string:

Handling PSD Error Codes
1// Handling PSD error codes
2if (response.status === 400 || response.status === 500) {
3 const error = await response.json();
4
5 if (error.error_code) {
6 // PSD processing error
7 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 TypeDescriptionSuggested Action
credits_exhaustedMonthly subscription credits and extra credits are depleted, and PAYG is not enabled.Enable Pay-As-You-Go or upgrade plan.
payment_requiredA pending PAYG invoice could not be collected. API access is blocked until payment.Pay the outstanding invoice or update payment method.
payg_limit_reachedPAYG spending limit for the current period has been reached.Increase PAYG limit in billing settings or upgrade plan.
no_subscriptionNo active subscription found for this account.Subscribe to a plan.
Handling 402 Credit Errors
1// Handling 402 responses
2if (response.status === 402) {
3 const error = await response.json();
4
5 switch (error.error) {
6 case 'credits_exhausted':
7 // Show upgrade prompt or enable PAYG
8 console.log('Credits depleted. Resets at:', error.credits_reset_at);
9 break;
10 case 'payment_required':
11 // Redirect to billing - invoice needs payment
12 console.error('Payment required:', error.message);
13 break;
14 case 'payg_limit_reached':
15 // PAYG limit hit
16 console.log('PAYG limit reached');
17 break;
18 case 'no_subscription':
19 // No active plan
20 console.log('No subscription found');
21 break;
22 }
23
24 // Actions array contains direct links
25 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.

400 PSD Processing Errors
1// Response - PSD too large
2{
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": false
12}
13
14// Response - No Smart Objects
15{
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": false
24}
25
26// Response - Vector Smart Object
27{
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": false
38}

401 Unauthorized

Missing or invalid x-api-key header.

401 Unauthorized
1// Response
2{
3 "detail": "Not authenticated",
4 "success": false
5}

422 Validation Error

Request body validation failed. Error messages are sanitized to prevent information disclosure.

422 Validation Error - Missing Field
1// Request missing required field
2POST /api/v1/psd/upload
3{
4 "psd_name": "My Mockup"
5 // Missing: psd_file_url (required)
6}
7
8// Response
9{
10 "detail": "Validation error",
11 "errors": [
12 {"field": "body -> psd_file_url", "message": "Missing required field: body -> psd_file_url"}
13 ],
14 "success": false
15}

Render Mockup - POST /api/v1/renders

404 Mockup Not Found

The specified mockup_uuid doesn't exist or doesn't belong to your account.

404 Mockup Not Found
1// Response
2{
3 "detail": "Mockup not found",
4 "success": false
5}

422 Validation Error

Invalid field types or constraint violations. Messages are sanitized and do not expose internal validation details.

422 Validation Error - Invalid Values
1// Request with invalid field type
2POST /api/v1/renders
3{
4 "mockup_uuid": "valid-uuid",
5 "smart_objects": "should-be-array"
6}
7
8// Response
9{
10 "detail": "Validation error",
11 "errors": [
12 {"field": "body -> smart_objects", "message": "Invalid type for field: body -> smart_objects"}
13 ],
14 "success": false
15}
16
17// Request with invalid export options
18POST /api/v1/renders
19{
20 "mockup_uuid": "valid-uuid",
21 "smart_objects": [...],
22 "export_options": {
23 "image_size": 10000, // Max is 8000
24 "quality": 150 // Max is 100
25 }
26}
27
28// Response
29{
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": false
36}

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.

502 Bad Gateway - Image Source Errors
1// Artwork URL returned 404
2{
3 "detail": "Image not found for 'Main Design'. example.com returned HTTP 404.",
4 "success": false
5}
6
7// Artwork source is down
8{
9 "detail": "The image source for 'Main Design' is temporarily unavailable. cdn.example.com returned HTTP 503. Please try again later.",
10 "success": false
11}
12
13// Artwork URL is unreachable
14{
15 "detail": "Failed to download image for 'Main Design'. The image source may be unreachable.",
16 "success": false
17}

API Keys - /api/v1/api-keys

401 Unauthorized

Invalid or missing Bearer token (requires Supabase JWT).

401 Unauthorized - Bearer Token
1// Request without Bearer token
2POST /api/v1/api-keys
3Authorization: (missing)
4
5// Response
6{
7 "detail": "Not authenticated",
8 "success": false
9}

404 API Key Not Found

When deleting, updating, or regenerating a non-existent key.

404 API Key Not Found
1// Request to delete non-existent key
2DELETE /api/v1/api-keys/non-existent-id
3
4// Response
5{
6 "detail": "API key not found",
7 "success": false
8}

422 Validation Error

Invalid API key name or expiration days.

422 Validation Error - API Key
1// Request with invalid expiration
2POST /api/v1/api-keys
3{
4 "name": "My Key",
5 "expires_in_days": 5000 // Max is 3650
6}
7
8// Response
9{
10 "detail": "Validation error",
11 "errors": [
12 {"field": "body -> expires_in_days", "message": "Invalid number for field: body -> expires_in_days"}
13 ],
14 "success": false
15}

500 Internal Server Error

Server errors are rare but can occur. They are safe to retry with exponential backoff.

500 Internal Server Error
1// Generic server error (unhandled exception)
2{
3 "detail": "Internal server error",
4 "success": false
5}
6
7// 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": false
17}

Report Persistent Errors

If you encounter repeated 500 errors, please contact us at [email protected] with your request details.

Retry Strategy

Implement exponential backoff for failed requests:

Retry with Exponential Backoff
1async function apiRequest(url, options, maxRetries = 3) {
2 for (let attempt = 0; attempt < maxRetries; attempt++) {
3 try {
4 const response = await fetch(url, options);
5
6 // Success
7 if (response.ok) {
8 return response.json();
9 }
10
11 // Rate or concurrent limit exceeded - use Retry-After header
12 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 }
18
19 // Server error or upstream error - exponential backoff
20 if (response.status >= 500) {
21 const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
22 console.log(`Server error ${response.status}. Retrying in ${delay}ms...`);
23 await sleep(delay);
24 continue;
25 }
26
27 // Client error - don't retry, throw immediately
28 const error = await response.json();
29 throw new Error(error.detail || error.message || JSON.stringify(error));
30
31 } catch (networkError) {
32 // Network error - retry with backoff
33 if (attempt < maxRetries - 1) {
34 const delay = Math.pow(2, attempt) * 1000;
35 await sleep(delay);
36 continue;
37 }
38 throw networkError;
39 }
40 }
41
42 throw new Error('Max retries exceeded');
43}
44
45function sleep(ms) {
46 return new Promise(resolve => setTimeout(resolve, ms));
47}
48
49// Optional: Check remaining requests before making calls
50function 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');
55
56 console.log(`Rate limit: ${remaining}/${limit} remaining, resets in ${reset}s (policy: ${policy})`);
57
58 // Check concurrent usage
59 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');
62
63 if (concurrentLimit) {
64 console.log(`Concurrent: ${concurrentUsed}/${concurrentLimit} in use, ${concurrentRemaining} remaining`);
65 }
66
67 // Warning when running low
68 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:

Parsing Validation Errors
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 });
10
11 if (response.status === 422) {
12 const error = await response.json();
13
14 // Parse sanitized validation errors
15 for (const err of error.errors) {
16 console.error(`Validation error at ${err.field}: ${err.message}`);
17
18 // Example message: "Invalid number for field: body -> export_options -> quality"
19 }
20
21 throw new Error('Validation failed: ' + error.errors.map(e => e.message).join(', '));
22 }
23
24 if (!response.ok) {
25 const error = await response.json();
26 throw new Error(error.detail || error.message);
27 }
28
29 return response.json();
30}

Parallel Limits by Plan

Free
Renders1
Uploads1
Starter
Renders3
Uploads2
Popular
Pro
Renders10
Uploads5
Scale
Renders25
Uploads10

Field Constraints Reference

These are the validation constraints that can trigger 422 errors:

FieldTypeConstraints
export_options.image_sizeinteger100 - 8000
export_options.qualityinteger1 - 100
export_options.image_formatenumpng, jpg, webp
smart_objects[].asset.fitenumfill, contain, cover
smart_objects[].asset.rotateinteger-360 to 360
adjustment_layers.brightnessinteger-150 to 150
adjustment_layers.contrastinteger-100 to 100
adjustment_layers.saturationinteger-100 to 100
adjustment_layers.vibranceinteger-100 to 100
adjustment_layers.opacityinteger0 - 100
adjustment_layers.blurinteger0 - 100
color.hexstringPattern: ^#[0-9A-Fa-f]{6}$
api_key.namestring1 - 255 characters
api_key.expires_in_daysinteger1 - 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.

429 Rate Limit Exceeded
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.

429 Concurrent Limit Exceeded
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-*):

HeaderDescriptionExample
RateLimit-LimitMaximum requests per window1000
RateLimit-RemainingRequests remaining in current window487
RateLimit-ResetSeconds until window resets30
RateLimit-PolicyRate limit policy string (limit and window)1000;w=60
X-Concurrent-LimitMaximum concurrent requests allowed by your plan10
X-Concurrent-UsedNumber of concurrent requests currently in progress3
X-Concurrent-RemainingConcurrent request slots remaining7
Retry-AfterSeconds to wait before retrying (429 only)42

Handling 429 in Code

429 Response Handling
1async function handle429(response) {
2 const error = await response.json();
3
4 // Get retry delay from header or error body
5 const retryAfter = parseInt(
6 response.headers.get('Retry-After') ||
7 error.error?.retry_after ||
8 '60'
9 );
10
11 // Check error type
12 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 }
17
18 // Wait and retry
19 await new Promise(r => setTimeout(r, retryAfter * 1000));
20 return retry();
21}

Always Respect Retry-After

Ignoring the 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.

Error Codes & Handling | SudoMock Docs