OpenClaw sandboxes on Vercel needed a manually-configured AI_GATEWAY_API_KEY environment variable to authenticate with Vercel's AI Gateway for LLM inference. This was a deployment friction point — every new deployment or team member needed the key configured.
Replaced the static API key with Vercel's OIDC (OpenID Connect) token federation. The dashboard's Vercel Functions automatically receive an OIDC JWT that proves they're running on Vercel infrastructure. AI Gateway accepts this JWT as a bearer token — no static key needed.
Vercel Function receives request
→ x-vercel-oidc-token header contains JWT
→ getAiGatewayBearerToken() extracts it via @vercel/oidc
→ Token passed to sandbox via Sandbox.create({ env: { AI_GATEWAY_API_KEY: token } })
→ Startup script reads $AI_GATEWAY_API_KEY env var (prefers env over file)
→ OpenClaw gateway uses token for AI Gateway requests
// packages/dashboard/src/server/ai-gateway-auth.ts
export async function getAiGatewayBearerToken(): Promise<string> {
// 1. Static key (local dev / explicit override)
const staticKey = process.env.AI_GATEWAY_API_KEY?.trim();
if (staticKey) return staticKey;
// 2. Vercel OIDC token (production/preview)
const oidcFn = await loadOidcHelper();
if (oidcFn) {
const token = await oidcFn();
if (token) return token;
}
throw new Error('AI Gateway auth unavailable');
}| Event | How Token is Injected |
|---|---|
| Sandbox create | bootstrapOpenClawWorkflow calls getAiGatewayBearerTokenOptional() and passes to setupOpenClaw |
| Sandbox restore | All restore callers pass token via Sandbox.create({ env: { AI_GATEWAY_API_KEY: token } }) |
| Token refresh | Hourly cron route /api/cron/token-refresh writes fresh token to sandbox file + restarts gateway |
| Startup script | Prefers $AI_GATEWAY_API_KEY env var over file, so fresh tokens override stale snapshot data |
- Format: JWT signed by Vercel (
iss: https://oidc.vercel.com/{team}) - Lifetime: ~1 hour (prod/preview), ~12 hours (dev)
- Scope:
owner:{team}:project:{project}:environment:{env} - Delivery:
x-vercel-oidc-tokenrequest header (Functions),VERCEL_OIDC_TOKENenv var (builds)
OIDC tokens expire in ~1 hour. The cron job at /api/cron/token-refresh runs hourly:
- Gets fresh OIDC token via
getAiGatewayBearerToken() - Lists all running OpenClaw sandboxes from Redis
- For each: writes fresh token to
.ai-gateway-api-keyfile + restarts gateway - Reports per-sandbox outcomes
packages/dashboard/src/server/ai-gateway-auth.ts— OIDC/static key helperpackages/dashboard/app/api/cron/token-refresh/route.ts— Hourly token refreshpackages/dashboard/test/cron/token-refresh.test.ts— Token refresh tests
packages/vercel-sandbox/src/sandboxes/service.ts—restoreSandboxacceptsenvoption,createSandboxacceptsaiGatewayApiKeypackages/vercel-sandbox/src/sandboxes/openclaw-setup.ts— Startup script prefers env var over filepackages/dashboard/app/api/sandboxes/route.ts— Uses OIDC for pi/openclaw creationpackages/dashboard/app/api/proxy/.../route.ts— Injects OIDC token on restorepackages/dashboard/app/api/sandboxes/[key]/restore/route.ts— Injects OIDC tokenpackages/dashboard/app/api/health/route.ts— Detects OIDC availabilitypackages/dashboard/src/server/workflows/openclaw/bootstrapOpenClawWorkflow.ts— Uses OIDC helper- Plus: fleet lifecycle, cron channel-maintenance, snapshot restore routes
- Added
@vercel/oidc@3.2.0to dashboard - Upgraded
@vercel/sandboxfrom 1.6.0 → 1.8.0 (forSandbox.create({ env })support)
Tested end-to-end on production:
- ✅ Removed
AI_GATEWAY_API_KEYfrom Vercel env vars - ✅ Health check reports
aiGatewayApiKey: true(via OIDC detection) - ✅ Restored sandbox
amber-frost-25— gateway process confirmed running with OIDC JWT - ✅ Chat completions work: AI responds correctly to messages
- ✅ Gateway process env shows JWT token (
eyJhbGciOiJSUzI1Ni...) not static key
- If
AI_GATEWAY_API_KEYis set, it's used (priority 1) - If not, OIDC token is tried (priority 2)
- Local development: set
AI_GATEWAY_API_KEYin.env.localor runvercel env pull - Existing snapshots with baked-in keys continue to work (startup script falls back to file)
- Vercel OIDC must be enabled in project settings (it was already enabled for this project)
@vercel/oidcpackage installed@vercel/sandbox>= 1.8.0 forenvparameter support