FastAPI

The FoPost Python client wired into FastAPI, with settings, dependency injection, and a webhook router.

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

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

pip install fopost-fastapi

This is a 0.x release. The surface is still settling and a minor version may break something. Pin an exact version if that matters to you.

Published on PyPI. Source and issues: github.com/fopost/fopost-fastapi. MIT licensed.

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 there is one.

FieldEnvironment variableDefault
api_keyFOPOST_API_KEYnone, required
base_urlFOPOST_BASE_URLthe hosted API
timeoutFOPOST_TIMEOUT30.0 seconds
max_retriesFOPOST_MAX_RETRIES3 attempts
default_workspace_idFOPOST_DEFAULT_WORKSPACE_IDnone
webhook_secretFOPOST_WEBHOOK_SECRETnone

Create an API key at Settings → API Keys in the dashboard. 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_..."))

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

Sync or async

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

Errors

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 errorResponse
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 other 4xxthe same status
any 5xx or transport failure502 (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 on the Python SDK page. This package adds no resources of its own and stores nothing.

Next

Related documentation
  • SDKs & Integrations

    Official FoPost clients for TypeScript, Python, PHP, Ruby, Go, Rust, Java, .NET, Swift, Kotlin, Dart, and Elixir, framework integrations from Laravel to Next.js, and tooling for the CLI, CI, Terraform, and the automation platforms.

  • SDKs Overview

    Every official FoPost client, what it covers, and how to pick one.

  • TypeScript SDK

    The official TypeScript and Node.js client for the FoPost API.

  • Python SDK

    The official Python client for the FoPost API.

  • PHP SDK

    The official PHP client for the FoPost API, with no framework and no HTTP library.

  • Ruby SDK

    The official Ruby client for the FoPost API, with no runtime dependencies.

  • Go SDK

    The official Go client for the FoPost API.

  • Rust SDK

    The official Rust client for the FoPost API, async and built on reqwest.

Was this helpful?

On this page