Customer Management Guide
Version: 1.0.0 | Updated: 2026-03-05 | Status: Phase 1 (Visibility)
Version: 1.0.0 | Updated: 2026-03-05 | Status: Phase 1 (Visibility)
This guide covers AEGIS customer management for platform operators: creating customers, querying usage, and understanding the Lambda integration.
1. Overview
Phase 1 provides visibility only — you can see who is using the API and how much, but there is no enforcement (no rate limits, no quota blocks). All customers receive the same service regardless of tier. Tiers are metadata for future billing phases.
What Phase 1 includes:
- Customer records in DynamoDB (single-table design)
- Per-evaluation usage metering with atomic counters
- Admin CLI for customer CRUD and usage queries
- Lambda integration with response header enrichment
- API key provisioning script
What Phase 1 does NOT include:
- Self-service customer portal
- Rate limiting or quota enforcement
- Billing or invoicing
- Customer-facing dashboards
2. How It Works
API Gateway request
│
├── requestContext.identity.apiKeyId extracted
│
▼
Lambda handler
│
├── APIKEY#{api_key_id} → DynamoDB lookup → customer_id
│ (cached in Lambda memory for warm starts)
│
├── Governance evaluation (pcw_decide) — unaffected by customer lookup
│
├── Usage recorded: USAGE#{customer_id}#{YYYY-MM} / DAY#{DD}
│ (fire-and-forget — never blocks response)
│
└── Response enriched with customer headers
X-AEGIS-Customer: cust_abc123
X-AEGIS-Tier: pro
body._customer_id: cust_abc123
body._tier: proKey design principle: Customer lookup and usage metering failures never block governance evaluation. The Lambda handler catches all exceptions from the customer subsystem and logs warnings.
3. DynamoDB Item Patterns
All customer data lives in the existing aegis-governance-state-{stage} table using single-table design.
| Pattern | pk | sk | Fields |
|---|---|---|---|
| Customer profile | CUSTOMER#{cust_id} | PROFILE | name, email, company, tier, status, api_key_id, timestamps |
| Daily usage | USAGE#{cust_id}#{YYYY-MM} | DAY#{DD} | evaluate_count, risk_check_count, total_calls, channels |
| API key mapping | APIKEY#{api_key_id} | MAPPING | customer_id |
Customer IDs follow Stripe convention: cust_ prefix + 12-character hex (e.g., cust_a1b2c3d4e5f6).
Tiers: free, pro, enterprise (metadata only in Phase 1).
Status: active or suspended.
4. Creating Customers
Via Admin CLI
# Minimal (community tier, no API key association)
aegis admin create-customer \
--name "Acme Corp" \
--email "admin@acme.example.com"
# Full options
aegis admin create-customer \
--name "Acme Corp" \
--email "admin@acme.example.com" \
--company "Acme Inc." \
--tier professional \
--api-key-id "abc123xyz" \
--table aegis-governance-state-dev \
--region us-west-2Output:
{
"customer_id": "cust_a1b2c3d4e5f6",
"name": "Acme Corp",
"email": "admin@acme.example.com",
"company": "Acme Inc.",
"tier": "pro",
"status": "active",
"api_key_id": "abc123xyz",
"created_at": "2026-03-05T12:00:00+00:00",
"updated_at": "2026-03-05T12:00:00+00:00",
"metadata": {}
}Via Provisioning Script
For full API Gateway key provisioning (creates the key, attaches it to a usage plan, and outputs the key value):
python scripts/provision-customer.py create \
--customer acme-corp \
--email admin@acme.example.com \
--tier standard \
--usage-plan-id abc123 \
--region us-west-2This creates the API Gateway key and prints the key value once (show-once pattern). After provisioning, use aegis admin create-customer --api-key-id <KEY_ID> to create the matching customer record.
Programmatic
from aegis_governance.customer import CustomerManager
mgr = CustomerManager(table_name="aegis-governance-state-dev")
customer = mgr.create_customer(
name="Acme Corp",
email="admin@acme.example.com",
tier="pro",
api_key_id="abc123xyz",
)
print(customer.customer_id) # cust_a1b2c3d4e5f65. Querying Usage
Via Admin CLI
# Current month usage
aegis admin usage cust_a1b2c3d4e5f6
# Specific month
aegis admin usage cust_a1b2c3d4e5f6 --month 2026-03Output:
{
"customer_id": "cust_a1b2c3d4e5f6",
"month": "2026-03",
"total_evaluations": 142,
"total_risk_checks": 28,
"total_calls": 170,
"channels": {},
"daily_breakdown": [
{
"day": "01",
"evaluate_count": 12,
"risk_check_count": 3,
"total_calls": 15
}
]
}Direct DynamoDB Query
For ad-hoc queries or reporting:
# All usage records for a customer in March 2026
aws dynamodb query \
--table-name aegis-governance-state-dev \
--key-condition-expression "pk = :pk AND begins_with(sk, :prefix)" \
--expression-attribute-values '{
":pk": {"S": "USAGE#cust_a1b2c3d4e5f6#2026-03"},
":prefix": {"S": "DAY#"}
}'6. Response Headers
When a request is made with a known API key, the Lambda handler adds customer context to the response:
Headers:
| Header | Example | Description |
|---|---|---|
X-AEGIS-Customer | cust_a1b2c3d4e5f6 | Customer ID |
X-AEGIS-Tier | professional | Customer tier |
X-AEGIS-Tenant | cust_a1b2c3d4e5f6 | Customer ID (always present; anonymous if unauthenticated) |
X-AEGIS-Request-Id | uuid | Request tracking ID (always present) |
Body fields (appended to the JSON response body):
| Field | Example | Description |
|---|---|---|
_customer_id | "cust_a1b2c3d4e5f6" | Customer ID (only if known) |
_tier | "professional" | Customer tier (only if known) |
_tenant_id | "cust_a1b2c3d4e5f6" | Customer ID (always present; "anonymous" if unauthenticated) |
_request_id | "uuid" | Request tracking ID (always present) |
For anonymous requests (no API key or unknown key), only _tenant_id ("anonymous") and _request_id are present.
7. Lambda Integration
The Lambda handler (src/lambda_handler.py) integrates customer management with a singleton pattern optimized for Lambda warm starts:
- Singleton init:
CustomerManageris created once per Lambda instance and reused across invocations - API key extraction:
requestContext.identity.apiKeyIdfrom API Gateway event - Customer lookup:
APIKEY#→CUSTOMER#via DynamoDB (cached in-memory after first lookup) - Governance evaluation:
pcw_decide()runs independently — customer lookup cannot affect it - Usage recording: Atomic DynamoDB counter increment (fire-and-forget)
- Response enrichment: Customer headers and body fields injected into the response
Cache behavior: The _customer_cache dict persists across warm-start invocations. A customer looked up once stays cached until the Lambda instance is recycled (typically 5-15 minutes of inactivity).
8. Troubleshooting
boto3 Not Installed
{"error": "boto3 required: pip install boto3"}The admin CLI commands require boto3. Install with:
pip install boto3Or install AEGIS with the kms extra which includes boto3:
pip install -e ".[kms]"DynamoDB Table Not Found
An error occurred (ResourceNotFoundException)Check that AEGIS_TABLE_NAME environment variable is set correctly, or pass --table explicitly:
export AEGIS_TABLE_NAME=aegis-governance-state-dev
aegis admin list-customersAnonymous Users in Response Headers
If X-AEGIS-Tenant shows anonymous, the request was made without an API key or with a key not registered in API Gateway. This is normal for health checks and unauthenticated requests.
Customer Not Found
{"error": "Customer not found: cust_abc123"}Verify the customer exists:
aegis admin list-customersIf the customer was created with scripts/provision-customer.py, ensure you also created the customer record with aegis admin create-customer --api-key-id <KEY_ID>.
Usage Shows Zero
Usage is recorded per API route (/evaluate and /risk-check). If a customer has been created but never called the API, usage will be zero. Verify the API key mapping exists by checking that aegis admin get-customer <ID> shows a non-empty api_key_id.
9. What's Next (Phase 2-3)
| Phase | Scope | Status |
|---|---|---|
| Phase 2: Self-Service | Customer portal, Unkey API keys, usage dashboards, rate limiting | Proposed |
| Phase 3: Monetization | Stripe Billing, quota enforcement, tiered pricing | Proposed |
See ADR-008 for the full strategy.
References
- ADR-008: Customer Monetization Strategy — Architecture decision record
- CLI Reference — Admin Commands — Full CLI documentation
- Production Guide — AWS deployment guide
- Market Research — Pricing and competitive analysis