Skip to main content

fopost-fastapi

PyPI Python versions CI License: MIT

Official FastAPI integration for the FoPost API. Schedule and publish to +30 social platforms from your code.

This is a thin wrapper. Every request, model, retry, and error type lives in the fopost SDK — this package wires that client into FastAPI's idioms: settings, dependency injection, a webhook receiver, and an exception handler.

pip install fopost-fastapi

Requires Python 3.10 or newer, FastAPI 0.110 or newer, and pydantic v2.

0.x release. The public API is still settling and minor versions may contain breaking changes. Pin an exact version if that matters to you.

Quick start

from fastapi import FastAPI

from fopost_fastapi import FoPostDep, install_exception_handlers, setup_fopost

app = FastAPI()
setup_fopost(app)              # one client, created at startup, closed at shutdown
install_exception_handlers(app)


@app.get("/workspaces")
def workspaces(fopost: FoPostDep):
    return fopost.workspaces.list()

FoPostDep is Annotated[Fopost, Depends(get_client)]. The client is built once when the app starts and shared by every request — the dependency looks it up, it never constructs one.

If your app already has its own lifespan, setup_fopost wraps it rather than replacing it. To wire FoPost through the lifespan directly instead:

from fopost_fastapi import fopost_lifespan

app = FastAPI(lifespan=fopost_lifespan())

Settings

FoPostSettings is a pydantic-settings model reading FOPOST_-prefixed environment variables (and a .env file, if present).

Field Environment variable Default
api_key FOPOST_API_KEY — (required)
base_url FOPOST_BASE_URL https://api.fopost.com/v1
timeout FOPOST_TIMEOUT 30.0 seconds
max_retries FOPOST_MAX_RETRIES 3 attempts
default_workspace_id FOPOST_DEFAULT_WORKSPACE_ID —
webhook_secret FOPOST_WEBHOOK_SECRET —

Create an API key at https://fopost.com/dashboard/api-keys. It is sent as X-API-Key.

Pass settings explicitly when you would rather not read the environment:

setup_fopost(app, FoPostSettings(api_key="fp_...", base_url="https://api.fopost.com/v1"))

FoPostSettingsDep injects the resolved settings into a route, which is how you reach default_workspace_id.

Sync or async? The SDK is synchronous

The fopost package ships a blocking client only — there is no async variant, and this package deliberately does not write one. That leaves two shapes:

# A `def` route — FastAPI already runs it in a worker thread. Call the SDK directly.
@app.get("/accounts")
def accounts(fopost: FoPostDep, settings: FoPostSettingsDep):
    return fopost.accounts.list(workspace_id=settings.default_workspace_id)


# An `async def` route — the call must leave the event loop, or it stalls the server.
from fopost_fastapi import run_fopost

@app.post("/posts")
async def create(fopost: FoPostDep, settings: FoPostSettingsDep):
    return await run_fopost(
        fopost.posts.create,
        workspace_id=settings.default_workspace_id,
        content="Hello from FastAPI",
        accounts=["<account id>"],
    )

run_fopost is a thin wrapper over fastapi.concurrency.run_in_threadpool. Never call the SDK straight from an async def route: a 30-second timeout would block every other request.

Receiving webhooks

from fopost_fastapi import WebhookEvent, on_event, webhook_router

app.include_router(webhook_router, prefix="/fopost")   # POST /fopost/webhooks


@on_event("post.published")
async def published(event: WebhookEvent) -> None:
    print(event.data["id"], event.timestamp)


@on_event("post.failed")
def failed(event: WebhookEvent) -> None:      # a `def` handler runs in a worker thread
    ...

Point a FoPost webhook at https://<your host>/fopost/webhooks and put its secret in FOPOST_WEBHOOK_SECRET.

FoPost signs each delivery with HMAC-SHA256 over the raw request body using that webhook's secret, and sends it as X-FoPost-Signature: sha256=<hex> alongside X-FoPost-Event and X-FoPost-Delivery. The router reads the raw bytes before any parsing, compares with hmac.compare_digest, and answers 401 on a mismatch or a missing header — no handler runs. With no secret configured at all it answers 500 rather than accepting unverifiable traffic.

Events: post.published, post.failed, post.partially_failed, delivery.published, delivery.failed, delivery.delayed, account.health_changed. @on_event() with no argument subscribes to all of them.

Run several receivers, or keep the secret out of the environment, by building your own router:

from fopost_fastapi import FoPostWebhookRouter

router = FoPostWebhookRouter(secret="whsec_...", path="/callbacks")
app.include_router(router, prefix="/fopost")

sign_payload(body, secret) and verify_webhook_signature(body, header, secret) are exported if you need to verify a delivery somewhere else.

Receive blog articles

A post goes to a social network; an article goes to a website. When FoPost publishes an article to a Website target it sends the article to your URL, signed with the website secret shown once in the blog settings. Mount the receiver in one line:

from fopost import ArticlePublishedPayload
from fopost_fastapi import article_router

app.include_router(article_router(secret=WEBSITE_SECRET, on_article_published=store_article))

The callback gets the typed article and may return the URL you published it at:

async def store_article(article: ArticlePublishedPayload) -> str:
    await db.articles.upsert(slug=article.slug, html=article.body_html, tags=article.tags)
    return f"https://yourbrand.com/blog/{article.slug}"

A def callback works too and runs in a worker thread. Point the Website target at https://<your host>/articles (path= changes it). The route reads the raw body, verifies it through fopost.webhooks.verify (X-FoPost-Signature-256 over timestamp.body, 300 s tolerance), answers 401 on a bad or stale signature, {"ok": true} for article.test, and echoes a returned URL as {"url": "..."}. Image URLs in the payload expire after six hours, so download your own copy.

fopost.blog on the injected client plans keywords, requests articles and publishes them; see the fopost SDK.

Error handling

install_exception_handlers(app) turns an SDK exception into the status the FoPost API actually answered with, instead of a 500 and a stack trace.

SDK error Response
AuthenticationError (401), PermissionDeniedError (403), NotFoundError (404) the same status
PaymentRequiredError (402) 402, body keeps upgrade_url
RateLimitError (429) 429 with a Retry-After header
any 4xx the same status
any 5xx or transport failure 502 — your app is fine, its dependency is not

The body is the API's own envelope: {"error": "<machine code>", "message": "<human text>"}.

Retries are the SDK's job, not this package's: a 429 is retried up to max_retries attempts, honouring Retry-After, before the error ever reaches the handler.

The rest of the API

Everything you can call on the injected client — posts, accounts, workspaces, labels, ai, blog, and the request() escape hatch for endpoints the SDK does not wrap — is documented in the fopost SDK. This package adds no resources of its own and stores nothing.

Example

examples/main.py is a complete app: settings from the environment, the injected client in both a def and an async def route, and the webhook receiver.

Development

python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]'
pytest
ruff check . && ruff format --check .
mypy

The suite is fully offline — it stubs the SDK's HTTP transport and never reaches the network.

MIT licensed. Copyright (c) 2026 Porter Bridge, LLC.

Metadata

Release files for fopost-fastapi 0.2.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 fopost-fastapi 0.2.0
File Size Uploaded Provenance
fopost_fastapi-0.2.0.tar.gz 16.9 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for fopost-fastapi 0.2.0
File Interpreter ABI Platform
fopost_fastapi-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 32.3 kB

Release files / fopost_fastapi-0.2.0.tar.gz

Download URL fopost_fastapi-0.2.0.tar.gz
Size 16.9 kB
Tags Source
SHA-256 checksum
How to use checksums
40594e717d49c9561890110f5dff133fdb6acae388904aa3fb5e519bbf4cc3da
BLAKE2b-256 checksum
How to use checksums
70ace5d2d87c5648ee301a16c371274ed8f584240972e444ac1e5bdeff92549a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 18, 2026.

Transparency log

Release files / fopost_fastapi-0.2.0-py3-none-any.whl

Download URL fopost_fastapi-0.2.0-py3-none-any.whl
Size 15.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6c45d3af7c94683f78a3287714cbf25df901cc73884403fd2d2398e04780a682
BLAKE2b-256 checksum
How to use checksums
90d59c7ab2ad7faefe93c30875b73526134defe158a829e583669cc118a4bb01
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Oct 18, 2026.

Transparency log

Security for fopost-fastapi 0.2.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 fopost_fastapi-0.2.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 Oct 18, 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.

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