Skip to main content
Cost tracking is in private beta. The API and configuration options may change before general availability.
Cost tracking lets you record the variable cost of serving each customer — AI inference, third-party API calls, cloud compute, data enrichment, or any other spend that varies with usage. Paygentic aggregates these cost events per customer and period, so you can compare total cost against revenue and understand your gross margin at the customer level. Unlike billable metrics (which measure what your customers consume for billing), cost metrics measure what you pay to serve them. Common use cases:
  • AI-powered products — Track LLM token costs, embedding calls, and agent compute per customer
  • Data enrichment platforms — Record the cost of third-party API calls (geocoding, credit checks, identity verification) attributed to each customer
  • Document processing — Attribute OCR, translation, or extraction pipeline costs to the customers who triggered them
  • Multi-tenant SaaS — Track cloud compute or storage spend that varies by customer workload

How It Works

  1. Create a cost metric that defines what you measure and how to aggregate it
  2. After each operation that incurs a cost, send a cost event to POST /v0/events with the dollar amount
  3. Paygentic aggregates cost events by customer and time period
  4. Query the aggregated costs via GET /v0/costs/{id}/summary to build margin dashboards or feed your own analytics

Before You Begin

  • Cost tracking enabled on your organization (Private Beta)
  • An API key with events:create and costs:write permissions
  • A product created in Paygentic (you need the productId)
  • Your customer IDs available at the time operations are performed

Step 1: Create a Cost Metric

A cost metric defines which events contribute to cost tracking, how to extract the amount, and how to slice costs by dimension. Every cost must be attached to a product via productId.
The POST /v0/costs endpoint is not generally available. To request access, contact your account team.
curl -X POST "https://api.paygentic.io/v0/costs" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "metered",
    "name": "AI Inference Cost",
    "eventType": "cost.ai.inference",
    "valueProperty": "$.amount",
    "aggregation": "SUM",
    "unitCost": 1,
    "currency": "USD",
    "productId": "prod_YOUR_PRODUCT_ID",
    "groupBy": {
      "provider": "$.provider",
      "operation": "$.operation"
    }
  }'
Response:
{
  "id": "cst_abc123",
  "object": "cost",
  "type": "metered",
  "name": "AI Inference Cost",
  "currency": "USD",
  "unitCost": "1",
  "unit": null,
  "aggregation": "SUM",
  "eventType": "cost.ai.inference",
  "valueProperty": "$.amount",
  "groupBy": {
    "provider": "$.provider",
    "operation": "$.operation"
  },
  "productId": "prod_YOUR_PRODUCT_ID",
  "merchantId": "org_YOUR_ORG_ID",
  "createdAt": "2026-02-01T10:00:00Z",
  "updatedAt": "2026-02-01T10:00:00Z",
  "deletedAt": null
}

Step 2: Send Cost Events

After each operation that incurs a cost, send an event with the dollar amount. Use the same POST /v0/events endpoint as meter events — the type field links the event to your cost metric.
curl -X POST "https://api.paygentic.io/v0/events" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "cost.ai.inference",
    "source": "https://api.myapp.com",
    "subject": "cus_abc123",
    "data": {
      "amount": 0.0042,
      "provider": "anthropic",
      "operation": "document-extraction"
    },
    "idempotencyKey": "job_xyz789-cost"
  }'
Send cost events after the operation completes, once you have the actual cost from your provider. The amount field should be the total cost in the currency configured on the cost metric.

Calculating the amount

The amount you record should reflect what you actually paid — not an estimate. Retrieve this from your provider’s response or invoice data:
// Example: LLM provider returns token counts, multiply by per-token rate
function computeInferenceCost(inputTokens, outputTokens, model) {
  const rates = {
    'claude-3-5-sonnet': { input: 0.000003, output: 0.000015 },
    'claude-3-haiku':    { input: 0.00000025, output: 0.00000125 },
  };
  const r = rates[model];
  return (inputTokens * r.input) + (outputTokens * r.output);
}

// Example: third-party API with a flat per-call rate
function computeApiCallCost(callCount, pricePerCall) {
  return callCount * pricePerCall;
}

Step 3: Query Cost Summary

Use GET /v0/costs/{id}/summary to retrieve aggregated cost data for a specific cost metric over a time window.
curl "https://api.paygentic.io/v0/costs/cst_abc123/summary?from=2026-02-01T00:00:00Z&to=2026-03-01T00:00:00Z&subject=cus_abc123" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response:
{
  "object": "cost",
  "id": "cst_abc123",
  "name": "AI Inference Cost",
  "type": "metered",
  "currency": "USD",
  "eventType": "cost.ai.inference",
  "totalCost": 42.50,
  "totalQuantity": 42500
}

Group by dimensions

Add groupBy to break down costs by the dimensions you configured on the cost metric:
curl "https://api.paygentic.io/v0/costs/cst_abc123/summary?from=2026-02-01T00:00:00Z&to=2026-03-01T00:00:00Z&groupBy=provider,operation" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response includes a groups array:
{
  "object": "cost",
  "id": "cst_abc123",
  "name": "AI Inference Cost",
  "type": "metered",
  "currency": "USD",
  "eventType": "cost.ai.inference",
  "totalCost": 42.50,
  "totalQuantity": 42500,
  "groups": [
    { "dimensions": { "provider": "anthropic", "operation": "extraction" }, "quantity": 30000, "cost": 30.00 },
    { "dimensions": { "provider": "anthropic", "operation": "summarization" }, "quantity": 12500, "cost": 12.50 }
  ]
}

Time-series breakdown

Add window to get a time-series breakdown:
curl "https://api.paygentic.io/v0/costs/cst_abc123/summary?from=2026-02-01T00:00:00Z&to=2026-02-03T00:00:00Z&window=DAY" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response includes a timeSeries array:
{
  "object": "cost",
  "id": "cst_abc123",
  "name": "AI Inference Cost",
  "type": "metered",
  "currency": "USD",
  "eventType": "cost.ai.inference",
  "totalCost": 15.00,
  "totalQuantity": 15000,
  "timeSeries": [
    { "windowStart": "2026-02-01T00:00:00Z", "windowEnd": "2026-02-02T00:00:00Z", "quantity": 8000, "cost": 8.00 },
    { "windowStart": "2026-02-02T00:00:00Z", "windowEnd": "2026-02-03T00:00:00Z", "quantity": 7000, "cost": 7.00 }
  ]
}

Filter by dimension values

Use filterGroupBy to narrow results to specific dimension values:
curl "https://api.paygentic.io/v0/costs/cst_abc123/summary?from=2026-02-01T00:00:00Z&to=2026-03-01T00:00:00Z&filterGroupBy=%7B%22provider%22%3A%22anthropic%22%7D" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
The filterGroupBy value is a JSON-encoded object. Keys must match those defined in the cost’s groupBy configuration.

Managing Costs

List costs

Retrieve all costs for your organization, optionally filtered by product:
curl "https://api.paygentic.io/v0/costs?productId=prod_YOUR_PRODUCT_ID&limit=20" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response:
{
  "object": "list",
  "data": [
    { "id": "cst_abc123", "object": "cost", "type": "metered", "name": "AI Inference Cost", "..." : "..." }
  ],
  "pagination": { "limit": 20, "offset": 0, "total": 1 }
}

Get a single cost

curl "https://api.paygentic.io/v0/costs/cst_abc123" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"

Update a cost

All fields are optional — only include the fields you want to change:
curl -X PATCH "https://api.paygentic.io/v0/costs/cst_abc123" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "LLM Inference Cost",
    "unitCost": 1.5
  }'

Delete a cost

Deleting a cost performs a soft delete. The cost is no longer returned in list queries, but historical data is preserved.
curl -X DELETE "https://api.paygentic.io/v0/costs/cst_abc123" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Returns 204 No Content on success.

Tracking Multiple Cost Dimensions

Create separate cost metrics for each distinct type of spend. Each metric has its own eventType and tracks independently. Example: separate metrics for different cost categories
# LLM inference
curl -X POST "https://api.paygentic.io/v0/costs" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "metered", "name": "LLM Inference", "eventType": "cost.ai.inference", "valueProperty": "$.amount", "aggregation": "SUM", "unitCost": 1, "currency": "USD", "productId": "prod_YOUR_PRODUCT_ID" }'

# Third-party data enrichment
curl -X POST "https://api.paygentic.io/v0/costs" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "metered", "name": "Data Enrichment", "eventType": "cost.enrichment", "valueProperty": "$.amount", "aggregation": "SUM", "unitCost": 1, "currency": "USD", "productId": "prod_YOUR_PRODUCT_ID" }'

# Cloud compute
curl -X POST "https://api.paygentic.io/v0/costs" \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "type": "metered", "name": "Compute", "eventType": "cost.compute", "valueProperty": "$.amount", "aggregation": "SUM", "unitCost": 1, "currency": "USD", "productId": "prod_YOUR_PRODUCT_ID" }'
Events are sent with the matching type:
{ "type": "cost.ai.inference", "subject": "cus_abc123", "data": { "amount": 0.0042, "operation": "extraction" } }
{ "type": "cost.enrichment",   "subject": "cus_abc123", "data": { "amount": 0.0010, "provider": "clearbit" } }
{ "type": "cost.compute",      "subject": "cus_abc123", "data": { "amount": 0.0023, "region": "us-east-1" } }
Multiple cost metrics can read from the same eventType. This lets you extract and aggregate different fields from the same event — for example, tracking GPU cost and CPU cost separately from a single compute event.A single event can also be processed by both a billable metric and a cost metric simultaneously. For example, an ai.inference event can drive customer billing via a billable metric while the same event feeds your cost tracking — no need to send separate events.

API Reference

Create cost request

FieldTypeRequiredDescription
typestringYesMust be "metered".
namestringYesDisplay name for this cost metric.
eventTypestringYesCloudEvents type that identifies the metered event. Convention: use a cost. prefix (e.g. cost.ai.inference).
aggregationenumYesHow to combine values: SUM, COUNT, AVG, MIN, MAX, UNIQUE_COUNT, or LATEST.
unitCostnumberYesCost per unit of measured quantity. Must be non-negative (0–1,000,000).
currencystringYesISO 4217 currency code (e.g. "USD"). Must be enabled on your organization.
productIdstringYesThe product this cost belongs to.
valuePropertystringConditionalJSONPath to extract the numeric value from event data (e.g. $.amount). Required when aggregation is SUM, AVG, MIN, MAX, or LATEST.
unitstringNoLabel for the metered unit (e.g. "token", "request").
groupByobjectNoMap of dimension names to JSONPath expressions. Allows slicing total cost by provider, operation, region, etc.

Cost response object

FieldTypeDescription
idstringUnique identifier with cst_ prefix.
objectstringAlways "cost".
typestringAlways "metered".
namestringDisplay name.
currencystringISO 4217 currency code.
unitCoststringDecimal as string to avoid floating-point precision loss.
unitstring | nullUnit label, if set.
aggregationenum | nullSUM, COUNT, AVG, MIN, MAX, UNIQUE_COUNT, or LATEST.
eventTypestring | nullCloudEvents type for metered costs.
valuePropertystring | nullJSONPath for value extraction.
groupByobject | nullDimension-to-JSONPath map.
productIdstringProduct this cost belongs to.
merchantIdstringOrganization that owns this cost.
createdAtstringISO 8601 creation timestamp.
updatedAtstringISO 8601 last-update timestamp.
deletedAtstring | nullSoft-delete timestamp. Always null for active costs.

Cost summary query parameters

ParameterTypeRequiredDescription
fromstringYesStart of query window (ISO 8601).
tostringYesEnd of query window (ISO 8601).
subjectstringNoFilter to a specific customer. When omitted, aggregates across all subjects.
groupBystringNoComma-separated dimension keys to group results by.
windowenumNoTime-series granularity: MINUTE, HOUR, or DAY.
filterGroupBystringNoJSON-encoded dimension filter (e.g. {"provider":"anthropic"}). Keys must match the cost’s groupBy configuration.

Cost summary response

FieldTypeDescription
objectstringAlways "cost".
idstringCost ID.
namestringCost name.
typestringAlways "metered".
currencystringISO 4217 currency code.
eventTypestringCloudEvents type.
totalCostnumber | nullTotal cost for the query window. null if usage data could not be computed.
totalQuantitynumber | nullTotal usage quantity. null if usage was not computed.
groupsarrayPer-group breakdown. Present when groupBy is specified. Each entry has dimensions, quantity, and cost.
timeSeriesarrayTime-series breakdown. Present when window is specified. Each entry has windowStart, windowEnd, quantity, and cost.

Cost Event data Fields

The data payload is arbitrary JSON. Only amount (or whichever path valueProperty points to) is required — all other fields are optional context that improves filtering and dimension slicing.
FieldDescription
amountCost in the metric’s currency. Required — this is the value extracted by valueProperty.
providerThe vendor or service that incurred the cost (e.g. "anthropic", "aws", "clearbit").
operationThe type of operation performed (e.g. "document-extraction", "geocoding", "ocr").
regionThe compute or data region, if relevant for cost attribution.
Any other fields you include in data can be referenced in groupBy on your cost metric.

Idempotency

Use idempotencyKey to avoid double-counting costs when retrying failed requests:
// Generate a deterministic key from the operation that incurred the cost
const idempotencyKey = `${jobId}-${eventType}`;
  • If two events share the same idempotencyKey, only the first is recorded
  • The deduplication window is 24 hours
  • Omitting idempotencyKey means retries will create duplicate cost entries

Best Practices

Send cost events after the operation completes. You need the actual cost from your provider — don’t estimate upfront and correct later. Use idempotencyKey on every event. Derive it from a stable operation identifier (job ID, request ID) to make retries safe. One cost event per billable operation, not per customer request. A single customer request may trigger multiple downstream calls — track each one individually for accurate attribution. Separate metrics for different cost categories. Inference, enrichment, and compute have different unit economics. Tracking them independently makes it easier to understand where margin pressure is coming from.

Next Steps