FeedHorizon API
Guides

Error Handling

Understand error responses, HTTP status codes, and retry strategies.

Error response format

All errors return a consistent JSON structure:

{
    "error": {
        "code": "VALIDATION_ERROR",
        "message": "Human-readable error description",
        "details": [
            { "field": "content", "message": "Content is required" }
        ]
    }
}
FieldTypeDescription
codestringMachine-readable error code
messagestringHuman-readable description
detailsarray?Per-field validation errors (only for 422)

HTTP status codes

StatusCodeDescription
401UNAUTHORIZEDMissing, invalid, or expired API key
403FORBIDDENKey doesn't have the required permission (e.g. read-only key on a write endpoint)
404NOT_FOUNDResource doesn't exist or doesn't belong to you
422VALIDATION_ERRORInvalid request body — check details array
429RATE_LIMITEDToo many requests — see Rate Limits
500INTERNAL_ERRORUnexpected server error

Error codes reference

CodeMeaning
UNAUTHORIZEDAPI key is missing, malformed, expired, or revoked
FORBIDDENPermission denied for this operation
NOT_FOUNDThe requested resource was not found
VALIDATION_ERRORRequest body failed validation
RATE_LIMITEDRate limit exceeded for this API key
INTERNAL_ERRORAn unexpected error occurred on our end

Handling errors

Node.js
const res = await fetch('https://app.feedhorizon.dev/api/v1/posts', {
    method: 'POST',
    headers: {
        'Authorization': 'Bearer fh_YOUR_API_KEY',
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({ content: 'Hello!' }),
});

if (!res.ok) {
    const { error } = await res.json();
    console.error(`[${error.code}] ${error.message}`);

    if (error.details) {
        error.details.forEach(d =>
            console.error(`  ${d.field}: ${d.message}`)
        );
    }
}

Retry strategy

StatusRetry?Strategy
401NoFix your API key
403NoUse a key with write permissions
404NoCheck the resource ID
422NoFix the request body
429YesWait for X-RateLimit-Reset header (seconds until reset)
500YesExponential backoff (1s, 2s, 4s, max 30s)

For 429 responses, use the rate limit headers:

if (res.status === 429) {
    const resetIn = parseInt(res.headers.get('X-RateLimit-Reset') || '60');
    await new Promise(resolve => setTimeout(resolve, resetIn * 1000));
    // Retry the request
}