Docs

SDKs

Official Node.js and Python clients for the FeedHorizon API, with types, retries, pagination and webhook verification.

FeedHorizon has official SDKs for Node.js and Python. They are thin clients over the REST API and use the same names for every field. Every example on this page works the same as the matching cURL request in the API reference.

What the SDKs do for you:

  • Types: requests and responses are typed, generated from /openapi.json.
  • Retries: network errors, timeouts, 429 and 5xx are retried with backoff, honouring Retry-After.
  • Idempotency: every write gets an Idempotency-Key, the same on each attempt, so a retry never creates a second post.
  • Pagination: one loop walks a list across all pages.
  • Errors: one error class per status, carrying code, param, details and the request id.
  • Webhooks: signature check and parsing in one call.
PackageSourceRequires
Node.js / TypeScript@feedhorizon/sdkbash-bash/feedhorizon-nodeNode.js 20+, also Deno, Bun and edge runtimes. No dependencies.
Pythonfeedhorizonbash-bash/feedhorizon-pythonPython 3.10+, depends on httpx

Both are MIT licensed.

Install

npm install @feedhorizon/sdk

Create a client

Create an API key under Dashboard → API & Apps and set it as the environment variable FEEDHORIZON_API_KEY. The client reads it from there.

import FeedHorizon from '@feedhorizon/sdk';

const client = new FeedHorizon(); // reads FEEDHORIZON_API_KEY

Pass the key as apiKey (Node.js) or api_key (Python) to set it in code instead. Never commit a key.

Create a post

const { data: profiles } = await client.profiles.list();

const post = await client.posts.create({
    profileId: profiles[0].id,
    content: 'Hello from the FeedHorizon SDK!',
    platforms: ['linkedin', 'bluesky'],
    scheduledFor: '2026-11-12T09:00:00+01:00',
});

console.log(post.id, post.status); // post_… scheduled

Methods return the object inside data directly. Leave out the timing field for a draft, or send addToQueue or publishNow instead of scheduledFor (see Scheduling). In Python, responses are plain dicts with type hints, and parameters keep the API's names (profileId, not profile_id).

Lists and pagination

A list call returns the first page. Loop over it to walk every item across all pages; the SDK fetches the next page when it needs it.

const page = await client.posts.list({ status: 'failed', limit: 50 });
console.log(page.data.length, page.hasNextPage());

for await (const post of client.posts.list({ updatedSince: '2026-11-01T00:00:00Z' })) {
    console.log(post.id, post.status);
}

from is a keyword in Python, so the calendar filter of posts.list is from_ there.

Errors

Every API error is an APIError with status, code, message, param, details and the request id. Branch on code; Errors lists them all.

import { ConflictError, InvalidRequestError } from '@feedhorizon/sdk';

try {
    await client.posts.update(postId, { content: 'New text' });
} catch (error) {
    if (error instanceof InvalidRequestError) {
        for (const d of error.details) console.log(d.code, d.param, d.message);
    } else if (error instanceof ConflictError && error.code === 'post_not_editable') {
        // already publishing or published
    } else {
        throw error;
    }
}
ClassStatus
InvalidRequestError400, 422
AuthenticationError401
PermissionDeniedError403
NotFoundError404
ConflictError409
RateLimitError429, with the wait in seconds as retryAfter / retry_after
InternalServerError5xx
APIConnectionError, APITimeoutErrorNo answer from the server

Include the request id (requestId / request_id) when you contact support.

Retries and idempotency

Failed requests are retried twice by default. Pass your own idempotency key to make a write safe to repeat across process restarts, for example from a job queue:

await client.posts.create(params, { idempotencyKey: `launch-${campaignId}` });

// per client or per request
const patient = new FeedHorizon({ maxRetries: 5, timeout: 30_000 });
await client.posts.get(postId, { maxRetries: 0, timeout: 5_000 });

See Idempotency for how the server treats repeated keys.

Media

// From a public URL
const image = await client.media.import('https://example.com/launch.jpg');

// From a file (up to 5 GB)
import { readFile } from 'node:fs/promises';
const video = await client.media.upload({ file: await readFile('teaser.mp4'), filename: 'teaser.mp4', mimeType: 'video/mp4' });

await client.posts.create({ profileId, content: 'The teaser is out', platforms: ['instagram'], mediaIds: [image.id, video.id], addToQueue: true });

upload announces the file and uploads the bytes in one call. See Media uploads.

Webhooks

Verify the signature against the raw request body, then handle the event. In TypeScript, event.data is typed by event.event.

import express from 'express';
import { constructWebhookEvent, WebhookVerificationError } from '@feedhorizon/sdk';

app.post('/webhooks/feedhorizon', express.raw({ type: 'application/json' }), async (req, res) => {
    let event;
    try {
        event = await constructWebhookEvent({
            payload: req.body,
            signature: req.get('X-Webhook-Signature'),
            secret: process.env.FEEDHORIZON_WEBHOOK_SECRET!,
        });
    } catch (error) {
        if (error instanceof WebhookVerificationError) return res.sendStatus(400);
        throw error;
    }
    res.sendStatus(200);

    if (event.event === 'post.target.failed') {
        console.log(event.data.platform, event.data.error?.code);
    }
});

Events older than five minutes are rejected, which stops replays. See Webhooks for every event.

Async Python

AsyncFeedHorizon has the same methods as FeedHorizon, awaited:

import asyncio
from feedhorizon import AsyncFeedHorizon


async def main() -> None:
    async with AsyncFeedHorizon() as client:
        post = await client.posts.get("post_…")
        async for failed in client.posts.list(status="failed"):
            print(failed["id"])


asyncio.run(main())

Resources

Node.jsPythonMethods
client.me()client.me()Organization, plan, rate limit and scopes of the key
client.postsclient.postslist, get, create, validate, update, delete, publish, cancel, retry, duplicate
client.mediaclient.medialist, get, create, import (Python: import_url), upload, delete
client.profilesclient.profileslist, get, create, update, delete
client.profiles.queueclient.profiles.queueget, set, slots
client.accountsclient.accountslist, get, delete, boards, tiktokCreator (Python: tiktok_creator)
client.platformsclient.platformslist, get
client.platformInvitesclient.platform_inviteslist, create, delete (connect links)
client.webhooksclient.webhookslist, get, create, update, delete, test
client.apiKeysclient.api_keyslist, create, delete

Configuration

Node.jsPythonDefault
apiKeyapi_keyFEEDHORIZON_API_KEY
baseURLbase_urlFEEDHORIZON_BASE_URL, else https://feedhorizon.com/api/v1
timeouttimeout60 seconds (60000 ms in Node.js)
maxRetriesmax_retries2
defaultHeadersdefault_headersnone
fetchhttp_clientglobal fetch / a new httpx client

Other languages

Every endpoint, field and webhook payload is in /openapi.json (OpenAPI 3.1). Use it with any OpenAPI client generator, or call the API with plain HTTP as the cURL examples show.

Versions

The SDKs follow semantic versioning. New API fields and endpoints arrive in minor releases; see Versioning for what the API itself promises. Release notes are in each repository's CHANGELOG and GitHub releases.