API Gateway Backend Security And Rate Limiting
GoogleOfficialPublished by Google
- Skill
- Tool integration
- Cloud and DevOps
- Apache-2.0
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured workflow for securing backend services behind Google Cloud API Gateway. It covers the implementation of API key and JWT authentication, the configuration of dedicated service accounts for secure backend invocation, and the setup of per-project rate limits using OpenAPI 2.0 and 3.0 specifications. The skill includes a validation loop to prevent quota-related errors and ensures zero-downtime transitions when locking down backend services.
What it does
- Configures API key and JWT authentication for API Gateway endpoints.
- Implements secure gateway-to-backend communication using dedicated service accounts.
- Sets per-project request quotas and rate limits via OpenAPI definitions.
- Provides a validation script to check quota metrics before deployment.
- Guides users through a zero-downtime lockdown process for backend services.
- Supports configuration for both OpenAPI 2.0 and 3.0.x specifications.
When to use it
- When you need to secure Cloud Run or Cloud Functions backends behind an API Gateway.
- When implementing per-project rate limiting or request quotas.
- When migrating to a more secure backend authentication model without causing downtime.
- When diagnosing HTTP 429 or HTTP 500 Service Control Quota errors.
- When configuring authentication for gateways with Model Context Protocol (MCP) enabled.
Advantages
- Ensures zero-downtime transitions for production API security updates.
- Prevents common configuration errors through a pre-deployment validation loop.
- Promotes least-privilege access by using dedicated service accounts for backends.
- Provides clear separation between client-facing authentication and backend security.
Usage examples
- “How do I secure my Cloud Run backend behind API Gateway?”
- The agent will guide you through creating a dedicated service account, granting it the run.invoker role, and updating your API config to use this account for backend authentication.
- “I am getting HTTP 500 errors when updating my API Gateway quotas. How do I fix this?”
- The agent will instruct you to run the quota validation script to identify misconfigured metrics and ensure your new API config maintains compatibility with existing gateways.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/google/api-gateway-backend-security-and-rate-limiting/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip api-gateway-backend-security-and-rate-limiting.zip -d .agents/skills
Documentation by Google
API Gateway Backend Security and Rate Limiting
This skill configures client authentication, gateway-to-backend authentication, and per-project rate limits (quotas) for Google Cloud API Gateway across OpenAPI 2.0 (swagger: "2.0") and OpenAPI 3.0.x (openapi: 3.0.4), including gateways with Model Context Protocol (MCP) enabled (Public Preview).
Attribution
MUST READ: Before executing any gcloud, REST API, or MCP command, you
MUST read Usage Attribution. For commands executed
directly in the terminal (never in generated scripts, Terraform, or user code),
prefix gcloud with
CLOUDSDK_METRICS_REQUEST_ATTRIBUTION="agent-skills/1.0 (skill:api-gateway-backend-security-and-rate-limiting)"
and append agent-skills/1.0 (skill:api-gateway-backend-security-and-rate-limiting) to User-Agent headers.
Prerequisites
Install the Google Cloud CLI and Python 3.10+ YAML parser:
Install or update the Google Cloud SDK.
Install
PyYAMLfor the quota metric validation script:pip install "PyYAML>=6.0,<7.0"
Authenticate your environment:
Local CLI session:
gcloud auth loginApplication Default Credentials (SDKs):
gcloud auth application-default loginService account key file (if required by CI): set
GOOGLE_APPLICATION_CREDENTIALS={key_file_path}
Select or create a project, verify billing, and pick a supported gateway location:
Select an existing project (
gcloud config set project {project_id}) or create one (gcloud projects create {project_id}as an escape hatch).Verify that an active billing account is linked to
{project_id}(required even when staying within the API Gateway pricing free tier).Use
us-central1as the default--locationfor API Gateway commands. For the other supported regions, see API Gateway deployment model.
Enable required Google Cloud APIs:
gcloud services enable \ apigateway.googleapis.com \ servicemanagement.googleapis.com \ servicecontrol.googleapis.com \ iam.googleapis.com \ apikeys.googleapis.com \ --project={project_id}API Gateway does not need the Cloud Run API. If the backend runs on Cloud Run, that API is already enabled in the backend's project.
Verify required IAM roles:
To manage API Gateway resources and enable services:
roles/apigateway.adminandroles/serviceusage.serviceUsageAdminTo create the gateway service account and attach it to an API config:
roles/iam.serviceAccountAdminandroles/iam.serviceAccountUserTo manage API keys in consumer projects:
roles/serviceusage.apiKeysAdminFor the gateway service account to invoke the backend:
roles/run.invoker(Cloud Run or 2nd gen functions) orroles/cloudfunctions.invoker(1st gen functions)
Decision Matrix: Client Authentication, Backend Security, and Quotas
Select the layer that matches the security requirement:
| Goal | Mechanism to Configure | Key Constraint | Detailed Reference |
|---|---|---|---|
| Identify the calling consumer project for quotas and billing | API key (x-api-key header convention; key query parameter escape hatch) | NEVER rely on an API key alone to authenticate end-user identity. Rationale: API keys identify a Google Cloud project rather than a user, and clients can extract them. | Client Authentication |
| Authenticate end users or calling services | JWT (oauth2 scheme with x-google-issuer / x-google-jwks_uri in OAS 2.0, or x-google-auth in OAS 3.x) | Operation-level security overrides root security. Include api_key: [] and the JWT scheme in the same security requirement object when an endpoint needs both user identity and quotas. | Client Authentication |
| Block direct calls to the backend service | Dedicated gateway service account (--backend-auth-service-account) + roles/run.invoker + backend --no-allow-unauthenticated | Roll out the gateway config with --backend-auth-service-account BEFORE setting --no-allow-unauthenticated on a live backend to avoid 403 downtime. | Backend Security |
Rate limit requests per consumer project (1/min/{project}) | x-google-management (OAS 2.0) or x-google-api-management (OAS 3.x) + x-google-quota | NEVER rename or remove a quota metric when creating a new API config while any gateway of the API runs an older config referencing that metric. Rationale: Creating the config immediately overwrites API-wide quotas and causes HTTP 500 Failed to call Service Control Quota on older gateways. | Quotas and Rate Limiting |
Secure MCP tools/list and tools/call on /mcp (Public Preview) | x-google-api-management.mcp.tools-list.security (exactly one JWT or API key scheme) + operation security and x-google-mcp-tool: false | x-google-mcp-tool: false hides an operation from MCP only; the underlying REST route remains live unless secured or removed. | MCP Traffic Security |
Execution Workflow and Validation Loop
Follow this zero-downtime sequence when authoring or updating an API Gateway security or quota configuration:
Create the gateway service account and grant backend invoker permissions:
Follow Steps 1–3 of Backend Security to create
{gateway_sa_name}and request human approval before grantingroles/run.invokeron{backend_service}.Update backend handler code to decode verified user claims from
X-Apigateway-Api-Userinfo(base64url-encoded JSON) instead ofAuthorization.
Author the OpenAPI authentication and quota definitions:
Use the OpenAPI 2.0 or OpenAPI 3.0.x templates and provider tables in Client Authentication and Quotas and Rate Limiting.
If the specification enables MCP (
x-google-api-management.mcp), configure exactly one JWT or API key scheme ontools-list(also list a JWT scheme in the rootsecuritylist), and review operation opt-outs using MCP Traffic Security.
Run the quota metric validation loop BEFORE running
api-configs create:If neither the active specs nor
{candidate_openapi_spec}define quota metrics (x-google-management.metricsorx-google-api-management.metrics), skip this step.Request human approval before creating an API config that defines or modifies quotas, because
api-configs createimmediately replaces quota definitions across all gateways of the API.By default, run
scripts/check_quota_metrics.pyagainst all active gateways deployed for{api_id}in{project_id}:python3 scripts/check_quota_metrics.py \ --spec={candidate_openapi_spec} \ --project={project_id} \ --api={api_id}Escape hatch for offline environments without
gcloudaccess (pass--baseline-specfor every config currently served by a gateway of the API):python3 scripts/check_quota_metrics.py \ --spec={candidate_openapi_spec} \ --baseline-spec={previous_openapi_spec} \ --dry-runIf
scripts/check_quota_metrics.pyexits with code1(status: "FAIL"), add the missing metrics and limits back to{candidate_openapi_spec}, move eachx-google-quotalisted inignored_quota_extensionsinto the operations it applies to, and re-run the script until it exits with code0(status: "PASS"). If the user wants to remove a limit on purpose, confirm the removal with the user, because it removes that limit from every gateway of the API. Ifskipped_configsis not empty, tell the user that the script did not inspect those API configs (for example, gRPC configs), and check their quota metrics and limits manually. If it exits with code2, fix the problem printed to stderr: the CLI arguments,gcloudauthentication, or an API config that the script cannot inspect.
Create the API config, update the gateway, enable the managed service, and lock down the backend:
Follow Steps 4–5 of Backend Security to run
gcloud api-gateway api-configs createandgcloud api-gateway gateways update(request human approval before updating a production gateway), enable the API'smanagedServicein consumer projects (Client Authentication), and then restrict{backend_service}with--no-allow-unauthenticated.Verify that direct calls to the backend
*.run.appURL return403, unauthenticated calls to the gatewaydefaultHostnamereturn401, and authenticated calls return200.
Pre-Deployment Checklist
[ ] Dedicated
{gateway_sa_name}is grantedroles/run.invoker(orroles/cloudfunctions.invoker), and--no-allow-unauthenticatedis applied after the gateway config is active.[ ] Backend code decodes
X-Apigateway-Api-Userinfofor user claims.[ ] Security enforcement does not depend solely on a load balancer or Google Cloud Armor in front of the gateway, because the gateway's
defaultHostname(*.gateway.dev) cannot be disabled.[ ] Every quota-enforced operation requires an API key (
api_key: []), and the API'smanagedServiceis enabled and allowed on the API key in each consumer project.[ ]
python3 scripts/check_quota_metrics.pyreturnsstatus: "PASS"(exit code0) against active gateway configs or all baseline specs beforeapi-configs create.[ ] If MCP is enabled,
x-google-api-management.mcp.tools-list.securitynames exactly one JWT or API key scheme (a JWT scheme is also in the rootsecuritylist), and administrative endpoints are secured on REST in addition tox-google-mcp-tool: false.
Resource Cleanup
To avoid ongoing charges when tearing down a test or staging environment, request human approval and delete the gateway, all API configs, the API, and the service account in dependency order:
gcloud api-gateway gateways delete {gateway_id} --location={region} --project={project_id}
gcloud api-gateway api-configs delete {config_id} --api={api_id} --project={project_id}
gcloud api-gateway apis delete {api_id} --project={project_id}
gcloud iam service-accounts delete {gateway_sa_name}@{project_id}.iam.gserviceaccount.com --project={project_id}Files
- ATTRIBUTION.mdAdded by AutoGineer910 B
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md11.2 KiB
- references/attribution.md1.9 KiB
- references/backend-security.md7.8 KiB
- references/client-authentication.md9.0 KiB
- references/mcp-traffic-security.md9.5 KiB
- references/quotas-and-rate-limiting.md7.5 KiB
- scripts/check_quota_metrics.pyScript15.7 KiB
scripts/check_quota_metrics.py
#!/usr/bin/env python3
"""Validates that a candidate OpenAPI spec keeps the active quota metrics.
API Gateway applies quota metrics and limits to the entire API rather than to an
individual API config. When a new API config is created, its quota definitions
immediately replace the definitions for the whole API. If a new API config
renames or removes a quota metric or limit that is still referenced by an active
gateway serving an older API config, requests to quota-enforced methods on that
older gateway fail with HTTP 500 ("Failed to call Service Control Quota").
The script also flags a metric that has a limit in a baseline but no limit in
the candidate, because the candidate's limits replace the limits of every
gateway of the API and the metric would no longer be rate limited.
It also flags an x-google-quota extension outside an operation, because API
Gateway reads x-google-quota only on operations and ignores it at the root and
path levels without an error.
"""
from __future__ import annotations
import argparse
import base64
import json
import re
import subprocess
import sys
from typing import Any
try:
import yaml
_HAS_YAML = True
except ImportError:
_HAS_YAML = False
# Matches the API ID and the config ID in a gateway's API config resource name.
_API_CONFIG_NAME_RE = re.compile(
r'/apis/(?P<api>[^/]+)/configs/(?P<config>[^/]+)$'
)
# The maximum time, in seconds, to wait for each gcloud command.
_GCLOUD_TIMEOUT_SECONDS = 60
def _load_spec(content: str, source_name: str) -> dict[str, Any]:
"""Parses a YAML or JSON OpenAPI specification string into a dictionary."""
try:
data = json.loads(content)
if isinstance(data, dict):
return data
except json.JSONDecodeError:
pass
if not _HAS_YAML:
raise RuntimeError(
'PyYAML is required to parse YAML specifications. Install it with: '
'pip install "PyYAML>=6.0,<7.0"'
)
try:
data = yaml.safe_load(content)
except yaml.YAMLError as exc:
raise ValueError(
f'Specification {source_name} is not valid JSON or YAML: {exc}'
) from exc
if not isinstance(data, dict):
raise ValueError(f'Specification {source_name} did not parse as a mapping.')
return data
def _read_spec(path: str) -> dict[str, Any]:
"""Reads and parses the OpenAPI specification file at path."""
with open(path, 'r', encoding='utf-8') as f:
return _load_spec(f.read(), path)
def extract_quota_metrics(spec: dict[str, Any]) -> set[str]:
"""Extracts declared quota metric names from an OpenAPI 2.0 or 3.x dict."""
metrics: set[str] = set()
# OpenAPI 2.0: x-google-management.metrics is a list of {name: ...} objects.
oas2_management = spec.get('x-google-management')
if isinstance(oas2_management, dict):
oas2_metrics = oas2_management.get('metrics', [])
if isinstance(oas2_metrics, list):
for item in oas2_metrics:
if isinstance(item, dict) and item.get('name') is not None:
metrics.add(str(item['name']))
# OpenAPI 3.x: x-google-api-management.metrics is a map keyed by metric name.
oas3_management = spec.get('x-google-api-management')
if isinstance(oas3_management, dict):
oas3_metrics = oas3_management.get('metrics', {})
if isinstance(oas3_metrics, dict):
for key in oas3_metrics.keys():
if key is not None:
metrics.add(str(key))
return metrics
def extract_limited_metrics(spec: dict[str, Any]) -> set[str]:
"""Extracts metric names that have a limit in quota.limits."""
limited: set[str] = set()
oas2_management = spec.get('x-google-management')
if isinstance(oas2_management, dict):
quota = oas2_management.get('quota', {})
if isinstance(quota, dict):
limits = quota.get('limits', [])
if isinstance(limits, list):
for item in limits:
if isinstance(item, dict) and item.get('metric') is not None:
limited.add(str(item['metric']))
oas3_management = spec.get('x-google-api-management')
if isinstance(oas3_management, dict):
quota = oas3_management.get('quota', {})
if isinstance(quota, dict):
limits = quota.get('limits', {})
if isinstance(limits, dict):
for item in limits.values():
if isinstance(item, dict) and item.get('metric') is not None:
limited.add(str(item['metric']))
return limited
def _quota_extension_metrics(quota_extension: Any) -> set[str]:
"""Returns the metric names that one x-google-quota extension charges."""
if not isinstance(quota_extension, dict):
return set()
costs = quota_extension.get('metricCosts')
if isinstance(costs, dict):
return {str(metric_name) for metric_name in costs}
return {str(metric_name) for metric_name in quota_extension}
def extract_referenced_quota_metrics(spec: dict[str, Any]) -> set[str]:
"""Extracts metric names that x-google-quota references at any level."""
referenced = _quota_extension_metrics(spec.get('x-google-quota'))
paths = spec.get('paths', {})
if isinstance(paths, dict):
for path_item in paths.values():
if isinstance(path_item, dict):
referenced |= _quota_extension_metrics(path_item.get('x-google-quota'))
for operation in path_item.values():
if isinstance(operation, dict):
referenced |= _quota_extension_metrics(
operation.get('x-google-quota')
)
return referenced
def find_ignored_quota_extensions(spec: dict[str, Any]) -> list[str]:
"""Returns the locations of x-google-quota extensions outside an operation.
API Gateway reads x-google-quota only on operations. It ignores the extension
at the root and path levels without an error, so the metrics that it charges
are never counted.
Args:
spec: The parsed OpenAPI 2.0 or 3.x specification.
Returns:
The location of each ignored extension, such as "x-google-quota" or
"paths./v1/orders.x-google-quota".
"""
locations: list[str] = []
if 'x-google-quota' in spec:
locations.append('x-google-quota')
paths = spec.get('paths', {})
if isinstance(paths, dict):
for path, path_item in paths.items():
if isinstance(path_item, dict) and 'x-google-quota' in path_item:
locations.append(f'paths.{path}.x-google-quota')
return locations
def _decode_contents(b64_contents: str) -> str:
"""Decodes base64 or base64url API config contents."""
normalized = b64_contents.replace('-', '+').replace('_', '/')
normalized += '=' * (-len(normalized) % 4)
return base64.b64decode(normalized).decode('utf-8-sig')
def _fetch_live_config_metrics(
project: str, api: str
) -> tuple[dict[str, list[str]], dict[str, list[str]], list[str]]:
"""Reads the quota metrics of each active API config of an API.
Args:
project: The Google Cloud project ID of the gateways.
api: The API Gateway API ID.
Returns:
The metrics of each active API config, the limited metrics of each active
API config, and the IDs of the active configs that have no OpenAPI
documents (for example, gRPC configs), which the script cannot inspect.
Raises:
RuntimeError: If a gcloud command fails, or if no active API config has
OpenAPI documents.
"""
gateways_command = [
'gcloud',
'api-gateway',
'gateways',
'list',
f'--project={project}',
'--format=json',
]
gateways_result = subprocess.run(
gateways_command,
capture_output=True,
text=True,
check=False,
stdin=subprocess.DEVNULL,
timeout=_GCLOUD_TIMEOUT_SECONDS,
)
if gateways_result.returncode != 0:
raise RuntimeError(
f'Failed to list gateways via gcloud: {gateways_result.stderr.strip()}'
)
gateways = json.loads(gateways_result.stdout or '[]')
config_ids: set[str] = set()
for gateway in gateways:
match = _API_CONFIG_NAME_RE.search(gateway.get('apiConfig', ''))
if match and match.group('api') == api:
config_ids.add(match.group('config'))
metrics_by_source: dict[str, list[str]] = {}
limited_by_source: dict[str, list[str]] = {}
skipped_configs: list[str] = []
for config_id in sorted(config_ids):
config_command = [
'gcloud',
'api-gateway',
'api-configs',
'describe',
config_id,
f'--api={api}',
f'--project={project}',
'--view=FULL',
'--format=json',
]
config_result = subprocess.run(
config_command,
capture_output=True,
text=True,
check=False,
stdin=subprocess.DEVNULL,
timeout=_GCLOUD_TIMEOUT_SECONDS,
)
if config_result.returncode != 0:
raise RuntimeError(
f'Failed to describe API config {config_id}:'
f' {config_result.stderr.strip()}'
)
config_data = json.loads(config_result.stdout or '{}')
openapi_documents = config_data.get('openapiDocuments', [])
if not openapi_documents:
# One gRPC config must not block the check of the other configs.
skipped_configs.append(config_id)
continue
config_metrics: set[str] = set()
config_limited: set[str] = set()
for document_entry in openapi_documents:
document = document_entry.get('document', {})
b64_contents = document.get('contents', '')
if b64_contents:
raw_text = _decode_contents(b64_contents)
parsed = _load_spec(
raw_text, f'{config_id}:{document.get("path", "spec")}'
)
config_metrics.update(extract_quota_metrics(parsed))
config_metrics.update(extract_referenced_quota_metrics(parsed))
config_limited.update(extract_limited_metrics(parsed))
metrics_by_source[f'api-config:{config_id}'] = sorted(config_metrics)
limited_by_source[f'api-config:{config_id}'] = sorted(config_limited)
if skipped_configs and not metrics_by_source:
raise RuntimeError(
f'No active API config of API {api} has OpenAPI documents (for'
f' example, gRPC configs: {", ".join(skipped_configs)}), so the script'
' cannot inspect them. Check their quota metrics and limits manually'
' before rollout.'
)
return metrics_by_source, limited_by_source, skipped_configs
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description=(
'Verify that a candidate OpenAPI specification retains all quota '
'metrics and limits required by baseline specs or active gateways.'
)
)
parser.add_argument(
'--spec',
required=True,
help='Path to the candidate OpenAPI 2.0 or 3.x specification file.',
)
parser.add_argument(
'--baseline-spec',
action='append',
default=[],
help=(
'Path to an existing OpenAPI spec currently served by a gateway. '
'Can be passed multiple times for offline comparison.'
),
)
parser.add_argument(
'--project',
help='Google Cloud project ID to inspect active gateways via gcloud.',
)
parser.add_argument(
'--api',
help='API Gateway API ID to inspect active gateways via gcloud.',
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Validate local --baseline-spec files only without calling gcloud.',
)
args = parser.parse_args(argv)
if args.dry_run and (args.project or args.api):
print(
'Error: Do not combine --dry-run with --project or --api. Use '
'--project and --api for live gateway checks, or --baseline-spec with '
'--dry-run for offline file comparisons.',
file=sys.stderr,
)
return 2
if (args.project or args.api) and not (args.project and args.api):
print(
'Error: Both --project and --api must be provided together when '
'querying live gateways.',
file=sys.stderr,
)
return 2
if not args.baseline_spec and not (args.project and args.api):
print(
'Error: Provide at least one baseline source via --project and --api '
'(live gateway check) or --baseline-spec (offline spec check).',
file=sys.stderr,
)
return 2
try:
candidate_spec = _read_spec(args.spec)
baseline_specs = {path: _read_spec(path) for path in args.baseline_spec}
live_sources, live_limited, skipped_configs = (
_fetch_live_config_metrics(args.project, args.api)
if args.project and args.api
else ({}, {}, [])
)
except (OSError, RuntimeError, ValueError, subprocess.SubprocessError) as exc:
print(f'Error: {exc}', file=sys.stderr)
return 2
for config_id in skipped_configs:
print(
f'Warning: Skipped active API config {config_id} because it has no'
' OpenAPI documents (for example, a gRPC config). Check its quota'
' metrics and limits manually.',
file=sys.stderr,
)
candidate_metrics = extract_quota_metrics(candidate_spec)
candidate_limited = extract_limited_metrics(candidate_spec)
candidate_references = extract_referenced_quota_metrics(candidate_spec)
ignored_quota_extensions = find_ignored_quota_extensions(candidate_spec)
baseline_sources: dict[str, list[str]] = {}
baseline_limited: dict[str, list[str]] = {}
for base_path, base_spec in baseline_specs.items():
base_names = extract_quota_metrics(
base_spec
) | extract_referenced_quota_metrics(base_spec)
baseline_sources[base_path] = sorted(base_names)
baseline_limited[base_path] = sorted(extract_limited_metrics(base_spec))
baseline_sources.update(live_sources)
baseline_limited.update(live_limited)
missing_by_source: dict[str, list[str]] = {}
all_missing: set[str] = set()
missing_limits: set[str] = set()
undeclared_candidate_references = sorted(
candidate_references - candidate_metrics
)
for source, old_metrics in baseline_sources.items():
missing = sorted(set(old_metrics) - candidate_metrics)
if missing:
missing_by_source[source] = missing
all_missing.update(missing)
# Flag only limits that a baseline has and the candidate drops. A metric
# that never had a limit does not need one now.
for old_limited in baseline_limited.values():
missing_limits.update(set(old_limited) - candidate_limited)
has_failure = bool(
all_missing
or missing_limits
or undeclared_candidate_references
or ignored_quota_extensions
)
status = 'FAIL' if has_failure else 'PASS'
report = {
'status': status,
'candidate_spec': args.spec,
'candidate_metrics': sorted(candidate_metrics),
'candidate_limited_metrics': sorted(candidate_limited),
'undeclared_candidate_references': undeclared_candidate_references,
'ignored_quota_extensions': ignored_quota_extensions,
'skipped_configs': skipped_configs,
'baseline_sources': baseline_sources,
'baseline_limited_metrics': baseline_limited,
'missing_metrics': sorted(all_missing),
'missing_limits_for_metrics': sorted(missing_limits),
'missing_by_source': missing_by_source,
}
print(json.dumps(report, indent=2))
if all_missing or undeclared_candidate_references:
print(
'Quota metric validation failed. Creating this API config will '
'overwrite API-wide quota definitions and can cause HTTP 500 "Failed '
'to call Service Control Quota" on active gateways.',
file=sys.stderr,
)
if missing_limits:
print(
'Quota limit validation failed. Creating this API config removes the '
'limits for the metrics in "missing_limits_for_metrics" from every '
'gateway of the API, so those metrics are no longer rate limited.',
file=sys.stderr,
)
if ignored_quota_extensions:
print(
'Quota extension validation failed. API Gateway reads x-google-quota '
'only on operations and ignores the extensions in '
'"ignored_quota_extensions", so the metrics that they charge are never '
'counted. Move each x-google-quota into the operations it applies to.',
file=sys.stderr,
)
return 1 if has_failure else 0
if __name__ == '__main__':
sys.exit(main())
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs an API key, Needs a command-line tool, Needs Python
Security
What our scanner noticed· Low risk
- Instructions that use the network
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- Publisher
- Google (Official)
- Source
- google/skills · skills/cloud/api-gateway-backend-security-and-rate-limiting · commit 2194d3b
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Google or Google.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download api-gateway-backend-security-and-rate-limiting.zip