fopost-fastapi
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
- Documentation — https://fopost.com/docs
- Python SDK — https://github.com/fopost/fopost-python
- Issues — https://github.com/fopost/fopost-fastapi/issues
- Support — https://fopost.com/contact
MIT licensed. Copyright (c) 2026 Porter Bridge, LLC.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fopost_fastapi-0.1.1.tar.gz.
File metadata
- Download URL: fopost_fastapi-0.1.1.tar.gz
- Upload date:
- Size: 14.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0707fd3927d687efb6a81df532e747c23589cb2f00241326ccb40caa719a10c7
|
|
| MD5 |
68994c01ef273fc23a0e55a8f41b36c0
|
|
| BLAKE2b-256 |
bfe42e732d89de4adcaf9552ae8f0b5b216f439f287e0d992cccfc693c868d7c
|
Provenance
The following attestation bundles were made for fopost_fastapi-0.1.1.tar.gz:
Publisher:
release.yml on fopost/fopost-fastapi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fopost_fastapi-0.1.1.tar.gz -
Subject digest:
0707fd3927d687efb6a81df532e747c23589cb2f00241326ccb40caa719a10c7 - Sigstore transparency entry: 2668483724
- Sigstore integration time:
-
Permalink:
fopost/fopost-fastapi@18ad41cfa5ce1f58ab0aa4fae9d67f0f97773fe1 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/fopost
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@18ad41cfa5ce1f58ab0aa4fae9d67f0f97773fe1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file fopost_fastapi-0.1.1-py3-none-any.whl.
File metadata
- Download URL: fopost_fastapi-0.1.1-py3-none-any.whl
- Upload date:
- Size: 13.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6dc4684f5e22723d93bc7a4f05ceb9c28054a109eeed394853003859195d108
|
|
| MD5 |
ed1a0ec437706052b6fb1450b637d7bb
|
|
| BLAKE2b-256 |
0ad7419f0d51ed3f0ab8b7594ef258eb4e18fa02d2b4097872349df9c3b68bf2
|
Provenance
The following attestation bundles were made for fopost_fastapi-0.1.1-py3-none-any.whl:
Publisher:
release.yml on fopost/fopost-fastapi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fopost_fastapi-0.1.1-py3-none-any.whl -
Subject digest:
e6dc4684f5e22723d93bc7a4f05ceb9c28054a109eeed394853003859195d108 - Sigstore transparency entry: 2668483752
- Sigstore integration time:
-
Permalink:
fopost/fopost-fastapi@18ad41cfa5ce1f58ab0aa4fae9d67f0f97773fe1 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/fopost
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@18ad41cfa5ce1f58ab0aa4fae9d67f0f97773fe1 -
Trigger Event:
push
-
Statement type: