Skip to content

Authentication and Authorization

There are two authentication paths into elsai Instructions Manager:

  • Users sign into the console (web UI) — Clerk on SaaS, JWT on on-prem.
  • Applications authenticate with an API key — the SDK and every HTTP API endpoint.

This page covers the concepts. For what an integrator needs — transports, scopes, and which endpoint returns which status — see HTTP API → Authentication. The on-prem authorization model has one important twist compared to SaaS, covered below.

API keys

Keys are created by signed-in users in the console (API Keys page). Each key has:

  • A name (human-readable, for finding it later)
  • A key_prefix (first few characters, shown in the UI)
  • A key_last4 (last 4 characters, shown in the UI)
  • A SHA-256 hash of the plaintext key (stored in the database)
  • The owning user_id
  • A status (active or revoked)
  • A scoperead or write (on-prem)

WARNING

The plaintext key is only shown once, at creation time. The platform stores the hash, not the plaintext, and cannot recover it. If you lose it, revoke it and create a new one.

Scopes

On-prem only. SaaS keys have no scope; they read.

ScopeReadPublish
read❌ — 403
write

Every key issued before scopes existed is read. Keys get pasted into CI configs, notebooks and client backends, so a default of write would have silently handed publish rights to every key already in circulation. A key gains publish rights only when someone asks for one.

A write scope is necessary but not always sufficient — see The approver tag.

Format

elsai_<32-char-random>

Example (do not use): elsai_xZicI0d0wBqFEOZLagi57XwhfJBRI0J8

Lifecycle

Keys are active by default. From the console:

  • Revoke — invalidates the key immediately, on the next request. One-way; there is no un-revoke.
  • Delete — same as revoke, and removes the record. Use revoke unless you need the row gone for compliance.

A revoked key fails everywhere, but not with the same status everywhere:

EndpointRevoked or invalid key
GET /prompts/active-prompt-version (SDK path)404
GET /prompts, GET /skills — listings401
POST publish401

The read endpoint collapses every failure into one indistinguishable 404 so it cannot be used to discover what exists. The others authenticate before resolving anything, so they can say plainly that the key is the problem.

Worth remembering when debugging: a 404 from the read path does not mean the prompt is missing. It may equally be the key.

The approver tag

On-prem only, and it applies to publishing rather than reading.

A write-scoped key can publish — but on a project whose publishing flow is Manual review, the key's owner must also hold the approver tag on that project.

The scope opens the door; the tag decides who walks through. Publishing by API skips the review workflow, never the authority check: someone who cannot approve in the console cannot approve through the API either.

This is why a publish can start returning 403 with nothing changed on the caller's side — the owner lost the tag, or left the project. Treat the key's owner as part of the integration's configuration.

Listing is stricter than reading

One asymmetry worth knowing, because it looks like a bug when you hit it.

OperationRequires
Read one promptkey owner is a member of the project's organisation
List prompts or skillskey owner is a member of that project

Reading one prompt somebody named for you is a narrower thing than enumerating every prompt name across an organisation — and organisations can have thousands of members. So a listing can return 403 where a read of the same project succeeds. If that happens, ask to be added to the project.

SaaS authorization model

On SaaS, the API key is checked at SDK call time:

  1. Validate the key (hash lookup).
  2. Verify the key is active.
  3. Verify the key's user_id is set.
  4. Load the project and the active version.
  5. Apply the environment filter (release check).

If any step fails, the server returns 404 — a single generic "Not found" so attackers can't enumerate which resource is missing.

The key is not bound to a specific organization — the organization context is inferred from the project the SDK requests. Cross-org reads aren't possible because the project itself is org-scoped.

On-prem authorization model

On-prem, the model is membership-based. The key carries identity; access is computed per-call by checking the key owner's current organization membership.

The check flow:

  1. Validate the key (hash lookup).
  2. Verify the key is active.
  3. Look up the project's organization.
  4. Is key.user_id a current member of project.organization_id?
  5. If yes, load the active version and apply the environment filter.
  6. If no — and at any failure above — return 404.

This is a deliberate departure from the SaaS model. Three reasons:

Multi-org users get one key

A user who's an admin of two orgs doesn't need two keys. One key works for every project they have access to via membership.

Access tracks membership

Remove a user from an org → their keys lose access to that org immediately. No separate key-revocation step.

Add a user → access flows

Newly added members can use their existing keys against the new org with no extra setup. Onboarding gets simpler.

Keys carry identity, not authorization

Cleaner separation: the key answers "who," membership answers "what they can reach." Audit logs become easier to reason about.

Example

This traces a read. Publishing adds the scope and tag checks described above.

Joe is a member of orgs Acme and Beta. He creates one API key.

key:                   elsai_xZ...          owner: user_joe        status: active

project Acme-app:      organization: Acme
project Beta-app:      organization: Beta
project Gamma-app:     organization: Gamma  (Joe is NOT a member)

SDK call with Joe's key + project=Acme-app  → membership(joe, Acme) exists  → 200
SDK call with Joe's key + project=Beta-app  → membership(joe, Beta) exists  → 200
SDK call with Joe's key + project=Gamma-app → no membership                  → 404

If an Acme admin removes Joe from Acme, the first call starts returning 404 immediately on the next request.

Copyright © 2026 elsai foundry.