/v1 API DocumentationThe /v1 API provides an OpenAI-compatible REST interface for chat completions and model management. It is designed to be a drop-in replacement for the OpenAI API, proxying requests to configured LLM providers (AWS Bedrock, Anthropic, OpenAI-compatible endpoints).
All /v1 endpoints require Bearer token authentication obtained through the OAuth 2.1 authorization flow.
Every request to /v1 must include an Authorization header with a valid Bearer access token:
Authorization: Bearer <access_token>
Tokens are issued by the local OAuth 2.1 authorization server. The full flow is:
Register a client
curl -X POST http://localhost:3000/oauth/register \
-H "Content-Type: application/json" \
-d '{"client_name":"my-app","redirect_uris":["http://localhost/callback"]}'
Login (sets session cookie)
curl -X POST http://localhost:3000/oauth/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "email=admin@delete.me&password=deleteme"
Authorize (user consent)
curl -G http://localhost:3000/oauth/authorize \
-b cookies.txt \
-d "response_type=code" \
-d "client_id=<client_id>" \
-d "redirect_uri=http://localhost/callback" \
-d "scope=llm" \
-d "code_challenge=<pkce_challenge>" \
-d "code_challenge_method=S256"
Consent
curl -X POST http://localhost:3000/oauth/consent \
-b cookies.txt \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=<client_id>&consent=true"
Token exchange
curl -X POST http://localhost:3000/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=<auth_code>" \
-d "redirect_uri=http://localhost/callback" \
-d "client_id=<client_id>" \
-d "code_verifier=<pkce_verifier>"
The response contains access_token which is used for all /v1 requests. Tokens are stored in Redis and validated on every request.
POST /v1/chat/completionsCreates a chat completion for the provided messages using the specified model.
| Header | Required | Description |
|---|---|---|
Authorization |
Yes | Bearer <access_token> |
Content-Type |
Yes | application/json |
| Field | Type | Required | Description |
|---|---|---|---|
model |
string | Yes | Model ID in provider/localModelId format (e.g., bedrock/claude-3-sonnet) |
messages |
array | Yes | Array of message objects {role, content} |
stream |
boolean | No | If true, returns a Server-Sent Events stream |
temperature |
number | No | Sampling temperature (0-2) |
max_tokens |
integer | No | Maximum tokens to generate |
top_p |
number | No | Nucleus sampling parameter |
stop |
string/array | No | Stop sequence(s) |
curl -X POST http://localhost:3000/v1/chat/completions \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"model": "bedrock/claude-3-sonnet",
"messages": [{"role": "user", "content": "Hello!"}],
"temperature": 0.7,
"max_tokens": 256
}'
curl -X POST http://localhost:3000/v1/chat/completions \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-3-5-sonnet",
"messages": [{"role": "user", "content": "Hello!"}],
"stream": true
}'
When stream: true, the response is a text/event-stream where each chunk is a JSON object prefixed with data:. The stream ends with data: [DONE].
{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1234567890,
"model": "bedrock/claude-3-sonnet",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello! How can I help you today?"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 20,
"total_tokens": 30
}
}
The exact response shape depends on the underlying provider; the proxy passes through the provider's response after normalizing errors.
GET /v1/modelsLists all available models across enabled providers.
| Header | Required | Description |
|---|---|---|
Authorization |
Yes | Bearer <access_token> |
curl http://localhost:3000/v1/models \
-H "Authorization: Bearer <token>"
{
"object": "list",
"data": [
{
"id": "bedrock/claude-3-sonnet",
"object": "model",
"created": 1783452810,
"owned_by": "bedrock"
},
{
"id": "anthropic/claude-3-5-sonnet",
"object": "model",
"created": 1783452810,
"owned_by": "anthropic"
}
]
}
GET /v1/models/{model}Retrieves details for a single model.
| Header | Required | Description |
|---|---|---|
Authorization |
Yes | Bearer <access_token> |
| Parameter | Description |
|---|---|
model |
Full model ID. Must URL-encode the slash (e.g., bedrock%2Fclaude-3-sonnet) |
curl http://localhost:3000/v1/models/bedrock%2Fclaude-3-sonnet \
-H "Authorization: Bearer <token>"
{
"id": "bedrock/claude-3-sonnet",
"object": "model",
"created": 1783452819,
"owned_by": "bedrock"
}
Model IDs use the format provider/localModelId and contain a slash. Because SvelteKit route parameters match a single path segment, the slash must be URL-encoded (%2F) in the request URL:
http://localhost:3000/v1/models/bedrock%2Fclaude-3-sonnethttp://localhost:3000/v1/models/bedrock/claude-3-sonnet (will 404)The model IDs returned by GET /v1/models are the raw IDs; clients must encode them before use in the detail endpoint.
Providers are configured in config.json under the providers array. Only providers with enabled: true are exposed through the API.
config.json{
"providers": [
{
"id": "bedrock",
"type": "bedrock",
"enabled": true,
"region": "us-east-1",
"credentials": {
"accessKeyId": "YOUR_ACCESS_KEY",
"secretAccessKey": "YOUR_SECRET_KEY"
},
"models": {
"claude-3-sonnet": "anthropic.claude-3-sonnet-20240229-v1:0"
}
},
{
"id": "anthropic",
"type": "anthropic",
"enabled": true,
"apiKey": "YOUR_ANTHROPIC_API_KEY",
"models": {
"claude-3-5-sonnet": "claude-3-5-sonnet-20241022"
}
},
{
"id": "local",
"type": "openai-compatible",
"enabled": false,
"baseUrl": "http://localhost:11434/v1",
"models": {
"llama3.1": "llama3.1:latest"
}
}
]
}
| Type | Description | Required Config |
|---|---|---|
bedrock |
AWS Bedrock | region, credentials.accessKeyId, credentials.secretAccessKey |
anthropic |
Anthropic API | apiKey |
openai-compatible |
Generic OpenAI-compatible endpoint | baseUrl |
The models object maps a local model key to the provider's native model identifier:
"models": {
"claude-3-sonnet": "anthropic.claude-3-sonnet-20240229-v1:0"
}
This model is then addressed via the API as bedrock/claude-3-sonnet.
All errors are returned in OpenAI-compatible JSON format:
{
"error": {
"message": "Human-readable error description",
"type": "invalid_request_error",
"param": null,
"code": "invalid_model"
}
}
type |
Meaning |
|---|---|
invalid_request_error |
Bad request (missing params, invalid model, malformed JSON) |
invalid_token |
Authentication failed (bad or expired token) |
insufficient_scope |
Token valid but missing required scope |
authentication_error |
Provider rejected credentials |
rate_limit_error |
Rate limit hit |
api_error |
Provider or internal server error |
| Status | Scenario |
|---|---|
200 |
Success |
400 |
Invalid request (bad JSON, missing model, missing messages, unknown model) |
401 |
Missing or invalid Bearer token |
403 |
Valid token but insufficient scope |
404 |
Model not found |
429 |
Rate limited by provider |
500 |
Internal server error (Redis failure, provider init failure) |
502 |
Provider service error (connection issue, bad credentials, etc.) |
503 |
Provider overloaded |
The following integration tests were executed against http://localhost:3000 with a valid Bearer token seeded in Redis.
| # | Test | Expected | Actual | Result |
|---|---|---|---|---|
| 1 | Missing auth header on /v1/models |
401 |
401 |
✅ |
| 2 | Invalid token on /v1/models |
401 |
401 |
✅ |
| 3 | Invalid model in chat completions | 400 |
400 |
✅ |
| 4 | Missing model field |
400 |
400 |
✅ |
| 5 | Missing messages field |
400 |
400 |
✅ |
| 6 | Invalid JSON body | 400 |
400 |
✅ |
| # | Test | Status | Result |
|---|---|---|---|
| 7 | List models with valid token | 200 |
✅ |
| 8 | Get model details (URL-encoded slash) | 200 |
✅ |
| 9 | Get model details (unknown model) | 404 |
✅ |
| 10 | Chat completions with valid request | 502 |
✅ (provider rejects placeholder credentials; error correctly translated to OpenAI format) |
npm run dev started successfully on port 3000