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

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_idrather 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_idrather 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:






















