How to call OpenAI, Claude, or Gemini without API keys
Netlify AI Gateway injects provider credentials into your Functions, so official SDKs work with no config. Setup, the four supported providers, and what it costs.
Key takeaways
Install the provider’s official SDK, instantiate the client with no arguments, and call it from a Netlify Function. Netlify injects the API key and base URL that the SDK already looks for, so there’s no provider account to create and no key to store. Works with OpenAI, Anthropic, Google Gemini, and OpenRouter. One prerequisite catches everyone: the gateway doesn’t activate until your project has had at least one production deploy, even for local development.
- Not browser-callable. The gateway lives in Functions and Edge Functions only.
- Runtime-only credentials. Build scripts, prerendering, SSG, and build plugins get no credentials and fail.
- 60-second sync timeout. Stream long generations or use a background function.
- Don’t hardcode model lists. Availability changes; check the live providers endpoint.
- Zero Data Retention only. The gateway routes only to ZDR providers, so some catalog models aren’t served.
- $1 USD of usage = 180 credits, with per-minute rate limits by plan.
The part of adding AI that isn’t the interesting part
Wiring up a model call involves one interesting decision (what to ask it) and several tedious ones. Which provider account. Where the key lives. How it reaches production without ending up in your repo. Who rotates it. What happens when a teammate needs to run the thing locally.
AI Gateway removes that whole layer. You write the SDK call you were going to write anyway, instantiate the client with no arguments, and the credentials are already there at runtime. Netlify never overrides keys you’ve set yourself, so if you’d rather bring your own provider account, that still works and takes precedence.
Here’s the setup, then the constraints that matter for cost and latency.
When to use the gateway, and when not to
Use it for server-side AI calls from a deployed Netlify project: a chatbot endpoint, a completion route, summarizing form submissions, generating images or text, any LLM-backed API route.
Don’t use it when:
- The call needs to happen in the browser. It isn’t browser-callable. Put the call in a Function and fetch that route from the client.
- The work happens at build time. Prerendering, SSG, and build plugins get no gateway credentials and will fail. Do the AI work at request time, and if the output needs to look precomputed, cache it to Netlify Blobs (see How to store files and objects with Netlify Blobs).
- You need a proprietary header-gated feature. No request headers pass through.
- You need batch inference or OpenAI priority processing. Neither is supported.
- You’re on a legacy plan. Credit-based plans only: Free, Personal, Pro, with Enterprise via an Account Manager.
Worked example
Create netlify/functions/joke.js (mkdir -p netlify/functions first) and npm install openai.
import process from "process";import OpenAI from "openai";
export default async () => { const client = new OpenAI(); // reads OPENAI_API_KEY + OPENAI_BASE_URL try { const res = await client.responses.create({ model: "gpt-5-mini", input: [{ role: "user", content: "Give me a random short dad joke" }], reasoning: { effort: "minimal" }, }); return Response.json({ joke: res.output_text?.trim() || "Out of jokes", model: res.model, tokens: { input: res.usage.input_tokens, output: res.usage.output_tokens }, }); } catch (e) { return Response.json({ error: `${e}` }, { status: 500 }); }};
export const config = { path: "/api/joke" }; // route, local + deployedThe client-side half is a plain fetch to your own route:
const res = await fetch("/api/joke");const data = await res.json();Note that new OpenAI() takes no arguments. That’s the whole trick: the SDK reads OPENAI_API_KEY and OPENAI_BASE_URL from the environment, and Netlify has already put them there.
The four providers
// Anthropic — npm i @anthropic-ai/sdkimport Anthropic from '@anthropic-ai/sdk';const anthropic = new Anthropic(); // ANTHROPIC_API_KEY + ANTHROPIC_BASE_URLawait anthropic.messages.create({ model: 'claude-sonnet-4-5-20250929', max_tokens: 1024, messages: [{ role: 'user', content: 'Hello!' }],});// OpenAI — npm i openaiimport OpenAI from 'openai';const openai = new OpenAI(); // OPENAI_API_KEY + OPENAI_BASE_URLawait openai.chat.completions.create({ model: 'gpt-5', messages: [{ role: 'user', content: 'Hello!' }],});// Google Gemini — npm i @google/genaiimport { GoogleGenAI } from '@google/genai';const genAI = new GoogleGenAI({}); // GEMINI_API_KEY + GOOGLE_GEMINI_BASE_URLawait genAI.models.generateContent({ model: 'gemini-2.5-pro', contents: 'Hello!',});// OpenRouter — npm i @openrouter/sdk@1.2.43 or laterimport { OpenRouter } from '@openrouter/sdk';const openRouter = new OpenRouter(); // OPENROUTER_API_KEY + OPENROUTER_BASE_URL auto-injectedawait openRouter.chat.send({ chatRequest: { model: 'x-ai/grok-4.5', messages: [{ role: 'user', content: 'Hello!' }], },});There’s a shortcut worth knowing: you can reach any OpenRouter-served model (xAI, DeepSeek, Meta, Mistral, Qwen) through the plain OpenAI SDK by passing the model ID in OpenRouter notation, with no extra configuration:
await openai.chat.completions.create({ model: 'deepseek/deepseek-v4-flash-0731', messages: [{ role: 'user', content: 'Hello!' }],});The model IDs in these examples are illustrative. The model list is dynamic, so check the live providers endpoint rather than baking IDs into your code.
Setup and deploy
- Be on a credit-based plan (Free, Personal, Pro; Enterprise via an Account Manager). Legacy plans need to switch.
- Link the project:
netlify init. - Deploy to production at least once. This is required to activate the gateway:
netlify deploy --prod --open. - Leave AI Features enabled, and don’t set your own provider keys unless you mean to override.
Local development
Both paths still require an existing production deploy.
Netlify CLI, with full support: netlify dev. You’ll need npm install -g netlify-cli@latest and netlify login.
Netlify Vite plugin, which gives gateway access without netlify dev:
import { defineConfig } from 'vite'import react from '@vitejs/plugin-react'import netlify from "@netlify/vite-plugin";
export default defineConfig({ plugins: [react(), netlify()],})Then run your normal npm run dev.
Long generations
A synchronous function is killed at 60 seconds, and a real generation can exceed that. Two options.
Stream it. Return the provider’s streaming response body as a ReadableStream from your function. The mechanics of streaming from a Netlify Function, including a worked OpenAI streaming example, are in How to add a backend API endpoint with Netlify Functions. Streamed responses are capped at 20 MB.
Or run it in the background. Set config.background: true, which gives you up to 15 minutes, and persist the output somewhere the client can fetch it, typically Netlify Blobs. The client gets an immediate 202, so you’ll need to poll or notify.
What you should not do is leave a slow generation unstreamed and hope it finishes.
Common failure modes
Everything fails locally and you can’t work out why. The gateway doesn’t activate until the project has at least one production deploy. That applies to local dev too. Run netlify deploy --prod once.
Your calls fail from the browser. The gateway isn’t browser-callable. Move the call into a Function and fetch that route.
Your build fails on an AI call. Gateway credentials are runtime-only. Build scripts, prerendering, SSG, and build plugins get nothing. Move the call to request time and cache the output if it needs to appear precomputed.
Your function times out at 60 seconds. Stream it, or move it to a background function.
Gemini credentials aren’t being injected. Netlify will not inject GEMINI_API_KEY or GOOGLE_GEMINI_BASE_URL if either GOOGLE_API_KEY or GOOGLE_VERTEX_BASE_URL is set. That’s deliberate, so you can point at Vertex or your own Google credentials, but it surprises people who set one of those for something else.
A model in the OpenRouter directory returns nothing. The gateway routes only to Zero Data Retention providers. A model listed in the directory isn’t served if no ZDR host offers it. Browse the ZDR-filtered catalog at openrouter.ai/models?zdr=true.
A model ID that worked last month doesn’t now. Availability changes. Don’t hardcode; check the live providers endpoint at runtime.
You hit a rate limit. Limits are per team, across all projects, per minute, measured in credits: Free 90, Personal 450, Pro 1,800, Enterprise 9,000.
Your credit usage spiked from visitor traffic. An unprotected AI endpoint is an expensive thing to leave open. Set up rate limiting rules on AI functions.
Your prompt was truncated. Input is capped at 200k tokens.
Prompt caching isn’t behaving as you’d expect. Anthropic gets only the default 5-minute ephemeral cache. OpenAI has a per-account prompt_cache_key set for you. Gemini explicit context caching isn’t supported.
Your own provider key seems to be ignored. It isn’t. Netlify only injects credentials if you have not already set them at the project or team level. Netlify never overrides your keys.
Reference
Injected environment variables
Set in all Netlify compute contexts at function init, only if you haven’t already set them yourself.
| Provider | Variables |
|---|---|
| OpenAI | OPENAI_API_KEY, OPENAI_BASE_URL |
| Anthropic | ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL |
| Google Gemini | GEMINI_API_KEY, GOOGLE_GEMINI_BASE_URL |
| OpenRouter | OPENROUTER_API_KEY, OPENROUTER_BASE_URL |
Two more are always injected and never collide with your provider variables: NETLIFY_AI_GATEWAY_KEY and NETLIFY_AI_GATEWAY_BASE_URL.
Use the SDK path with the per-provider variables as your default. Reach for the NETLIFY_AI_GATEWAY_* pair only when a third-party or unsupported library needs credentials passed explicitly. That’s the case they exist for.
SDK instantiation
| Provider | Package | Client |
|---|---|---|
| OpenAI | openai | new OpenAI() |
| Anthropic | @anthropic-ai/sdk | new Anthropic() |
| Google Gemini | @google/genai | new GoogleGenAI({}) |
| OpenRouter | @openrouter/sdk (v1.2.43+) | new OpenRouter() |
Limits and constraints
| Constraint | Value |
|---|---|
| Plans | Credit-based only (Free, Personal, Pro; Enterprise via Account Manager) |
| Context window | 200k input tokens |
| Rate limit (per team, per minute, in credits) | Free 90 · Personal 450 · Pro 1,800 · Enterprise 9,000 |
| Credit cost | Tokens → USD at provider published rates → credits. $1 USD = 180 credits. |
| Sync function timeout | 60s |
| Request headers passed through | None |
| Batch inference | Not supported |
| OpenAI priority processing | Not supported |
| Data retention | Zero Data Retention providers only |
| Prompt storage | The gateway does not store prompts or outputs |
Prompt caching by provider
| Provider | Support |
|---|---|
| Anthropic | Default 5-minute ephemeral cache only |
| OpenAI | Per-account prompt_cache_key is set |
| Gemini | Explicit context caching not supported |
FAQs
How do I add an AI model call to my Netlify site? Install the provider’s official SDK, create a Netlify Function, and instantiate the client with no arguments. Netlify injects the API key and base URL at runtime. Deploy to production once to activate the gateway.
Do I need an OpenAI or Anthropic account to use AI Gateway? No. The gateway injects credentials for OpenAI, Anthropic, Google Gemini, and OpenRouter. If you do set your own provider keys, Netlify won’t override them.
Can I call AI Gateway from the browser? No. It’s available in Functions and Edge Functions only. Put the call in a Function and fetch that route from your client code.
Why isn’t AI Gateway working locally?
The gateway doesn’t activate until the project has at least one production deploy, which applies to local development too. Run netlify deploy --prod, then use netlify dev or the Netlify Vite plugin.
What does AI Gateway cost? Token usage converts to USD at the provider’s published rates, then to credits, where $1 USD of usage equals 180 credits. Per-minute rate limits run from 90 credits on Free to 9,000 on Enterprise.
Can I use AI Gateway during the build or in SSG? No. Credentials are runtime-only, so build scripts, prerendering, and build plugins get nothing. Do the work at request time and cache the result to Netlify Blobs if it needs to look precomputed.
How do I handle a generation that takes longer than 60 seconds?
Stream the response using the SDK’s streaming mode returned as a ReadableStream, or run it in a background function with config.background: true and persist the output for the client to fetch.
Which models can I use? Anthropic, OpenAI, and Gemini models served directly, plus OpenRouter-served models like xAI, DeepSeek, Meta, Mistral, and Qwen using OpenRouter notation. The list is dynamic, so check the live providers endpoint instead of hardcoding IDs. Only Zero Data Retention hosts are routed to.
Does Netlify store my prompts? No. The gateway does not store prompts or outputs.
Related
This article is generated from Netlify’s open-source agent guidance at netlify/context-and-tools, the same reference our AI coding agents use.
Ready to build something with a model in it? Start at netlify.new.