Identity & access — reference

The exhaustive surface for identity, app contexts, the scope model, access profiles, roles, and credentials: every method, its parameters, validation rules, limits, the response envelope, error codes, and an honest Notes & limits section. For concepts see explanation.md; for runnable guides see how-to.md.

For the canonical, always-current request and response field shapes, use the generated API reference (rendered from the OpenAPI specification). This page is the durable map and the honest edges — not a regenerated copy of the raw endpoint schemas.


Client construction

import { VectrosClient } from '@vectros-ai/sdk';
const client = new VectrosClient({ token, environment });
FieldMeaning
tokenThe bearer credential: sk_*, ssk_*, or st_*.
environmentThe API base URL, e.g. https://api.vectros.ai (production) or https://api.staging.vectros.ai (staging).

Identity and access methods live under two sub-clients: client.identity.* (users, orgs, clients) and client.auth.* (contexts, roles, access profiles, scoped keys, token minting, the identity-binding check, and the cross-context principal lookup).

Response envelope

List, lookup, and version-history methods return the standard page envelope:

{ data: T[], nextCursor: string | null }

Drain by feeding nextCursor back as the next call's startFrom until it is null. The identity-binding check (ping) and token minting are not enveloped — they return a plain object.


Identity plane — client.identity.*

Three dimensions — users, orgs, clients — each with the same lifecycle. All are tenant-wide and idempotent on the caller-supplied externalId.

MethodPurpose
createUser / createOrg / createClientCreate (idempotent by externalId).
getUser / getOrg / getClientFetch one by Vectros id.
updateUser / updateOrg / updateClientFull-replace update (PUT) of the body.
deleteUser / deleteOrg / deleteClientDelete by id.
listUsers / listOrgs / listClientsList, filterable; enveloped.
getUserVersions / getOrgVersions / getClientVersionsVersion history; enveloped.
lookup…Exact lookup by external id / declared fields (see the generated reference for per-dimension shapes).

Fields by dimension

FieldUserOrgClientNotes
externalIdYour id; create is idempotent on it. Capped at 256 chars; permissive about characters.
idVectros-assigned UUID; returned on create/get/list.
emailUsers carry email, not name.
nameOrgs/clients carry name, not email.
typeHUMAN (default) or SERVICE.
orgIdA client's owning org.
payloadFree-form Record<string, unknown> attribute bag, round-tripped as-is.
statusACTIVE on create; lifecycle states apply.

Filters. listUsers / listOrgs accept externalId. listClients accepts externalId and orgId. All list methods accept limit and startFrom (cursor).

Idempotency semantics. A second create with an existing externalId returns the existing identity unchanged — the duplicate call's other fields are dropped, it does not update. Use the update method to change an identity.

CLI equivalents

vectros identity create --type <user|org|client> --external-id <id>
                        [--name <n>] [--email <e>] [--service] [--org <orgId>] [--metadata <json>]
vectros identity list   --type <user|org|client> [--external-id <id>] [--limit <n>]
vectros identity get    --type <user|org|client> --id <vectrosId>
vectros identity delete --type <user|org|client> --id <vectrosId>

--name and --email are mutually dimension-specific (--email only for users; --name only for orgs/clients). --service only applies to users; --org only to clients.


App contexts — client.auth.*

The isolation partition. contextId must match ^[a-z][a-z0-9-]{2,30}$ (starts with a lowercase letter; lowercase letters, digits, hyphens; 3–31 chars total).

MethodPurpose
createAppContextCreate (idempotent by contextId). name is required. Root sk_* only.
getAppContextFetch one by contextId.
updateAppContextUpdate name / description (the path supplies contextId; the body's contextId is required by the schema but ignored — it is immutable).
listAppContextsList; enveloped.
deleteAppContextConfirm-gated async cascade — see below. Root sk_* only.

Root-only lifecycle. Creating and deleting an app context require a root sk_* key. A scoped key or token (ssk_* / st_*) cannot create or tear down a context — not even one carrying the wildcard * scope. (Get / update / list are reachable with appropriate scope.)

Delete contract. deleteAppContext({ contextId, confirm }) requires confirm to equal contextId:

  • Without a matching confirm400, and nothing is touched (the rejected delete is a no-op; child roles/profiles still exist).
  • With a matching confirm → accepted (202); the context flips active → purging immediately and drains all of its records, documents, folders, schemas, roles, and profiles in the background, reaching deleted when the drain completes.

Reserved context. Certain context ids are reserved by the platform and cannot be created by a tenant — vectros-admin (backs the hosted admin surfaces) and default (the base context auto-provisioned for every tenant).

Errors. Malformed contextId → 400 with a partner-friendly message. Get on a well-formed but never-created contextId → 404 (not 500). Cross-tenant probes collapse to 404.

CLI equivalents

vectros context create <contextId> [--name <n>]
vectros context list
vectros context get <contextId>

(The CLI exposes create/list/get; context teardown is performed through the API delete with its confirm gate.)


The scope model

Scope grammar

An allowed action is resource:ops[:qualifier]:

  • resource — one of the data-plane resources records, schemas, search, documents, folders, inference (control-plane resources exist for the control surface but are not mintable through the bootstrap gate — see below).
  • ops — any combination of the letters c create, r read, u update, d delete. E.g. records:r, records:cru, documents:crud.
  • qualifier — optional tail that narrows (e.g. records:r:intake_form = read only the intake_form record type). It never widens.

What does NOT grant access (author the letter form instead):

FormEffect at runtime
records:r, search:r, …✓ Grants the named operations.
* (the single literal wildcard)✓ Grants everything — the shape root keys carry. Reserve it.
A coarse verb (read, write, delete)✗ Grants nothing.
resource:* (operations-wildcard)✗ Grants nothing at runtime. Author explicit c/r/u/d letters.

The operations-wildcard resource:* is accepted by the bootstrap scope gate (it stays within the data plane), but it does not grant access at the runtime enforcement layer. Always author the explicit letter form. The only wildcard that grants anything is the bare literal *.

dataScope

A map from an ownership field to the list of allowed values:

{ "dataScope": { "clientId": ["client_abc", null] } }
FieldMeaning
userIdConfine to rows owned by the listed users.
orgIdConfine to rows owned by the listed orgs.
clientIdConfine to rows owned by the listed clients.
  • Enforced as a server-side filter below any caller-supplied filter; cannot be widened by the caller.
  • Multiple values in one field's list → union (OR). Multiple fields → intersection (AND).
  • Strict by default. A scoped credential must include the matching filter on every list and search call, or the request is rejected (e.g. "clientId is required by token scope").
  • null sentinel. Include JSON null in a field's value list to additively grant access to tenant-level rows (no value for that field). Opt-in only — never implicit.

The bootstrap scope gate (data-plane allowlist)

The CLI / blueprint bootstrap flow mints scoped keys only for the data plane. The allowlist is exactly:

records, schemas, search, documents, folders, inference

Any other resource — the control plane keys, profiles, app-contexts, users, billing, admin, clients, orgs, or any unrecognized resource — and the literal * are hard-rejected: the bootstrap mints nothing and exits non-zero. There is no override flag; control-plane scoped keys are created deliberately in the developer portal.


Access profiles — client.auth.*

The per-principal, per-context permission binding.

MethodPurpose
createAccessProfileCreate/bind (idempotent by (context, principalId)).
getAccessProfileFetch one by (contextId, principalId).
updateAccessProfileUpdate scopes/role/status/overrides.
deleteAccessProfileRemove the binding.
listAccessProfilesList a context's profiles; enveloped.
listProfilesForPrincipalCross-context: every context a principal is bound to; enveloped.

Profile fields

FieldMeaning
principalIdThe bound principal: usr_<userId> or key_<keyId>. Must start with usr_ or key_; structural characters like : are rejected with 400.
scopesInline clauses, each { allowed_actions: string[] } (snake_case on the wire). XOR with roleId.
roleIdReference to a reusable role. XOR with scopes.
statusactive or suspended. Suspending denies access without deletion.
identityOverridesOwnership values stamped onto what the principal touches. Accepts orgId and clientId only (each { value: "<id>" }). userId and the tenant id are sacred and rejected with 400.

XOR enforcement. A profile carries exactly one of inline scopes or a roleId. Updating to set one clears the other (empty-string / empty-array sentinels). On create, the unset half is absent or an empty sentinel.

Idempotency. A repeat create for an existing (context, principalId) returns the existing profile unchanged.

Cross-context lookup. listProfilesForPrincipal({ principalId }) returns every profile for that principal across all contexts. A principal with no profiles returns an empty array (200), not 404. A malformed principalId (e.g. containing :) → 400.

CLI equivalents (vectros access)

vectros access grant  --principal <usr_|key_> --context <c> (--role <r> | --actions <csv>)
vectros access revoke --principal <usr_|key_> --context <c>
vectros access list   (--context <c> | --principal <usr_|key_>)
vectros access get    --principal <usr_|key_> --context <c>

--role and --actions are mutually exclusive (exactly one). --actions mints a single-clause inline profile. access list requires exactly one of --context (a context's members) or --principal (a principal's contexts).


Roles — client.auth.*

Reusable, context-scoped, identity-agnostic permission shapes.

MethodPurpose
createRoleCreate (idempotent by roleId).
getRoleFetch by (contextId, roleId).
updateRoleUpdate name / scopes.
deleteRoleDelete — blocked with 409 while a profile still references the role.
listRolesList a context's roles; enveloped.

Role fields

FieldMeaning
roleIdThe stable role handle within the context.
nameHuman-readable name.
descriptionOptional.
scopesOne or more clauses, each { allowed_actions: string[] }. Roles may be multi-clauseany clause that matches grants access.

Roles support an ownership placeholder ${{ self.* }} (resolves to the acting principal at runtime) and a null data-scope sentinel (additively grants tenant-level/owner-less records). These richer forms are authored through blueprints.

Referential integrity. Deleting a role referenced by a profile is rejected with 409 — remove or re-point the profile first. Deleting an unreferenced role succeeds; the platform does not cascade.

CLI equivalents (vectros role)

vectros role create --context <c> --role-id <id> --name <n> --actions <csv> [--description <d>]
vectros role list   --context <c>
vectros role get    --context <c> --role-id <id>
vectros role delete --context <c> --role-id <id>

The CLI role create authors single-clause roles from --actions.


Credentials

Types

PrefixLifetimeScopeUse
sk_live_* / sk_test_*Permanent (revoke to retire)Wildcard within its tenantServer-to-server from your own backend.
ssk_live_* / ssk_test_*Permanent (revoke to retire)Bound to a profile; identity-bearingAgents, bots, long-running workers; audit attribution.
st_*1h default / 24h maxEmbedded in the tokenFront-end-safe per-session credentials.

The raw secret of an sk_*/ssk_* is shown once at creation and never re-readable; the platform stores only a hash.

Scoped key lifecycle — client.auth.*

MethodPurpose
createScopedKeyMint an ssk_* for a principal that already has a profile in the context; raw secret returned once.
getScopedKeyMetadata for one key (no secret).
revokeScopedKeySoft-delete; stops working within ~5 minutes (authorizer cache).
listScopedKeysThe tenant's keys; enveloped (single page — no cursor input on this endpoint).

createScopedKey fields: keyName, tenantId, contextId, userId (the bare principal user id), optional label. A re-issue of an existing (tenant, context, principal, keyName) tuple returns the key without the secret (the platform never re-discloses) — rotate to get a fresh secret.

CLI equivalents (vectros key)

vectros key issue  --principal <p> --context <c> [--name <n>] [--label <l>] [--format human|raw|env|json]
vectros key list   [--principal <p>] [--context <c>]
vectros key get    <keyId>
vectros key revoke <keyId>
vectros key rotate --principal <p> --context <c> [--name <n>] [--format …]

key rotate has no dedicated endpoint — it revokes the matching active key and mints a fresh one. There is no in-place rotation.

Token minting — client.auth.mintToken

const { token, expiresAt } = await client.auth.mintToken({
  scope: { allowedActions: string[], dataScope?: {...}, identity?: {...} },
  userId?: string,                 // mint on behalf of a real user in the tenant
  expiresInSeconds?: number,       // default 3600, max 86400
});
FieldMeaning
scope.allowedActionsRequired; array of resource:ops[:qualifier] strings. Malformed entries → 400 at mint.
scope.dataScopeOptional; the data the token may read/write.
scope.identityOptional; ownership values stamped onto resources the token creates.
userIdOptional; must reference a real user in the caller's tenant (unknown id → 400 naming the field).
expiresInSecondsOptional; default 3600 (1h), capped at 86400 (24h).

Returns { token: "st_…", expiresAt: <unix-seconds> }. Tokens cannot be revoked in flight — expiry is the only lever; mint short.

Identity-binding check — client.auth.ping

Returns the authenticated principal's identity: status, tenantId, environment, principalType (root_key | scoped_key | token), and principalKeyId. For a scoped_key, allowedActions is present (and a dataScope if bound to a user/org). For a token, tokenExpiresAt is present. An invalid credential is denied at the edge with 403.


Error codes

CodeWhen
400Malformed contextId / principalId / scope token; userId/ownership id not a real row in the tenant; identityOverrides.userId (sacred field); context delete without a matching confirm.
403Invalid or unauthorized credential; a scoped action the credential's scope does not permit. Messages are uniform on purpose — they do not reveal which check failed.
404Get on a non-existent (or cross-tenant) context/identity; list/create under a non-existent parent context. Cross-tenant probes collapse to 404.
409Delete a role still referenced by a profile.

A scoped credential lacking permission for a list/search filter required by its dataScope is rejected (strict scope) with a message naming the required field.


Notes & limits

What this surface does not do, stated plainly:

  • No identity PATCH. Users, orgs, and clients update via full-replace PUT only — there is no partial-update on the identity plane.
  • No schema PATCH on the identity plane and no reparenting of identities — orgs/clients are referenced by id, not moved through a hierarchy.
  • Single scope clause on mintToken. The token-mint endpoint serializes one (allowedActions, dataScope) clause per request. Multi-clause shapes are expressed through a role (referenced by a profile) or a blueprint, not minted directly as a single multi-clause token.
  • Single-clause access-profile create. The profile-create API accepts one inline clause; reach for a multi-clause role for compound shapes.
  • identityOverrides is orgId / clientId only. userId and the tenant id are sacred and rejected — a profile cannot forge another user's identity.
  • resource:* grants nothing at runtime. Author explicit c/r/u/d letters. Only the bare literal * grants everything (and that is the root-key shape).
  • s is an advanced op letter, beyond c/r/u/d. A fifth op, s, grants reveal of a type's sensitive fields (e.g. customer:rs = read + reveal-sensitive). It is a per-type capability, not part of the standard CRUD set — the blueprint/bootstrap scope gate accepts only c/r/u/d, so packs are authored with those; s is minted deliberately where sensitive-field reveal is intended.
  • No in-place key rotation. Rotation is revoke-then-reissue; a key's raw secret is shown once and never re-readable.
  • Revocation is not instant. A revoked sk_*/ssk_* keeps working until the edge authorizer cache expires — up to about five minutes. st_* tokens cannot be revoked at all; they expire on their lifetime. Plan offboarding with this window in mind.
  • listScopedKeys is a single page — the endpoint takes no cursor input; filter client-side by context/principal.
  • Cross-tenant existence is unobservable. Probing for another tenant's id returns the same uniform 404 as a non-existent id; scope-mismatch failures return the same generic shape.

Where to go next

  • explanation.md — the concepts: contexts as the isolation moat, the identity plane, the scope model, and the three credential types.
  • how-to.md — runnable guides for every method above.
  • The generated API reference (rendered from the OpenAPI specification) — canonical, always-current request/response field shapes.
  • The blueprint walkthroughs — end-to-end builds that wire contexts, profiles, roles, and scoped keys together.