Billing Service — API Contracts
Status: populated Owner: Platform Engineering + Finance Last updated: 2026-04-18 Companion: APPLICATION_LOGIC · ADR-0001 Kong
All REST endpoints are admin/internal only; fronted by Kong. No public customer-facing billing API — customer-portal reads invoices and usage via these same endpoints (with account.admin scope).
1. Usage Query API
GET /v1/billing/usage
Returns aggregated usage for the caller's account (or a target account for platform.admin).
Query params:
accountId(platform.admin only)fromISO date (required)toISO date (required)granularityhour|day|month(defaultday)operatorId(optional filter)
Response 200:
{
"accountId": "uuid",
"currency": "USD",
"from": "2026-03-01",
"to": "2026-03-31",
"granularity": "day",
"buckets": [
{
"period": "2026-03-01",
"messageCount": 15200,
"segmentCount": 16800,
"totalCustomerPrice": "304.00",
"totalOperatorCost": "168.00",
"margin": "136.00"
}
],
"totals": {
"messageCount": 450000,
"segmentCount": 495000,
"totalCustomerPrice": "9000.00",
"totalOperatorCost": "4950.00",
"margin": "4050.00"
}
}
2. Invoice API
GET /v1/billing/invoices
Paginated list. Params: accountId (admin), status, from, to, cursor, limit (max 50).
Response 200:
{
"data": [
{
"invoiceId": "uuid",
"accountId": "uuid",
"periodStart": "2026-03-01",
"periodEnd": "2026-03-31",
"totalMessages": 450000,
"subtotalAmount": "9000.00",
"currency": "USD",
"status": "FINALIZED",
"generatedAt": "2026-04-01T00:10:00Z"
}
],
"nextCursor": "opaque",
"total": 12
}
GET /v1/billing/invoices/{invoiceId}/download
Returns a presigned S3 URL (TTL 15 min).
Response 200:
{ "url": "https://s3.../invoices/...", "expiresAt": "2026-04-18T10:15:00Z" }
POST /v1/admin/invoices/{invoiceId}/void
Finance admin only (role platform.finance). Body: { "reason": "string" }.
Response 200: updated invoice record.
3. Pricing Admin API
GET /v1/admin/pricing
List pricing tables. Params: accountTier, operatorId, activeOnly=true.
POST /v1/admin/pricing
Create pricing table entry.
{
"accountTier": "GROWTH",
"operatorId": "op-uk-001",
"direction": "MT",
"pricingModel": "PER_SEGMENT",
"unitPrice": "0.0200",
"currency": "USD",
"effectiveFrom": "2026-05-01"
}
Response 201: created PricingTable record.
PATCH /v1/admin/pricing/{id}
Updates effectiveTo of existing row and creates a successor row (versioned update).
Body: same as POST; effectiveFrom must be after existing row's effectiveFrom.
DELETE /v1/admin/pricing/{id}
Soft-deletes by setting effectiveTo = today. Response 204.
4. Operator Cost Admin API
GET /v1/admin/operator-costs
List. Params: operatorId, activeOnly=true.
POST /v1/admin/operator-costs
{
"operatorId": "op-uk-001",
"direction": "MT",
"costPerSegment": "0.0100",
"currency": "USD",
"effectiveFrom": "2026-05-01"
}
Response 201.
PATCH /v1/admin/operator-costs/{id}
Versioned update — same pattern as pricing PATCH.
5. Internal / Operational
| Endpoint | Caller | Purpose |
|---|---|---|
GET /health/live | K8s | Liveness |
GET /health/ready | K8s | Readiness (PG + Redis + NATS reachable) |
GET /metrics | Prometheus | Metrics scrape |
6. Error Response Shape
{
"type": "https://errors.ghasi.io/billing/PRICING_NOT_FOUND",
"title": "No active pricing rule found",
"status": 404,
"code": "PRICING_NOT_FOUND",
"detail": "No pricing_tables row for accountTier=GROWTH, operatorId=op-xx-999, direction=MT",
"requestId": "req_01H..."
}
7. Auth & Scopes (Kong-enforced)
| Scope | Endpoints |
|---|---|
billing:read | GET /v1/billing/usage, GET /v1/billing/invoices, download |
billing:admin | All /v1/admin/* endpoints |
platform.finance | void invoice |