Tested directly with curl against the auth-only /v1/credits endpoint (no model call involved) — still 403, so this rules out any model/payload/request-format issue
No requests are reaching upstream providers — no logs shown in Dashboard, suggesting this is an account/team-level block on Vercel’s side
Since /v1/credits is returning the same 403, I agree this is probably not a model ID, payload, or provider-routing issue. That endpoint should only need a valid AI Gateway auth method, and the API key should identify the team.
One thing I’d double-check before treating it as a team/account-side block: make sure the key was created under the same Vercel team that owns the AI Gateway credits, not a personal account or another team. If you use the CLI, you can switch scope and create a test key from the intended team:
vercel switch
vercel ai-gateway api-keys create
Then test only the new redacted key against /v1/credits. If a brand-new key from the same team still returns:
then the strongest evidence to share is exactly what you already have: status code, endpoint, response body, and confirmation that /v1/models works without auth while /v1/credits fails with the team key. I’d avoid posting the full key or any extra account details publicly.
I tried this — ran vercel switch to confirm I was scoped to the correct team, then generated a brand new key with vercel ai-gateway api-keys create under that same team.
Testing the new key against /v1/credits gives the exact same result:
I also checked /v1/models as you suggested — that one returns successfully without issue, so it does look like this is isolated to the authenticated endpoints specifically for my team, not a gateway-wide outage.
Thanks for testing that. With a brand-new key created under the confirmed team, /v1/credits still returning the same 403 is a pretty strong signal that this is no longer a request-format or model/provider issue.
At this point I’d keep the report focused on the authenticated endpoint failure:
Expected: credits response
Actual: HTTP/2 403 with {"error":{"message":"Forbidden.","type":"access_denied"}}
One extra sanity check you could do is create one key from the dashboard, not the CLI, and test it in a clean terminal variable so there are no copied spaces/newlines involved:
If that also fails, I think you’ve ruled out the normal client-side causes. I’d avoid posting any more team/account details publicly and consider removing the team ID from the original post. The useful private escalation details are the redacted key prefix, team scope, timestamp, endpoint, status code, and response body.
Thanks again for the extra sanity check. I went ahead and generated another key directly from the Vercel Dashboard, copied it cleanly, and ran it via the environment variable in a fresh terminal session to ensure zero whitespace or newline issues.
The result is exactly the same: 403 Forbidden with {"error":{"message":"Forbidden.","type":"access_denied"}}.
At this point, every client-side variable (CLI vs. UI generation, character escaping, scope, and token syntax) has been completely ruled out. It’s definitely a hard backend or billing/account flag on Vercel’s routing layer.
Good call on the security hygiene, too—I’ll go ahead and edit out the explicit Team ID from the main post. Hopefully, a Vercel team member can jump in here shortly now that we’ve given them a fully isolated repro case. Appreciate all your help!
That’s a solid isolation result. At this point I’d phrase it a little more cautiously than “definitely billing/account,” but I agree you’ve ruled out the normal local causes: key source, scope, header format, whitespace, CLI vs dashboard generation, and model/provider routing.
I think the cleanest summary for a Vercel-side check is:
Unauthenticated endpoint:
GET /v1/models -> succeeds
Authenticated endpoint:
GET /v1/credits with freshly generated team-scoped key -> 403 access_denied
Keys tested:
- CLI-generated key under confirmed team scope
- Dashboard-generated key under same team
- clean shell env var, no whitespace/newline issue
Result:
Same 403 response every time
I’d avoid posting any more identifiers publicly now. If you need to keep production moving while this is investigated, the safest temporary workaround is probably to bypass AI Gateway for the affected path and call the provider directly with a new provider key stored server-side only. Then switch back once the authenticated Gateway endpoint works again.