Skip to content
Anlyon
Esc
↑↓navigate↵open⌘Jpreview
On this page

Authentication & API Keys

Create API keys, bind each one to an environment, and grant the scopes a caller needs.

Your code authenticates to the Anlyon API with an API key. This page covers creating keys, scoping them and checking them.

Overview

The API endpoints in this documentation take an API key unless a page says otherwise. MCP clients sign in with OAuth, and the console uses a session. A key carries two things that decide what it can reach:

  • One environment. A key belongs to exactly one environment and resolves it on authentication. A staging key cannot read or write production data, because the environment is bound to the credential itself. This is logical isolation: rows in shared storage, filtered by an enforced column. It is not dedicated storage or compute. See Environments.
  • A set of scopes. Scopes decide which operations the key may perform, from actions:read to approvals:decide. See Scopes below.

Neither is something you remember to do correctly. A key that is missing a scope gets a 403 with the scope named, and a key pointed at the wrong environment simply does not find the data.

API Key Format

API keys follow this format:

anlyon_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • Prefix: every key starts with anlyon_live_. The prefix does not name an environment. A staging key carries it too.
  • Key: 32 random characters

Getting Your API Key

From the Dashboard

Log in

Log in to your Anlyon account

Navigate to Settings

Navigate to Settings → API Keys

Create Key

Click Create API key and enter a descriptive name (for example “Support agent, staging”)

Choose the environment

Pick the environment this key belongs to. It cannot be changed later, and the key will never reach any other environment.

Choose the permissions

Standard is the default and is what an agent needs for ordinary work. Full access adds every privileged scope: secrets, approval decisions, budgets, policies, impact limits, halting the environment, changing an approval gate and resolving unknown outcomes. Grant it rarely. Read only is a good fit for observability. You can also pick individual scopes.

Set an expiry (optional)

Optionally set an expiration date

Copy Key

Copy the key when it is shown. It is shown once.

Using API Keys

HTTP Header

Include your API key in the Authorization header using the Bearer scheme:

curl -X POST https://api.anlyon.com/api/v2/actions/update-status-file/invoke \
  -H "Authorization: Bearer anlyon_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "input": { "path": "notes/status.txt", "content": "status: green\n", "message": "Update status" } }'

The example invokes the action the quickstart defines.

Code Examples

const response = await fetch('https://api.anlyon.com/api/v2/actions/update-status-file/invoke', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    input: { path: 'notes/status.txt', content: 'status: green\n', message: 'Update status' },
  }),
});

const { data } = await response.json();
console.log(data.status, data.grade);
import requests

headers = {
    'Authorization': f'Bearer {api_key}',
    'Content-Type': 'application/json'
}

response = requests.post(
    'https://api.anlyon.com/api/v2/actions/update-status-file/invoke',
    headers=headers,
    json={
        'input': {'path': 'notes/status.txt', 'content': 'status: green\n', 'message': 'Update status'}
    }
)

data = response.json()['data']
print(data['status'], data.get('grade'))
const axios = require('axios');

const response = await axios.post(
  'https://api.anlyon.com/api/v2/actions/update-status-file/invoke',
  {
    input: { path: 'notes/status.txt', content: 'status: green\n', message: 'Update status' }
  },
  {
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    }
  }
);

Security Best Practices

Store Keys Securely

Never commit API keys to version control!

Use environment variables:

# .env file (add to .gitignore)
ANLYON_API_KEY=anlyon_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
// In your code
const apiKey = process.env.ANLYON_API_KEY;
One Key per Environment

You do not have to remember this one. It is enforced. Every key belongs to exactly one environment, chosen when you create it. A key cannot reach another environment’s actions, secrets, invocations, approvals, events, agents, runs or spans, or any other data scoped to it.

So a staging key running against production credentials does not quietly write to production. It finds nothing there. Create one key per environment per service, and name them so the environment is obvious.

Rotate Keys Regularly
  • Set expiration dates on API keys
  • Rotate keys every 90 days
  • Revoke unused or compromised keys
Use Least Privilege
  • Give each key only the scopes it uses. An agent that never touches the vault should not hold secrets:read.
  • Keep secrets:*, approvals:decide, budgets:write, actions:govern, actions:resolve, policies:write and environments:halt on separate operator keys. The agent must not be able to rewrite the policy that governs it. None of them is agent work, and none is granted by default.
  • Create separate keys for different services, so revoking one does not stop the others.
  • Use descriptive names, and monitor key usage in the dashboard.

API Key Management

List API Keys

Get all API keys for your workspace:

curl https://api.anlyon.com/api/v2/workspaces/{workspaceId}/api-keys \
  -H "Cookie: better-auth.session_token=YOUR_SESSION_TOKEN"
`data` (array)
`id` (string)
API key ID
`name` (string)
Descriptive name
`keyPrefix` (string)
Key prefix (full key only shown once)
`isActive` (boolean)
Whether the key is active
`createdAt` (string)
Creation timestamp
`expiresAt` (string | null)
Expiration timestamp
`lastUsedAt` (string | null)
Last usage timestamp

Delete an API key

Delete a key. It stops working and cannot be restored:

curl -X DELETE https://api.anlyon.com/api/v2/workspaces/{workspaceId}/api-keys/{keyId} \
  -H "Cookie: better-auth.session_token=YOUR_SESSION_TOKEN"

Scopes

A scope is a resource:action pair, and a key holds a fixed set of them. They are enforced on every request: a call the key has no scope for is refused with 403 and the missing scope named, whatever the key is otherwise allowed to do.

Scopes are chosen when the key is created and cannot be changed afterwards. To widen or narrow a key, create a new one and retire the old one. That is deliberate, so a key’s authority cannot drift after you have reviewed it.

Presets

Preset What it holds Use it for
Standard Every current scope except the privileged ones below. That includes reading, defining and invoking actions, requesting approvals, runs, events and reading impact limits Agent keys. This is the default.
Read only Every current :read scope, including the privileged read scopes Dashboards, monitoring, exports
Full access Every current scope, including the privileged ones below, except integrations:read, integrations:write and messages:ingest Rare. Prefer a Standard key plus a separate operator key.

No console preset grants a scope for an earlier namespace, such as messages:* or memory:*. A key created through the API with no scopes named receives every scope that is not privileged, those included.

Privileged scopes

These are not granted by default. None of them is a way to use Anlyon. Each one either reads credentials or changes what Anlyon will stop you doing.

An OAuth consent screen offers four of them as separate groups that are off unless you turn them on: approval decisions, notification channels, secrets and budgets. It does not offer actions:govern, actions:resolve, environments:halt, policies:read, policies:write or impact-limits:write.

Scope What it allows
secrets:read, secrets:write Manage vault secrets, and reference them from an action definition
approvals:decide Approve or deny pending approvals without a human clicking approve
budgets:read, budgets:write Read and change a key’s monthly caps on Anlyon operations. A budget does not cap model tokens or the money an action moves
integrations:read, integrations:write Connect and change Slack and Discord channels, which redirect the workspace’s alerts
notifications:manage Change the workspace’s notification settings: which channels and alert types are on, which email addresses and which webhook URL receive alerts. Also send a test webhook. A console session or an OAuth connection also needs the admin role. An ordinary credential must not be able to redirect an alert
actions:govern Change whether an action requires approval, and change or remove what a governed action declares
actions:resolve Record the confirmed outcome of an invocation whose outcome is unknown. The key that made the call cannot resolve it. See Receipts and grades
policies:read, policies:write Read, create, change, disable and delete the approval policies for the key’s own environment. See Policy as code
environments:halt Halt and resume the key’s own environment
messages:ingest Keep publishing messages while the environment is halted
impact-limits:write Create, change and archive the shared impact limits for the key’s own environment. An agent must not be able to raise the cap that bounds it

Every scope

messages:read messages:write schedules:read schedules:write queues:read queues:write workflows:read workflows:write integrations:read integrations:write logs:read dlq:read dlq:write flow-control:read flow-control:write url-groups:read url-groups:write billing:read notifications:read feedback:write analytics:read memory:read memory:write events:read events:write files:read files:write cache:read cache:write sessions:read sessions:write context:read secrets:read secrets:write actions:read actions:write actions:invoke approvals:read approvals:write approvals:decide budgets:read budgets:write runs:read runs:write actions:govern actions:resolve environments:halt messages:ingest policies:read policies:write impact-limits:read impact-limits:write notifications:write notifications:manage

Check the key you were given

GET /api/v2/auth/identity (client.auth.identity() in both SDKs) returns only the caller’s own credential: type, key id (API keys only), workspace and environment, effective scopes and expiry. It needs no scope beyond being authenticated, is never cached, and returns no secret, hash or other key. Call it at startup to fail fast if the key is missing a scope you need, or holds one you should not. Holding a scope does not prove a call will run: policies, budgets, environment halt and quotas still apply at request time.

Key Expiration

API keys can have optional expiration dates. When a key expires:

  • Requests using that key return 401
  • You’ll need to create a new key
  • Expired keys cannot be reactivated

Set expiration when creating a key:

{
  "name": "Temporary Integration",
  "expiresAt": "2027-12-31T23:59:59Z"
}

Error Responses

A key that does not authenticate

An invalid, expired or revoked key gets the same answer, HTTP 401. The response does not say which of the three it was.

{
  "success": false,
  "error": { "code": "AUTHENTICATION_ERROR", "message": "Authentication failed" },
  "requestId": "..."
}

A key that lacks a scope

HTTP 403, with the scope named.

{
  "success": false,
  "error": { "code": "AUTHORIZATION_ERROR", "message": "API key is missing required scope: actions:invoke" },
  "requestId": "..."
}

Troubleshooting

Authentication failed (401)
  1. Verify the key is copied correctly (no extra spaces)
  2. Check that you’re using the Bearer prefix
  3. Check in the dashboard that the key has not expired or been revoked
  4. Verify you’re using a key for the right workspace and environment
Missing required scope Error

The key authenticated fine but is not allowed to perform this operation. The message names the scope it needs, e.g. API key is missing required scope: actions:invoke.

  1. Check the key’s scopes in Settings → API Keys
  2. If the key should have it, create a new key with that scope. Scopes are fixed at creation
  3. If it should not, the caller is doing something the key was not meant to do
A resource you expected is missing

Most often the key belongs to a different environment than the data. A key resolves its environment on authentication, so it sees that environment and no other. A missing resource usually means “not in this environment” rather than “deleted”.

A key that expired or was revoked

The API answers 401 with Authentication failed in both cases.

  1. Check the expiration date and status in the dashboard
  2. Create a new API key
  3. Update your application with the new key

Check an agent key at startup

GET /api/v2/auth/identity reports the caller’s credential and scopes without returning a secret. Both SDKs also provide a startup check:

await client.auth.requireScopes(
  ['actions:invoke', 'runs:write'],
  { forbidden: ['policies:write', 'approvals:decide'] },
);
client.auth.require_scopes(
    ['actions:invoke', 'runs:write'],
    forbidden=['policies:write', 'approvals:decide'],
)
# With the async client, await client.auth.require_scopes(...).

Choose required scopes from the operations your application actually performs. A ScopeCheckError exposes missing and forbidden lists. The helper inspects permission. It does not grant scopes or change a key. Malformed identity responses fail the check. Session credentials use workspace roles instead of a scope list and cannot satisfy a nonempty required-scope list.

This is an early configuration check, not permission to bypass policies, budgets, quotas, environment boundaries or a later key revocation. Keep governance credentials separate from runtime agent credentials.

Next Steps

  • Controls: Approvals, environments, kill switch, rollback
  • Approval gates: Gate dangerous calls on human approval
  • Actions: Define a production call once and invoke it by name
  • Error Handling: Understand API errors and rate limits

Was this page helpful?