# `/v1` API Documentation ## Overview The `/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. ## Authentication Every request to `/v1` must include an `Authorization` header with a valid Bearer access token: ``` Authorization: Bearer ``` ### Obtaining a Token Tokens are issued by the local OAuth 2.1 authorization server. The full flow is: 1. **Register a client** ```bash curl -X POST http://localhost:3000/oauth/register \ -H "Content-Type: application/json" \ -d '{"client_name":"my-app","redirect_uris":["http://localhost/callback"]}' ``` 2. **Login** (sets session cookie) ```bash curl -X POST http://localhost:3000/oauth/login \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "email=admin@delete.me&password=deleteme" ``` 3. **Authorize** (user consent) ```bash curl -G http://localhost:3000/oauth/authorize \ -b cookies.txt \ -d "response_type=code" \ -d "client_id=" \ -d "redirect_uri=http://localhost/callback" \ -d "scope=llm" \ -d "code_challenge=" \ -d "code_challenge_method=S256" ``` 4. **Consent** ```bash curl -X POST http://localhost:3000/oauth/consent \ -b cookies.txt \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=&consent=true" ``` 5. **Token exchange** ```bash curl -X POST http://localhost:3000/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=authorization_code" \ -d "code=" \ -d "redirect_uri=http://localhost/callback" \ -d "client_id=" \ -d "code_verifier=" ``` The response contains `access_token` which is used for all `/v1` requests. Tokens are stored in Redis and validated on every request. ## Endpoints ### `POST /v1/chat/completions` Creates a chat completion for the provided messages using the specified model. #### Request Headers | Header | Required | Description | |--------|----------|-------------| | `Authorization` | Yes | `Bearer ` | | `Content-Type` | Yes | `application/json` | #### Request Body | 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) | #### Example Request (Non-streaming) ```bash curl -X POST http://localhost:3000/v1/chat/completions \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "model": "bedrock/claude-3-sonnet", "messages": [{"role": "user", "content": "Hello!"}], "temperature": 0.7, "max_tokens": 256 }' ``` #### Example Request (Streaming) ```bash curl -X POST http://localhost:3000/v1/chat/completions \ -H "Authorization: Bearer " \ -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]`. #### Response Format (Non-streaming) ```json { "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/models` Lists all available models across enabled providers. #### Request Headers | Header | Required | Description | |--------|----------|-------------| | `Authorization` | Yes | `Bearer ` | #### Example Request ```bash curl http://localhost:3000/v1/models \ -H "Authorization: Bearer " ``` #### Response Format ```json { "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. #### Request Headers | Header | Required | Description | |--------|----------|-------------| | `Authorization` | Yes | `Bearer ` | #### URL Parameter | Parameter | Description | |-----------|-------------| | `model` | Full model ID. **Must URL-encode the slash** (e.g., `bedrock%2Fclaude-3-sonnet`) | #### Example Request ```bash curl http://localhost:3000/v1/models/bedrock%2Fclaude-3-sonnet \ -H "Authorization: Bearer " ``` #### Response Format ```json { "id": "bedrock/claude-3-sonnet", "object": "model", "created": 1783452819, "owned_by": "bedrock" } ``` #### Important Note on Model IDs 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-sonnet` - ❌ `http://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. ## Provider Configuration Providers are configured in `config.json` under the `providers` array. Only providers with `enabled: true` are exposed through the API. ### Example `config.json` ```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" } } ] } ``` ### Provider Types | Type | Description | Required Config | |------|-------------|-----------------| | `bedrock` | AWS Bedrock | `region`, `credentials.accessKeyId`, `credentials.secretAccessKey` | | `anthropic` | Anthropic API | `apiKey` | | `openai-compatible` | Generic OpenAI-compatible endpoint | `baseUrl` | ### Model ID Format The `models` object maps a **local model key** to the provider's native model identifier: ```json "models": { "claude-3-sonnet": "anthropic.claude-3-sonnet-20240229-v1:0" } ``` This model is then addressed via the API as `bedrock/claude-3-sonnet`. ## Error Format All errors are returned in OpenAI-compatible JSON format: ```json { "error": { "message": "Human-readable error description", "type": "invalid_request_error", "param": null, "code": "invalid_model" } } ``` ### Error Types | `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 | ### Common HTTP Status Codes | 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 | ## QA Evidence The following integration tests were executed against `http://localhost:3000` with a valid Bearer token seeded in Redis. ### Error Case Tests | # | 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` | ✅ | ### Positive Tests | # | 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) | ### Server Startup - `npm run dev` started successfully on port 3000 - No new errors in startup logs - Redis connected successfully ## Related Documentation - [OAuth 2.1 Synthesis](./OAUTH-2.1-SYNTHESIS.md) — Authorization server metadata and security requirements - [Project README](../README.md) — Setup, configuration, and deployment