Skip to main content

MicroAuth SDK for FastAPI

API key authentication, per-customer rate limiting and metered billing for your FastAPI API — powered by your MicroAuth tenant, in one dependency.

from fastapi import FastAPI, Security
from microauth_fastapi import MicroAuth, Customer

app = FastAPI()
auth = MicroAuth(app)  # tenant secret from MICROAUTH_SECRET_KEY

@app.get("/forecast")
async def forecast(customer: Customer = Security(auth)):
    return {"hello": customer.id}

That's the whole integration. Your customers sign up on your MicroAuth developer portal, create API keys, pick a plan or top up credits — and every request to /forecast is now authenticated, rate-limited and billed.

What it does

  • API key auth via the X-API-Key header (configurable), including the security scheme in your OpenAPI docs — the Swagger "Authorize" button just works.
  • Suspension, balance and quota enforcement — suspended customers get 403, customers who ran out of prepaid credit get 402, customers over their monthly quota get 429.
  • Per-customer rate limiting at each customer's effective RPS (custom override → plan → pay-as-you-go default), scoped to each API credential, with 429 and Retry-After.
  • Atomic request reservations for customer quota, possible prepaid spend, and the platform monthly allowance before your endpoint starts. Redis coordinates the complete decision across workers.
  • Complete usage reporting for every completed authenticated request, grouped by API key and exact response status. Only configured billable statuses consume prepaid credit.

Designed for the hot path

  • A snapshot of your customers, keys and limits is cached in memory and refreshed in the background (default every 30s). With Redis, validated snapshots are shared across instances and one distributed refresh lock prevents autoscaling cold starts from stampeding the control plane.
  • Keys not in the snapshot yet (created seconds ago) are resolved once via a single-flight on-demand lookup; invalid keys are negatively cached so a flood of bad keys can't reach MicroAuth.
  • Usage is journaled to SQLite as each request completes, then sent in background batches (default every 15s). Every item is immutable and keeps one idempotency key across transport retries, requeues, concurrent workers, and process restarts.
  • Authentication itself is a SHA-256 and a couple of dict lookups.

Long-lived processes report usage in background batches and do not call MicroAuth on the normal authentication hot path. Vercel and AWS Lambda can freeze the event loop as soon as a response completes, so the SDK automatically awaits usage delivery before releasing the final response there. Set flush_on_response explicitly to override environment detection.

If MicroAuth is briefly unreachable, your API keeps serving with the last known data (fail_open=True, the default), but never forever. Stale data has an absolute max_stale_snapshot_age limit. Set fail_open=False to return 503 at the earlier max_snapshot_age threshold. An expired usage policy is always refreshed before another request is authorized; if that refresh fails, the SDK returns 503 rather than creating usage under stale billing terms.

Multiple workers? Add Redis

In-memory request limits are exact within one process. With 4 uvicorn workers, each process has independent RPS, quota, balance, and platform reservation state. Point the SDK at Redis for one atomic shared reservation across every worker and machine. The same Redis connection also shares snapshots and coordinates refreshes:

auth = MicroAuth(app, redis_url="redis://localhost:6379/0")
pip install 'microauth-fastapi[redis]'

RPS uses a fixed one-second window in both backends. Its key includes both the customer and credential, so traffic on one key does not throttle another key owned by the same customer. RPS fails open briefly if Redis is down. Quota, potential spend, and platform reservations fail closed because serving without an atomic decision could oversubscribe a hard limit.

Serverless runtimes

On Vercel and AWS Lambda, flush_on_response defaults to True. This fixes the case where the report timer is frozen after a low-traffic request and only runs when the next invocation arrives. Delivery is attempted before the final body is released; transient failures remain in the configured journal for retry.

Use Redis in autoscaling deployments so snapshots are shared across instances. SQLite journals are local to each instance, so configure a durable usage_spool_path only on platforms that provide a shared durable filesystem. You can also call await auth.flush_usage() from an explicit lifecycle or job.

Optional authentication

For endpoints that serve both anonymous and authenticated traffic:

@app.get("/status")
async def status(customer: Customer | None = Security(auth.optional)):
    return {"authenticated": customer is not None}

Settings

Everything has a sensible default; override only what you need.

Setting Default What it does
secret_key $MICROAUTH_SECRET_KEY Tenant secret key (mas_...)
base_url https://api.microauth.com MicroAuth API ($MICROAUTH_BASE_URL)
header_name X-API-Key Header customers send their key in
redis_url $MICROAUTH_REDIS_URL Share snapshots and enforce exact cross-worker limits
shared_snapshot_cache True Share snapshots and refresh locks when Redis is configured
sync_interval 30 Seconds between snapshot refreshes
report_interval 15 Maximum active-runtime age of the oldest queued usage
flush_on_response Auto on Vercel/Lambda Await usage delivery before final response
max_snapshot_age 300 Fail-closed staleness threshold
max_stale_snapshot_age 3 × max_snapshot_age Absolute stale-data ceiling
fail_open True Serve stale data only up to the absolute ceiling
enforce_balance True 402 when prepaid credit is exhausted
enforce_quota True 429 when the monthly quota is used up
enforce_rps True Per-customer RPS limiting
enforce_platform_allowance True Enforce the platform monthly hard cap
verify_negative_ttl 30 Seconds an invalid key is cached
timeout 5 HTTP timeout for MicroAuth calls
usage_spool_path Tenant-specific temp file SQLite journal for completed usage
persistence_namespace Snapshot tenant ID or secret fingerprint Stable Redis and journal namespace
persist_usage True Keep stable IDs across process restarts
max_usage_queue 10000 Bounded journaled usage item count
shutdown_timeout 10 Maximum seconds for final usage drain
http_client None Optional externally owned async HTTP client
redis_client None Optional externally owned async Redis client

Error responses

Status When
401 Missing or invalid API key
402 Prepaid balance exhausted
403 Customer suspended
429 RPS, customer quota, or platform monthly hard cap exceeded
503 Snapshot too stale, shared hard-cap backend down, or usage queue full

All request denials subclass microauth_fastapi.AuthDenied (itself a FastAPI HTTPException), so you can add your own exception handler to reshape response bodies. Configuration, snapshot validation, API transport, usage acknowledgement, spool and shutdown-drain failures also have exported typed exceptions.

Consistency notes

  • Redis provides one atomic shared decision for quota, possible spend, and the platform hard cap. Without Redis, each process enforces independently.
  • Every request reserves customer and platform capacity before the endpoint starts. Those reservations remain consumed for every final status because the control plane counts every request. A nonbillable result releases only its monetary reservation.
  • Completed usage is written to SQLite before it enters the delivery queue. Put usage_spool_path on a durable volume when host replacement must also be survived.
  • A stable tenant identifier from the snapshot is used for persistence when available. Set persistence_namespace or an explicit usage_spool_path when the control plane does not expose one and secret rotation must retain the same journal.
  • Graceful shutdown drains the queue within shutdown_timeout. If delivery cannot complete, shutdown fails with UsageDrainError; persisted items are retained for the next process.
  • Suspensions and key revocations propagate within one sync_interval.

SDK API contract

The SDK expects GET /sdk/v1/snapshot to return this shape:

{
  "generated_at": "2026-08-04T00:15:00Z",
  "billable_status_codes": [200, 201, 207],
  "platform_monthly_limit": 1000000,
  "platform_monthly_used": 125000,
  "platform_monthly_remaining": 875000,
  "platform_period_end": "2026-09-01T00:00:00Z",
  "platform_hard_cap": true,
  "customers": [
    {
      "id": "cus_123",
      "status": "active",
      "credit_balance_micro": 25000000,
      "month_requests": 1250,
      "effective": {
        "rps": 25,
        "price_per_request_micro": 1000,
        "monthly_quota": 100000,
        "billing_model": "plan",
        "source": "plan"
      }
    }
  ],
  "keys": [
    {
      "id": "key_123",
      "customer_id": "cus_123",
      "key_hash": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
    }
  ]
}

generated_at and platform_period_end must be offset-aware ISO 8601. platform_monthly_remaining must equal max(0, platform_monthly_limit - platform_monthly_used). Counts and micro-unit amounts must be JSON integers in the inclusive range 0..9007199254740991, except a credit balance may be negative down to -9007199254740991. Status codes must be integers from 100 through 599.

The SDK sends POST /sdk/v1/usage with up to 1000 items:

{
  "items": [
    {
      "idempotency_key": "bb02a6a7-10f4-4ab7-bf9c-8936770b67b7",
      "api_key_id": "11111111-1111-4111-8111-111111111111",
      "status_code": 207,
      "count": 42,
      "period_start": "2026-08-04T00:00:00Z"
    }
  ]
}

The API must return exactly one result per submitted idempotency key:

{
  "results": [
    {
      "idempotency_key": "bb02a6a7-10f4-4ab7-bf9c-8936770b67b7",
      "status": "accepted"
    }
  ]
}

Successful statuses are accepted and duplicate. The reporter also handles a per-item rejected status with an optional string detail. The current Go API returns terminal item failures as an HTTP error, so the SDK bisects a failed batch to isolate and dead-letter only the terminal item. Network failures, 429, and 5xx responses retain the original payload for retry. Missing, repeated, unknown, or unsupported results are treated as transient acknowledgement failures.

Example app

See examples/weather_api.py:

MICROAUTH_SECRET_KEY=mas_... uvicorn examples.weather_api:app --reload

Metadata

Release files for microauth-fastapi 2.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for microauth-fastapi 2.2.0
File Size Uploaded
microauth_fastapi-2.2.0.tar.gz 52.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for microauth-fastapi 2.2.0
File Interpreter ABI Platform
microauth_fastapi-2.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 92.9 kB

Release files / microauth_fastapi-2.2.0.tar.gz

Download URL microauth_fastapi-2.2.0.tar.gz
Size 52.9 kB
Tags Source
SHA-256 checksum
How to use checksums
fdb25f4a9f71749e53823307fef72b1067f6ccd234b46cc9f333655f68b388d8
BLAKE2b-256 checksum
How to use checksums
4563cffdb876655c4f3db44ccdfe3a9a5fae69d8270cbee906ad606eadc18933
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Release files / microauth_fastapi-2.2.0-py3-none-any.whl

Download URL microauth_fastapi-2.2.0-py3-none-any.whl
Size 40.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0a139c1c1b09a876d678fc197c178926f8c0107597567b3f2f8192a8e136c0f8
BLAKE2b-256 checksum
How to use checksums
fa488d5ca3bb79e476bf25cffa44ebec111a8d2ec291c12a7d81aec00b1b662d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.

Transparency log

Release history Release notifications | RSS feed

3.0.0

2 release files

2.8.1

2 release files

2.8.0

2 release files

2.7.0

2 release files

2.6.0

2 release files

2.5.0

2 release files

2.4.0

2 release files

2.3.0

2 release files

This release

2.2.0 This release

2 release files

2.1.0

2 release files

2.0.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page