Meters define how usage events are filtered and aggregated to calculate consumption. Each meter represents a billable metric in your product.
Creating a Meter
A meter consists of:
Name : Human-readable label (shown on invoices)
Filter : Which events to include
Aggregation : How to calculate the quantity
curl -X POST "https://api.polar.sh/v1/meters" \
-H "Authorization: Bearer polar_at_..." \
-H "Content-Type: application/json" \
-d '{
"name": "API Requests",
"filter": {
"conjunction": "and",
"clauses": [
{
"property": "name",
"operator": "eq",
"value": "api.request"
}
]
},
"aggregation": {
"func": "count"
}
}'
Filter Configuration
Filters determine which events are included in the meter calculation.
Filter Structure
A filter has:
conjunction : "and" or "or" - how to combine clauses
clauses : Array of filter clauses (or nested filters)
Each clause has:
property : Event field to filter on
operator : Comparison operator
value : Value to compare against
Filter Operators
Operator Description Example eqEquals "model" eq "gpt-4"neNot equals "status" ne "error"gtGreater than "tokens" gt 1000gteGreater than or equal "duration" gte 60ltLess than "size" lt 1000000lteLess than or equal "price" lte 100likeContains (case-insensitive) "endpoint" like "chat"not_likeDoes not contain "path" not_like "admin"
Filterable Properties
You can filter on:
name : Event name
source : "user" or "system"
timestamp : Unix timestamp (integer)
metadata. : Any metadata field (use dot notation for nested fields)
Basic Filter Examples
Count All API Requests
{
"conjunction" : "and" ,
"clauses" : [
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "api.request"
}
]
}
Count GPT-4 Requests Only
{
"conjunction" : "and" ,
"clauses" : [
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "api.request"
},
{
"property" : "metadata.model" ,
"operator" : "eq" ,
"value" : "gpt-4"
}
]
}
Count Large File Uploads
{
"conjunction" : "and" ,
"clauses" : [
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "storage.upload"
},
{
"property" : "metadata.size_bytes" ,
"operator" : "gt" ,
"value" : 10485760
}
]
}
Complex Filters (OR Logic)
Match multiple event types:
{
"conjunction" : "or" ,
"clauses" : [
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "api.request"
},
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "api.batch"
}
]
}
Nested Filters
Combine AND and OR logic:
{
"conjunction" : "and" ,
"clauses" : [
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "api.request"
},
{
"conjunction" : "or" ,
"clauses" : [
{
"property" : "metadata.model" ,
"operator" : "eq" ,
"value" : "gpt-4"
},
{
"property" : "metadata.model" ,
"operator" : "eq" ,
"value" : "gpt-4-turbo"
}
]
}
]
}
This matches: api.request events where model is gpt-4 OR gpt-4-turbo
Aggregation Functions
Aggregation determines how filtered events are calculated into a quantity.
Count Aggregation
Count the number of events:
Use for: API calls, transactions, page views, file uploads
Sum Aggregation
Sum a numeric metadata field:
{
"func" : "sum" ,
"property" : "metadata.tokens"
}
Use for: Total tokens, total bytes, total duration, total cost
Max Aggregation
Find the maximum value:
{
"func" : "max" ,
"property" : "metadata.concurrent_users"
}
Use for: Peak concurrent users, max API calls per hour, highest latency
Min Aggregation
Find the minimum value:
{
"func" : "min" ,
"property" : "metadata.response_time_ms"
}
Use for: Minimum latency, lowest throughput
Average Aggregation
Calculate the average:
{
"func" : "avg" ,
"property" : "metadata.duration_seconds"
}
Use for: Average request duration, average file size, average cost per request
Unique Aggregation
Count distinct values:
{
"func" : "unique" ,
"property" : "metadata.user_id"
}
Use for: Monthly active users, unique IPs, distinct projects accessed
Common Meter Configurations
API Usage by Model
GPT-4 Tokens
GPT-3.5 Tokens
{
"name" : "GPT-4 Tokens" ,
"filter" : {
"conjunction" : "and" ,
"clauses" : [
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "llm.completion"
},
{
"property" : "metadata._llm.model" ,
"operator" : "like" ,
"value" : "gpt-4"
}
]
},
"aggregation" : {
"func" : "sum" ,
"property" : "metadata._llm.total_tokens"
}
}
{
"name" : "GPT-3.5 Tokens" ,
"filter" : {
"conjunction" : "and" ,
"clauses" : [
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "llm.completion"
},
{
"property" : "metadata._llm.model" ,
"operator" : "like" ,
"value" : "gpt-3.5"
}
]
},
"aggregation" : {
"func" : "sum" ,
"property" : "metadata._llm.total_tokens"
}
}
Storage Usage
{
"name" : "Storage (GB)" ,
"filter" : {
"conjunction" : "and" ,
"clauses" : [
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "storage.snapshot"
}
]
},
"aggregation" : {
"func" : "sum" ,
"property" : "metadata.gigabytes"
}
}
Compute Hours
{
"name" : "Compute Hours" ,
"filter" : {
"conjunction" : "and" ,
"clauses" : [
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "compute.job"
}
]
},
"aggregation" : {
"func" : "sum" ,
"property" : "metadata.duration_hours"
}
}
Monthly Active Users
{
"name" : "Monthly Active Users" ,
"filter" : {
"conjunction" : "and" ,
"clauses" : [
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "user.active"
}
]
},
"aggregation" : {
"func" : "unique" ,
"property" : "metadata.user_id"
}
}
Peak Concurrent Connections
{
"name" : "Peak Concurrent Connections" ,
"filter" : {
"conjunction" : "and" ,
"clauses" : [
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "connection.active"
}
]
},
"aggregation" : {
"func" : "max" ,
"property" : "metadata.count"
}
}
Querying Meter Quantities
Retrieve consumption for a time period:
from datetime import datetime, timezone
quantities = client.meters.quantities(
id = meter.id,
start_timestamp = datetime( 2024 , 3 , 1 , tzinfo = timezone.utc),
end_timestamp = datetime( 2024 , 3 , 31 , tzinfo = timezone.utc),
interval = "day" ,
)
print ( f "Total: { quantities.total } " )
for q in quantities.quantities:
print ( f " { q.timestamp } : { q.quantity } " )
Intervals
Available intervals:
hour: Hourly breakdown
day: Daily breakdown
week: Weekly breakdown
month: Monthly breakdown
year: Yearly breakdown
Customer Filtering
Query quantities for specific customers:
quantities = client.meters.quantities(
id = meter.id,
start_timestamp = start,
end_timestamp = end,
interval = "day" ,
customer_id = [customer_id], # Filter by customer
)
Per-Customer Aggregation
Aggregate quantities across customers:
# Get sum across all customers
quantities = client.meters.quantities(
id = meter.id,
start_timestamp = start,
end_timestamp = end,
interval = "day" ,
customer_aggregation_function = "sum" ,
)
# Get average per customer
quantities = client.meters.quantities(
id = meter.id,
start_timestamp = start,
end_timestamp = end,
interval = "day" ,
customer_aggregation_function = "avg" ,
)
Available functions: sum, avg, max, min, count
Updating Meters
Update meter configuration:
updated_meter = client.meters.update(
id = meter.id,
name = "API Requests (Updated)" ,
filter = {
"conjunction" : "and" ,
"clauses" : [
{
"property" : "name" ,
"operator" : "eq" ,
"value" : "api.request" ,
},
{
"property" : "metadata.version" ,
"operator" : "eq" ,
"value" : "v2" ,
},
],
},
)
Updating a meter’s filter or aggregation affects future billing but does not retroactively change past invoices.
Archiving Meters
Archive meters that are no longer used:
client.meters.update(
id = meter.id,
is_archived = True ,
)
Archived meters:
Stop processing new events
Are not used for billing
Can be restored by setting is_archived=False
Historical data is preserved
Store additional information with meters:
meter = client.meters.create(
name = "API Requests" ,
filter = { ... },
aggregation = { ... },
metadata = {
"category" : "api" ,
"priority" : "high" ,
"docs_url" : "https://docs.example.com/api-billing" ,
},
)
Linking Meters to Products
To charge for metered usage, create a metered product price:
Create a meter
Define what to measure
Create a product
Create or use an existing product
Add a metered price
Link the meter to a product price with pricing tiers
Add to subscription
Include the product in subscriptions
See the Billing documentation for details on pricing configuration.
Best Practices
Naming
Be specific : “GPT-4 Tokens” instead of “Tokens”
Include units : “Storage (GB)” instead of “Storage”
Use customer language : Match your UI and documentation
Keep it concise : Names appear on invoices
Filter Design
Start simple : Begin with basic filters, add complexity as needed
Test thoroughly : Verify events match as expected
Document filters : Explain what each meter includes/excludes
Use consistent event names : Makes filtering easier
Aggregation Selection
Scenario Aggregation Notes API calls, requests countSimple event counting Tokens, bytes, time sumAccumulate totals Peak usage, capacity maxHighest value in period Active users, IPs uniqueDistinct values Average latency, size avgMean value
Avoid overly complex filters : Keep filter clauses reasonable
Use appropriate properties : Filter on indexed fields when possible
Consider cardinality : Be careful with unique aggregations on high-cardinality fields
Monitor query times : Test meters with realistic event volumes
Troubleshooting
Meter Shows Zero Usage
Check filter : Verify events match your filter clauses
Test with sample event : Send a test event and check if it matches
Verify aggregation property : Ensure the metadata field exists
Check time range : Query for the correct time period
Usage Doesn’t Match Expectations
Review filter logic : AND vs OR conjunctions
Check operator : Using eq vs like vs gt
Verify property names : Typos in metadata.field references
Inspect actual events : List events to see what’s being ingested
Aggregation Returns Unexpected Values
Null handling : Aggregations skip events missing the property
Type mismatches : Ensure metadata values are numeric for sum/max/min/avg
Summability : Non-summable aggregations (max/min/avg/unique) behave differently across price changes
Next Steps
Credits Add credit benefits to your products
Billing Configure pricing and generate invoices