Authentication
Mint a short-lived Google identity token. Never use a key file.
The API authenticates Google service-account identity tokens. Send the token in Authorization only. Do not send X-Serverless-Authorization, do not send both headers, and do not send X-Partner-API-Key.
Authorization: Bearer <identity-token>
Docs password protection (Scalar dashboard) is not API authentication. A valid docs password cannot call the API. A valid API token cannot open the docs.
UAT impersonation
Approved developers impersonate the dedicated non-production tester. No user-managed key is created, downloaded, stored, or logged.
SA=modulate-api-uat-tester@cadence-478808.iam.gserviceaccount.com
AUD=https://uat.modulate-api.es.flute.com
TOKEN=$(curl -sS -X POST \
"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SA}:generateIdToken" \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
-d "{\"audience\":\"${AUD}\",\"includeEmail\":false}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
echo "$TOKEN"
Paste the value into Postman or the built-in API client as Authorization: Bearer …. Authentication uses the verified sub claim, never email.
The tester account is modulate-api-uat-tester@cadence-478808.iam.gserviceaccount.com. Developers receive roles/iam.serviceAccountOpenIdTokenCreator on that tester only. That role can mint ID tokens via generateIdToken. Do not use gcloud auth print-identity-token --impersonate-service-account — that path also calls iam.serviceAccounts.getAccessToken and requires roles/iam.serviceAccountTokenCreator, which we deliberately do not grant. Do not grant TokenCreator unless a separately reviewed workflow needs access-token creation or signing.
Local developer authentication
Local application tests use audience http://localhost:8080:
SA=modulate-api-uat-tester@cadence-478808.iam.gserviceaccount.com
AUD=http://localhost:8080
TOKEN=$(curl -sS -X POST \
"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SA}:generateIdToken" \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
-d "{\"audience\":\"${AUD}\",\"includeEmail\":false}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
The local process must be configured with that same audience. A UAT token is rejected locally, and a localhost token is rejected by UAT.
Production
Production invocation is not granted to interactive users from these docs. Production keeps Cloud Run invoker IAM. There is no production server in the API Reference. Workload Identity on the approved consumer is the production path; do not mint production tokens with a downloaded key.
Token lifetime and renewal
Google identity tokens last about one hour. Renew before expiry. A stale token returns an application 401. The API client and Postman do not refresh for you — mint again with the same generateIdToken command.
Authorization-only rule
| Header | Allowed |
|---|---|
Authorization: Bearer <token> |
Yes — required on every non-OPTIONS request |
X-Serverless-Authorization |
No — rejected |
| Both headers | No — rejected |
X-Partner-API-Key |
No — that scheme is the public partner surface |
Confirm the principal
GET /api/partner/me returns the registered principal behind the token — principal id, google_service_account, explicit organization or system scope, and rate-limit policy. Organization, key, and prefix fields are null for a system-scoped principal.
SA=modulate-api-uat-tester@cadence-478808.iam.gserviceaccount.com
AUD=https://uat.modulate-api.es.flute.com
TOKEN=$(curl -sS -X POST \
"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SA}:generateIdToken" \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
-d "{\"audience\":\"${AUD}\",\"includeEmail\":false}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")
curl https://uat.modulate-api.es.flute.com/api/partner/me \
-H "Authorization: Bearer ${TOKEN}"