API Gateway, Apigee and Cloud Endpoints
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
Commands and behaviour below are current for the gcloud CLI 586.x line and the API Gateway, Apigee and Cloud Endpoints
documentation as checked on 2026-09-30; re-verify against gcloud version, gcloud api-gateway --help,
gcloud apigee --help and the linked reference pages before relying on them in a pipeline. Dated facts quoted here
(Cloud Endpoints accepting OpenAPI 3.0 on 2025-11-12, the Endpoints developer portal discontinued on 2023-03-21,
an MCP Preview on API Gateway since 2026-09) come from the release notes of each product.
Once the Bookshelf catalog-api and orders-api run on Cloud Run (see Cloud Run) or GKE
(see Deploying Microservices on GKE), clients should not call them directly. A gateway in front gives
one public entry point, authentication, quotas, a stable contract and a place to evolve versions. Google Cloud offers
three managed products for this job, plus the Kubernetes Gateway API when the backends live on GKE. This page explains
how to choose, builds the Bookshelf gateway with each product, and shows how tokens are validated at the edge. The
generic pattern (backend for frontend, gateway aggregation, when a gateway becomes a bottleneck) is covered in
API Gateway and Backends for Frontends, and a follow-up page on API management
patterns is tracked as issue #184 of this repository; neither is repeated here.
Choosing a product
| Aspect | API Gateway | Apigee | Cloud Endpoints |
|---|---|---|---|
What it is |
A serverless, regional gateway configured from an OpenAPI document. Fully managed; nothing to size or patch. |
A full API management platform: proxies with policies, API products, developer portal, analytics, monetisation, hybrid runtime. |
A distributed gateway: the Extensible Service Proxy v2 (ESPv2), an Envoy-based proxy, that you run yourself (Cloud Run service, sidecar, GKE or VM) and configure from an OpenAPI or gRPC service config. |
Typical backends |
Cloud Run, Cloud Run functions, App Engine |
Anything reachable by HTTP: Cloud Run, GKE through Private Service Connect, on-premises, other clouds |
Anything ESPv2 can reach, including GKE and Compute Engine |
Specification |
OpenAPI 2.0, 3.0 and 3.1 (with Google extensions); gRPC supported |
Proxy bundles of XML policies and flows; OpenAPI used to import or document |
OpenAPI 2.0 and, since 2025-11-12, 3.0; gRPC service config |
Authentication |
API keys, JWT (Google ID tokens, Firebase, any OIDC issuer with a JWKS URI) |
API keys, OAuth 2.0 token endpoint, JWT verification, SAML, mutual TLS, custom code |
API keys, JWT (same model as API Gateway) |
Traffic management |
Per-method quotas |
Spike arrest, quotas, caching, retries, fault rules, per-product limits |
Per-method quotas |
Developer experience |
None built in |
Integrated and drop-in developer portals, API products and apps, key management |
The old Endpoints developer portal was discontinued on 2023-03-21; none built in |
Operational burden |
Lowest |
Medium (environments, environment groups, networking), more with hybrid |
Highest of the three: you own the proxy runtime and its upgrades |
Cost shape |
Per million calls with a monthly free allotment |
Pay-as-you-go environments and API calls, or a committed subscription |
Per million calls with a free allotment, plus the compute that runs ESPv2 |
Rules of thumb for Bookshelf:
-
One team, a handful of Cloud Run services, JWT or API keys, simple quotas: API Gateway.
-
Many consumer teams or external partners, products and plans, per-consumer analytics, request transformation, or monetisation: Apigee.
-
Need gRPC transcoding, or must run the gateway as a sidecar next to the backend, or already use ESPv2: Cloud Endpoints.
-
GKE-only traffic with path routing and no API management needs: just the Kubernetes Gateway API (below).
-
A global external Application Load Balancer can sit in front of any of them for a custom domain, Cloud Armor, Cloud CDN and multi-region failover; see Load Balancing and Cloud Armor.
Cloud Armor, custom domain] LB --> AG[API Gateway
bookshelf-gw] LB --> AP[Apigee
environment group] AG -->|x-google-backend
ID token| CR1[Cloud Run catalog-api] AG --> CR2[Cloud Run orders-api] AP -->|target server
via PSC| GK[GKE services] AP --> CR2
Reference: API Gateway — About and Apigee — What is Apigee.
API Gateway
API Gateway has three resources:
-
An API is a named container (
bookshelf-api). Creating it also creates a Service Management managed service, which is what API keys and quotas attach to. -
An API config is an immutable snapshot of one OpenAPI document plus the service account the gateway uses to call backends. A change means a new config; you never edit one.
-
A gateway is the regional deployment that serves one API config on a
*.gateway.devhostname. Updating a gateway to a new API config is a rolling, zero-downtime operation.
Operational facts: gateways are regional and only available in a subset of regions, a project may have up to 50 gateways
per region, the data plane accepts TLS 1.2 or later only, and the control plane uses the services
apigateway.googleapis.com, servicemanagement.googleapis.com and servicecontrol.googleapis.com.
Service accounts and roles
Two identities are involved, and mixing them up is the commonest first-day failure.
| Identity | Purpose | Roles |
|---|---|---|
Gateway service account (you create it, for example |
Passed as |
|
The person or pipeline creating APIs |
Runs the |
|
In the Google Cloud console
-
Open the project picker and select the Bookshelf project (or use Cloud Shell, which is already authenticated).
-
Open API Gateway from the navigation menu (search "API Gateway"). On first visit, click Enable API to enable API Gateway and its dependent services.
-
Click Create gateway.
-
Under API, choose Create new API and name it
bookshelf-api(display name "Bookshelf API"). -
Under API config, upload the OpenAPI file (next section), name the config
bookshelf-config-v1and select the Service account the gateway will use to call backends (bookshelf-gw-sa). -
Under Gateway details, name the gateway
bookshelf-gwand pick a region such asus-central1. -
Click Create gateway. After a few minutes the Default hostname ending in
gateway.devappears; open it with a path from the specification to test. -
To add a new version later, open the API, choose Configs then Create config, and finally open the gateway and use Edit to switch it to the new config.
The OpenAPI document with x-google-backend
The gateway reads standard OpenAPI plus a few x-google-* extensions. The most important is x-google-backend, which
says where requests are forwarded:
-
address— the backend URL (a Cloud Run service URL or Cloud Run functions URL). -
path_translation—APPEND_PATH_TO_ADDRESS(default for backend addresses without a path: the request path is appended) orCONSTANT_ADDRESS(the path inaddressis used and request path parameters become query parameters). -
jwt_audience— the audience of the ID token the gateway mints for the backend; set it to the backend service URL. -
deadline,protocol(http/1.1orh2),disable_authandmin_deadlinefor timeouts and protocol choice.
swagger: "2.0"
info:
title: Bookshelf API
description: Public API of the Bookshelf application
version: "1.0.0"
host: bookshelf-gw-REPLACE_ME.uc.gateway.dev # informational; the gateway hostname is assigned on creation
schemes:
- https
produces:
- application/json
# Default backend for every operation (can be overridden per operation)
x-google-backend:
address: https://catalog-api-REPLACE_ME-uc.a.run.app
jwt_audience: https://catalog-api-REPLACE_ME-uc.a.run.app
deadline: 15.0
securityDefinitions:
api_key:
type: apiKey
name: x-api-key # a header; use "key" with "in: query" for the legacy query form
in: header
bookshelf_idp:
authorizationUrl: ""
flow: implicit
type: oauth2
x-google-issuer: https://idp.example.com/
x-google-jwks_uri: https://idp.example.com/.well-known/jwks.json
x-google-audiences: bookshelf-api
paths:
/v1/books:
get:
operationId: listBooks
summary: List books (public, needs an API key)
security:
- api_key: []
responses:
"200":
description: A page of books
/v1/books/{id}:
get:
operationId: getBook
parameters:
- name: id
in: path
required: true
type: string
security:
- api_key: []
responses:
"200":
description: One book
/v1/orders:
post:
operationId: createOrder
summary: Create an order (needs a user JWT)
x-google-backend:
address: https://orders-api-REPLACE_ME-uc.a.run.app
jwt_audience: https://orders-api-REPLACE_ME-uc.a.run.app
path_translation: APPEND_PATH_TO_ADDRESS
security:
- bookshelf_idp: []
responses:
"201":
description: Order created
Derived from the API Gateway — OpenAPI overview and
Authenticating with a service account
pages. OpenAPI 3.x documents use the same extension names and the components.securitySchemes section instead of
securityDefinitions; check the OpenAPI 3 page of the API Gateway documentation for the exact support level.
With the gcloud CLI
gcloud services enable apigateway.googleapis.com servicemanagement.googleapis.com \
servicecontrol.googleapis.com run.googleapis.com
PROJECT_ID=$(gcloud config get-value project)
REGION=us-central1
API=bookshelf-api
GATEWAY_SA=bookshelf-gw-sa
# 1. The service account the gateway uses to call Cloud Run backends
gcloud iam service-accounts create "$GATEWAY_SA" --display-name="Bookshelf API Gateway"
GATEWAY_SA_EMAIL="$GATEWAY_SA@$PROJECT_ID.iam.gserviceaccount.com"
for SVC in catalog-api orders-api; do
gcloud run services add-iam-policy-binding "$SVC" --region="$REGION" \
--member="serviceAccount:$GATEWAY_SA_EMAIL" --role="roles/run.invoker"
done
# 2. The API, an immutable API config, and the gateway
gcloud api-gateway apis create "$API" --project="$PROJECT_ID"
gcloud api-gateway api-configs create bookshelf-config-v1 \
--api="$API" --openapi-spec=openapi.yaml \
--backend-auth-service-account="$GATEWAY_SA_EMAIL" --project="$PROJECT_ID"
gcloud api-gateway gateways create bookshelf-gw \
--api="$API" --api-config=bookshelf-config-v1 \
--location="$REGION" --project="$PROJECT_ID"
# 3. Find the hostname and test
HOST=$(gcloud api-gateway gateways describe bookshelf-gw --location="$REGION" \
--format='value(defaultHostname)')
curl -s "https://$HOST/v1/books" -H "x-api-key: $API_KEY"
Derived from the API Gateway — Getting started with Cloud Run quickstart and the gcloud reference — api-gateway.
Shipping a new version:
gcloud api-gateway api-configs create bookshelf-config-v2 \
--api="$API" --openapi-spec=openapi.yaml \
--backend-auth-service-account="$GATEWAY_SA_EMAIL"
gcloud api-gateway gateways update bookshelf-gw \
--api="$API" --api-config=bookshelf-config-v2 --location="$REGION"
Derived from API Gateway — Creating an API config and Deploying an API to a gateway. To roll back, point the gateway at the previous config; keep at least the last two configs until the new one has proven itself.
API keys
An API key identifies the calling project or application, not a user. It is suitable for quota attribution and low-risk public data, never for authorising access to private data.
MANAGED_SERVICE=$(gcloud api-gateway apis describe "$API" --format='value(managedService)')
# Allow the API to be called with keys (the managed service must be enabled in the consumer project)
gcloud services enable "$MANAGED_SERVICE"
# Create a restricted key; the key string is printed once and goes to the consumer, not to source control
gcloud services api-keys create --display-name="bookshelf-web-key" \
--api-target=service="$MANAGED_SERVICE"
Derived from API Gateway — Authentication methods and gcloud services api-keys create. Keep keys out of repositories and mobile bundles you cannot rotate; store server-side keys in Secret Manager (see Secret Manager and Cloud KMS).
JWT validation at the gateway
When a path requires an OAuth 2.0 / OIDC access token or ID token, the gateway validates the token before forwarding:
signature (from the JWKS URI), iss, aud and expiry. The mapping is the securityDefinitions block above:
| Extension | Meaning |
|---|---|
|
Exact value of the token’s |
|
Where the public keys are published (for Google service accounts, the service account’s own x509 JWKS URL). |
|
Comma-separated accepted |
After validation, the gateway forwards the original token in Authorization unchanged only if the backend needs it;
otherwise it calls the backend with its own ID token (the jwt_audience above), and passes the verified end-user claims
base64url-encoded in the X-Apigateway-Api-Userinfo header. The Cloud Run service itself is private
(--no-allow-unauthenticated), so nobody can bypass the gateway by calling the run.app URL.
On the Spring Boot side nothing changes if the service should also validate the end-user JWT (defence in depth): add
spring-boot-starter-oauth2-resource-server and set the issuer. The token concepts are covered in
JWT and the JOSE Family, OpenID Connect and
ID Tokens vs. Access Tokens; resource-server wiring in
Authorization Server & Social Login and, for Quarkus,
JWT, OIDC and Keycloak.
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com/
audiences: bookshelf-api
Derived from API Gateway — Authenticating users with JWT and the Spring Security resource server reference.
Quotas and rate limiting
Per-method quotas are declared in the OpenAPI document with the x-google-management extension, then attributed to
API keys (quota counts per consuming project).
x-google-management:
metrics:
- name: read-requests
displayName: Read requests
valueType: INT64
metricKind: DELTA
quota:
limits:
- name: read-limit
metric: read-requests
unit: 1/min/{project}
values:
STANDARD: 600
paths:
/v1/books:
get:
operationId: listBooks
x-google-quota:
metricCosts:
read-requests: 1
security:
- api_key: []
responses:
"200":
description: A page of books
Derived from API Gateway — Configuring quotas. Exceeding a quota returns HTTP 429. API Gateway has no spike-arrest or per-IP limiter; for burst protection or IP-based limits put Cloud Armor rate limiting in front (see Load Balancing and Cloud Armor) or use Apigee.
Custom domains
A gateway.dev hostname is fine for tests. For api.bookshelf.example.com put a global external Application Load
Balancer in front: a serverless network endpoint group of type API Gateway, as the backend of a backend service,
plus a Google-managed certificate from Certificate Manager. The load balancer also brings Cloud Armor and Cloud CDN.
gcloud compute network-endpoint-groups create bookshelf-gw-neg \
--region="$REGION" --network-endpoint-type=serverless \
--serverless-deployment-platform=apigateway.googleapis.com \
--serverless-deployment-resource=bookshelf-gw
gcloud compute backend-services create bookshelf-gw-backend \
--global --load-balancing-scheme=EXTERNAL_MANAGED
gcloud compute backend-services add-backend bookshelf-gw-backend --global \
--network-endpoint-group=bookshelf-gw-neg \
--network-endpoint-group-region="$REGION"
Derived from API Gateway — Getting started with serverless NEGs. The remaining pieces (URL map, target proxy, forwarding rule, certificate, DNS) are the same as for any global external ALB and are built in Load Balancing and Cloud Armor and Cloud DNS and Certificate Manager.
Monitoring, limits and the MCP Preview
-
Logs and metrics land in Cloud Logging and Cloud Monitoring (request count, latency, response codes per gateway and per API); see Monitoring and Observability.
-
The request size, header size and timeout limits are documented on the quotas page; a
deadlinelarger than the gateway maximum is capped. -
API Gateway has had a Preview for exposing APIs as Model Context Protocol (MCP) servers since 2026-09. Preview features carry no SLA and may change; confirm the current stage on the release notes before relying on it.
-
There is no built-in developer portal, request transformation or response cache. If you need those, step up to Apigee.
Cloud Endpoints and ESPv2
Cloud Endpoints predates API Gateway and uses the same service-management back end (API keys, JWT definitions and quotas use the same extensions). The difference is where the proxy runs: you deploy the ESPv2 container yourself. That buys gRPC transcoding, a sidecar placement (lowest latency) and control over networking, at the cost of operating it. Two points often wrong in older material:
-
ESPv2 on Cloud Run is generally available (it was Beta for years), and ESP version 1 (the NGINX-based proxy) is superseded by ESPv2; new designs should not start on v1.
-
Cloud Endpoints accepts OpenAPI 3.0 documents since 2025-11-12; older guides say OpenAPI 2.0 only.
The flow on Cloud Run has three moves: deploy a placeholder ESPv2 service to learn its hostname, publish the API config to Service Management, then build an ESPv2 image bound to that config and redeploy.
gcloud services enable servicemanagement.googleapis.com servicecontrol.googleapis.com \
endpoints.googleapis.com run.googleapis.com cloudbuild.googleapis.com
ESP_SERVICE=bookshelf-esp
# 1. Deploy a placeholder ESPv2 (the sample image) to obtain the service hostname
gcloud run deploy "$ESP_SERVICE" --region="$REGION" \
--image="gcr.io/endpoints-release/endpoints-runtime-serverless:2" \
--allow-unauthenticated --platform=managed
ESP_HOST=$(gcloud run services describe "$ESP_SERVICE" --region="$REGION" \
--format='value(status.url)' | sed 's#https://##')
# 2. Put the hostname in the OpenAPI document ("host: $ESP_HOST", plus x-google-backend for the real service),
# then publish the config to Service Management and note the printed config ID
gcloud endpoints services deploy openapi-run.yaml --project="$PROJECT_ID"
CONFIG_ID=$(gcloud endpoints configs list --service="$ESP_HOST" --limit=1 --format='value(id)')
# 3. Build an ESPv2 image bound to that config (script from the esp-v2 repository), then redeploy
./gcloud_build_image -s "$ESP_HOST" -c "$CONFIG_ID" -p "$PROJECT_ID"
gcloud run deploy "$ESP_SERVICE" --region="$REGION" \
--image="gcr.io/$PROJECT_ID/endpoints-runtime-serverless:$ESP_HOST-$CONFIG_ID" \
--allow-unauthenticated --platform=managed \
--service-account="esp-sa@$PROJECT_ID.iam.gserviceaccount.com"
Derived from Cloud Endpoints — Getting
started with Cloud Endpoints for Cloud Run and gcloud
reference — endpoints. The gcloud_build_image script lives in the esp-v2 repository linked from that page; it
builds the image in Cloud Build, so the image repository is Artifact Registry or Container Registry’s replacement
(see Container Registry and Builds) and the exact image path in your project may differ;
use the path the script prints.
Security notes specific to ESPv2:
-
The ESPv2 service account needs
roles/servicemanagement.serviceControllerso it can report usage and fetch the config, androles/run.invokeron the backend if the backend is private. -
ESPv2 itself is public (it is the front door); the backend Cloud Run service should be authenticated and invoked with an ID token (
x-google-backendwithjwt_audience), exactly as with API Gateway. -
Pin the config ID:
--rolloutstyle deployments that follow "latest" make rollbacks harder.
When to still choose Cloud Endpoints: gRPC services needing HTTP/JSON transcoding, a sidecar on GKE, or an existing estate that already runs ESPv2. Otherwise prefer API Gateway (less to operate) or Apigee (more features).
Apigee
Apigee is Google Cloud’s API management platform. The Google Cloud variant (Apigee, formerly "Apigee X") runs the runtime in a Google-managed tenant project peered with your VPC; Apigee hybrid puts the runtime on your own Kubernetes cluster while the management plane stays in Google Cloud.
The resource model
| Concept | Meaning |
|---|---|
Organization |
The top-level Apigee resource; one per Google Cloud project. Holds environments, proxies, products, developers and apps. |
Instance |
A runtime deployment in a region, attached to an environment (the managed tenant network is reached over VPC peering or Private Service Connect). |
Environment |
A deployment target for proxies ( |
Environment group |
A set of hostnames ( |
API proxy |
A bundle ( |
API product |
A bundle of proxies/paths plus quotas and scopes that consumers can subscribe to. |
Developer and app |
The consumer and the credentials (key and secret) they get for one or more API products. |
Provisioning options:
-
Evaluation: free for 60 days, for learning; created from the console wizard or with a single provision command.
-
Pay-as-you-go: billed by environment type (Base, Intermediate, Comprehensive) per hour plus per API call volume, with no commitment; the sensible default for a first production use.
-
Subscription: a committed annual entitlement (Standard, Enterprise, Enterprise Plus) with included API call volume.
Pricing figures change; read the pay-as-you-go and subscription pages in References rather than quoting numbers.
In the Google Cloud console
-
Select the project, then open Apigee from the navigation menu; click Enable APIs if prompted.
-
Choose the Evaluation or Pay-as-you-go set-up wizard. Select the network (VPC) to peer, the runtime location (such as
us-central1) and the analytics region, then confirm. Provisioning takes up to an hour. -
Open Proxy development > API proxies > Create. Choose Reverse proxy, set the name
catalog-api, base path/catalogand the target URL of the Cloud Run service. -
Accept the default policies, create the proxy and use Develop to edit XML or the visual flow editor.
-
Click Deploy and select the environment (
evalin the evaluation organisation). -
Open Publish > API products > Create and add the
catalog-apiproxy with a quota and an OAuth scope; then Publish > Apps > Create to create a developer and an app that receives a key. -
Use Analyze > API metrics to see traffic, latency and errors per product, app, and developer.
With gcloud, apigeecli and Maven
gcloud services enable apigee.googleapis.com compute.googleapis.com servicenetworking.googleapis.com \
cloudkms.googleapis.com
# Evaluation / pay-as-you-go organisation, runtime in us-central1 on the default VPC (create the peering first)
gcloud apigee organizations provision \
--runtime-location="$REGION" --analytics-region="$REGION" \
--authorized-network=default
# Verify the organisation, environments and environment groups
ORG=$PROJECT_ID
gcloud apigee organizations describe "$ORG"
gcloud apigee environments list --organization="$ORG"
Derived from Apigee — Provisioning
overview and gcloud reference — apigee. The gcloud apigee
group mostly covers organisations, environments, instances and deployments; proxy authoring and API products are
usually scripted with the open-source apigeecli or the Maven plugins, which fit a CI pipeline better.
TOKEN=$(gcloud auth print-access-token)
# Import a proxy bundle directory and deploy revision 1 to the eval environment
apigeecli apis create bundle -f apiproxy --name catalog-api --org "$ORG" --token "$TOKEN"
apigeecli apis deploy --name catalog-api --env eval --ovr --wait --org "$ORG" --token "$TOKEN"
# API product, developer and app (key is returned once)
apigeecli products create --name bookshelf-public --displayname "Bookshelf public" \
--proxies catalog-api --envs eval --approval auto \
--quota 1000 --interval 1 --unit minute --org "$ORG" --token "$TOKEN"
apigeecli developers create --email dev@example.com --first Dev --last User \
--user dev@example.com --org "$ORG" --token "$TOKEN"
apigeecli apps create --name bookshelf-web --email dev@example.com \
--prods bookshelf-public --org "$ORG" --token "$TOKEN"
Derived from the apigeecli documentation; flag names change between releases,
so check apigeecli --help. The Maven route uses apigee-deploy-maven-plugin (bundle build and deploy) and
apigee-config-maven-plugin (products, developers, apps, key-value maps) driven from the Bookshelf build; see
Apigee — Deploying
proxies with Maven.
A proxy for catalog-api
A proxy bundle has this shape:
apiproxy/
catalog-api.xml # proxy descriptor
proxies/default.xml # ProxyEndpoint (what clients see)
targets/default.xml # TargetEndpoint (the Cloud Run service)
policies/
Verify-API-Key.xml
Spike-Arrest.xml
Quota.xml
Verify-JWT.xml
Add-Headers.xml
Cache-Books.xml
CORS.xml
The ProxyEndpoint wires the policies into the request flow:
<ProxyEndpoint name="default">
<PreFlow name="PreFlow">
<Request>
<Step><Name>CORS</Name></Step>
<Step><Name>Spike-Arrest</Name></Step>
<Step><Name>Verify-API-Key</Name></Step>
<Step><Name>Quota</Name></Step>
</Request>
<Response/>
</PreFlow>
<Flows>
<Flow name="orders">
<Condition>(proxy.pathsuffix MatchesPath "/orders/**") and (request.verb = "POST")</Condition>
<Request>
<Step><Name>Verify-JWT</Name></Step>
</Request>
</Flow>
<Flow name="books-cache">
<Condition>(proxy.pathsuffix MatchesPath "/books") and (request.verb = "GET")</Condition>
<Request><Step><Name>Cache-Books</Name></Step></Request>
<Response><Step><Name>Cache-Books</Name></Step></Response>
</Flow>
</Flows>
<PostFlow name="PostFlow">
<Request/>
<Response/>
</PostFlow>
<HTTPProxyConnection>
<BasePath>/catalog</BasePath>
</HTTPProxyConnection>
<RouteRule name="default">
<TargetEndpoint>default</TargetEndpoint>
</RouteRule>
</ProxyEndpoint>
The TargetEndpoint calls the Cloud Run service with a Google ID token, so the backend stays private:
<TargetEndpoint name="default">
<PreFlow name="PreFlow">
<Request><Step><Name>Add-Headers</Name></Step></Request>
<Response/>
</PreFlow>
<HTTPTargetConnection>
<URL>https://catalog-api-REPLACE_ME-uc.a.run.app</URL>
<Authentication>
<GoogleIDToken>
<Audience>https://catalog-api-REPLACE_ME-uc.a.run.app</Audience>
</GoogleIDToken>
</Authentication>
</HTTPTargetConnection>
</TargetEndpoint>
Derived from Apigee — Understanding APIs and API proxies and
Authentication with Google
tokens. The Apigee service account (shown on the organisation page) needs roles/run.invoker on catalog-api.
For a GKE backend, publish the Service through Private Service Connect (a service attachment) and define an Apigee
target server or an Apigee endpoint attachment pointing at it; the VPC-side details are in
VPC Networking on Google Cloud.
CORS, SpikeArrest, VerifyAPIKey, Quota] PRE --> COND{Conditional flows
path and verb} COND -->|orders POST| JWT[VerifyJWT] COND -->|books GET| CACHE[ResponseCache lookup] COND -->|other| ROUTE[RouteRule] JWT --> ROUTE CACHE --> ROUTE ROUTE --> TPRE[TargetEndpoint PreFlow
AssignMessage headers] TPRE --> TGT[Backend Cloud Run
GoogleIDToken auth] TGT --> TPOST[Target response flow] TPOST --> POST[ProxyEndpoint PostFlow
ResponseCache populate] POST --> RESP[Response to client] PRE -.->|error| FAULT[FaultRules and DefaultFaultRule] JWT -.->|error| FAULT TGT -.->|error| FAULT FAULT --> RESP
Policy examples
Rate protection in the first step of every request, then consumer identity and quota:
<!-- Spike-Arrest.xml: smooths bursts; 30pm means one request every 2 seconds, not 30 at once -->
<SpikeArrest name="Spike-Arrest">
<Rate>30pm</Rate>
<UseEffectiveCount>true</UseEffectiveCount>
</SpikeArrest>
<!-- Verify-API-Key.xml: reads the key from a header, resolves the app and its API product -->
<VerifyAPIKey name="Verify-API-Key">
<APIKey ref="request.header.x-api-key"/>
</VerifyAPIKey>
<!-- Quota.xml: limits come from the API product's quota settings -->
<Quota name="Quota">
<Identifier ref="verifyapikey.Verify-API-Key.client_id"/>
<Allow count="1000" countRef="verifyapikey.Verify-API-Key.apiproduct.developer.quota.limit"/>
<Interval ref="verifyapikey.Verify-API-Key.apiproduct.developer.quota.interval">1</Interval>
<TimeUnit ref="verifyapikey.Verify-API-Key.apiproduct.developer.quota.timeunit">minute</TimeUnit>
<Distributed>true</Distributed>
<Synchronous>false</Synchronous>
</Quota>
Validating a user token issued by the identity provider (OIDC discovery keys, not a shared secret):
<!-- Verify-JWT.xml -->
<VerifyJWT name="Verify-JWT">
<Algorithm>RS256</Algorithm>
<Source>request.header.authorization</Source>
<PublicKey>
<JWKS uri="https://idp.example.com/.well-known/jwks.json"/>
</PublicKey>
<Issuer>https://idp.example.com/</Issuer>
<Audience>bookshelf-api</Audience>
<IgnoreIssuedAt>false</IgnoreIssuedAt>
</VerifyJWT>
When the IdP issues opaque tokens instead, use the OAuthV2 policy against Apigee’s own token endpoint
(operation GenerateAccessToken / VerifyAccessToken) or an introspection callout; see
Opaque Tokens, Introspection and Revocation. Shaping the request, caching and CORS:
<!-- Add-Headers.xml: pass verified identity to the backend, remove the client-supplied header first -->
<AssignMessage name="Add-Headers">
<Remove><Headers><Header name="X-Bookshelf-Client"/></Headers></Remove>
<Set>
<Headers>
<Header name="X-Bookshelf-Client">{verifyapikey.Verify-API-Key.client_id}</Header>
</Headers>
</Set>
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</AssignMessage>
<!-- Cache-Books.xml: used in both request and response flows -->
<ResponseCache name="Cache-Books">
<CacheKey>
<KeyFragment ref="request.uri" type="string"/>
</CacheKey>
<ExpirySettings>
<TimeoutInSec>60</TimeoutInSec>
</ExpirySettings>
<SkipCacheLookup>request.header.cache-control = "no-cache"</SkipCacheLookup>
</ResponseCache>
<!-- CORS.xml -->
<CORS name="CORS">
<AllowOrigins>https://bookshelf.example.com</AllowOrigins>
<AllowMethods>GET, POST, OPTIONS</AllowMethods>
<AllowHeaders>authorization, content-type, x-api-key</AllowHeaders>
<MaxAge>3628800</MaxAge>
</CORS>
All derived from the Apigee — Policy reference overview and the individual policy pages of the same section (SpikeArrest, Quota, VerifyAPIKey, VerifyJWT, AssignMessage, ResponseCache, CORS). Note that SpikeArrest and Quota solve different problems: spike arrest protects the backend from bursts; quota enforces per-consumer entitlements.
Other Apigee features
-
Analytics record each call by proxy, product, app and developer, and can be exported to BigQuery for custom reports.
-
Developer portals (integrated, or Drupal-based) publish documentation and let developers self-register and obtain keys against your API products.
-
Monetisation turns API products into priced rate plans, for partner APIs.
-
API hub is a catalogue for APIs of any gateway (including API Gateway and Cloud Endpoints) with lint and versioning metadata; useful once teams multiply, and out of scope for a first build.
-
Security: Advanced API Security and Cloud Armor integration add abuse detection and WAF in front of the environment group; mTLS to targets is configured on the target server.
The Gateway API on GKE
For Bookshelf services that run on GKE, the Kubernetes Gateway API is the native way to expose routes: a Gateway
resource creates a Google Cloud load balancer, and HTTPRoute objects attach hosts and paths to Services. It is the
successor to Ingress on GKE, and the full walkthrough, with GatewayClasses such as gke-l7-global-external-managed,
policies and Certificate Manager integration, lives in Deploying Microservices on GKE; the
vendor-neutral model is in Kubernetes. The part that matters for API management:
-
The Gateway API provides routing, TLS termination, header matching and traffic splitting, plus Cloud Armor via a
GCPBackendPolicy. It does not issue API keys, apply per-consumer quotas or publish a developer portal. -
JWT validation is not a built-in feature of the standard
HTTPRoute. Options: Identity-Aware Proxy (for Google identities and workforce access), Cloud Service MeshRequestAuthenticationandAuthorizationPolicywhen the mesh is installed, validation in the Spring Boot resource server, or place Apigee or API Gateway in front. -
A common layout: external Application Load Balancer (from the Gateway) for the public hostname, with the Gateway routing
/catalogto thecatalog-apiService and/apito Apigee through Private Service Connect.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: catalog-api
spec:
parentRefs:
- name: bookshelf-gateway
hostnames:
- api.bookshelf.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /catalog
backendRefs:
- name: catalog-api
port: 8080
Derived from GKE — Deploying Gateways and the Kubernetes Gateway API specification.
Security checklist
-
Make every backend private (
--no-allow-unauthenticatedon Cloud Run, internal load balancing or a firewall on GKE) so the only way in is through the gateway. For Cloud Run, also set ingress tointernal-and-cloud-load-balancingwhen the gateway sits behind a load balancer; API Gateway and Apigee call from Google-managed networks, so rely on IAM (roles/run.invokerfor the gateway identity only). -
Give the gateway identity the narrowest role:
roles/run.invokeron specific services, not at project level. -
Do not use API keys to authorise users. Use JWT (OIDC) for users; API keys only to identify an application and apply a quota. Restrict each key to the API’s managed service and, for browser keys, to referrers.
-
Validate
iss,audand expiry; reject tokens with the wrong audience even if the signature is valid. See Security Best Practices. -
Put Cloud Armor in front for IP controls and WAF (see Load Balancing and Cloud Armor).
-
Keep secrets (backend credentials, signing keys, API key strings for server use) in Secret Manager; see Secret Manager and Cloud KMS and, for service accounts, Identity and Access.
-
Never download service-account keys for the gateway or ESPv2; they use attached identities.
-
Enable audit logs for API Gateway, Service Management and Apigee admin activity.
Troubleshooting
| Symptom | Likely cause and fix |
|---|---|
|
The gateway service account lacks |
|
The path requires a JWT but the client sent none, or the |
|
The API’s managed service is not enabled in the consumer project, or the key is restricted to a different service. |
|
The path is not declared in the OpenAPI document, or |
Gateway stays in |
The OpenAPI document is invalid; read |
|
A quota or Apigee SpikeArrest/Quota policy triggered; check the metric and the per-consumer counts. |
Apigee deployment |
The environment is not attached to an environment group, or the hostname does not resolve to the load balancer. |
ESPv2 returns |
The ESPv2 service account lacks |
Pricing model and cost drivers
Prices are deliberately not quoted; they change and differ by region. Read the dated pages in References.
-
API Gateway: billed per million API calls after a monthly free allotment, plus data transferred out. The gateway itself has no hourly charge.
-
Cloud Endpoints: billed per million API calls after a free allotment, plus the Cloud Run, GKE or VM capacity that runs ESPv2.
-
Apigee: pay-as-you-go charges per environment-hour by type (Base, Intermediate, Comprehensive), per API call volume and for add-ons; the subscription model bills a committed annual tier. An idle evaluation or pay-as-you-go environment still costs money, which makes Apigee the product most worth deleting after a lab.
-
All three: egress, Cloud Logging volume, the optional load balancer (forwarding rules and data processed) and Cloud Armor if used. See Cost Management and Billing for budgets and alerts.
Clean up
Delete in dependency order so nothing is left billing:
# Load balancer pieces first, if you created them (see the load balancing page)
gcloud compute backend-services delete bookshelf-gw-backend --global --quiet
gcloud compute network-endpoint-groups delete bookshelf-gw-neg --region="$REGION" --quiet
# API Gateway: gateways, then configs, then the API
gcloud api-gateway gateways delete bookshelf-gw --location="$REGION" --quiet
gcloud api-gateway api-configs delete bookshelf-config-v1 --api="$API" --quiet
gcloud api-gateway api-configs delete bookshelf-config-v2 --api="$API" --quiet
gcloud api-gateway apis delete "$API" --quiet
# API keys and the gateway service account
KEY_NAME=$(gcloud services api-keys list --filter='displayName=bookshelf-web-key' --format='value(name)')
gcloud services api-keys delete "$KEY_NAME"
gcloud iam service-accounts delete "$GATEWAY_SA_EMAIL" --quiet
# Cloud Endpoints / ESPv2
gcloud run services delete "$ESP_SERVICE" --region="$REGION" --quiet
gcloud endpoints services delete "$ESP_HOST" --quiet
# Apigee: undeploy and delete proxies, then the evaluation organisation (stops the billing for the runtime)
apigeecli apis undeploy --name catalog-api --env eval --rev 1 --org "$ORG" --token "$TOKEN"
apigeecli apis delete --name catalog-api --org "$ORG" --token "$TOKEN"
gcloud apigee organizations delete "$ORG"
Derived from API Gateway — Deleting an API,
gcloud endpoints services delete and
Apigee — Deleting an organization.
Service Management keeps a deleted service for 30 days (you can gcloud endpoints services undelete), so the name
cannot be reused immediately. Deleting an Apigee organization is permanent and removes every proxy, product and app
in it. For a throwaway lab project, deleting the whole project (see
Resource Hierarchy and Projects) is the surest way to stop all charges.
Related pages
-
API Gateway and Backends for Frontends covers gateway and BFF patterns independent of cloud.
-
Load Balancing and Cloud Armor covers the load balancer, Cloud Armor and custom domains in front of a gateway.
-
Cloud Run and Deploying Microservices on GKE cover the backends.
-
Identity and Access covers service accounts, impersonation and the roles used here.
-
OAuth Reference covers OAuth 2.0 and OpenID Connect in depth.
-
API Gateway and Azure are the AWS and Azure counterparts.