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

# API Keys

> Legacy, scoped personal, and device credentials

## Overview

Lua accepts existing legacy API keys and typed credentials. Choose the credential class for the client that uses it:

<CardGroup cols={3}>
  <Card title="Legacy keys" icon="key">
    `api_` followed by 32 hex characters. Created before scoped keys existed. Act with the owner's full permissions in every organization the owner belongs to.
  </Card>

  <Card title="Scoped keys" icon="shield-halved">
    `api_<uuid>.<secret>`. Personal keys with an explicit role on one or more organizations or agents. They never exceed the owner's current access.
  </Card>

  <Card title="Device credentials" icon="microchip" href="/devices/credentials">
    Typed credentials bound to one exact agent, one exact device name, and selected device operations.
  </Card>
</CardGroup>

Legacy and scoped personal keys use the same bearer-token request format. Device credentials use the device client and device protocol fields. They do not grant general personal API access.

## Legacy keys

If your key was created before scoped keys shipped, nothing about it changes. Legacy keys:

* Act with the owner's full permissions, in **every** organization the owner belongs to.
* Keep working indefinitely.

<Note>
  There is no deprecation date, forced rotation, or automatic expiry for legacy keys. Existing integrations can keep using them. New CLI email login creates a renewable user session instead of another API key.
</Note>

## Scoped keys

A scoped key is a **personal** key: it belongs to one member, and it carries an explicit role on each organization or agent it's granted against — the same kind of role a human member holds on that resource in the admin dashboard.

Two rules govern what a scoped key can do:

<Steps>
  <Step title="Capped at the owner's role">
    A scoped key can never do more than its owner can. Its effective role on a resource is capped at the owner's current role there, even if the key was originally granted a broader role.
  </Step>

  <Step title="Follows the owner's access">
    If the owner loses access to an organization, any key of theirs scoped to that organization loses access too. Keys the owner holds on their other organizations keep working normally.
  </Step>
</Steps>

<Note>
  The role picker in the dashboard (**Settings → API Keys**) shows the current set of roles you can grant, since it's the same roster used for human org members. Scope each key to the organizations or agents it actually needs — see [Best practices](#best-practices).
</Note>

## Interactive login and scoped credentials

Use Email login for interactive work on a developer workstation:

```bash theme={null}
lua auth configure
```

Choose **Email** and confirm the OTP. The CLI stores a renewable first-party session and reads your current access when each command runs. It does not create an API key. Each project's `lua.skill.yaml` selects its agent.

For CI, a direct HTTP integration, or another client that needs a separately managed credential, create a scoped key under **Settings → API Keys**:

```bash theme={null}
lua admin
# Or visit https://admin.heylua.ai
```

<Steps>
  <Step title="Open Settings → API Keys">
    Manage your own keys or, if you are an organization administrator, keys for another member.
  </Step>

  <Step title="Choose the scope">
    Grant a role on one or more organizations or agents. A key created for another member cannot exceed that member's role.
  </Step>

  <Step title="Set an optional expiry">
    Defaults to never. Set a date if the key is for a temporary integration.
  </Step>

  <Step title="Copy the secret">
    Lua shows the full secret once. Store it in a secret manager or `.env` file. You cannot view it again after you leave the page.
  </Step>
</Steps>

<Warning>
  Never commit an API key to version control or share it publicly.
</Warning>

## Device credentials

New Node.js installations and custom protocol clients use a device credential instead of a personal scoped key. Provision it for one exact agent, one exact device name, and only the `commands`, `triggers`, or `assets.upload` operations that the device needs. See [Device credentials](/devices/credentials) for the endpoint, client setup, and compatibility behavior.

## Managing a key

Scoped personal keys and device credentials use the same rotation, suspension, reactivation, and revocation lifecycle.

<AccordionGroup>
  <Accordion title="Rotate">
    Replaces the key's secret while keeping its role and grants intact. Only the key's owner can rotate it. The new secret is shown exactly once. The old secret stops working as the change propagates, within about a minute.
  </Accordion>

  <Accordion title="Suspend / reactivate">
    Temporarily disables a key without losing its configuration. A suspended key fails authentication after the change propagates. Its role and grants stay unchanged.
  </Accordion>

  <Accordion title="Revoke">
    Permanently disables a key. Either the key's owner or an org admin can revoke it. Revocation is one-way — a revoked key can't be reactivated, only replaced with a new one. It takes effect within about a minute everywhere.
  </Accordion>
</AccordionGroup>

## Using a key

Usage is identical for legacy and scoped keys:

<Tabs>
  <Tab title="HTTP API">
    ```bash theme={null}
    Authorization: Bearer YOUR_API_KEY
    ```

    See the [HTTP API reference](/channels/http-api).
  </Tab>

  <Tab title="CLI">
    ```bash theme={null}
    export LUA_API_KEY=your-api-key
    # or
    lua auth configure --api-key your-api-key
    ```

    See [Authentication](/cli/authentication).
  </Tab>

  <Tab title="Device clients">
    ```typescript theme={null}
    new DeviceClient({
      deviceCredential: process.env.LUA_DEVICE_CREDENTIAL!,
      agentId: 'your-agent-id',
      deviceName: 'your-device-name',
    });
    ```

    New Node.js installations use a [device credential](/devices/credentials) with an exact agent, device name, and operation binding. Existing `apiKey` and `api_key` configurations remain supported for non-dotted legacy keys indefinitely.
  </Tab>
</Tabs>

## Errors

| Status | Meaning                                                                    |
| ------ | -------------------------------------------------------------------------- |
| `401`  | The key is invalid, expired, suspended, or revoked.                        |
| `403`  | The key is valid, but its role doesn't allow that action on that resource. |

A `403` on a scoped key usually means the key needs a broader role, or a grant on an additional organization or agent — check **Settings → API Keys** in the dashboard.

## Best practices

<AccordionGroup>
  <Accordion title="One key per integration">
    Don't reuse a single key across unrelated integrations. If one is compromised or needs rotating, a dedicated key limits the blast radius and makes it obvious what to revoke.
  </Accordion>

  <Accordion title="Grant the narrowest role that works">
    Give a key only the role it needs on a resource, not a broader one "to be safe." A key capped at the right role can't be misused for actions it was never meant to perform.
  </Accordion>

  <Accordion title="Prefer agent-level over org-level scope">
    If an integration only ever needs one agent, scope the key to that agent instead of the whole organization. It keeps the key from reaching agents it has no business touching.
  </Accordion>

  <Accordion title="Set an expiry for temporary access">
    Contractor access, a one-off migration script, a demo environment — anything with a known end date should get an expiry instead of relying on someone remembering to revoke it later.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="CLI Authentication" icon="terminal" href="/cli/authentication">
    Configure the CLI with an API key
  </Card>

  <Card title="HTTP API" icon="bolt" href="/channels/http-api">
    Call your agent directly over HTTP
  </Card>
</CardGroup>
