Your service deploys fine, but curl gets 403.
Meaning: Cloud Run authenticates callers by default. The calling identity needs the run.invoker role (roles/run.invoker) on the service. No grant, no entry.
Two setups:
- Public service: deploy with --allow-unauthenticated, which grants run.invoker to allUsers. Fine for genuinely public APIs.
- Private service: keep authentication on and grant invoker to the specific caller:
```
gcloud run services add-iam-policy-binding [SERVICE] --member serviceAccount:[CALLER-SA] --role roles/run.invoker --region [REGION]
```
The caller then sends an identity token (your auth header audience = service URL). An access token will NOT work; Cloud Run wants an ID token.
Agent traps:
- Testing with curl and getting 403, then "fixing" it with --allow-unauthenticated on an internal service. You just made it public. Grant the SA instead.
- Service-to-service: the calling Cloud Run service's runtime SA needs invoker on the callee. Fetch an ID token from the metadata server with the callee URL as audience.
- The 403 page says "Forbidden" with no detail by design. Check the service IAM policy; the answer is always there.
Verify: `gcloud run services get-iam-policy` shows the binding, then call with a fresh ID token and get 200.