Skip to main content

Cost-center chargeback

Internal chargeback starts with one habit: tag every send with the department that should carry its cost. Devotel Orbit turns those tags into a department-first statement — every cost center as a line item with its per-channel split and share of the period total, plus the totals for spend that is still untagged. This guide covers the full loop:
  1. Tag sends with a metadata.cost_center label.
  2. Read the rollup at GET /api/v1/billing/chargeback-rollup.
  3. Cross-check a single tag on GET /api/v1/billing/usage-by-channel.
  4. Export the period with the statement exporter.

1. Tag sends with a cost center

Set a free-form string on the request metadata object. The same field works on every messaging send endpoint (/sms, /mms, /rcs, /whatsapp, /email, …) and on outbound voice calls — any entry you put there is stored on the message or call row.
Pick a naming convention for the value and hold to it. The tag is a plain string — no case-folding, no canonicalisation — so dept_sales and Dept_Sales are two different cost centers. A short, stable convention like dept_<name> or <name>_<region> keeps the rollup readable. Tagging works from today onward. Only outbound rows carry price, so inbound traffic never enters the totals.

2. The chargeback rollup

GET /api/v1/billing/chargeback-rollup folds the tagged sends into a department-first statement over a rolling window:
The response is department-first: a grand total, the split of allocated (tagged) against unallocated (untagged) spend, and one line per cost center with its per-channel breakdown and its share of the period:
Amounts come back in both integer minor units (*_minor, cents) and a two-decimal string. Channel lines sum exactly to their cost-center total, and the cost-center lines sum exactly to the grand total — the rollup uses integer arithmetic, so there is no rounding drift between the field groups. Reading the response:
  • allocated is spend carrying a real tag; unallocated is the untagged pool; allocated_pct tells you how much of the bill is ready to charge back. Watch that percentage rather than the raw totals.
  • cost_centers is ordered by spend descending, with the untagged line always sorted last — real departments lead the report.
  • The sourcing is your own metered usage ledger (message and voice prices), so figures exclude subscription fees, credits, discounts, and tax. Treat the rollup as a proportional allocation of usage spend, not invoice math.

3. Slice one cost center with usage-by-channel

The companion axis on GET /api/v1/billing/usage-by-channel answers the inverse question: break a single cost center (or the whole tag dimension) down by channel, country, campaign, or sender.
Two behaviours matter:
  • group_by=cost_center with no cost_center filter returns one bucket per tag — with no upper bound on the bucket list (other axes return the top 100). Use it to enumerate tags.
  • Adding cost_center=dept_sales narrows the response to that tag, split by the axis you chose (e.g. group_by=channel&cost_center=dept_sales). The filter is only valid when group_by is one of the tag-bearing axes (country, campaign, sender, cost_center); on group_by=channel the filter is rejected with a 400.
Both endpoints derive the tag the same way, so a given cost center totals to the same figure on either surface.

4. Untagged spend and the untagged bucket

A send without a metadata.cost_center entry (or with an empty one) lands in the untagged bucket on both surfaces. It is not dropped — it totals separately and shows up as the unallocated totals in the rollup, so the rollup always reconciles to the full bill. Best practice is to require the tag at the send layer in your integration: reject outbound sends that miss it, or stamp a fallback department server-side. Drive allocated_pct toward 100 and treat what is left as the exception queue. Before changing tags, remember the tag is a plain string: renaming a value re-buckets history going forward, so settle the convention before sends scale.

5. Access and rate limits

Both endpoints are read-only and gated to the billing role: owner, admin, or billing session tokens (and API keys carrying the billing:read scope where Billing is scope-able). They run under the standard billing read limit, and responses are cached for 60 seconds, so a dashboard or polling script is not running the aggregation on every load.

6. Where this fits with Billing overview and the statements feed

The passing mention of cost centers on the Billing overview page lands behind this guide: the cost_center axis and the rollup are the attribution surfaces; overview then frames them against usage, the ledger, and spend alerts. For the month-end packet, the statement exporter serves the same period as CSV or JSON:
  • GET /api/v1/billing/statements — list monthly summaries.
  • GET /api/v1/billing/statements/:period — a month’s detail rows.
  • GET /api/v1/billing/statements/:period/download — CSV or JSON export.
Tag the sends, watch the allocated_pct, export when the period closes.