Authentication¶
Two credentials, for two different callers.
| Credential | Header / transport | For | Grants |
|---|---|---|---|
| API key | Authorization: Bearer vk_live_… |
Agents, SDK, MCP, servers | Organization scope |
| Session JWT | venkai_session httponly cookie |
The dashboard, in a browser | Organization and user scope |
Most endpoints accept either. A few — /api/auth/me, API-key management, team
management — require a session and explicitly reject API keys:
That is by design: a key cannot mint or revoke keys.
API keys¶
Create¶
curl -sX POST https://api.venkai.fr/api/auth/api-keys \
-H 'Content-Type: application/json' -b cookies.txt \
-d '{"label":"production-agent","environment":"production"}'
{
"id": "key_b8f08563afb5",
"org_id": "org_1f200d429c50",
"label": "production-agent",
"environment": "production",
"api_key": "vk_live_EXAMPLE_KEY_NOT_A_REAL_CREDENTIAL_0000000000"
}
| Field | Constraint |
|---|---|
label |
≤ 100 chars, default "default" |
environment |
production → vk_live_ · development → vk_test_ |
Returned once
Only a hash is stored. GET /api/auth/api-keys lists keys but never
returns the raw value again.
Both prefixes authenticate identically and reach the same data. The prefix is a labelling convention for humans and log scrubbers — not a permission boundary and not a separate environment.
List / revoke / rotate¶
curl -s https://api.venkai.fr/api/auth/api-keys -b cookies.txt
curl -sX DELETE https://api.venkai.fr/api/auth/api-keys/key_b8f08563afb5 -b cookies.txt
curl -sX POST https://api.venkai.fr/api/auth/api-keys/key_b8f08563afb5/rotate -b cookies.txt
rotate revokes the old key and mints a new one with the same label, returning
the new raw key. There is no grace period — the old key stops working
immediately, so deploy the new one first if you cannot tolerate a gap.
Sessions¶
curl -sX POST https://api.venkai.fr/api/auth/register \
-H 'Content-Type: application/json' -c cookies.txt \
-d '{"email":"you@example.com","password":"a-real-password","organization":"Acme"}'
{
"token": "eyJhbGciOiJIUzI1NiIs…",
"user": {"id": "usr_…", "email": "you@example.com"},
"organization": {"id": "org_…", "name": "Acme"}
}
The same JWT is returned in the body and set as an httponly venkai_session
cookie (Secure only when the server runs with VENKAI_ENV=production).
| Endpoint | Purpose |
|---|---|
POST /api/auth/login |
{email, password} → token + cookie |
POST /api/auth/logout |
Clears the cookie |
GET /api/auth/me |
{id, email, org_id} |
POST /api/auth/forgot-password |
Sends a reset mail. Rate-limited more strictly than general traffic. |
POST /api/auth/reset-password |
{token, password} — password ≥ 8 chars |
GET /api/auth/reset-password/verify |
Check a reset token before showing the form |
Registration requires a password ≥ 6 characters; reset requires ≥ 8. A password valid at signup can be too short to re-set — a real inconsistency, noted here rather than smoothed over.
OAuth Beta¶
Returns the providers configured on this deployment, which depends on its
environment — do not hard-code the list. GET /api/auth/oauth/{provider}
starts the flow; the callback exchanges {code, state, code_verifier} (PKCE)
for a session.
OAuth produces a dashboard session, not an API key. Agents still need a key.
Failure modes¶
| Status | Meaning |
|---|---|
401 Not authenticated |
No credential at all |
401 Invalid API key |
Key unknown or revoked |
401 User not found |
Valid JWT for a deleted user |
401 …requires a dashboard session… |
API key used on a session-only endpoint |
429 |
Rate limited — honour Retry-After |
An unauthenticated read is a 401, never an empty result — verified by the
docs test suite. If you get {"memories": []}, you are authenticated and the
project really is empty.
Practices¶
- Keep keys in a secret store or
.env, never in source control. - One key per deployed component: revoking one then does not take down the rest.
- Use
vk_test_in non-production so log scrubbing and greps can tell them apart. - Never ship a key to a browser. CORS is restricted, but a key in client-side JavaScript is a published key.