Ulsav Docs

API Keys

Issue, scope, rotate, and revoke Organization machine credentials, and understand what a key can never do.

An API key is an Organization-owned machine credential. It is not a personal access token: once issued, its authority is independent of the person who created it, so a Role change or a departure neither widens nor narrows the integration.

  • Prefix

    ulsav_sk_

  • Scope

    Exactly one

    It covers that subtree's descendants

  • Secret

    Shown once

    Later reads return the prefix only

  • Authority

    Frozen at issue

    Independent of the creator

  • Managed by

    A session, never a key

  • Expiry

    Optional

    High-risk keys should carry one

Create a key

Create keys in the organizer webapp under Settings → API Keys, or call the operation below with a signed-in session.

ActionCapability
Create, edit, or rollapi_keys.create
Read metadataapi_keys.read
Revokeapi_keys.revoke

Every action requires a session-authenticated human, and an API key can never manage another.

A creation request carries:

FieldMeaning
nameA label for the integration, up to 80 characters
capabilitiesThe exact actions the key may perform
scopeType / scopeIdThe one resource subtree the key applies to; defaults to the Organization
expiresAtOptional expiry; high-risk keys should carry one
allowedOriginsOptional browser-origin allowlist for non-credentialed CORS
POST/api/v1/organizations/{organization_id}/api-keys

Issue an Organization API key and return its secret once.

Auth signed-in AccountCapability api_keys.create
POST /api/v1/organizations/{organization_id}/api-keys
Content-Type: application/json

{
  "name": "Order sync",
  "capabilities": ["orders.read", "registrations.read"],
  "scopeType": "Organization",
  "expiresAt": "2027-11-06T00:00:00Z",
  "allowedOrigins": ["https://app.example.com"]
}

The response contains the key metadata and the secret, shown exactly once:

{
  "apiKey": {
    "id": "019ae4b0-...",
    "name": "Order sync",
    "prefix": "ulsav_sk_...",
    "capabilities": ["orders.read", "registrations.read"],
    "scopeType": "Organization",
    "status": "active"
  },
  "secret": "ulsav_sk_..."
}

The apiKey object also carries organizationId, scopeId, allowedOrigins, createdAt, and updatedAt. Store the secret in your secret manager before leaving the page. Later reads return metadata and the prefix only; there is no "show secret again" operation.

What the key can do

Authority is fixed at issuance and is deliberately narrow:

  • It is a subset of the issuer. Selected capabilities and Scope must fit inside the creating member's effective authority at that Scope.
  • It does not follow the issuer. If the creator later loses a Role or leaves the Organization, the key keeps its frozen authority until a member with api_keys.create or api_keys.revoke in the Organization acts on it.
  • It has exactly one Scope. That Scope covers its descendants through the resource hierarchy, so a key scoped to an EventGroup can act on that group's Events, but not on a sibling Event.
  • It is allowed per operation. Key access is a default-deny decision made operation by operation. An endpoint used by the browser session is not automatically available to keys.

An operation that does not declare API-key policy rejects a key with 403 api_key_forbidden, even if the key names a similarly named capability. An operation that does declare policy checks three things in order:

  1. The Organization in the path must equal the key's Organization.
  2. The capability must match exactly.
  3. The key's Scope must be the target or one of its ancestors.

Failures are 403 api_key_forbidden or 403 api_key_scope_denied.

What a key can never do

The api_keys.* capability family is human-only and is never valid on a key. A key cannot create, list, read, edit, roll, or revoke any key, including itself. This is enforced at the HTTP layer as well as at issuance, so an operation mistakenly configured to accept keys for key management still rejects them.

Organization suspension or platform suspension overrides every key: requests fail with organization_suspended or platform_suspended until the suspension is lifted.

Rotate and revoke

POST/api/v1/organizations/{organization_id}/api-keys/{api_key_id}/roll

Replace the secret while keeping the key's id, capabilities, and Scope.

Auth signed-in AccountCapability api_keys.create
POST /api/v1/organizations/{organization_id}/api-keys/{api_key_id}/roll

Rolling and revoking answer different questions:

Roll

  • Keeps the key's id, capabilities, and Scope
  • Returns the new secret once
  • Stops the previous secret immediately

Revoke

  • Permanent; the key's status becomes revoked
  • Keeps the metadata, creator, and audit trail useful

Unlike webhook signing secrets, a roll has no grace window, so schedule the cutover and restart workers that cache the old value. Revoke rather than delete.

POST/api/v1/organizations/{organization_id}/api-keys/{api_key_id}/revoke

Permanently disable a key and keep its audit trail.

Auth signed-in AccountCapability api_keys.revoke
POST /api/v1/organizations/{organization_id}/api-keys/{api_key_id}/revoke

A revoked key and an expired key both fail authentication with 401; their statuses read revoked and expired.

One key per integration and environment

Separate keys make rotation boring, keep last-used metadata meaningful, and bound the blast radius of a leak. Do not share one key between staging and production, or between two services that only happen to be written by the same team.

Questions

Can an API key manage other API keys?

No. The api_keys.* family is human-only, so a key cannot create, list, read, edit, roll, or revoke any key, including itself.

Does rolling a key change its id, capabilities, or Scope?

No. Rolling keeps the key's id, capabilities, and Scope, and replaces only the secret.

Is there a grace window after I roll a secret?

No. The previous secret stops working immediately, unlike webhook signing secrets, so schedule the cutover and restart any worker that caches the old value.

What happens to a key when its creator leaves the Organization?

Nothing. Authority is frozen at issue, so the key keeps working until a member with api_keys.create or api_keys.revoke in the Organization edits or revokes it.

Should I revoke a key or delete it?

Revoke it. The metadata, creator, and audit trail stay useful, and the key's status becomes revoked.

On this page