AEGISdocs
Deployment

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: pro

Key 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.

PatternpkskFields
Customer profileCUSTOMER#{cust_id}PROFILEname, email, company, tier, status, api_key_id, timestamps
Daily usageUSAGE#{cust_id}#{YYYY-MM}DAY#{DD}evaluate_count, risk_check_count, total_calls, channels
API key mappingAPIKEY#{api_key_id}MAPPINGcustomer_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-2

Output:

{
  "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-2

This 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_a1b2c3d4e5f6

5. Querying Usage

Via Admin CLI

# Current month usage
aegis admin usage cust_a1b2c3d4e5f6

# Specific month
aegis admin usage cust_a1b2c3d4e5f6 --month 2026-03

Output:

{
  "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:

HeaderExampleDescription
X-AEGIS-Customercust_a1b2c3d4e5f6Customer ID
X-AEGIS-TierprofessionalCustomer tier
X-AEGIS-Tenantcust_a1b2c3d4e5f6Customer ID (always present; anonymous if unauthenticated)
X-AEGIS-Request-IduuidRequest tracking ID (always present)

Body fields (appended to the JSON response body):

FieldExampleDescription
_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:

  1. Singleton init: CustomerManager is created once per Lambda instance and reused across invocations
  2. API key extraction: requestContext.identity.apiKeyId from API Gateway event
  3. Customer lookup: APIKEY#CUSTOMER# via DynamoDB (cached in-memory after first lookup)
  4. Governance evaluation: pcw_decide() runs independently — customer lookup cannot affect it
  5. Usage recording: Atomic DynamoDB counter increment (fire-and-forget)
  6. 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 boto3

Or 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-customers

Anonymous 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-customers

If 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)

PhaseScopeStatus
Phase 2: Self-ServiceCustomer portal, Unkey API keys, usage dashboards, rate limitingProposed
Phase 3: MonetizationStripe Billing, quota enforcement, tiered pricingProposed

See ADR-008 for the full strategy.


References

On this page