Skip to content

API contract

The plnt playground implements a subset of the OpenAI API — just the surface the plnt.work chat panel and any OpenAI client SDK actually need. This document is the canonical spec; the pytest suite tests/test_site_contract.py mechanically enforces it.

Base URL: https://playground.plnt.work (prod) · http://127.0.0.1:8080 (local plnt playground up).

MethodPathPurpose
GET/healthzLiveness. Always 200 once up.
GET/readyzReadiness. 503 if no models.
GET/v1/modelsList registered models.
POST/v1/chat/completionsChat completion (± SSE stream).
GET/Service banner + link to /docs.
GET/docsSwagger UI (FastAPI auto-gen).
{
"object": "list",
"data": [
{
"id": "plnt-mock-7b",
"object": "model",
"created": 1784017752,
"owned_by": "plnt",
"runtime": "mock",
"backend": "mock"
}
]
}

The runtime and backend fields are plnt-specific extensions — an OpenAI client will ignore them. The site uses runtime to badge each model card.

Request:

{
"model": "plnt-mock-7b",
"messages": [
{"role": "system", "content": "You are helpful."},
{"role": "user", "content": "hello"}
],
"stream": false,
"max_tokens": 256,
"temperature": 0.7,
"top_p": 1.0
}
  • model — required. Must appear in /v1/models.
  • messages — required, ≥ 1.
  • stream — default false. Set to true for SSE.
  • max_tokens — default 256, range 1..8192.
  • temperature — default 0.7, range 0.0..2.0.
  • top_p — default 1.0, range 0.0..1.0.

Non-streaming response (stream: false):

{
"id": "chatcmpl-45dca0d6a9cd470bb4d2b8b4",
"object": "chat.completion",
"created": 1784017753,
"model": "plnt-mock-7b",
"choices": [
{
"index": 0,
"message": {"role": "assistant", "content": "..."},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 4,
"completion_tokens": 42,
"total_tokens": 46
}
}

finish_reason is "stop" | "length" | "error".

Streaming response (stream: true) — Server-Sent Events, text/event-stream:

data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":...,"model":"...","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":...,"model":"...","choices":[{"index":0,"delta":{"content":"Hello "},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":...,"model":"...","choices":[{"index":0,"delta":{"content":"world"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":...,"model":"...","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]

Frame invariants (asserted in tests/test_site_contract.py):

  • Content-Type is text/event-stream.
  • First frame’s delta.role is "assistant".
  • Interior frames carry delta.content deltas — clients concatenate them.
  • Last real frame sets choices[0].finish_reason.
  • Stream terminates with data: [DONE].
StatusWhen
400Malformed body (pydantic validation).
404model not in /v1/models.
502Upstream backend (HTTPBackend) errored or timed out.
503Registry empty (/readyz only).

Errors are JSON: {"detail": "..."}, matching FastAPI’s default.

None. The playground is a public demo surface. If you need to lock it down later, the cleanest layer is a Cloudflare Access policy on the ingress; the pod itself stays auth-free.

  • Chart version bumped in plnt/charts/playground-api/Chart.yaml.
  • API version — this contract is v1 (the /v1/... path prefix). A v2 path prefix would ship alongside v1, not replace it.
  • Breaking changes to /v1/... require a corresponding update to tests/test_site_contract.py AND the plnt-site api.ts. That’s the guard against silent breakage.

These OpenAI endpoints are deliberately not served:

  • POST /v1/completions — legacy completions. Use chat.
  • POST /v1/embeddings — out of playground scope.
  • POST /v1/files, POST /v1/fine_tuning/* — model lifecycle is a Helm concern.
  • POST /v1/images/*, POST /v1/audio/* — different playground, different day.