Skip to main content

FeedHorizon Python SDK

The official Python client for the FeedHorizon API: schedule and publish posts on X, LinkedIn, Instagram, Bluesky, Pinterest and TikTok with one API.

  • Sync and async clients (FeedHorizon, AsyncFeedHorizon)
  • Typed parameters and responses (TypedDict), generated from the OpenAPI spec
  • Automatic retries with idempotency keys, so a retry never posts twice
  • Iterate any list across all pages
  • Webhook signature verification
  • Python 3.10+, depends only on httpx

Install

pip install feedhorizon

Quickstart

Create an API key under API & Apps in the dashboard and set it as FEEDHORIZON_API_KEY.

from feedhorizon import FeedHorizon

client = FeedHorizon()  # reads FEEDHORIZON_API_KEY

profile = client.profiles.list().data[0]

post = client.posts.create(
    profileId=profile["id"],
    content="Hello from the FeedHorizon SDK!",
    platforms=["linkedin", "bluesky"],
    scheduledFor="2026-11-12T09:00:00+01:00",
)
print(post["id"], post["status"])  # post_… scheduled

Parameters and fields use the API's names (profileId, scheduledFor), so everything in the API reference applies one to one. Responses are plain dicts with type hints.

Leave out the timing field for a draft, or send addToQueue=True or publishNow=True instead of scheduledFor. See Scheduling.

Async

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())

Check a post before creating it

check = client.posts.validate(profileId=profile_id, content=text, platforms=["twitter", "instagram"])
if not check["valid"]:
    for issue in check["errors"]:
        print(issue["code"], issue["message"])

Media

# From a public URL
image = client.media.import_url("https://example.com/launch.jpg")

# From a file (up to 5 GB)
with open("teaser.mp4", "rb") as f:
    video = client.media.upload(f, filename="teaser.mp4", mime_type="video/mp4")

client.posts.create(
    profileId=profile_id, content="The teaser is out", platforms=["instagram"], mediaIds=[image["id"], video["id"]], addToQueue=True
)

Lists and pagination

A list call returns the first page. Iterate it for every item across all pages:

page = client.posts.list(status="failed", limit=50)
print(len(page.data), page.has_next_page())

for post in client.posts.list(updatedSince="2026-11-01T00:00:00Z"):
    print(post["id"], post["status"])

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

Errors

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

from feedhorizon import ConflictError, InvalidRequestError

try:
    client.posts.update(post_id, content="New text")
except InvalidRequestError as e:
    for d in e.details:
        print(d["code"], d.get("param"), d["message"])
except ConflictError as e:
    if e.code == "post_not_editable":
        ...  # already publishing or published
Class Status
InvalidRequestError 400, 422
AuthenticationError 401
PermissionDeniedError 403
NotFoundError 404
ConflictError 409
RateLimitError 429 (retry_after in seconds)
InternalServerError 5xx
APIConnectionError, APITimeoutError no answer

Retries and idempotency

Network errors, timeouts, 429 and 5xx are retried twice with backoff (honouring Retry-After). Every write gets an Idempotency-Key, the same on each attempt, so a retry after a lost answer returns the first result instead of creating a second post. Pass your own key to make retries safe across process restarts:

client.posts.create(**params, idempotency_key=f"launch-{campaign_id}")
client = FeedHorizon(max_retries=5, timeout=30)
client.posts.get(post_id, max_retries=0, timeout=5)

Webhooks

Verify the signature against the raw body, then handle the event:

import os

from flask import Flask, request
from feedhorizon import WebhookVerificationError, construct_webhook_event

app = Flask(__name__)


@app.post("/webhooks/feedhorizon")
def feedhorizon_webhook():
    try:
        event = construct_webhook_event(
            request.get_data(), request.headers.get("X-Webhook-Signature"), os.environ["FEEDHORIZON_WEBHOOK_SECRET"]
        )
    except WebhookVerificationError:
        return "", 400

    if event["event"] == "post.target.failed":
        print(event["data"]["platform"], event["data"]["error"])
    return "", 200

Resources

Methods
client.me() Organization, plan, rate limit and scopes of the key
client.posts list, get, create, validate, update, delete, publish, cancel, retry, duplicate
client.media list, get, create, import_url, upload, delete
client.profiles list, get, create, update, delete
client.profiles.queue get, set, slots
client.accounts list, get, delete, boards, tiktok_creator
client.platforms list, get
client.platform_invites list, create, delete (connect links)
client.webhooks list, get, create, update, delete, test
client.api_keys list, create, delete

Every method also takes timeout, max_retries and extra_headers; writes take idempotency_key.

Configuration

Argument Default
api_key FEEDHORIZON_API_KEY
base_url FEEDHORIZON_BASE_URL, else https://feedhorizon.com/api/v1
timeout 60 seconds
max_retries 2
default_headers none
http_client a new httpx.Client / httpx.AsyncClient

Use the client as a context manager (with FeedHorizon() as client:) or call close() to release connections.

Development

uv sync
uv run pytest
uv run mypy
curl -fsSL https://feedhorizon.com/openapi.json -o spec/openapi.json && python scripts/generate_types.py

_async_resources.py is generated from _resources.py by scripts/generate_async.py. To release, bump version in pyproject.toml and add a CHANGELOG entry. When that lands on main, the Release workflow publishes to PyPI with trusted publishing and creates the vX.Y.Z tag and GitHub release.

License

MIT

Metadata

Release files for feedhorizon 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for feedhorizon 0.1.0
File Size Uploaded Provenance Actions
feedhorizon-0.1.0.tar.gz 23.3 kB With provenance Details for feedhorizon-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for feedhorizon 0.1.0
File Interpreter ABI Platform Actions
feedhorizon-0.1.0-py3-none-any.whl Python 3 none any Details for feedhorizon-0.1.0-py3-none-any.whl

Total release size: 50.5 kB

Release files / feedhorizon-0.1.0.tar.gz

Download URL feedhorizon-0.1.0.tar.gz
Size 23.3 kB
Tags Source
SHA-256 checksum
How to use checksums
75a5ef2efd79242f456aa228c512eef910bab8ac8b97d9202069ffaa71b9d594
BLAKE2b-256 checksum
How to use checksums
ff6d5b452efda4eca8d0b30f0791cff87f87b009d4b84ccce16a5dbbb5a38698
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Release files / feedhorizon-0.1.0-py3-none-any.whl

Download URL feedhorizon-0.1.0-py3-none-any.whl
Size 27.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
26147692fc8788477fee34ff7269711ccc9c5e1cc7769c50e6beb3759807c1d1
BLAKE2b-256 checksum
How to use checksums
c1b193f0ad0e8480f412b75767d1c1bf3c48d52fea068f248b6159216f6c5b2a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

Security for feedhorizon 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for feedhorizon-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page