Self-hosted API gateway for Claude Code on Amazon Bedrock
CCAG has three layers of configuration, each serving a different purpose:
environments.json for CDK, or ~/.ccag/config.json for the CLI): deployment targets and infrastructure settingsenvironments.json / CLI config (deploy-time, infrastructure)
|
Environment vars (startup-time, bootstrap)
|
DB settings / portal (runtime, dynamic, no restart needed)
Environment variables bootstrap the gateway on startup. Once running, most settings can be managed through the admin portal or API and are stored in the database. DB settings are synced across instances via a polling mechanism (every 5 seconds).
Deployment configuration varies by deployment method:
environments.json in the project root (see infra/README.md for the schema)ccag): uses ~/.ccag/config.json for storing the gateway URL and auth tokens (created automatically by ccag config set-url)The fields below apply to ~/.ccag/config.json (CLI config):
| Field | Description |
|---|---|
account_id |
AWS account ID |
region |
AWS region for Bedrock API calls and infrastructure |
aws_profile |
AWS CLI profile name (configured in ~/.aws/config) |
domain_name |
Full domain for the gateway (e.g., ccag.example.com) |
hosted_zone_name |
Route53 hosted zone for DNS record creation |
certificate_arn |
ACM certificate ARN (null = auto-created via DNS validation) |
admin_users |
Comma-separated OIDC subjects bootstrapped as admin |
desired_count |
Number of ECS Fargate tasks |
To update CLI configuration, edit ~/.ccag/config.json directly or use ccag config set-url.
Environment variables are set in the ECS task definition (via CDK) or locally when running the gateway. They control startup behavior and cannot be changed at runtime without restarting.
| Variable | Default | Description |
|---|---|---|
PROXY_HOST |
127.0.0.1 |
Listen address. Set to 0.0.0.0 for container/remote access. |
PROXY_PORT |
8080 |
HTTP listen port. |
RUST_LOG |
info |
Log level. Set to debug to see request/response bodies. |
LOG_FORMAT |
text |
Log output format: text (human-readable) or json (structured, recommended for production/CloudWatch Logs Insights). |
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
(none) | Full Postgres connection string (e.g., postgres://user:pass@host/db). If unset, the gateway runs without DB features (no virtual keys, spend tracking, or persistent settings). |
DATABASE_HOST |
(none) | Postgres hostname. Alternative to DATABASE_URL for ECS/CDK deployments where host, port, and credentials are separate. |
DATABASE_PORT |
5432 |
Postgres port (used with DATABASE_HOST). |
DATABASE_NAME |
proxy |
Postgres database name (used with DATABASE_HOST). |
DATABASE_USER |
proxy |
Postgres username (used with DATABASE_HOST). |
DB_PASSWORD |
(none) | Postgres password (used with DATABASE_HOST). |
RDS_IAM_AUTH |
false |
Enable IAM authentication for RDS. When true, the gateway generates short-lived IAM auth tokens instead of using a static password. Requires the DB user to be configured for IAM auth and the task role to have rds-db:connect permission. |
When DATABASE_HOST is set, the gateway constructs the connection string from the individual components. This is the pattern used by the CDK stack, where credentials come from AWS Secrets Manager.
| Variable | Default | Description |
|---|---|---|
ADMIN_USERNAME |
admin |
Bootstrap admin username for password-based login. |
ADMIN_PASSWORD |
admin |
Bootstrap admin password. Change this in production. The gateway logs a warning on startup if the default is used. Setting this env var does NOT force admin login to be enabled; use ADMIN_PASSWORD_ENABLE for that. |
ADMIN_PASSWORD_ENABLE |
(none) | Set to true to force admin login on, overriding the portal setting. Use as a recovery mechanism when SSO is broken. Remove after recovery. |
ADMIN_USERS |
(none) | Comma-separated OIDC subject identifiers auto-provisioned as admin users on login. |
OIDC_ISSUER |
(none) | OIDC issuer URL (e.g., https://dev-12345.okta.com). Env-level IDP takes precedence over DB-configured IDPs. |
OIDC_NAME |
(auto) | Display name for the env-level IDP. Auto-detected from issuer URL (Okta, Azure AD, Google, or “SSO”). |
OIDC_AUDIENCE |
(none) | Expected JWT audience claim. |
OIDC_JWKS_URL |
(auto) | JWKS endpoint URL. Auto-discovered from {issuer}/.well-known/openid-configuration if not set. |
| Variable | Default | Description |
|---|---|---|
TLS_PORT |
443 |
HTTPS listener port. Automatically starts when listening on loopback (needed for OIDC redirect flows). |
TLS_CERT |
(none) | Path to TLS certificate file. If unset, a self-signed certificate is generated for local development. |
TLS_KEY |
(none) | Path to TLS private key file. |
| Variable | Default | Description |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
(none) | OTLP gRPC endpoint for exporting metrics (e.g., http://otel-collector:4317). When set, all metric instruments are exported via gRPC every 60 seconds alongside the Prometheus scrape endpoint. See docs/metrics.md for the full metric list. |
Alarm notifications (via CloudWatch Alarms and EventBridge) are configured in the CDK stack props:
| CDK Prop | Description |
|---|---|
alarmWebhookUrl |
Webhook URL for alarm notifications (Slack incoming webhook, etc.) |
These settings are stored in the database and can be changed at any time through the admin portal or the PUT /admin/settings/{key} API. Changes propagate to all gateway instances within 5 seconds (via the cache version polling mechanism).
| Setting Key | Default | Description |
|---|---|---|
virtual_keys_enabled |
true |
Enable/disable virtual key authentication. When disabled, only OIDC and admin password auth work. |
admin_login_enabled |
true |
Enable/disable the admin username/password login form. Useful to disable once OIDC is configured. |
session_token_ttl_hours |
24 |
How long session tokens (from portal login) remain valid. |
Additional OIDC providers can be configured through the portal or API (beyond the env-level OIDC_ISSUER):
curl -X POST https://ccag.example.com/admin/idps \
-H "authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{
"name": "Okta",
"issuer_url": "https://dev-12345.okta.com",
"audience": "ccag",
"auto_provision": true,
"default_role": "member"
}'
Rate limits are set per virtual key:
curl -X POST https://ccag.example.com/admin/keys \
-H "authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"name": "rate-limited-key", "rate_limit_rpm": 60}'
Budget limits can be configured per team or globally through the admin portal or API. See the portal’s Budget section for details.
Custom model ID mappings (Anthropic model ID to Bedrock model ID) can be configured through the admin portal. By default, CCAG auto-detects the correct Bedrock model IDs based on the AWS region.
Multi-endpoint routing (e.g., routing to different AWS accounts or regions) is configured through the admin portal’s Endpoints section.
The admin portal includes a cross-organization analytics dashboard at /portal#/org-analytics. Analytics queries aggregate data from the spend_log table (no additional tables required).
API endpoints (all require admin authentication):
| Endpoint | Description |
|---|---|
GET /admin/analytics/org/overview |
KPI summary + filter options |
GET /admin/analytics/org/spend |
Spend timeseries, by-team/user/model breakdowns, budget status, OLS forecast |
GET /admin/analytics/org/activity |
Active users over time, hourly request heatmap |
GET /admin/analytics/org/models |
Model mix, latency percentiles, cache hit rate, token breakdown, endpoint utilization |
GET /admin/analytics/org/tools |
MCP server usage, top tools, tool call totals |
GET /admin/analytics/org/export |
CSV export of filtered spend data |
Query parameters (all endpoints):
| Parameter | Example | Description |
|---|---|---|
days |
7 |
Relative time range (1, 7, 14, 30, 90) |
from / to |
2026-03-01 / 2026-03-15 |
Absolute date range (overrides days) |
granularity |
hour |
Time bucket size: auto, hour, day, week |
team |
Frontend,ML |
Comma-separated team filter (multi-select) |
user |
alice@co.com |
Comma-separated user filter |
model |
claude-opus-4-6 |
Comma-separated model filter |
endpoint |
Default |
Comma-separated endpoint filter |
CCAG can deliver event notifications (budget alerts, rate limit events) to a single destination that you configure through the admin portal.
| Type | Value | Description |
|---|---|---|
| Webhook | https://hooks.example.com/ccag |
HTTPS endpoint receives JSON POST with event payload |
| SNS Topic | arn:aws:sns:REGION:ACCOUNT:TOPIC |
Your own AWS SNS topic. Add a resource-based policy granting sns:Publish |
| EventBridge | arn:aws:events:REGION:ACCOUNT:event-bus/NAME |
Your own custom event bus. Add a resource-based policy granting events:PutEvents |
Each category can be independently toggled on the active config:
| Category | Events | Default |
|---|---|---|
budget |
budget_warning, budget_shaped, budget_blocked, team_budget_* |
ON |
rate_limit |
rate_limit_hit |
OFF |
{
"source": "ccag",
"version": "1",
"category": "budget",
"event_type": "budget_warning",
"severity": "warning",
"user_identity": "jane@corp.com",
"team_id": "uuid",
"team_name": "frontend",
"detail": {
"threshold_percent": 80,
"spend_usd": 41.20,
"limit_usd": 50.00,
"percent": 82.4,
"period": "weekly",
"period_start": "2026-03-17T00:00:00Z"
},
"timestamp": "2026-03-19T14:30:00Z"
}
For EventBridge, events use source: "ccag.notifications" and detail-type set to the event type.
For SNS topics and EventBridge buses in your account, add a resource-based policy allowing the CCAG task role to publish:
SNS Topic Policy Statement:
{
"Effect": "Allow",
"Principal": { "AWS": "<TaskRoleArn>" },
"Action": "sns:Publish",
"Resource": "<this-topic-arn>"
}
EventBridge Bus Policy Statement:
{
"Effect": "Allow",
"Principal": { "AWS": "<TaskRoleArn>" },
"Action": "events:PutEvents",
"Resource": "<this-bus-arn>"
}
The TaskRoleArn is available as a CDK output after deployment.
The BUDGET_NOTIFICATION_URL environment variable continues to work as a fallback. When both a DB config and env var exist, the DB config takes precedence. To migrate:
BUDGET_NOTIFICATION_URL| Output | Purpose |
|---|---|
AlarmTopicArn |
SNS topic for infrastructure alarms (CloudWatch). Subscribe for ops alerts. |
TaskRoleArn |
ECS task role ARN. Use in resource-based policies for BYO SNS/EventBridge. |
| Method | Path | Description |
|---|---|---|
GET |
/admin/notifications/config |
Get active + draft config with delivery history |
PUT |
/admin/notifications/config |
Save/update draft destination |
DELETE |
/admin/notifications/config |
Deactivate (remove active config) |
DELETE |
/admin/notifications/draft |
Discard draft |
POST |
/admin/notifications/test |
Test deliver to draft |
POST |
/admin/notifications/activate |
Promote draft to active |
PUT |
/admin/notifications/categories |
Update event categories on active |
The ADMIN_PASSWORD environment variable serves as a break-glass recovery mechanism. Even if all OIDC providers are misconfigured or the database is corrupted, you can always log in with the admin username and password to regain access.
For production deployments:
ADMIN_PASSWORD to a strong, unique valueadmin_login_enabled in the portal once OIDC is working, but keep the password documented for emergencies