Home / AI

Production Gemini API on Vertex AI: Project Setup, IAM, Quotas & Python SDK | 2026

Production Gemini API on Vertex AI: Project Setup, IAM, Quotas & Python SDK | 2026

To run the Gemini API on Vertex AI in production: enable aiplatform.googleapis.com, create a service account with roles/aiplatform.user (never roles/owner), authenticate locally with gcloud auth application-default login, then call client.models.generate_content(model="gemini-2.5-flash") via the google-genai SDK with location="global". Pay-as-you-go capacity is governed by Dynamic Shared Quota; buy Provisioned Throughput when you need guaranteed throughput.

By Mateusz Chmielewski · Aug 19, 2026 · 14 min read

What Is the Gemini API on Vertex AI?

The Gemini API on Vertex AI is Google Cloud's enterprise surface for calling Gemini models: the same model family as Google AI Studio, but served through the aiplatform.googleapis.com endpoint with GCP-native authentication (IAM, service accounts, ADC), per-model per-region quotas, VPC-SC support, audit logging, and consolidated billing. You call it with the google-genai Python SDK — one client class that targets Vertex AI when constructed with vertexai=True, project, and location. Models current in 2026 are gemini-2.5-pro (hardest reasoning), gemini-2.5-flash (default workhorse), and gemini-2.5-flash-lite (cheap high-volume classification and extraction).

Think of AI Studio as a hotel's demo kitchen where you cook with a door code (an API key) — fine for tasting. Vertex AI is the restaurant's real kitchen staff badges (IAM roles) decide who can use which station, every dish is logged (Cloud Audit Logs), the ingredients arrive on your restaurant's invoice (project billing), and when the dinner rush hits, the shared supply (Dynamic Shared Quota) is first-come-first-served unless you have reserved your own supply contract (Provisioned Throughput).

ConceptExplanationWhen to use
ADC (Application Default Credentials)The credential chain Google client libraries resolve automatically — your user identity locally via gcloud, an attached service account on GCP compute.Always. One auth mechanism that works on your laptop, Cloud Run, GKE, and Cloud Functions without code changes.
roles/aiplatform.userThe least-privilege role that allows calling Vertex AI models and running jobs in a project.For every workload identity that calls Gemini — never roles/owner or roles/editor for application service accounts.
location (global vs regional)The endpoint geography for the request; "global" routes to the newest models and dynamic capacity, regional endpoints pin data to a specific region.Use "global" for newest models and highest DSQ availability; use a regional endpoint when data residency requires it.
Dynamic Shared Quota (DSQ)Pay-as-you-go capacity model where requests-per-minute share a regional pool per model instead of a static per-project number.Default for all pay-as-you-go usage — design for occasional 429s with backoff instead of filing quota-increase tickets.
Provisioned Throughput (PT)Purchased capacity in Generative AI SKU Units (GSUs) that guarantees throughput for a model in a region.When latency SLOs or burst patterns make shared capacity unacceptable — steady, high-volume production traffic.

Why Vertex AI Instead of an AI Studio API Key?

The default path — an API key from Google AI Studio pasted into an environment variable — falls apart in production: keys are long-lived secrets that leak into repos and logs, there is no IAM story (you cannot scope a key to one model or one service), no VPC Service Controls, no Cloud Audit Logs per caller, and quota/billing live outside your GCP project's guardrails. Rotating a leaked key means redeploying every consumer.

On Vertex AI, authentication is a service account with roles/aiplatform.user — short-lived OAuth tokens minted by the metadata server or ADC, nothing to leak, full audit trail, and spend lands in your project's budget alerts. The google-genai SDK keeps the calling code identical to AI Studio, so migration is a client-constructor change, not a rewrite. Once traffic grows, control spend with [Gemini API cost controls, quotas, and budget alerts](/tutorial/gemini-api-cost-control-vertex-ai-quotas-budget-alerts), and for keyless CI pipelines use [Workload Identity Federation for GitHub Actions](/tutorial/workload-identity-federation-github-actions-gcp).

FeaturethisServicealtAaltB
AuthenticationIAM + ADC / service accounts, short-lived tokensAI Studio API key (long-lived secret)Self-hosted open model on GKE
Least-privilege scopingPer-identity roles/aiplatform.user, auditableOne key = full access to the APIFull cluster IAM + network policies to build
Infrastructure to manageNone (managed endpoint)None (managed endpoint)GPUs, model serving stack, autoscaling
Compliance postureVPC-SC, audit logs, regional endpoints, CMEKLimited; outside GCP project controlsYours to build and certify end to end
Capacity modelDSQ shared pool; Provisioned Throughput for guaranteesShared free/paid tiers with fixed rate limitsWhatever hardware you provision

Prerequisites

  • GCP project with billing enabled and permission to enable APIs (roles/serviceusage.serviceUsageAdmin or owner)
  • gcloud CLI v450.0+ installed and authenticated (gcloud auth login)
  • Python 3.10+ with a virtual environment (python3 -m venv .venv)
  • IAM permission to create service accounts and grant roles (roles/iam.serviceAccountAdmin + roles/resourcemanager.projectIamAdmin, or owner)
  • Approximate budget expectation: gemini-2.5-flash at approx. $0.30 / 1M input + $2.50 / 1M output tokens — verify on the pricing page

Step-by-Step Guide

Step 1: Set the Project and Enable the Vertex AI API

Create or select a dedicated GCP project for your Gemini workload and enable aiplatform.googleapis.com — the single API that fronts every Gemini model call on Vertex AI. A dedicated project isolates quota consumption, billing attribution, and IAM blast radius from your other workloads; every 403 PERMISSION_DENIED you will ever see traces back to either this API being disabled or a missing role.

export PROJECT_ID=my-gemini-project
gcloud config set project $PROJECT_ID

gcloud services enable aiplatform.googleapis.com --project=$PROJECT_ID

# Confirm it stuck
gcloud services list --enabled --project=$PROJECT_ID \
  --filter="name:aiplatform.googleapis.com"

Step 2: Create a Least-Privilege Service Account

Create the service account your application will run as, and grant it roles/aiplatform.user at project scope — the minimum role that permits Gemini inference calls. roles/owner or roles/editor on an application identity means a prompt-injection or dependency compromise turns into full project takeover. roles/aiplatform.user can call models and nothing else — no storage reads, no IAM changes. (roles/aiplatform.serviceAgent is a different role: Google grants it to the Vertex AI service agent itself, not to your workloads.)

gcloud iam service-accounts create gemini-app-sa \
  --display-name="Gemini App Service Account" \
  --project=$PROJECT_ID

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:gemini-app-sa@$PROJECT_ID.iam.gserviceaccount.com" \
  --role="roles/aiplatform.user"

Step 3: Authenticate Locally with Application Default Credentials

Set up ADC on your workstation so the SDK picks up credentials with zero code, and export the three environment variables that configure the google-genai client without touching source. ADC is the same contract your code uses in production (attached service account) and locally (your user identity) — no auth branches in code, no credentials in the repo. The env vars make project, location, and backend deployment-time configuration instead of compile-time constants.

gcloud auth application-default login

# Point the ADC quota at your project (avoids warnings + misattributed usage)
gcloud auth application-default set-quota-project $PROJECT_ID

# Zero-code SDK configuration
export GOOGLE_CLOUD_PROJECT=$PROJECT_ID
export GOOGLE_CLOUD_LOCATION=global
export GOOGLE_GENAI_USE_VERTEXAI=true

Step 4: Install the SDK and Make the First generate_content Call

Install the google-genai package in a virtual environment and call gemini-2.5-flash — the 2026 default workhorse model — through the Vertex AI backend with location="global". google-genai is the current, unified SDK (the older google-cloud-aiplatform generative-models surface is legacy for new code). location="global" is where the newest models land first and where DSQ capacity is pooled across regions — region-pinned endpoints are for data-residency requirements, not defaults.

from google import genai

# Explicit constructor — or delete the args and rely on the env vars from step 3
client = genai.Client(
    vertexai=True,
    project="PROJECT_ID",
    location="global",
)

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Explain the difference between ADC and a service account key in two sentences.",
)
print(response.text)
print("input tokens:", response.usage_metadata.prompt_token_count)
print("output tokens:", response.usage_metadata.candidates_token_count)

Step 5: Harden the Client — Timeouts, Retries, and Structured Output

Turn the demo call into production shape: a request timeout, retry-aware error handling for 429/5xx, and schema-enforced JSON output so downstream code parses a contract instead of hoping free text is well-formed. Unbounded HTTP calls hang workers under DSQ contention; unhandled 429s turn transient contention into user-facing errors; and regex-parsing prose output is the single most common cause of silent LLM pipeline failures. response_mime_type="application/json" plus response_schema makes the model emit conforming JSON natively.

import time
from google import genai
from google.genai import types

client = genai.Client(vertexai=True, project="PROJECT_ID", location="global")

ticket_schema = {
    "type": "OBJECT",
    "properties": {
        "category": {"type": "STRING"},
        "priority": {"type": "STRING", "enum": ["low", "medium", "high"]},
        "summary": {"type": "STRING"},
    },
    "required": ["category", "priority", "summary"],
}

def classify(text: str, max_attempts: int = 4) -> dict:
    for attempt in range(max_attempts):
        try:
            resp = client.models.generate_content(
                model="gemini-2.5-flash",
                contents=f"Classify this support ticket: {text}",
                config=types.GenerateContentConfig(
                    response_mime_type="application/json",
                    response_schema=ticket_schema,
                    temperature=0.1,
                    http_options=types.HttpOptions(timeout=30_000),  # ms
                ),
            )
            return resp.parsed if hasattr(resp, "parsed") else resp.text
        except Exception as e:
            if "429" in str(e) or "503" in str(e):
                time.sleep(min(2 ** attempt, 30))  # exponential backoff + jitter in prod
                continue
            raise
    raise RuntimeError("exhausted retries against Gemini endpoint")

Step 6: Check Quotas and Understand Dynamic Shared Quota

Inspect the Vertex AI quotas for your project and learn why generative models behave differently on pay-as-you-go: there is no static RPM number to raise — capacity is a shared per-model, per-region pool (DSQ). Teams burn weeks filing quota-increase requests that cannot be granted for Gemini on pay-as-you-go. The real levers are: backoff and jitter, spreading traffic across regions, switching to flash-lite for cheap bulk work, or buying Provisioned Throughput (GSUs) for guaranteed capacity. Knowing which lever to pull starts with reading your actual usage.

# Console: IAM & Admin > Quotas, filter Service: aiplatform.googleapis.com

# CLI equivalent — list quota info for the Vertex AI service
gcloud alpha service-quotas list \
  --service=aiplatform.googleapis.com \
  --project=$PROJECT_ID

# Watch your real-time usage and errors in Metrics Explorer:
#   aiplatform.googleapis.com/prediction/online/response_count
#   filtered by model and response code (429 = DSQ contention)

Step 7: Codify Project and IAM Setup in Terraform (Optional but Recommended)

Express steps 1-2 as Terraform so the environment is reproducible across dev/stage/prod and reviewable in pull requests. Click-ops IAM drifts — someone grants editor "temporarily" and it lives forever. Terraform makes roles/aiplatform.user the reviewed, permanent state and recreates the whole project bootstrap in minutes for a new environment.

variable "project_id" {
  type        = string
  description = "GCP project hosting the Gemini workload"
}

resource "google_project_service" "aiplatform" {
  project            = var.project_id
  service            = "aiplatform.googleapis.com"
  disable_on_destroy = false
}

resource "google_service_account" "gemini_app" {
  account_id   = "gemini-app-sa"
  display_name = "Gemini App Service Account"
  project      = var.project_id
}

resource "google_project_iam_member" "gemini_app_aiplatform_user" {
  project = var.project_id
  role    = "roles/aiplatform.user"
  member  = "serviceAccount:${google_service_account.gemini_app.email}"
}

output "gemini_service_account" {
  value = google_service_account.gemini_app.email
}

Verification & Health Check

Best Practices

  • Least Privilege, Always
  • ADC and Service Accounts, Never API Keys
  • Config in Environment, Not Code
  • Contracted Output, Not Free Text

Common Mistakes

  • {"errorCode":"PERMISSION_DENIED (403)","symptoms":"google.api_core.exceptions.PermissionDenied: 403 Permission 'aiplatform.endpoints.predict' denied — or API has not been used in project before.","rootCause":"The calling identity lacks roles/aiplatform.user on the project, or aiplatform.googleapis.com was never enabled. With ADC, remember the identity is your user account, not the app's service account.","fixCommand":"gcloud services enable aiplatform.googleapis.com --project=$PROJECT_ID\ngcloud projects add-iam-policy-binding $PROJECT_ID \\\n --member=\"serviceAccount:gemini-app-sa@$PROJECT_ID.iam.gserviceaccount.com\" \\\n --role=\"roles/aiplatform.user\"\n","code":"resource \"google_project_iam_member\" \"gemini_app_aiplatform_user\" {\n project = var.project_id\n role = \"roles/aiplatform.user\"\n member = \"serviceAccount:${google_service_account.gemini_app.email}\"\n}\n","language":"hcl","filename":"fix-iam.tf","prevention":"Codify the API enablement and the single role binding in Terraform (step 7) so no environment ever depends on click-ops IAM."}
  • {"errorCode":"RESOURCE_EXHAUSTED (429)","symptoms":"429 Quota exceeded for aiplatform.googleapis.com/generate_content_requests — intermittent under burst, no fixed limit visible in the quota console.","rootCause":"Dynamic Shared Quota contention — on pay-as-you-go your requests compete for a shared per-model, per-region capacity pool, and bursts lose the race.","fixCommand":"gcloud alpha service-quotas list \\\n --service=aiplatform.googleapis.com --project=$PROJECT_ID\n","code":"# Exponential backoff with jitter; spread across regions as a second lever\nimport random, time\ndelay = min(2 ** attempt, 30) + random.uniform(0, 1)\ntime.sleep(delay)\n","language":"python","filename":"fix-backoff.py","prevention":"Build backoff+jitter in from day one, alert on sustained 429 rate, and buy Provisioned Throughput (GSUs) when latency SLOs cannot tolerate shared-capacity contention."}
  • {"errorCode":"UNAUTHENTICATED / 401-403 with API key","symptoms":"Requests with x-goog-api-key or an AIza... key against aiplatform.googleapis.com are rejected, while the same key works on generativelanguage.googleapis.com.","rootCause":"Mixing the two backends — AI Studio keys authenticate against the Gemini Developer API only; Vertex AI endpoints require OAuth2 tokens from ADC or a service account.","fixCommand":"gcloud auth application-default print-access-token\n","code":"# Wrong backend for a key — use ADC against Vertex AI instead\nfrom google import genai\nclient = genai.Client(vertexai=True, project=\"PROJECT_ID\", location=\"global\")\n","language":"python","filename":"fix-auth.py","prevention":"Standardize on genai.Client(vertexai=True) everywhere and never store API keys in CI secrets for Vertex workloads; use Workload Identity Federation for pipelines."}
  • {"errorCode":"NOT_FOUND (404) model not available","symptoms":"404 Publisher Model projects/.../locations/europe-west4/publishers/google/models/gemini-2.5-flash was not found or your project does not have access.","rootCause":"Location mismatch — the model is not served in the pinned region (newest models land on the global endpoint and selected regions first).","fixCommand":"# Switch the client to the global endpoint\nexport GOOGLE_CLOUD_LOCATION=global\n","code":"client = genai.Client(vertexai=True, project=\"PROJECT_ID\", location=\"global\")\n","language":"python","filename":"fix-location.py","prevention":"Default to location=\"global\" unless data residency forces a regional endpoint, and verify model availability per region in the docs before pinning one."}
  • {"errorCode":"ADC quota project warning / attribution errors","symptoms":"WARNING: Your default credentials were found, but not associated with a quota project — or org policy blocks ADC calls without a quota project.","rootCause":"gcloud auth application-default login stores user credentials without binding them to a project for quota and billing attribution.","fixCommand":"gcloud auth application-default set-quota-project $PROJECT_ID\n","prevention":"Bake the set-quota-project command into the developer onboarding script right after application-default login so no one hits it fresh."}

Cost Analysis

Featuremetriccost1kcost10kcost100kcost1m
Gemini 2.5 Flash tokens (approx. $0.30/1M in + $2.50/1M out)$0.90$9.00$90.00$900.00
Gemini 2.5 Flash-Lite tokens (approx. $0.10/1M in + $0.40/1M out)$0.17$1.70$17.00$170.00
Gemini 2.5 Pro tokens (approx. $1.25/1M in + $10.00/1M out)$3.63$36.25$362.50$3,625.00
Provisioned Throughput (GSU purchase, guaranteed capacity)n/a (flat monthly commit)n/a (flat monthly commit)n/a (flat monthly commit)n/a (flat monthly commit)
Cloud Logging / Monitoring ingestion (approx.)$0.00$0.05$0.50$5.00

References

Browse all tutorials