A device that decides on its own (a camera, a kiosk, a robot) needs a key on the device. Give it a Decisions-only key of its own, made by your provisioning server when the device enrolls. The device can then make decisions and list the models, and nothing else; your server keeps the only key that can make more. Why this matters is on API keys.
Provision a device#
- 1
Make a Manage device keys key#
In the console, under API keys, create a key of the kind Manage device keys. Keep it on your provisioning server, in a secret manager or an environment variable such as
DECISIONNODE_MANAGE_KEY. It can make, change and revoke Decisions-only keys, and it cannot make a decision. - 2
Make the device's key when it enrolls#
When a device asks for a key, your server first checks it your own way (its serial number, a certificate, a sign-in of yours), then calls
POST /v1/keysfor it. Send anIdempotency-Keyof that enrollment: a retry within 24 hours returns the same key and secret instead of making a second one. A replay goes only to the Manage device keys key that made the key, and a workspace'sIdempotency-Keyvalues are one namespace, so if several provisioning servers make keys, give each its own prefix.curl -X POST https://api.decisionnode.com/v1/keys \ -H "Authorization: Bearer $DECISIONNODE_MANAGE_KEY" \ -H "Idempotency-Key: enroll-kiosk-0412" \ -H "Content-Type: application/json" \ -d '{"name":"Kiosk 0412","device_id":"kiosk-0412","labels":{"store":"118","fw":"2.4"},"limits":{"requests_per_second":5,"tokens_per_second":20000},"daily_spend_cap":{"usd":2},"expires_at":"2027-10-13T00:00:00Z","may_search_collections":false}' - 3
Hand the secret to the device once#
Send the secret over your own channel to that device alone, and store it in the device's secure storage. Never log it. Keep the key's
idon your server, to change or revoke it later. - 4
Decide from the device#
The device uses the SDK, or plain HTTPS, with its own key.
from decisionnode import DecisionNode, Image, truth dn = DecisionNode(api_key=read_device_secret()) # this device's Decisions-only key d = dn.decide( images=[Image("counter", path="/run/camera/latest.jpg")], questions={"waiting": truth("Is a customer waiting at the counter?")}, ) d["waiting"].truth # 0.91
Pictures from a device#
A Decisions-only key cannot make uploads (POST /v1/uploads is 403 key_scope_denied), so a device sends its pictures inline or by url, inside the decision. Inline takes up to 25 MiB a request, far more than a camera frame. The SDKs upload a request's local pictures once they reach their inline threshold, 512 KiB in all by default: on a device, raise it (inline_threshold in Python, dn.WithInlineThreshold in Go, inlineThreshold in PHP) so every picture goes inline.
Limits, spend cap and expiry#
- Rate limits.
limitssets the key's own requests and input tokens a second, each a whole number from 1 to the workspace's own (nullclears both). Over them, a429rate_limit_errorwithRetry-After, as for any key. Set them a little above what the device really needs. - Daily spend cap.
daily_spend_capis the most the key may spend in a day,{"usd": 2.0}to two decimals and at most USD 1,000, andspent_todaywhat it has spent so far. Past it, every call is429key_spend_cap_reached, "This key's daily spend cap of USD 2.00 is reached. It resets at 00:00 UTC.", withdetailsresets_at,capandspent, andRetry-Afterup to 00:00 UTC. Treat it as a stop, not a retry: report it to your server, which can raise the cap or replace the key. - The cap counts each request at its worst case until it is answered. Every request reserves its largest possible cost before it runs, and the key is charged the real cost once it is answered. So a key near its cap is refused a little early, by up to one request's worst case, and a capped key can have only about its cap divided by one request's worst-case cost in flight at once: with a very low cap and many calls at the same time,
429key_spend_cap_reachedcan come before the day's real spend reaches the cap. Set the cap with headroom over what you expect to spend. - A capped key's answer never costs more than was reserved for it. If an answer would cost more (one priced as another version than the one reserved for, or more tokens than the model's context, as the model reports them), its cost is cut to the reservation and DecisionNode absorbs the difference. The answer's
usage.cost, the usage page, per-key usage, the invoice and a prepaid balance all carry the cut amount, and the usage page and per-key usage count the tokens billed; the answer's own token counts stay as the model reported them, so on such an answer its tokens times the price come to more than itsusage.cost. - Expiry.
expires_atis at least a minute and at most 10 years ahead. After it the key answers401authentication_errorwithdetails.reasonkey_expired: do not retry, provision the device again. An expired key's settings cannot change (409key_expired): it is replaced, never extended. Adevice_idbelongs to one active key at a time, so to renew, revoke the old key and make the new one in the same provisioning call.
Revoke#
Revoke one key by its id, or every key whose labels match all the labels you name. Count a group first with dry_run. One call revokes at most 10,000 keys, and more is true while matching keys are left, which can happen with fewer when a key was in use at that moment: call again until more is false. A revocation is immediate and cannot be undone: the next call with the key gets 401.
curl -X DELETE https://api.decisionnode.com/v1/keys/key_7c2e9a41b0d34f6e8a5c1d2b3e4f5a60 \
-H "Authorization: Bearer $DECISIONNODE_MANAGE_KEY"{ "would_revoke": 37, "more": false }Fleet operations with labels#
- Labels are names and values, such as
{"store": "118", "fw": "2.4"}: a label per site, per hardware batch or per firmware version lets you act on exactly the group a problem touches. - Find keys.
GET /v1/keys?label=store:118lists the keys of one store; repeatlabelto match them all (?label=store:118&label=fw:2.4), or?device_id=kiosk-0412for one device's key. - Move a device.
PATCH /v1/keys/{id}with newlabelsmoves it to another group without touching its secret. - A stolen batch of hardware. Revoke its label in one call, then provision the replacements as new devices.
- In the console, Revoke by label does the same: Count keys first, then Revoke N keys.
- Bill by label.
GET /reads every key's usage and cost for a period in one call, revoked keys included, so a label per customer is also an invoice per customer: Bill your customers per device.v1/ usage?start=...&end=...&label=customer:acme
Every field and route is on the /v1/keys reference.