-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathopenapi.yaml
More file actions
567 lines (485 loc) · 21.5 KB
/
openapi.yaml
File metadata and controls
567 lines (485 loc) · 21.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
openapi: 3.1.0
info:
title: Plexus API
version: '0'
summary: >-
Unified gateway for LLM provider APIs with usage tracking, quotas,
cooldowns, and an admin management plane.
description: >-
Plexus is a unified API gateway for LLM providers. It exposes
OpenAI-, Anthropic-, and Gemini-compatible endpoints and routes
requests to any backend provider, handling request/response
transformation automatically.
## Surfaces
Plexus exposes three logical surfaces:
1. **Inference APIs** (`/v1/*`, `/v1beta/*`) — OpenAI-, Anthropic-, and
Gemini-compatible endpoints that transparently fan out to configured
upstream providers. Authenticated with a Plexus API key (see the
[`PlexusApiKey`](#/components/securitySchemes/PlexusApiKey) security
scheme).
2. **Management APIs** (`/v0/management/*`) — Administrative operations
(configuration CRUD, usage analytics, debug tracing, quotas, OAuth
onboarding, Prometheus metrics, etc.). Authenticated with the
`x-admin-key` header. The admin header may be either the server's
`ADMIN_KEY` (full access) or the plaintext secret of a configured
API key (limited access, scoped to that key's own data). Endpoints
marked with the `AdminOnly` note reject limited principals with 403.
3. **MCP Proxy** (`/mcp/:name`) — Proxies JSON-RPC and SSE traffic to
database-registered MCP servers. Plus unauthenticated MCP OAuth
discovery under `/.well-known/*` and `POST /register`.
## Authentication
### Inference (`PlexusApiKey`)
A Plexus API key is accepted from any of the following, checked in order:
| Location | Example |
|---|---|
| `Authorization` header (Bearer) | `Authorization: Bearer sk-my-key` |
| `Authorization` header (no prefix) | `Authorization: sk-my-key` (server prepends `Bearer`) |
| `x-api-key` header | `x-api-key: sk-my-key` |
| `x-goog-api-key` header | `x-goog-api-key: sk-my-key` |
| `?key=` query parameter | `GET /v1/models?key=sk-my-key` |
Keys are matched against the `secret` field of entries in the `keys`
configuration table.
### Management (`AdminKey`)
Pass the header `x-admin-key: <value>`. A successful match with
`process.env.ADMIN_KEY` produces an `admin` principal. Any other value
is checked against `keys[*].secret` — a match yields a `limited`
principal scoped to its own key's usage/debug/error records. Limited
principals are rejected (403) from endpoints documented as admin-only.
Use `GET /v0/management/auth/verify` to test a candidate admin/key
credential before storing it in the dashboard.
## Dynamic key attribution
Append `:label` to a Plexus API key (e.g. `sk-abc:copilot`) to tag
every resulting usage record with an `attribution` label. The label
surfaces in the `UsageRecord.attribution` field and in
`api_key_attribution_*` Prometheus metrics.
## Principal model
The `x-admin-key` header resolves to one of two principal types:
- **`admin`** — full access to every management endpoint. Matched when
the header equals `process.env.ADMIN_KEY`.
- **`limited`** — scoped to the key's own usage, debug, and error
records. Matched when the header equals a configured key's `secret`.
Limited principals are rejected (403) from admin-only endpoints.
On `/v0/management/self/*` routes an `admin` principal can impersonate
any key by passing `?keyName=<name>`.
## Header forwarding
The following client headers, when present, are forwarded to upstream
providers:
- `x-app` — application identifier. Echoed into
`plexus_metadata.clientHeaders` on the usage record (all inference
routes).
- `session_id` — client session identifier. Forwarded as an HTTP
header for prompt cache routing (`/v1/responses` only).
- `x-client-request-id` — client-supplied request correlation ID.
Forwarded as an HTTP header for prompt cache routing
(`/v1/responses` only).
## Environment variables
Key environment variables that affect runtime behaviour:
| Variable | Purpose |
|---|---|
| `ADMIN_KEY` | Master admin key for management endpoints. |
| `DATA_DIR` | Directory for SQLite database and data files. |
| `LOG_LEVEL` | Runtime log level (`error`, `warn`, `info`, `debug`, `verbose`, `silly`). |
| `DEBUG` | Set to `true` to enable debug log capture at startup. |
| `PLEXUS_METRICS_CACHE_TTL_MS` | TTL for the Prometheus metrics cache in milliseconds. |
| `DATABASE_URL` | Connection string for PostgreSQL (omit for SQLite). |
| `ENCRYPTION_KEY` | Key used to encrypt secrets at rest. |
See `docs/CONFIGURATION.md` for the full list and configuration file
format.
## Public endpoints (no auth)
- `GET /health`
- `GET /v1/models`
- `GET /v1/openrouter/models`
- `GET /v1/metadata/search`
- `GET /v1/metadata/lookup`
- All `/.well-known/*` MCP discovery endpoints
- `POST /register` (MCP dynamic client registration)
license:
name: MIT
identifier: MIT
servers:
- url: http://localhost:4000
description: Local dev server (default port)
- url: '{protocol}://{host}'
description: Custom deployment
variables:
protocol:
default: https
enum:
- http
- https
host:
default: plexus.example.com
tags:
- name: Health
description: Liveness probe.
- name: Inference — Chat
description: >-
OpenAI Chat Completions compatible.
Requests are dispatched through the transformer pipeline: the `model`
field is resolved against configured aliases, the request is
transformed to the target provider format, the upstream call is made,
and the response is transformed back.
When an upstream provider fails, Plexus retries across alternate
targets per the failover policy. Providers that return consecutive
errors are placed on exponential-backoff cooldown (see the
`Management — Cooldowns` tag).
- name: Inference — Messages
description: Anthropic Messages compatible.
- name: Inference — Responses
description: OpenAI Responses API compatible (stateful, tool-using).
- name: Inference — Gemini
description: Google Gemini `generateContent` / `streamGenerateContent` compatible.
- name: Inference — Embeddings
description: OpenAI Embeddings compatible.
- name: Inference — Audio
description: OpenAI Audio speech and transcription compatible.
- name: Inference — Images
description: OpenAI Images generation and edit compatible.
- name: Inference — Models
description: Model and catalog discovery.
- name: Management — Auth
description: Admin-key verification and self-service identity.
- name: Management — Config
description: Providers, aliases, keys, system settings, MCP servers.
- name: Management — Usage
description: >-
Request-level usage records and time-series aggregates.
### Cost source semantics
| `costSource` | Meaning |
|---|---|
| `default` | No pricing configured; all cost fields are zero. |
| `simple` | Per-token pricing from `input_price_per_million` / `output_price_per_million` on the model. |
| `openrouter` | Pricing fetched from OpenRouter at request time. |
| `defined` | Explicit pricing ranges from `pricing.range` in model config. |
| `per_request` | Flat fee per call; full amount in `costInput`, others zero. |
- name: Management — Debug
description: Per-request debug capture toggles and logs.
- name: Management — Errors
description: Inference error log storage.
- name: Management — MCP Logs
description: Usage records for the MCP proxy.
- name: Management — Cooldowns
description: Provider/model cooldown state.
- name: Management — Performance
description: TTFT / throughput aggregates per (provider, model).
- name: Management — Metrics
description: Prometheus scrape endpoint.
- name: Management — Logging
description: Runtime log-level control.
- name: Management — OAuth
description: >-
Per-provider OAuth onboarding sessions (pi-ai).
Sessions follow a state machine:
```
waiting → prompt ↔ manual-code → complete
\ |
→ cancelled ←
→ failed
```
- **waiting** — session created, waiting for user to open the auth URL.
- **prompt** — provider requires a user confirmation (e.g. "yes").
- **manual-code** — provider requires a manually pasted auth code
(e.g. Codex CLI, Claude Code).
- **complete** — credentials stored, provider ready.
- **failed** — authentication error.
- **cancelled** — user cancelled the session.
Providers that use the callback server (`usesCallbackServer: true`)
transition automatically; others require manual code paste.
- name: Management — Quotas (Provider)
description: >-
Upstream provider quota checkers (`checkerId`-based).
Each checker periodically polls an upstream provider for its current
quota utilisation and stores a `QuotaSample` snapshot.
### Window types
| Window Type | Description |
|---|---|
| `subscription` | Monthly/billing-cycle quota or prepaid balance. |
| `hourly` | Hourly rolling window. |
| `five_hour` | 5-hour rolling window (common across multiple providers). |
| `daily` | Daily reset quota. |
| `weekly` | 7-day rolling window (common across multiple providers). |
| `monthly` | Calendar month quota. |
| `custom` | Provider-specific window. |
### Status thresholds
| Status | Utilisation | Description |
|---|---|---|
| `ok` | 0–75% | Healthy, plenty of quota remaining. |
| `warning` | 75–90% | Approaching exhaustion. |
| `critical` | 90–100% | Near exhaustion, take action soon. |
| `exhausted` | 100% | Quota fully consumed, requests will fail. |
- name: Management — Quotas (User)
description: >-
Per-API-key quota definitions and enforcement.
### Definition lifecycle
1. Create a quota definition with `PUT /v0/management/user-quotas/{name}`
2. Assign it to keys via `keys[*].quota` in config or `PATCH /keys/{name}`
3. The enforcement middleware checks requests against the assigned quota
### Assignment vs enforcement separation
- **Assignment**: A quota definition is linked to a key via the key's
`quota` field or via the keys API.
- **Enforcement**: The middleware evaluates the key's usage against its
assigned quota definition at request time. If `quota_name` is null,
the key has no quota and is unlimited.
### Why 200 for "no quota"
`GET /v0/management/quota/status/{key}` returns 200 (not 404) even when
no quota is assigned. This is intentional — the UI needs to render a
"no quota" state (unlimited) rather than an error state.
- name: Management — Test
description: Synthetic inference request used by the admin UI.
- name: Management — Restart
description: Graceful process restart.
- name: Management — Backup
description: >-
Full database export and import for disaster recovery and migration.
Two backup modes are available:
- **Config-only** (default): Exports providers, model aliases, API keys,
user quotas, MCP servers, system settings, and OAuth credentials as a
JSON envelope. Small and fast — covers most migration and cloning needs.
- **Full backup** (`?full=true`): Exports configuration plus all
operational/telemetry data (usage records, debug logs, errors, cooldowns,
performance stats, quota snapshots, responses) as a gzipped tar archive
with per-table compressed CSV files.
Both endpoints are admin-only. Backup files contain decrypted secrets
(API keys, OAuth tokens) and should be stored securely.
- name: Management — Models
description: External model metadata helpers (HuggingFace).
- name: Events
description: Server-Sent Event streams.
- name: MCP Proxy
description: >-
JSON-RPC/SSE proxy for configured MCP servers plus OAuth discovery.
### Header forwarding rules
- Client `Authorization` header is **stripped** — not forwarded to
upstream servers.
- Client `x-api-key` header is **stripped** — not forwarded.
- Static `headers` from the server's config are forwarded to the
upstream.
### Redaction
Sensitive headers are removed from requests flowing through the
proxy. Only configured static headers from `mcp_servers` are used.
### Session semantics
Each proxy connection maintains a session with the upstream MCP
server. Sessions are per-connection and terminate when the client
disconnects or `DELETE` is called.
### OAuth discovery endpoints
The `.well-known/*` and `/register` endpoints exist solely for MCP
client compatibility — they return static metadata and don't
perform functional OAuth.
security:
- PlexusApiKey: []
paths:
/health:
$ref: paths/health.yaml
/v1/chat/completions:
$ref: paths/v1_chat_completions.yaml
/v1/messages:
$ref: paths/v1_messages.yaml
/v1/responses:
$ref: paths/v1_responses.yaml
/v1/codex/responses:
$ref: paths/v1_codex_responses.yaml
/v1/responses/{response_id}:
$ref: paths/v1_responses_{response_id}.yaml
/v1/conversations/{conversation_id}:
$ref: paths/v1_conversations_{conversation_id}.yaml
/v1beta/models/{modelWithAction}:
$ref: paths/v1beta_models_{modelWithAction}.yaml
/v1/embeddings:
$ref: paths/v1_embeddings.yaml
/v1/audio/transcriptions:
$ref: paths/v1_audio_transcriptions.yaml
/v1/audio/speech:
$ref: paths/v1_audio_speech.yaml
/v1/images/generations:
$ref: paths/v1_images_generations.yaml
/v1/images/edits:
$ref: paths/v1_images_edits.yaml
/v1/models:
$ref: paths/v1_models.yaml
/v1/openrouter/models:
$ref: paths/v1_openrouter_models.yaml
/v1/metadata/search:
$ref: paths/v1_metadata_search.yaml
/v1/metadata/lookup:
$ref: paths/v1_metadata_lookup.yaml
/v0/management/auth/verify:
$ref: paths/v0_management_auth_verify.yaml
/v0/management/self/me:
$ref: paths/v0_management_self_me.yaml
/v0/management/self/rotate:
$ref: paths/v0_management_self_rotate.yaml
/v0/management/self/comment:
$ref: paths/v0_management_self_comment.yaml
/v0/management/self/debug/toggle:
$ref: paths/v0_management_self_debug_toggle.yaml
/v0/management/self/quota:
$ref: paths/v0_management_self_quota.yaml
/v0/management/config:
$ref: paths/v0_management_config.yaml
/v0/management/config/status:
$ref: paths/v0_management_config_status.yaml
/v0/management/config/export:
$ref: paths/v0_management_config_export.yaml
/v0/management/config/vision-fallthrough:
$ref: paths/v0_management_config_vision-fallthrough.yaml
/v0/management/config/background-exploration:
$ref: paths/v0_management_config_background-exploration.yaml
/v0/management/config/timeout:
$ref: paths/v0_management_config_timeout.yaml
/v0/management/config/stall:
$ref: paths/v0_management_config_stall.yaml
/v0/management/config/cooldown:
$ref: paths/v0_management_config_cooldown.yaml
/v0/management/config/exploration-rate:
$ref: paths/v0_management_config_exploration-rate.yaml
/v0/management/config/failover:
$ref: paths/v0_management_config_failover.yaml
/v0/management/system-settings:
$ref: paths/v0_management_system-settings.yaml
/v0/management/providers:
$ref: paths/v0_management_providers.yaml
/v0/management/providers/{slug}:
$ref: paths/v0_management_providers_{slug}.yaml
/v0/management/providers/fetch-models:
$ref: paths/v0_management_providers_fetch-models.yaml
/v0/management/pi/providers:
$ref: paths/v0_management_pi_providers.yaml
/v0/management/pi/models:
$ref: paths/v0_management_pi_models.yaml
/v0/management/aliases:
$ref: paths/v0_management_aliases.yaml
/v0/management/aliases/{slug}:
$ref: paths/v0_management_aliases_{slug}.yaml
/v0/management/models:
$ref: paths/v0_management_models.yaml
/v0/management/models/{aliasId}:
$ref: paths/v0_management_models_{aliasId}.yaml
/v0/management/models/huggingface/{modelId}:
$ref: paths/v0_management_models_huggingface_{modelId}.yaml
/v0/management/keys:
$ref: paths/v0_management_keys.yaml
/v0/management/keys/{name}:
$ref: paths/v0_management_keys_{name}.yaml
/v0/management/mcp-servers:
$ref: paths/v0_management_mcp-servers.yaml
/v0/management/mcp-servers/{serverName}:
$ref: paths/v0_management_mcp-servers_{serverName}.yaml
/v0/management/quota-checker-types:
$ref: paths/v0_management_quota-checker-types.yaml
/v0/management/quota-checkers:
$ref: paths/v0_management_quota-checkers.yaml
/v0/management/quotas/backup-legacy-snapshots:
$ref: paths/v0_management_quotas_backup-legacy-snapshots.yaml
/v0/management/quotas/legacy-snapshot-status:
$ref: paths/v0_management_quotas_legacy-snapshot-status.yaml
/v0/management/quotas/migrate-legacy-snapshots:
$ref: paths/v0_management_quotas_migrate-legacy-snapshots.yaml
/v0/management/quotas/truncate-legacy-snapshots:
$ref: paths/v0_management_quotas_truncate-legacy-snapshots.yaml
/v0/management/usage:
$ref: paths/v0_management_usage.yaml
/v0/management/usage/summary:
$ref: paths/v0_management_usage_summary.yaml
/v0/management/usage/{requestId}:
$ref: paths/v0_management_usage_{requestId}.yaml
/v0/management/concurrency:
$ref: paths/v0_management_concurrency.yaml
/v0/management/debug:
$ref: paths/v0_management_debug.yaml
/v0/management/debug/logs:
$ref: paths/v0_management_debug_logs.yaml
/v0/management/debug/logs/{requestId}:
$ref: paths/v0_management_debug_logs_{requestId}.yaml
/v0/management/errors:
$ref: paths/v0_management_errors.yaml
/v0/management/errors/{requestId}:
$ref: paths/v0_management_errors_{requestId}.yaml
/v0/management/mcp-logs:
$ref: paths/v0_management_mcp-logs.yaml
/v0/management/mcp-logs/{requestId}:
$ref: paths/v0_management_mcp-logs_{requestId}.yaml
/v0/management/cooldowns:
$ref: paths/v0_management_cooldowns.yaml
/v0/management/cooldowns/{provider}:
$ref: paths/v0_management_cooldowns_{provider}.yaml
/v0/management/performance:
$ref: paths/v0_management_performance.yaml
/v0/management/metrics:
$ref: paths/v0_management_metrics.yaml
/v0/management/logging/level:
$ref: paths/v0_management_logging_level.yaml
/v0/management/logging/modules:
$ref: paths/v0_management_logging_modules.yaml
/v0/management/oauth/providers:
$ref: paths/v0_management_oauth_providers.yaml
/v0/management/oauth/sessions:
$ref: paths/v0_management_oauth_sessions.yaml
/v0/management/oauth/sessions/{id}:
$ref: paths/v0_management_oauth_sessions_{id}.yaml
/v0/management/oauth/sessions/{id}/prompt:
$ref: paths/v0_management_oauth_sessions_{id}_prompt.yaml
/v0/management/oauth/sessions/{id}/manual-code:
$ref: paths/v0_management_oauth_sessions_{id}_manual-code.yaml
/v0/management/oauth/sessions/{id}/cancel:
$ref: paths/v0_management_oauth_sessions_{id}_cancel.yaml
/v0/management/oauth/credentials:
$ref: paths/v0_management_oauth_credentials.yaml
/v0/management/oauth/credentials/status:
$ref: paths/v0_management_oauth_credentials_status.yaml
/v0/management/oauth/models:
$ref: paths/v0_management_oauth_models.yaml
/v0/management/quotas:
$ref: paths/v0_management_quotas.yaml
/v0/management/quotas/{checkerId}:
$ref: paths/v0_management_quotas_{checkerId}.yaml
/v0/management/quotas/{checkerId}/history:
$ref: paths/v0_management_quotas_{checkerId}_history.yaml
/v0/management/quotas/{checkerId}/check:
$ref: paths/v0_management_quotas_{checkerId}_check.yaml
/v0/management/user-quotas:
$ref: paths/v0_management_user-quotas.yaml
/v0/management/user-quotas/{name}:
$ref: paths/v0_management_user-quotas_{name}.yaml
/v0/management/quota/clear:
$ref: paths/v0_management_quota_clear.yaml
/v0/management/quota/status/{key}:
$ref: paths/v0_management_quota_status_{key}.yaml
/v0/management/test:
$ref: paths/v0_management_test.yaml
/v0/management/restart:
$ref: paths/v0_management_restart.yaml
/v0/management/backup:
$ref: paths/v0_management_backup.yaml
/v0/management/restore:
$ref: paths/v0_management_restore.yaml
/v0/management/events:
$ref: paths/v0_management_events.yaml
/v0/system/logs/stream:
$ref: paths/v0_system_logs_stream.yaml
/mcp/{name}:
$ref: paths/mcp_{name}.yaml
/.well-known/oauth-authorization-server:
$ref: paths/.well-known_oauth-authorization-server.yaml
/.well-known/oauth-protected-resource:
$ref: paths/.well-known_oauth-protected-resource.yaml
/.well-known/openid-configuration:
$ref: paths/.well-known_openid-configuration.yaml
/register:
$ref: paths/register.yaml
components:
securitySchemes:
PlexusApiKey:
type: http
scheme: bearer
description: |
Plexus API key. The server also accepts the same value in
`x-api-key`, `x-goog-api-key`, or the `?key=` query string.
Suffix the key with `:label` (e.g. `sk-abc:copilot`) to attach a
dynamic attribution label.
AdminKey:
type: apiKey
in: header
name: x-admin-key
description: |
Either the server's `ADMIN_KEY` (full admin access) or the plaintext
secret of a configured API key (limited access, scoped to that key).