A publishable key is the one kind of key meant to be seen: it ships inside a web page or an app, where anyone can read it. It can make decisions and nothing else, not even list the models, and every publishable key has rate limits and a daily spend cap, so a copied key can cost at most its cap a day. When you have a server of your own, keep the key there instead and let the page call your server.
Make one#
- In the console only, under API keys, of the kind Publishable.
/v1/keysnever makes one. - Allowed origins for a web page: each one exact, as the browser sends it,
https://hosthttps://hostorhttps://host:porthttps://host:portsuch ashttps://shop.example.comhttps://shop.example.com, with no path and no wildcard;http://http://only forlocalhost,127.0.0.1and[::1]. At most 20, kept in lowercase. A newly listed origin is answered within about 10 seconds. - Apps send no
Origin. Tick Allow native apps (no Origin) for a key an app uses; a key with no origins must have it ticked. Without origins, this key's limits are its only protection. - Models it may use: the key may call only the models it lists, DecisionNode Flash unless you add more, and never a fine-tuned model. Nor does a fallback answer it with another: when the fallback is not one of its models, the call gets what it would without one, the usual
529while nothing is ready. - Rate limits and a daily spend cap are always on. A new key starts at 5 requests and 20,000 input tokens a second, 1 request a second per client address (bursts of 10), 8 requests in flight, and USD 5 a day; set them to what your traffic needs (calls in flight from 1 to 64). One client address may make the per-address rate times 10 calls in each 10-second window, so a burst of 10 at the default; an IPv6 visitor is counted by its
/64, an IPv4 address on its own. - 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.
In a browser#
A page on an allowed origin calls https://api.decisionnode.com/v1/decidehttps://api.decisionnode.com/ straight from the browser with the key. A publishable key starts with dn_pub_, so it is never taken for a secret one. Name a model the key lists: a request without model goes to DecisionNode MoE, which a key on DecisionNode Flash alone refuses with 403 model_not_allowed.
const r = await fetch("https://api.decisionnode.com/v1/decide", {
method: "POST",
headers: {
Authorization: "Bearer dn_pub_...", // publishable: safe to ship
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "decisionnode-1.0-flash",
state: { review: text },
questions: { abusive: { type: "truth", instructions: "Is the review abusive?" } },
}),
});
const { answers } = await r.json();A browser may call POST /v1/decide: the preflight gets 204 only from an origin a publishable key lists. From an origin no publishable key lists, and for any other route these docs describe, it gets 400 Disallowed CORS origin, so list the origin first. A publishable key's answers to a listed origin, its errors and a 401 included, carry Access-Control-Allow-Origin and let the page read x-request-id, retry-after, retry-after-ms, x-decisionnode-cache and x-decisionnode-model: read the request id from x-request-id, and the wait from retry-after on a 429. No other kind of key's answers carry them, whatever origin the page sends, so only a publishable key works in a page: a Full access key pasted into one fails, as it should.
In a mobile app#
An app sends no Origin. A call without one is accepted only on a key with Allow native apps (no Origin) ticked (otherwise it is 403 origin_not_allowed), and then the rate limits and the spend cap are its only protection: anyone with the app can take the key out of it. If the app has a server of yours behind it, give each install its own Decisions-only key from that server instead: see Keys on devices.
Refusals#
| Answer | When |
|---|---|
400 Disallowed CORS origin | A browser's preflight for any route these docs describe but POST /v1/decide, or from an origin no publishable key lists |
403 origin_not_allowed | An Origin the key does not list: "This key does not answer calls from this origin." Or no Origin on a key without Allow native apps (no Origin): "This key answers calls from its origins only: a call without an Origin needs a key that allows native apps." |
403 model_not_allowed | A model the key does not list, a fine-tuned model, or no model on a key without DecisionNode MoE (a body without one asks DecisionNode MoE): "This key may not ask this model. Its models: <the key's models>.", with details.allowed, the key's list |
403 key_scope_denied | Any route but a decision, the models list included: "A publishable key may only make decisions." Or a face_recognition booster: "This key may not search collections." |
401 authentication_error | A revoked key |
429 key_spend_cap_reached | The daily spend cap is spent, until 00:00 UTC |
429 rate_limit_error | Over the key's rate: "Rate limit reached. Retry after 1 s." Over one client address's (an IPv6 visitor's /64): "Rate limit reached for this address. Retry after <n> s.", details.reason client_rate. Too many calls running at once: "Too many of this key's calls are running at once. Retry after 1 s.", details.reason in_flight. Each with Retry-After |
What a publishable key can never do#
- List the models:
GET /v1/modelsis403key_scope_denied, "A publishable key may only make decisions." - Search collections: a
face_recognitionbooster is403key_scope_denied, whatever the key's settings. - Make uploads, sessions, or read, change or delete collections.
- Make, list or revoke keys.
- Call a model it does not list, spend past its daily spend cap, or run without rate limits.
When a publishable key is abused, revoke it in the console and publish a new one; the revocation is immediate.