Authentication
API keys
How Dhanu API keys authenticate, how project and capability scopes limit what a key can reach, and how to rotate or revoke one.
Every public API request authenticates with a bearer key:
Authorization: Bearer dhanu_sk_test_…
There is no other credential. Session cookies cannot reach the public API, and an API key cannot reach the app’s management API, the two are kept completely separate.
Test keys and live keys
Every key is minted in one of two modes, baked into its prefix and fixed for the life of the key:
| Prefix | Mode | What happens on a call |
|---|---|---|
dhanu_sk_test_ | Test | Never spends credits. Generation endpoints return fixture output; a trigger returns a fixture task and creates nothing. |
dhanu_sk_live_ | Live | Runs real models and real staff, billed to the workspace’s credits. |
Use a test key in CI and local development, and switch to a live key when you
deploy. Both modes go through the same authentication, scope checks, validation,
rate limits and idempotency, and return the same response shapes, a client that
works against a test key works against a live one. GET /me reports the mode as
key.mode.
Keys created before modes existed are test keys; mint a live key to spend credits.
Expiry
A key can be given an expiry when it is created, 7, 30, 90 or 365 days, or
never. An expired key is refused with api_key_expired (401) exactly like a
revoked one, and the console shows how long each key has left. Short-lived keys
for CI and one-off integrations limit how long a leaked key can be used; a
service that must run unattended can still hold a key that never expires.
Monthly spend cap
A key can carry a monthly spend cap in credits. Once the key’s generation calls
have spent that much in a calendar month (UTC), further calls fail with
key_spend_limit_reached (402) until the month rolls over or the cap is raised;
the key’s creator is notified at 80% and at 100%. The cap counts image and text
generation made with that key, staff runs started through a trigger are billed
to the task and are not counted. The console shows each key’s spend against its
cap.
IP allowlist
A key can be limited to a list of up to 20 IPv4 or IPv6 addresses or CIDR ranges.
A call from any other address is refused with api_key_ip_not_allowed (403) before
it runs. Leave the list empty to allow any address. The console shows the address
each key was last used from, and flags keys that have gone 90 days without use so
you can revoke what you no longer need.
Keys are shown once
When you create a key, the secret is displayed exactly once. Only a hash of it is stored, so it cannot be recovered afterwards. If you lose it, delete the key and create a new one, there is no way to reveal an existing secret again.
Project scopes
A key belongs to a workspace and is scoped to an explicit set of projects. You must select at least one project when creating a key, and every generation is metered against a project in that scope.
How the acting project is chosen:
- Single-project key: the project is implied. You can omit
projectId. - Multi-project key: you must pass
projectIdon each generation request. Omitting it fails withproject_id_required(422). - Out of scope: passing a
projectIdthe key does not cover fails withapi_key_project_scope(403).
A key whose scope resolves to zero active projects, because they were all
deselected or the projects were archived, stops working entirely, with
api_key_project_scope (403), before any endpoint runs.
Capability scopes
Projects answer whose data a key acts on. Capability scope answers which APIs it may call. You choose one of three levels when you create the key:
| Level | The key can call |
|---|---|
app | Every API, including ones added to the platform later. The default. |
package | Only the packages you pick, growing as APIs are added to those packages. |
api | Only the exact APIs you pick. Nothing is granted automatically. |
Packages group related APIs, text covers text.generate, images covers
images.generate. Pick package when you want a key that follows a product area
as it grows; pick api when you want a key frozen to exactly what it does today.
Calling an API outside the key’s capability scope fails with
api_key_operation_scope (403) before the request runs, so a narrowed key never
spends credits on a call it was not meant to make.
Both scopes apply together: a request must be inside the key’s project scope and its capability scope.
Discovering what a key can call
/reference lists the whole catalogue and marks what the calling key is allowed
to use, so you can build against the API without installing an SDK:
curl https://api.dhanu.ai/api/pub/v1/reference \
-H "Authorization: Bearer $DHANU_API_KEY"
Each package lists its APIs with the method, path, whether the call is
billed, and an allowed flag for this key. /me and /reference are
introspection endpoints, any live key can call them at any scope level, since
they only reveal what the holder of the key already has.
Access is tied to its creator
A key keeps working only while the person who created it still has developer
access in that workspace. If their Developer role is removed, or they are removed
from or demoted in the workspace, the key stops working with
developer_access_revoked (403).
This is deliberate: a key that leaked, or that an ex-colleague kept, dies with their access rather than outliving it.
If a key leaks to GitHub
Dhanu is registered with GitHub’s secret scanning: when a dhanu_sk_… key is
pushed to a public repository, GitHub reports it to us, we revoke it at once,
record it in the workspace’s security audit log as api_key.leaked, and email the
person who created the key with where it was found. Anything using the key fails
from that moment, create a replacement and remove the old value from wherever it
was published.
Replacing a leaked key
To roll a key, create the replacement first, deploy it, then delete the old one, that way the integration never has a window with no working key. Both actions live in Developers → API keys; deleting is immediate and permanent.
A key can also be revoked through the API (POST /developer-keys/:keyId/revoke),
which keeps the row for audit instead of removing it. A revoked key fails with
api_key_revoked (401) and cannot be brought back.
Narrowing a key’s scope is the softer option: if a key reaches further than it should, edit its projects or API scope rather than replacing it.
Checking a key
curl https://api.dhanu.ai/api/pub/v1/me \
-H "Authorization: Bearer $DHANU_API_KEY"import { Dhanu } from "dhanu";
const dhanu = new Dhanu(process.env.DHANU_API_KEY!);
const me = await dhanu.me();
console.log(me.projects, me.scope);import os
from dhanu import Dhanu
dhanu = Dhanu(os.environ["DHANU_API_KEY"])
me = dhanu.me()
print(me.projects, me.scope) A 200 tells you the key is live and shows exactly which projects it can reach and which packages and APIs its scope allows. A 401 means the key is unknown or revoked; a 403 means it is real but no longer permitted.