Skip to content

Service Account Permissions for Vertex AI

10 min read · updated August 11, 2026

Almost every Vertex AI permission problem is really a confusion between three different identities that all appear in one prediction call. Name them and the fix is usually one line.

Three identities, one request

  • The caller. Whoever sends the request to endpoints:predict. This is your application’s service account, and the only permission it strictly needs is aiplatform.endpoints.predict.
  • The deployed container’s identity. Set with --service-account on gcloud ai endpoints deploy-model. This is who your serving code is when it reads model artifacts from Cloud Storage, fetches a secret, or writes a log. It is not the caller and it does not inherit the caller’s permissions.
  • The Vertex AI service agent. A Google-managed account in the form [email protected] holding roles/aiplatform.serviceAgent. It is what stages your artifacts, pulls your image from Artifact Registry and provisions machines. You do not create it and you should not remove its role — a deploy that fails with a permission error naming an account you have never seen is usually this one, having lost access to a bucket or a repository in another project.

The single most common wasted afternoon in this area is granting the caller access to the model’s Cloud Storage bucket because prediction is failing to load artifacts. The caller never touches that bucket. The container’s identity does.

Why roles/aiplatform.user is too much

roles/aiplatform.user — labelled Vertex AI User in older documentation and Agent Platform User in newer pages, with the identifier unchanged — is the default advice because it contains aiplatform.endpoints.predict and therefore works. It also contains a great deal else: creating and deleting endpoints, deploying and undeploying models, starting training and tuning jobs, and creating batch prediction jobs. Every one of those spends money, and several of them can take a production endpoint offline.

For a web application whose entire relationship with Vertex AI is “send a prompt, receive a completion”, that grant means a compromised application credential can also undeploy the model it was calling. Google’s own access-control guidance acknowledges the alternative directly: where least privilege is required, create a custom role containing only aiplatform.endpoints.predict and bind it in place of roles/aiplatform.user.

Building the minimal role

  1. Create the role at project or organisation level. Organisation level is better if more than one project will use it, because a role defined per project has to be maintained per project.
  2. Bind it to the calling service account.
  3. Verify by calling the endpoint and by attempting something the role should not permit.
gcloud iam roles create vertexPredictOnly \
  --project=PROJECT \
  --title="Vertex AI predict only" \
  --description="Call deployed endpoints; nothing else." \
  --permissions=aiplatform.endpoints.predict \
  --stage=GA

gcloud projects add-iam-policy-binding PROJECT \
  --member=serviceAccount:[email protected] \
  --role=projects/PROJECT/roles/vertexPredictOnly

Verification matters more than usual here, because the failure is asymmetric: too few permissions produces a loud error at the first call, while too many produces nothing at all until something goes wrong. Prove the negative explicitly — with the new role bound and the old one removed, gcloud ai endpoints list should fail. If it succeeds, the broad role is still bound somewhere, most often inherited from a folder or granted to a group the account belongs to.

The reliable way to answer “what can this account actually do” is not to read the role. Policy Troubleshooter and the IAM policy analyser evaluate the effective grant including inheritance from folders and the organisation, group memberships, and conditional bindings, and they routinely surface a grant nobody in the room knew about. A minimal custom role bound alongside an inherited Editor is a minimal role in name only.

For generative models called by publisher path rather than through your own endpoint, the same permission is the one in play. Callers hitting publishers/google/models/MODEL:generateContent need predict permission on the project, not a separate grant per model, which is convenient and also means the role above covers both cases without modification.

Binding it as narrowly as it will go

The obvious next step is to bind the role on a single endpoint rather than on the whole project, so a service that should only reach the summarisation endpoint cannot reach the classification one. This is supported, and it is worth knowing that the CLI will not do it for you: the gcloud ai endpoints command group contains create, deploy-model, describe, list, predict, raw-predict, stream-raw-predict, undeploy-model, update and their direct and streaming variants, and no IAM policy subcommands at all. Resource-level bindings go through the setIamPolicy method on the endpoint resource, or through the endpoint’s permissions pane in the console.

If per-endpoint policy is more machinery than you want, the pragmatic alternative is one project per trust boundary. A project boundary is cheap, is understood by every other Google Cloud control including budgets and VPC Service Controls, and does not require anyone to remember that a resource-level policy exists.

Not creating a key at all

The last step of most tutorials is to download a JSON key for the service account. Do not, unless nothing else is possible. A downloaded key is a long-lived credential that does not expire, does not rotate itself, and cannot be revoked without knowing it leaked. Organisation policy can disable service account key creation outright, and on a project that serves models it is one of the more defensible constraints to impose early, while nothing depends on a key yet.

  • On Google Cloud compute — Cloud Run, GKE, Compute Engine, Cloud Functions — attach the service account to the workload and let Application Default Credentials find it. There is no key.
  • Outside Google Cloud — GitHub Actions, AWS, an on-premises runner — use Workload Identity Federation, which exchanges the external identity’s own token for a short-lived Google credential. Still no key.
  • Where a key is genuinely unavoidable, keep it in Secret Manager rather than in an environment variable baked into an image, and rotate it on a schedule somebody owns. A secret baked into an image layer is in that image forever, including in every cached copy of it.