Appearance
SDK reference
Everything the FastAPI SDK exposes, in one place. For a guided walkthrough with the reasoning behind each setting, read the FastAPI SDK guide.
The SDK needs Python 3.10 or newer. PyPI has the latest release, and the changelog lists what changed in each one.
Install
bash
pip install microauth-fastapi
# With Redis, for limits shared across workers
pip install 'microauth-fastapi[redis]'Environment variables
The SDK reads these when the matching constructor argument isn't passed.
| Variable | Used for |
|---|---|
MICROAUTH_SECRET_KEY | Your API's SDK secret key (mas_...). Required |
MICROAUTH_BASE_URL | MicroAuth API base URL. Defaults to https://api.microauth.com |
MICROAUTH_REDIS_URL | Optional Redis URL for shared limits, snapshots and the durable usage queue |
MICROAUTH_JOURNAL_DIR | Directory for the usage journal. Defaults to a per user state directory, see journal_dir |
When one of these isn't set in the environment at all, the SDK and the check command read it from a .env file in the working directory. Real environment variables always win and other entries in the file are ignored, so you don't need python-dotenv.
bash
# .env
MICROAUTH_SECRET_KEY=mas_...You see the secret when you launch your API, and you can reveal it again on the Connect tab. Admins can rotate it under Settings, SDK secret key, with a grace period for the old one. Keep it on your server, never in a browser or mobile app, and keep .env out of version control.
MicroAuth(app, secret_key=None, **options)
Creates the integration and installs a small middleware on app. It records the final status code of every authenticated response, loads the first snapshot when the app starts and drains pending usage when it stops.
python
from fastapi import FastAPI, Security
from microauth_fastapi import Customer, MicroAuth
app = FastAPI()
auth = MicroAuth(app)
@app.get("/forecast")
async def forecast(customer: Customer = Security(auth)):
return {"customer": customer.id}If you build the app later, create MicroAuth() without it and call auth.install(app) before the first request.
Options
All options are keyword arguments with defaults, so pass only what you want to change.
| Option | Default | What it does |
|---|---|---|
secret_key | MICROAUTH_SECRET_KEY | SDK secret key (mas_...) |
base_url | https://api.microauth.com | MicroAuth API base (MICROAUTH_BASE_URL) |
header_name | X-API-Key | Header your customers send their key in |
redis_url | MICROAUTH_REDIS_URL | Shares limits, snapshots and pending usage across processes |
redis_client | None | A redis.asyncio client you own, used instead of redis_url |
redis_max_connections | 64 | Cap for the pool built from redis_url. Requests wait up to one second for a free connection |
shared_snapshot_cache | True | With Redis, share snapshots so cold starts don't all fetch at once |
sync_interval | 30 | Seconds between snapshot refreshes |
report_interval | 5 | Usage is sent when 500 requests are waiting or this many seconds pass |
flush_on_response | Auto | True on Vercel and AWS Lambda. Sends a due batch after each response |
trailing_flush | False | Keeps a serverless invocation alive until the batch deadline so the last burst is reported |
max_snapshot_age | 300 | With fail_open=False, requests get 503 once the snapshot is this old |
max_stale_snapshot_age | 3 × max_snapshot_age | Hard ceiling for serving known keys from stale data |
fail_open | True | Keep serving known keys from a stale snapshot until the hard ceiling |
enforce_balance | True | Return 402 when a paying customer has no credit left |
enforce_quota | True | Return 429 when the monthly quota is used up |
enforce_rps | True | Apply per customer requests a second limits |
enforce_platform_allowance | True | Reserve against your MicroAuth plan's monthly allowance before the handler runs |
verify_negative_ttl | 30 | Seconds an unknown key is remembered as invalid |
timeout | 5 | HTTP timeout in seconds for MicroAuth calls. Each call makes up to three attempts |
http_client | None | An httpx.AsyncClient you own |
journal_dir | MICROAUTH_JOURNAL_DIR | Where queued usage is kept on disk. Defaults to ~/.local/state/microauth/journal on Linux, ~/Library/Application Support/microauth/journal on macOS and %LOCALAPPDATA%\microauth\journal on Windows. Each API gets its own subdirectory |
persist_usage | True | Keep unacknowledged usage across restarts. Turn off only in tests |
max_usage_queue | 10000 | Most reserved and queued usage items at once |
shutdown_timeout | 10 | Seconds allowed for the final usage drain |
on_receipts | None | Called with the receipts of each delivery |
on_rejections | None | Called with the usage MicroAuth refused for good |
Clients you pass in through redis_client or http_client stay yours. The SDK uses them but never closes them.
Dependencies
| Dependency | Resolves to | Use it for |
|---|---|---|
Security(auth) | Customer | Routes that require a valid key |
Security(auth.optional) | Customer | None | Routes that serve anonymous and authenticated callers |
Both add an APIKeyHeader security scheme to your OpenAPI schema, so the Authorize button in Swagger UI works without extra setup.
Customer
The principal your handler receives. It is a frozen dataclass.
| Field | Type | Meaning |
|---|---|---|
id | str | The customer's team ID, a UUID. Stable across keys and teammates |
key_id | str | ID of the API key used for this request |
status | str | Always active inside a handler, because other customers are rejected first |
billing_model | str | payg, subscription or none |
rps | int | Effective requests a second |
price_per_request_micro | int | Effective price per billable request, in micro-USD |
monthly_quota | int | None | Effective monthly request cap, or None for no cap |
credit_balance_micro | int | Estimated prepaid balance after this request's reservation |
platform_monthly_limit | int | None | Your MicroAuth plan's monthly allowance |
platform_monthly_remaining | int | None | Allowance left this month |
platform_monthly_period_end | datetime | None | When the allowance resets |
Money is always an integer in micro-USD: 1,000,000 is one dollar.
Lifecycle
| Method | When to call it |
|---|---|
auth.install(app) | Only if you didn't pass app to the constructor |
await auth.startup() | Loads the first snapshot and starts delivery. The middleware calls it when the app starts, and the first request does when the server skips lifespan events |
await auth.flush_usage() | Delivers everything queued now and waits for MicroAuth's answer, for scripts and tests |
auth.usage_stats() | Returns the process's delivery health |
auth.tenant_id, auth.tenant_name | The API this process serves, once the first snapshot arrived |
await auth.aclose() | During graceful shutdown, so pending usage drains. The installed middleware calls it on app shutdown |
Errors returned to callers
Each rejection is a subclass of AuthDenied, which is a FastAPI HTTPException. Register a handler for AuthDenied to change the body.
| Exception | Status | Body detail | Headers |
|---|---|---|---|
InvalidAPIKey | 401 | Invalid or missing API key | WWW-Authenticate: ApiKey header="X-API-Key" |
PaymentRequired | 402 | Insufficient credit balance | |
CustomerSuspended | 403 | This account is suspended | |
CustomerPending | 403 | This account is waiting for approval | |
RateLimited | 429 | Rate limit exceeded | Retry-After |
QuotaExceeded | 429 | Monthly request quota exceeded | |
PlatformAllowanceExceeded | 429 | Platform monthly request allowance exhausted | |
AuthUnavailable | 503 | Authorization is temporarily unavailable |
python
from fastapi.responses import JSONResponse
from microauth_fastapi import AuthDenied
@app.exception_handler(AuthDenied)
async def auth_denied(request, exc):
return JSONResponse(
status_code=exc.status_code,
content={"error": exc.detail, "docs": "https://docs.example.com/errors"},
headers=exc.headers or {},
)Problems on your side raise subclasses of MicroAuthError instead, for example MicroAuthConfigurationError for a missing secret or UsageQueueFull when max_usage_queue is reached. These show up in your logs, not in responses to your customers.
Receipts
on_receipts is called with a list of UsageReceipt after each delivery, before the items leave the queue. A receipt can arrive twice when a process stops between the two steps, so store receipts by idempotency_key. The FastAPI guide shows the hooks in use.
| Field | Type | Meaning |
|---|---|---|
idempotency_key | str | The item's ID, the same on every delivery attempt |
outcome | str | accepted when this delivery recorded the item, duplicate when MicroAuth had recorded it before |
customer_id | str | None | The customer MicroAuth charged |
api_key_id | str | The key the requests used |
usage_policy_id | str | The prices the requests were admitted under |
status_code | int | The response status the item counts |
count | int | Requests in the item. They share a key, policy, status and UTC hour |
period_start | datetime | The UTC hour the requests were served in |
charged_micro | int | None | What MicroAuth charged, in micro-USD |
expected_charge_micro | int | None | What the SDK expected from the prices it enforced. None when this process didn't admit all of the requests |
charge_matches | bool | None | Whether the two agree, when both are known. The SDK logs every mismatch |
recorded_at | datetime | None | When MicroAuth recorded the item |
delivered_at | datetime | When this process received the receipt, on MicroAuth's clock |
Rejections
on_rejections is called with a list of UsageRejection when MicroAuth refuses items for good, for example usage for a key that isn't part of this API. The SDK also refuses usage older than MicroAuth's 45 day limit itself, without sending it. Refused usage is never charged and is kept as a dead letter for the usage command.
| Field | Type | Meaning |
|---|---|---|
idempotency_key | str | The item's ID |
api_key_id, usage_policy_id, status_code, count, period_start | As on a receipt | |
http_status | int | None | The status MicroAuth gave the item, or None when the SDK refused it |
detail | str | Why it was refused |
rejected_at | datetime | When, on MicroAuth's clock |
Hooks can be async. A plain function runs in a worker thread so it can't stall your requests. Each call gets five seconds, and an error or timeout is logged and counted in hook_failures without holding up delivery.
UsageStats
auth.usage_stats() returns the delivery health of the current process. Counters cover the life of the process.
| Field | Meaning |
|---|---|
durability | Where queued usage survives a crash: redis, journal or memory |
journal_dir | This API's journal directory. With Redis, it is the fallback while Redis is unreachable |
queued_items, queued_requests | Usage waiting for delivery |
oldest_queued_at | When the oldest waiting request was queued |
delivered_items, delivered_requests | Usage MicroAuth has recorded, duplicates included |
duplicate_items | Items MicroAuth had already recorded on an earlier attempt |
rejected_items, rejected_requests | Usage MicroAuth refused for good |
charged_micro | The sum of charged_micro over every receipt |
charge_mismatches | Receipts whose charge differed from the expected one |
last_delivery_at | When MicroAuth last acknowledged a delivery |
consecutive_failures | Deliveries that failed in a row. It resets on the next success |
last_error, last_error_at | The latest delivery error |
clock_offset_seconds | Seconds to add to this machine's clock to read MicroAuth's |
hook_failures | Receipt and rejection hook calls that raised or timed out |
Alert on rejected_items and charge_mismatches above zero, on consecutive_failures that keeps growing, and on an oldest_queued_at that keeps getting older.
The check command
check tests an integration end to end without serving traffic or reporting usage. Run it where your API runs, with the same environment:
bash
python -m microauth_fastapi checkIt checks, in order:
- Configuration. The SDK secret key is present and looks like one (
mas_...). - MicroAuth. MicroAuth accepts the secret and returns a snapshot. It prints your API's name and ID, the number of customers and keys, your billable status codes and the allowance used this month. It also measures this machine's clock against MicroAuth's and warns at 2 seconds or more. The SDK uses MicroAuth's time either way, but NTP should be fixed.
- API key. With
--key, it explains how a request with that key would be treated: allowed, or refused with402,403or429and why. - Shared state. With Redis configured, it confirms Redis is reachable and accepts Lua scripts.
- Usage journal. The journal directory is writable and isn't somewhere a reboot clears. It also counts usage still queued there and usage MicroAuth refused.
text
Configuration
ok SDK secret mas_...v6a9
ok MicroAuth API https://api.microauth.com
MicroAuth
ok connected to Weather API (0199d4f2-6c1e-7a3b-9f10-2b6c8d4e5a71)
ok snapshot loaded: 12 customer(s), 19 key(s)
ok clock agrees with MicroAuth within 0.004s
billable status codes: 200, 201, 202, 203, 204, 205, 206
platform allowance: 48,210 of 1,000,000 requests used this month
API key
ok key belongs to customer 01a12b67-8fad-7075-8819-fd5ef0cecc10
plan source: payg, billing: payg
balance: $0.999
price per billable request: $0.001
rate limit: 10 requests per second
ok a request with this key would be allowed
Shared state
ok no Redis configured; each worker enforces limits on its own
Usage journal
ok journal directory is writable (/home/app/.local/state/microauth/journal/5b1e0c7a9d2f4e6b8a3c1d0e)
Everything looks good. Your API is ready to serve keys.The first line of the output names the installed SDK version.
| Flag | Default | Meaning |
|---|---|---|
--key | none | A customer API key (map_...) to evaluate |
--secret-key | MICROAUTH_SECRET_KEY | SDK secret key to test |
--base-url | MICROAUTH_BASE_URL | MicroAuth API URL |
--redis-url | MICROAUTH_REDIS_URL | Redis URL to test |
--journal-dir | MICROAUTH_JOURNAL_DIR | Usage journal directory to test |
--timeout | 10 | HTTP timeout in seconds |
The command exits with 0 when everything passed and 1 when any line reads fail, so it works as a deploy smoke test in CI. On failure it ends with a link to troubleshooting. Settings are read from .env too when they aren't in the environment.
The usage command
usage shows what this host still has to deliver and every item MicroAuth refused, from the journal and, when configured, Redis. It only reads, so it is safe to run next to a live app:
bash
python -m microauth_fastapi usage
python -m microauth_fastapi usage --json # for scripts and alertstext
Weather API (0199d4f2-6c1e-7a3b-9f10-2b6c8d4e5a71)
journal: /home/app/.local/state/microauth/journal/5b1e0c7a9d2f4e6b8a3c1d0e
queued in the journal: 2 item(s) covering 9 request(s), oldest queued 2026-10-11 14:02:17 UTC
usage-c45225c89b50.wal: 2 item(s), last written 2026-10-11 14:02:19 UTC
refused by MicroAuth (journal): 1 item(s)
2026-10-11 13:58:40 UTC HTTP 422 3 request(s) with status 200 in the hour of 2026-10-11 13:00:00 UTC
api_key_id is not a key of this API
item c41d9e7b-2a6f-4b83-9e0c-5f7a1d3b8e62, key 9e7f3a1c-4b2d-4e6f-8a0c-1d3e5f7b9a24Queued usage is delivered by the app itself, so a queue that empties is healthy. One that keeps growing means the app can't reach MicroAuth. Without a secret, or when MicroAuth can't be reached, the command lists every API journal in the directory.
| Flag | Default | Meaning |
|---|---|---|
--json | off | Print JSON instead of text |
--limit | 50 | How many of the newest refused items to list per queue |
--secret-key, --base-url, --redis-url, --journal-dir, --timeout | As for check | Which API, Redis and journal to read |
What the SDK sends
- Every call carries
Authorization: Bearer mas_...and aUser-Agent: microauth-fastapi/<version> python/<version>header. The dashboard shows the SDK version on the Connect tab. - Snapshots are revalidated with
If-None-Match. When nothing changed, MicroAuth answers304 Not Modifiedwith no body. - Usage goes out in batches of up to 500 items to
POST /sdk/v1/usage. Each item names the usage policy its requests were admitted under and keeps the same idempotency key across retries until MicroAuth acknowledges it. MicroAuth answers with a receipt per item.
The endpoints are documented in the HTTP API guide and the API reference.