Error Handling
Understand the Unmarkdown API error response format, all error codes, and retry strategies with code examples.
Error Response Format
All API errors return a JSON response body with a consistent structure. The response includes an error object containing a machine-readable code, a human-readable message, and the HTTP status.
{
"error": {
"code": "unauthorized",
"message": "Invalid or missing API key",
"status": 401
}
}The code field is a stable string identifier for use in your error handling logic. The message field may change over time, so do not rely on exact message text.
Error Codes
The complete list of error codes returned by the API:
- invalid_body (400): Request body is malformed or not valid JSON
- validation_error (400): Required field missing or field value invalid
- unauthorized (401): API key is missing, invalid, or revoked
- forbidden (403): Authenticated but not authorized (wrong account, Pro-only feature on Free)
- not_found (404): Resource does not exist
- conflict (409): Resource was modified since last read (optimistic locking)
- quota_exceeded (429): Monthly API quota exceeded
- rate_limited (429): Too many requests per second
- internal_error (500): Unexpected server error
- conversion_error (500): Markdown conversion pipeline failed
- render_error (500): Template rendering failed
HTTP Status Codes
400 Bad Request
The request is malformed or missing required fields. Check the error message for which field is invalid.
401 Unauthorized
The API key is missing, invalid, or revoked. Verify your Authorization header.
403 Forbidden
Authenticated but not authorized. The resource belongs to another account, or the feature requires Pro.
404 Not Found
The requested resource does not exist. Verify the resource ID.
409 Conflict
The resource was modified since you last read it (optimistic locking). Re-fetch and retry.
429 Too Many Requests
Rate limit or monthly quota exceeded. Check the Retry-After header and the error code (rate_limited vs quota_exceeded).
500 Internal Server Error
Unexpected server error. Retry with exponential backoff.
Retry Logic
Use the status code to determine whether to retry:
- 400, 401, 403, 404: Do not retry. Fix the request first.
- 409: Re-fetch the resource, apply your changes, and retry.
- 429 (rate_limited): Retry after the Retry-After header delay.
- 429 (quota_exceeded): Do not retry until next month or upgrade.
- 500: Retry with exponential backoff (max 3-5 attempts).
Error Handling (JavaScript)
async function apiCall(url, options) {
const response = await fetch(url, options);
if (response.ok) return response.json();
const error = await response.json();
switch (response.status) {
case 429:
if (error.error.code === "rate_limited") {
const retryAfter = response.headers.get("Retry-After") || "1";
await new Promise((r) => setTimeout(r, parseInt(retryAfter) * 1000));
return apiCall(url, options); // retry once
}
throw new Error(`Quota exceeded: ${error.error.message}`);
case 401:
throw new Error("Invalid API key");
default:
throw new Error(`API error ${response.status}: ${error.error.message}`);
}
}Error Handling (Python)
import requests, time
def api_call(url, headers):
response = requests.get(url, headers=headers)
if response.ok:
return response.json()
error = response.json()["error"]
if response.status_code == 429 and error["code"] == "rate_limited":
retry_after = int(response.headers.get("Retry-After", "1"))
time.sleep(retry_after)
return api_call(url, headers) # retry once
if response.status_code == 401:
raise Exception("Invalid API key")
raise Exception(f"API error {response.status_code}: {error['message']}")Common Errors
- "The content field is required": Include a content field when creating documents
- "Invalid API key": Check that the key starts with um_ and has not been revoked
- "Quota exceeded": Wait for monthly reset or upgrade to Pro
- "Document not found": Verify the document ID exists and belongs to your account
- "Conflict detected": Re-fetch the document and retry with the latest version