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