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?