Some products put DecisionNode on their customers' hardware: a drone's flight computer, a camera at a customer's site, a robot on a customer's floor, each calling the API itself rather than through your servers. Every call is metered on the key that made it, so with one key per device you can read each device's usage and cost for a month, and bill each of your customers for theirs. This guide builds on Keys on devices.
Give every device a labelled key#
Make each device's Decisions-only key with POST /v1/keys when the device enrolls, as on Keys on devices, and label it with what you bill by: the customer, and the plan the device is on. Labels are the filter of the month's read, and they are read as the key carries them now, not as it carried them when each call was made: a key whose labels you change mid-month bills its whole month to its new labels. So set them when the key is made, and when a device moves to another customer, give it a new key and revoke the old one.
{
"name": "Drone 0042",
"device_id": "acme-drone-0042",
"labels": { "customer": "acme", "plan": "pro" },
"daily_spend_cap": { "usd": 5 }
}- One key per device, never shared: the key is the meter, so a key on two devices bills one customer for both.
- A daily spend cap bounds what one device can cost you in a day if its key is copied, whatever you bill for it.
Read the month#
On the first of the month, read last month for each customer from your server, with a Full access or Manage device keys key: GET /. You get one row per key used in the month, with its totals, newest key first. Follow next_cursor until it is null: a page can hold fewer keys than limit (Paging). end is exclusive, so a month runs from its first midnight to the next month's, in UTC.
curl -G https://api.decisionnode.com/v1/usage \
-H "Authorization: Bearer $DECISIONNODE_MANAGE_KEY" \
--data-urlencode "start=2026-10-01T00:00:00Z" \
--data-urlencode "end=2026-11-01T00:00:00Z" \
--data-urlencode "label=customer:acme" \
--data-urlencode "status=all"{
"period": {
"start": "2026-10-01T00:00:00Z",
"end": "2026-11-01T00:00:00Z"
},
"as_of": "2026-11-01T00:00:00Z",
"items": [
{
"key": {
"id": "key_3b8f0d6e2a914c7b9e5f1a2c4d6e8f01",
"name": "Drone 0042",
"scope": "decide",
"device_id": "acme-drone-0042",
"labels": { "customer": "acme", "plan": "pro" },
"status": "active",
"created_at": "2026-09-02T08:14:00Z",
"revoked_at": null,
"expires_at": null
},
"totals": {
"requests": 12480,
"free_tier_requests": 0,
"input_tokens": 68640000,
"image_tokens": 49920000,
"video_tokens": 0,
"booster_calls": 0,
"cost": { "usd": 2.88288, "model_usd": 2.88288, "boosters_usd": 0 }
}
},
{
"key": {
"id": "key_9d1c5e7a3b2f4e6d8c0a1b3e5f7a9c20",
"name": "Drone 0017",
"scope": "decide",
"device_id": "acme-drone-0017",
"labels": { "customer": "acme", "plan": "pro" },
"status": "revoked",
"created_at": "2026-08-11T10:02:00Z",
"revoked_at": "2026-10-19T16:40:00Z",
"expires_at": null
},
"totals": {
"requests": 5210,
"free_tier_requests": 0,
"input_tokens": 28655000,
"image_tokens": 20840000,
"video_tokens": 0,
"booster_calls": 0,
"cost": { "usd": 1.20351, "model_usd": 1.20351, "boosters_usd": 0 }
}
}
],
"next_cursor": null
}Revoked keys are included#
status=all, which is also the default, keeps every key the month used, so a device revoked or expired during the month still has its row, with what it did before: Drone 0017 above was revoked on the 19th and is billed for its 5,210 requests. Its usage stays readable after it is revoked: usage is kept indefinitely. Pass status=active only when you want today's fleet, not the month's.
Wait for as_of#
Usage is folded in every minute, from calls at least two minutes old, and as_of says how far the figures are complete. It never passes the period's end: wait until it equals end before you invoice. For a month that is normally a few minutes after 00:00 UTC on the 1st, and from then on the month's figures do not change: usage is dated when it is recorded, and nothing arrives late in a past hour. The one exception is a rebuild of past hours, during which as_of steps back to the first of them. The samples above refuse to invoice before as_of reaches end; run them again later. See as_of.
Turn rows into invoice lines#
Each row is a line: key.name or key.device_id says which device, key.labels which customer and plan, and totals what it did. Charge your own price: per request, per token, per device a month, or your cost plus a margin. At USD 0.002 a request, the month above reads:
| Device | Requests | Cost to you | Invoice line |
|---|---|---|---|
| Drone 0042 | 12,480 | USD 2.88288 | USD 24.96 |
| Drone 0017 | 5,210 | USD 1.20351 | USD 10.42 |
- Keep money exact.
cost.usdis in whole micro-dollars, at most 6 decimals: read it as a decimal (Python'sDecimal, Go'sjson.Number), not a float, and round only the invoice's own total. - Group by label in your own code when one invoice spans several labels: read once per customer, or once for every key and group the rows by
key.labels.customer. - Several months, or a day by the hour:
granularity=monthordayaddsbucketsto each row (and holds a page to 100 keys); one key's own figures are atGET /v1/keys/{key_id}/usage.
Why the cost is what we charge you#
totals.cost is what your workspace is charged for the key's calls in the period: refused and failed calls, and the free tier's model part, at 0, and a capped key's answers at most what was reserved for them. On a prepaid workspace it is the list price as metered: what your balance was drawn down by, and your usage page's spend. On a workspace invoiced at a negotiated rate, it is the invoice's own arithmetic at the month's rate: summed over your keys, it matches the usage on your invoice, each answer's usage.cost, and your usage page's spend, which on such a workspace runs a couple of minutes behind. An invoice's workspace-wide lines (reserved capacity, collection storage, and the credit for usage drawn before invoicing began) are in no key's figures. Totals are priced per key, month and model, and each bucket and split on its own sums: at list prices they add up exactly; at a negotiated rate a part can differ from its share by a micro-dollar. So the rows of a month add up to what that month's usage costs your workspace, and what you invoice above it is your margin.
- The free tier, where your workspace has one, makes the first requests of each day (it renews at 00:00 UTC) cost 0 on whichever keys made them; each row's
free_tier_requestscounts them. If your price is your cost plus a margin, price fromrequestsand tokens instead, so the free tier lowers your costs and not one customer's bill. - A capped key's answer never costs more than was reserved for it: an answer that would cost more is cut to the reservation, and the difference is ours. The rows count the tokens billed for it.
Every parameter and field is on GET /v1/usage and GET /v1/keys/{key_id}/usage.