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

# Authentication

> Bearer tokens — workspace API keys for machines, OAuth sessions for humans.

export const ExpectedOutput = ({caption = "Expected output", children}) => <div className="gv-output">
    <div className="gv-output__label">
      <svg className="gv-output__icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
        <polyline points="15 10 20 15 15 20" />
        <path d="M4 4v7a4 4 0 0 0 4 4h12" />
      </svg>
      <span>{caption}</span>
    </div>
    <div className="gv-output__body">{children}</div>
  </div>;

export const ErrorTable = ({errors}) => <div className="gv-error-table" role="table" aria-label="Errors and fixes">
    <div className="gv-error-table__head" role="row">
      <span className="gv-error-table__col-head" role="columnheader">Error</span>
      <span className="gv-error-table__col-head" role="columnheader">Cause</span>
      <span className="gv-error-table__col-head" role="columnheader">Fix</span>
    </div>
    {errors.map((err, i) => <div className="gv-error-table__row" role="row" key={i}>
        <code className="gv-error-table__code" role="cell">{err.code}</code>
        <div className="gv-error-table__cause" role="cell">{err.cause}</div>
        <div className="gv-error-table__fix" role="cell">{err.fix}</div>
      </div>)}
  </div>;

export const Def = ({term, type, defaultValue, required, children}) => <>
    <dt className="gv-def-list__term">
      <span className="gv-def-list__name">{term}</span>
      {type && <span className="gv-def-list__type">{type}</span>}
      {required && <span className="gv-def-list__required">required</span>}
      {defaultValue !== undefined && <span className="gv-def-list__default">
          default <code>{defaultValue}</code>
        </span>}
    </dt>
    <dd className="gv-def-list__description">{children}</dd>
  </>;

export const DefList = ({children}) => <dl className="gv-def-list">{children}</dl>;

export const APIBadge = ({method = "GET", path, scope}) => {
  const m = method.toUpperCase();
  return <span className="gv-api-badge">
      <span className={`gv-api-badge__method gv-api-badge__method--${m.toLowerCase()}`}>
        {m}
      </span>
      <code className="gv-api-badge__path">{path}</code>
      {scope && <span className="gv-api-badge__scope">
          <span className="gv-api-badge__scope-label">scope</span>
          <code>{scope}</code>
        </span>}
    </span>;
};

export const NumberedStep = ({n, title, children}) => <li className="gv-step">
    <span className="gv-step__index">{n}</span>
    <div className="gv-step__body">
      <h3 className="gv-step__title">{title}</h3>
      <div className="gv-step__content">{children}</div>
    </div>
  </li>;

export const NumberedSteps = ({children}) => <ol className="gv-steps">{children}</ol>;

export const Eyebrow = ({label, tone = "default"}) => <div className={`gv-eyebrow gv-eyebrow--${tone}`}>
    <span className="gv-eyebrow__bar" aria-hidden="true" />
    <span className="gv-eyebrow__label">{label}</span>
  </div>;

<Eyebrow label="AUTH" />

Every request to `https://api.gavai.app/v1` must carry `Authorization: Bearer <token>`. Two kinds of token exist: workspace API keys for machine callers and OAuth session tokens for interactive humans. They have different lifecycles, different prefixes, and different ways of being scoped. By the end of this page you will know which token type to pick, how to mint or refresh it, and how scopes gate every endpoint.

## Pick a token type

| Prefix       | Type                          | Use it for                                                           |
| ------------ | ----------------------------- | -------------------------------------------------------------------- |
| `gak_live_*` | Workspace API key — live mode | Backend services, CI pipelines, production automation                |
| `gak_test_*` | Workspace API key — test mode | Integration tests, sandboxes — isolated data, no real Stripe charges |
| `gst_<jwt>`  | OAuth session token           | The official CLI, IDE tooling, any flow where a human signs in       |

If you are wiring a server to gavAI, you want a workspace API key. If you are building a tool a developer logs into, you want OAuth.

## Workspace API keys

Workspace API keys are minted in the [console](https://console.gavai.app) or via <APIBadge method="POST" path="/v1/workspaces/{slug}/tokens" scope="tokens:write" />. Each key is bound to one workspace and one explicit scope set at creation time. Keys do not expire on a timer — they live until you rotate or revoke them.

```bash theme={null}
curl https://api.gavai.app/v1/me \
  -H "Authorization: Bearer gak_live_xxxxxxxxxxxxx"
```

### Lifecycle

| Operation | Method and path                                 | Notes                                                                              |
| --------- | ----------------------------------------------- | ---------------------------------------------------------------------------------- |
| Mint      | `POST /v1/workspaces/{slug}/tokens`             | Body carries the requested scope set. The secret value is returned exactly once.   |
| Inspect   | `GET /v1/workspaces/{slug}/tokens/{id}`         | Returns metadata only — scopes, last-used, created-by. Never the secret.           |
| Rotate    | `POST /v1/workspaces/{slug}/tokens/{id}/rotate` | New secret, old secret invalidated. Use for scheduled rotation or breach response. |
| Revoke    | `DELETE /v1/workspaces/{slug}/tokens/{id}`      | Permanent. The key stops working immediately.                                      |

The operating discipline: one key per integration, narrowest scopes that integration needs, store the secret in your secret manager, rotate on schedule and on suspected compromise. See [API tokens](/api-reference/resources/tokens) for the full endpoint contract.

### Test vs live mode

`gak_test_*` keys operate in a sandbox: isolated data, isolated counters, no real Stripe charges. The endpoint surface is identical between modes — only the effect and the data differ. Use `gak_test_*` in CI; switch to `gak_live_*` only for production traffic.

## OAuth 2.1 + PKCE

The official CLI and any IDE integration use OAuth 2.1 with PKCE to authenticate a human user. The resulting access token (`gst_<jwt>`) is short-lived; a refresh token handles transparent renewal.

<NumberedSteps>
  <NumberedStep n={1} title="Build the authorize URL">
    Fetch the unauthenticated CLI config from `GET https://console.gavai.app/oauth/cli-config`. Open `authorize_url` in the user's browser with these query parameters:

    <DefList>
      <Def term="code_challenge" type="string" required>SHA-256 of your random `code_verifier`, base64url-encoded.</Def>
      <Def term="code_challenge_method" type="string" required defaultValue="S256">Hash method for the PKCE challenge.</Def>
      <Def term="redirect_uri" type="string" required>Where the auth server returns the user — e.g. `http://localhost:47823/cli-callback`.</Def>
      <Def term="state" type="string" required>Random CSRF token, verified on return.</Def>
      <Def term="scope" type="string" required>Space-separated list of required scopes.</Def>
    </DefList>

    The user signs in via the control-plane identity service (email/password or SSO), and the browser redirects back to your `redirect_uri` with `?code=<auth_code>&state=<state>`.
  </NumberedStep>

  <NumberedStep n={2} title="Exchange the code for tokens">
    POST the code and the original verifier to the token endpoint:

    <APIBadge method="POST" path="/v1/oauth/token" />

    ```http theme={null}
    POST https://api.gavai.app/v1/oauth/token
    Content-Type: application/json

    {
      "auth_code":    "...",
      "code_verifier": "...",
      "redirect_uri":  "http://localhost:47823/cli-callback"
    }
    ```

    The server checks that the verifier hashes to the stored challenge, then returns:

    <ExpectedOutput>
      ```json theme={null}
      {
        "access_token":  "gst_eyJ...",
        "refresh_token": "...",
        "expires_in":    3600,
        "token_type":    "Bearer"
      }
      ```
    </ExpectedOutput>
  </NumberedStep>

  <NumberedStep n={3} title="Call the API with the access token">
    Pass `Authorization: Bearer gst_eyJ...` on every subsequent call. The access token is valid for `expires_in` seconds.
  </NumberedStep>

  <NumberedStep n={4} title="Refresh before or after expiry">
    Refresh tokens are single-use and rotate on every call:

    <APIBadge method="POST" path="/v1/oauth/refresh" />

    ```http theme={null}
    POST https://api.gavai.app/v1/oauth/refresh
    Content-Type: application/json

    { "refresh_token": "..." }
    ```

    The old refresh token is invalidated; the response carries a fresh access + refresh pair. Persist the new pair before discarding the old one. The official CLI handles this transparently before each command.
  </NumberedStep>
</NumberedSteps>

The CLI stores credentials at `~/.config/gavai/credentials.json` (Linux and macOS) and refreshes automatically when the access token has expired.

## Scopes

Every endpoint requires a specific scope — for example `pages:read` or `tokens:destructive`. Workspace API keys are minted with an explicit scope set. OAuth session tokens inherit the authenticated user's permissions for the active workspace.

The action levels imply each other: `:destructive` implies `:write`, and `:write` implies `:read`. A token with `pages:destructive` can do everything a `pages:read` token can do and more.

If a token lacks the required scope, the API returns `403` with `error.code = "insufficient_scope"` and `details.required_scope` names the missing scope:

```json theme={null}
{
  "error": {
    "code": "insufficient_scope",
    "message": "Token lacks pages:write",
    "details": { "required_scope": "pages:write" }
  }
}
```

## Next

<Card title="Full scope reference" icon="shield" href="/security/scopes">
  Every endpoint's required scope, the four action levels, and least-privilege key patterns.
</Card>
