ConsumIQ Documentation
ConsumIQ is a flexible billing platform that lets you meter product consumption and generate charges based on configurable pricing — tiered, dimensional, flat, and price-per-unit. Build once, price however your customers use your product.
What can you build?
ConsumIQ gives you the primitives to implement any billing model — from simple per-seat fees to complex multi-dimensional usage pricing.
Usage-based billing
Charge customers based on API calls, storage, compute hours, or any measurable metric
Tiered pricing
Volume and graduated tiers so pricing scales automatically as usage grows
Dimensional pricing
Price the same metric differently by region, tier, or any custom dimension
Recurring subscriptions
Combine flat fees and usage charges with flexible billing schedules
Free tiers & discounts
Allowances deducted before billing, plus flexible discount lifecycles
Proration
Day- or second-precise proration for mid-period changes and cancellations
How it works
ConsumIQ models your billing in five layers, from product definition down to charge generation. You define the structure once in the catalogue, then send usage events as your customers consume your product.
Define a Product — create the product you sell (e.g. "Data Processing API")
Create a Product Offering — package your product into tiers (e.g. Starter, Growth, Enterprise) each with its own pricing
Set up a Rate Card — attach a Rate Card to the offering and define Rates for each chargeable element with your chosen pricing model
Configure Billable Metrics — define how usage events map to your charge elements and which aggregation function applies (SUM, MAX, COUNT, etc.)
Send Usage Events — as customers use your product, POST events to the metering API with a unique eventId and quantity
Calculate Charges — call the billing API to aggregate events, apply allowances, and compute charges using your configured pricing model
Quickstart
Build a complete metered billing pipeline end-to-end. You'll configure a product, subscribe a customer, meter their usage, and calculate their first invoice.
Replace https://api.consumiq.io with your deployment's base URL. All endpoints are also explorable at /swagger-ui.html on your server. Java examples require Java 11+ and Jackson (jackson-databind).
Scenario
You're building "CloudProcess" — a document-processing API. Your pricing model:
- $49/month platform fee
- 10,000 free API calls per month (included in every plan)
- $0.05/call for the first 1,000 billable calls (after the free tier)
- $0.03/call for calls 1,001–10,000
- $0.01/call above 10,000
Step 1 — Create a Product
A Product is the top-level entity representing what you sell.
curl -X POST https://api.consumiq.io/api/v1/products \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CIQ_API_KEY" \ -d '{ "productName": "CloudProcess API", "productCode": "CLOUD-PROC", "description": "Metered document processing API", "status": "ACTIVE" }' # Response 200 OK { "id": "prod_7x9a2b", "productName": "CloudProcess API", "status": "ACTIVE" }
const BASE = 'https://api.consumiq.io'; const headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.CIQ_API_KEY}` }; const product = await fetch(`${BASE}/api/v1/products`, { method: 'POST', headers, body: JSON.stringify({ productName: 'CloudProcess API', productCode: 'CLOUD-PROC', status: 'ACTIVE' }) }).then(r => r.json()); const productId = product.id; // "prod_7x9a2b"
import requests, os BASE = 'https://api.consumiq.io' headers = {'Authorization': f'Bearer {os.environ["CIQ_API_KEY"]}'} product = requests.post(f'{BASE}/api/v1/products', headers=headers, json={ 'productName': 'CloudProcess API', 'productCode': 'CLOUD-PROC', 'status': 'ACTIVE' }).json() product_id = product['id'] # "prod_7x9a2b"
// One-time setup (inject as beans in Spring Boot) HttpClient client = HttpClient.newHttpClient(); ObjectMapper mapper = new ObjectMapper(); String BASE = "https://api.consumiq.io"; String apiKey = System.getenv("CIQ_API_KEY"); String body = mapper.writeValueAsString(Map.of( "productName", "CloudProcess API", "productCode", "CLOUD-PROC", "status", "ACTIVE" )); HttpRequest req = HttpRequest.newBuilder() .uri(URI.create(BASE + "/api/v1/products")) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + apiKey) .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); Map<?,?> product = mapper.readValue( client.send(req, HttpResponse.BodyHandlers.ofString()).body(), Map.class); String productId = (String) product.get("id"); // "prod_7x9a2b"
Step 2 — Create a Product Offering
A Product Offering is a specific plan tier of your product (e.g. "Starter Plan").
curl -X POST https://api.consumiq.io/api/v1/product-offerings \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CIQ_API_KEY" \ -d '{ "name": "Starter Plan", "productId": "prod_7x9a2b", "status": "ACTIVE" }' # Response: { "id": "off_3k8f9g", "name": "Starter Plan", ... }
const offering = await fetch(`${BASE}/api/v1/product-offerings`, { method: 'POST', headers, body: JSON.stringify({ name: 'Starter Plan', productId: productId, status: 'ACTIVE' }) }).then(r => r.json()); const offeringId = offering.id;
offering = requests.post(f'{BASE}/api/v1/product-offerings', headers=headers, json={'name': 'Starter Plan', 'productId': product_id, 'status': 'ACTIVE'}).json() offering_id = offering['id']
Map<?,?> offering = post("/api/v1/product-offerings", Map.of( "name", "Starter Plan", "productId", productId, "status", "ACTIVE" )); String offeringId = (String) offering.get("id");
Step 3 — Define a Usage Element
A Usage Element declares the billable metric your customers generate — in this case, API calls.
curl -X POST https://api.consumiq.io/api/v1/usage-elements \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CIQ_API_KEY" \ -d '{ "usageKey": "api.calls", "usageUnit": "calls", "usageType": "UNLIMITED", "displayName": "API Calls", "productId": "prod_7x9a2b" }'
await fetch(`${BASE}/api/v1/usage-elements`, { method: 'POST', headers, body: JSON.stringify({ usageKey: 'api.calls', usageUnit: 'calls', usageType: 'UNLIMITED', productId: productId }) });
requests.post(f'{BASE}/api/v1/usage-elements', headers=headers, json={ 'usageKey': 'api.calls', 'usageUnit': 'calls', 'usageType': 'UNLIMITED', 'productId': product_id })
post("/api/v1/usage-elements", Map.of( "usageKey", "api.calls", "usageUnit", "calls", "usageType", "UNLIMITED", "displayName", "API Calls", "productId", productId ));
Step 4 — Create a Rate Card
curl -X POST https://api.consumiq.io/api/v1/rate-cards \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CIQ_API_KEY" \ -d '{ "name": "Monthly Standard", "productOfferingId": "off_3k8f9g", "status": "ACTIVE" }' # Response: { "id": "rc_4m7n9p", ... }
const rateCard = await fetch(`${BASE}/api/v1/rate-cards`, { method: 'POST', headers, body: JSON.stringify({ name: 'Monthly Standard', productOfferingId: offeringId, status: 'ACTIVE' }) }).then(r => r.json()); const rateCardId = rateCard.id;
rate_card = requests.post(f'{BASE}/api/v1/rate-cards', headers=headers, json={'name': 'Monthly Standard', 'productOfferingId': offering_id, 'status': 'ACTIVE'}).json() rate_card_id = rate_card['id']
Map<?,?> rateCard = post("/api/v1/rate-cards", Map.of( "name", "Monthly Standard", "productOfferingId", offeringId, "status", "ACTIVE" )); String rateCardId = (String) rateCard.get("id");
Step 5 — Add a Platform Fee Rate
Create a flat $49/month fee charged regardless of usage.
curl -X POST https://api.consumiq.io/api/v1/rates \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CIQ_API_KEY" \ -d '{ "chargeElement": "platform-fee", "priceType": "FLAT", "rateType": "RECURRING", "chargeType": "RECURRING", "currency": "USD", "flatCharge": 49.00, "rateCardId": "rc_4m7n9p" }'
const feeRate = await fetch(`${BASE}/api/v1/rates`, { method: 'POST', headers, body: JSON.stringify({ chargeElement: 'platform-fee', priceType: 'FLAT', rateType: 'RECURRING', chargeType: 'RECURRING', currency: 'USD', flatCharge: 49.00, rateCardId: rateCardId }) }).then(r => r.json()); const feeRateId = feeRate.id;
fee_rate = requests.post(f'{BASE}/api/v1/rates', headers=headers, json={ 'chargeElement': 'platform-fee', 'priceType': 'FLAT', 'rateType': 'RECURRING', 'chargeType': 'RECURRING', 'currency': 'USD', 'flatCharge': 49.00, 'rateCardId': rate_card_id }).json() fee_rate_id = fee_rate['id']
Map<?,?> feeRate = post("/api/v1/rates", Map.of( "chargeElement", "platform-fee", "priceType", "FLAT", "rateType", "RECURRING", "chargeType", "RECURRING", "currency", "USD", "flatCharge", 49.00, "rateCardId", rateCardId )); String feeRateId = (String) feeRate.get("id");
Step 6 — Add a Tiered API Call Rate
Create a graduated tiered rate for API calls. Set ceiling to null for the last tier.
curl -X POST https://api.consumiq.io/api/v1/rates \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CIQ_API_KEY" \ -d '{ "chargeElement": "api-calls", "priceType": "TIERED", "rateType": "RECURRING", "chargeType": "RECURRING", "currency": "USD", "rateCardId": "rc_4m7n9p", "tiers": [ { "tierOrder": 1, "floor": 0, "ceiling": 1000, "pricePerUnit": 0.05 }, { "tierOrder": 2, "floor": 1000, "ceiling": 10000, "pricePerUnit": 0.03 }, { "tierOrder": 3, "floor": 10000, "ceiling": null, "pricePerUnit": 0.01 } ] }'
const callRate = await fetch(`${BASE}/api/v1/rates`, { method: 'POST', headers, body: JSON.stringify({ chargeElement: 'api-calls', priceType: 'TIERED', rateType: 'RECURRING', chargeType: 'RECURRING', currency: 'USD', rateCardId: rateCardId, tiers: [ { tierOrder: 1, floor: 0, ceiling: 1000, pricePerUnit: 0.05 }, { tierOrder: 2, floor: 1000, ceiling: 10000, pricePerUnit: 0.03 }, { tierOrder: 3, floor: 10000, ceiling: null, pricePerUnit: 0.01 } ] }) }).then(r => r.json()); const callRateId = callRate.id;
call_rate = requests.post(f'{BASE}/api/v1/rates', headers=headers, json={ 'chargeElement': 'api-calls', 'priceType': 'TIERED', 'rateType': 'RECURRING', 'chargeType': 'RECURRING', 'currency': 'USD', 'rateCardId': rate_card_id, 'tiers': [ {'tierOrder': 1, 'floor': 0, 'ceiling': 1000, 'pricePerUnit': 0.05}, {'tierOrder': 2, 'floor': 1000, 'ceiling': 10000, 'pricePerUnit': 0.03}, {'tierOrder': 3, 'floor': 10000, 'ceiling': None, 'pricePerUnit': 0.01}, ] }).json() call_rate_id = call_rate['id']
Map<?,?> callRate = post("/api/v1/rates", Map.of( "chargeElement", "api-calls", "priceType", "TIERED", "rateType", "RECURRING", "chargeType", "RECURRING", "currency", "USD", "rateCardId", rateCardId, "tiers", List.of( Map.of("tierOrder", 1, "floor", 0, "ceiling", 1000, "pricePerUnit", 0.05), Map.of("tierOrder", 2, "floor", 1000, "ceiling", 10000, "pricePerUnit", 0.03), Map.of("tierOrder", 3, "floor", 10000, "pricePerUnit", 0.01) // null ceiling omitted ) )); String callRateId = (String) callRate.get("id");
Step 7 — Create a Billable Metric
Link api.calls events to the api-calls charge element using SUM aggregation.
curl -X POST https://api.consumiq.io/api/v1/billable-metrics \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CIQ_API_KEY" \ -d '{ "usageKey": "api.calls", "chargeElement": "api-calls", "aggregationType": "SUM", "rateId": "rate_calls_001", "productOfferingId": "off_3k8f9g" }'
await fetch(`${BASE}/api/v1/billable-metrics`, { method: 'POST', headers, body: JSON.stringify({ usageKey: 'api.calls', chargeElement: 'api-calls', aggregationType: 'SUM', rateId: callRateId, productOfferingId: offeringId }) });
requests.post(f'{BASE}/api/v1/billable-metrics', headers=headers, json={ 'usageKey': 'api.calls', 'chargeElement': 'api-calls', 'aggregationType': 'SUM', 'rateId': call_rate_id, 'productOfferingId': offering_id })
post("/api/v1/billable-metrics", Map.of( "usageKey", "api.calls", "chargeElement", "api-calls", "aggregationType", "SUM", "rateId", callRateId, "productOfferingId", offeringId ));
Step 8 — Configure Free Allowance
Create an allowance group with 10,000 free calls. ConsumIQ deducts this automatically before billing.
# 1. Create allowance group curl -X POST https://api.consumiq.io/api/v1/allowance-groups \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CIQ_API_KEY" \ -d '{ "name": "Starter Free Tier", "productOfferingId": "off_3k8f9g" }' # 2. Add 10k allowance (use the returned allowanceGroupId) curl -X POST https://api.consumiq.io/api/v1/allowances \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CIQ_API_KEY" \ -d '{ "usageKey": "api.calls", "allowanceAmount": 10000, "allowanceGroupId": "<group-id>" }'
const group = await fetch(`${BASE}/api/v1/allowance-groups`, { method: 'POST', headers, body: JSON.stringify({ name: 'Starter Free Tier', productOfferingId: offeringId }) }).then(r => r.json()); await fetch(`${BASE}/api/v1/allowances`, { method: 'POST', headers, body: JSON.stringify({ usageKey: 'api.calls', allowanceAmount: 10000, allowanceGroupId: group.id }) });
group = requests.post(f'{BASE}/api/v1/allowance-groups', headers=headers, json={'name': 'Starter Free Tier', 'productOfferingId': offering_id}).json() requests.post(f'{BASE}/api/v1/allowances', headers=headers, json={ 'usageKey': 'api.calls', 'allowanceAmount': 10000, 'allowanceGroupId': group['id'] })
Map<?,?> group = post("/api/v1/allowance-groups", Map.of( "name", "Starter Free Tier", "productOfferingId", offeringId )); post("/api/v1/allowances", Map.of( "usageKey", "api.calls", "allowanceAmount", 10000, "allowanceGroupId", group.get("id") ));
Step 9 — Subscribe a Customer
Create a subscription linking the customer to the offering and rate card. The billingItems array specifies which rates to bill.
curl -X POST https://api.consumiq.io/api/v1/subscriptions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CIQ_API_KEY" \ -d '{ "name": "Acme Corp — Starter", "customerId": "cust_acme_001", "productOfferingId": "off_3k8f9g", "billing": { "rateCardId": "rc_4m7n9p", "billingItems": [ { "rateId": "rate_fee_001" }, { "rateId": "rate_calls_001" } ] }, "billingCycleAnchor": { "anchorDay": 1 } }' # Response 201 Created: { "id": "sub_acme_001", "status": "ACTIVE", ... }
const sub = await fetch(`${BASE}/api/v1/subscriptions`, { method: 'POST', headers, body: JSON.stringify({ name: 'Acme Corp — Starter', customerId: 'cust_acme_001', productOfferingId: offeringId, billing: { rateCardId: rateCardId, billingItems: [{ rateId: feeRateId }, { rateId: callRateId }] }, billingCycleAnchor: { anchorDay: 1 } }) }).then(r => r.json()); // sub.status === "ACTIVE"
sub = requests.post(f'{BASE}/api/v1/subscriptions', headers=headers, json={ 'name': 'Acme Corp — Starter', 'customerId': 'cust_acme_001', 'productOfferingId': offering_id, 'billing': { 'rateCardId': rate_card_id, 'billingItems': [{'rateId': fee_rate_id}, {'rateId': call_rate_id}] }, 'billingCycleAnchor': {'anchorDay': 1} }).json()
Map<?,?> sub = post("/api/v1/subscriptions", Map.of( "name", "Acme Corp — Starter", "customerId", "cust_acme_001", "productOfferingId", offeringId, "billing", Map.of( "rateCardId", rateCardId, "billingItems", List.of( Map.of("rateId", feeRateId), Map.of("rateId", callRateId) ) ), "billingCycleAnchor", Map.of("anchorDay", 1) )); // sub.get("status") == "ACTIVE"
Step 10 — Send Usage Events
Send events from your application as customers consume your product. Always include a unique eventId for idempotency.
curl -X POST https://api.consumiq.io/api/v1/usage-events \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CIQ_API_KEY" \ -d '{ "eventId": "evt_acme_20240701_001", "usageKey": "api.calls", "usageIdentifier": "cust_acme_001", "value": 1, "usageTimestamp": 1719820800000 }' # Response 202 Accepted
async function trackApiCall(customerId, eventId) { await fetch(`${BASE}/api/v1/usage-events`, { method: 'POST', headers, body: JSON.stringify({ eventId, // unique — safe to retry usageKey: 'api.calls', usageIdentifier: customerId, value: 1, usageTimestamp: Date.now() }) }); // 202 Accepted — do not await billing }
import time def track_api_call(customer_id: str, event_id: str): requests.post(f'{BASE}/api/v1/usage-events', headers=headers, json={ 'eventId': event_id, # unique — safe to retry 'usageKey': 'api.calls', 'usageIdentifier': customer_id, 'value': 1, 'usageTimestamp': int(time.time() * 1000) })
void trackApiCall(String customerId, String eventId) throws Exception { String body = mapper.writeValueAsString(Map.of( "eventId", eventId, // unique — safe to retry "usageKey", "api.calls", "usageIdentifier", customerId, "value", 1, "usageTimestamp", System.currentTimeMillis() )); HttpRequest req = HttpRequest.newBuilder() .uri(URI.create(BASE + "/api/v1/usage-events")) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + apiKey) .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); client.send(req, HttpResponse.BodyHandlers.ofString()); // 202 Accepted }
In the Java examples above, post() is a helper method using java.net.http.HttpClient + ObjectMapper. See the Java helper pattern below.
Java helper pattern
Declare this once in your service class to keep API calls concise throughout:
import java.net.URI; import java.net.http.*; import java.util.*; import com.fasterxml.jackson.databind.ObjectMapper; public class ConsumIQClient { private final HttpClient client = HttpClient.newHttpClient(); private final ObjectMapper mapper = new ObjectMapper(); private final String base = "https://api.consumiq.io"; private final String apiKey = System.getenv("CIQ_API_KEY"); private Map<?,?> post(String path, Object body) throws Exception { HttpRequest req = HttpRequest.newBuilder() .uri(URI.create(base + path)) .header("Content-Type", "application/json") .header("Authorization", "Bearer " + apiKey) .POST(HttpRequest.BodyPublishers.ofString( mapper.writeValueAsString(body))) .build(); return mapper.readValue( client.send(req, HttpResponse.BodyHandlers.ofString()).body(), Map.class); } }
Errors
ConsumIQ uses standard HTTP status codes. Errors return a JSON body with the status code, a human-readable message, and the request path.
Error response format
{
"status": 400,
"error": "Bad Request",
"message": "productCode: must not be blank",
"path": "/api/v1/products",
"timestamp": "2024-07-01T10:30:00.000Z"
}
HTTP status codes
| Code | Status | When it occurs |
|---|---|---|
| 200 | OK | Request succeeded (GET, PUT, PATCH) |
| 201 | Created | Resource created (POST for subscriptions, accounts, persons) |
| 202 | Accepted | Usage event queued for async processing — do not wait for billing confirmation |
| 204 | No Content | Resource deleted |
| 400 | Bad Request | Validation failure — missing required fields or invalid values. Check the message field for the specific constraint. |
| 404 | Not Found | Resource ID does not exist |
| 409 | Conflict | Duplicate resource — e.g., duplicate productCode |
| 422 | Unprocessable Entity | Violates a business rule — e.g., referencing an INACTIVE rate card |
| 500 | Internal Server Error | Unexpected server error — safe to retry with exponential backoff |
Common validation messages
| Message | Cause | Fix |
|---|---|---|
| must not be blank | Required string is missing or empty | Check required fields marked required |
| must not be null | Required field absent from body | Include the field explicitly |
| must not be empty | Required array (e.g. billingItems) was [] | Include at least one item |
| not found | Referenced ID does not exist | Verify the ID was returned from a prior create call |
| size must be between 1 and 500 | Batch event count exceeds 500 | Split into batches of ≤500 |
Products
A Product is the top-level entity representing what you sell. All pricing configuration (offerings, rate cards, rates) is attached to a product.
| Field | Type | Description |
|---|---|---|
| productNamerequired | string | Display name, e.g. "Data Processing API" |
| productCode | string | Unique machine-readable code, e.g. "DATA-API" |
| description | string | Optional description |
| status | ACTIVE | INACTIVE | ARCHIVED | Only ACTIVE products can accept subscriptions |
# Create POST /api/v1/products { "productName": "Data Processing API", "productCode": "DATA-API", "status": "ACTIVE" } # List / Get / Update / Delete GET /api/v1/products GET /api/v1/products/{id} PUT /api/v1/products/{id} DELETE /api/v1/products/{id}
// Create const product = await fetch(`${BASE}/api/v1/products`, { method: 'POST', headers, body: JSON.stringify({ productName: 'Data Processing API', productCode: 'DATA-API', status: 'ACTIVE' }) }).then(r => r.json()); // List const all = await fetch(`${BASE}/api/v1/products`, { headers }).then(r => r.json());
# Create product = requests.post(f'{BASE}/api/v1/products', headers=headers, json={'productName': 'Data Processing API', 'productCode': 'DATA-API', 'status': 'ACTIVE'}).json() # List products = requests.get(f'{BASE}/api/v1/products', headers=headers).json()
// Create Map<?,?> product = post("/api/v1/products", Map.of( "productName", "Data Processing API", "productCode", "DATA-API", "status", "ACTIVE" )); // List — GET with no body HttpRequest listReq = HttpRequest.newBuilder() .uri(URI.create(BASE + "/api/v1/products")) .header("Authorization", "Bearer " + apiKey) .GET().build(); String listJson = client.send(listReq, HttpResponse.BodyHandlers.ofString()).body();
Product Offerings
A Product Offering packages a product into a specific plan or tier — for example, "Starter" and "Enterprise" plans of the same API. Each offering has its own rate cards, billable metrics, and optional free-usage allowances.
| Field | Type | Description |
|---|---|---|
| namerequired | string | e.g. "Starter Plan" |
| productIdrequired | UUID | Parent product |
| status | ACTIVE | INACTIVE | ARCHIVED | INACTIVE offerings cannot be subscribed to |
The GET response for a product offering includes its full rate cards, allowance group, and billable metrics — you can fetch the complete pricing config for an offering in a single call.
Query offerings for a specific product with GET /api/v1/product-offerings?productId={id}.
Usage Elements
A Usage Element defines a trackable unit of consumption for a product. It establishes the usageKey that appears in usage events, and describes the unit type.
| Field | Type | Description |
|---|---|---|
| usageKeyrequired | string | Unique identifier used in events, e.g. api.calls |
| usageUnitrequired | string | Human-readable unit name, e.g. "requests", "GB" |
| usageTyperequired | UNLIMITED | RATE_LIMITED | THROTTLED | Metering behaviour |
| primaryDimension | string | Dimension key for multi-dimensional pricing |
| productIdrequired | UUID | Parent product |
Billable Metrics
A Billable Metric bridges metering and pricing. It defines how raw usage events for a usageKey are aggregated and mapped onto a charge element in the rate card.
| Field | Type | Description |
|---|---|---|
| usageKeyrequired | string | Must match the Usage Element key |
| chargeElementrequired | string | Must match the Rate's chargeElement |
| aggregationTyperequired | SUM | MAX | MIN | COUNT | AVERAGE | UNIQUE | How to aggregate events before pricing |
| rateIdrequired | UUID | The Rate this metric feeds into |
| productOfferingId | UUID | Parent product offering |
Rate Cards
A Rate Card groups one or more Rates under a Product Offering. When a customer subscribes, they reference a specific rate card whose terms are then snapshotted.
| Field | Type | Description |
|---|---|---|
| namerequired | string | e.g. "Monthly Standard", "Annual Discounted" |
| status | ACTIVE | INACTIVE | ARCHIVED | Only ACTIVE rate cards can be used by new subscriptions |
| productOfferingIdrequired | UUID | Parent product offering |
Rate card terms are snapshotted at subscription time. Changing a rate card's rates after a customer subscribes does not affect their existing subscription — use a Subscription Schedule to apply new pricing at a future date.
Rates & Pricing Models
A Rate defines how a specific chargeElement is priced within a Rate Card.
Price Per Unit
Fixed price × aggregated usage. Best for linear cost scaling.
Flat Fee
Fixed fee regardless of usage. Useful for seat licenses or minimum commitments.
Volume Tiered
All usage priced at the rate of the tier the total falls into.
Graduated Tiered
Usage split across tiers — each portion priced at its tier's rate.
Dimensional
Price varies by dimension value (region, storage class). Defined as a matrix.
Time-Based
Pricing changes by time of day for off-peak discounts or peak surcharges.
Rate fields
| Field | Type | Description |
|---|---|---|
| chargeElementrequired | string | Must match a Billable Metric's chargeElement |
| priceTyperequired | FLAT | PER_UNIT | TIERED | Selects the pricing calculator |
| rateTyperequired | STANDARD | RECURRING | TIERED | DIMENSIONAL | TIME_BASED | Rate behaviour type |
| chargeTyperequired | ONE_TIME | RECURRING | ONE_TIME: charged once. RECURRING: charged each billing period. |
| currencyrequired | string | ISO 4217 code, e.g. "USD", "EUR", "GBP" |
| pricePerUnit | decimal | Unit price for PUP pricing |
| flatCharge | decimal | Fixed amount for FLAT pricing |
| tiers | Tier[] | Ordered tier breakpoints for TIERED pricing |
| dimensionalPrices | DimensionalPrice[] | Dimension matrix for DIMENSIONAL pricing |
| rateCardIdrequired | UUID | Parent rate card |
Allowances
An Allowance gives customers a free-usage entitlement before billing starts. The billing engine deducts allowances from aggregated usage automatically.
Container
Groups all allowances for a product offering. One offering can have one allowance group.
Global allowance
A flat quantity subtracted from total usage for a usageKey. Supports optional resetCycle (DAILY, WEEKLY, MONTHLY, YEARLY, NONE).
Per-dimension allowance
Different free-usage amounts per dimension value.
POST /api/v1/allowances { "usageKey": "api.calls", "allowanceAmount": 10000, "allowanceGroupId": "<group-id>", "resetCycle": { "resetInterval": "MONTHLY", "resetCycleType": "RECURRING" } }
await fetch(`${BASE}/api/v1/allowances`, { method: 'POST', headers, body: JSON.stringify({ usageKey: 'api.calls', allowanceAmount: 10000, allowanceGroupId: groupId, resetCycle: { resetInterval: 'MONTHLY', resetCycleType: 'RECURRING' } }) });
requests.post(f'{BASE}/api/v1/allowances', headers=headers, json={ 'usageKey': 'api.calls', 'allowanceAmount': 10000, 'allowanceGroupId': group_id, 'resetCycle': {'resetInterval': 'MONTHLY', 'resetCycleType': 'RECURRING'} })
post("/api/v1/allowances", Map.of( "usageKey", "api.calls", "allowanceAmount", 10000, "allowanceGroupId", groupId, "resetCycle", Map.of( "resetInterval", "MONTHLY", "resetCycleType", "RECURRING" ) ));
Discounts & Discount Codes
The catalogue supports reusable Discounts — percentage or fixed-amount reductions applied over a configurable duration. Discount Codes are short redemption tokens customers use to apply a discount to their subscription.
| Field | Type | Description |
|---|---|---|
| namerequired | string | Internal name, e.g. "Q3 Launch Promo" |
| discountTyperequired | PERCENTAGE | FIXED_AMOUNT | Percentage off or a fixed currency reduction |
| discountMethodrequired | PERCENTAGE_OFF | FIXED_AMOUNT_OFF | TIERED_DISCOUNT | SEASONAL_DISCOUNT | How the discount is calculated |
| valuerequired | decimal | e.g. 20 for 20% off, or 10.00 for $10 off |
| discountDurationrequired | PERMANENT | RECURRING | LIMITED_TIME | How long the discount applies |
| redemptionCount | integer | Max redemptions allowed |
Catalogue discounts become active on a subscription via Discount Instances. The discount terms are snapshotted at activation — future changes to the catalogue discount do not affect existing subscribers.
Price Per Unit (PUP)
Every unit consumed is multiplied by a fixed pricePerUnit. Ideal for APIs, tokens, messages, or any metric where cost scales linearly.
{
"chargeElement": "api-calls",
"priceType": "PER_UNIT", "rateType": "RECURRING",
"chargeType": "RECURRING", "currency": "USD",
"pricePerUnit": 0.005, "rateCardId": "<rate-card-id>"
}
Flat Fee
A fixed charge regardless of usage. Use it for base subscription fees, seat licenses, or platform access charges.
{
"chargeElement": "platform-fee",
"priceType": "FLAT", "rateType": "RECURRING",
"chargeType": "RECURRING", "currency": "USD",
"flatCharge": 49.00, "rateCardId": "<rate-card-id>"
}
Tiered Pricing
Tiered pricing rewards high-usage customers with lower unit rates. ConsumIQ supports Volume (entire usage priced at one tier) and Graduated (usage split across tiers) variants.
{
"chargeElement": "compute-units",
"priceType": "TIERED", "rateType": "RECURRING",
"chargeType": "RECURRING", "currency": "USD",
"tiers": [
{ "tierOrder": 1, "floor": 0, "ceiling": 1000, "pricePerUnit": 0.05 },
{ "tierOrder": 2, "floor": 1000, "ceiling": 5000, "pricePerUnit": 0.03 },
{ "tierOrder": 3, "floor": 5000, "ceiling": null, "pricePerUnit": 0.01 }
],
"rateCardId": "<rate-card-id>"
}
Set ceiling: null on the last tier to indicate "unlimited" — all usage above the floor is priced at that tier's rate.
Dimensional Pricing
Dimensional pricing charges different rates for the same metric based on attribute values — for example, data egress priced differently by destination region.
{
"chargeElement": "egress-gb",
"priceType": "PER_UNIT", "rateType": "DIMENSIONAL",
"chargeType": "RECURRING", "currency": "USD",
"dimensionalPrices": [
{ "dimensions": { "region": "us-east-1" }, "pricePerUnit": 0.08 },
{ "dimensions": { "region": "eu-west-1" }, "pricePerUnit": 0.12 },
{ "dimensions": { "region": "ap-southeast-1" }, "pricePerUnit": 0.15 }
],
"rateCardId": "<rate-card-id>"
}
Usage Events
Usage events are the consumption signals your application sends to ConsumIQ. The API responds with 202 Accepted immediately — events are durably queued and do not block your request handler.
Send a single event
POST /api/v1/usage-events { "eventId": "evt_cust123_1719820800", "usageKey": "api.calls", "usageIdentifier": "customer-123", "value": 1, "usageTimestamp": 1719820800000, "dimensions": { "region": "us-east-1" } } # Response 202 Accepted { "eventId": "evt_cust123_1719820800", "status": "ACCEPTED" }
const ack = await fetch(`${BASE}/api/v1/usage-events`, { method: 'POST', headers, body: JSON.stringify({ eventId: 'evt_cust123_1719820800', // must be unique usageKey: 'api.calls', usageIdentifier: 'customer-123', value: 1, usageTimestamp: Date.now(), dimensions: { region: 'us-east-1' } }) }).then(r => r.json()); // ack.status === "ACCEPTED"
ack = requests.post(f'{BASE}/api/v1/usage-events', headers=headers, json={ 'eventId': 'evt_cust123_1719820800', # unique 'usageKey': 'api.calls', 'usageIdentifier': 'customer-123', 'value': 1, 'usageTimestamp': int(time.time() * 1000), 'dimensions': {'region': 'us-east-1'} }).json()
Map<?,?> ack = post("/api/v1/usage-events", Map.of( "eventId", "evt_cust123_1719820800", // unique "usageKey", "api.calls", "usageIdentifier", "customer-123", "value", 1, "usageTimestamp", System.currentTimeMillis(), "dimensions", Map.of("region", "us-east-1") )); // ack.get("status") == "ACCEPTED"
Send a batch
Batch ingestion is recommended for high-throughput scenarios. Up to 500 events per batch request.
POST /api/v1/usage-events/batch { "events": [ { "eventId": "evt_001", "usageKey": "api.calls", "usageIdentifier": "cust-1", "value": 10, "usageTimestamp": 1719820800000 }, { "eventId": "evt_002", "usageKey": "api.calls", "usageIdentifier": "cust-2", "value": 55, "usageTimestamp": 1719820801000 } ] }
await fetch(`${BASE}/api/v1/usage-events/batch`, { method: 'POST', headers, body: JSON.stringify({ events: [ { eventId: 'evt_001', usageKey: 'api.calls', usageIdentifier: 'cust-1', value: 10, usageTimestamp: Date.now() }, { eventId: 'evt_002', usageKey: 'api.calls', usageIdentifier: 'cust-2', value: 55, usageTimestamp: Date.now() } ] }) });
requests.post(f'{BASE}/api/v1/usage-events/batch', headers=headers, json={ 'events': [ {'eventId': 'evt_001', 'usageKey': 'api.calls', 'usageIdentifier': 'cust-1', 'value': 10, 'usageTimestamp': now_ms}, {'eventId': 'evt_002', 'usageKey': 'api.calls', 'usageIdentifier': 'cust-2', 'value': 55, 'usageTimestamp': now_ms}, ] })
long nowMs = System.currentTimeMillis(); post("/api/v1/usage-events/batch", Map.of( "events", List.of( Map.of("eventId", "evt_001", "usageKey", "api.calls", "usageIdentifier", "cust-1", "value", 10, "usageTimestamp", nowMs), Map.of("eventId", "evt_002", "usageKey", "api.calls", "usageIdentifier", "cust-2", "value", 55, "usageTimestamp", nowMs) ) ));
Event fields
| Field | Type | Description |
|---|---|---|
| eventIdrequired | string | Unique ID — used for deduplication on retries |
| usageKeyrequired | string | Must match a defined Usage Element |
| usageIdentifierrequired | string | The customer or entity whose usage this records |
| valuerequired | number (≥ 0) | Quantity consumed in this event |
| usageTimestamprequired | long (Unix ms) | When this consumption occurred |
| dimensions | map<string, string> | Key/value attributes for dimensional pricing |
| schemaVersion | integer | Event schema version for validation |
Usage Event Schemas
A Usage Event Schema defines the versioned contract for events with a given usageKey. Schema versioning lets producers and consumers evolve independently.
| Field | Type | Description |
|---|---|---|
| usageKeyrequired | string | The usage key this schema applies to |
| schemaVersionrequired | integer | Monotonically increasing version number |
| dimensions | DimensionDefinition[] | Expected dimension keys and their types for validation |
Proration
When a subscription starts, ends, or changes mid-period, ConsumIQ can prorate charges to reflect only the time the customer was active.
Day proration
Charge × (active days ÷ total days in period). Standard for monthly and annual subscriptions.
Second proration
Charge × (active seconds ÷ total seconds). High-precision for infrastructure or compute billing.
Enable proration on a Rate with "prorationEnabled": true and set supportedProrationSetting to DAY or SECOND.
Invoices
An Invoice is created automatically when a billing timeline is processed. It consolidates all charges for a subscription period into a single document with line items, an amount, and a status lifecycle.
Status lifecycle
Create an invoice
Invoices are created automatically when processing a billing timeline. You can also create them directly.
POST /api/v1/invoices { "subscriptionId": "sub_acme_001", "customerId": "cust-acme", "items": [ { "billingTimelineId": 42, "timelineItemId": "tli_001", "billingPeriodStart": 1719792000000, "billingPeriodEnd": 1722470400000, "subtotal": 49.00, "total": 49.00, "qty": 1 } ] }
const invoice = await fetch(`${BASE}/api/v1/invoices`, { method: 'POST', headers, body: JSON.stringify({ subscriptionId: 'sub_acme_001', customerId: 'cust-acme', items: [{ billingTimelineId: 42, timelineItemId: 'tli_001', billingPeriodStart: 1719792000000, billingPeriodEnd: 1722470400000, subtotal: 49.00, total: 49.00, qty: 1 }] }) }).then(r => r.json());
invoice = requests.post(f'{BASE}/api/v1/invoices', headers=headers, json={ 'subscriptionId': 'sub_acme_001', 'customerId': 'cust-acme', 'items': [{ 'billingTimelineId': 42, 'timelineItemId': 'tli_001', 'billingPeriodStart': 1719792000000, 'billingPeriodEnd': 1722470400000, 'subtotal': 49.00, 'total': 49.00, 'qty': 1 }] }).json()
Map<?,?> invoice = post("/api/v1/invoices", Map.of( "subscriptionId", "sub_acme_001", "customerId", "cust-acme", "items", List.of(Map.of( "billingTimelineId", 42, "timelineItemId", "tli_001", "billingPeriodStart", 1719792000000L, "billingPeriodEnd", 1722470400000L, "subtotal", 49.00, "total", 49.00, "qty", 1 )) ));
Response — 201 Created
{
"id": 1,
"invoiceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"subscriptionId": "sub_acme_001",
"customerId": "cust-acme",
"amount": 49.00,
"status": "DRAFT",
"createdAt": "2024-07-01T00:00:00Z",
"items": [
{
"id": 1,
"billingTimelineId": 42,
"timelineItemId": "tli_001",
"billingPeriodStart": 1719792000000,
"billingPeriodEnd": 1722470400000,
"subtotal": 49.00,
"total": 49.00,
"qty": 1.0000
}
]
}
Get an invoice
GET /api/v1/invoices/{id} // Returns 200 OK with the same schema as the create response
Invoice item fields
| Field | Type | Description |
|---|---|---|
| billingTimelineIdrequired | long | ID of the billing timeline that generated this charge |
| timelineItemIdrequired | string | Rate card interval identifier within the timeline |
| billingPeriodStartrequired | long (Unix ms) | Start of the billed period |
| billingPeriodEndrequired | long (Unix ms) | End of the billed period |
| subtotalrequired | decimal | Pre-discount amount |
| discountAmount | decimal | Discount applied to this line item |
| totalrequired | decimal | Amount after discounts |
| qtyrequired | decimal | Quantity billed |
| ratecardId | long | Rate card that drove this charge |
| prorationUnit | DAY | SECOND | Proration basis if charge is prorated |
Subscriptions
A Subscription links a customer to a Product Offering and Rate Card, carrying billing configuration and driving the billing lifecycle.
Status lifecycle
Create a subscription
POST /api/v1/subscriptions { "name": "Acme Corp — Starter", "customerId": "cust-acme", "productOfferingId": "<offering-id>", "billing": { "rateCardId": "<rate-card-id>", "billingItems": [ { "rateId": "<fee-rate-id>" }, { "rateId": "<calls-rate-id>" } ] }, "billingCycleAnchor": { "anchorDay": 1 } }
const sub = await fetch(`${BASE}/api/v1/subscriptions`, { method: 'POST', headers, body: JSON.stringify({ name: 'Acme Corp — Starter', customerId: 'cust-acme', productOfferingId: offeringId, billing: { rateCardId: rateCardId, billingItems: [{ rateId: feeRateId }, { rateId: callRateId }] }, billingCycleAnchor: { anchorDay: 1 } }) }).then(r => r.json());
sub = requests.post(f'{BASE}/api/v1/subscriptions', headers=headers, json={ 'name': 'Acme Corp — Starter', 'customerId': 'cust-acme', 'productOfferingId': offering_id, 'billing': { 'rateCardId': rate_card_id, 'billingItems': [{'rateId': fee_rate_id}, {'rateId': call_rate_id}] }, 'billingCycleAnchor': {'anchorDay': 1} }).json()
Map<?,?> sub = post("/api/v1/subscriptions", Map.of( "name", "Acme Corp — Starter", "customerId", "cust-acme", "productOfferingId", offeringId, "billing", Map.of( "rateCardId", rateCardId, "billingItems", List.of( Map.of("rateId", feeRateId), Map.of("rateId", callRateId) ) ), "billingCycleAnchor", Map.of("anchorDay", 1) ));
Amend a subscription
PATCH /api/v1/subscriptions/{id}/amend { "status": "INACTIVE" } // GET /api/v1/subscriptions/{id}/amendments — list all amendments
Billing Schedules
A Billing Schedule defines when a subscription is billed. It decouples billing cadence from the rate card so the same pricing can be billed monthly, quarterly, or annually.
Create a billing schedule
POST /api/v1/billing-schedules { "name": "Monthly on 1st", "type": "MONTHLY", "billingCycleAnchor": { "anchorDay": 1 } } // For a custom quarterly schedule: { "name": "Quarterly", "type": "CUSTOM", "cycleInterval": "QUARTERLY", "intervalCount": 1 } // Response — 201 Created // GET /api/v1/billing-schedules/{id} // DELETE /api/v1/billing-schedules/{id}
const sched = await fetch(`${BASE}/api/v1/billing-schedules`, { method: 'POST', headers, body: JSON.stringify({ name: 'Monthly on 1st', type: 'MONTHLY', billingCycleAnchor: { anchorDay: 1 } }) }).then(r => r.json());
sched = requests.post(f'{BASE}/api/v1/billing-schedules', headers=headers, json={ 'name': 'Monthly on 1st', 'type': 'MONTHLY', 'billingCycleAnchor': {'anchorDay': 1} }).json()
Map<?,?> sched = post("/api/v1/billing-schedules", Map.of( "name", "Monthly on 1st", "type", "MONTHLY", "billingCycleAnchor", Map.of("anchorDay", 1) ));
| Field | Type | Description |
|---|---|---|
| namerequired | string | Human-readable name |
| typerequired | STANDARD | CUSTOM | ANNUAL | SEMI_ANNUAL | QUARTERLY | MONTHLY | WEEKLY | DAILY | Billing frequency |
| cycleInterval | DAILY | WEEKLY | MONTHLY | QUARTERLY | SEMI_ANNUALLY | ANNUALLY | Interval unit for CUSTOM type |
| intervalCount | integer (≥1) | Number of intervals per cycle for CUSTOM type |
| billingCycleAnchor | object | Anchor configuration (anchorDay, anchorMonth, anchorTime) |
Subscription Schedules
A Subscription Schedule lets you pre-plan future billing phases — for example, a trial period that transitions to paid, or a plan upgrade that takes effect at a specific date. Each phase overrides billing configuration for its time window.
Create a subscription schedule
POST /api/v1/subscription-schedules { "subscriptionId": "sub_acme_001", "phases": [ { "startAt": 1719792000000, "endAt": 1722470400000, "billing": { "rateCardId": "<starter-rate-card-id>", "billingItems": [{ "rateId": "<rate-id>" }] } }, { "startAt": 1722470400000, "billing": { "rateCardId": "<growth-rate-card-id>", "billingItems": [{ "rateId": "<rate-id>" }] } } ] }
const schedule = await fetch(`${BASE}/api/v1/subscription-schedules`, { method: 'POST', headers, body: JSON.stringify({ subscriptionId: 'sub_acme_001', phases: [ { startAt: 1719792000000, endAt: 1722470400000, billing: { rateCardId: starterRcId, billingItems: [{ rateId: rateId }] } }, { startAt: 1722470400000, billing: { rateCardId: growthRcId, billingItems: [{ rateId: rateId }] } } ] }) }).then(r => r.json());
schedule = requests.post(f'{BASE}/api/v1/subscription-schedules', headers=headers, json={ 'subscriptionId': 'sub_acme_001', 'phases': [ { 'startAt': 1719792000000, 'endAt': 1722470400000, 'billing': {'rateCardId': starter_rc_id, 'billingItems': [{'rateId': rate_id}]} }, { 'startAt': 1722470400000, 'billing': {'rateCardId': growth_rc_id, 'billingItems': [{'rateId': rate_id}]} } ] }).json()
Map<?,?> schedule = post("/api/v1/subscription-schedules", Map.of( "subscriptionId", "sub_acme_001", "phases", List.of( Map.of("startAt", 1719792000000L, "endAt", 1722470400000L, "billing", Map.of("rateCardId", starterRcId, "billingItems", List.of(Map.of("rateId", rateId)))), Map.of("startAt", 1722470400000L, "billing", Map.of("rateCardId", growthRcId, "billingItems", List.of(Map.of("rateId", rateId)))) ) ));
Phase fields
| Field | Type | Description |
|---|---|---|
| startAtrequired | long (Unix ms) | When this phase begins |
| endAt | long (Unix ms) | When this phase ends — omit for an open-ended final phase |
| billingrequired | object | Rate card and billing items that apply during this phase |
| billingScheduleId | string | Override the default billing schedule for this phase |
| billingCycleAnchor | object | Override anchor for this phase |
| freeUsage | object | Free usage allowance override for this phase |
Retrieve a schedule with GET /api/v1/subscription-schedules/{id}, or list all schedules for a subscription with GET /api/v1/subscription-schedules?subscriptionId={id}.
Discount Instances
A Discount Instance is an active discount applied to a specific subscription. Discount terms are snapshotted at activation time.
| Field | Type | Description |
|---|---|---|
| status | ACTIVE | PENDING | EXPIRED | REVOKED | Only ACTIVE instances are applied at billing time |
| snapshot | object | Immutable snapshot of discount terms at activation |
| startAt / endAt | long (Unix ms) | Validity window for this discount instance |
Query discount instances with GET /api/v1/discount-instances?subscriptionId={id}&status=ACTIVE.
Accounts
An Account represents a billing entity — typically a company or customer organization. Use sandbox accounts for development and testing.
| Field | Type | Description |
|---|---|---|
| companyNamerequired | string | Company or organization name |
| personIdrequired | UUID | The initial member and owner of this account |
| externalOrgId | string | Optional external identifier for mapping to your system |
| livemode | boolean | true = production; false = sandbox |
| status | ACTIVE | INACTIVE | DELETED | INACTIVE pauses access without deletion |
# Manage members POST /api/v1/accounts/{accountId}/memberships { "personId": "..." } GET /api/v1/accounts/{accountId}/memberships DELETE /api/v1/accounts/{accountId}/memberships/{personId} # Graduate sandbox → live POST /api/v1/internal/accounts/provision-live { "sandboxAccountId": "..." }
Persons
A Person is an individual user who can be a member of one or more Accounts. Persons authenticate via their externalAuthId which maps to your identity provider.
| Field | Type | Description |
|---|---|---|
| externalAuthIdrequired | string | ID from your identity provider (e.g. Auth0 sub) |
| emailrequired | string | Valid email address |
| namerequired | string | Display name |
| timezone | string | IANA timezone, e.g. "America/New_York" |
| status | ACTIVE | DELETED | Person lifecycle status |
Best Practices
Always include eventId for idempotency
Set eventId to a unique value on every usage event — e.g. "evt_{customerId}_{timestamp}_{seq}". If your request times out and you retry, ConsumIQ will deduplicate using this ID so usage is never double-counted.
Use batch ingestion for high throughput
POST to /api/v1/usage-events/batch with up to 500 events per request. Batching reduces network overhead and is recommended for applications generating more than a few events per second.
Use Unix milliseconds for all timestamps
Every timestamp field — usageTimestamp, periodFrom, periodTo, startAt, endAt — expects a Unix epoch in milliseconds (13-digit long). Use System.currentTimeMillis() in Java, Date.now() in Node.js, or int(time.time()*1000) in Python.
Send raw usage — don't pre-subtract allowances
Report the actual quantity consumed. ConsumIQ deducts allowances automatically during charge calculation. Pre-subtracting in your application would result in under-reporting and incorrect charges.
Rate cards are locked at subscription time
Once a customer subscribes, their rate card terms are fixed. To change pricing for an existing subscriber, create a Subscription Schedule with a new phase that references the updated rate card — the change takes effect at the scheduled date.
Test with sandbox accounts before going live
Create accounts with livemode: false for development and integration testing. Sandbox data is isolated from production. Graduate to production with POST /api/v1/internal/accounts/provision-live.
Java: inject HttpClient and ObjectMapper as beans
Both HttpClient and ObjectMapper are thread-safe and expensive to construct. Declare them as @Bean singletons in your Spring Boot configuration and inject them into your ConsumIQClient service class.
API Reference
All endpoints share the /api/v1 prefix. The full interactive reference is available at /swagger-ui.html on your ConsumIQ server.
Catalogue
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/products | Create a product |
| GET | /api/v1/products | List all products |
| GET | /api/v1/products/{id} | Get a product |
| PUT | /api/v1/products/{id} | Update a product |
| DELETE | /api/v1/products/{id} | Delete a product |
| POST | /api/v1/product-offerings | Create a product offering |
| GET | /api/v1/product-offerings?productId={id} | List offerings for a product |
| GET | /api/v1/product-offerings/{id} | Get an offering (includes rates, allowances, metrics) |
| PUT | /api/v1/product-offerings/{id} | Update an offering |
| DELETE | /api/v1/product-offerings/{id} | Delete an offering |
| POST | /api/v1/usage-elements | Create a usage element |
| GET | /api/v1/usage-elements?productId={id} | List usage elements for a product |
| DELETE | /api/v1/usage-elements/{id} | Delete a usage element |
| POST | /api/v1/billable-metrics | Create a billable metric |
| GET | /api/v1/billable-metrics?rateId={id} | List billable metrics for a rate |
| DELETE | /api/v1/billable-metrics/{id} | Delete a billable metric |
| POST | /api/v1/rate-cards | Create a rate card |
| GET | /api/v1/rate-cards?productOfferingId={id} | List rate cards for an offering |
| GET | /api/v1/rate-cards/{id} | Get a rate card |
| PUT | /api/v1/rate-cards/{id} | Update a rate card |
| DELETE | /api/v1/rate-cards/{id} | Delete a rate card |
| POST | /api/v1/rates | Create a rate |
| GET | /api/v1/rates?rateCardId={id} | List rates for a rate card |
| GET | /api/v1/rates/{id} | Get a rate |
| PUT | /api/v1/rates/{id} | Update a rate |
| DELETE | /api/v1/rates/{id} | Delete a rate |
| POST | /api/v1/allowance-groups | Create an allowance group |
| GET | /api/v1/allowance-groups/{id} | Get an allowance group |
| POST | /api/v1/allowances | Create an allowance |
| GET | /api/v1/allowances?allowanceGroupId={id} | List allowances in a group |
| DELETE | /api/v1/allowances/{id} | Delete an allowance |
| POST | /api/v1/discounts | Create a discount |
| GET | /api/v1/discounts/{id} | Get a discount |
| POST | /api/v1/discount-codes | Create a discount code |
| GET | /api/v1/discount-codes?discountId={id} | List codes for a discount |
| POST | /api/v1/usage-event-schemas | Create an event schema |
| GET | /api/v1/usage-event-schemas?usageKey={key} | List schemas for a usage key |
Metering
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/usage-events | Ingest a usage event — 202 Accepted |
| POST | /api/v1/usage-events/batch | Ingest a batch of events (max 500) |
| GET | /api/v1/usage-events | Query events by usageKey + usageIdentifier + date range |
Invoices
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/invoices | Create an invoice — 201 Created |
| GET | /api/v1/invoices/{id} | Get an invoice by ID |
Subscriptions
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/subscriptions | Create a subscription — 201 Created |
| GET | /api/v1/subscriptions?customerId={id} | List subscriptions for a customer |
| GET | /api/v1/subscriptions/{id} | Get a subscription |
| PATCH | /api/v1/subscriptions/{id}/amend | Amend a live subscription |
| GET | /api/v1/subscriptions/{id}/amendments | List amendments for a subscription |
| POST | /api/v1/billing-schedules | Create a billing schedule — 201 Created |
| GET | /api/v1/billing-schedules/{id} | Get a billing schedule |
| DELETE | /api/v1/billing-schedules/{id} | Delete a billing schedule — 204 No Content |
| POST | /api/v1/subscription-schedules | Create a subscription schedule — 201 Created |
| GET | /api/v1/subscription-schedules/{id} | Get a subscription schedule by ID |
| GET | /api/v1/subscription-schedules?subscriptionId={id} | List schedules for a subscription |
| GET | /api/v1/discount-instances?subscriptionId={id}&status={s} | List discount instances for a subscription |
Accounts & Persons
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/accounts | Create an account — 201 Created |
| GET | /api/v1/accounts/{id} | Get an account |
| PATCH | /api/v1/accounts/{id} | Update an account |
| DELETE | /api/v1/accounts/{id} | Delete an account |
| POST | /api/v1/accounts/{id}/memberships | Add a person to an account |
| GET | /api/v1/accounts/{id}/memberships | List members of an account |
| DELETE | /api/v1/accounts/{accountId}/memberships/{personId} | Remove a person from an account |
| POST | /api/v1/persons | Create a person |
| GET | /api/v1/persons/{id} | Get a person |
| PATCH | /api/v1/persons/{id} | Update a person's name or timezone |
| DELETE | /api/v1/persons/{id} | Delete a person |
| GET | /api/v1/persons/{id}/accounts | List all accounts a person belongs to |
| POST | /api/v1/internal/accounts/provision-live | Graduate a sandbox account to live mode |