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

# OAuth

> Exchange a PKCE auth code for tokens, and rotate refresh tokens to keep sessions alive.

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="RESOURCE" />

The OAuth resource handles token issuance and refresh for interactive callers — by the end of this page you will know the two endpoints, the rotation rules on refresh tokens, and where the wider PKCE flow lives. It implements OAuth 2.1 with PKCE: the pattern the official `gavai` CLI and any IDE integration use to authenticate a human user. Both endpoints are unauthenticated and do not require an existing bearer token.

If you are building a machine-to-machine integration, use workspace API keys (`gak_live_*` / `gak_test_*`) instead. The OAuth flow is for tools acting on behalf of a human who signs in interactively.

## Endpoints

Both endpoints are unauthenticated — no bearer token required.

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

Exchange a PKCE auth code for access + refresh tokens.

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

Rotate a refresh token, returning a new access + refresh pair.

## The PKCE handshake

The full flow is documented on the [Authentication](/api-reference/authentication) page — these endpoints are steps 2 and 4 of that walkthrough. The summary:

<NumberedSteps>
  <NumberedStep n={1} title="Generate a PKCE pair">
    Create a random `code_verifier` (43–128 URL-safe characters). Derive `code_challenge` as the base64url-encoded SHA-256 hash of the verifier.
  </NumberedStep>

  <NumberedStep n={2} title="Send the user to the authorize URL">
    Fetch `authorize_url` from `GET https://console.gavai.app/oauth/cli-config`, open it in the user's browser with `code_challenge`, `code_challenge_method=S256`, `redirect_uri`, `state`, and the required `scope`.
  </NumberedStep>

  <NumberedStep n={3} title="Receive the auth code">
    Your callback (e.g. `http://localhost:47823/cli-callback`) receives `?code=<auth_code>&state=<state>`. Verify `state` matches what you sent.
  </NumberedStep>

  <NumberedStep n={4} title="Exchange the code for tokens">
    Call <APIBadge method="POST" path="/v1/oauth/token" /> with the code and the verifier. The server returns `access_token` (`gst_<jwt>`), `refresh_token`, and `expires_in`.
  </NumberedStep>

  <NumberedStep n={5} title="Refresh on or before expiry">
    Call <APIBadge method="POST" path="/v1/oauth/refresh" /> with the current refresh token. The old refresh token is invalidated; the response carries a fresh pair. Persist the new pair before discarding the old one.
  </NumberedStep>
</NumberedSteps>

## The refresh contract

Refresh tokens rotate on every use. A refresh token can be exchanged exactly once — calling `/v1/oauth/refresh` twice with the same token will fail the second time with `400`.

| Operation     | What the server returns                                                                                 |
| ------------- | ------------------------------------------------------------------------------------------------------- |
| Exchange code | `access_token` (`gst_<jwt>`), `refresh_token`, `expires_in` (seconds), `token_type` (`"Bearer"`)        |
| Refresh       | Same shape — a fresh access token and a fresh refresh token. The supplied refresh token is invalidated. |

Store the new refresh token before retrying anything else. Losing it means re-running the full PKCE handshake.

## Related

<Card title="Authentication" icon="key" href="/api-reference/authentication">
  The full handshake, the scope model, and the lifecycle of session tokens vs API keys.
</Card>
