Authentication and keys
Agent keys, scope limits, strict mode, and what a key can reach.
The Product API authenticates with an agent key. Browser sessions are not accepted.
There are two ways to get one:
- Connect — the product sends the workspace owner to Buddhi, they approve, and the product receives its credentials directly. Nobody copies a key. See Connect a product.
- Create a key by hand — described below. Fine for your own integrations and for testing.
Create a key
- In Buddhi, open Gateway & Agents.
- Create a new agent key and name it after the product, for example
Burrito AI. Memories the key writes show this name as their source. - Set the surface to API (another product).
- Under Limit this key to scopes, tick only the scopes the product should use.
- Leave Strict mode on, and choose an expiry if you want one.
- Copy the key (
crtx_…) and, for a strict key, the signing secret (bss_…). Each is shown once.
Treat both values like passwords. Keep them in server-side environment variables and never ship them to a browser or mobile app. If either leaks, revoke the key in Gateway & Agents; it stops working immediately.
What a key can do
A key acts as the person who issued it. It can never do more than that person can.
A key limited to scopes is narrower still:
| Unlimited key | Limited key | |
|---|---|---|
| Reads | Everything its issuer can read | Only the listed scopes |
| Writes | Everything its issuer can write | Only the listed scopes its issuer can write |
| Issuer's personal scope (their Gmail and Drive) | Included | Never included |
| Overruling an existing memory | Anywhere its issuer may write | Only inside the listed scopes |
The last row matters for generated content. If a marketing-only key writes something that contradicts an engineering memory, the engineering memory is not replaced. Both stay in use and the workspace owner decides between them in Conflict Resolution.
For a product that publishes content, limit the key to the Publishable scope. It exists for exactly this: knowledge the company has cleared to appear in public. A whole marketing scope also holds plans, budgets and unannounced launches.
You can only give a key scopes you have yourself. Asking for others returns 403.
Writing: review by default
What a key writes is held for a person to approve unless the workspace owner ticked Write without review when issuing it. Reading is unaffected. A product that only fetches context needs nothing more; one that records published content back into Buddhi needs a trusted key, or its writes wait in the Agent Memory inbox. See Agent memory.
Plain keys and strict keys
| Plain key | Strict key | |
|---|---|---|
| Sent on every request | Yes, as the bearer | No — only to the token exchange |
| Data calls carry | The key | A 10-minute access token |
| Requests are signed | No | Yes |
| Works on the MCP gateway and CLI | Yes | No |
| Use it for | AI agents, quick tests | Another product's server |
A strict key protects against the two things that go wrong with long-lived credentials: the key is almost never on the wire, and a captured request cannot be changed or replayed.
1. Exchange the key for an access token
POST /api/v1/token
Send the key as the bearer and sign the request (below). There is no body.
{ "access_token": "bat_…", "token_type": "Bearer", "expires_in": 600 }
Use access_token as the bearer on every other call. When it expires, or a call returns 401 invalid_token, exchange again. Revoking the key cuts off its outstanding tokens at once.
2. Sign every request
Add two headers:
X-Buddhi-Timestamp: 1791234567890
X-Buddhi-Signature: v1=<hex>
- Timestamp: the current Unix time in milliseconds. It must be within five minutes of Buddhi's clock.
- Signature:
v1=followed by the hex HMAC-SHA256, keyed with the signing secret, of this string:
<timestamp>.<METHOD>.<path and query>.<sha256 hex of the body>
For a GET, the body is the empty string. The path is exactly what you request, for example /api/v1/context.
import { createHash, createHmac } from "node:crypto";
function sign(secret: string, method: string, path: string, body = "") {
const timestamp = String(Date.now());
const bodyHash = createHash("sha256").update(body).digest("hex");
const signature = createHmac("sha256", secret)
.update(`${timestamp}.${method.toUpperCase()}.${path}.${bodyHash}`)
.digest("hex");
return { "X-Buddhi-Timestamp": timestamp, "X-Buddhi-Signature": `v1=${signature}` };
}
Sign the exact bytes you send. Serialise the JSON body once and use that same string for both the signature and the request.
Each signature is accepted once. Sending the identical request again with the same timestamp returns 401 replayed_request; a retry needs a fresh timestamp.
If you may send two identical requests in the same millisecond, add X-Buddhi-Nonce with a random value of up to 64 URL-safe characters and append .<nonce> to the string you sign. The drop-in client does this for every request.
Say who is asking
Send the person who triggered the call in your product:
X-Buddhi-End-User: priya@yourcompany.com
Buddhi records it in the audit log and shows it in the key's activity, so "what did the product ask, and for whom" has an answer. It is for attribution only: it never changes what the key can reach. Up to 200 characters.
Check a key
GET /api/v1/me
Confirms the credentials work and shows what they reach. Make this the first call of any integration.
{
"key": {
"id": "0d3c…",
"name": "Burrito AI",
"limitedToScopes": ["publishable"]
},
"actingAs": { "name": "Vansh", "role": "CEO" },
"scopes": {
"read": ["publishable"],
"write": ["publishable"],
"includesPersonal": false
}
}
| Field | Description |
|---|---|
key.limitedToScopes | The key's scope limit, or null for an unlimited key |
actingAs | The person the key acts for |
scopes.read | Scopes recall, context and facts draw from |
scopes.write | Scopes the key may add memories to |
scopes.includesPersonal | Whether the issuer's personal scope is reachable. Always false for a limited key |
Expiry
A key can be given an expiry of 30 days, 90 days or a year when it is created. After that it stops working and is shown as expired. Issue a new key before then; there is no way to extend one.
See what a key has been doing
In Gateway & Agents, open Activity on a key. It lists each context read, memory written, fact lookup and token exchange, with the time, the scope, whether it was allowed, and the end user the product reported. The key's owner and the workspace owner can see it.
Revoke a key
Revoke it in Gateway & Agents. The next request returns 401 invalid_key, including requests carrying a token that was issued from it.