Skip to main content

Error Handling

This guide explains the error response format, common error codes, and strategies for handling errors in your integration.

Error Response Format

All error responses follow a consistent structure:

{
"error": {
"code": "invalid_api_key",
"message": "Invalid API key",
"details": {},
"retry_after": 45,
"request_id": "req_abc123"
}
}
FieldTypeDescription
codestringMachine-readable error code for programmatic handling
messagestringHuman-readable description
detailsobjectAdditional context (varies by error)
retry_afternumberSeconds to wait before retrying (for 429 errors)
request_idstringUnique ID for support debugging

Error Codes Reference

Authentication Errors (401)

CodeMessageCauseSolution
missing_authAuthorization header requiredNo Authorization headerAdd Authorization: Bearer <key> header
invalid_auth_formatExpected 'Bearer <api_key>'Wrong header formatUse Bearer <key>, not just the key
invalid_api_keyInvalid API keyKey doesn't exist or is incorrectCheck for typos, verify key
revoked_api_keyAPI key has been revokedKey was revoked in portalCreate a new key

Example handling:

async function makeRequest(endpoint, data) {
const response = await fetch(`https://api.cmfy.cloud${endpoint}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});

if (response.status === 401) {
const { error } = await response.json();

switch (error.code) {
case 'missing_auth':
case 'invalid_auth_format':
throw new Error('API key not properly configured');
case 'invalid_api_key':
throw new Error('Check your API key is correct');
case 'revoked_api_key':
throw new Error('Create a new API key in the portal');
}
}

return response.json();
}

Authorization Errors (403)

CodeMessageCauseSolution
account_suspendedAccount suspended - contact supportAccount issueContact support@cmfy.cloud
insufficient_permissionsInsufficient permissionsAction not allowed for your tierUpgrade your plan

Validation Errors (400)

CodeMessageCause
invalid_jsonRequest body is not valid JSONMalformed JSON
missing_promptRequired 'prompt' field missingNo prompt in request body
invalid_prompt_type'prompt' must be an objectprompt is not a JSON object
empty_workflowWorkflow has no nodesprompt object is empty
too_many_nodesWorkflow exceeds 500 nodesToo many nodes in workflow
missing_class_typeNode missing 'class_type'Node lacks required field
missing_inputsNode missing 'inputs'Node lacks required field
invalid_urlURL in workflow is invalidMalformed URL
insecure_urlOnly HTTPS URLs are allowedHTTP URL in workflow
webhook_must_be_httpsWebhook URL must use HTTPSHTTP webhook URL
webhook_private_hostWebhook cannot use private IPlocalhost or private IP

Example handling:

import requests
import logging

logger = logging.getLogger(__name__)

def submit_job(workflow, webhook_url=None):
response = requests.post(
'https://api.cmfy.cloud/v1/jobs',
headers={'Authorization': f'Bearer {API_KEY}'},
json={'prompt': workflow, 'webhook_url': webhook_url}
)

if response.status_code == 400:
error = response.json()['error']

# Log detailed error for debugging
logger.error(f"Validation error: {error['code']} - {error['message']}")

# Handle specific cases
if error['code'] == 'missing_prompt':
raise ValueError("Workflow cannot be empty")
elif error['code'] == 'too_many_nodes':
raise ValueError("Workflow too complex, split into smaller jobs")
else:
raise ValueError(f"Invalid request: {error['message']}")

response.raise_for_status()
return response.json()

Rate Limit Errors (429)

CodeMessageCause
rate_limit_exceededRate limit exceededToo many requests per minute
concurrent_limit_reachedMaximum concurrent jobs reachedToo many running jobs
queue_fullQueue limit reachedPersonal queue is full

Rate limit responses include helpful headers:

HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1699574400

Example handling with retry:

async function submitWithRetry(workflow, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch('https://api.cmfy.cloud/v1/jobs', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ prompt: workflow })
});

if (response.status !== 429) {
return response.json();
}

const { error } = await response.json();

// Handle different 429 types
if (error.code === 'rate_limit_exceeded') {
// Wait for rate limit window to reset
const retryAfter = error.retry_after || 60;
console.log(`Rate limited, waiting ${retryAfter}s`);
await sleep(retryAfter * 1000);

} else if (error.code === 'concurrent_limit_reached') {
// Wait for a running job to complete
console.log('At concurrent limit, waiting for job to finish');
await sleep(5000);

} else if (error.code === 'queue_full') {
// Queue is full, wait longer
console.log('Queue full, waiting for jobs to process');
await sleep(10000);
}
}

throw new Error('Max retries exceeded');
}

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

Not Found Errors (404)

CodeMessageCause
job_not_foundJob not foundJob ID doesn't exist or belongs to another user
def get_job_status(job_id):
response = requests.get(
f'https://api.cmfy.cloud/v1/jobs/{job_id}',
headers={'Authorization': f'Bearer {API_KEY}'}
)

if response.status_code == 404:
raise JobNotFoundError(f"Job {job_id} not found")

return response.json()

Server Errors (5xx)

HTTPCodeMessageCause
500internal_errorInternal server errorUnexpected error
503service_unavailableService temporarily unavailableMaintenance or overload

Server errors are rare but should be handled:

def submit_job_with_error_handling(workflow):
try:
response = requests.post(
'https://api.cmfy.cloud/v1/jobs',
headers={'Authorization': f'Bearer {API_KEY}'},
json={'prompt': workflow},
timeout=30
)

if response.status_code >= 500:
# Server error - retry with backoff
raise ServerError(response.json()['error']['message'])

response.raise_for_status()
return response.json()

except requests.exceptions.Timeout:
raise ServerError("Request timed out")
except requests.exceptions.ConnectionError:
raise ServerError("Could not connect to server")

Retry Strategies

Exponential Backoff

The recommended retry strategy for transient errors:

import time
import random

def exponential_backoff(attempt, base_delay=1, max_delay=60):
"""Calculate delay with jitter."""
delay = min(base_delay * (2 ** attempt), max_delay)
jitter = random.uniform(0, delay * 0.1)
return delay + jitter

def submit_with_backoff(workflow, max_retries=5):
for attempt in range(max_retries):
try:
response = requests.post(
'https://api.cmfy.cloud/v1/jobs',
headers={'Authorization': f'Bearer {API_KEY}'},
json={'prompt': workflow}
)

if response.status_code == 429:
# Use Retry-After if provided, otherwise backoff
retry_after = response.json()['error'].get('retry_after')
delay = retry_after or exponential_backoff(attempt)
time.sleep(delay)
continue

if response.status_code >= 500:
# Server error - retry with backoff
time.sleep(exponential_backoff(attempt))
continue

response.raise_for_status()
return response.json()

except requests.exceptions.RequestException:
if attempt == max_retries - 1:
raise
time.sleep(exponential_backoff(attempt))

raise Exception("Max retries exceeded")

Which Errors to Retry

Error TypeShould Retry?Notes
400 (validation)NoFix the request first
401 (auth)NoFix credentials
403 (forbidden)NoContact support
404 (not found)NoResource doesn't exist
429 (rate limit)YesUse Retry-After header
500 (server error)YesWith exponential backoff
503 (unavailable)YesWith exponential backoff
TimeoutYesMay need longer timeout
Connection errorYesWith exponential backoff

Error Logging Best Practices

Log Useful Context

import logging

logger = logging.getLogger(__name__)

def handle_api_error(response, context=None):
error = response.json().get('error', {})

log_data = {
'status_code': response.status_code,
'error_code': error.get('code'),
'error_message': error.get('message'),
'request_id': error.get('request_id'),
**(context or {})
}

if response.status_code >= 500:
logger.error("Server error", extra=log_data)
elif response.status_code == 429:
logger.warning("Rate limited", extra=log_data)
else:
logger.info("API error", extra=log_data)

Building Robust Clients

Complete Error Handler

class CmfyCloudError(Exception):
"""Base exception for cmfy.cloud API errors."""

def __init__(self, code, message, status_code=None, request_id=None):
super().__init__(message)
self.code = code
self.message = message
self.status_code = status_code
self.request_id = request_id

class AuthenticationError(CmfyCloudError):
"""Raised for 401 errors."""
pass

class ValidationError(CmfyCloudError):
"""Raised for 400 errors."""
pass

class RateLimitError(CmfyCloudError):
"""Raised for 429 errors."""

def __init__(self, code, message, retry_after=None, **kwargs):
super().__init__(code, message, **kwargs)
self.retry_after = retry_after

class ServerError(CmfyCloudError):
"""Raised for 5xx errors."""
pass

def handle_response(response):
"""Convert API response to appropriate exception or return data."""
if response.ok:
return response.json()

error = response.json().get('error', {})
kwargs = {
'code': error.get('code', 'unknown'),
'message': error.get('message', 'Unknown error'),
'status_code': response.status_code,
'request_id': error.get('request_id')
}

if response.status_code == 401:
raise AuthenticationError(**kwargs)
elif response.status_code == 400:
raise ValidationError(**kwargs)
elif response.status_code == 429:
raise RateLimitError(retry_after=error.get('retry_after'), **kwargs)
elif response.status_code >= 500:
raise ServerError(**kwargs)
else:
raise CmfyCloudError(**kwargs)

Using the Error Handler

def main():
try:
result = submit_job(my_workflow)
print(f"Job submitted: {result['job_id']}")

except AuthenticationError as e:
print(f"Auth failed: {e.message}")
print("Check your API key in environment variables")

except ValidationError as e:
print(f"Invalid request: {e.message}")
# Log the full error for debugging
logger.error(f"Validation error: {e.code}", extra={
'request_id': e.request_id
})

except RateLimitError as e:
print(f"Rate limited: {e.message}")
if e.retry_after:
print(f"Retry after {e.retry_after} seconds")

except ServerError as e:
print("Server error, please try again later")
# Report to monitoring
report_server_error(e.request_id)

What's Next?

Was this page helpful?