Skip to main content
Sentry CLI uses OAuth 2.0 device flow (RFC 8628) for secure authentication without requiring a browser redirect. Tokens are stored locally in SQLite with automatic refresh support.

Authentication Methods

The CLI supports three authentication methods, checked in this priority order:
  1. SENTRY_AUTH_TOKEN environment variable (highest priority)
  2. SENTRY_TOKEN environment variable (legacy)
  3. OAuth tokens stored in SQLite (lowest priority)

Environment Variable Authentication

The simplest way to authenticate is via environment variables:
export SENTRY_AUTH_TOKEN="your-token-here"
sentry issue list
Tokens from environment variables:
  • Bypass all expiry and refresh logic
  • Are never written to disk
  • Take priority over stored OAuth tokens
  • Empty or whitespace-only values are treated as unset
SENTRY_AUTH_TOKEN takes precedence over SENTRY_TOKEN to match the legacy sentry-cli behavior.

OAuth Device Flow

The recommended authentication method is OAuth device flow:
sentry auth login
This initiates the device authorization flow:
  1. CLI requests a device code from Sentry’s /oauth/device/code/ endpoint
  2. CLI displays a user code and verification URL
  3. User visits the URL and enters the code (or scans QR code)
  4. CLI polls /oauth/token/ endpoint until user completes authorization
  5. Access token and refresh token are stored in SQLite

Device Flow Implementation

The device flow is implemented in src/lib/oauth.ts:
export async function performDeviceFlow(
  callbacks: DeviceFlowCallbacks,
  timeout = 600_000 // 10 minutes
): Promise<TokenResponse> {
  // Step 1: Request device code
  const {
    device_code,
    user_code,
    verification_uri,
    verification_uri_complete,
    interval,
  } = await requestDeviceCode();

  // Step 2: Show user code
  await callbacks.onUserCode(
    user_code,
    verification_uri,
    verification_uri_complete
  );

  // Step 3: Poll for token
  let pollInterval = interval;
  while (Date.now() < timeoutAt) {
    await sleep(pollInterval * 1000);
    const result = await attemptPoll(device_code);
    
    switch (result.status) {
      case "success":
        return result.token;
      case "slow_down":
        pollInterval += 5; // Increase interval if rate-limited
        continue;
    }
  }
}

OAuth Scopes

The CLI requests the following scopes:
  • project:read - Read project data
  • project:write - Write project data
  • org:read - Read organization data
  • event:read - Read events
  • event:write - Write events
  • member:read - Read organization members
  • team:read - Read team data

Token Storage

OAuth tokens are stored in SQLite at ~/.sentry/config.db:
CREATE TABLE auth (
  id INTEGER PRIMARY KEY,
  token TEXT,
  refresh_token TEXT,
  expires_at INTEGER,
  issued_at INTEGER,
  updated_at INTEGER
);
The auth table uses a single-row pattern (always id = 1) to store the active credential.

Token Retrieval Priority

The getAuthToken() function checks sources in priority order:
export function getAuthToken(): string | undefined {
  // 1. Check environment variables first
  const envToken = getEnvToken();
  if (envToken) {
    return envToken.token;
  }

  // 2. Fall back to SQLite-stored token
  const db = getDatabase();
  const row = db.query("SELECT * FROM auth WHERE id = 1").get();
  
  if (!row?.token) {
    return undefined;
  }
  
  // Verify token hasn't expired
  if (row.expires_at && Date.now() > row.expires_at) {
    return undefined;
  }
  
  return row.token;
}

Automatic Token Refresh

OAuth tokens are automatically refreshed when:
  • Less than 10% of the token’s lifetime remains (default threshold)
  • A 401 Unauthorized response is received from the API
  • Forced via sentry auth refresh

Refresh Flow

The refresh flow is implemented in src/lib/db/auth.ts:
export async function refreshToken(
  options: RefreshTokenOptions = {}
): Promise<RefreshTokenResult> {
  // Environment tokens never expire
  const envToken = getEnvToken();
  if (envToken) {
    return { token: envToken.token, refreshed: false };
  }

  const row = db.query("SELECT * FROM auth WHERE id = 1").get();
  
  const remainingRatio = (expiresAt - now) / (expiresAt - issuedAt);
  
  // Refresh if less than 10% lifetime remains
  if (remainingRatio > REFRESH_THRESHOLD && now < expiresAt) {
    return { token: row.token, refreshed: false };
  }
  
  // Use refresh token to get new access token
  const tokenResponse = await refreshAccessToken(row.refresh_token);
  await setAuthToken(
    tokenResponse.access_token,
    tokenResponse.expires_in,
    tokenResponse.refresh_token
  );
  
  return {
    token: tokenResponse.access_token,
    refreshed: true,
    expiresIn: tokenResponse.expires_in
  };
}
Token refresh uses a singleton promise to prevent concurrent refresh attempts during parallel API calls.

Self-Hosted Sentry

For self-hosted Sentry instances, configure both URL and client ID:
export SENTRY_URL="https://sentry.yourcompany.com"
export SENTRY_CLIENT_ID="your-oauth-client-id"
sentry auth login

Creating an OAuth App

To use OAuth with self-hosted Sentry (requires 26.1.0+):
  1. Navigate to Settings → Developer Settings in your Sentry instance
  2. Create a new public OAuth application
  3. Copy the Client ID
  4. Set SENTRY_CLIENT_ID environment variable
OAuth device flow requires Sentry 26.1.0 or later. For older versions, use sentry auth login --token with an API token instead.

Configuration Resolution

The CLI reads configuration lazily (not at module load) to respect environment variables set after import:
function getSentryUrl(): string {
  return process.env.SENTRY_URL ?? "https://sentry.io";
}

function getClientId(): string {
  return process.env.SENTRY_CLIENT_ID ?? SENTRY_CLIENT_ID_BUILD;
}
This allows URL parsing from command arguments to set SENTRY_URL before the OAuth flow begins.

Authentication State Commands

Check Status

sentry auth status
Shows the current authentication state and token source.

Manual Refresh

sentry auth refresh
Forces a token refresh even if the current token hasn’t expired.

Logout

sentry auth logout
Clears stored tokens and related cached data:
  • Auth tokens
  • User info cache
  • Organization region cache
  • Pagination cursors
Logging out only clears SQLite-stored tokens. Environment variable tokens remain active.

Token Source Tracking

The CLI tracks where each token originated via the AuthSource type:
type AuthSource = 
  | "env:SENTRY_AUTH_TOKEN"
  | "env:SENTRY_TOKEN"
  | "oauth";

type AuthConfig = {
  token: string;
  refreshToken?: string;
  expiresAt?: number;
  issuedAt?: number;
  source: AuthSource;
};
This allows commands to conditionally skip refresh logic for environment tokens and provide accurate status messages.

Error Handling

Authentication errors use the AuthError class with specific reason codes:
type AuthErrorReason = 
  | "not_authenticated"    // No token found
  | "expired"              // Token expired and can't refresh
  | "invalid";             // Token rejected by API

throw new AuthError(
  "expired",
  "Session expired. Run 'sentry auth login' to re-authenticate."
);
The CLI automatically triggers the login flow when AuthError is thrown, providing a seamless authentication experience.

Build docs developers (and LLMs) love