Skip to content

Latest commit

 

History

18 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.

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, 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.

Links

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

About

Official FastAPI integration for FoPost — an injectable client dependency, a signed webhook router, and handlers mapping FoPost failures to responses.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages