# How pi-web Integrates with Pi's Auth System for OAuth and API Keys

> Discover how pi-web integrates with Pi's auth system for OAuth and API keys. Learn to manage authentication seamlessly using the @earendil-works/pi-coding-agent SDK.

- Repository: [Alex Yang/pi-web](https://github.com/agegr/pi-web)
- Tags: deep-dive
- Published: 2026-07-26

---

**pi-web acts as a thin HTTP wrapper around Pi's `ModelRuntime` class, delegating all OAuth and API-key operations to the `@earendil-works/pi-coding-agent` SDK while managing interactive prompts via Server-Sent Events.**

The `agegr/pi-web` repository implements a complete authentication layer that bridges the Pi coding agent's runtime with a web-based UI. By leveraging the **`ModelRuntime`** class provided by the Pi SDK, pi-web exposes REST endpoints that handle both interactive OAuth flows and static API-key storage without persisting sensitive credentials in the application layer.

## Architecture Overview

### The ModelRuntime Wrapper

At the core of pi-web's auth integration is the **`ModelRuntime`** class from `@earendil-works/pi-coding-agent`. Each authentication endpoint creates a fresh runtime instance via `ModelRuntime.create()` to access configured providers and stored credentials. This design keeps the web layer stateless while delegating credential management to the underlying Pi runtime.

All auth-related API routes live under `app/api/auth/` and follow a consistent pattern: initialize the runtime, execute a login or logout operation with custom prompt handlers, and call **`invalidateModelsCache()`** to refresh the available model list.

## OAuth Authentication Flow

### Listing Available Providers

The entry point for OAuth discovery is `GET /api/auth/providers`, implemented in [`app/api/auth/providers/route.ts`](https://github.com/agegr/pi-web/blob/main/app/api/auth/providers/route.ts). This endpoint instantiates a `ModelRuntime` and calls `modelRuntime.getProviders()` to retrieve the full provider catalog.

The route filters the results to exclude non-OAuth providers (such as Anthropic) and enriches each entry with a **`loggedIn`** boolean indicating whether valid credentials already exist. The response includes the provider's ID, display name, and authentication status, enabling the UI to show appropriate connection buttons.

### SSE-Based OAuth Login

The OAuth handshake operates through `app/api/auth/login/[provider]/route.ts`, which implements a **Server-Sent Events (SSE)** stream for bidirectional communication without WebSockets.

When a client opens `GET /api/auth/login/[provider]`, the server:

1. Creates a `ModelRuntime` instance
2. Invokes `modelRuntime.login(provider, "oauth", { … })` with a custom prompt callback
3. Registers a one-time token in **`globalThis.__piLoginCallbacks`** that maps to a Promise awaiting user input

As the OAuth flow progresses, `ModelRuntime` emits prompt requests (`select`, `prompt`, `manual_code`) that pi-web forwards to the client via SSE messages (`select_request`, `prompt_request`, `device_code`). The client renders the appropriate UI and submits responses via `POST /api/auth/login/[provider]`, which resolves the stored Promise in the callback registry.

Upon successful authentication, the endpoint calls **`invalidateModelsCache()`** to ensure the newly available models appear in the UI immediately.

## API Key Management

### Storing API Keys

For providers that require static credentials, pi-web exposes `app/api/auth/api-key/[provider]/route.ts` supporting `GET`, `POST`, and `DELETE` methods.

The `GET` handler returns the provider's authentication status and configuration source without exposing the secret. The `POST` handler receives the raw API key from the client and executes `modelRuntime.login(provider, "api_key", { … })`. During this flow, the runtime prompts the user to select the appropriate secret field (`api-key` or `bearer-token`) via the callback mechanism, then accepts the submitted key through a `secret` prompt type.

After successful storage, the endpoint triggers `invalidateModelsCache()` to refresh the model catalog.

### Removing Credentials

The `DELETE` method on the same route invokes `modelRuntime.logout(provider)`, which erases the stored API key or OAuth tokens from the Pi runtime's credential store. This operation also invalidates the models cache to remove provider-specific models from the UI.

## Cache Synchronization Pattern

Every authentication state change—whether OAuth completion, API-key storage, or logout—concludes with a call to **`invalidateModelsCache()`**. This function ensures the UI receives an updated list of available models based on the current credential set. By centralizing cache invalidation in the auth layer, pi-web maintains consistency between the Pi runtime's credential state and the web interface's provider selection.

## Implementation Examples

The following examples demonstrate how to interact with pi-web's authentication endpoints from a client application.

Fetch the list of OAuth-enabled providers:

```typescript
// GET /api/auth/providers
fetch('/api/auth/providers')
  .then(r => r.json())
  .then(data => console.log('Providers:', data.providers));

```

Initiate an OAuth login using Server-Sent Events:

```typescript
// GET /api/auth/login/[provider] (SSE stream)
const provider = 'openai';
const es = new EventSource(`/api/auth/login/${provider}`);

es.onmessage = ev => {
  const msg = JSON.parse(ev.data);
  
  if (msg.type === 'select_request') {
    // Show UI for user selection, then POST response
    fetch(`/api/auth/login/${provider}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ token: msg.token, code: selectedOption })
    });
  }
  // Handle prompt_request, auth, device_code, success, error...
};

```

Submit an API key for storage:

```typescript
// POST /api/auth/api-key/[provider]
fetch('/api/auth/api-key/openai', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ apiKey: 'sk-...' })
})
  .then(r => r.json())
  .then(res => console.log('API key stored:', res.success));

```

Remove stored credentials:

```typescript
// DELETE /api/auth/api-key/[provider]
fetch('/api/auth/api-key/openai', { method: 'DELETE' })
  .then(r => r.json())
  .then(res => console.log('Key removed:', res.success));

```

## Summary

- **pi-web delegates authentication to Pi's `ModelRuntime`** from `@earendil-works/pi-coding-agent`, creating a fresh runtime instance for each request in `app/api/auth/`.
- **OAuth flows use SSE streams** managed in `app/api/auth/login/[provider]/route.ts`, with interactive prompts handled through `globalThis.__piLoginCallbacks`.
- **API keys are stored via `modelRuntime.login()`** with type `"api_key"` in `app/api/auth/api-key/[provider]/route.ts`, supporting GET, POST, and DELETE operations.
- **Cache invalidation** via `invalidateModelsCache()` ensures the UI syncs with credential changes immediately after login or logout.

## Frequently Asked Questions

### What is ModelRuntime in pi-web?

**`ModelRuntime`** is the core class provided by the `@earendil-works/pi-coding-agent` SDK that pi-web wraps to handle authentication. It manages provider configurations, credential storage, and OAuth negotiations. In pi-web, each auth endpoint creates a runtime via `ModelRuntime.create()` to access these capabilities without implementing auth logic directly.

### How does pi-web handle OAuth callbacks without blocking the server?

pi-web implements OAuth as an **SSE (Server-Sent Events)** stream in `app/api/auth/login/[provider]/route.ts`. Instead of blocking during user interaction, the server registers a Promise in **`globalThis.__piLoginCallbacks`** and sends prompt requests to the client. The client POSTs responses back to the same endpoint, which resolves the Promise and continues the OAuth flow asynchronously.

### Can pi-web store both OAuth and API key credentials for the same provider?

Yes. The `ModelRuntime` class supports multiple authentication methods per provider. pi-web exposes separate endpoints for OAuth (`/api/auth/login/[provider]`) and API keys (`/api/auth/api-key/[provider]`), allowing users to choose their preferred authentication method. The runtime manages these credentials independently.

### Where are authentication credentials stored in pi-web?

pi-web does not store credentials in its own application layer. Instead, it delegates storage to the **Pi runtime** via `ModelRuntime.login()` and `ModelRuntime.logout()`. The web layer only transiently handles secrets during the API key submission POST request, while the underlying Pi SDK manages secure persistence and retrieval.