> ## Documentation Index
> Fetch the complete documentation index at: https://docs.airctrl.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Service Accounts

> Use a machine identity safely while keeping credential administration in the dashboard.

export function AirctrlWordmark() {
  return <span className="airctrl-wordmark" aria-label="AIRCTRL">
      <span aria-hidden="true" className="airctrl-wordmark-air">AIR</span>
      <span aria-hidden="true" className="airctrl-wordmark-ctrl">CTRL</span>
    </span>;
}

A Service Account represents CI, a server, an agent, or another unattended workload. It receives only the custom roles and project access assigned to it.

## Security boundary

Creating, pausing, reactivating or deleting a Service Account is Web-only. Provisioning, rotating, revoking and repairing its credential is also Web-only.

A human PAT can read safe Service Account metadata through API, SDK, CLI and MCP. A Service Account
cannot inspect the account's Service Account list, including other machine identities. Record sharing
is performed by an authorized human through the normal Records capability. Developer tools do not
administer the Service Account lifecycle.

## Prepare access in the dashboard

1. Open **Settings > Service accounts**.
2. Create the Service Account.
3. Assign a custom role and the project where it will work.
4. Open **Machine access** and provision a credential.
5. Store the complete credential bundle through the offered <AirctrlWordmark /> save flow or your approved custody system.

The protected credential project stores custody material. It does not grant the Service Account access to unrelated projects or other Service Accounts.

## List Service Accounts

### API

```bash theme={}
curl --request GET \
  --url "$AIRCTRL_API_URL/v1/service-accounts?accountId=$AIRCTRL_ACCOUNT_ID" \
  --header "Authorization: Bearer $AIRCTRL_TOKEN" \
  --header "x-account-id: $AIRCTRL_ACCOUNT_ID"
```

The response contains metadata such as ID, name, active state and revocation time. It never returns tokens, private keys or credential bundles.

### SDK

```ts theme={}
const accounts = await airctrl.listServiceAccounts({ accountId })
const selected = await airctrl.getServiceAccount(serviceAccountId, { accountId })
```

### CLI

```bash theme={}
airctrl sa list --account "$AIRCTRL_ACCOUNT_ID"
airctrl sa get "$SERVICE_ACCOUNT_ID" --account "$AIRCTRL_ACCOUNT_ID"
```

### MCP

These tools require a human PAT and return metadata only.

```text theme={}
Show the Service Accounts in account
"00000000-0000-4000-8000-000000000001". Then show the safe details for
Service Account "00000000-0000-4000-8000-000000000040". Return names, IDs,
providers and states only. Do not change roles, projects, state or credentials.
```

<div className="technical-reference">
  **MCP tools used:** `list_service_accounts`, `get_service_account`
</div>

## Use a Service Account with the SDK

Load one complete active credential bundle:

```ts theme={}
import { readFile } from 'node:fs/promises'
import { createClient, type SaCredentialBundleV1 } from '@airctrl/sdk'

const bundle = JSON.parse(
  await readFile(process.env.AIRCTRL_SA_BUNDLE_PATH!, 'utf8'),
) as SaCredentialBundleV1

const airctrl = createClient({
  baseUrl: bundle.baseUrl,
  auth: bundle.bearer,
  credential: {
    credentialId: bundle.credentialId,
    capPrivateKeyB64: bundle.capPrivateKeyB64,
  },
})

const secret = await airctrl.getSecret({ recordId })
```

No vault passphrase is required. The capability private key opens the Service Account activation material locally and is never sent to <AirctrlWordmark />.

## Use a Service Account with the CLI

Inject the five active bundle fields through your runtime environment, then run a supported machine command:

```bash theme={}
airctrl get "$RECORD_ID"
airctrl run --project "$PROJECT_ID" -- node server.js
```

A Service Account must pass `--project` to `run`. The CLI never falls back to a human default project for a machine identity.
It may also list accessible records, list providers, and read one
accessible gateway's details, tokens, logs and key-access events. Project creation, record creation,
sharing and Gateway configuration remain human-only.

## Use a Service Account with MCP

Map the active bundle to the MCP server environment:

```text theme={}
AIRCTRL_MCP_API_URL                 = bundle.baseUrl
AIRCTRL_MCP_API_TOKEN               = bundle.bearer
AIRCTRL_MCP_SA_CREDENTIAL_ID        = bundle.credentialId
AIRCTRL_MCP_SA_CAP_PRIVATE_KEY_B64  = bundle.capPrivateKeyB64
AIRCTRL_MCP_SA_SERVICE_ACCOUNT_ID   = bundle.serviceAccountId  # optional
```

MCP passes the bearer and capability material to `@airctrl/sdk`. Record values are decrypted locally
without a human vault passphrase. In Service Account mode, MCP registers only the supported record and
Gateway read tools, plus local status, format-discovery and generation helpers that do not change <AirctrlWordmark />.
It does not register record writes, sharing, project changes, Gateway changes or Service Account
administration. RBAC and project access can further reduce the available reads.

## Share a record with a Service Account

The record owner or an authorized human encrypts the record key for the Service Account identity. Sharing through any developer surface requires a human PAT. Sharing one record does not expose another record or another Service Account.

```ts theme={}
await airctrl.shareRecordWithServiceAccount({
  accountId,
  recordId,
  serviceAccountId,
  vaultPassword: process.env.AIRCTRL_VAULT_PASSPHRASE!,
})
```

```bash theme={}
airctrl record share-sa "$RECORD_ID" \
  --account "$AIRCTRL_ACCOUNT_ID" \
  --service-account "$SERVICE_ACCOUNT_ID"
```

MCP request:

```text theme={}
Give Service Account "00000000-0000-4000-8000-000000000040" access to record
"00000000-0000-4000-8000-000000000020" in account
"00000000-0000-4000-8000-000000000001". Show both resources and wait for my
confirmation. Change only this record grant.
```

<div className="technical-reference">
  **MCP tool used:** `share_record_service_account`
</div>

## Common failures

* `service_account_disabled`: reactivate it in the dashboard before using its credential.
* `service_account_revoked`: the identity can no longer authenticate.
* `sa_sealed_unlock_requires_capability`: supply the matching credential ID and capability private key.
* `sa_env_bundle_incomplete`: the CLI received only part of an active bundle.
* `403`: the Service Account lacks the required custom-role permission or project grant.
