Skip to main content
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:
  1. Name: Human-readable label (shown on invoices)
  2. Filter: Which events to include
  3. 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

OperatorDescriptionExample
eqEquals"model" eq "gpt-4"
neNot equals"status" ne "error"
gtGreater than"tokens" gt 1000
gteGreater than or equal"duration" gte 60
ltLess than"size" lt 1000000
lteLess than or equal"price" lte 100
likeContains (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:
{
  "func": "count"
}
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

{
  "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"
  }
}

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

Meter Metadata

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

Create a meter

Define what to measure
2

Create a product

Create or use an existing product
3

Add a metered price

Link the meter to a product price with pricing tiers
4

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

ScenarioAggregationNotes
API calls, requestscountSimple event counting
Tokens, bytes, timesumAccumulate totals
Peak usage, capacitymaxHighest value in period
Active users, IPsuniqueDistinct values
Average latency, sizeavgMean value

Performance

  • 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

  1. Check filter: Verify events match your filter clauses
  2. Test with sample event: Send a test event and check if it matches
  3. Verify aggregation property: Ensure the metadata field exists
  4. Check time range: Query for the correct time period

Usage Doesn’t Match Expectations

  1. Review filter logic: AND vs OR conjunctions
  2. Check operator: Using eq vs like vs gt
  3. Verify property names: Typos in metadata.field references
  4. Inspect actual events: List events to see what’s being ingested

Aggregation Returns Unexpected Values

  1. Null handling: Aggregations skip events missing the property
  2. Type mismatches: Ensure metadata values are numeric for sum/max/min/avg
  3. 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

Build docs developers (and LLMs) love