WhenDocumentation Index
Fetch the complete documentation index at: https://docs.getbindu.com/llms.txt
Use this file to discover all available pages before exploring further.
AUTH__ENABLED=true, every call to a Bindu agent has to do two things at once:
- Prove you’re allowed — attach a short-lived bearer token from Hydra.
- Prove you’re really you — sign the request body with your DID’s private key and attach the signature.
The four headers
Every call to an auth-on Bindu agent carries these four headers:| Gate | What’s checked | On failure |
|---|---|---|
| 1 | Bearer token present and active in Hydra | HTTP 401 + JSON-RPC -32009 "Authentication is required..." |
| 2 | X-DID matches the token’s client_id | HTTP 403 + details.reason: did_mismatch |
| 3 | Public key for that DID is registered in Hydra client metadata | HTTP 403 + details.reason: public_key_unavailable |
| 4 | Timestamp within 300s and signature verifies | HTTP 403 + details.reason: invalid_signature |
Gate 4 collapses two sub-causes (clock skew and bad signature) into one
invalid_signature reason. If you get this error, re-signing with a fresh timestamp eliminates clock-skew and replay as the cause.I just want it to work — use a built-in caller
Most teams should not hand-roll this. Three callers in the Bindu repo do the whole chain for you:| Caller | What you provide | What it does |
|---|---|---|
Inbox (POST /api/compose) | Persona + OpenRouter key (via UI) | Spawns your personal agent, registers it with Hydra, signs every outbound message |
Gateway (POST /plan) | BINDU_GATEWAY_DID_SEED + Hydra URLs | Same identity for every peer call |
| Postman collection | Seed + DID + secret in environment | Pre-request script signs each call automatically |
ok:true, status:200 back means every gate above passed. You’re done.
The rest of this page is for people writing the caller from scratch in a new language.
Hand-rolling it: one-time setup
You need three durable artifacts:- A seed — your 32-byte secret. Generates the Ed25519 keypair.
- A DID — your public name, deterministically derived from the seed.
- An OAuth client in Hydra — registered against the DID, with the public key in metadata.
Hand-rolling it: every request
Four steps. The first runs once per hour (token cache). The other three run on every call.Build the JSON-RPC body
Serialize the body once and keep the exact bytes. The bytes you sign must equal the bytes you send.
Sign
The signing payload is a second JSON object that wraps the body as a string. Sort keys and keep Python’s default whitespace.
What can go wrong
| Response | Most likely cause | Fix |
|---|---|---|
HTTP 401, JSON-RPC -32009 Authentication is required | No Authorization header, or token is invalid/expired | Mint a fresh token, attach as Authorization: Bearer … |
HTTP 403, details.reason = did_mismatch | X-DID doesn’t match the token’s client_id | Mint the token with the same DID you send as X-DID |
HTTP 403, details.reason = public_key_unavailable | metadata.public_key missing on the Hydra client, or you registered against a different Hydra | GET /admin/clients/<did> and check metadata.public_key |
HTTP 403, details.reason = invalid_signature | Clock skew > 300s, replayed timestamp, body bytes drifted between sign and send, sort_keys/whitespace mismatch, or signed with the wrong seed | Sign fresh on every request, sign the exact bytes you’ll send, verify against the canonical fixture |
HTTP 400, -32700 JSON parse error (e.g. params.configuration field required) | Body shape wrong before auth runs — JSON-RPC validator rejects upfront | Body bug, not an auth bug. Include params.configuration and confirm against an unauthed peer first |
invalid_client from /oauth2/token | Wrong client_secret or client not registered on this Hydra | GET /admin/clients/<did> to confirm |
invalid_scope from /oauth2/token | Requesting a scope the client wasn’t registered with | Re-register with the scope, or drop it |
The middleware collapses four sub-causes of “signature didn’t verify” into one
invalid_signature reason. To narrow it down, re-sign with a fresh timestamp first — that eliminates clock skew and replay. If it still fails, you have a body-byte or key-mismatch issue.active: true, client_id == your DID, and exp > now. Anything off here is your bug.
Canonical fixture
Use this to verify your sign-and-encode implementation against every other Bindu caller, in any language.| Input | Value |
|---|---|
| Seed | 32 zero bytes |
| DID | did:bindu:test |
| Body | {"test": "value"} |
| Timestamp | 1000 |
: and ,):
nacl-base58 is the same).
What’s next
Security Stack
How mTLS, Hydra, and DID signatures compose on a single request
DID Identity
The signing side in depth — Ed25519, canonical JSON, key rotation