Documentation API v1 / Fornax Labs ↗
Usage-Based Billing

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.

● Catalogue ● Metering ● Billing ● Subscriptions ● Accounts ⚠ Invoicing — Coming Soon

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


Overview

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


Getting Started

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
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" }
node.js
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"
python
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"
java
// 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
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", ... }
node.js
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;
python
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']
java
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
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"
  }'
node.js
await fetch(`${BASE}/api/v1/usage-elements`, {
  method: 'POST', headers,
  body: JSON.stringify({
    usageKey: 'api.calls', usageUnit: 'calls',
    usageType: 'UNLIMITED', productId: productId
  })
});
python
requests.post(f'{BASE}/api/v1/usage-elements', headers=headers, json={
    'usageKey': 'api.calls', 'usageUnit': 'calls',
    'usageType': 'UNLIMITED', 'productId': product_id
})
java
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
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", ... }
node.js
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;
python
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']
java
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
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"
  }'
node.js
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;
python
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']
java
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
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 }
    ]
  }'
node.js
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;
python
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']
java
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
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"
  }'
node.js
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
  })
});
python
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
})
java
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.

curl
# 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>"
  }'
node.js
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
  })
});
python
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']
})
java
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
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", ... }
node.js
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"
python
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()
java
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
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
node.js
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
}
python
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)
    })
java
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:

java — service setup
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);
    }
}

Getting Started

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

json — error response
{
  "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

CodeStatusWhen it occurs
200OKRequest succeeded (GET, PUT, PATCH)
201CreatedResource created (POST for subscriptions, accounts, persons)
202AcceptedUsage event queued for async processing — do not wait for billing confirmation
204No ContentResource deleted
400Bad RequestValidation failure — missing required fields or invalid values. Check the message field for the specific constraint.
404Not FoundResource ID does not exist
409ConflictDuplicate resource — e.g., duplicate productCode
422Unprocessable EntityViolates a business rule — e.g., referencing an INACTIVE rate card
500Internal Server ErrorUnexpected server error — safe to retry with exponential backoff

Common validation messages

MessageCauseFix
must not be blankRequired string is missing or emptyCheck required fields marked required
must not be nullRequired field absent from bodyInclude the field explicitly
must not be emptyRequired array (e.g. billingItems) was []Include at least one item
not foundReferenced ID does not existVerify the ID was returned from a prior create call
size must be between 1 and 500Batch event count exceeds 500Split into batches of ≤500

Catalogue

Products

A Product is the top-level entity representing what you sell. All pricing configuration (offerings, rate cards, rates) is attached to a product.

FieldTypeDescription
productNamerequiredstringDisplay name, e.g. "Data Processing API"
productCodestringUnique machine-readable code, e.g. "DATA-API"
descriptionstringOptional description
statusACTIVE | INACTIVE | ARCHIVEDOnly ACTIVE products can accept subscriptions
curl
# 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}
node.js
// 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());
python
# 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()
java
// 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();
Catalogue

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.

FieldTypeDescription
namerequiredstringe.g. "Starter Plan"
productIdrequiredUUIDParent product
statusACTIVE | INACTIVE | ARCHIVEDINACTIVE 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}.

Catalogue

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.

FieldTypeDescription
usageKeyrequiredstringUnique identifier used in events, e.g. api.calls
usageUnitrequiredstringHuman-readable unit name, e.g. "requests", "GB"
usageTyperequiredUNLIMITED | RATE_LIMITED | THROTTLEDMetering behaviour
primaryDimensionstringDimension key for multi-dimensional pricing
productIdrequiredUUIDParent product
Catalogue

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.

FieldTypeDescription
usageKeyrequiredstringMust match the Usage Element key
chargeElementrequiredstringMust match the Rate's chargeElement
aggregationTyperequiredSUM | MAX | MIN | COUNT | AVERAGE | UNIQUEHow to aggregate events before pricing
rateIdrequiredUUIDThe Rate this metric feeds into
productOfferingIdUUIDParent product offering
Catalogue

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.

FieldTypeDescription
namerequiredstringe.g. "Monthly Standard", "Annual Discounted"
statusACTIVE | INACTIVE | ARCHIVEDOnly ACTIVE rate cards can be used by new subscriptions
productOfferingIdrequiredUUIDParent 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.

Catalogue

Rates & Pricing Models

A Rate defines how a specific chargeElement is priced within a Rate Card.

PUP
Price Per Unit

Fixed price × aggregated usage. Best for linear cost scaling.

FLAT
Flat Fee

Fixed fee regardless of usage. Useful for seat licenses or minimum commitments.

TIERED · VOLUME
Volume Tiered

All usage priced at the rate of the tier the total falls into.

TIERED · GRADUATED
Graduated Tiered

Usage split across tiers — each portion priced at its tier's rate.

DIMENSIONAL
Dimensional

Price varies by dimension value (region, storage class). Defined as a matrix.

TIME_BASED
Time-Based

Pricing changes by time of day for off-peak discounts or peak surcharges.

Rate fields

FieldTypeDescription
chargeElementrequiredstringMust match a Billable Metric's chargeElement
priceTyperequiredFLAT | PER_UNIT | TIEREDSelects the pricing calculator
rateTyperequiredSTANDARD | RECURRING | TIERED | DIMENSIONAL | TIME_BASEDRate behaviour type
chargeTyperequiredONE_TIME | RECURRINGONE_TIME: charged once. RECURRING: charged each billing period.
currencyrequiredstringISO 4217 code, e.g. "USD", "EUR", "GBP"
pricePerUnitdecimalUnit price for PUP pricing
flatChargedecimalFixed amount for FLAT pricing
tiersTier[]Ordered tier breakpoints for TIERED pricing
dimensionalPricesDimensionalPrice[]Dimension matrix for DIMENSIONAL pricing
rateCardIdrequiredUUIDParent rate card
Catalogue

Allowances

An Allowance gives customers a free-usage entitlement before billing starts. The billing engine deducts allowances from aggregated usage automatically.

AllowanceGroup
Container

Groups all allowances for a product offering. One offering can have one allowance group.

Allowance
Global allowance

A flat quantity subtracted from total usage for a usageKey. Supports optional resetCycle (DAILY, WEEKLY, MONTHLY, YEARLY, NONE).

DimensionalAllowance
Per-dimension allowance

Different free-usage amounts per dimension value.

curl
POST /api/v1/allowances
{
  "usageKey": "api.calls",
  "allowanceAmount": 10000,
  "allowanceGroupId": "<group-id>",
  "resetCycle": { "resetInterval": "MONTHLY", "resetCycleType": "RECURRING" }
}
node.js
await fetch(`${BASE}/api/v1/allowances`, {
  method: 'POST', headers,
  body: JSON.stringify({
    usageKey: 'api.calls', allowanceAmount: 10000,
    allowanceGroupId: groupId,
    resetCycle: { resetInterval: 'MONTHLY', resetCycleType: 'RECURRING' }
  })
});
python
requests.post(f'{BASE}/api/v1/allowances', headers=headers, json={
    'usageKey': 'api.calls', 'allowanceAmount': 10000,
    'allowanceGroupId': group_id,
    'resetCycle': {'resetInterval': 'MONTHLY', 'resetCycleType': 'RECURRING'}
})
java
post("/api/v1/allowances", Map.of(
    "usageKey",         "api.calls",
    "allowanceAmount",   10000,
    "allowanceGroupId",  groupId,
    "resetCycle", Map.of(
        "resetInterval",  "MONTHLY",
        "resetCycleType", "RECURRING"
    )
));
Catalogue

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.

FieldTypeDescription
namerequiredstringInternal name, e.g. "Q3 Launch Promo"
discountTyperequiredPERCENTAGE | FIXED_AMOUNTPercentage off or a fixed currency reduction
discountMethodrequiredPERCENTAGE_OFF | FIXED_AMOUNT_OFF | TIERED_DISCOUNT | SEASONAL_DISCOUNTHow the discount is calculated
valuerequireddecimale.g. 20 for 20% off, or 10.00 for $10 off
discountDurationrequiredPERMANENT | RECURRING | LIMITED_TIMEHow long the discount applies
redemptionCountintegerMax 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.


Pricing Models

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.

Example — $0.005 per API call
2,400 API calls× $0.005
Total$12.00
json — rate definition
{
  "chargeElement": "api-calls",
  "priceType": "PER_UNIT", "rateType": "RECURRING",
  "chargeType": "RECURRING", "currency": "USD",
  "pricePerUnit": 0.005, "rateCardId": "<rate-card-id>"
}
Pricing Models

Flat Fee

A fixed charge regardless of usage. Use it for base subscription fees, seat licenses, or platform access charges.

Example — $49/month platform fee
Platform access$49.00
Total$49.00
json — rate definition
{
  "chargeElement": "platform-fee",
  "priceType": "FLAT", "rateType": "RECURRING",
  "chargeType": "RECURRING", "currency": "USD",
  "flatCharge": 49.00, "rateCardId": "<rate-card-id>"
}
Pricing Models

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.

Graduated tiered example — 8,000 units
First 1,000 units × $0.05$50.00
Next 4,000 units (1,001–5,000) × $0.03$120.00
Remaining 3,000 units (5,001–8,000) × $0.01$30.00
Total$200.00
json — tiered rate definition
{
  "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.

Pricing Models

Dimensional Pricing

Dimensional pricing charges different rates for the same metric based on attribute values — for example, data egress priced differently by destination region.

Example — egress priced by region
region: us-east-1  (500 GB)500 × $0.08 = $40.00
region: eu-west-1  (200 GB)200 × $0.12 = $24.00
region: ap-southeast-1 (100 GB)100 × $0.15 = $15.00
Total$79.00
json — dimensional rate definition
{
  "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>"
}

Metering

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

curl
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" }
node.js
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"
python
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()
java
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.

curl
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 }
  ]
}
node.js
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() }
    ]
  })
});
python
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},
    ]
})
java
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

FieldTypeDescription
eventIdrequiredstringUnique ID — used for deduplication on retries
usageKeyrequiredstringMust match a defined Usage Element
usageIdentifierrequiredstringThe customer or entity whose usage this records
valuerequirednumber (≥ 0)Quantity consumed in this event
usageTimestamprequiredlong (Unix ms)When this consumption occurred
dimensionsmap<string, string>Key/value attributes for dimensional pricing
schemaVersionintegerEvent schema version for validation
Metering

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.

FieldTypeDescription
usageKeyrequiredstringThe usage key this schema applies to
schemaVersionrequiredintegerMonotonically increasing version number
dimensionsDimensionDefinition[]Expected dimension keys and their types for validation

Billing

Proration

When a subscription starts, ends, or changes mid-period, ConsumIQ can prorate charges to reflect only the time the customer was active.

DAY
Day proration

Charge × (active days ÷ total days in period). Standard for monthly and annual subscriptions.

SECOND
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

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

DRAFT PENDING PAID

Create an invoice

Invoices are created automatically when processing a billing timeline. You can also create them directly.

http
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
    }
  ]
}
node.js
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());
python
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()
java
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

json — response
{
  "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

http
GET /api/v1/invoices/{id}

// Returns 200 OK with the same schema as the create response

Invoice item fields

FieldTypeDescription
billingTimelineIdrequiredlongID of the billing timeline that generated this charge
timelineItemIdrequiredstringRate card interval identifier within the timeline
billingPeriodStartrequiredlong (Unix ms)Start of the billed period
billingPeriodEndrequiredlong (Unix ms)End of the billed period
subtotalrequireddecimalPre-discount amount
discountAmountdecimalDiscount applied to this line item
totalrequireddecimalAmount after discounts
qtyrequireddecimalQuantity billed
ratecardIdlongRate card that drove this charge
prorationUnitDAY | SECONDProration basis if charge is prorated

Subscriptions

Subscriptions

A Subscription links a customer to a Product Offering and Rate Card, carrying billing configuration and driving the billing lifecycle.

Status lifecycle

PENDING ACTIVE INACTIVE CANCELLED
ACTIVE EXPIRED(at configured end date)

Create a subscription

curl
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 }
}
node.js
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());
python
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()
java
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

http
PATCH /api/v1/subscriptions/{id}/amend
{ "status": "INACTIVE" }

// GET /api/v1/subscriptions/{id}/amendments — list all amendments
Subscriptions

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

http
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}
node.js
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());
python
sched = requests.post(f'{BASE}/api/v1/billing-schedules', headers=headers, json={
    'name': 'Monthly on 1st',
    'type': 'MONTHLY',
    'billingCycleAnchor': {'anchorDay': 1}
}).json()
java
Map<?,?> sched = post("/api/v1/billing-schedules", Map.of(
    "name", "Monthly on 1st",
    "type", "MONTHLY",
    "billingCycleAnchor", Map.of("anchorDay", 1)
));
FieldTypeDescription
namerequiredstringHuman-readable name
typerequiredSTANDARD | CUSTOM | ANNUAL | SEMI_ANNUAL | QUARTERLY | MONTHLY | WEEKLY | DAILYBilling frequency
cycleIntervalDAILY | WEEKLY | MONTHLY | QUARTERLY | SEMI_ANNUALLY | ANNUALLYInterval unit for CUSTOM type
intervalCountinteger (≥1)Number of intervals per cycle for CUSTOM type
billingCycleAnchorobjectAnchor configuration (anchorDay, anchorMonth, anchorTime)
Subscriptions

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

http
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>" }]
      }
    }
  ]
}
node.js
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());
python
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()
java
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

FieldTypeDescription
startAtrequiredlong (Unix ms)When this phase begins
endAtlong (Unix ms)When this phase ends — omit for an open-ended final phase
billingrequiredobjectRate card and billing items that apply during this phase
billingScheduleIdstringOverride the default billing schedule for this phase
billingCycleAnchorobjectOverride anchor for this phase
freeUsageobjectFree 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}.

Subscriptions

Discount Instances

A Discount Instance is an active discount applied to a specific subscription. Discount terms are snapshotted at activation time.

FieldTypeDescription
statusACTIVE | PENDING | EXPIRED | REVOKEDOnly ACTIVE instances are applied at billing time
snapshotobjectImmutable snapshot of discount terms at activation
startAt / endAtlong (Unix ms)Validity window for this discount instance

Query discount instances with GET /api/v1/discount-instances?subscriptionId={id}&status=ACTIVE.


Accounts

Accounts

An Account represents a billing entity — typically a company or customer organization. Use sandbox accounts for development and testing.

FieldTypeDescription
companyNamerequiredstringCompany or organization name
personIdrequiredUUIDThe initial member and owner of this account
externalOrgIdstringOptional external identifier for mapping to your system
livemodebooleantrue = production; false = sandbox
statusACTIVE | INACTIVE | DELETEDINACTIVE pauses access without deletion
http
# 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": "..." }
Accounts

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.

FieldTypeDescription
externalAuthIdrequiredstringID from your identity provider (e.g. Auth0 sub)
emailrequiredstringValid email address
namerequiredstringDisplay name
timezonestringIANA timezone, e.g. "America/New_York"
statusACTIVE | DELETEDPerson lifecycle status

Reference

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.

Reference

API Reference

All endpoints share the /api/v1 prefix. The full interactive reference is available at /swagger-ui.html on your ConsumIQ server.

Catalogue

MethodPathDescription
POST/api/v1/productsCreate a product
GET/api/v1/productsList 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-offeringsCreate 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-elementsCreate 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-metricsCreate 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-cardsCreate 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/ratesCreate 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-groupsCreate an allowance group
GET/api/v1/allowance-groups/{id}Get an allowance group
POST/api/v1/allowancesCreate an allowance
GET/api/v1/allowances?allowanceGroupId={id}List allowances in a group
DELETE/api/v1/allowances/{id}Delete an allowance
POST/api/v1/discountsCreate a discount
GET/api/v1/discounts/{id}Get a discount
POST/api/v1/discount-codesCreate a discount code
GET/api/v1/discount-codes?discountId={id}List codes for a discount
POST/api/v1/usage-event-schemasCreate an event schema
GET/api/v1/usage-event-schemas?usageKey={key}List schemas for a usage key

Metering

MethodPathDescription
POST/api/v1/usage-eventsIngest a usage event — 202 Accepted
POST/api/v1/usage-events/batchIngest a batch of events (max 500)
GET/api/v1/usage-eventsQuery events by usageKey + usageIdentifier + date range

Invoices

MethodPathDescription
POST/api/v1/invoicesCreate an invoice — 201 Created
GET/api/v1/invoices/{id}Get an invoice by ID

Subscriptions

MethodPathDescription
POST/api/v1/subscriptionsCreate 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}/amendAmend a live subscription
GET/api/v1/subscriptions/{id}/amendmentsList amendments for a subscription
POST/api/v1/billing-schedulesCreate 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-schedulesCreate 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

MethodPathDescription
POST/api/v1/accountsCreate 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}/membershipsAdd a person to an account
GET/api/v1/accounts/{id}/membershipsList members of an account
DELETE/api/v1/accounts/{accountId}/memberships/{personId}Remove a person from an account
POST/api/v1/personsCreate 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}/accountsList all accounts a person belongs to
POST/api/v1/internal/accounts/provision-liveGraduate a sandbox account to live mode