> ## 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.

# Device credentials

> Provision and manage a credential for one exact device

New Node.js installations and custom protocol clients use a device credential. The server binds each credential to one agent, one device name, and the device operations that you select.

<Note>
  `@lua-ai-global/device-client` 1.1.0 and later support the `deviceCredential` option. The published Python 1.3.0 client and the current MicroPython distribution use `api_key` with existing non-dotted legacy keys. Those legacy configurations remain supported indefinitely.
</Note>

## Choose the permitted operations

Grant only the operations that the device uses.

| Operation       | Allows the device to                                                     |
| --------------- | ------------------------------------------------------------------------ |
| `commands`      | Advertise command handlers, receive commands, and return command results |
| `triggers`      | Send device triggers to the agent                                        |
| `assets.upload` | Upload files through the device client's CDN helper                      |

A device credential is different from a scoped personal API key. Lua accepts it only on supported device connections and device file uploads. A typed personal API key cannot replace a device credential on these device-only paths. Existing non-dotted legacy keys keep their permanent compatibility behavior.

## Provision a device credential

The provisioning request requires a renewable first-party user session. Do not send an API key to this endpoint, and never store the user session on the device.

The following example creates a credential for one device that receives commands and sends triggers:

```bash theme={null}
curl --fail-with-body --silent --show-error \
  --request POST "https://api.heylua.ai/admin/users/me/credentials/device" \
  --header "Authorization: Bearer $FIRST_PARTY_SESSION_TOKEN" \
  --header "Content-Type: application/json" \
  --output response.json \
  --data '{
    "agentId": "baseAgent_agent_abc123",
    "deviceName": "warehouse-scanner",
    "operations": ["commands", "triggers"],
    "name": "Warehouse scanner"
  }'
```

`FIRST_PARTY_SESSION_TOKEN` is a placeholder for the current signed-in user's session token. It is not a Lua API key.

The signed-in user must be allowed to issue credentials and perform every requested operation on the target agent. The endpoint returns `403` without creating a credential when either check fails.

The request body accepts these fields:

| Field        | Required | Description                                                          |
| ------------ | -------- | -------------------------------------------------------------------- |
| `agentId`    | Yes      | The exact agent that the device may connect to                       |
| `deviceName` | Yes      | The exact device identity used by the client and MQTT topics         |
| `operations` | Yes      | One or more values from `commands`, `triggers`, and `assets.upload`  |
| `name`       | No       | A label for credential management, up to 120 characters              |
| `expiresAt`  | No       | An absolute expiry time in epoch milliseconds. Omit it for no expiry |

The `deviceName` must contain 1 to 200 characters. Do not use whitespace or the MQTT topic characters `/`, `+`, or `#`.

The response identifies the credential class, exact binding, operations, and lifecycle state:

| Response field              | Value                                                                  |
| --------------------------- | ---------------------------------------------------------------------- |
| `secret`                    | The full opaque credential. Lua returns it only after create or rotate |
| `credential.id`             | The stable identifier used by lifecycle endpoints                      |
| `credential.credentialType` | `deviceCredential`                                                     |
| `credential.status`         | `active` after creation                                                |
| `credential.secretVersion`  | The current secret version                                             |
| `credential.deviceBinding`  | The exact `agentId`, `deviceName`, and `operations` from the request   |

Treat the secret as opaque. Do not parse its prefix or dotted format.

```bash theme={null}
export LUA_DEVICE_CREDENTIAL="$(jq -r '.secret' response.json)"
export LUA_DEVICE_CREDENTIAL_ID="$(jq -r '.credential.id' response.json)"
```

Store the secret in the device's secret store. Keep the credential identifier in your provisioning system so that you can rotate, suspend, or revoke the device later.

<Warning>
  The `agentId` and `deviceName` in the client configuration must exactly match the values used at provisioning. The gateway rejects a different agent or device name even when the secret is valid.
</Warning>

## Configure the device client

Use `deviceCredential` with the Node.js client 1.1.0 or later:

```typescript theme={null}
import { DeviceClient } from '@lua-ai-global/device-client';

const device = new DeviceClient({
  agentId: process.env.LUA_AGENT_ID!,
  deviceName: 'warehouse-scanner',
  deviceCredential: process.env.LUA_DEVICE_CREDENTIAL!,
});
```

The published Python 1.3.0 client and the current MicroPython distribution do not expose a typed credential option. Keep their existing `api_key` configuration unchanged.

For raw MQTT clients, use `{agentId}:{deviceName}` as the username and the device credential as the MQTT CONNECT password. Do not include a typed device credential in a status payload. See [Build your own client](/devices/build-your-own).

## Manage the lifecycle

All lifecycle requests require a renewable first-party user session. Replace `credential-id` with the stable `credential.id` returned at provisioning.

<AccordionGroup>
  <Accordion title="List credentials">
    List the signed-in user's personal and device credentials:

    ```bash theme={null}
    curl "https://api.heylua.ai/admin/users/me/credentials" \
      --header "Authorization: Bearer $FIRST_PARTY_SESSION_TOKEN"
    ```

    Device entries have `credentialType: "deviceCredential"` and include their `deviceBinding`. List responses never contain the secret.
  </Accordion>

  <Accordion title="Rotate">
    Replace the secret without changing the credential identifier, device binding, or permitted operations:

    ```bash theme={null}
    curl --request POST \
      "https://api.heylua.ai/admin/users/me/credentials/credential-id/rotate" \
      --header "Authorization: Bearer $FIRST_PARTY_SESSION_TOKEN"
    ```

    Save the new `secret`, then replace the old secret on the device. Lua returns the new secret only in this response.
  </Accordion>

  <Accordion title="Suspend and reactivate">
    Suspend the credential without deleting its binding:

    ```bash theme={null}
    curl --request POST \
      "https://api.heylua.ai/admin/users/me/credentials/credential-id/suspend" \
      --header "Authorization: Bearer $FIRST_PARTY_SESSION_TOKEN"
    ```

    Reactivate an unexpired suspended credential:

    ```bash theme={null}
    curl --request POST \
      "https://api.heylua.ai/admin/users/me/credentials/credential-id/reactivate" \
      --header "Authorization: Bearer $FIRST_PARTY_SESSION_TOKEN"
    ```
  </Accordion>

  <Accordion title="Revoke">
    Permanently revoke the credential:

    ```bash theme={null}
    curl --request DELETE \
      "https://api.heylua.ai/admin/users/me/credentials/credential-id" \
      --header "Authorization: Bearer $FIRST_PARTY_SESSION_TOKEN"
    ```

    You cannot reactivate a revoked credential. Provision a new one if the device needs access again.
  </Accordion>
</AccordionGroup>

Lifecycle changes disconnect affected Socket.IO and MQTT sessions as the change propagates. The authorization backstop applies within about one minute.

## Preserve existing installations

Existing device settings remain compatible:

* Node.js continues to accept `apiKey` indefinitely.
* Python and MicroPython continue to accept `api_key` indefinitely.
* Existing non-dotted legacy keys remain valid indefinitely. Lua does not rotate, rewrite, revoke, or expire them automatically.
* Socket.IO continues to send the credential in the `auth.apiKey` wire field.
* MQTT continues to send the credential as the CONNECT password.

For Node.js and custom clients, that password can be the typed device credential. Published Python 1.3.0 and current MicroPython clients send their existing non-dotted legacy `api_key`.

For new Node.js installations, use `deviceCredential`. This tells the MQTT client not to repeat the typed secret in the non-retained status message. If you provide both `deviceCredential` and `apiKey`, their values must match.

## Next steps

<CardGroup cols={2}>
  <Card title="Device quickstart" icon="rocket" href="/devices/quickstart">
    Connect a Node.js or Python device
  </Card>

  <Card title="MQTT transport" icon="tower-broadcast" href="/devices/mqtt-client">
    Configure MQTT clients and topics
  </Card>
</CardGroup>
