View as Markdown

Credentials

Credentials are the keys your integrations and agents call the API with. There are two kinds: a client_secret is an OAuth client you register here and exchange for tokens with its secret; a public_key is an API key Aleta registers for you; you keep the private key and sign an assertion with it. A key belongs to its owner, a person in the tenant: on v1 it has the owner's role, groups and access points, cut to the key's narrowing and capped by its scopes, and it loses what the owner loses. A key whose owner leaves the tenant, is deleted or suspended stops working, on v2 and v3 too. On v2 and v3 a key owned by an admin sees the whole tenant; a key owned by a user, or narrowed to some modules, sees only what its own access grants give, which is nothing unless Aleta gave it clients. An API key without an owner acts as its own API user, with the access Aleta gave it. Only an admin registers, revokes and gives keys an owner; a key acting as an admin may too. Reading requires the access:read scope, writes require access:write.

Data model

idstring

Stable identifier for the credential. A UUID — treat it as opaque.

namestring

Human label shown in the admin list — name it after the integration.

kindstring

client_secret or public_key.

owner_user_idstring or null

The person whose rights the key has; the key is listed under this user. null for an API key that acts as its own API user until an admin gives it an owner.

created_by_user_idstring

Who registered the key: the admin, or the Aleta staff member for an API key.

scopesarray of string

The scopes the key's tokens carry: the ceiling of what it may call.

narrowingobject or null

What the key may do of what its owner may. null is everything the owner has. modules maps a module to read or read_write; access_points limits the key to the clients Aleta chose for it.

client_idstring or null

client_secret only: the OAuth client id tokens are issued under. null while provisioning is pending.

key_idstring or null

public_key only: the kid you sign assertions with.

issuer, subjectstring or null

public_key only: the iss and sub you put in the assertion you sign. null for a client_secret.

statusstring

pending_provisioning (a client_secret not yet provisioned), active, or revoked.

created_attimestamp

When the credential was registered.

last_used_attimestamp or null

When the credential last authenticated. null if never used.

revoked_attimestamp or null

When the credential was revoked, if it has been.

write_quotaobject

The credential's daily quota of v1 writes, set by Aleta. per_day is the number of writes per UTC day, used_today the writes counted so far today (null if the count cannot be read right now), and resets_at the next 00:00 UTC, when the count starts again. Past the quota a write gets 429 rate_limited with Retry-After.


GET/v1/users/{user_id}/credentials

List a user's credentials

Returns the keys {user_id} owns, of both kinds, revoked ones included, newest first. {user_id} is a person who has or had a grant on the tenant; anything else (a machine user's id, an unknown id) is a 404, for every caller. A non-admin caller may only list their own — passing another user's id is a 403. Admins list anyone's.

The set is small and returned in full: the response keeps the standard v1 list envelope, but next_cursor is always null and limit echoes the row count.

Request
GET/v1/users/{user_id}/credentials
curl https://api.aleta.ai/v1/users/019eb68d-5b44-7491-911a-ac66602675a7/credentials \
  -H "Authorization: Bearer {access_token}"
Response
{
  "data": [
    {
      "id": "019f95e2-4c11-7a02-93d5-6b0e8f1c2a44",
      "name": "Reporting sync",
      "kind": "client_secret",
      "owner_user_id": "019eb68d-5b44-7491-911a-ac66602675a7",
      "created_by_user_id": "019eb68d-5b44-7491-911a-ac66602675a7",
      "scopes": ["reporting:read"],
      "narrowing": null,
      "client_id": "client_01JCT4S9ZD9M9V0Q2W6R8XH3KE",
      "key_id": null,
      "issuer": null,
      "subject": null,
      "status": "active",
      "created_at": "2026-07-24T09:14:02.000000Z",
      "last_used_at": "2026-07-25T06:01:44.000000Z",
      "revoked_at": null,
      "write_quota": {
        "per_day": 10000,
        "used_today": 132,
        "resets_at": "2026-07-26T00:00:00Z"
      }
    }
  ],
  "next_cursor": null,
  "limit": 1
}

POST/v1/users/{user_id}/credentials

Register a credential

Registers a client_secret key owned by {user_id} and returns 201 with the credential plus its secret and token endpoint — the only time either is disclosed. Store the secret immediately; the platform cannot show it again. API keys (public_key) are registered by Aleta.

Registration is an admin action: the caller's role must be admin, on top of the access:write scope, or 403. The owner {user_id} must be a person with a live grant on the tenant, not Aleta staff, or the answer is 404. The caller is the key's created_by_user_id.

Takes dry_run and Idempotency-Key. A dry run registers no OAuth client, so it answers without a client_id and without a secret. A replay of the same key answers the first registration with client_secret: null: the secret is shown once and never stored. Unknown body fields are a 400.

Body parameters

namestring

Human label for the credential. Required.

scopesarray of string

Scopes to grant. Must be a subset of the caller's own scopes — escalation is a 403. Optional; omitting it grants the caller's read scopes.

narrowingobject or null

Optional. {"modules": {"core": "read", "reports": "read_write"}} keeps the key to those modules at those levels of what its owner has; an unknown module or level is a 400. Left out, the key has everything its owner has. A key that is itself narrowed may only register a narrower key: a narrowing is required, with no module above the calling key's level, or the answer is 403.

Extra response fields

client_secretstring or null

The OAuth client secret. Present only here; null on a dry run, on a replay, and when no OAuth client could be made at registration (the key is then pending_provisioning and cannot get tokens).

token_endpointstring

The endpoint to POST a client_credentials grant to when exchanging the secret for access tokens: the /oauth2/token of the sign-in service that issued the secret. Use it as returned.

Request
POST/v1/users/{user_id}/credentials
curl -X POST https://api.aleta.ai/v1/users/019eb68d-5b44-7491-911a-ac66602675a7/credentials \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: {idempotency_key}" \
  -d '{
    "name": "Reporting sync",
    "scopes": ["reporting:read"],
    "narrowing": { "modules": { "core": "read" } }
  }'
Response
{
  "id": "019f95e2-4c11-7a02-93d5-6b0e8f1c2a44",
  "name": "Reporting sync",
  "kind": "client_secret",
  "owner_user_id": "019eb68d-5b44-7491-911a-ac66602675a7",
  "created_by_user_id": "019eb68d-5b44-7491-911a-ac66602675a7",
  "scopes": ["reporting:read"],
  "narrowing": { "modules": { "core": "read" } },
  "client_id": "client_01JCT4S9ZD9M9V0Q2W6R8XH3KE",
  "key_id": null,
  "issuer": null,
  "subject": null,
  "status": "active",
  "created_at": "2026-07-24T09:14:02.000000Z",
  "last_used_at": null,
  "revoked_at": null,
  "write_quota": {
    "per_day": 10000,
    "used_today": 0,
    "resets_at": "2026-07-25T00:00:00Z"
  },
  "client_secret": "sk_live_...",
  "token_endpoint": "https://{auth_domain}/oauth2/token"
}

GET/v1/credentials/{id}

Retrieve a credential

Fetch a single key by id — without its secret, always. Non-admins can only retrieve keys they own; others come back 404. Every id a list returns can be read here.

Request
GET/v1/credentials/{id}
curl https://api.aleta.ai/v1/credentials/019f95e2-4c11-7a02-93d5-6b0e8f1c2a44 \
  -H "Authorization: Bearer {access_token}"

PATCH/v1/credentials/{id}

Give a key its owner

Sets the person whose rights the key has from now on, and answers 200 with the credential (its write_quota.used_today is null here). Use it to give an API key that acts as its own API user an owner, or to move a key to another person. The owner must be a person with a live grant on the tenant, not Aleta staff, or the answer is 400. Admin only (403), and never from a narrowed key (403); a revoked key is 409, an unknown id 404.

A key never gets more by having an owner. The first owner of an API key limited to chosen clients gets those clients as the key's narrowing.access_points, and such a key needs an admin owner (400 otherwise). An API key that is not limited to chosen clients and is not itself an admin key takes no owner (400): it would get its owner's rights, more than it has. Register a new key for the person instead. The key keeps its scopes. Each change is logged in the key's history.

Takes dry_run and Idempotency-Key. Unknown fields are a 400.

Body parameters

owner_user_idstring

The new owner's user id. Required.

Request
PATCH/v1/credentials/{id}
curl -X PATCH https://api.aleta.ai/v1/credentials/019f95e2-4c11-7a02-93d5-6b0e8f1c2a44 \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{ "owner_user_id": "019eb68d-5b44-7491-911a-ac66602675a7" }'

POST/v1/credentials/{id}/revoke

Revoke a credential

Permanently disables a key of either kind: its grant ends, an API key's live tokens are deleted, and a client_secret's OAuth client is disabled. Revocation is one-way; there is no un-revoke. Admin only (403). Returns 204, also when the key was already revoked (no second history entry), and 404 for an unknown id.

The body is optional: {"reason": "..."}, at most 500 characters (400 beyond), is kept with the key. Unknown fields are a 400. Takes dry_run and Idempotency-Key. A dry run leaves the key and its OAuth client active.

Request
POST/v1/credentials/{id}/revoke
curl -X POST https://api.aleta.ai/v1/credentials/019f95e2-4c11-7a02-93d5-6b0e8f1c2a44/revoke \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Rotated after the integration moved." }'