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

# SDK Authentication

Every SDK session (TypeScript's `query()`, Python's `query()` / `QoderSDKClient`) must be configured with one authentication method. A session can only use one:

| Authentication method                              | Identity                     | Use case                                                                              |
| -------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------- |
| [Personal Access Token (PAT)](#use-a-pat)          | A Qoder user                 | Scripts, CI, or host applications that need the user's permissions and data           |
| [Service Account](#use-a-service-account)          | An organization workload     | Backend services, CI, and scheduled jobs that should not depend on a personal account |
| [Local qodercli session](#reuse-the-local-sign-in) | The currently signed-in user | A developer workstation that is already signed in to Qoder                            |

## Use a PAT

A PAT represents a Qoder user. Use it for automation that must access that user's permissions and data.

### Get a PAT

Create a PAT in [Qoder Account Integrations](https://qoder.com/account/integrations):

1. Sign in to Qoder.
2. Open **Account → Integrations**.
3. Choose the required permissions and expiry, then create the PAT.
4. Copy the generated value immediately. It cannot be viewed again after the page is closed.

Create separate PATs for local scripts, CI, and production so they can be rotated or revoked independently.

### Read a PAT from the environment

```bash
export QODER_PERSONAL_ACCESS_TOKEN="<your-qoder-personal-access-token>"
```

<CodeGroup>
  ```typescript TypeScript
  import { accessTokenFromEnv, query } from '@qoder-ai/qoder-agent-sdk';

  const q = query({
    prompt: 'Summarize the current workspace.',
    options: {
      auth: accessTokenFromEnv(),
    },
  });
  ```

  ```python Python
  from qoder_agent_sdk import QoderAgentOptions, access_token_from_env

  options = QoderAgentOptions(auth=access_token_from_env())
  ```
</CodeGroup>

The function reads `QODER_PERSONAL_ACCESS_TOKEN` by default. To use a custom variable name:

<CodeGroup>
  ```typescript TypeScript
  auth: accessTokenFromEnv('MY_QODER_PAT')
  ```

  ```python Python
  auth = access_token_from_env("MY_QODER_PAT")
  ```
</CodeGroup>

If `options.env` and the process environment contain the same variable, the SDK uses the value from `options.env`.

### Pass a PAT directly

When a trusted backend has already obtained a PAT, pass it directly:

<CodeGroup>
  ```typescript TypeScript
  import { accessToken, query } from '@qoder-ai/qoder-agent-sdk';

  const token = await readTokenFromSecretManager();
  const q = query({
    prompt: 'List the most recently modified files.',
    options: {
      auth: accessToken(token),
    },
  });
  ```

  ```python Python
  from qoder_agent_sdk import QoderAgentOptions, access_token

  token = read_token_from_secret_manager()
  options = QoderAgentOptions(auth=access_token(token))
  ```
</CodeGroup>

The SDK does not refresh PATs. After a PAT becomes invalid, obtain a valid PAT and create a new SDK session.

## Use a Service Account

Before you begin, ask a Qoder organization administrator to create a Service Account, grant the permissions your application needs, and generate a key. Store the key in a secret manager and provide it only to a trusted backend process or CI job.

### Pass a Service Account key directly

When a trusted backend has already read the key from a secret manager, pass it directly:

<CodeGroup>
  ```typescript TypeScript
  import { query, serviceAccount } from '@qoder-ai/qoder-agent-sdk';

  // Get the Service Account key from the host's secret manager adapter.
  const serviceAccountKey = await readSecret('qoder-service-account-key');

  const q = query({
    prompt: 'Explain the purpose of this project in one sentence.',
    options: {
      auth: serviceAccount({ serviceAccountKey }),
      cwd: process.cwd(),
    },
  });
  ```

  ```python Python
  from qoder_agent_sdk import QoderAgentOptions, service_account

  # Get the Service Account key from the host's secret manager adapter.
  service_account_key = read_secret("qoder-service-account-key")
  options = QoderAgentOptions(
      auth=service_account(service_account_key=service_account_key)
  )
  ```
</CodeGroup>

The caller passes the Key into the session via `serviceAccount({ serviceAccountKey })` (TypeScript) / `service_account(service_account_key=...)` (Python). The SDK and qodercli obtain and refresh a short-lived Service Account Token (SAT) for the session.

```text
secret manager
     |
     | Service Account Key
     v
caller reads the key
     |
     | serviceAccount({ serviceAccountKey })
     v
SDK starts qodercli and obtains a short-lived SAT
     |
     `----> authenticate session requests with the SAT
```

The SDK uses the key and SAT for the current session. The caller provides the key again when creating a new session. Read the key from a secret manager, and keep key literals out of source code, browser bundles, mobile applications, logs, and test snapshots.

### Provide and refresh SATs from the host

If the host application embedding the SDK is responsible for exchanging SATs, use the fetch-callback form (TypeScript's `serviceAccount({ fetchServiceAccountToken })`, Python's `service_account(fetch_service_account_token=...)`). The Service Account Key stays in the host process, and qodercli receives SATs from the host callback.

The callback is implemented by the host. qodercli invokes it whenever it needs a SAT; on each request, the host calls the Token exchange API and returns the fresh SAT from the response to qodercli.

```text
qodercli
     |
     | request a SAT
     v
fetchServiceAccountToken callback in the SDK host
     |
     | call Token exchange with the host-managed Service Account key
     v
Qoder Token exchange
     |
     `----> return the short-lived SAT to qodercli
```

This complete example performs the exchange inside the host:

<CodeGroup>
  ```typescript TypeScript
  import {
    query,
    serviceAccount,
    type ServiceAccountTokenResult,
  } from '@qoder-ai/qoder-agent-sdk';

  // Get the Service Account key from the host's secret manager adapter.
  const serviceAccountKey = await readSecret('qoder-service-account-key');
  // Example: select the scopes used for model listing and inference.
  const serviceAccountScopes = ['models.read', 'chat.completions'];

  async function fetchServiceAccountToken(): Promise<ServiceAccountTokenResult> {
    const response = await fetch(
      'https://openapi.qoder.sh/api/v1/serviceToken/exchange',
      {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${serviceAccountKey}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          grant_type: 'client_credentials',
          audience: 'qoder',
          scope: serviceAccountScopes.join(' '),
          ttl_seconds: 3600,
        }),
      },
    );

    if (!response.ok) {
      throw new Error(`Unable to obtain a Qoder SAT: HTTP ${response.status}`);
    }

    const result = (await response.json()) as {
      access_token: string;
      expires_in?: number;
    };

    return {
      token: result.access_token,
      expiresAt:
        result.expires_in === undefined
          ? undefined
          : Date.now() + result.expires_in * 1000,
    };
  }

  const q = query({
    prompt: 'Summarize the current deployment configuration.',
    options: {
      auth: serviceAccount({ fetchServiceAccountToken }),
    },
  });
  ```

  ```python Python
  import time

  import httpx

  from qoder_agent_sdk import (
      QoderAgentOptions,
      ServiceAccountTokenResult,
      service_account,
  )

  # Get the Service Account key from the host's secret manager adapter.
  service_account_key = read_secret("qoder-service-account-key")
  # Example: select the scopes used for model listing and inference.
  service_account_scopes = ("models.read", "chat.completions")

  async def fetch_service_account_token() -> ServiceAccountTokenResult:
      async with httpx.AsyncClient() as client:
          response = await client.post(
              "https://openapi.qoder.sh/api/v1/serviceToken/exchange",
              headers={"Authorization": f"Bearer {service_account_key}"},
              json={
                  "grant_type": "client_credentials",
                  "audience": "qoder",
                  "scope": " ".join(service_account_scopes),
                  "ttl_seconds": 3600,
              },
          )

      response.raise_for_status()
      result = response.json()
      return ServiceAccountTokenResult(
          token=result["access_token"],
          expires_at=(
              None
              if result.get("expires_in") is None
              else int(time.time()) + int(result["expires_in"])
          ),
      )

  options = QoderAgentOptions(
      auth=service_account(
          fetch_service_account_token=fetch_service_account_token
      )
  )
  ```
</CodeGroup>

The scope and callback return value are configured as follows:

- When obtaining the SAT, enter the scopes that it should contain. For example, use `models.read chat.completions` to list models and call the inference API.
- Put the SAT returned by Token exchange in the callback result. You can also provide the SAT expiry.
- When a valid SAT cannot be obtained, return `null` (`None` in Python) or throw an exception so the current session fails explicitly.

## Reuse the local sign-in

If the workstation is already signed in through `qodercli`, the SDK can use the same session. This method is suitable for a developer workstation, not stateless CI or production services.

<CodeGroup>
  ```typescript TypeScript
  import { qodercliAuth, query } from '@qoder-ai/qoder-agent-sdk';

  const q = query({
    prompt: 'Summarize the current workspace.',
    options: {
      auth: qodercliAuth(),
    },
  });
  ```

  ```python Python
  from qoder_agent_sdk import QoderAgentOptions, qodercli_auth

  options = QoderAgentOptions(auth=qodercli_auth())
  ```
</CodeGroup>

## Authentication Failure Callback

When the remote side rejects the token, the token expires, or the CLI exits with an authentication error, use `onAuthExpired` (TypeScript) / `on_auth_expired` (Python) to trigger a re-login or token-exchange flow. It fires at most once per SDK session.

<CodeGroup>
  ```python Python
  from qoder_agent_sdk import QoderAgentOptions, access_token_from_env

  def show_sign_in_required() -> None:
      print("Authentication has expired. Please sign in again.")

  options = QoderAgentOptions(
      auth=access_token_from_env(),
      on_auth_expired=show_sign_in_required,
  )
  ```
</CodeGroup>

The SDK does not automatically refresh PATs. After obtaining a new token, create a new SDK session with the new `auth` configuration.

## Authentication Errors

Python SDK authentication configuration errors raise exceptions with a `code`:

- Missing authentication configuration: `AuthNotConfiguredError`, with `code == "auth_not_configured"`.
- Missing PAT environment variable: `AuthAccessTokenEnvVarError`, with `code == "auth_access_token_env_var_not_configured"`.
- Missing Service Account Key environment variable: `AuthServiceAccountEnvVarError`, with `code == "auth_service_account_env_var_not_configured"`.

## Best Practices

- In production and CI, provide credentials through a secret manager; do not put credentials in source code.
- Do not write PATs, Service Account keys, or SATs to logs, error objects, or debug output.
- Configure a PAT or Service Account explicitly for automated environments instead of relying on the local `qodercli` sign-in.
- For user-facing applications, register the auth-expired callback to turn authentication failures into clear sign-in prompts.
- After updating or rotating credentials, create a new SDK session; do not reuse a session that has already failed authentication.
