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

# Migrate from API keys

> Move an existing integration to private keys and short-lived access tokens.

API keys are deprecated but remain supported, so you can migrate one service or environment at a time. A good place to start is anywhere you hand credentials to a sandbox or a short-lived job, since those environments benefit the most from short-lived tokens.

Upgrading the SDKs on its own changes nothing for an existing API-key integration: client construction, `tokens.create(...)`, commit operations with `author`, and token lifetimes all keep working the way they do today.

## What changes

Moving a client from an API key to a private key changes these behaviors:

* **`org` is no longer a client option.** The key is bound to one organization.
* **`authors` becomes required.** Minting tokens, MesaFS mounts, and commit operations all take an ordered `authors` list.
* **Token lifetimes shrink.** Private-key tokens default to 15 minutes and max out at 4 hours. API-key tokens default to 1 hour and max out at 24.
* **Sandboxes read `MESA_ACCESS_TOKEN`.** `MESA_API_KEY` stays reserved for actual API keys and takes precedence if both are set.
* **The SDK `auth` option only accepts private-key tokens.** Tokens minted by an API-key client keep working in the CLI, REST, and MesaFS mounts, just not there.

The steps below walk through the switch.

<Steps>
  <Step title="Create a private key">
    In the [dashboard](https://app.mesa.dev), open your organization's settings and create a key under **Keys**. The private key is shown once, so save it to your secrets manager as `MESA_PRIVATE_KEY` before leaving the page. Mesa stores only the matching public key.
  </Step>

  <Step title="Update trusted SDK clients">
    Replace the API key with the private key in backends and orchestrators you trust:

    <CodeGroup>
      ```typescript TypeScript theme={null}
      // Before
      const mesa = new Mesa({ apiKey: process.env.MESA_API_KEY, org: "acme" });

      // After
      const mesa = new Mesa({ privateKey: process.env.MESA_PRIVATE_KEY });
      ```

      ```python Python theme={null}
      # Before
      mesa = Mesa(api_key=os.environ["MESA_API_KEY"], org="acme")

      # After
      mesa = Mesa(private_key=os.environ["MESA_PRIVATE_KEY"])
      ```
    </CodeGroup>

    The `org` option goes away: a private key is bound to a single organization. Authors move to the individual operations.
  </Step>

  <Step title="Mint tokens for sandboxes and jobs">
    Mint a short-lived token in the trusted process, then pass only that token to the less-trusted environment.

    **`tokens.create(...)` now takes `authors`.** Every commit the token writes is attributed to that list, so it records who the sandbox's work belongs to. See [Authentication](/content/concepts/authentication#authors) for more info.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      const { token } = await mesa.tokens.create({
        // New: who this token's commits belong to
        authors: [{ name: "Mesa Bot", email: "mesa-bot@example.com" }],
        scopes: ["read", "write"],
        repos: ["acme/agent-workspace"],
        ttl_seconds: 60 * 60, // 1 hour
      });
      ```

      ```python Python theme={null}
      minted = await mesa.tokens.create(
          # New: who this token's commits belong to
          authors=[{"name": "Mesa Bot", "email": "mesa-bot@example.com"}],
          scopes=["read", "write"],
          repos=["acme/agent-workspace"],
          ttl_seconds=60 * 60,  # 1 hour
      )
      ```
    </CodeGroup>

    Private-key tokens default to a 15 minute lifetime and cannot exceed 4 hours. Pick a value that covers the whole job or mount.
  </Step>

  <Step title="Use MESA_ACCESS_TOKEN in the receiving environment">
    Replace the sandbox's `MESA_API_KEY` variable with `MESA_ACCESS_TOKEN`:

    ```typescript theme={null}
    await createSandbox({
      env: {
        MESA_ORG: "acme",
        MESA_ACCESS_TOKEN: token,
      },
    });
    ```

    The CLI picks up `MESA_ACCESS_TOKEN` automatically. For SDK code running in the sandbox, pass the token through `auth: { accessToken }` in TypeScript or `auth={"access_token": token}` in Python.

    <Warning>
      Remove `MESA_API_KEY` from the environment after switching. When both variables are set, the API key takes precedence.
    </Warning>
  </Step>

  <Step title="Update REST and MCP credentials">
    Use the access token anywhere you previously sent the raw API key as a bearer credential.

    Keep the minting step in trusted infrastructure, and never copy the private key into an MCP client, sandbox, or browser.
  </Step>

  <Step title="Revoke unused API keys">
    Once an API key is no longer used, revoke it in the dashboard. Revoking the key also invalidates any tokens minted from it.
  </Step>
</Steps>

## What remains compatible

* SDK clients and the CLI still accept `apiKey`, `api_key`, and `MESA_API_KEY`.
* API-key clients can still call `tokens.create(...)` with `scopes`, `repos`, `repo_ids`, and a lifetime of up to 24 hours.
* Commit operations on API-key clients keep their singular `author` parameter.
* Existing REST, MCP, and MesaFS API-key integrations keep their current behavior.

See [Authentication reference](/content/reference/authentication) for the differences between private-key and API-key tokens.
