API Gateway
Edge routing, authentication, purchases, voice proxy, and service orchestration.
The api-gateway is the singular ingress point into the AnyaSelf cluster. It handles external authentication, proxies requests to downstream services, manages the purchase approval flow, and bridges Gemini Live WebSocket connections for voice.
Architecture
graph LR
CLIENT["Client"] --> GW["API Gateway :8080"]
GW --> AUTH["JWT + OIDC"]
GW --> ORCH["orchestrator :8003"]
GW --> WARD["wardrobe :8081"]
GW --> VTO_S["vto :8004"]
GW --> HB["hyperbeam :8006"]
GW --> AA["artifacts-audit :8007"]
GW --> GEMINI["Gemini Live WS"]
GW --> UNSPLASH["Unsplash API"]Endpoints
Authentication & Identity
| Method | Path | Description |
|---|---|---|
POST | /api/v1/auth/login | Verify external OIDC idToken and mint internal bearer token |
GET | /api/v1/me | Return authenticated principal (userId, email) |
Login Response:
{
"accessToken": "eyJhbG...",
"tokenType": "Bearer",
"expiresInSeconds": 3600,
"user": {
"userId": "usr_abc123",
"email": "user@example.com"
}
}Me Response:
{
"userId": "usr_abc123",
"email": "user@example.com"
}Public Discover Feed
| Method | Path | Description |
|---|---|---|
GET | /api/v1/discover/feed | Unsplash-backed public fashion inspiration feed. Query params: page, perPage, category |
Feed Response:
{
"items": [
{
"id": "unsplash_abc",
"title": "Spring Essentials",
"image": "https://images.unsplash.com/...",
"label": "SPRING",
"tone": "warm",
"photographerName": "Jane Doe",
"photographerLink": "https://unsplash.com/@janedoe",
"unsplashLink": "https://unsplash.com/photos/abc"
}
],
"page": 1,
"perPage": 24,
"hasMore": true,
"nextPage": 2
}Household Management
| Method | Path | Description |
|---|---|---|
POST | /api/v1/households | Create a new household |
GET | /api/v1/households | List households for the authenticated user |
GET | /api/v1/households/{id} | Get household detail with members and purchase requests |
POST | /api/v1/households/{id}/members | Add a member to the household |
DELETE | /api/v1/households/{id} | Delete household and all associated data |
Household Response:
{
"householdId": "h_abc123",
"name": "The Smiths",
"members": [
{
"memberId": "usr_abc123",
"role": "PARENT/GUARDIAN",
"displayName": "Alex",
"addedAt": "2026-03-08T10:00:00+00:00",
"addedBy": "usr_abc123"
}
],
"purchaseRequests": [],
"createdAt": "2026-03-08T10:00:00+00:00",
"updatedAt": "2026-03-08T10:00:00+00:00"
}Delete Response:
{
"householdId": "h_abc123",
"deleted": true,
"deletedMembersCount": 3,
"deletedPurchaseRequestsCount": 2,
"deletedAuditLogsCount": 15
}Purchase Request Flow
| Method | Path | Description |
|---|---|---|
POST | /api/v1/households/{id}/requests | Create a purchase request |
POST | /api/v1/households/{id}/requests/{reqId}/approve | Guardian approves or rejects |
POST | /api/v1/households/{id}/requests/{reqId}/purchase-intents | Create purchase intent with ephemeral confirmation token |
POST | /api/v1/households/{id}/requests/{reqId}/cart-ready | Mark cart prepared (internal, requires X-Internal-Token) |
POST | /api/v1/households/{id}/requests/{reqId}/failed | Mark request failed (internal, requires X-Internal-Token) |
POST | /api/v1/households/{id}/requests/{reqId}/confirm-checkout | Final checkout with intentId + confirmationToken |
Purchase Request Response:
{
"requestId": "req_xyz789",
"requestedByMemberId": "usr_abc123",
"forMemberId": "usr_child456",
"constraints": { "maxPrice": 200, "category": "SHOES" },
"status": "SUBMITTED",
"approvals": [
{
"byMemberId": "usr_abc123",
"decision": "APPROVE",
"at": "2026-03-08T11:00:00+00:00",
"note": "Looks good, go ahead"
}
],
"createdAt": "2026-03-08T10:00:00+00:00",
"updatedAt": "2026-03-08T11:00:00+00:00"
}For all purchase flow error responses, see the Error Reference → API Gateway.
Voice Live Proxy
| Method | Path | Description |
|---|---|---|
POST | /api/v1/voice/live/session | Mint authenticated WebSocket session token |
POST | /api/v1/voice/live/public-session | Mint guest WebSocket session token (rate-limited) |
WS | /api/v1/voice/live/ws?session={token} | Bidirectional audio bridge to Gemini Live |
Voice Session Response:
{
"websocketUrl": "wss://api.anyaself.com/api/v1/voice/live/ws?session=tok_...",
"model": "gemini-live-2.5-flash-native-audio",
"expiresInSeconds": 300
}The gateway proxies binary WebSocket frames between clients and Google Cloud's LlmBidiService/BidiGenerateContent endpoint, authenticating via GCP OAuth2 service account credentials.
Service Proxying
All downstream service calls are proxied through path-based routing:
| Path Pattern | Target Service |
|---|---|
/api/v1/households/{id}/wardrobe/... | wardrobe :8081 |
/api/v1/households/{id}/orchestrator/... | orchestrator :8003 |
/api/v1/households/{id}/vto/... | vto :8004 |
/api/v1/households/{id}/hyperbeam/... | hyperbeam-bridge :8006 |
Audit
| Method | Path | Description |
|---|---|---|
GET | /api/v1/households/{id}/audit-logs | Retrieve household audit log entries |
Audit Log Response:
{
"logId": "log_abc123",
"householdId": "h_abc123",
"actorUserId": "usr_abc123",
"action": "PURCHASE_REQUEST_CREATED",
"targetType": "PurchaseRequest",
"targetId": "req_xyz789",
"details": {},
"createdAt": "2026-03-08T10:00:00+00:00"
}Configuration
| Variable | Default | Description |
|---|---|---|
APP_ENV | dev | Runtime environment (dev, staging, prod) |
AUTH_JWT_SECRET | dev-secret-change-me | Shared JWT signing secret (rejected in prod) |
AUTH_JWT_ALG | HS256 | JWT algorithm |
AUTH_ACCESS_TOKEN_TTL_SECONDS | 3600 | Internal token lifetime |
AUTH_EXTERNAL_LOGIN_ENABLED | false | Enable OIDC external login flow |
AUTH_EXTERNAL_JWKS_URL | — | JWKS endpoint for external token verification |
AUTH_EXTERNAL_ISSUER | — | Expected token issuer |
AUTH_EXTERNAL_AUDIENCE | — | Expected token audience |
AUTH_EXTERNAL_ALGORITHMS | RS256 | External token algorithm |
AUTH_EXTERNAL_REQUIRE_EXP | true (staging/prod) | Require expiry claim |
CHECKOUT_CONFIRMATION_TTL_SECONDS | 900 | Purchase confirmation token TTL |
BUYFLOW_INTERNAL_TOKEN | dev-internal-token | Shared secret for internal buy-flow endpoints |
REQUIRE_BUYFLOW_INTERNAL_TOKEN | true (staging/prod) | Require X-Internal-Token for cart-ready/failed |
PERSISTENCE_BACKEND | inmemory | firestore or inmemory |
VOICE_LIVE_ENABLED | true (staging/prod) | Enable voice proxy |
VOICE_LIVE_API_KEY | — | Gemini API key for voice proxy |
VOICE_LIVE_MODEL | gemini-live-2.5-flash-native-audio | Voice model identifier |
VOICE_LIVE_SESSION_TTL_SECONDS | 300 | Authenticated voice session lifetime |
VOICE_LIVE_PUBLIC_SESSION_ENABLED | true | Enable guest voice sessions |
VOICE_LIVE_PUBLIC_SESSION_TTL_SECONDS | 300 | Guest session lifetime |
VOICE_LIVE_PUBLIC_RATE_LIMIT_WINDOW_SECONDS | 60 | Rate limit window |
VOICE_LIVE_PUBLIC_RATE_LIMIT_MAX_REQUESTS | 12 | Max public sessions per window |
UNSPLASH_ENABLED | auto | Enabled when UNSPLASH_ACCESS_KEY is set |
UNSPLASH_ACCESS_KEY | — | Unsplash API access key |
UNSPLASH_APP_NAME | anyaself | UTM attribution app name |
UNSPLASH_TIMEOUT_SECONDS | 6.0 | Unsplash API timeout |
UNSPLASH_CACHE_TTL_SECONDS | 900 | In-memory cache TTL for feed responses |
ARTIFACTS_AUDIT_BASE_URL | http://artifacts-audit:8007/api/v1 | Audit service URL |
ARTIFACTS_AUDIT_ENABLED | true | Enable audit log writes |
ARTIFACTS_AUDIT_REQUIRED | false | Fail if audit service is unreachable |