|
| 1 | +# Model-as-a-Service (MaaS) |
| 2 | + |
| 3 | +The MaaS module provides LLM inference usage metering for an inference-as-a-service backend. Users create API keys via the web UI, and an inference backend authenticates each request with a user's API key, validates it, and reports usage via a REST API. |
| 4 | + |
| 5 | +## Screenshots |
| 6 | + |
| 7 | +### User page |
| 8 | +Shows API keys, usage summary, and recent usage records for a user. |
| 9 | + |
| 10 | +### API key creation |
| 11 | +API keys are shown in plaintext only once at creation time with a copy-to-clipboard button. |
| 12 | + |
| 13 | +### Provider management |
| 14 | +Staff can manage inference providers, each with a secret key used to associate usage with the correct provider. |
| 15 | + |
| 16 | +## Features |
| 17 | + |
| 18 | +- **API key management**: Create, view, and revoke API keys with optional expiration dates |
| 19 | +- **Provider management**: Admin-managed providers with secret keys |
| 20 | +- **Usage recording**: Record inference usage with tokens, cost, latency, and model info |
| 21 | +- **Key validation**: Pre-inference endpoint to verify both user key and provider |
| 22 | +- **Show-once keys**: API keys are displayed only once at creation |
| 23 | +- **User isolation**: Users can only see their own keys; staff/admin can browse any user |
| 24 | + |
| 25 | +## How It Works |
| 26 | + |
| 27 | +``` |
| 28 | +End User (Bearer key) → Inference Backend → Portal API |
| 29 | + ├── Verify key + provider |
| 30 | + └── POST usage record |
| 31 | +``` |
| 32 | + |
| 33 | +The inference backend receives the user's Bearer token, forwards it to the portal for validation, and includes the provider's secret key to associate the usage with the correct provider. |
| 34 | + |
| 35 | +--- |
| 36 | + |
| 37 | +## API Reference |
| 38 | + |
| 39 | +All API endpoints return JSON. Authentication uses `Authorization: Bearer <api_key>`. |
| 40 | + |
| 41 | +### Verify Key + Provider |
| 42 | + |
| 43 | +Validate a user's API key and provider before running inference. |
| 44 | + |
| 45 | +**POST** `/api/maas/verify/` |
| 46 | + |
| 47 | +**Headers:** |
| 48 | +- `Authorization: Bearer <user_api_key>` |
| 49 | + |
| 50 | +**Request body:** |
| 51 | +```json |
| 52 | +{ |
| 53 | + "provider_key": "<provider-secret-key>" |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +**Response 200:** |
| 58 | +```json |
| 59 | +{ |
| 60 | + "valid": true, |
| 61 | + "user": "username", |
| 62 | + "expires_at": "2026-12-31T23:59:59Z", |
| 63 | + "provider": { |
| 64 | + "id": 1, |
| 65 | + "name": "OpenAI Production", |
| 66 | + "url": "https://inference.example.com" |
| 67 | + } |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +**Response 401** (invalid/expired user key): |
| 72 | +```json |
| 73 | +{ "detail": "Invalid API key" } |
| 74 | +``` |
| 75 | + |
| 76 | +**Response 400** (invalid provider key): |
| 77 | +```json |
| 78 | +{ "error": "Invalid provider key" } |
| 79 | +``` |
| 80 | + |
| 81 | +**cURL:** |
| 82 | +```bash |
| 83 | +curl -X POST http://localhost/api/maas/verify/ \ |
| 84 | + -H "Authorization: Bearer <user-api-key-value>" \ |
| 85 | + -H "Content-Type: application/json" \ |
| 86 | + -d '{"provider_key": "<provider-secret-key>"}' |
| 87 | +``` |
| 88 | + |
| 89 | +### Record Usage |
| 90 | + |
| 91 | +Record the usage for a completed inference request. |
| 92 | + |
| 93 | +**POST** `/api/maas/usage/` |
| 94 | + |
| 95 | +**Headers:** |
| 96 | +- `Authorization: Bearer <user_api_key>` |
| 97 | + |
| 98 | +**Request body:** |
| 99 | +```json |
| 100 | +{ |
| 101 | + "provider_key": "<provider-secret-key>", |
| 102 | + "model": "gpt-4o", |
| 103 | + "endpoint": "/v1/chat/completions", |
| 104 | + "input_tokens": 150, |
| 105 | + "output_tokens": 320, |
| 106 | + "cost": 0.00475, |
| 107 | + "latency_ms": 450, |
| 108 | + "request_id": "550e8400-e29b-41d4-a716-446655440000" |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +| Field | Required | Type | Description | |
| 113 | +|-------|----------|------|-------------| |
| 114 | +| `provider_key` | Yes | string | Secret key identifying the provider | |
| 115 | +| `model` | Yes | string | Model name (e.g., `gpt-4o`, `claude-3`) | |
| 116 | +| `endpoint` | Yes | string | API endpoint used (e.g., `/v1/chat/completions`) | |
| 117 | +| `input_tokens` | Yes | integer | Number of input tokens consumed | |
| 118 | +| `output_tokens` | Yes | integer | Number of output tokens generated | |
| 119 | +| `cost` | No | number | Cost of the request (if applicable) | |
| 120 | +| `latency_ms` | Yes | integer | Response time in milliseconds | |
| 121 | +| `request_id` | No | string | Unique request identifier (auto-generated if omitted) | |
| 122 | + |
| 123 | +**Response 201:** |
| 124 | +```json |
| 125 | +{ |
| 126 | + "id": 1, |
| 127 | + "user": "username", |
| 128 | + "provider_name": "OpenAI Production", |
| 129 | + "request_id": "550e8400-e29b-41d4-a716-446655440000", |
| 130 | + "model": "gpt-4o", |
| 131 | + "endpoint": "/v1/chat/completions", |
| 132 | + "input_tokens": 150, |
| 133 | + "output_tokens": 320, |
| 134 | + "cost": 0.00475, |
| 135 | + "latency_ms": 450, |
| 136 | + "created_at": "2026-05-16T12:00:00Z" |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +**cURL:** |
| 141 | +```bash |
| 142 | +curl -X POST http://localhost/api/maas/usage/ \ |
| 143 | + -H "Authorization: Bearer <user-api-key-value>" \ |
| 144 | + -H "Content-Type: application/json" \ |
| 145 | + -d '{ |
| 146 | + "provider_key": "<provider-secret-key>", |
| 147 | + "model": "gpt-4o", |
| 148 | + "endpoint": "/v1/chat/completions", |
| 149 | + "input_tokens": 150, |
| 150 | + "output_tokens": 320, |
| 151 | + "cost": 0.00475, |
| 152 | + "latency_ms": 450 |
| 153 | + }' |
| 154 | +``` |
| 155 | + |
| 156 | +### Query Usage |
| 157 | + |
| 158 | +Retrieve usage records for the authenticated user. |
| 159 | + |
| 160 | +**GET** `/api/maas/usage/` |
| 161 | + |
| 162 | +**Headers:** |
| 163 | +- `Authorization: Bearer <user_api_key>` |
| 164 | + |
| 165 | +**Query parameters:** |
| 166 | +| Parameter | Description | |
| 167 | +|-----------|-------------| |
| 168 | +| `model` | Filter by model name | |
| 169 | +| `start` | Filter by start date (ISO format) | |
| 170 | +| `end` | Filter by end date (ISO format) | |
| 171 | + |
| 172 | +**Response 200:** |
| 173 | +```json |
| 174 | +[ |
| 175 | + { |
| 176 | + "id": 1, |
| 177 | + "user": "username", |
| 178 | + "provider_name": "OpenAI Production", |
| 179 | + "request_id": "550e8400-...", |
| 180 | + "model": "gpt-4o", |
| 181 | + "endpoint": "/v1/chat/completions", |
| 182 | + "input_tokens": 150, |
| 183 | + "output_tokens": 320, |
| 184 | + "cost": 0.00475, |
| 185 | + "latency_ms": 450, |
| 186 | + "created_at": "2026-05-16T12:00:00Z" |
| 187 | + } |
| 188 | +] |
| 189 | +``` |
| 190 | + |
| 191 | +**cURL:** |
| 192 | +```bash |
| 193 | +curl -H "Authorization: Bearer <user-api-key-value>" \ |
| 194 | + "http://localhost/api/maas/usage/?model=gpt-4o&start=2026-05-01" |
| 195 | +``` |
| 196 | + |
| 197 | +### Revoke Key (Public) |
| 198 | + |
| 199 | +**POST** `/api/maas/key/revoke/` |
| 200 | + |
| 201 | +A public endpoint that revokes a key by presenting it in the auth header. Always returns `200` — never reveals whether the key existed — to prevent key enumeration. |
| 202 | + |
| 203 | +**Headers:** |
| 204 | +- `Authorization: Bearer <key_to_revoke>` |
| 205 | + |
| 206 | +**Response 200:** |
| 207 | +```json |
| 208 | +{} |
| 209 | +``` |
| 210 | + |
| 211 | +**cURL:** |
| 212 | +```bash |
| 213 | +curl -X POST -H "Authorization: Bearer <key-to-revoke>" \ |
| 214 | + http://localhost/api/maas/key/revoke/ |
| 215 | +``` |
| 216 | + |
| 217 | +## Requirements |
| 218 | + |
| 219 | +No external dependencies beyond Django REST Framework (already included in the project). |
0 commit comments