Docs

Errors

One error format for every endpoint, stable codes, and when to retry.

Diese Seite gibt es noch nicht auf Deutsch, deshalb siehst du sie auf Englisch.

Error response format

Every error has the same shape:

{
    "error": {
        "type": "invalid_request_error",
        "code": "validation_failed",
        "message": "content: Too big: expected string to have <=63206 characters",
        "param": "content",
        "details": [
            { "param": "content", "message": "content: Too big: expected string to have <=63206 characters" }
        ],
        "docUrl": "https://feedhorizon.com/docs/api/errors#invalid_request_error",
        "requestId": "req_m2x81a0kfd3h9s"
    }
}
FieldStableDescription
typeYesError class, one of the headings below.
codeYesMachine-readable code in snake_case. Branch on this.
messageNoFor humans. May be reworded between releases, never branch on it.
paramYesThe field or parameter at fault, dotted when nested (targets.0.accountId).
detailsYesOne entry per problem, on validation_failed and on post checks. Each has param, message and, for post checks, its own code.
docUrlYesLink to the section of this page for the error type.
requestIdYesThe same value as the x-request-id header. Quote it when you contact support.

Error types

invalid_request_error

The request is malformed or breaks a rule. Fix it before retrying.

Request format

Status 400.

CodeMeaning
invalid_jsonThe body is not valid JSON
validation_failedOne or more fields are invalid, see details
invalid_requestThe request breaks a rule not covered by a more specific code; message says which
invalid_cursorcursor is not a value the API returned as nextCursor
profile_not_foundA profileId in the body does not exist
platform_mismatchThe endpoint does not fit this account's platform, e.g. boards of a LinkedIn account
invalid_slotA queue slot has an invalid dayOfWeek or time

Post checks. A post is checked against every platform before it is stored. With one problem, its code is error.code. With several, error.code is post_invalid and each entry in details carries its own code. POST /posts/validate returns the same codes without creating anything.

Status 400.

CodeMeaning
post_invalidSeveral problems at once, see details[].code
content_requiredA post needs text or media
content_too_longThe text is longer than a platform allows (param names which text)
no_targetsA post needs platforms, accountIds or targets
no_connected_accountThe profile has no active account for a requested platform
account_not_in_profileAn account belongs to another profile
duplicate_accountThe same account appears twice in targets
platform_unavailableThe platform is planned but not live yet
schedule_in_pastscheduledFor lies in the past
invalid_schedulescheduledFor is not a valid date
queue_not_configuredaddToQueue was sent but the profile has no queue slots
media_requiredThe platform needs media (Instagram and Pinterest any, TikTok a video)
too_many_mediaMore files than the platform takes
media_type_unsupportedA file type the platform does not take. Also answered by POST /media for a type FeedHorizon does not store
media_too_largeA file is bigger than the platform allows. Also answered by POST /media for an import over 100 MB or an upload over 5 GB
media_not_foundA mediaIds entry does not exist in your organization
duplicate_mediaThe same file appears twice in mediaIds
media_not_uploadedThe file was announced but never uploaded
tiktok_privacy_requiredoptions.tiktok.privacyLevel is missing
tiktok_branded_content_privatebrandedContent cannot be combined with SELF_ONLY
account_not_activeA warning, not an error: the account needs a reconnect. The post is stored, but that account fails until it is reconnected

Media (POST /media)

Status 400.

CodeMeaning
invalid_urlNot a valid https URL, a URL with credentials, or one that points to a private network
media_fetch_failedThe URL could not be fetched or answered with an error

authentication_error

Status 401.

CodeMeaning
missing_api_keyNo Authorization: Bearer header
invalid_api_keyUnknown, revoked or expired key

permission_error

Status 403.

CodeMeaning
insufficient_scopeThe key lacks the scope for this endpoint
profile_access_deniedThe key is limited to other profiles
all_profiles_requiredThe endpoint manages the whole organization (API keys, webhooks, new profiles) and needs a key with access to all profiles

not_found_error

Status 404.

CodeMeaning
resource_not_foundThe resource does not exist, belongs to another organization, or the key cannot see its profile. A malformed id is also a 404

conflict_error

The request is valid but the current state does not allow it.

Status 409.

CodeMeaning
post_not_editableThe post is publishing or already published
post_publishingThe post is being published right now and cannot be deleted
post_not_failedOnly failed posts can be retried
post_not_scheduledOnly scheduled posts can be cancelled
media_in_useThe file is still used by a post
external_id_takenAnother profile already uses this externalId
already_existsA resource with these values already exists
queue_fullNo free queue slot in the next 8 weeks
default_profileThe default profile cannot be deleted
account_limit_reachedThe free plan's 2 social accounts are connected; start the paid plan to connect more
webhook_limit_reachedThe organization already has 10 webhooks
key_in_useA key cannot revoke itself
invalid_stateThe current state does not allow this; message says why

idempotency_error

See Idempotency.

StatusCodeMeaning
400invalid_idempotency_keyThe key is empty or longer than 255 characters
409idempotency_request_in_progressThe first request with this key is still running. Retry in a few seconds
422idempotency_key_reusedThe key was used for a different request. Use a new key

rate_limit_error

Status 429.

CodeMeaning
rate_limitedToo many requests. Wait the seconds in Retry-After

See Rate Limits.

Publishing errors

Errors that happen after a post was accepted, while it goes out, do not come back as an HTTP error. They land on the account's target in the post, targets[].error, and in the post.target.failed webhook:

{
  "code": "platform_rejected",
  "category": "rejected",
  "source": "user",
  "message": "LinkedIn publish failed (422): Content is a duplicate of urn:li:share:7654…"
}

Branch on code, or on category and source for a coarser decision. source says who can fix it: user (you or your customer), platform (wait, FeedHorizon retries) or system (FeedHorizon). message carries the platform's own reason and may change.

CodeCategorySourceRetriedWhat to do
account_needs_reauthauth_expireduserNoReconnect the account, then POST /posts/{id}/retry
platform_auth_failedauth_expireduserNoThe platform refused the token or a permission is missing. Reconnect, then retry
platform_rate_limitedrate_limitedplatformYesNothing; FeedHorizon tries again. Space out posts to this account
platform_rejectedrejecteduserNoThe platform refused this content (duplicate, format, policy). Change it, then retry
platform_unavailableplatform_errorplatformYesNothing; FeedHorizon tries again
timeouttimeoutsystemNoPublishing did not finish within 10 minutes. Check the platform first: the post may be live. Retry only if it is not
publish_failedunknownsystemYesRead message; contact support with the post id if it persists

Retried errors get 3 attempts in total, after 2 and 10 minutes. Only the final outcome sends post.target.failed.

api_error

Status 500.

CodeMeaning
internal_errorSomething failed on our side. Retry with backoff, using the same Idempotency-Key for writes

Handling errors

Node.js
const res = await fetch('https://feedhorizon.com/api/v1/posts', {
    method: 'POST',
    headers: {
        'Authorization': 'Bearer fh_YOUR_API_KEY',
        'Content-Type': 'application/json',
        'Idempotency-Key': crypto.randomUUID(),
    },
    body: JSON.stringify({ profileId: 'YOUR_PROFILE_ID', platforms: ['linkedin'], content: 'Hello!' }),
});

if (!res.ok) {
    const { error } = await res.json();
    if (error.code === 'validation_failed') {
        for (const d of error.details) console.error(`${d.param}: ${d.message}`);
    } else {
        console.error(`${error.type}/${error.code}: ${error.message} (${error.requestId})`);
    }
}

When to retry

StatusRetry?How
400, 401, 403, 404, 422NoFix the request, key or id
409SometimesOnly idempotency_request_in_progress, after a few seconds
429YesAfter Retry-After seconds
500YesExponential backoff (1 s, 2 s, 4 s, at most 30 s), same Idempotency-Key