Niravi Docs
GitHub ↗ Get a key ↗

Concepts

The building blocks shared by every endpoint and SDK.

Authentication

Send your key on every request as the X-API-Key header (or Authorization: Bearer nv_… — both work):

curl https://api.niravi.io/api/v1/videos -H "X-API-Key: nv_..."

Keys are created and revoked in the dashboard → Settings → Developer. They are stored hashed; the plaintext is shown only once at creation.

Workspaces & scopes

Each key has an explicit workspace access list — its home workspace (the default) plus any workspaces you grant it (your other workspaces and/or the read-only demos). You set this when you create the key and edit it anytime under Settings → API access. A key can only ever reach workspaces on its list, and only ones you yourself can access.

Target one workspace — send the X-Workspace-Id header (the SDKs take a workspace option). Omit it to use the home workspace.

curl https://api.niravi.io/api/v1/search \
  -H "X-API-Key: nv_..." -H "X-Workspace-Id: demo_content" \
  -H "Content-Type: application/json" -d '{"query":"a sunset"}'
client = niravi.connect("nv_...", workspace="demo_content")
const niravi = connect("nv_...", { workspace: "demo_content" });

Search across several at once (federated) — pass workspace_ids to /search. Results from every listed workspace merge into one ranked list, and each hit carries its own workspace_id. Only workspaces on the key’s list are queried (others are silently skipped).

hits = client.search("a sunset", workspace_ids=["demo_content", "demo_media"])
for h in hits:
    print(h.workspace_id, h.score, h.video_id)   # each hit tagged with its workspace
const hits = await niravi.search("a sunset", { workspaceIds: ["demo_content", "demo_media"] });

Discover what a key can reach with GET /api/v1/workspaces (SDK: client.workspaces()): returns the key’s access list, each with an access field (home / member / demo).

Demos (demo_*) are read-only — search/recall/chat work, but upload/delete return 403. Targeting a workspace not on the key’s list returns 403.

Each key also carries scopes:

ScopeGrants
readSearch, recall, chat, and all read endpoints
writeUpload and delete

Calling a write endpoint with a read-only key returns 403.

The response envelope

Every response is a consistent envelope.

// success
{ "success": true, "data": { /* ... */ }, "meta": { /* request id, timing */ } }

// list (paginated)
{ "success": true, "data": [ /* ... */ ], "pagination": { "limit": 50, "offset": 0, "total": 1280, "has_more": true } }

// error
{ "success": false, "error": { "code": "forbidden", "message": "…", "status": 403 } }

The SDKs unwrap data for you and surface pagination/errors as language-native types.

Pagination

List endpoints (e.g. GET /videos) take limit (max 200) and offset, and return a pagination object with has_more. To page through everything, advance offset by the number of items until has_more is false — or let an SDK do it:

videos = client.videos(all_pages=True)   # follows the cursor for you
const videos = await niravi.videos({ allPages: true });

Errors

Failures use standard HTTP status codes; the SDKs raise typed errors:

StatusMeaningSDK error
401 / 403Missing/invalid key, or scope/workspace deniedAuthError
404Resource not foundNotFoundError
422Invalid request bodyValidationError
429Rate limited (see Retry-After)RateLimitError
5xxServer errorServerError

Rate limits

Responses carry X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers; the SDKs parse them into client.rate_limit / niravi.rateLimit after each call. On 429 the SDKs retry automatically with backoff that honours Retry-After.