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, or malformed JSON.

401Unauthorized

Invalid or missing API key (X-API-KEY header) or Bearer token.

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

Request limit exceeded. Check the Retry-After header and wait before retrying.

500Internal Server Error

Unexpected server error. Safe to retry with backoff.

Error Response Format

Standard error responses (400, 401, 404, 500) follow this structure:

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

Validation errors (422) have a different structure with detailed field information:

HTTPValidationError Schema (422)
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.

400 Bad Request - Upload
1// Request with invalid URL
2POST /api/v1/psd/upload
3{
4 "psd_file_url": "not-a-valid-url"
5}
6
7// Response
8{
9 "success": false,
10 "detail": "Invalid PSD file URL format"
11}

401 Unauthorized

Missing or invalid X-API-KEY header.

401 Unauthorized
1// Request without API key
2POST /api/v1/psd/upload
3X-API-KEY: (missing or invalid)
4
5// Response
6{
7 "success": false,
8 "detail": "Invalid or missing API key"
9}

422 Validation Error

Request body validation failed.

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": [
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.

400 Bad Request - Render
1// Request with invalid asset URL
2POST /api/v1/renders
3{
4 "mockup_uuid": "valid-uuid",
5 "smart_objects": [{
6 "uuid": "so-uuid",
7 "asset": { "url": "not-accessible-url" }
8 }]
9}
10
11// Response
12{
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.

404 Mockup Not Found
1// Request with invalid mockup_uuid
2POST /api/v1/renders
3{
4 "mockup_uuid": "non-existent-uuid",
5 "smart_objects": [...]
6}
7
8// Response
9{
10 "success": false,
11 "detail": "Mockup not found"
12}

422 Validation Error

Invalid field types or constraint violations.

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": [
11 {
12 "loc": ["body", "smart_objects"],
13 "msg": "value is not a valid list",
14 "type": "type_error.list"
15 }
16 ]
17}
18
19// Request with invalid export options
20POST /api/v1/renders
21{
22 "mockup_uuid": "valid-uuid",
23 "smart_objects": [...],
24 "export_options": {
25 "image_size": 10000, // Max is 8000
26 "quality": 150 // Max is 100
27 }
28}
29
30// Response
31{
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).

401 Unauthorized - Bearer Token
1// Request without Bearer token
2POST /api/v1/api-keys
3Authorization: (missing)
4
5// Response
6{
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.

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 "success": false,
7 "detail": "API key not found"
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": [
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.

500 Internal Server Error
1// Response on server error
2{
3 "success": false,
4 "detail": "An unexpected error occurred. Please try again."
5}

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 - exponential backoff
20 if (response.status >= 500) {
21 const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
22 console.log(`Server error. 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 || 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
55 console.log(`Rate limit: ${remaining}/${limit} remaining, resets in ${reset}s`);
56
57 // Warning when running low
58 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:

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 validation errors
15 for (const err of error.detail) {
16 const fieldPath = err.loc.join('.');
17 console.error(`Validation error at ${fieldPath}: ${err.msg}`);
18
19 // Example: "body.export_options.quality: ensure this value is less than or equal to 100"
20 }
21
22 throw new Error('Validation failed: ' + error.detail.map(e => e.msg).join(', '));
23 }
24
25 if (!response.ok) {
26 const error = await response.json();
27 throw new Error(error.detail);
28 }
29
30 return response.json();
31}

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 (1000 requests/minute for authenticated users).

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 "retry_after": 42
9 }
10}

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 (IETF Standard)

All responses include rate limit headers following the IETF standard:

HeaderDescriptionExample
RateLimit-LimitMaximum requests per window1000
RateLimit-RemainingRequests remaining in current window487
RateLimit-ResetSeconds until window resets30
Retry-AfterSeconds to wait before retrying (429 only)5

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. Implement exponential backoff as a fallback.

Need Higher Limits?

Contact us for enterprise parallel limits and dedicated support.