Guides

Service Accounts

Bot identities for agents, automations, and CI jobs that need to call MCP without a human user.

A service account is a bot identity inside an organization. Use one when you want an AI agent, automation, or long-running script to call airlock without being tied to a specific human user, for example a CI job or a background agent.

Service accounts authenticate with service tokens, not OAuth. They are granted access to specific integrations, skills, and agents the same way a user or space is.

When to use a service account

  • A scheduled job that needs to call MCP tools on its own schedule.
  • A self-hosted agent that should keep working when no human is signed in.
  • A CI/CD pipeline that calls airlock from a build runner.
  • An agent runtime that needs persistent, non-interactive access.

If a real person is driving the calls, use their normal user sign-in instead.

Creating a service account

Service accounts are an admin-only feature. The Control Room calls them Agent IDs: open Identities from the admin section of the sidebar and choose its Agent IDs tab (or go directly to /identities?tab=agent-ids).

  1. Click Create service account, which opens a separate page
  2. Give it a descriptive name (e.g. nightly-research-bot)
  3. Optionally add a description so other admins know what it's for
  4. Click Create service account

The new service account is created in ACTIVE status, scoped to your organization. An organization can hold at most 25 service accounts. It has no credentials yet (you mint those separately in the next step) and, importantly, no access to anything until you grant it some (see Access grants).

Issuing a service token

Service tokens are the credentials a service account uses to call MCP. They look like this:

alk_svc_<random>

The alk_svc_ prefix lets you recognise an airlock service token in logs and dashboards.

  1. Open the service account's detail page
  2. Enter a token name (e.g. github-actions)
  3. Click Create token
  4. Copy the token immediately, because airlock only ever shows the full value once

Note: The Control Room always issues tokens with the default 1-year lifetime. To set a custom lifetime (anywhere between 1 hour and 1 year), create the token via the API and pass expiresInSeconds. There is no lifetime picker in the UI.

After that, only the prefix (alk_svc_XXXXXXXX) is visible in the Control Room. The full token is hashed with SHA-256 before storage; even airlock admins can't recover it.

Expired tokens are cleaned up automatically (DynamoDB TTL); revoked tokens stay visible in the audit trail but stop authenticating immediately.

Org-wide vs service-account tokens

There are two flavours of service token:

Org-wide tokens default to full admin access. Unlike a service-account token, which starts with access to nothing, a token created at POST /v1/orgs/:orgSlug/service-tokens with no accessRole gets every integration and every tool, unfiltered. Pass accessRole: "MEMBER" to scope it down, or better, use a service-account token instead.

FlavourEndpointWhen to use
Service-account tokenPOST /v1/orgs/:orgSlug/service-accounts/:id/tokensThe typical case. The token is scoped to a specific bot identity, inherits its access grants, and shows up under that account in audit logs.
Org-wide service tokenPOST /v1/orgs/:orgSlug/service-tokensA token not tied to any service account. It defaults to full organization-admin access: every integration and every tool, with no access filtering. Pass accessRole: "MEMBER" on creation to scope it to ordinary member permissions instead. Use it only for one-off scripts or migrations that genuinely need org-wide reach.

Prefer service-account tokens whenever possible; they make audit trails and access scoping much cleaner.

Using a service token

Store the token in an environment variable (or your platform's secret manager) and pass it as a bearer token on the MCP HTTP endpoint:

export AIRLOCK_SERVICE_TOKEN="<your alk_svc_ token>"
 
curl https://mcp.air-lock.ai/org/<your-org-slug> \
  -H "Authorization: Bearer $AIRLOCK_SERVICE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'

The same token works for every MCP method the service account is authorised to use: initialize, tools/list, tools/call, and the airlock meta-tools.

Access grants

Service accounts are granted access to specific resources (integrations, skills, and agents) rather than to a bundle.

A service account starts with no access at all. Unlike human members, it never inherits an integration's open default. It still sees airlock's meta-tools, but list_services and search_tools come back empty until you grant it something.

In the Control Room, open the service account's detail page and use the Access section to grant it the integrations, skills, and agents it needs. For an integration you can grant every tool or select individual tools. Grants take effect on the service account's next tools/list call.

Attribution: Tracking Who Triggered Agent Runs

When an agent authenticates with a service account token, the caller can assert which human triggered the run, so agent activity is attributable to a person rather than only the bot identity.

Why this matters

A service token (alk_svc_...) is shared by many people: the agent runtime might be called by Alice's workflow one minute and Bob's the next. The X-Airlock-On-Behalf-Of header lets the caller assert who is behind each request, so approval screens, audit logs, and agent history show the name of the person who triggered it rather than just the service account.

How to use it

Pass the human's identifier in the X-Airlock-On-Behalf-Of HTTP header when calling MCP:

curl https://mcp.air-lock.ai/org/<your-org-slug> \
  -H "Authorization: Bearer $AIRLOCK_SERVICE_TOKEN" \
  -H "X-Airlock-On-Behalf-Of: alice@example.com" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"tools/call","params":{...},"id":1}'

The identifier can be an email address or a user profile id. airlock resolves it best-effort against members of your organization and persists both the raw claim and the resolution result.

Protocol-native fallback: If you cannot set custom HTTP headers, you can pass the claim in the request body under params._meta.onBehalfOf instead. The header takes precedence when both are present.

Security model

  • Service-token auth only. This header is accepted only when authenticating with a service account token (alk_svc_...). Any other authentication mode (user bearer tokens, personal tokens) ignores it completely, so regular user requests behave byte-for-byte as they always have.
  • Caller-asserted, not verified. The claim is spoofable by design: anyone holding the service token can assert any name, including a colleague's. airlock resolves it against org members and displays it in the UI, but treats it as attribution metadata only. It never influences policy evaluation, tool access, approval routing, or replay identity; those all still use the service account's own identity.
  • Always labeled. Every surface in the Control Room renders the claim with a "(caller-asserted)" marker, so an approver or auditor knows it came from the agent runtime, not from airlock's authentication layer.

From an ElevenLabs voice agent

The header is not the only source of the claim. An agent pushed to ElevenLabs with Name the caller switched on sends the conversation's user_email variable as the claim on every tool call, so a voice agent's calls and approval requests are attributed the same way, with the same label and the same limits.

Input validation

The claim is trimmed and capped at 128 characters, following the same hygiene as the correlation header. Display-control characters (bidi overrides, zero-width spaces) are stripped, so a crafted claim cannot visually obscure the "caller-asserted" marker.

Where it appears

When you set this header, airlock shows the claim wherever the service account identity appears:

  • Agent history (/history): the Actor line shows agentX (service account) · on behalf of alice@example.com (caller-asserted)
  • Pending request details (/pending-requests): the expanded row and the full request page both show an "On behalf of … (caller-asserted)" line
  • The request's timeline: the "Requested by" entry ends with "on behalf of … (caller-asserted)"
  • Slack and Microsoft Teams approval cards: an On behalf of line, with the same "(caller-asserted)" marker
  • Audit logs: request-created events and tool-call events carry the claim in their metadata

When the claim resolves to a member, that member can also see the requests made on their behalf, and follow them through to a decision. Seeing is all it gives them: deciding a request still takes an admin or an assignment as approver, like any other request.

If the claim resolves to a member of your organization (by email or profile id), airlock persists the resolution and shows the member's email. If it doesn't resolve, perhaps because of a typo, a member from a different org, or someone not yet in airlock, the raw claim is still stored and displayed.

Credentials for target APIs

A service token authenticates the bot to airlock. It says nothing about the APIs behind your integrations: the service account needs its own credentials for those, exactly like a human user does.

On the service account's detail page, the Credentials section lists every integration you granted it in the Access section:

  • For OAuth integrations, click Connect with OAuth and complete the flow.
  • For API-key or bearer integrations, paste the token and save.
  • Rows marked No credentials required are internal airlock surfaces.

Credentials are stored with the same KMS envelope encryption as user credentials, and only apply to integrations the service account has been granted.

Disabling and revoking

You have two levers, depending on how surgical you want to be:

  • Revoke a single token: invalidates that one credential immediately. Other tokens on the same service account keep working.
  • Disable the service account: flips its status to DISABLED. All tokens on the account stop working at once, but the account itself (and its access grants) is preserved so you can re-enable it later.

Disabled accounts can be re-enabled from the same screen; revoked tokens cannot, so you'd issue a new one.

Deleting a service account

Deleting a service account revokes its tokens and removes its per-integration credentials, policies and access grants. It cannot be undone. It is refused while any agent deployment still runs as the account, and the refusal names those deployments: delete them first.

Service accounts and pushed agents

When airlock pushes an agent to ElevenLabs or Anthropic Managed Agents, the deployment runs as a service account you pick. The push mints a token bound to that deployment and that account, and delivers it straight to the platform: it is never shown to you, in the Control Room or anywhere else, and it stops working when the deployment is suspended or deleted. You do not issue a token for a pushed agent yourself. Grant the account what the agent needs to reach. See Portable Agents.

Managing service accounts from an agent

The airlock-management integration carries four admin-only tools, so an agent connected as an admin can manage service accounts without the Control Room:

ToolWhat it does
list_service_accountsLists the organization's service accounts and their ids
create_service_accountCreates one from a name and an optional description. It creates no token
update_service_accountChanges the name, description, status (ACTIVE or DISABLED) or IP allowlist
delete_service_accountDeletes one, under the same rules as above

Under the default policy, the three writes wait for an approval before they run. See the Agents API reference.

Service accounts and the AI Use Policy

If your organization enforces a company AI use policy, service accounts and service tokens are exempt from the signature gate: there is no person to present a policy to, and a bot has no Control Room session in which to sign. Their tool calls are never blocked by the policy gate.

To keep the governance record complete, an admin can record an acknowledgement on behalf of a service account. It is stored against the currently published policy version, permanently, with the admin who recorded it named on the record and an entry in the audit log. Only admins can do this, only for active service accounts in their own organization, and only against the version currently in force. You cannot back-date an acknowledgement to a superseded version.

Security notes

  • Tokens are stored hashed (SHA-256); only the prefix is recoverable from storage.
  • Treat service tokens like passwords: never commit them to git, prefer your platform's secret manager, and rotate them well before the expiry date.
  • All token creation, revocation, and service-account changes are written to the audit log with the admin's identity.
  • A revoked or expired token returns 401 Unauthorized on its next MCP call, so agents using it should refresh or fail loudly rather than retrying silently.
  • Service accounts go through approval workflows just like human users. When a service account calls a tool marked Require Approval, the call pauses as a pending request that an org admin can approve or reject, and the agent can poll airlock/check_status to resume once it's decided. Unattended callers should be built to handle a PENDING_APPROVAL response rather than treating it as an error.
  • For defense in depth, pair service accounts with an IP allowlist so their tokens only work from the networks you expect. See Network access for the per-organization and per-service-account allowlists.