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

# Admin setup

> One-time SSO, provider, routing-policy, and budget configuration for an organization rolling out LangWatch Governance.

This is the **admin** side of [Personal IDE keys](/docs/ai-gateway/governance/personal-keys).
Once configured, every developer in your organization can run
`langwatch login --device` and immediately get a working CLI.
No per-user setup, no IT tickets.

## Time budget

The numbers below are hands-on time at the LangWatch admin console, not net of organisational approvals.

| Path                                               | Hands-on time | Net of approvals                                                  |
| -------------------------------------------------- | ------------- | ----------------------------------------------------------------- |
| **Self-serve** (existing email signup, no SSO yet) | \~5 minutes   | \~5 minutes                                                       |
| **With Okta / Azure AD / Google Workspace SSO**    | \~30 minutes  | Days, depending on IT / security review of a new SAML or OIDC app |

If your IT team is the bottleneck, you can start on the self-serve path today (skip §1 below) and add SSO later. The providers, routing policies, and budgets you set up keep working unchanged when SSO drops in.

## Before you start: brand-new to LangWatch?

If this is your first LangWatch session, sign up at `/auth/signup`. The
post-signup flow routes you to `/onboarding/welcome` to create your
organization (name + ToS) and pick a flavor (Platform / Coding Agent /
MCP / Manual). Once that completes, you land at your first project, and
this guide picks up from there. The bootstrap creates: your
Organization, a Personal Team membership, an Owner RoleBinding on the
org, and your first Project. The subsequent steps assume all of this is
already in place.

If you already have a LangWatch account on the org you're configuring,
skip to §1 below.

## Prerequisites

* Organization owner or admin role at LangWatch.
* Workspace API keys for the providers you want to make available
  (one each from the providers your team needs: Anthropic, OpenAI,
  Gemini, Azure, Bedrock, Vertex).
* Your IdP's SAML metadata or OIDC discovery URL (Okta, Azure AD,
  Google Workspace, Auth0).

<Note>
  On SaaS, governance rollout is managed per organization: if you don't see the **Govern** menu, or AI-tools device login is refused with a governance error, contact your LangWatch account rep.

  You can reach `/governance` with no project created yet: governance is org-scoped, not project-scoped.
</Note>

## 1. Connect SSO

In **Settings → SSO**, click **Connect identity provider**.

<Tabs>
  <Tab title="Okta (SAML)">
    1. In Okta admin, create a new SAML 2.0 application.
    2. **Single sign-on URL**:
       `https://app.langwatch.ai/api/auth/sso/{your-org-slug}/callback`
    3. **Audience URI**: `https://app.langwatch.ai`
    4. Map the **NameID** to email and add the standard claims
       (`email`, `firstName`, `lastName`).
    5. Download the IdP metadata XML.
    6. Paste it into LangWatch's **Connect identity provider** form.
    7. (Optional) Enable **SCIM provisioning** so users are
       auto-created the first time they sign in.
  </Tab>

  <Tab title="Azure AD (OIDC)">
    1. In Azure portal, register a new application.
    2. Set redirect URI to
       `https://app.langwatch.ai/api/auth/sso/{your-org-slug}/callback`
    3. Generate a client secret.
    4. Note the tenant ID, client ID, client secret.
    5. Paste into LangWatch's **Connect identity provider** form.
  </Tab>

  <Tab title="Google Workspace (OIDC)">
    1. In Google Cloud Console, create OAuth 2.0 credentials.
    2. Set authorized redirect URI to
       `https://app.langwatch.ai/api/auth/sso/{your-org-slug}/callback`
    3. Restrict to your Workspace domain.
    4. Paste client ID + secret into LangWatch.
  </Tab>
</Tabs>

Once SSO is live, anyone with an `@yourdomain.com` email is
auto-routed to your IdP at login. You can disable password
authentication entirely from this page if you want SSO-only.

## 2. Connect providers

In **Settings → Model Providers**, add an entry per upstream:

1. Click **Add provider** and pick from the list (Anthropic, OpenAI,
   Azure OpenAI, Bedrock, Vertex, Gemini, custom OpenAI-compatible).
2. Paste the workspace API key (or AWS / GCP credentials).
3. Set the **scope**: `Organization` (everyone), `Team` (only members
   of one team), or `Project` (only one project).

When you start out, scope at `Organization` so the credential
is available everywhere. You can tighten later by creating
team-scoped overrides.

<Note>The provider entries here are the same ones the AI Gateway
already uses for service virtual keys. Personal keys reuse them. There is
no parallel pool.</Note>

## 3. Define routing policies

Open **AI Gateway → Routing Policies** and click **New policy**.

A policy is the answer to "when one of my users wants to call a
model, what providers can serve it, in what order?" See the
full reference at [Routing policies](/docs/ai-gateway/governance/routing-policies).

Two policies to create on day one:

<Steps>
  <Step title="developer-default">
    * Scope: Organization
    * Strategy: `priority`
    * Providers (in order): Anthropic, OpenAI, Gemini
    * Allowed models: `claude-3-5-*`, `claude-3-opus-*`,
      `gpt-4o*`, `gpt-5*`, `o1-*`, `o3-*`, `o4-*`, `gemini-2.5-*`
    * Mark as **default for ORG scope**

    This is what every user's personal key references unless a
    team-level override exists.
  </Step>

  <Step title="evaluator-default">
    * Scope: Organization
    * Strategy: `cost`
    * Providers: OpenAI, Gemini, Anthropic
    * Allowed models: `gpt-4o-mini`, `gpt-5-mini`,
      `gemini-2.5-flash`, `claude-3-5-haiku-*`
    * Mark as **default for the evaluator system VK** (your
      LangWatch-internal evaluators use this, which keeps eval
      costs predictable).
  </Step>
</Steps>

Policies cascade: a `TEAM`-scoped policy with `isDefault=true`
overrides the `ORG`-scoped default for members of that team.

<Note>
  **Smoke-test before you publish the portal**: the org-default routing
  policy MUST have at least one provider in its chain before personal
  keys can serve traffic. If you publish `/me` to teammates with an
  empty `developer-default` policy, every issued VK provisions
  successfully but the first call returns `504 provider_timeout` (the
  gateway has nothing to forward to). After §2 (Connect providers) +
  this section, mint a test key against your own user, fire one
  completion, and confirm a 200 before announcing the portal.
</Note>

## 4. Set per-user budgets

In **AI Gateway → Budgets**:

1. Click **New budget** and pick scope `User (default)`.
2. Set the monthly cap (e.g. `$500/mo`).
3. Click save.

Every personal VK auto-issued by `langwatch login` inherits this
budget. To exempt a power user, override at scope `User` with a
specific user ID and a higher limit.

You can also set:

* **Team budgets**: cap an entire team's total spend.
* **Project budgets**: cap a production agent's spend.
* **Org budgets**: a hard ceiling for the whole organization.

The gateway enforces the **strictest binding**: if any scope is
exceeded, the gateway rejects the request with a structured error
that identifies the blocking scope.

## 5. Roll out to your team

Send your team this snippet:

```text theme={null}
Welcome to LangWatch! To use AI tools at <YourCompany>:

1. Install: npm install -g langwatch     (or run via npx -y langwatch ...)
2. Sign in: langwatch login
3. Use it:  langwatch claude  (or codex / cursor / gemini)

Dashboard: https://app.langwatch.ai/me
Questions: <#langwatch slack channel>
```

The CLI ships as an npm package (the only canonical install path); see
the [LangWatch CLI guide](/docs/integration/cli) for full prerequisites and
self-hosted endpoint configuration.

The first time each user runs `langwatch login`, the
backend auto-provisions their personal team + project, mints a
personal VK against the `developer-default` routing policy, and
returns it in the same round-trip. They have a working CLI in
under a minute.

## What you'll see as users come online

In **AI Gateway → Activity**:

* Per-user request counts and spend, in real time.
* A **By tool** breakdown (Claude Code vs Codex vs Cursor).
* A **By model** breakdown.
* The full audit trail of `vk-lw-…` issuance, `langwatch login`
  events, budget violations, and policy denials.

In **Settings → Members**:

* A list of every user with their personal team auto-created.
* Per-user budget ceiling and current spend.
* A **Revoke all credentials** button per user, for off-boarding.

## Off-boarding flow

When someone leaves:

1. Disable them in your IdP (this is what your IT process already
   does).
2. The next time the LangWatch SCIM sync runs (or when their access
   token expires, \~1h), their credentials are dead.
3. (Optional, for immediate revocation) In **Settings → Members**,
   click **Revoke all credentials** for the user. This invalidates
   their refresh token and any active access tokens within 60s.

Their personal team and traces stay in the audit log for
compliance, but no new gateway calls succeed.

## CLI device-flow REST API (for custom clients)

The `langwatch` CLI uses a standard
[RFC 8628 device-code](https://www.rfc-editor.org/rfc/rfc8628) flow
against the control plane. Self-hosters who need to integrate
custom CLI clients, or audit the wire surface, can hit these
endpoints directly. The format is **snake\_case JSON** (matches
RFC 8628 + every other OAuth library). Origin enforcement applies
to all endpoints. Pass `Origin: <your-base-url>` from any client
outside a browser.

| Endpoint                                 | Purpose                                 | Success                                                                                                                             | Notable error states                                                                                                               |
| ---------------------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `POST /api/auth/cli/device-code`         | Mint a device-code + user-code pair     | `200` JSON `{ device_code, user_code, verification_uri, verification_uri_complete, expires_in, interval }`                          | `400 invalid_request`                                                                                                              |
| `POST /api/auth/cli/exchange`            | Long-poll for approval                  | `200` JSON `{ access_token, refresh_token, expires_in, refresh_expires_in, user, organization, default_personal_vk }`               | `428 authorization_pending` (keep polling), `408 expired_token`, `410 access_denied`, `429 slow_down` (re-poll too fast, back off) |
| `POST /api/auth/cli/refresh`             | Rotate access token                     | `200` JSON `{ access_token, refresh_token, expires_in, refresh_expires_in }` (refresh-token rotation, the old token is invalidated) | `401 invalid_grant` (CLI must wipe local state)                                                                                    |
| `POST /api/auth/cli/logout`              | Revoke a refresh-token server-side      | `200 { ok: true }` (idempotent)                                                                                                     | None                                                                                                                               |
| `GET /api/auth/cli/lookup?user_code=...` | Browser-side: verify a pasted user-code | `200 { user_code, status, created_at, expires_at }`                                                                                 | `401 unauthorized` (user must sign in first), `404 not_found`, `410 expired`                                                       |
| `POST /api/auth/cli/approve`             | Browser-side: user clicks "Approve"     | `200 { ok, personal_vk_label, organization_id }`                                                                                    | `401 unauthorized`, `403 forbidden` (caller not in target org), `409 already_resolved`                                             |
| `POST /api/auth/cli/deny`                | Browser-side: user clicks "Deny"        | `200 { ok }` (idempotent)                                                                                                           | `401 unauthorized`                                                                                                                 |

The browser approval surface is
`https://<your-base-url>/cli/auth?user_code=XXXX-YYYY` (the
`verification_uri_complete` field). Unauthenticated visitors are
bounced through SSO and return to the page automatically.

### Verified behavior

The reference server passes all of:

* Each `device-code` mint returns a unique `user_code` and `device_code`
* `user_code` is 8 chars, base32 alphabet (no I/O/0/1/L/U), dashed `XXXX-YYYY`
* `verification_uri` ends with `/cli/auth`
* `expires_in` defaults to 600s (configurable), `interval` defaults to 5s
* Polling `/exchange` faster than the per-device rate-limit (4s) returns `429 slow_down`
* Polling with an unknown / TTL-expired `device_code` returns `408 expired_token`
* `/refresh` with an unknown / expired token returns `401 invalid_grant`
* All browser-side endpoints (`/lookup`, `/approve`, `/deny`) require an
  active session cookie; unauthenticated requests get `401 unauthorized`
  with a body the CLI surfaces verbatim
* `/logout` is idempotent: passing an unknown / already-revoked refresh
  token still returns `200 { ok: true }`

## Verify it yourself

Mint a personal virtual key for your own account and send one request
through it. The spend then lands in the same surfaces your users and
admins read.

1. Open `/me`, pick a provider you connected in step 2, and mint a
   personal virtual key. The secret is shown once.
2. Send one completion through the gateway with that key, from the
   `langwatch` CLI or any OpenAI-compatible client.
3. Wait a few seconds for the trace pipeline, then open `/me/usage`
   for your own spend and `/governance` for the org-wide activity.

<Note>
  **Why `/me/usage` can read `$0.00`**: spend is computed from the
  `gateway_budget_ledger_events` fold, which writes ledger rows only
  when a Budget applies to the request. A personal virtual key with no
  Budget bound produces real spans in the trace store and no ledger
  rows, so `/me/usage` stays at `$0.00`.

  Bind a Budget to the user, to the personal team, or to the personal
  virtual key ([step 4](#4-set-per-user-budgets)) before you send the
  request, and the spend appears.
</Note>

## What's next

* [Routing policies](/docs/ai-gateway/governance/routing-policies) for
  provider order, model tiers, model name mapping, and restrictions.
* [`langwatch` CLI](/docs/integration/cli). The unified CLI lives in the
  TypeScript SDK. Its device-flow login sends every coding CLI
  through your governance plane.
