Table of Content

Table of Content

How to Add Metering to a FastAPI App for Usage Billing

How to Add Metering to a FastAPI App for Usage Billing

How to Add Metering to a FastAPI App for Usage Billing

How to Add Metering to a FastAPI App for Usage Billing

How to Add Metering to a FastAPI App for Usage Billing

flexprice logo

Team Flexprice

Editorial

To add usage metering to a FastAPI app, put middleware on the request path that captures the billable event, hand it to a background task that ships it to your billing system, and never block the response on that call. Flexprice ingests those events and handles aggregation, rating and invoicing so your service only emits.

Key Takeaways

  • FastAPI middleware is the right capture point: it sees the customer, the route and the response status in one place.

  • Ship events asynchronously. A synchronous call to a billing API adds its latency to every request.

  • Only meter successful responses, or write down which status codes bill, because a 500 that charges becomes a support ticket.

  • Every event needs an idempotency key, or retries at any layer double-bill.

  • Flexprice ingests at up to 1 million events per second with exactly-once delivery, and its event debugger shows every event sent.

Where does metering belong in a FastAPI app?

Metering belongs in middleware, not each route handler. Middleware runs once per request, reads the identity your dependency chain set, and sees the status code, so events stay consistent without touching every endpoint.

  • Middleware for per-request metrics: calls, bytes, duration, status.

  • Inside the handler for metrics only that handler knows, like tokens a model call consumed.

  • A background task for shipping, so the response doesn't wait on your billing system.

  • A queue past a few hundred requests a second, so a billing outage can't drop revenue. Our guide to tracking API usage for billing in real time covers the pipeline.

How do I write the middleware?

Capture the customer, the metric and a unique event ID, then hand it off. This example meters only successful responses, and reads the customer with getattr so a route that never sets one can't turn a 200 into a 500.

@app.middleware("http")
async def meter(request: Request, call_next):
    start = time.perf_counter()
    response = await call_next(request)
    customer_id = getattr(request.state, "customer_id", None)
    if customer_id and response.status_code < 400:
        event = {
            "event_id": str(uuid.uuid4()),
            "event_name": "api_request",
            "external_customer_id": customer_id,
            "timestamp": datetime.now(timezone.utc)
                .isoformat().replace("+00:00", "Z"),
            "properties": {
                "route": request.url.path,
                "duration_ms": round((time.perf_counter() - start) * 1000, 2),
            },
        }
        asyncio.create_task(send_event(event))
    return response
@app.middleware("http")
async def meter(request: Request, call_next):
    start = time.perf_counter()
    response = await call_next(request)
    customer_id = getattr(request.state, "customer_id", None)
    if customer_id and response.status_code < 400:
        event = {
            "event_id": str(uuid.uuid4()),
            "event_name": "api_request",
            "external_customer_id": customer_id,
            "timestamp": datetime.now(timezone.utc)
                .isoformat().replace("+00:00", "Z"),
            "properties": {
                "route": request.url.path,
                "duration_ms": round((time.perf_counter() - start) * 1000, 2),
            },
        }
        asyncio.create_task(send_event(event))
    return response
@app.middleware("http")
async def meter(request: Request, call_next):
    start = time.perf_counter()
    response = await call_next(request)
    customer_id = getattr(request.state, "customer_id", None)
    if customer_id and response.status_code < 400:
        event = {
            "event_id": str(uuid.uuid4()),
            "event_name": "api_request",
            "external_customer_id": customer_id,
            "timestamp": datetime.now(timezone.utc)
                .isoformat().replace("+00:00", "Z"),
            "properties": {
                "route": request.url.path,
                "duration_ms": round((time.perf_counter() - start) * 1000, 2),
            },
        }
        asyncio.create_task(send_event(event))
    return response

The event_id makes the send retryable: post the same event twice and it counts once. external_customer_id is your own identifier for the account, and it's the required field on Flexprice's POST /v1/events, which authenticates with an x-api-key header and returns 202.

How do I design billable metrics for a Python API?

Design one primary metric customers can count themselves, and add a second only when the first misses a real cost driver. Every metric needs a name, a customer, a timestamp and an idempotency key.

Element

Purpose

Common mistake

Event shape



event_id

Deduplication across retries

Generating it inside the retry loop

external_customer_id

Attribution and entitlements

Sending a user ID on a team account

timestamp

Rating into the right period

Server receipt time instead of event time

event_name

Selects the meter

One generic name for every route

Properties



route or endpoint

Per-feature pricing later

Flattening all routes into one metric

quantity

Rating multiple units per event

Emitting one event per unit instead

model or tier

Cost attribution

Omitting it, then losing margin visibility

Delivery



Async dispatch

Keeps latency off the request path

Awaiting the billing call inline

Retry with backoff

Survives a transient outage

Dropping the event on first failure

Local buffer or queue

Survives a longer outage

Relying on in-process tasks only

Batching

Cuts overhead at volume

Batching until balances go stale

To add usage metering to a FastAPI app, put middleware on the request path that captures the billable event, hand it to a background task that ships it to your billing system, and never block the response on that call. Flexprice ingests those events and handles aggregation, rating and invoicing so your service only emits.

Key Takeaways

  • FastAPI middleware is the right capture point: it sees the customer, the route and the response status in one place.

  • Ship events asynchronously. A synchronous call to a billing API adds its latency to every request.

  • Only meter successful responses, or write down which status codes bill, because a 500 that charges becomes a support ticket.

  • Every event needs an idempotency key, or retries at any layer double-bill.

  • Flexprice ingests at up to 1 million events per second with exactly-once delivery, and its event debugger shows every event sent.

Where does metering belong in a FastAPI app?

Metering belongs in middleware, not each route handler. Middleware runs once per request, reads the identity your dependency chain set, and sees the status code, so events stay consistent without touching every endpoint.

  • Middleware for per-request metrics: calls, bytes, duration, status.

  • Inside the handler for metrics only that handler knows, like tokens a model call consumed.

  • A background task for shipping, so the response doesn't wait on your billing system.

  • A queue past a few hundred requests a second, so a billing outage can't drop revenue. Our guide to tracking API usage for billing in real time covers the pipeline.

How do I write the middleware?

Capture the customer, the metric and a unique event ID, then hand it off. This example meters only successful responses, and reads the customer with getattr so a route that never sets one can't turn a 200 into a 500.

@app.middleware("http")
async def meter(request: Request, call_next):
    start = time.perf_counter()
    response = await call_next(request)
    customer_id = getattr(request.state, "customer_id", None)
    if customer_id and response.status_code < 400:
        event = {
            "event_id": str(uuid.uuid4()),
            "event_name": "api_request",
            "external_customer_id": customer_id,
            "timestamp": datetime.now(timezone.utc)
                .isoformat().replace("+00:00", "Z"),
            "properties": {
                "route": request.url.path,
                "duration_ms": round((time.perf_counter() - start) * 1000, 2),
            },
        }
        asyncio.create_task(send_event(event))
    return response

The event_id makes the send retryable: post the same event twice and it counts once. external_customer_id is your own identifier for the account, and it's the required field on Flexprice's POST /v1/events, which authenticates with an x-api-key header and returns 202.

How do I design billable metrics for a Python API?

Design one primary metric customers can count themselves, and add a second only when the first misses a real cost driver. Every metric needs a name, a customer, a timestamp and an idempotency key.

Element

Purpose

Common mistake

Event shape



event_id

Deduplication across retries

Generating it inside the retry loop

external_customer_id

Attribution and entitlements

Sending a user ID on a team account

timestamp

Rating into the right period

Server receipt time instead of event time

event_name

Selects the meter

One generic name for every route

Properties



route or endpoint

Per-feature pricing later

Flattening all routes into one metric

quantity

Rating multiple units per event

Emitting one event per unit instead

model or tier

Cost attribution

Omitting it, then losing margin visibility

Delivery



Async dispatch

Keeps latency off the request path

Awaiting the billing call inline

Retry with backoff

Survives a transient outage

Dropping the event on first failure

Local buffer or queue

Survives a longer outage

Relying on in-process tasks only

Batching

Cuts overhead at volume

Batching until balances go stale

To add usage metering to a FastAPI app, put middleware on the request path that captures the billable event, hand it to a background task that ships it to your billing system, and never block the response on that call. Flexprice ingests those events and handles aggregation, rating and invoicing so your service only emits.

Key Takeaways

  • FastAPI middleware is the right capture point: it sees the customer, the route and the response status in one place.

  • Ship events asynchronously. A synchronous call to a billing API adds its latency to every request.

  • Only meter successful responses, or write down which status codes bill, because a 500 that charges becomes a support ticket.

  • Every event needs an idempotency key, or retries at any layer double-bill.

  • Flexprice ingests at up to 1 million events per second with exactly-once delivery, and its event debugger shows every event sent.

Where does metering belong in a FastAPI app?

Metering belongs in middleware, not each route handler. Middleware runs once per request, reads the identity your dependency chain set, and sees the status code, so events stay consistent without touching every endpoint.

  • Middleware for per-request metrics: calls, bytes, duration, status.

  • Inside the handler for metrics only that handler knows, like tokens a model call consumed.

  • A background task for shipping, so the response doesn't wait on your billing system.

  • A queue past a few hundred requests a second, so a billing outage can't drop revenue. Our guide to tracking API usage for billing in real time covers the pipeline.

How do I write the middleware?

Capture the customer, the metric and a unique event ID, then hand it off. This example meters only successful responses, and reads the customer with getattr so a route that never sets one can't turn a 200 into a 500.

@app.middleware("http")
async def meter(request: Request, call_next):
    start = time.perf_counter()
    response = await call_next(request)
    customer_id = getattr(request.state, "customer_id", None)
    if customer_id and response.status_code < 400:
        event = {
            "event_id": str(uuid.uuid4()),
            "event_name": "api_request",
            "external_customer_id": customer_id,
            "timestamp": datetime.now(timezone.utc)
                .isoformat().replace("+00:00", "Z"),
            "properties": {
                "route": request.url.path,
                "duration_ms": round((time.perf_counter() - start) * 1000, 2),
            },
        }
        asyncio.create_task(send_event(event))
    return response

The event_id makes the send retryable: post the same event twice and it counts once. external_customer_id is your own identifier for the account, and it's the required field on Flexprice's POST /v1/events, which authenticates with an x-api-key header and returns 202.

How do I design billable metrics for a Python API?

Design one primary metric customers can count themselves, and add a second only when the first misses a real cost driver. Every metric needs a name, a customer, a timestamp and an idempotency key.

Element

Purpose

Common mistake

Event shape



event_id

Deduplication across retries

Generating it inside the retry loop

external_customer_id

Attribution and entitlements

Sending a user ID on a team account

timestamp

Rating into the right period

Server receipt time instead of event time

event_name

Selects the meter

One generic name for every route

Properties



route or endpoint

Per-feature pricing later

Flattening all routes into one metric

quantity

Rating multiple units per event

Emitting one event per unit instead

model or tier

Cost attribution

Omitting it, then losing margin visibility

Delivery



Async dispatch

Keeps latency off the request path

Awaiting the billing call inline

Retry with backoff

Survives a transient outage

Dropping the event on first failure

Local buffer or queue

Survives a longer outage

Relying on in-process tasks only

Batching

Cuts overhead at volume

Batching until balances go stale

AI Billing Is Not Easy, But Flexprice Can Make it Easy

AI Billing Is Not Easy, But Flexprice Can Make it Easy

How do I handle async, background and streaming endpoints?

Emit the event where the work finishes, not where the request returns. A background job running after the response has gone out emits its own event, and a streaming endpoint emits once on completion with the final counts.

  • For background jobs, emit from the worker with the original customer ID.

  • For streaming responses, emit in the completion or cancellation handler, so a cancelled stream still bills what it generated.

  • For WebSockets, emit on close with the session duration and message counts.

  • For retries in your own code, reuse the original event_id rather than minting a new one.

Which billing system should FastAPI send events to?

Flexprice is enterprise-grade, open source usage based billing infrastructure for AI and SaaS companies. It can be deployed in your own VPC, on-prem, or on Flexprice's managed cloud.

Your service emits events and Flexprice does the rest: aggregation, tiered rating, entitlement checks, invoicing and reconciliation.

  • Usage Metering ingests from APIs, microservices, webhooks or a warehouse at up to 1 million events per second, under 60ms P99.

  • Exactly-once delivery and automatic reconciliation mean a retried send doesn't become a double charge.

  • The event debugger shows every ingested event, so you verify accuracy before customers see an invoice.

  • Sandbox testing replays billing changes against real event shapes before production.

  • Plans run monthly or yearly: free to 100K events, $500 at 1M, $1,000 at 5M. The full API reference sits in the Flexprice docs.

Frequently asked questions

How do I test metering accuracy in a FastAPI service?

Replay a known set of requests through staging, then reconcile the event count in your billing system against your application logs. They should match exactly. Assert on that count in an integration test, so a refactor that drops the middleware fails CI rather than a customer's invoice.

Should I meter in middleware or in a dependency?

Middleware, for anything measurable from the request and response. A dependency runs before the handler, so it can't see the status code or response size and would bill failed requests. Use a dependency to attach the customer ID instead: request.state rides on the ASGI scope, so the middleware reads it after call_next returns.

What happens if my billing system is unreachable?

Buffer locally and retry with backoff, keeping the original event ID so delivery deduplicates. Never fail a customer's request because metering failed. For high-volume services, put a queue between your app and the billing system so a longer outage doesn't lose revenue.

How do I handle async, background and streaming endpoints?

Emit the event where the work finishes, not where the request returns. A background job running after the response has gone out emits its own event, and a streaming endpoint emits once on completion with the final counts.

  • For background jobs, emit from the worker with the original customer ID.

  • For streaming responses, emit in the completion or cancellation handler, so a cancelled stream still bills what it generated.

  • For WebSockets, emit on close with the session duration and message counts.

  • For retries in your own code, reuse the original event_id rather than minting a new one.

Which billing system should FastAPI send events to?

Flexprice is enterprise-grade, open source usage based billing infrastructure for AI and SaaS companies. It can be deployed in your own VPC, on-prem, or on Flexprice's managed cloud.

Your service emits events and Flexprice does the rest: aggregation, tiered rating, entitlement checks, invoicing and reconciliation.

  • Usage Metering ingests from APIs, microservices, webhooks or a warehouse at up to 1 million events per second, under 60ms P99.

  • Exactly-once delivery and automatic reconciliation mean a retried send doesn't become a double charge.

  • The event debugger shows every ingested event, so you verify accuracy before customers see an invoice.

  • Sandbox testing replays billing changes against real event shapes before production.

  • Plans run monthly or yearly: free to 100K events, $500 at 1M, $1,000 at 5M. The full API reference sits in the Flexprice docs.

Frequently asked questions

How do I test metering accuracy in a FastAPI service?

Replay a known set of requests through staging, then reconcile the event count in your billing system against your application logs. They should match exactly. Assert on that count in an integration test, so a refactor that drops the middleware fails CI rather than a customer's invoice.

Should I meter in middleware or in a dependency?

Middleware, for anything measurable from the request and response. A dependency runs before the handler, so it can't see the status code or response size and would bill failed requests. Use a dependency to attach the customer ID instead: request.state rides on the ASGI scope, so the middleware reads it after call_next returns.

What happens if my billing system is unreachable?

Buffer locally and retry with backoff, keeping the original event ID so delivery deduplicates. Never fail a customer's request because metering failed. For high-volume services, put a queue between your app and the billing system so a longer outage doesn't lose revenue.

Share it on:

Ship Usage-Based Billing with Flexprice

Ship Usage-Based Billing with Flexprice

Ship Usage-Based Billing with Flexprice

More insights on billing

More insights on billing

Get Instant Feedback on Your Pricing | Join the Flexprice Community with 400+ Builders on Slack

Join the Flexprice Community on Slack