Environment Variables¶
All configuration is loaded from environment variables with the APPMECCA_API_ prefix, using Pydantic Settings. The definitive list is the Settings class in src/appmecca_api/config.py.
get_settings() is not cached — every call constructs a fresh Settings() and re-reads the environment.
Required Variables¶
| Variable | Description | Example |
|---|---|---|
APPMECCA_API_DATABASE_URL |
PostgreSQL connection string, password embedded | postgresql://dbadmin:pass@db.internal:5432/appmecca |
The API never resolves the database password itself
The agent reads the RDS master password from Secrets Manager and bakes it
into APPMECCA_API_DATABASE_URL before launching the container.
_resolve_database_url raises if the URL is missing or carries no
password — it will not fall back to its own Secrets Manager call.
Core Variables¶
| Variable | Description | Default |
|---|---|---|
APPMECCA_API_ENVIRONMENT |
Environment name — the env this container is bound to | dev |
APPMECCA_API_DOMAIN |
Domain used to build app URLs, e.g. dev.appmecca.net |
(empty) |
APPMECCA_API_INTERNAL_DOMAIN |
Route53 private-zone domain for service discovery | (empty → {environment}.appmecca.internal) |
APPMECCA_API_URL_PATTERN |
Tenant URL pattern; mirrors conf/{env}.yaml routing.url_pattern |
single_label |
APPMECCA_API_AWS_REGION |
Primary AWS region | af-south-1 |
APPMECCA_API_AWS_ACCOUNT_ID |
AWS account ID | (empty) |
APPMECCA_API_DYNAMODB_REGION |
Region for the asset-index table | us-east-2 |
APPMECCA_API_DB_MIN_POOL_SIZE |
Minimum asyncpg connections | 1 |
APPMECCA_API_DB_MAX_POOL_SIZE |
Maximum asyncpg connections | 10 |
APPMECCA_API_API_TITLE |
OpenAPI title | AppMecca API |
APPMECCA_API_API_VERSION |
Reported version | installed package version |
APPMECCA_API_CORS_ORIGINS |
Allowed CORS origins (JSON list) | ["*"] |
APPMECCA_API_DEBUG |
Enable debug mode | false |
DYNAMODB_REGION is vestigial
The API makes no DynamoDB calls at all. The setting survives from the retired App Registry table; the asset index is read only by the admin CLI and the agent.
Celery, Redis and Secrets¶
| Variable | Description | Default |
|---|---|---|
APPMECCA_API_CELERY_BROKER_URL |
Redis broker, e.g. redis://host:6379/0 |
(empty) |
APPMECCA_API_CELERY_RESULT_BACKEND |
Redis result backend, e.g. redis://host:6379/1 |
(empty) |
APPMECCA_API_ROOT_KEY_SECRET_NAME |
Secrets Manager name for the root API key | (empty) |
APPMECCA_API_GATEWAY_INTERNAL_SECRET |
Shared secret the auth gateway sends on /api/internal/* |
(empty) |
APPMECCA_API_OAUTH_TOKEN_KEY |
Base64 AES-256-GCM key sealing linked-git tokens | (empty → repo linking disabled) |
Authentication and Rate Limits¶
| Variable | Description | Default |
|---|---|---|
APPMECCA_API_INVITE_REQUIRED |
A GRANT, not a filter — see the ⚠ below. When on, an address may create an account if it holds a pending invite. Enforced at the account-CREATION seams (both the emailed-code path and the OAuth callback), not only on POST /api/auth/code/request. On the request route an uninvited address gets the same body and no mail (never a 403 — that would be an enumeration oracle); on the two post-proof doors it gets 403 signup_not_permitted. An EXISTING user is always let through: the gate governs who may create an account, not who may sign back into one. |
false |
APPMECCA_API_AUTH_SIGNUP_SELFSERVE_DOMAINS |
A GRANT, not a filter. Comma-separated domains whose addresses may self-serve signup (e.g. oronomo.com,unomena.com). Empty = off. Matching is exact, case-insensitive equality on the label after the last @ — never a suffix match, so evil-unomena.com and unomena.com.evil.co are refused. |
(empty) |
APPMECCA_API_AUTH_RATE_LIMIT_REDIS_URL |
Redis for cross-instance limit state (empty → per-process memory) | (empty) |
APPMECCA_API_AUTH_RATE_LIMIT_LOGIN_IP |
Per-IP login cap | 20/minute |
APPMECCA_API_AUTH_RATE_LIMIT_REGISTER_IP |
Per-IP register cap | 5/minute |
APPMECCA_API_AUTH_RATE_LIMIT_RESET_IP |
Per-IP password-reset cap | 5/minute |
APPMECCA_API_AUTH_RATE_LIMIT_APP_TICKET_IP |
Per-IP app-ticket mint cap | 60/minute |
APPMECCA_API_AUTH_RATE_LIMIT_LOGIN_EMAIL |
Per-email login cap | 5/minute |
APPMECCA_API_AUTH_RATE_LIMIT_REGISTER_EMAIL |
Per-email register cap | 3/hour |
APPMECCA_API_AUTH_RATE_LIMIT_RESET_EMAIL |
Per-email password-reset cap | 3/hour |
⚠ The two signup settings compose with OR, so configuring either one WIDENS admission — it never narrows it. An address is admitted if it passes any gate that is on; if neither is on, signup is open. Setting the domain list while invite-only is on does not add safety: it re-opens self-serve signup for those whole domains. The API logs the resolved policy in words at boot (signup_policy). See the operator runbook for the three-stage rollout and its mandatory OAuth acceptance probe.
Social-login credentials are read directly from the process environment by auth/oauth/providers.py, not through Settings:
APPMECCA_API_OAUTH_{GOOGLE|GITHUB|GITLAB}_CLIENT_ID
APPMECCA_API_OAUTH_{GOOGLE|GITHUB|GITLAB}_CLIENT_SECRET
On a real environment these are provisioned into Secrets Manager at appmecca/{env}/oauth/{provider} — infra setup seeds them pre-DAG, and appmecca config oauth set rotates them. The agent injects them on the next deploy.
Email¶
| Variable | Description | Default |
|---|---|---|
APPMECCA_API_EMAIL_PROVIDER |
ses, dev (stdout + ring buffer) or null |
(empty) |
APPMECCA_API_EMAIL_SENDER |
Verified SES identity | (empty) |
APPMECCA_API_EMAIL_CONFIGURATION_SET |
SES configuration set | (empty) |
APPMECCA_API_EMAIL_LINK_BASE_URL |
Base URL for invite / reset links | (empty → https://api.{domain}) |
APPMECCA_API_EMAIL_VERIFICATION_RESEND_COOLDOWN_SECONDS |
Anti-spam cooldown; set 0 on test envs |
120 |
APPMECCA_API_NOTIFICATION_RECIPIENTS |
Comma-separated operational-notification recipients | (empty → dispatch disabled) |
Certificates¶
These are live-tunable — appmecca infra conf-update -e <env> pushes them from conf/{env}.yaml into the platform_config table, and the runtime-config service caches the row for 30 s. The values below are the fallbacks used when no row exists.
| Variable | Description | Default |
|---|---|---|
APPMECCA_API_CUSTOM_DOMAIN_PROVIDER |
letsencrypt or acm for new custom domains |
letsencrypt |
APPMECCA_API_LETSENCRYPT_STAGING |
Use the LE staging endpoint | false |
APPMECCA_API_LETSENCRYPT_CONTACT_EMAIL |
ACME account contact | (empty) |
APPMECCA_API_RENEWAL_THRESHOLD_DAYS |
Renew this many days before expiry | 30 |
APPMECCA_API_DNS_CHECK_INTERVAL_SECONDS |
DNS polling cadence | 60 |
Deployed vs Local¶
On a deployed environment the agent injects these variables at docker run time from the environment's config and Secrets Manager — you never set them by hand.
When running locally (via make run), export them in your shell or use the local Docker stack at appmecca-api/dev/:
export APPMECCA_API_DATABASE_URL="postgresql://appmecca:password@localhost:5432/appmecca?sslmode=disable"
export APPMECCA_API_ENVIRONMENT="dev"
sslmode=disable for local PostgreSQL
get_pool() requires TLS by default. A local Postgres container has no TLS
configured, so the URL needs sslmode=disable to opt out. Deployed URLs
never carry that flag. Whether TLS is verified or merely encrypted is a
separate, shared policy governed by APPMECCA_DB_TLS_VERIFY (note: no
_API_ prefix — it is read by appmecca.db.tls, not by Settings).
Connection Pool Sizing¶
The defaults (DB_MIN_POOL_SIZE=1, DB_MAX_POOL_SIZE=10) are per worker process. The container runs uvicorn --workers 2 and the api service runs replicas: 3, so a fully-warm environment can hold up to 2 × 3 × 10 = 60 connections from the API alone — before the Celery worker and Beat containers.
That matters because the deploy-queue admission gate budgets tenant connections against a fraction of the RDS max_connections. Raise these values only alongside the RDS instance class.
For local development the defaults are fine.