When to use an API key
Use an API key when:
- Your backend needs to call Oshara endpoints (sync transcripts into your CRM, build a customer-facing dashboard, automate reports).
- You want to authenticate without managing a user’s JWT refresh cycle.
- You need to revoke access independently — disable one key without rotating user passwords.
For browser-side calls (the widget, session-start from a logged-in user) keep using JWT. Don’t expose API keys to the browser.
What a key can do
An API key inherits the permissions of the user who created it. It can read any data the owning user can read:
| Endpoint | What the key can access |
|---|---|
Session History — GET /api/billing/usage/ | Sessions for any AI character the key’s owner created |
Chat History — GET /api/billing/sessions/<id>/ | Full transcript + recording URL for owned-character sessions |
Form Responses — GET /api/agents/<slug>/form-responses/ | Form submissions for owned characters |
Non-owned data returns 404 (or empty results on list endpoints) — keys never see another user’s sessions.
Sending the header
X-API-Key: sk_<32-byte hex>curl https://api.oshara.ai/api/billing/sessions/sess_a1b2c3/ \
-H "X-API-Key: sk_a1b2c3d4e5f6..."Both X-API-Key and x-api-key work (HTTP headers are case-insensitive); use the capitalised form in docs and code.
When a request carries both a valid X-API-Key and a JWT, the API key wins — auth resolves to the API key’s user.
Create a key
POST /api/auth/api-keys/
Authorization: Bearer <jwt>
Content-Type: application/jsoncurl -X POST https://api.oshara.ai/api/auth/api-keys/ \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"name": "transcript-sync-prod"}'Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ✓ | Human-readable label. Pick something that tells your future self where the key is used ("crm-sync", "staging-dashboard"). |
is_active | boolean | Defaults to true. Set false to create a disabled key. |
Response (201 Created)
The full key value is returned only once — store it immediately.
{
"success": true,
"message": "Request successful",
"data": {
"message": "API key created successfully. Save this key safely — you won't see it again!",
"api_key": {
"id": 17,
"name": "transcript-sync-prod",
"key": "sk_a1b2c3d4e5f6789...",
"created_at": "2026-05-28T14:23:00Z",
"is_active": true
}
},
"errors": null
}List your keys
GET /api/auth/api-keys/
Authorization: Bearer <jwt>curl https://api.oshara.ai/api/auth/api-keys/ \
-H "Authorization: Bearer <jwt>"The full key value is not included — only a masked preview. If you lost a key, create a new one and revoke the old.
Response
{
"data": [
{
"id": 17,
"name": "transcript-sync-prod",
"key_preview": "sk_a1...f6789",
"created_at": "2026-05-28T14:23:00Z",
"last_used_at": "2026-05-28T18:42:11Z",
"is_active": true
},
{
"id": 16,
"name": "old-zapier-key",
"key_preview": "sk_z9...4231",
"created_at": "2026-03-14T09:00:00Z",
"last_used_at": null,
"is_active": false
}
]
}| Field | Description |
|---|---|
key_preview | First 5 + last 4 chars of the key, e.g. sk_a1...f6789. Useful for matching keys to environments. |
last_used_at | Timestamp of the most recent successful request. null if never used. Updated on every authenticated call. |
is_active | If false, the key is rejected. |
Revoke a key
Disable a key without deleting it (preferred — keeps the audit row):
curl -X PATCH https://api.oshara.ai/api/auth/api-keys/17/ \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"is_active": false}'Permanently delete a key (loses history):
curl -X DELETE https://api.oshara.ai/api/auth/api-keys/17/ \
-H "Authorization: Bearer <jwt>"A revoked key returns 401 Unauthorized on subsequent requests.
Rename a key
curl -X PATCH https://api.oshara.ai/api/auth/api-keys/17/ \
-H "Authorization: Bearer <jwt>" \
-H "Content-Type: application/json" \
-d '{"name": "transcript-sync-prod-v2"}'Security guidance
- One key per integration. If
staging-dashboardis compromised, you revoke only that key —crm-synckeeps working. - Never embed in browser code. Keys give full read access to all your sessions/transcripts. Keep them server-side.
- Store as a secret. Use a secret manager (AWS Secrets Manager, HashiCorp Vault, GitHub Actions secrets). Don’t commit to git.
- Rotate periodically. Create the new key, deploy it, then revoke the old one — overlapping rotation, no downtime.
- Monitor
last_used_at. A key withlast_used_atgoing stale or appearing from an unexpected IP is a signal to investigate.
Errors
| Status | Cause | Fix |
|---|---|---|
401 Unauthorized | Missing header, unknown key, or is_active: false | Check the header is exactly X-API-Key and the key is active |
404 Not Found on a session/character endpoint | Key’s owner doesn’t own that character | Use a key owned by the character’s creator, or grant access via the dashboard |
200 with empty results | Same as above, on list endpoints | Confirm character_slug filter matches a character the owner created |
Code examples
Node.js (server-side)
const OSHARA = "https://api.oshara.ai";
async function fetchSession(sessionId) {
const r = await fetch(`${OSHARA}/api/billing/sessions/${sessionId}/`, {
headers: { "X-API-Key": process.env.OSHARA_API_KEY }
});
if (!r.ok) throw new Error(`Oshara ${r.status}`);
return (await r.json()).data;
}Python (server-side)
import os, httpx
OSHARA = "https://api.oshara.ai"
HEADERS = {"X-API-Key": os.environ["OSHARA_API_KEY"]}
def fetch_session(session_id: str) -> dict:
r = httpx.get(f"{OSHARA}/api/billing/sessions/{session_id}/", headers=HEADERS)
r.raise_for_status()
return r.json()["data"]Sync new sessions hourly
const since = new Date(Date.now() - 3600_000).toISOString();
let url =
`${OSHARA}/api/billing/usage/` +
`?character_slug=support-bot&start=${since}`;
while (url) {
const { data } = await fetch(url, {
headers: { "X-API-Key": process.env.OSHARA_API_KEY }
}).then(r => r.json());
for (const s of data.sessions.data) {
await db.sessions.upsert(s);
}
url = data.sessions.pagination.next;
}Related
- Session History — list sessions by character
- Chat History — single-session transcript + recording
- Form Responses — form submissions
- Authentication — JWT (the other auth scheme)