Skip to Content

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:

EndpointWhat the key can access
Session HistoryGET /api/billing/usage/Sessions for any AI character the key’s owner created
Chat HistoryGET /api/billing/sessions/<id>/Full transcript + recording URL for owned-character sessions
Form ResponsesGET /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/json
curl -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

FieldTypeRequiredDescription
namestringHuman-readable label. Pick something that tells your future self where the key is used ("crm-sync", "staging-dashboard").
is_activebooleanDefaults 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 } ] }
FieldDescription
key_previewFirst 5 + last 4 chars of the key, e.g. sk_a1...f6789. Useful for matching keys to environments.
last_used_atTimestamp of the most recent successful request. null if never used. Updated on every authenticated call.
is_activeIf 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-dashboard is compromised, you revoke only that key — crm-sync keeps 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 with last_used_at going stale or appearing from an unexpected IP is a signal to investigate.

Errors

StatusCauseFix
401 UnauthorizedMissing header, unknown key, or is_active: falseCheck the header is exactly X-API-Key and the key is active
404 Not Found on a session/character endpointKey’s owner doesn’t own that characterUse a key owned by the character’s creator, or grant access via the dashboard
200 with empty resultsSame as above, on list endpointsConfirm 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; }
Last updated on