Error Reference

HTTP status codes, error response format, and how to handle common errors.

Error Response Format

All API errors return a consistent JSON structure:

{
  "error": "Human-readable error message",
  "message": "Additional details about the error",
  "requestId": "req_abc123"
}
Tip

Include the requestId when contacting support — it helps us locate and debug your specific request.

HTTP Status Codes

Status Meaning Description
400 Bad Request Invalid input, missing required fields, invalid parties, content too large
401 Unauthorized Missing or invalid API key, expired key
403 Forbidden Valid key but insufficient permissions (wrong scope, not contract owner)
404 Not Found Contract ID doesn't exist or you don't have access
429 Too Many Requests Rate limit exceeded or monthly quota exhausted
500 Internal Server Error Server-side issue — retry with exponential backoff

Common Errors

Error Message Status Cause Fix
"Missing or invalid parties" 400 parties is missing, not an array, or has fewer than 2 entries Provide a parties array with at least 2 entries, each with role, legalName, email
"Party at index N missing required fields" 400 A party in the array is missing role, legalName, or email Include all required fields for each party
"Invalid email format for party at index N" 400 Email address for a party is malformed Provide a valid email address
"Invalid contract type" 400 contractType not one of the 8 valid types Use a valid type: nda, business, employment, service, custom, sales-agreement, consignment-agreement, revenue-share
"Missing or invalid content" 400 content is missing or not a string Provide contract content as a non-empty string
"Contract content exceeds maximum size (50KB)" 400 content string is larger than 50KB Reduce content size
"Invalid API key" 401 x-api-key header missing or key doesn't exist Check your API key is correct
"API key is not active" 401 Key was revoked or disabled Create a new key in Settings → API Access
"Insufficient permissions" 403 Key doesn't have the required scope Create a key with the needed scope (contracts:write for creating, contracts:read for fetching)
"Contract not found" 404 ID doesn't exist or belongs to another account Verify the contract ID
"Rate limit exceeded" 429 Too many requests per minute Wait and retry with backoff
"Monthly quota exceeded" 429 Monthly API call quota reached Upgrade your plan or wait for next billing period

Rate Limit Headers

When you receive a 429 response, check these headers to understand your limits:

Header Description
RateLimit-Limit Your per-minute request limit
RateLimit-Remaining Requests remaining in current window
RateLimit-Reset Seconds until the rate limit resets

Handling Errors

Always check the HTTP status code and handle errors gracefully. Here is an example with exponential backoff for rate limits:

async function createContract(data) { try { const response = await fetch( 'https://api.draftory.ca/api/external/v2/contracts', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.DRAFTORY_API_KEY }, body: JSON.stringify(data) } ); if (!response.ok) { const err = await response.json(); switch (response.status) { case 401: console.error('Auth error:', err.error); break; case 429: const retryAfter = response.headers.get('RateLimit-Reset'); console.error(`Rate limited. Retry in ${retryAfter}s`); break; default: console.error(`Error ${response.status}:`, err.error); } return null; } return await response.json(); } catch (error) { console.error('Network error:', error.message); return null; } }
import requests import os def create_contract(data): try: response = requests.post( 'https://api.draftory.ca/api/external/v2/contracts', headers={ 'Content-Type': 'application/json', 'x-api-key': os.environ['DRAFTORY_API_KEY'] }, json=data ) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as e: status = e.response.status_code err = e.response.json() if status == 401: print(f'Auth error: {err["error"]}') elif status == 429: retry_after = e.response.headers.get('RateLimit-Reset') print(f'Rate limited. Retry in {retry_after}s') else: print(f'Error {status}: {err["error"]}') return None except requests.exceptions.ConnectionError as e: print(f'Network error: {e}') return None