{"openapi":"3.1.0","info":{"title":"LLM Trace Gateway API","version":"1","description":"Unified gateway in front of multiple LLM providers. Clients authenticate with a\ngateway-issued API key (prefix `lgw_`) in the `Authorization: Bearer` header.\n\nEvery key has per-provider (and optionally per-model) access. Model values are\neither a model alias or a public model id of the form `provider-name/model-id`\n(list what a key can use via `GET /v1/models`).\n\nWhen `model` is omitted, the gateway resolves a default in order: the key's\ndefault model for that endpoint group, then the gateway-wide default for that\nendpoint group, then the first allowed provider with an active model. The\n`/api/v1/decide` endpoint group only considers Jev decisions providers; the\nchat/responses group only considers non-Jev providers."},"servers":[{"url":"https://lgw.vpn.johnmcd.com"}],"tags":[{"name":"decisions","description":"Jev decisions API (structured single-pass model decisions)"},{"name":"chat","description":"OpenAI-compatible chat and responses endpoints"}],"security":[{"bearerAuth":[]}],"paths":{"/api/v1/decide":{"post":{"tags":["decisions"],"operationId":"decide","summary":"Ask the model to make structured decisions over a state","description":"Single-pass decision endpoint in the Jev wire format. The request carries a\n`state` (the context the model decides over) and one or more typed `questions`\nabout that state. The model answers all questions in one inference pass.\n\nOnly routes to providers configured in `jev_decisions` mode; any other model\nis rejected with 400. Streaming is not supported.\n\nOmit `model` to use the decide default resolution (key decide default ->\ngateway decide default -> first allowed Jev provider's default model).","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecideRequest"},"examples":{"choice":{"summary":"Choice question","value":{"state":"Deploying a migration to the production cluster","questions":{"proceed":{"instructions":"Should this change be deployed now?","type":"choice","criteria":{"yes":"Safe, reversible, and tested","no":"Too risky right now"}}}}},"score":{"summary":"Score question with a 4-level legend","value":{"state":"PR that refactors the proxy layer","questions":{"quality":{"instructions":"Rate the quality of this change","type":"score","criteria":["poor","fair","good","excellent"]}}}},"noul":{"summary":"No-output-logit yes/no question","value":{"state":"User asked to delete all backups","questions":{"destructive":{"instructions":"Is this request destructive?","type":"noul","criteria":{"true":"Irreversible or data-losing","false":"Safe"}}}}}}}}},"responses":{"200":{"description":"Answers for every question in the request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecideResponse"},"example":{"model":"semif-qwen3.5-4b-mlx@851bf6e806ef","answers":{"proceed":{"type":"choice","choice":"yes","confidence":0.91,"probabilities":{"yes":0.91,"no":0.09}},"quality":{"type":"score","score":2.4,"confidence":0.5,"legend":["poor","fair","good","excellent"]},"destructive":{"type":"noul","noul":0.87}},"usage":{"input_tokens":104,"output_tokens":0,"cost_usd":0}}}}},"400":{"description":"Invalid payload, unknown model, model provider mismatch, or unsupported feature (e.g. streaming)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Model provider or model not allowed for this API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate or token limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/chat/completions":{"post":{"tags":["chat"],"operationId":"createChatCompletion","summary":"OpenAI-compatible chat completions","description":"Standard OpenAI `/v1/chat/completions` request shape, proxied to the resolved provider. `model` is optional and falls back to the key/gateway chat default.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionRequest"}}}},"responses":{"200":{"description":"OpenAI chat completion (streaming supported where the provider allows)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpstreamOpenAIResponse"}}}},"400":{"description":"Invalid payload or unsupported model/provider combination","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Model provider or model not allowed for this API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate or token limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/responses":{"post":{"tags":["chat"],"operationId":"createResponse","summary":"OpenAI Responses API","description":"Standard OpenAI `/v1/responses` request shape. Only providers that support the Responses API (or are bridged to it) can be routed to; otherwise 400.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpstreamOpenAIResponse"}}}},"responses":{"200":{"description":"OpenAI response object","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpstreamOpenAIResponse"}}}},"400":{"description":"Provider does not support /v1/responses","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Model provider or model not allowed for this API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate or token limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/chat":{"post":{"tags":["chat"],"operationId":"chat","summary":"Chat endpoint (same behavior as /v1/chat/completions)","description":"Alternate chat path used by some clients; accepts the chat completions request shape.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionRequest"}}}},"responses":{"200":{"description":"OpenAI chat completion","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpstreamOpenAIResponse"}}}},"400":{"description":"Invalid payload or unsupported model/provider combination","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Model provider or model not allowed for this API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate or token limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/models":{"get":{"tags":["chat"],"operationId":"listModels","summary":"List models available to this API key","description":"Returns every active model on providers allowed for the calling key, plus a synthetic `default` entry whose `resolves_to` field shows which model an omitted `model` would resolve to.","responses":{"200":{"description":"Model list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelList"},"example":{"object":"list","data":[{"id":"default","object":"model","owned_by":"llm-trace-gateway","provider_model":null,"provider_backend_id":null,"provider_name":"default","resolves_to":"some-provider/some-model","sampling_defaults":null},{"id":"jev-decisions/semif-qwen3.5-4b-mlx@851bf6e806ef","object":"model","owned_by":"jev-decisions","provider_model":"semif-qwen3.5-4b-mlx@851bf6e806ef","provider_backend_id":"cmuafuhvk000m13kksfg4dkze","provider_name":"jev-decisions","sampling_defaults":null}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Gateway client API key (starts with `lgw_`), issued via the admin console"}},"schemas":{"DecideRequest":{"type":"object","required":["state","questions"],"properties":{"model":{"type":"string","description":"Optional. Model alias or `provider/model` public id. Must resolve to a Jev decisions provider. Omit to use the decide default resolution."},"state":{"oneOf":[{"type":"string","minLength":1,"description":"Free-text context"},{"type":"object","minProperties":1,"additionalProperties":true,"description":"Structured context"},{"type":"array","minItems":1,"description":"Sequence of context items"}],"description":"The state the model decides over. Must be nonempty."},"questions":{"type":"object","minProperties":1,"additionalProperties":{"$ref":"#/components/schemas/JevQuestion"},"description":"Map of question name to question definition. All questions are answered in one pass."}}},"JevQuestion":{"oneOf":[{"type":"object","required":["instructions","type","criteria"],"properties":{"instructions":{"type":"string","minLength":1},"type":{"const":"choice"},"criteria":{"type":"object","minProperties":2,"maxProperties":16,"additionalProperties":{"type":"string"},"description":"Option label -> short description of the option"}}},{"type":"object","required":["instructions","type","criteria"],"properties":{"instructions":{"type":"string","minLength":1},"type":{"const":"score"},"criteria":{"type":"array","minItems":2,"maxItems":10,"items":{"type":"string"},"description":"Ordered scale levels, low to high (e.g. [\"poor\", \"fair\", \"good\", \"excellent\"])"}}},{"type":"object","required":["instructions","type"],"properties":{"instructions":{"type":"string","minLength":1},"type":{"const":"noul"},"criteria":{"type":"object","additionalProperties":{"type":"string"},"maxProperties":2,"description":"Optional {\"true\": ..., \"false\": ...} labels for the yes/no branches"}}}]},"DecideResponse":{"type":"object","properties":{"model":{"type":"string","description":"Upstream model id that produced the answers"},"answers":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/JevAnswer"},"description":"One entry per requested question name"},"usage":{"$ref":"#/components/schemas/Usage"}}},"JevAnswer":{"oneOf":[{"type":"object","required":["type","choice"],"properties":{"type":{"const":"choice"},"choice":{"type":"string","description":"The selected criteria label"},"confidence":{"type":"number"},"probabilities":{"type":"object","additionalProperties":{"type":"number"},"description":"Probability per criteria label"}}},{"type":"object","required":["type","score"],"properties":{"type":{"const":"score"},"score":{"type":"number","description":"Continuous score over the criteria scale"},"confidence":{"type":"number"},"legend":{"type":"array","items":{"type":"string"},"description":"The criteria scale, low to high"}}},{"type":"object","required":["type","noul"],"properties":{"type":{"const":"noul"},"noul":{"type":"number","description":"Logit-derived probability of the true branch (0..1)"}}}]},"Usage":{"type":"object","properties":{"input_tokens":{"type":"integer"},"output_tokens":{"type":"integer","description":"Always 0 for decide requests (single pass, no generated tokens)"},"cost_usd":{"type":"number"}}},"ChatCompletionRequest":{"type":"object","required":["messages"],"properties":{"model":{"type":"string","description":"Optional. Model alias or `provider/model` public id. Omit to use the chat default resolution."},"messages":{"type":"array","items":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","enum":["system","user","assistant","tool"]},"content":{"type":["string","null"]}}}},"stream":{"type":"boolean"},"temperature":{"type":"number"},"max_tokens":{"type":"integer"},"extra_body":{"type":"object","additionalProperties":true,"description":"Gateway extension: extra parameters merged into the upstream request"}},"description":"Standard OpenAI chat completions request; additional OpenAI parameters are passed through."},"UpstreamOpenAIResponse":{"type":"object","description":"OpenAI-shaped response as returned by the upstream provider (see the OpenAI API reference for the full shape)."},"ModelList":{"type":"object","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"object":{"const":"model"},"owned_by":{"type":"string"},"provider_model":{"type":["string","null"]},"provider_backend_id":{"type":["string","null"]},"provider_name":{"type":"string"},"resolves_to":{"type":"string","description":"Only on the synthetic `default` entry"},"sampling_defaults":{"type":["object","null"]}}}}}},"Error":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable error message"}}}}}}