Skip to main content
Core Concepts

SDK Authentication

Every SDK session (TypeScript's query(), Python's query() / QoderSDKClient) must be configured with one authentication method. Java configures it on QoderClient.Builder.auth(), and every query and session created by that client uses the same method. A session can only use one:
Authentication methodIdentityUse case
Personal Access Token (PAT)A Qoder userScripts, CI, or host applications that need the user's permissions and data
Service AccountAn organization workloadBackend services, CI, and scheduled jobs that should not depend on a personal account
Local qodercli sessionThe currently signed-in userA 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:
  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

export QODER_PERSONAL_ACCESS_TOKEN="<your-qoder-personal-access-token>"
import { accessTokenFromEnv, query } from '@qoder-ai/qoder-agent-sdk';

const q = query({
  prompt: 'Summarize the current workspace.',
  options: {
    auth: accessTokenFromEnv(),
  },
});
The function reads QODER_PERSONAL_ACCESS_TOKEN by default. To use a custom variable name:
auth: accessTokenFromEnv('MY_QODER_PAT')
If options.env and the process environment contain the same variable, the SDK uses the value from options.env. Java reads the variable from the JVM process environment each time it starts the CLI; a same-named variable passed through putEnvironment() is not used for authentication. If the variable is unset or empty, startup fails with AuthenticationException. The variable is not passed to the CLI subprocess.

Pass a PAT directly

When a trusted backend has already obtained a PAT, pass it directly:
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),
  },
});
Java wraps credentials in SecretValue, whose toString() always returns [REDACTED]. SecretValue.of(char[]) copies the array, so the caller can zero it afterward. 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:
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(),
  },
});
The caller passes the Key into the session via serviceAccount({ serviceAccountKey }) (TypeScript) / service_account(service_account_key=...) (Python) / Auth.serviceAccountKey(SecretValue) (Java). The SDK and qodercli obtain and refresh a short-lived Service Account Token (SAT) for the session.
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. In Java the key is kept in the QoderClient authentication configuration, and every session the client creates afterward uses it. 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=...), Java's Auth.serviceAccountTokenProvider(...)). 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.
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:
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 }),
  },
});
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.
  • In Java the callback returns CompletionStage<SecretValue>. It carries only the token, with no expiry field; when no token is available, return an exceptionally completed stage. The SDK reports a fixed error message to the CLI and does not forward the callback's failure details.
  • Java invokes the callback on the client's callbackExecutor. Run slow work such as HTTP requests on the application's own executor and report the result through the returned stage, as in the example above. Do not block the callback thread.
  • Set connect and read timeouts on the HTTP request and release the connection when done. HttpURLConnection waits forever by default. The SDK's callback timeout (controlRequestTimeoutMs) only ends the authentication request; it does not stop an HTTP call that is still running. If the server never responds, the task keeps the executor thread busy, later authentication requests queue behind it, and the JVM cannot exit normally.

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.
import { qodercliAuth, query } from '@qoder-ai/qoder-agent-sdk';

const q = query({
  prompt: 'Summarize the current workspace.',
  options: {
    auth: qodercliAuth(),
  },
});
The Java CLI subprocess inherits only PATH by default. Variables the CLI needs to read the sign-in state, such as HOME, must be passed explicitly. See Subprocess environment (Java).

Authentication Failure Callback (TypeScript, Python)

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.
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,
)
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".
Java SDK authentication errors all extend QoderException; tell them apart with getCode():
  • auth() was not called: QoderClient.Builder.build() throws InvalidOptionsException synchronously, and getCode() is INVALID_OPTIONS.
  • The variable read by Auth.accessTokenEnv() is unset or empty: startup fails with AuthenticationException, and getCode() is AUTHENTICATION_FAILED.
  • The CLI exits with an authentication failure (exit code 41): AuthenticationException with getCode() CLI_AUTH_FATAL. Java has no authentication failure callback. Catch the exception, prompt the user to sign in again or replace the credential, then create a new query or session.
Startup and runtime failures are delivered through the message cursor, the stage returned by consume(), or the stage returned by openSession(). For the full list, see Java exception list.

Subprocess environment (Java)

When the Java SDK starts the CLI subprocess, it inherits only the parent's PATH by default. On Windows it also inherits system directory variables such as SystemRoot, TEMP, TMP, USERPROFILE, APPDATA, and LOCALAPPDATA. Pass any other variable the CLI needs, such as HOME, locale settings, or a proxy address, explicitly through SessionOptions.Builder:
Java
SessionOptions.Builder options = SessionOptions.builder();
String home = System.getenv("HOME");
if (home != null) {
    options.putEnvironment("HOME", home);
}
String proxy = System.getenv("HTTPS_PROXY");
if (proxy != null) {
    options.putProxyEnvironment("HTTPS_PROXY", SecretValue.of(proxy));
}
SessionOptions sessionOptions = options.build();
  • putEnvironment(String, String) adds one variable and environment(Map) adds several; neither clears variables set earlier.
  • A proxy address can contain a username and password, so pass it with putProxyEnvironment(String, SecretValue). The SDK redacts its credentials in diagnostics and stderr. Proxy variables such as HTTPS_PROXY passed through putEnvironment are handled the same way.
  • Credential variables managed by the SDK, such as QODER_PERSONAL_ACCESS_TOKEN, are never passed to the CLI and are stripped even when set explicitly. Authentication can only be configured through Auth.

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.