Skip to content

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.

VariableUsed for
MICROAUTH_SECRET_KEYYour API's SDK secret key (mas_...). Required
MICROAUTH_BASE_URLMicroAuth API base URL. Defaults to https://api.microauth.com
MICROAUTH_REDIS_URLOptional Redis URL for shared limits, snapshots and the durable usage queue
MICROAUTH_JOURNAL_DIRDirectory 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.

OptionDefaultWhat it does
secret_keyMICROAUTH_SECRET_KEYSDK secret key (mas_...)
base_urlhttps://api.microauth.comMicroAuth API base (MICROAUTH_BASE_URL)
header_nameX-API-KeyHeader your customers send their key in
redis_urlMICROAUTH_REDIS_URLShares limits, snapshots and pending usage across processes
redis_clientNoneA redis.asyncio client you own, used instead of redis_url
redis_max_connections64Cap for the pool built from redis_url. Requests wait up to one second for a free connection
shared_snapshot_cacheTrueWith Redis, share snapshots so cold starts don't all fetch at once
sync_interval30Seconds between snapshot refreshes
report_interval5Usage is sent when 500 requests are waiting or this many seconds pass
flush_on_responseAutoTrue on Vercel and AWS Lambda. Sends a due batch after each response
trailing_flushFalseKeeps a serverless invocation alive until the batch deadline so the last burst is reported
max_snapshot_age300With fail_open=False, requests get 503 once the snapshot is this old
max_stale_snapshot_age3 × max_snapshot_ageHard ceiling for serving known keys from stale data
fail_openTrueKeep serving known keys from a stale snapshot until the hard ceiling
enforce_balanceTrueReturn 402 when a paying customer has no credit left
enforce_quotaTrueReturn 429 when the monthly quota is used up
enforce_rpsTrueApply per customer requests a second limits
enforce_platform_allowanceTrueReserve against your MicroAuth plan's monthly allowance before the handler runs
verify_negative_ttl30Seconds an unknown key is remembered as invalid
timeout5HTTP timeout in seconds for MicroAuth calls. Each call makes up to three attempts
http_clientNoneAn httpx.AsyncClient you own
journal_dirMICROAUTH_JOURNAL_DIRWhere 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_usageTrueKeep unacknowledged usage across restarts. Turn off only in tests
max_usage_queue10000Most reserved and queued usage items at once
shutdown_timeout10Seconds allowed for the final usage drain
on_receiptsNoneCalled with the receipts of each delivery
on_rejectionsNoneCalled 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 ​

DependencyResolves toUse it for
Security(auth)CustomerRoutes that require a valid key
Security(auth.optional)Customer | NoneRoutes 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.

FieldTypeMeaning
idstrThe customer's team ID, a UUID. Stable across keys and teammates
key_idstrID of the API key used for this request
statusstrAlways active inside a handler, because other customers are rejected first
billing_modelstrpayg, subscription or none
rpsintEffective requests a second
price_per_request_microintEffective price per billable request, in micro-USD
monthly_quotaint | NoneEffective monthly request cap, or None for no cap
credit_balance_microintEstimated prepaid balance after this request's reservation
platform_monthly_limitint | NoneYour MicroAuth plan's monthly allowance
platform_monthly_remainingint | NoneAllowance left this month
platform_monthly_period_enddatetime | NoneWhen the allowance resets

Money is always an integer in micro-USD: 1,000,000 is one dollar.

Lifecycle ​

MethodWhen 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_nameThe 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.

ExceptionStatusBody detailHeaders
InvalidAPIKey401Invalid or missing API keyWWW-Authenticate: ApiKey header="X-API-Key"
PaymentRequired402Insufficient credit balance
CustomerSuspended403This account is suspended
CustomerPending403This account is waiting for approval
RateLimited429Rate limit exceededRetry-After
QuotaExceeded429Monthly request quota exceeded
PlatformAllowanceExceeded429Platform monthly request allowance exhausted
AuthUnavailable503Authorization 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.

FieldTypeMeaning
idempotency_keystrThe item's ID, the same on every delivery attempt
outcomestraccepted when this delivery recorded the item, duplicate when MicroAuth had recorded it before
customer_idstr | NoneThe customer MicroAuth charged
api_key_idstrThe key the requests used
usage_policy_idstrThe prices the requests were admitted under
status_codeintThe response status the item counts
countintRequests in the item. They share a key, policy, status and UTC hour
period_startdatetimeThe UTC hour the requests were served in
charged_microint | NoneWhat MicroAuth charged, in micro-USD
expected_charge_microint | NoneWhat the SDK expected from the prices it enforced. None when this process didn't admit all of the requests
charge_matchesbool | NoneWhether the two agree, when both are known. The SDK logs every mismatch
recorded_atdatetime | NoneWhen MicroAuth recorded the item
delivered_atdatetimeWhen 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.

FieldTypeMeaning
idempotency_keystrThe item's ID
api_key_id, usage_policy_id, status_code, count, period_startAs on a receipt
http_statusint | NoneThe status MicroAuth gave the item, or None when the SDK refused it
detailstrWhy it was refused
rejected_atdatetimeWhen, 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.

FieldMeaning
durabilityWhere queued usage survives a crash: redis, journal or memory
journal_dirThis API's journal directory. With Redis, it is the fallback while Redis is unreachable
queued_items, queued_requestsUsage waiting for delivery
oldest_queued_atWhen the oldest waiting request was queued
delivered_items, delivered_requestsUsage MicroAuth has recorded, duplicates included
duplicate_itemsItems MicroAuth had already recorded on an earlier attempt
rejected_items, rejected_requestsUsage MicroAuth refused for good
charged_microThe sum of charged_micro over every receipt
charge_mismatchesReceipts whose charge differed from the expected one
last_delivery_atWhen MicroAuth last acknowledged a delivery
consecutive_failuresDeliveries that failed in a row. It resets on the next success
last_error, last_error_atThe latest delivery error
clock_offset_secondsSeconds to add to this machine's clock to read MicroAuth's
hook_failuresReceipt 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 check

It checks, in order:

  1. Configuration. The SDK secret key is present and looks like one (mas_...).
  2. 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.
  3. API key. With --key, it explains how a request with that key would be treated: allowed, or refused with 402, 403 or 429 and why.
  4. Shared state. With Redis configured, it confirms Redis is reachable and accepts Lua scripts.
  5. 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.

FlagDefaultMeaning
--keynoneA customer API key (map_...) to evaluate
--secret-keyMICROAUTH_SECRET_KEYSDK secret key to test
--base-urlMICROAUTH_BASE_URLMicroAuth API URL
--redis-urlMICROAUTH_REDIS_URLRedis URL to test
--journal-dirMICROAUTH_JOURNAL_DIRUsage journal directory to test
--timeout10HTTP 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 alerts
text
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-1d3e5f7b9a24

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

FlagDefaultMeaning
--jsonoffPrint JSON instead of text
--limit50How many of the newest refused items to list per queue
--secret-key, --base-url, --redis-url, --journal-dir, --timeoutAs for checkWhich API, Redis and journal to read

What the SDK sends ​

  • Every call carries Authorization: Bearer mas_... and a User-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 answers 304 Not Modified with 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.

MicroAuth is a product of Zyref, LLC.