An OpenAI v1 API-compatible LLM proxy that routes requests to AWS Bedrock, enforces per-user monthly spending limits, and uses OAuth 2.1 + optional legacy Bearer tokens for authentication.
Client (OpenAI SDK)
↓
[POST /v1/chat/completions with Bearer token]
↓
[Auth Middleware] → Detect token type (JWT vs opaque)
↓
[OAuth Validator OR Introspection]
↓
[Spending Check] → Estimate cost, verify budget
↓
[AWS Bedrock InvokeModel]
↓
[Atomic Deduction] → PostgreSQL transaction
↓
[OpenAI-formatted Response]
Config File: /run/secrets/bedrock-proxy-config.json (mounted as Docker secret)
{
"oauth": {
"discovery_url": "https://data.titleproject.space/.well-known/oauth-authorization-server"
},
"bedrock": {
"aws_region": "us-east-1",
"aws_access_key_id": "...",
"aws_secret_access_key": "..."
},
"database": {
"url": "postgresql://user:pass@db:5432/bedrock_proxy"
},
"spending": {
"default_limit_usd": null
}
}
That's it! OAuth metadata auto-fetches from discovery URL. Zero manual configuration.
curl -X POST http://localhost:3000/api/v1/chat/completions \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-d '{"model":"claude-3-haiku-20240307","messages":[...]}'
curl -X POST http://localhost:3000/api/v1/chat/completions \
-H "Authorization: Bearer abc123def456ghi..." \
-d '{"model":"claude-3-haiku-20240307","messages":[...]}'
Detection: Automatic. If token has 3 dots → JWT. Otherwise → opaque Bearer.
{
"model": "claude-3-haiku-20240307",
"messages": [
{"role": "user", "content": "Hello"}
],
"stream": false
}
Returns list of supported Bedrock models with native names.
-- 3 core tables
users (user_id, email, monthly_limit_usd, current_spend_usd, spend_reset_at)
spending_records (user_id, model_id, input_tokens, output_tokens, cost_usd)
bedrock_models (model_id, provider, input_price_usd, output_price_usd)
Working curl endpoint:
curl -X POST http://localhost:3000/api/v1/chat/completions \
-H "Authorization: Bearer <JWT_or_Bearer_token>" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-3-haiku-20240307",
"messages": [{"role": "user", "content": "Hello"}]
}'
# Response
{
"id": "bedrock-1704067200000",
"object": "chat.completion",
"model": "claude-3-haiku-20240307",
"choices": [{
"message": {"content": "Hello! How can I help?"},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 8,
"total_tokens": 18
}
}
| File | Purpose |
|---|---|
src/lib/config/loader.js |
Load config, fetch OAuth discovery |
src/lib/auth/middleware.js |
Token detection + validation |
src/hooks.server.js |
SvelteKit auth integration |
src/lib/bedrock/client.js |
AWS SDK wrapper |
src/lib/spending/enforcer.js |
Cost estimation + atomic deduction |
src/routes/api/v1/chat/completions/+server.js |
Main endpoint |
src/routes/api/v1/models/+server.js |
Model list endpoint |
Before coding:
{
"error": {
"code": "authentication_error",
"message": "Invalid or expired token",
"type": "authentication_error"
}
}
Common codes:
authentication_error (401): Bad/missing tokeninvalid_request_error (400): Bad parametersinsufficient_credits (429): Budget exceededmodel_not_found_error (404): Unsupported modelserver_error (500): Bedrock error