Gemini

Gemini (AI Studio) API reference

The /gemini surface: generateContent, the Interactions API, tool calls, embeddings, files and the OpenAI-compatibility layer — all served on both v1beta and v1.

Gemini (AI Studio) · Endpoints

Everything under the /gemini prefix. Both v1beta and v1 serve the same mock, so either apiVersion works. Custom methods are addressed the Google way, as {resource}:{method}.

MethodPathNotes
POST/gemini/v1beta/models/{model}:generateContentFull response: candidates, finishReason, usageMetadata by modality, modelVersion, responseId
POST/gemini/v1beta/models/{model}:streamGenerateContentSSE with ?alt=sse, streamed JSON array without it
POST/gemini/v1beta/models/{model}:batchEmbedContentsDeterministic unit vectors — what the SDK calls even for one input
POST/gemini/v1beta/models/{model}:embedContentThe singular form, for curl and non-SDK callers
POST/gemini/v1beta/models/{model}:countTokensAccepts bare contents or a whole generateContentRequest
GET/gemini/v1beta/modelsSimulated catalog, ids checked against the live listing
GET/gemini/v1beta/models/{model}404 in Google's google.rpc.Status format
POST/gemini/v1beta/interactionsInteractions API — steps, usage, tool calls, SSE with stream: true
GET/gemini/v1beta/interactions/{id}Stateless: synthesizes a deterministic interaction for any id
POST/gemini/v1beta/interactions/{id}/cancelReturns the interaction as cancelled
DELETE/gemini/v1beta/interactions/{id}Idempotent; 200 with an empty body
POST/gemini/upload/v1beta/filesResumable upload, both steps. upload/ sits before the version
GET/gemini/v1beta/filesSimulated catalog with pageSize/pageToken
GET/gemini/v1beta/files/{name}Stateless: the name carries the metadata, so an upload round-trips exactly
DELETE/gemini/v1beta/files/{name}Idempotent; returns {}
POST/gemini/v1beta/openai/chat/completionsOpenAI-compatibility layer — see below
POST/gemini/v1beta/openai/embeddingsSame layer, at Gemini's dimensions
GET/gemini/v1beta/openai/modelsGemini's catalog in OpenAI's list envelope

Gemini · generateContent

A turn is a Content and everything inside it is a Part. Multi-turn input, systemInstruction (counted towards the prompt tokens) and candidateCount are all simulated.

import { GoogleGenAI } from "@google/genai";

const ai = new GoogleGenAI({
  apiKey: "sk-mock-key-01",
  httpOptions: { baseUrl: "http://localhost:3000/gemini" },
});

const response = await ai.models.generateContent({
  model: "gemini-3.6-flash",
  contents: "Hello!",
});

console.log(response.text); // "Echo: Hello!"
Streaming here carries no [DONE] sentinel, unlike OpenAI's. The Gemini SDK parses every event as JSON and rejects a trailing one it cannot parse.

Gemini · Tool calls

Declare tools as tools[].functionDeclarations[]. Tool selection lives in toolConfig.functionCallingConfig.mode rather than in a tool_choice field.

ModeBehaviour
ANYForces a call. With allowedFunctionNames, the named tool is picked
NONENever calls a tool
AUTO (default)Leaves it to the model — and this mock has none, so it answers with prose

A call comes back as a functionCall part whose args is a decoded object, where OpenAI carries a JSON string. Streaming never splits a call across chunks. The loop terminates once a functionResponse part is in the conversation.

Gemini · Interactions API

Google's next-generation surface, GA since June 2026 and the one its own quickstart now uses. It is a different contract from generateContent, not a wrapper: fields are snake_case, and a turn is a Step rather than a Content.

const interaction = await ai.interactions.create({
  model: "gemini-3.6-flash",
  input: "Hello!",
});

console.log(interaction.output_text); // "Echo: Hello!"

Streaming emits interaction.createdstep.start / step.delta / step.stopinteraction.completed. A function call's arguments arrive as arguments_delta pieces of a JSON string — the one place this API re-encodes what it otherwise sends decoded. Note that tool_choice lives inside generation_config here, not beside tools.

The mock stays stateless here, as it does on OpenAI's Responses API: create stores nothing and GET /interactions/{id} synthesizes a deterministic interaction for any id rather than 404ing. So store, background and previous_interaction_id are accepted and ignored — a test that depends on real server-side continuation is the one thing this endpoint cannot fake.

Gemini · Files

The full resumable protocol works, and still nothing is persisted.

const file = await ai.files.upload({
  file: new Blob(["hello"], { type: "text/plain" }),
  config: { mimeType: "text/plain", displayName: "notes.txt" },
});

const same = await ai.files.get({ name: file.name }); // round-trips exactly

Both halves stay stateless. The upload session has no server-side table behind it: the metadata declared at start travels back inside the x-goog-upload-url the client is told to use, and returns with the bytes. The file's own metadata then rides inside the name it is given. sha256Hash is the genuine digest of the bytes received, so a client that verifies it succeeds.

Minted names run past the 40 characters Google documents. That cap applies to names a client supplies, not to ones the server returns — verified against @google/genai, which reads a 90-character name back without complaint. Use the files/missing… prefix to exercise the not-found path, which the real API reports as 403 PERMISSION_DENIED rather than a 404.

Gemini · OpenAI-compatibility layer

Google runs an OpenAI-shaped surface at /v1beta/openai, so an app built on the openai SDK can talk to Gemini by changing only its baseURL. The mock serves it too.

const client = new OpenAI({
  baseURL: "http://localhost:3000/gemini/v1beta/openai",
  apiKey: "sk-mock-key-01",
});

await client.chat.completions.create({
  model: "gemini-3.6-flash",
  messages: [{ role: "user", content: "Hello!" }],
});

Successful responses come back in OpenAI's shapes — chat.completion objects, [DONE]-terminated streams, tool call arguments as a JSON string. What it serves underneath is still Gemini, and that shows: /models lists Gemini models with owned_by: "google" and a display_name but no created, completions carry no system_fingerprint and no chatcmpl- id prefix, and embeddings come out at Gemini's dimensions rather than OpenAI's 1536.

Errors are Google's, not OpenAI's. The layer translates requests, not failures, and because the request has already become a generateContent call by the time it is validated, that is what the complaint names:

{
  "error": {
    "code": 400,
    "message": "* GenerateContentRequest.contents: contents is not specified\n",
    "status": "INVALID_ARGUMENT"
  }
}
Only what Google actually exposes is mounted. The Responses API and the Files endpoints are absent from the real compatibility layer, so calling them here 404s exactly as it would against Google — use /openai/v1 for the former and Gemini's native /gemini/v1beta/files for the latter.

Gemini · Errors

Two envelopes, and which one you get depends on the surface. Everything except /interactions answers in the google.rpc.Status envelope shared by every Google Cloud API:

{
  "error": {
    "code": 400,
    "message": "API key not valid. Please pass a valid API key.",
    "status": "INVALID_ARGUMENT",
    "details": [ /* only ever present for an invalid API key */ ]
  }
}

The Interactions API uses a flatter, OpenAI-looking envelope for its own errors:

{
  "error": { "code": "invalid_request", "message": "Missing input." }
}
A credential failure on /interactions still uses the classic envelope: Google's API frontend rejects the key before the service ever runs. Verified against the live API, along with everything else on this page marked as checked.

Other providers

Same server, same key set, one page each.

ProviderPrefixReference
OpenAI/openai/v1Read →
Anthropic/anthropicRead →
Gemini Enterprise/gemini-enterpriseRead →
Azure OpenAI/azure/openaiRead →