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

> ## Agent Instructions
> For agents: install the OpenWork desktop app with `brew install --cask openwork` (macOS) or from https://openworklabs.com/download. Do not run `npx openwork`; the npm package named `openwork` is a different project.
> Connect an agent to OpenWork with the MCP Gateway: `claude mcp add --transport http openwork https://api.openworklabs.com/mcp/agent`. Teams sign up at https://app.openworklabs.com?mode=sign-up.
> Step-by-step agent guide: https://openworklabs.com/docs/start-here/use-openwork-from-an-ai-agent.md
> OpenWork in Slack is private alpha for approved organizations. Setup guide: https://openworklabs.com/docs/slack/overview.md. Agent skill: https://openworklabs.com/.well-known/agent-skills/set-up-openwork-slack/SKILL.md.

# Enable OpenWork Gateway

> Configure Gateway with matching images, explicit destinations, and shared secrets for admins in every organization.

OpenWork Gateway routes model requests to configured providers with server-held credentials, model groups, credential sets, and access grants. It runs from `ee/apps/gateway`, normally on port `8791`.

It is **not** `den-gateway`, the separate OpenWork Web proxy, and it is not the [OpenWork MCP gateway](https://openworklabs.com/docs/cloud/run-in-the-cloud/cloud-mcp.md). **OpenWork Models** is still the managed model catalog and subscription product. Its billing, keys, and legacy API contracts are separate.

> [!WARNING]
>
> This guide requires the Gateway-capable chart implementation introduced in chart `0.2.0` and matching Den API, Den Web, and Gateway images from this release. Chart version alone is not evidence that an image supports deployment capability version `1`. The checkout's `Chart.appVersion: "0.1.0"` is a packaging placeholder, not a compatible image recommendation. Older stable images do not gain support by using new values. For an existing database, complete [Upgrade Gateway safely](https://openworklabs.com/docs/self-host/gateway-upgrade.md) before starting the new images.

## Deployment configuration and admin access

Gateway is generally available to admins and above in every organization when the deployment is configured. New organizations require no opt-in or platform-admin grant; there is no organization flag or API toggle.

| Control | Who changes it | Meaning |
| - | - | - |
| `GATEWAY_ENABLED=true` | Deployment operator | Validated deployment intent; advertises `deploymentCapabilities: { version: 1, aiGateway: true }` and admits Gateway management requests. Helm sets it through `gateway.enabled: true`. |

Neither organization mode, a running legacy inference service, nor the presence of billing/provider credentials opts a deployment in. With `GATEWAY_ENABLED` absent, the deployment capability is false and existing OpenWork Models behavior is preserved. Hosted operators explicitly set `GATEWAY_ENABLED=true` through their existing hosting environment mechanism on Den API and Gateway; hosted deployments are not assumed to use Helm. These remain installation configuration checks, not organization rollout controls.

Do not create a replacement flag, put deployment capabilities in organization metadata, or use ordinary organization metadata updates to grant dashboard access. Management APIs still enforce organization-admin permissions and fresh authentication for privileged writes. Regular members do not gain shared credential administration.

## Prepare the deployment

1. Select matching, immutable release image tags for Den API, Den Web, and Gateway. Remove or reconcile component-specific image tags that would override the shared `image.tag`.
2. Initialize or migrate the database with a reviewed procedure, and verify the release schema before starting application traffic. A new empty database can use this release's current-schema bootstrap; an existing database requires the [upgrade procedure](https://openworklabs.com/docs/self-host/gateway-upgrade.md).
3. Precreate the database/authentication Secret in the release namespace using your secret manager. Den API and Gateway must use the same database and the same existing `DEN_DB_ENCRYPTION_KEY`; do not generate a new encryption key during an upgrade.
4. Configure the internal Gateway origin and a separate desktop-reachable Gateway origin, DNS, trusted certificates, and network access.
5. Render and inspect the values, deploy the matching images, verify the authenticated deployment contract, then verify Gateway access as an organization admin without an organization opt-in.

### Helm values

This fragment is for the Gateway-capable chart `0.2.0` or later **with the matching release images**. Merge it with the normal Den deployment values. Replace the image tag and precreated Secret name with your approved references, not secret values.

```yaml theme={null}
image:
  tag: REPLACE_MATCHING_RELEASE_IMAGE_TAG
gateway:
  enabled: true
config:
  databaseMode: mysql
  internal:
    gatewayProxyBaseUrl: http://openwork-ee-inference:8791
    inferenceProxyBaseUrl: https://existing-models.example.com # Keep your existing Models endpoint.
  public:
    gatewayPublicBaseUrl: https://gateway.example.com
secret:
  create: false
  existingSecret: REPLACE_PRECREATED_DEN_SECRET
  keys:
    databaseUrl: DATABASE_URL
    betterAuthSecret: BETTER_AUTH_SECRET
    denDbEncryptionKey: DEN_DB_ENCRYPTION_KEY
```

Use your release's actual retained Service name instead of assuming `openwork-ee-inference`. Gateway continues to use the `*-inference` Deployment, Service, container name, selectors, and default `ghcr.io/different-ai/openwork-inference` image repository. No resource rename is required.

The chart does not create a Gateway ingress automatically. Provision its desktop-reachable HTTPS endpoint through your ingress, service mesh, or `gateway.service` load balancer configuration. Existing web/API ingress settings do not publish Gateway for you.

### Database and encryption references

| `config.databaseMode` | Required shared Secret key settings | Den API / Gateway runtime variables |
| - | - | - |
| `mysql` | `secret.keys.databaseUrl` | `DATABASE_URL` |
| `planetscale` | `secret.keys.databaseHost`, `databaseUsername`, `databasePassword` | `DATABASE_HOST`, `DATABASE_USERNAME`, `DATABASE_PASSWORD` |
| Both modes | `secret.keys.denDbEncryptionKey` | `DEN_DB_ENCRYPTION_KEY` |

The default key names match the runtime variables. Key remapping is supported: set each `secret.keys` entry to the corresponding data key in your precreated Secret. When explicitly enabled, Den API and Gateway receive required `secretKeyRef` entries; Helm cannot read or validate the contents of an existing Secret. Keep the normal Den authentication keys as well, including the key selected by `secret.keys.betterAuthSecret`.

The encryption key must contain at least 32 characters after trimming. MySQL mode requires a valid `mysql://` URL with username, host, database name, and valid port. PlanetScale mode requires a valid database hostname and nonempty username/password. Production must not use loopback database hosts. Require verified database TLS and use [custom CA support](https://openworklabs.com/docs/start-here/certificate-trust-and-proxies.md) where needed.

Migration connectivity is separate from runtime mode. Whenever `migrations.enabled: true`, the chart always supplies the Job's `DATABASE_URL` from the existing `secret.keys.databaseUrl` mapping, plus `DEN_DB_ENCRYPTION_KEY` from `secret.keys.denDbEncryptionKey`. This also applies with `config.databaseMode: planetscale`: runtime host/user/password credentials do not replace bootstrap's TCP MySQL connection. The mapped key in the precreated Secret must contain a TCP `mysql://` URL for the **same database**, including its database name, port, and approved TLS options. No new migration values are needed; the chart does not expose bootstrap's alternative `DATABASE_NAME`/`DATABASE_PORT` configuration. Verify TCP reachability, TLS trust, and migration privileges separately. With `migrations.enabled: false`, the chart skips this migration-specific validation, but the external [upgrade procedure](https://openworklabs.com/docs/self-host/gateway-upgrade.md) still needs its reviewed migration connection.

Use `secret.create: false` for these examples. Chart-created Secrets use `secret.values`, and the current pre-install hook inlines their credentials into the Job. Do not place literal credentials in values, commands, documentation, or diagnostic output.

### Internal and public origins

* `GATEWAY_PROXY_BASE_URL` is the internal/service-to-service destination. Set it on Den API and Gateway. Internal HTTP is permitted across a trusted private boundary.
* `GATEWAY_PUBLIC_BASE_URL` is the desktop-reachable destination, not the internal Service address. Den API uses it in member Gateway provider configurations. Configure it on both services for shared startup validation.
* Models clients retain an explicit, valid desktop origin from `INFERENCE_PROXY_BASE_URL`; otherwise they use a valid `GATEWAY_PUBLIC_BASE_URL`. This selection is independent of `GATEWAY_ENABLED`: changing only management enablement does not replace a known public destination with the internal proxy. Preserve your existing Models HTTPS hostname even when Gateway management is disabled on the deployment. No subscription, key, or upstream-routing changes are required.
* Both must be explicit origins without credentials, non-root paths, queries, or fragments. A trailing `/` is allowed. Do not append `/api/v1` to either value.
* Production public origins require HTTPS and a non-local qualified hostname or valid non-loopback IP address. Private DNS is possible, but member devices must resolve it, route to it, and trust its certificate. Internal `.svc` or `.internal` names are not valid production public origins.
* Helm validates production-style origins statically; startup validation does not check DNS, TLS, network reachability, or schema readiness. Local development exceptions require `OPENWORK_DEV_MODE=1` and a non-production `NODE_ENV`, not a production workaround.

`config.internal.gatewayProxyBaseUrl` wins over `config.internal.inferenceProxyBaseUrl` **by presence**. An explicitly empty canonical URL clears the legacy value and fails enabled-Gateway validation. If the canonical key is absent, an explicitly configured legacy URL can satisfy the internal requirement. The automatically generated legacy Service URL alone does not satisfy new opt-in. There is no inferred public fallback for enabled deployments.

Helm preserves nonempty `config.internal.inferenceProxyBaseUrl` in `INFERENCE_PROXY_BASE_URL` when a canonical proxy or public destination is configured, and uses the configured public Gateway origin when the legacy value is unset/empty. Despite its legacy `internal` name, keep the existing Models client endpoint in this setting. Shared public configuration is retained when management is disabled. Internal proxy resolution still follows canonical presence, including an explicit empty clear. With no usable public destination, disabled deployments retain historical Models fallback behavior; invalid optional Gateway settings do not add new startup requirements. That compatibility fallback can be private or loopback, so configure a valid public destination for desktop clients. A URL change does not repair previously stored client configurations automatically.

## Canonical environment reference

These are native runtime variables, not values to inject indiscriminately through Helm per-app `env`. Helm owns shared enablement, URLs, database references, ports, and optional tokens; conflicting per-app overrides are rejected. Den Web consumes the authenticated API capability, not a separate Web environment toggle.

| Variable | Required or default | Alias / scope |
| - | - | - |
| `GATEWAY_ENABLED` | Required explicit `true` to opt in; absent or exact `false` disables capability. Empty, `1`, or other spellings fail startup. | No legacy enablement alias. Den API and Gateway. |
| `GATEWAY_PROXY_BASE_URL` | Explicit internal origin required when enabled. | `INFERENCE_PROXY_BASE_URL` only when canonical is absent. Den API and Gateway. |
| `GATEWAY_PUBLIC_BASE_URL` | Explicit desktop-reachable origin required when enabled. | No deprecated alias. Den API and Gateway. |
| `DB_MODE` | `mysql` or `planetscale`; if absent, inferred as `mysql` when `DATABASE_URL` is set, otherwise `planetscale`. | Use explicit `config.databaseMode` with Helm. |
| Database variables and `DEN_DB_ENCRYPTION_KEY` | Required as described above; Gateway requires its database and encryption configuration even when capability is disabled. | No `GATEWAY_*` replacement for these names. |
| `GATEWAY_PORT` | `8791`; integer `1` through `65535`. | Precedence: `GATEWAY_PORT`, then `PORT`, then `INFERENCE_PORT`. Gateway only; use Helm `gateway.containerPort`. |
| `GATEWAY_ADMIN_TOKEN` | Optional; absent/empty disables bearer access to internal admin/rollup endpoints. | `INFERENCE_ADMIN_TOKEN`. Gateway; not an organization-dashboard login token. |
| `GATEWAY_WEBHOOK_SECRET` | Optional; absent/empty disables the webhook credential in production. | `INFERENCE_WEBHOOK_SECRET`. Gateway. |
| `GATEWAY_UPSTREAM_TIMEOUT_MS` | Provider forwarding (`upstreamTimeoutMs`): `1800000`; managed Models chat (`managedUpstreamTimeoutMs`): `120000` when neither alias is set. Explicit configuration applies to both. Integer `1000` through `86400000`. | `INFERENCE_UPSTREAM_TIMEOUT_MS`. Gateway; see route-specific behavior below. |
| `GATEWAY_STREAM_IDLE_MS` | `120000`; integer `1000` through `900000`. | `INFERENCE_STREAM_IDLE_MS`. Managed Models chat streaming, not the provider relay. |
| `GATEWAY_RESPONSE_START_MS` | `20000`; integer `1000` through `86400000`. | No alias. Streaming requests (managed chat and SSE provider protocols) whose provider has not answered by then get `200 text/event-stream` with SSE comment heartbeats; a later provider failure becomes one error event in the request's protocol. Keep it well below any proxy response timeout in front of Gateway (Cloudflare: 100 seconds, answered as HTTP 524). |
| `GATEWAY_RESPONSE_HEARTBEAT_MS` | `15000`; integer `1000` through `90000`. | No alias. Interval of those heartbeats while the provider has not answered. |
| `GATEWAY_CREDITS_PER_DOLLAR` | `1000000`; positive numeric value. | `INFERENCE_CREDITS_PER_DOLLAR`. Gateway accounting; does not enable a product. |
| `GATEWAY_EGRESS_ALLOWED_ORIGINS` | Optional comma-separated exact origins; empty means no exceptions to public-HTTPS egress policy. | `INFERENCE_EGRESS_ALLOWED_ORIGINS`. Set the same policy on Den API and Gateway. |
| `CORS_ORIGINS` | Optional comma-separated browser origins. Enabled Gateway requires exact origins (no wildcards); production opt-in requires non-local HTTPS. Disabled deployments retain legacy parsing, including `*`. | No new alias. Gateway CORS settings are not an egress allowlist. |
| `OPENROUTER_UPSTREAM_URL` | Optional; defaults to `https://openrouter.ai/api/v1`. With opt-in, production requires non-local HTTPS, no credentials/query/fragment; a provider API path is allowed. | Existing Models upstream setting, not a renamed Gateway destination. |
| `OPENAI_REALTIME_API_KEY` | Optional for the existing realtime integration; falls back to nonempty `OPENAI_API_KEY`. | Existing integration settings, not required for basic Gateway opt-in. Inject through secret management. |

Canonical alias precedence is based on whether the variable is set, including an empty string. Empty token values disable that credential; an empty egress allowlist removes inherited exceptions rather than merging them. Invalid or empty canonical numeric values fail startup instead of falling back. An empty canonical proxy URL fails validation when enabled; legacy disabled behavior is not a supported opt-in shortcut. Keep `OPENWORK_INFERENCE_BASE_URL`, `STRIPE_INFERENCE_PRICE_ID`, and `config.inference.*` Models contracts unchanged.

The timeout names describe different handlers, not interchangeable products. In `gateway.ts`, `/api/v1/providers/:inferenceProviderId/*` uses `env.upstreamTimeoutMs` for the upstream lifetime, including response relay. In `proxy.ts`, the retained `/api/v1/chat/completions` API uses `env.managedUpstreamTimeoutMs` for managed OpenWork Models: response headers and non-streaming bodies have separate timers, while successful streaming uses `env.streamIdleMs`. The shared timeout override sets both timeout fields, but does not make their timing behavior identical.

Egress exceptions can permit private destinations or HTTP, so they require a security review of exact operator-owned origins. Do not use wildcards or confuse upstream egress exceptions with the internal/public Gateway URLs. Default egress checks reject private/reserved addresses and redirects.

### Optional admin, webhook, and retention

Canonical Helm enablement does not implicitly enable admin or webhook credentials. To opt in, reference keys in the same precreated shared Secret:

```yaml theme={null}
gateway:
  enabled: true
  admin:
    enabled: true
  webhook:
    enabled: true
secret:
  create: false
  existingSecret: REPLACE_PRECREATED_DEN_SECRET
  keys:
    gatewayAdminToken: GATEWAY_ADMIN_TOKEN
    gatewayWebhookSecret: GATEWAY_WEBHOOK_SECRET
```

The optional canonical key settings override `secret.keys.inferenceAdminToken` and `secret.keys.inferenceWebhookSecret` by presence. Without canonical settings, the default Secret data keys remain `INFERENCE_ADMIN_TOKEN` and `INFERENCE_WEBHOOK_SECRET`. Disabled features explicitly clear both runtime token aliases, preventing `envFrom` from enabling them accidentally. Do not set token values in `gateway.env` or `inference.env`.

Enable retention only after verifying the accounting migrations and policy. It is a product maintenance CronJob, not a member Automation:

```yaml theme={null}
gateway:
  enabled: true
  retention:
    enabled: true
    adminTokenSecret: REPLACE_PRECREATED_RETENTION_SECRET
    adminTokenKey: GATEWAY_ADMIN_TOKEN
```

The retention Secret reference is authoritative for **both** `GATEWAY_ADMIN_TOKEN` and `INFERENCE_ADMIN_TOKEN` on the Gateway and CronJob, even if the shared Secret has different tokens. It enables admin access without `gateway.admin.enabled`. Service enablement is also required.

The job runs hourly, matching the hosted deployment. Each run calls the retained internal Service's `/internal/rollups/run`, folds request logs older than two days into hourly usage rollups, and deletes the folded rows in the same transaction; hourly rollups older than 90 days fold into daily rollups. The usage dashboard reads raw logs and rollups together, so totals stay correct between runs.

| Setting | Default | Notes |
| - | - | - |
| `schedule` | `5 * * * *` | Charts before this change defaulted to nightly `0 3 * * *`. An explicitly set schedule is kept. |
| `timeZone` | `Etc/UTC` | |
| `maxBucketsPerRun` | `48` | Hours or days handled per run, `1` to `1000`. |
| `maxSourceRowsPerBucket` | `5000` | Rows consumed per hour per run in one transaction, `1` to `5000`. |

One run consumes at most `maxBucketsPerRun` × `maxSourceRowsPerBucket` rows (240,000 by default). An hour with more rows finishes over the next runs, so hourly runs keep up with roughly 240,000 requests per hour. The job logs its summary, for example `{"hourBuckets":1,"dayBuckets":0,"rawRowsDeleted":1253,...}`. If `hourBuckets` stays above `1` for several runs, the job is catching up.

Non-Helm deployments must supply their own hourly caller with the same token and body:

```sh theme={null}
curl -fsS -X POST -H "Authorization: Bearer $GATEWAY_ADMIN_TOKEN" \
  -H "content-type: application/json" \
  -d '{"maxBucketsPerRun":48,"maxSourceRowsPerBucket":5000}' \
  http://<gateway-internal-host>:8791/internal/rollups/run
```

A request without a body uses the same defaults.

## Sparse Helm compatibility

The default `gateway: {}` deliberately has no canonical defaults. An absent `gateway` root, including an older release upgraded with `--reuse-values`, is supported and treated as `{}`; an explicitly supplied non-map is rejected. The root empty map means no canonical overrides, not deletion of the legacy component. Legacy `inference.*` values still configure the component, but only explicit `gateway.enabled: true` opts in to capability version `1`.

| Final values | Component | Capability intent |
| - | - | - |
| Defaults | Absent | False |
| `inference.enabled: true`, `gateway.enabled` absent | Retained | False; both services receive `GATEWAY_ENABLED=false` |
| `gateway.enabled: false`, even with `inference.enabled: true` | Absent | False |
| `gateway.enabled: true`, valid shared configuration | Present | True on Den API and Gateway |

Structural settings such as image, replicas, service, env, probes, resources, pod metadata, and retention inherit from `inference.*` unless the canonical key is present. Nonempty maps merge recursively; explicit `{}`, `[]`, `false`, `0`, and `""` replace inherited values. These rules apply to the canonical-over-legacy merge **after Helm coalesces values**. For example, a new `gateway.env: {}` clears inherited `inference.env`, `gateway.probes: {}` removes inherited probes, and `gateway.replicaCount: 0` scales down without clearing capability intent. Clearing required fields fails validation when the component is enabled.

An empty component image tag clears an inherited tag and falls back to shared `image.tag`, then `Chart.appVersion`. It does not select a compatible release for you.

Clearing **saved canonical maps** is different. Helm can refill an empty map before templates run: after saving `gateway.env.SAVED`, `--reuse-values --set-json 'gateway.env={}'` retains that entry. Earlier values files can refill nested maps too. To clear previously canonical entries, use `--reset-values` with a complete, reviewed values file that omits the old entries and explicitly includes the desired empty maps. Preserve all other deployment settings and Secret references. A final empty override file or `--reset-then-reuse-values` does not reliably discard saved maps. Inspect the resulting render; `null` is Helm's deletion operator, not a supported chart clear value.

## Open Gateway and retire old opt-in procedures

After deployment verification, sign in as an organization admin or above and open **AI Gateway → AI Providers**. This applies to every organization, including a newly created one with no capability metadata. No backoffice grant is needed; platform-admin routes keep their existing allowlist authorization but no longer offer a Gateway dashboard toggle.

For older clients, `capabilities.gatewayDashboard` in organization and admin capability responses is constant `true`. It is deprecated wire compatibility, **not a real feature flag**, not a deployment-readiness signal, and not an authorization decision. The admin capabilities API retains `gatewayDashboard` boolean/null input as a deprecated no-op; sending `true`, `false`, or `null` cannot change Gateway access. Other capabilities retain their existing controls.

Stored `organization.metadata.capabilities.gatewayDashboard` values, including explicit `false`, are ignored. No database migration or backfill is required for this retirement. The release's required schema migrations still apply. Do not add a replacement organization flag or use metadata-write workarounds.

## Google Vertex AI: each member signs in

Google now calls this product **Gemini Enterprise Agent Platform (formerly Vertex AI)**. For the complete project, billing, IAM, model access, OAuth client, and in-app setup walkthrough, follow [Google Agent Platform (Vertex AI)](https://openworklabs.com/docs/ai-gateway/google-agent-platform.md). The operational reference below retains the catalog names used by OpenWork.

Use this mode for `google-vertex` (Gemini) or `google-vertex-anthropic` (Claude on Vertex). Each member delegates their own Google account to Gateway. Google access/refresh tokens and the OAuth client secret stay encrypted on the server; Desktop and hosted workers receive only an OpenWork Gateway credential. Members do **not** need gcloud, local Application Default Credentials (ADC), `GOOGLE_APPLICATION_CREDENTIALS`, or a service-account key for this mode. It is separate from Google Workspace connections and from shared service-account authentication.

### Prepare Google Cloud and the OAuth app

1. In the inference project, enable the Vertex AI API and billing, arrange model quota, and confirm the selected model is available in the configured location. For Claude, enable the required partner-model access and accept its terms. A `global` endpoint is not a promise that every model supports it; verify each model/location combination.
2. Grant each member's **Google principal** the required inference-project IAM permissions. `roles/aiplatform.user` is the documented broad role; a reviewed narrower role must include the prediction permissions needed by the model, such as `aiplatform.endpoints.predict`. OpenWork membership, model grants, Google consent, and Google IAM are separate checks. The Google email need not match the member's OpenWork email; this mode does not enforce a Workspace-domain restriction by default.
3. Configure your organization's Google OAuth consent screen and create an OAuth client with application type **Web application**, not Desktop app. Use an Internal audience when eligible; otherwise configure the appropriate External audience/test users and complete Google's production/verification requirements. Your organization must satisfy Google's authorized-domain policies for the callback deployment, including when using a hosted callback it does not own.
4. In Den **AI Gateway > AI Providers > Add provider**, choose the appropriate Vertex provider. In **Key**, select **Each member signs in**, enter the intended project and location, and supply the Web OAuth client ID and client secret. Review **Models** and **Who can use it** before saving; the form creates the upstream credential set and selected access rules together. Open the saved provider's row to view its callback or edit **Key**. The secret is write-only; leave it blank for ordinary edits. Project/location are fixed on the provider: create a separate provider to change its destination.
5. Copy the **exact callback URL displayed by Den** into Google's **Authorized redirect URIs**. The value is generated for this deployment and ends in `/v1/inference-providers/oauth/callback`; provider details expose it as `oauthCallbackUrl`. Do not substitute the Gateway inference origin, `/gateway/connect`, a desktop loopback URL, or another deployment's hostname. Ensure Den's canonical public API URL (`DEN_API_PUBLIC_URL` when explicitly configured), HTTPS reverse proxy, and browser session cookies agree with the displayed URL. Compare the complete URI, including any deployment path prefix, and do not add a trailing slash.
6. Confirm **Who can use it** includes only the intended members, teams, or organization. The form defaults to **Everyone in the organization**; turn it off and use **Add person** or **Add team** to restrict access. Saving links the chosen models and credential set through access rules. Creating a set through the API alone, being its creator, or being an administrator does not automatically grant inference access. Each granted member must still connect their own Google account.

The authorization request uses exactly these scopes:

* `openid`
* `email`
* `https://www.googleapis.com/auth/cloud-platform`

It requests offline access with consent and PKCE. Cloud-platform consent does not grant IAM permissions by itself. The OAuth-client project and inference project can differ; neither is automatically a separately configured quota project.

> [!WARNING]
>
> There is currently no administrator-configurable quota-project setting or Gateway injection of `x-goog-user-project`. Caller-supplied quota-project headers and Vertex quota query parameters are stripped. If your Google setup requires an explicit quota project, this is an unsupported gap, not something to fix by adding a client header or local ADC. A future supported implementation must select the project from administrator-controlled configuration and verify `serviceusage.services.use` on that project.

See Google's [Web server OAuth guide](https://developers.google.com/identity/protocols/oauth2/web-server), [OAuth policies](https://developers.google.com/identity/protocols/oauth2/policies), and [Vertex access-control guidance](https://cloud.google.com/vertex-ai/generative-ai/docs/access-control).

### Member connection and browser account

Open the assigned OAuth provider's **Login** action in Desktop's AI provider settings, or use Den's **My Model Connections** page at `/dashboard/model-connections`. Choose the intended credential set if more than one is assigned. The browser first opens an OpenWork connection entry page, then continues to Google:

1. Sign in to Den in that browser with the **same OpenWork account that started Connect**. A Desktop login does not automatically sign the external browser in. If the browser is using another OpenWork account, sign out there and use the initiating account; an entry link or OAuth state is not a substitute for that session.
2. Choose a Google account with IAM access to the configured inference project and approve the requested scopes. Google sign-in does not change the OpenWork member who owns the connection.
3. Return to OpenWork and refresh connection/model status as needed. Check the connected account and selected set before testing a model. Closing the browser or stopping the wait does not revoke a connection; use **Disconnect** to cancel pending sign-ins and remove the credential. If the attempt expires, start Connect again.

**Configured** means the administrator supplied the OAuth app. **Connected/ready** describes the stored member authorization, not a live Vertex permission or quota test. Missing offline authorization, malformed token responses, or legacy credentials without a valid expiry require sign-in again rather than being treated as indefinite credentials. A failed replacement authorization does not replace the existing stored database credential; this is not proof that its Google grant remains valid. If Google issues a validated token response but OIDC verification fails, OpenWork stores no replacement and best-effort revokes the newly issued refresh token. This fail-closed cleanup policy also applies when verification cannot complete because signing-key retrieval times out or encounters a network failure. Recognizable key-fetch timeout/network errors report temporary verification unavailability rather than invalid identity; other verification failures remain generic. Cleanup revocation may affect the previous connection and other Google connections sharing the same grant/OAuth client, so those connections may require reconnecting even while their local rows still appear ready. Failed token-response validation does not grant cleanup ownership of arbitrary unvalidated token material.

### Session policy, rotation, and disconnect

* Google Cloud session-control policy can require interactive reauthentication (`invalid_grant`, including `invalid_rapt`). Revocation and token expiry can do the same. Use **Reconnect** when required; member OAuth does not guarantee indefinitely unattended operation or bypass organization policy. Use a separately approved workload-authentication mode for workloads that cannot pause for user sign-in; Gateway does not silently fall back to it.
* External OAuth apps in **Testing** generally issue refresh tokens that expire after **seven days** for this scope set. The identity-only exception does not apply because cloud-platform is requested. Complete the appropriate Google production process rather than weakening session policy. See Google's [token-expiration guidance](https://developers.google.com/identity/protocols/oauth2) and [production-readiness guide](https://developers.google.com/identity/protocols/oauth2/production-readiness/overview).
* Rotating the OAuth client ID or client secret in a credential set cancels its pending sign-ins and revokes/erases its affected local Google credentials. Coordinate the Google-side change and Den update, then have affected members reconnect. Omitting the secret in an API edit preserves it; renaming alone does not revoke credentials. Changing credential mode or disabling the **credential set** also revokes its credentials. Re-enabling it does not restore erased tokens.
* Disabling the **provider** blocks inference and cancels pending sign-ins, but retains stored credentials; removing an access grant blocks that access without being a Google disconnect. These actions are not equivalent to revoking a Google grant. A current organization member can disconnect their retained credential even after grant loss or provider disablement; membership-offboarding cleanup handles removed members.
* **Disconnect** immediately marks the selected local member credential revoked, erases its token material, and cancels pending sign-ins. Remote Google revocation is best effort; a successful local disconnect does not prove Google completed revocation, and tokens are not retained for a later retry. If needed, remove the app's access in the Google account directly. Refresh models in open Desktop/Web sessions; live Gateway authorization still prevents new requests from using the revoked credential.
* Google may revoke other connections sharing the same Google authorization grant, including another local set or the other Vertex provider using the same OAuth client. Do not assume separate local rows are independent Google grants. Disconnect does not delete your Google OAuth application. Gateway client keys and `DEN_DB_ENCRYPTION_KEY` are different credentials: rotating them is not a substitute for Google disconnect, and replacing the database encryption key is not an OAuth rotation procedure.

### Verify before rollout

> [!WARNING]
>
> The automated fixtures and configuration checks are not real-Google acceptance evidence. They do not establish that an organization-owned OAuth client can use your hosted/self-hosted callback, that Google has verified the app, or that a member has Vertex IAM, partner-model entitlement, quota, or policy-compatible refresh. Validate these with an explicitly authorized test project and accounts before rollout.

Use approved non-sensitive prompts to test both Gemini and Claude, streaming and non-streaming, from Desktop and hosted Web with no local Google credentials. Verify the displayed account, model/set selection, token reuse and refresh, reconnect after rotation/revocation, denial for a Google account without IAM, and session-control behavior where applicable. Check Google principal attribution in Cloud Audit Logs where enabled. Record only sanitized outcomes and release identifiers, never tokens, client secrets, cookies, callback query strings, or raw token endpoint responses.

The client-facing SDKs are the ordinary Google and Anthropic static-key SDKs; Gateway adapts their requests to the Google or Anthropic Vertex publisher respectively. Matching model-level Vertex SDK overrides are mapped too, while model aliases and supported metadata remain intact. Mixed-SDK partner models remain outside this support: Google sign-in does not enable unrelated adapters. Existing HTTP fixtures cover Vertex routing and relay behavior, but actual SDK-to-Vertex-adapter decoding through the materialized path still requires verification for both providers and both response modes.

For `redirect_uri_mismatch`, compare the exact displayed callback with Google's registration. For `invalid_client`, ask the credential-set administrator to repair the OAuth client rather than repeatedly asking members for consent. For Vertex permission/model-access failures, review IAM, project, region, and partner access; do not assume every upstream `401`/`403` is an expired refresh token. Transient token-endpoint failures and quota errors need separate diagnosis. Do not replay an ambiguous or partly delivered inference stream automatically.

## Amazon Bedrock and Microsoft Foundry: each member signs in

Members can also sign in with their own account to **Amazon Bedrock** and **Amazon Bedrock (OpenAI)** through AWS IAM Identity Center, and to **Microsoft Foundry (Claude)** through Microsoft Entra ID. Follow [Amazon Bedrock](https://openworklabs.com/docs/ai-gateway/amazon-bedrock.md) and [Microsoft Foundry](https://openworklabs.com/docs/ai-gateway/microsoft-foundry.md) for the cloud-side setup.

Both, and the Microsoft Foundry provider itself, are available in every organization; there is nothing to turn on in `/admin`.

Run the release's database migration first: it adds the `aws_sso` credential kind and the credential-set columns `oauth_tenant_id` and `aws_sso`.

Like Google sign-in, Den completes each sign-in and stores the member's refresh token encrypted; Gateway renews it and, for AWS, exchanges it for short-lived role credentials that sign Bedrock requests. Den API and Gateway need public HTTPS egress to:

| Host | Service | Used by |
| - | - | - |
| `oidc.<sso-region>.amazonaws.com` | IAM Identity Center sign-in and renewal | Den API, Gateway |
| `portal.sso.<sso-region>.amazonaws.com` | Role credentials; sign-out | Den API, Gateway |
| `sts.<sso-region>.amazonaws.com` | Checking who signed in | Den API |
| `login.microsoftonline.com` | Entra ID sign-in, renewal and signing keys | Den API, Gateway |
| `<resource>.services.ai.azure.com` | Microsoft Foundry inference | Gateway |

These are public AWS and Microsoft endpoints and pass the default egress policy; no `GATEWAY_EGRESS_ALLOWED_ORIGINS` exception is needed. Microsoft Foundry sign-in uses the same Den callback as Google, ending in `/v1/inference-providers/oauth/callback`. AWS sign-in uses the OAuth device flow and needs no callback.

## Verify and troubleshoot

Sign in to Den as an organization admin, select the intended organization, and inspect its authenticated **`GET /v1/org`** response using the browser's Network panel or your approved authenticated API client. Check the Den API response, not a health endpoint, organization metadata field, or Web runtime-config response. The relevant top-level fields are:

```json theme={null}
{
  "deploymentCapabilities": { "version": 1, "aiGateway": true },
  "capabilities": { "gatewayDashboard": true }
}
```

The `capabilities.gatewayDashboard` value above is only the deprecated constant-true compatibility response; do not use it to verify enablement. Check `deploymentCapabilities.version` and `deploymentCapabilities.aiGateway` instead.

Do not export cookies, authorization headers, or the full organization response into tickets. Record only the capability fields and release image identifiers. A missing/malformed `deploymentCapabilities` object, unsupported version, or `aiGateway` value other than literal boolean `true` is unsupported and fails closed in the UI. Verify all API replicas serve the matching release; one response is not proof of a consistent rollout.

| State | Expected result / action |
| - | - |
| Organization access is loading or switching | `Checking workspace access...`; management UI and requests wait. This is not a deployment-unavailable verdict. |
| Organization admin, but unsupported/disabled deployment | Exact notice below, with no provider editor or management fetch. Check images, the top-level contract, and operator configuration. |
| Insufficient organization role | Redirect to the dashboard; review membership. There is no Gateway-specific organization grant. |
| Organization-context request fails | An error notice, not a fabricated disabled capability. Resolve session/API failure. |
| Enabled deployment with an upstream/provider outage | Diagnose credentials, DNS, TLS, egress, catalog warnings, and upstream errors. Availability is configuration intent, not an upstream health probe; do not relabel the deployment as unsupported. |

The deployment-unavailable UI message is exactly:

> This feature is not part of your deployment system, please ask an instance admin to configure deployment

An authenticated, authorized management API request on a disabled deployment returns HTTP `403`, error `gateway_not_enabled`, and:

> Gateway management is not enabled on this deployment. Ask your administrator to configure GATEWAY\_ENABLED=true.

Authentication, role, and fresh-session failures remain distinct. Member usable/connect/sign-in/revoke paths and OAuth callbacks are not governed by the deployment management gate; retiring the organization flag does not change their authorization.

`/health` proves process liveness. Gateway `/ready` executes `select 1` to check database connectivity; it does **not** prove the Gateway schema or migrations exist. The capability response also does not prove schema health. Verify migration receipts and actual schema separately, then test an authorized provider path with approved non-sensitive data before reopening traffic.

For organization admins, **AI Gateway** is the navigation entry, with **AI Providers** and **OpenWork Models** as tabs. Provider creation and editing stay inside the **AI Providers** tab. Gateway provider management retains its deployment and admin guards; loading temporarily defers access decisions. No organization-grant guard remains. The OpenWork Models tab retains its own availability rules. This navigation does not change subscriptions, billing, Models keys, or provider data. See [disable and recovery choices](https://openworklabs.com/docs/self-host/gateway-upgrade.md#disable-without-a-schema-downgrade) before turning anything off.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.