Use Google Cloud (Vertex AI)
Many teams can’t send their data straight to a model provider. The contract, the bill and the rules about where data may go already live in their cloud account, and a new vendor would mean starting all of that again. Google Cloud runs Gemini inside that account, as a service called Vertex AI, and it signs you in the same way as the rest of your Google Cloud work.
LM15 reaches Vertex AI through the provider vertex. The request is the one
you already know; only the model string changes. What’s new is who signs in,
and which Google Cloud project (the unit that owns the bill, the
permissions and the data rules) the request belongs to.
On this page, we’ll start with the one-time setup in Google Cloud. Then we’ll send a question from a laptop and ask LM15 where it found the sign-in and the project. Next, we’ll run the same code on Google Cloud itself, where there’s nothing to configure, and use an API key instead of a sign-in. We’ll finish with what happens when a sign-in expires, and the mistakes that cost people the most time.
You’ll need a Google account, and the gcloud command-line tool, which
Google’s install page explains.
Every request on this page costs a fraction of a cent.
Set up your project once
Section titled “Set up your project once”Everything on Vertex AI belongs to a project. You need one with billing
turned on, the Vertex AI API enabled, and permission for whoever will call it.
In the Cloud console, or with gcloud:
gcloud projects create station-assistantgcloud billing projects link station-assistant \ --billing-account=YOUR-BILLING-IDgcloud services enable aiplatform.googleapis.com \ --project=station-assistantThe person or program that sends requests needs the Vertex AI User role on
the project. You get it automatically if you created the project; a service
account is given it with gcloud projects add-iam-policy-binding.
A new project, role or service account takes a few minutes to reach all of
Google’s servers. In that time, requests fail with 403 PERMISSION_DENIED,
even though everything is right. If you set something up in the last few
minutes, wait five more before you look for a mistake.
Sign in on your laptop
Section titled “Sign in on your laptop”Sign in once, and tell gcloud which project you work in:
gcloud auth application-default logingcloud config set project station-assistantThe first command opens your browser and saves the sign-in where Google’s
libraries look for it. The second saves the project in gcloud’s settings.
That’s all LM15 needs. The examples in these guides follow
one small program: an assistant for a
wildlife research station. Here’s its first question, sent to Gemini on
Vertex AI:
from lm15 import LMRouter, Message, Request
router = LMRouter()request = Request( model="vertex:gemini-2.5-flash", system=( "You are the field assistant for a wildlife research " "station. Answer in two sentences." ), messages=[Message.user( "What might be eating the acorns under our oak trees at " "night?" )],)print(router.complete(request).text)You're likely seeing activity from smaller critters like mice and voles, who diligently gather acorns for their caches. Larger nocturnal animals such as raccoons, deer, and possibly even bears or wild pigs depending on your location, are also common acorn foragers.
import { LMRouter, Message } from "@lm15/lm15";
const router = new LMRouter();const request = { model: "vertex:gemini-2.5-flash", system: "You are the field assistant for a wildlife research station. " + "Answer in two sentences.", messages: [Message.user( "What might be eating the acorns under our oak trees at night?", )],};const response = await router.complete(request);console.log(response.text);Deer, raccoons, and opossums are prime suspects, as they are all nocturnal and commonly forage for acorns. Smaller mammals like mice and voles also eat acorns at night, though they are much harder to detect.
use lm15::{LMRouter, Message, Request};
// Dependencies: lm15, tokio (macros, rt-multi-thread)#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let router = LMRouter::new(); let request = Request { model: "vertex:gemini-2.5-flash".into(), system: Some(concat!( "You are the field assistant for a wildlife research ", "station. Answer in two sentences.", ).into()), messages: vec![Message::user(concat!( "What might be eating the acorns under our oak trees at ", "night?", ))?], ..Default::default() }; let response = router.complete(&request).await?; println!("{}", response.text().unwrap_or_default()); Ok(())}Common nocturnal visitors like raccoons, opossums, and deer are likely candidates, as they readily consume acorns. Additionally, various small rodents such as mice and voles frequently forage for them under the cover of darkness.
package main
import ( "context" "fmt" lm15 "github.com/lm15-dev/lm15-go/v2")
func main() { router := lm15.NewRouter() request := &lm15.Request{ Model: "vertex:gemini-2.5-flash", System: lm15.System( "You are the field assistant for a wildlife research " + "station. Answer in two sentences.", ), Messages: []lm15.Message{ lm15.UserMessage( "What might be eating the acorns under our oak trees " + "at night?", ), }, } response, err := router.Complete(context.Background(), request) if err != nil { panic(err) } fmt.Println(response.TextOr(""))}You're likely seeing activity from raccoons, opossums, or even deer, all of which actively forage on fallen acorns at night. Checking for distinct tracks or scat around the base of the oak trees could help you identify your specific nighttime feeder.
This page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
The model string vertex:gemini-2.5-flash names the provider, vertex, and
Google’s name for the model. Nothing in the program names a project or a
key: LM15 found both on its own, and the answer came back through our test
project.
Ask where the sign-in came from
Section titled “Ask where the sign-in came from”A program that finds its sign-in on its own should be able to say where. The doctor answers that question without sending anything:
from lm15.doctor import explain_auth
print(explain_auth("vertex"))auth for provider 'vertex': - explicit api_keys entry: not provided - GOOGLE_APPLICATION_CREDENTIALS file: GOOGLE_APPLICATION_CREDENTIALS not set ? gcloud application default credentials file: authorized_user credentials in ~/.config/gcloud/application_default_credentials.json (token exchange at request time) ? GCE metadata server: GCE metadata server probed at request time ? gcloud auth print-access-token: `gcloud` run at request time configured: probably — gcloud application default credentials file, GCE metadata server, gcloud auth print-access-token (unprobed offline) setting location: global (from default) setting project: lm15-vertex-live (from gcloud's active configuration) base url: https://aiplatform.googleapis.com/v1/projects/lm15-vertex-live/locations/global/publishers/google (from template)
import { describeReport, explainAuth } from "@lm15/lm15";
console.log(describeReport(explainAuth("vertex")));auth for provider "vertex": - explicit api_keys entry: not provided - GOOGLE_APPLICATION_CREDENTIALS file: GOOGLE_APPLICATION_CREDENTIALS not set ? gcloud application default credentials file: authorized_user credentials in ~/.config/gcloud/application_default_credentials.json (token exchange at request time) ? GCE metadata server: GCE metadata server probed at request time ? gcloud auth print-access-token: `gcloud` run at request time base URL: https://aiplatform.googleapis.com/v1/projects/lm15-vertex-live/locations/global/publishers/google (template) configured: probably — gcloud application default credentials file, GCE metadata server, gcloud auth print-access-token (unprobed offline) setting location: global (from default) setting project: lm15-vertex-live (from gcloud's active configuration)
use lm15::auth::explain_auth;
fn main() -> Result<(), Box<dyn std::error::Error>> { let report = explain_auth("vertex", &Default::default())?; println!("{}", report.describe()); Ok(())}auth for provider "vertex": - explicit api_keys entry: not provided - GOOGLE_APPLICATION_CREDENTIALS file: GOOGLE_APPLICATION_CREDENTIALS not set ? gcloud application default credentials file: authorized_user credentials in ~/.config/gcloud/application_default_credentials.json (token exchange at request time) ? GCE metadata server: GCE metadata server probed at request time ? gcloud auth print-access-token: `gcloud` run at request time configured: probably — gcloud application default credentials file, GCE metadata server, gcloud auth print-access-token (unprobed offline) setting location: global (from default) setting project: lm15-vertex-live (from gcloud's active configuration) base url: https://aiplatform.googleapis.com/v1/projects/lm15-vertex-live/locations/global/publishers/google (from template)
package main
import ( "fmt" lm15 "github.com/lm15-dev/lm15-go/v2")
func main() { report, err := lm15.ExplainAuth("vertex", lm15.ExplainOptions{}) if err != nil { panic(err) } fmt.Println(report.Describe())}auth for provider "vertex": - explicit api_keys entry: not provided - GOOGLE_APPLICATION_CREDENTIALS file: GOOGLE_APPLICATION_CREDENTIALS not set ? gcloud application default credentials file: authorized_user credentials in ~/.config/gcloud/application_default_credentials.json (token exchange at request time) ? GCE metadata server: GCE metadata server probed at request time ? gcloud auth print-access-token: `gcloud` run at request time configured: probably — gcloud application default credentials file, GCE metadata server, gcloud auth print-access-token (unprobed offline) setting location: global (from default) setting project: lm15-vertex-live (from gcloud's active configuration) base url: https://aiplatform.googleapis.com/v1/projects/lm15-vertex-live/locations/global/publishers/google (from template)
This page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
The report lists every place LM15 looks for a Google sign-in, in the order it
looks. A - means nothing is there. A ? means something is there, but using
it needs the network, so the doctor, which never uses the network, can’t
say whether it will work. Here, the first ? is the saved sign-in from
gcloud auth application-default login, and it’s the one the request above
used. The metadata server, the line after it, only exists on Google Cloud’s
own machines; we’ll come back to it.
The lines that begin with setting say where the request would go, and where
each answer came from. The project, lm15-vertex-live, came from
gcloud’s settings, and the location,
which picks the Google data centers that serve the request, is global,
LM15’s default: Google sends the request wherever it has room. The next
section shows how to choose a location instead.
Run the doctor whenever a request seems to use an account or a project you didn’t expect. It follows the same steps as the real request, and prints no secret, so it’s safe to paste into a bug report.
Where the project comes from
Section titled “Where the project comes from”LM15 looks for the project in the same places, in the same order, as Google’s own libraries. The first place that names one wins:
- The router’s settings, if your program gives a project.
- The
GOOGLE_CLOUD_PROJECTenvironment variable (or its older name,GCLOUD_PROJECT). - The service-account file named by
GOOGLE_APPLICATION_CREDENTIALS. gcloud’s settings: theCLOUDSDK_CORE_PROJECTvariable, then the project saved bygcloud config set project.- The quota project saved with your
gcloudsign-in. - The metadata server, on Google Cloud’s own machines.
The location works the same way, with fewer places: the router’s settings,
then GOOGLE_CLOUD_LOCATION, then global. Setting it matters when your data
must stay in a region: europe-west4, for example, sends requests to Google’s
data centers in the Netherlands.
A good rule of thumb: on a laptop, let gcloud hold the project; in
production, set GOOGLE_CLOUD_PROJECT, or let the metadata server answer,
and never depend on a developer’s settings being there.
Run on Google Cloud
Section titled “Run on Google Cloud”On Cloud Run, GKE, a virtual machine or Cloud Functions, your program runs as a service account, an identity Google gives to programs instead of people. Give it the Vertex AI User role, attach it to the service, and set nothing else. Both the sign-in and the project come from the metadata server, a small server every Google Cloud machine can reach, which answers for the machine.
The code from your laptop works there unchanged. It’s better, though, to say which sign-in you expect:
from lm15 import LMRouter, Message, Request, RouterConfig
router = LMRouter(RouterConfig( credentials={"vertex": "platform"},))request = Request( model="vertex:gemini-2.5-flash", system=( "You are the field assistant for a wildlife research " "station. Answer in two sentences." ), messages=[Message.user( "What might be eating the acorns under our oak trees at " "night?" )],)print(router.complete(request).text)import { LMRouter, Message } from "@lm15/lm15";
const router = new LMRouter({ credentials: { vertex: "platform" },});const request = { model: "vertex:gemini-2.5-flash", system: "You are the field assistant for a wildlife research station. " + "Answer in two sentences.", messages: [Message.user( "What might be eating the acorns under our oak trees at night?", )],};const response = await router.complete(request);console.log(response.text);use lm15::{LMRouter, Message, Request, RouterConfig};
// Dependencies: lm15, tokio (macros, rt-multi-thread)#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let config = RouterConfig::new().credential("vertex", "platform"); let router = LMRouter::with_config(config)?; let request = Request { model: "vertex:gemini-2.5-flash".into(), system: Some(concat!( "You are the field assistant for a wildlife research ", "station. Answer in two sentences.", ).into()), messages: vec![Message::user(concat!( "What might be eating the acorns under our oak trees at ", "night?", ))?], ..Default::default() }; let response = router.complete(&request).await?; println!("{}", response.text().unwrap_or_default()); Ok(())}package main
import ( "context" "fmt" lm15 "github.com/lm15-dev/lm15-go/v2")
func main() { router, err := lm15.NewRouterWithConfig(lm15.RouterConfig{ Credentials: map[string]string{"vertex": "platform"}, }) if err != nil { panic(err) } request := &lm15.Request{ Model: "vertex:gemini-2.5-flash", System: lm15.System( "You are the field assistant for a wildlife research " + "station. Answer in two sentences.", ), Messages: []lm15.Message{ lm15.UserMessage( "What might be eating the acorns under our oak trees " + "at night?", ), }, } response, err := router.Complete(context.Background(), request) if err != nil { panic(err) } fmt.Println(response.TextOr(""))}This page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
"platform" tells LM15 to use the machine’s own identity and nothing else.
Without it, LM15 tries every place in the doctor’s list, so a program that
someone deployed with a leftover key file would quietly run as that key
instead. With it, a missing identity stops the program at the first request,
with an error that names what it looked for. We sent the same request this way
from a Google Cloud virtual machine with no project set anywhere, in all four
languages; each found the project and the identity, and answered. On a laptop, it stops at
once, which is what you want: a laptop isn’t the machine it names.
Run somewhere else
Section titled “Run somewhere else”GitHub Actions, another cloud, or a server in your own building have no
metadata server. The best choice there is workload identity federation:
Google trusts the identity those systems already give your program, so no
Google password ever sits on the machine. Google’s
federation guide
walks through the setup; at the end, gcloud iam workload-identity-pools create-cred-config writes a small file. Point GOOGLE_APPLICATION_CREDENTIALS
at that file, and name the sign-in "workload" instead of "platform".
A service-account key file works the same way, named "environment". It’s a
password that never expires: anyone who copies it can spend your money until
someone notices. Use federation when you can.
Use an API key
Section titled “Use an API key”Some tools, and some people, would rather work with a key than with a sign-in. Vertex AI accepts API keys too. Create one in the Cloud console, under APIs & Services, then Credentials, and restrict it to the Vertex AI API.
Give the key to the router, and choose a location while you’re at it:
import os
from lm15 import LMRouter, Message, Request, RouterConfig
router = LMRouter(RouterConfig( api_keys={"vertex": os.environ["VERTEX_API_KEY"]}, settings={"vertex": {"location": "europe-west4"}},))request = Request( model="vertex:gemini-2.5-flash", system=( "You are the field assistant for a wildlife research " "station. Answer in two sentences." ), messages=[Message.user( "What might be eating the acorns under our oak trees at " "night?" )],)print(router.complete(request).text)Deer, raccoons, mice, and opossums are common nocturnal visitors that frequently forage for acorns under oak trees. You might also find squirrels gathering them very early in the morning or late evening, or even a bear if they are present in your area.
import { LMRouter, Message } from "@lm15/lm15";
const router = new LMRouter({ apiKeys: { vertex: process.env.VERTEX_API_KEY! }, settings: { vertex: { location: "europe-west4" } },});const request = { model: "vertex:gemini-2.5-flash", system: "You are the field assistant for a wildlife research station. " + "Answer in two sentences.", messages: [Message.user( "What might be eating the acorns under our oak trees at night?", )],};const response = await router.complete(request);console.log(response.text);Deer and raccoons are very common nocturnal visitors that feast on fallen acorns. You might also find opossums, mice, or even local squirrels foraging under the cover of darkness.
use lm15::{LMRouter, Message, Request, RouterConfig};
// Dependencies: lm15, tokio (macros, rt-multi-thread)#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let config = RouterConfig::new() .api_key("vertex", std::env::var("VERTEX_API_KEY")?) .setting("vertex", "location", "europe-west4"); let router = LMRouter::with_config(config)?; let request = Request { model: "vertex:gemini-2.5-flash".into(), system: Some(concat!( "You are the field assistant for a wildlife research ", "station. Answer in two sentences.", ).into()), messages: vec![Message::user(concat!( "What might be eating the acorns under our oak trees at ", "night?", ))?], ..Default::default() }; let response = router.complete(&request).await?; println!("{}", response.text().unwrap_or_default()); Ok(())}Common culprits include deer, raccoons, and mice, all of whom are active at night and relish acorns. Depending on your location, opossums, bears, or wild pigs could also be foraging under the trees.
package main
import ( "context" "fmt" "os" lm15 "github.com/lm15-dev/lm15-go/v2")
func main() { key := os.Getenv("VERTEX_API_KEY") router, err := lm15.NewRouterWithConfig(lm15.RouterConfig{ APIKeys: map[string]lm15.CredentialLike{"vertex": key}, Settings: map[string]map[string]string{ "vertex": {"location": "europe-west4"}, }, }) if err != nil { panic(err) } request := &lm15.Request{ Model: "vertex:gemini-2.5-flash", System: lm15.System( "You are the field assistant for a wildlife research " + "station. Answer in two sentences.", ), Messages: []lm15.Message{ lm15.UserMessage( "What might be eating the acorns under our oak trees " + "at night?", ), }, } response, err := router.Complete(context.Background(), request) if err != nil { panic(err) } fmt.Println(response.TextOr(""))}Common culprits eating acorns at night include deer, raccoons, mice, and even some squirrel species depending on the region and food availability. You might also find evidence of opossums or various rodent species feasting on the fallen nuts.
This page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
We ran this on a machine with no gcloud sign-in at all; the project came
from GOOGLE_CLOUD_PROJECT. The request still belongs to the project, and
it ran in europe-west4, the location in the settings.
If you have a key and nothing else, not even a project, use the provider
vertex-express instead. It reads the key from GOOGLE_API_KEY, and Google
chooses where the request runs:
from lm15 import LMRouter, Message, Request
router = LMRouter()request = Request( model="vertex-express:gemini-2.5-flash", system=( "You are the field assistant for a wildlife research " "station. Answer in two sentences." ), messages=[Message.user( "What might be eating the acorns under our oak trees at " "night?" )],)print(router.complete(request).text)Deer, raccoons, and opossums are common nocturnal visitors that feast on acorns. You might also find smaller critters like mice or even flying squirrels foraging for them under the cover of darkness.
import { LMRouter, Message } from "@lm15/lm15";
const router = new LMRouter();const request = { model: "vertex-express:gemini-2.5-flash", system: "You are the field assistant for a wildlife research station. " + "Answer in two sentences.", messages: [Message.user( "What might be eating the acorns under our oak trees at night?", )],};const response = await router.complete(request);console.log(response.text);Raccoons, opossums, and deer are common nocturnal visitors that readily forage for fallen acorns under oak trees. Smaller mammals like mice and voles also consume acorns at night, sometimes dragging them away to caches.
use lm15::{LMRouter, Message, Request};
// Dependencies: lm15, tokio (macros, rt-multi-thread)#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let router = LMRouter::new(); let request = Request { model: "vertex-express:gemini-2.5-flash".into(), system: Some(concat!( "You are the field assistant for a wildlife research ", "station. Answer in two sentences.", ).into()), messages: vec![Message::user(concat!( "What might be eating the acorns under our oak trees at ", "night?", ))?], ..Default::default() }; let response = router.complete(&request).await?; println!("{}", response.text().unwrap_or_default()); Ok(())}Common nocturnal visitors that enjoy acorns include raccoons, deer, opossums, and various species of mice or voles. You might find scat, tracks, or partially eaten nuts that could help identify the specific culprit.
package main
import ( "context" "fmt" lm15 "github.com/lm15-dev/lm15-go/v2")
func main() { router := lm15.NewRouter() request := &lm15.Request{ Model: "vertex-express:gemini-2.5-flash", System: lm15.System( "You are the field assistant for a wildlife research " + "station. Answer in two sentences.", ), Messages: []lm15.Message{ lm15.UserMessage( "What might be eating the acorns under our oak trees " + "at night?", ), }, } response, err := router.Complete(context.Background(), request) if err != nil { panic(err) } fmt.Println(response.TextOr(""))}Common nocturnal visitors like deer, raccoons, and various rodent species such as mice or voles are frequent acorn eaters. You might also find opossums, or even black bears in some regions, feasting on the fallen nuts under the cover of darkness.
This page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
Two details avoid surprises. First, vertex sends text you give it as a key,
unless it looks like a sign-in token (Google’s begin with ya29.), which it
sends as a token; either works. Second, vertex never reads GOOGLE_API_KEY
from the environment. That variable usually holds a key for Google’s other
Gemini service, and picking it up would quietly change which account pays.
When a sign-in expires
Section titled “When a sign-in expires”A saved sign-in doesn’t last forever. Your organization may end sessions after a set time, Google may ask you to sign in again after a password change, and anyone can revoke one. To show what that looks like, we revoked the saved sign-in on a test machine, sent the question again, and printed the error:
from lm15 import AuthError, LMRouter, Message, Request
router = LMRouter()request = Request( model="vertex:gemini-2.5-flash", system=( "You are the field assistant for a wildlife research " "station. Answer in two sentences." ), messages=[Message.user( "What might be eating the acorns under our oak trees at " "night?" )],)try: print(router.complete(request).text)except AuthError as error: print(error)Google OAuth refresh (~/.config/gcloud/application_default_credentials.json): HTTP 400 (invalid_grant)
To fix:
- the saved Google login in ~/.config/gcloud/application_default_credentials.json has expired or was revoked; run `gcloud auth application-default login` (Google ends these sessions on its own schedule)import { LMRouter, Message } from "@lm15/lm15";
const router = new LMRouter();const request = { model: "vertex:gemini-2.5-flash", system: "You are the field assistant for a wildlife research station. " + "Answer in two sentences.", messages: [Message.user( "What might be eating the acorns under our oak trees at night?", )],};try { console.log((await router.complete(request)).text);} catch (error) { console.log(String(error));}AuthError: Google OAuth refresh (~/.config/gcloud/application_default_credentials.json): HTTP 400 (invalid_grant)
Credential source attempted: gcloud application default credentials file [adc-file]
To fix:
- the saved Google login in ~/.config/gcloud/application_default_credentials.json has expired or was revoked; run `gcloud auth application-default login` (Google ends these sessions on its own schedule)use lm15::{LMRouter, Message, Request};
// Dependencies: lm15, tokio (macros, rt-multi-thread)#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let router = LMRouter::new(); let request = Request { model: "vertex:gemini-2.5-flash".into(), system: Some(concat!( "You are the field assistant for a wildlife research ", "station. Answer in two sentences.", ).into()), messages: vec![Message::user(concat!( "What might be eating the acorns under our oak trees at ", "night?", ))?], ..Default::default() }; match router.complete(&request).await { Ok(response) => { println!("{}", response.text().unwrap_or_default()) } Err(error) => println!("{error}"), } Ok(())}AuthError: vertex: Google OAuth refresh (~/.config/gcloud/application_default_credentials.json): HTTP 400 (invalid_grant)
Credential source: gcloud application default credentials file (vertex)
To fix:
- the saved Google login in ~/.config/gcloud/application_default_credentials.json has expired or was revoked; run `gcloud auth application-default login` (Google ends these sessions on its own schedule)package main
import ( "context" "fmt" lm15 "github.com/lm15-dev/lm15-go/v2")
func main() { router := lm15.NewRouter() request := &lm15.Request{ Model: "vertex:gemini-2.5-flash", System: lm15.System( "You are the field assistant for a wildlife research " + "station. Answer in two sentences.", ), Messages: []lm15.Message{ lm15.UserMessage( "What might be eating the acorns under our oak trees " + "at night?", ), }, } response, err := router.Complete(context.Background(), request) if err != nil { fmt.Println(err) return } fmt.Println(response.TextOr(""))}Google OAuth refresh (~/.config/gcloud/application_default_credentials.json): HTTP 400 (invalid_grant)
To fix:
- the saved Google login in ~/.config/gcloud/application_default_credentials.json has expired or was revoked; run `gcloud auth application-default login` (Google ends these sessions on its own schedule)This page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
The first line says which step failed: renewing the saved sign-in, from the
file named in parentheses. invalid_grant is the standard word for a sign-in
the server no longer accepts. The rest says what to do. Run the command, and
the next request works.
You won’t see Google’s full explanation in the error, only that standard
word. A server’s explanation can repeat parts of the request, and this
request carried your saved sign-in; LM15 never lets it into an error message
that might end up in a log. To see why Google refused, run the gcloud
command yourself.
Mistakes that cost the most time
Section titled “Mistakes that cost the most time”- A 403 right after setup. New projects, roles and service accounts take a few minutes to apply. Wait before changing anything.
- Claude on Vertex AI. The provider
vertex-anthropicreaches Anthropic’s models in your project, with the same sign-ins. A new project can’t use them yet: in ours, the request signed in and then Google answered429 Quota exceeded, because a new project has no quota for these models until you request it. Claude on Vertex AI doesn’t accept API keys. - A model that isn’t in your location. Not every model runs everywhere.
In our tests,
gemini-2.5-flashanswered inglobal,us-central1andeurope-west4, and Google said “not found” in theusandeumulti-region locations. - Two projects in play. If
GOOGLE_CLOUD_PROJECTandgcloud’s settings name different projects, the variable wins. The doctor shows which one.
Try it yourself
Section titled “Try it yourself”Predict the answer before you run the doctor.
- You run
gcloud config set project station-dev, and later, in the same terminal,export GOOGLE_CLOUD_PROJECT=station-prod. Which project will the request use? - On Cloud Run, with the service account attached and nothing set, a
colleague adds
GOOGLE_APPLICATION_CREDENTIALSpointing at a key file for another project. Which identity signs the request, and which project does it use? - The same colleague now adds
"platform", as in Run on Google Cloud. What changes?
What to expect
station-prod. The environment variable comes beforegcloud’s settings, and the doctor saysfrom env $GOOGLE_CLOUD_PROJECT.- The key file’s service account, and the key file’s project. The file comes before the metadata server in both lists, so the machine’s own identity is never asked. This is the leftover-key problem from Run on Google Cloud.
- LM15 uses the machine’s own identity and ignores the file as a sign-in.
The project still comes from the key file, because the project list doesn’t
depend on which sign-in is used; set
GOOGLE_CLOUD_PROJECTif that matters.
Use a cloud provider compares Google Cloud with AWS and Azure, and lists the providers LM15 has for each. Set up authentication explains the rule behind this page, that LM15 always says which credential it used, for every provider, and how to give it one from your own code. Handle errors and retries shows how to catch a failed sign-in in a long-running program. Choose a model lists the Gemini models you can ask for.