# clusterstuck API

The platform's API, for programs: a personal access token in `Authorization: Bearer cs_pat_...`. The account, its settings, the models it may call, and each of its projects' own APIs.

Authentication: `Authorization: Bearer <token>` unless an operation says it is public. Errors answer `{ "error": "...", "code"?: "..." }`.

## GET /v1/health

Is the API up

Public: no token needed.

200: Success.

## GET /v1/me

Who the token is for

The account, its projects, its settings and the models its tokens may call: everything a client needs about its user in one call.

200: Success.
```json
{
  "type": "object",
  "properties": {
    "user": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "username": {
          "type": "string"
        },
        "display_name": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "username",
        "display_name"
      ],
      "additionalProperties": false
    },
    "projects": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "ui_path": {
            "type": "string"
          }
        },
        "required": [
          "slug",
          "title",
          "ui_path"
        ],
        "additionalProperties": false
      }
    },
    "settings": {
      "type": "object",
      "properties": {
        "theme": {
          "description": "A theme id, e.g. `jade`.",
          "type": [
            "string",
            "null"
          ]
        },
        "mode": {
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "light",
                "dark"
              ]
            },
            {
              "type": "null"
            }
          ]
        },
        "lang": {
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "en",
                "ru"
              ]
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "required": [
        "theme",
        "mode",
        "lang"
      ],
      "additionalProperties": false
    },
    "modelApi": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "The models this account's tokens may call (`/v1/<model>/...`)."
    }
  },
  "required": [
    "user",
    "projects",
    "settings",
    "modelApi"
  ],
  "additionalProperties": false
}
```

401: No valid token.

## GET /v1/settings

The account's settings

200: Success.
```json
{
  "type": "object",
  "properties": {
    "settings": {
      "type": "object",
      "properties": {
        "theme": {
          "description": "A theme id, e.g. `jade`.",
          "type": [
            "string",
            "null"
          ]
        },
        "mode": {
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "light",
                "dark"
              ]
            },
            {
              "type": "null"
            }
          ]
        },
        "lang": {
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "en",
                "ru"
              ]
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "required": [
        "theme",
        "mode",
        "lang"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "settings"
  ],
  "additionalProperties": false
}
```

401: No valid token.

## PATCH /v1/settings

Change some settings

Only the fields present change; `null` clears one.

Body (application/json):
```json
{
  "type": "object",
  "properties": {
    "theme": {
      "description": "A theme id, e.g. `jade`.",
      "type": [
        "string",
        "null"
      ]
    },
    "mode": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "light",
            "dark"
          ]
        },
        {
          "type": "null"
        }
      ]
    },
    "lang": {
      "anyOf": [
        {
          "type": "string",
          "enum": [
            "en",
            "ru"
          ]
        },
        {
          "type": "null"
        }
      ]
    }
  }
}
```

200: Success.
```json
{
  "type": "object",
  "properties": {
    "settings": {
      "type": "object",
      "properties": {
        "theme": {
          "description": "A theme id, e.g. `jade`.",
          "type": [
            "string",
            "null"
          ]
        },
        "mode": {
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "light",
                "dark"
              ]
            },
            {
              "type": "null"
            }
          ]
        },
        "lang": {
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "en",
                "ru"
              ]
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "required": [
        "theme",
        "mode",
        "lang"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "settings"
  ],
  "additionalProperties": false
}
```

400: The request is not valid.

401: No valid token.

## GET /v1/llm/models

The models behind `llm`

OpenAI's model list. Informational: whatever `model` a request names, `llm` answers. A personal access token only, for an account the model is open to (`modelApi` in `/v1/me`); a few calls in flight per account at most (429 beyond). The answer streams back as the model writes it.

200: Success.

401: No valid token.

403: This model is not open to the account, or the call did not come with a token.

413: The body is too large for this model.

429: Too many calls in flight for this account.

503: The model is not running.

## POST /v1/llm/chat/completions

Chat completion (OpenAI)

OpenAI's Chat Completions. `"stream": true` streams server-sent events, the last chunk carrying `usage`. Structured output (`response_format` with a JSON schema), tools and vLLM's extras (`chat_template_kwargs: { enable_thinking: false }`) pass through. Any OpenAI client works with the base URL `https://api.<host>/v1/llm` and the token as its key. Body up to 32 MiB. A personal access token only, for an account the model is open to (`modelApi` in `/v1/me`); a few calls in flight per account at most (429 beyond). The answer streams back as the model writes it.

Body (application/json):
```json
{
  "type": "object",
  "properties": {
    "messages": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "system",
              "user",
              "assistant",
              "tool"
            ]
          },
          "content": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "propertyNames": {
                    "type": "string"
                  },
                  "additionalProperties": {}
                }
              }
            ],
            "description": "Text, or OpenAI content parts (images inline)."
          }
        },
        "required": [
          "role",
          "content"
        ],
        "additionalProperties": {}
      }
    },
    "stream": {
      "type": "boolean"
    },
    "max_tokens": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "temperature": {
      "type": "number"
    },
    "response_format": {
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {}
    },
    "tools": {
      "type": "array",
      "items": {
        "type": "object",
        "propertyNames": {
          "type": "string"
        },
        "additionalProperties": {}
      }
    }
  },
  "required": [
    "messages"
  ],
  "additionalProperties": {}
}
```

200: Success.

400: The request is not valid.

401: No valid token.

403: This model is not open to the account, or the call did not come with a token.

413: The body is too large for this model.

429: Too many calls in flight for this account.

503: The model is not running.

## POST /v1/llm/completions

Text completion (OpenAI)

OpenAI's legacy Completions: a `prompt`, continued. Streams like chat. Body up to 32 MiB. A personal access token only, for an account the model is open to (`modelApi` in `/v1/me`); a few calls in flight per account at most (429 beyond). The answer streams back as the model writes it.

Body (application/json):
```json
{
  "type": "object",
  "properties": {
    "prompt": {
      "type": "string"
    },
    "max_tokens": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991
    },
    "stream": {
      "type": "boolean"
    }
  },
  "required": [
    "prompt"
  ],
  "additionalProperties": {}
}
```

200: Success.

400: The request is not valid.

401: No valid token.

403: This model is not open to the account, or the call did not come with a token.

413: The body is too large for this model.

429: Too many calls in flight for this account.

503: The model is not running.

## POST /v1/llm/responses

Responses (OpenAI)

OpenAI's Responses API, as vLLM serves it. Usage comes as `input_tokens`/`output_tokens`. Body up to 32 MiB. A personal access token only, for an account the model is open to (`modelApi` in `/v1/me`); a few calls in flight per account at most (429 beyond). The answer streams back as the model writes it.

Body (application/json):
```json
{
  "type": "object",
  "properties": {
    "input": {}
  },
  "required": [
    "input"
  ],
  "additionalProperties": {}
}
```

200: Success.

400: The request is not valid.

401: No valid token.

403: This model is not open to the account, or the call did not come with a token.

413: The body is too large for this model.

429: Too many calls in flight for this account.

503: The model is not running.

## POST /v1/jev/systemone

Ask the decision model (System One)

Questions about a JSON `state`, each answered with probabilities: `noul` (yes/no; `criteria` may say what true and false mean), `choice` (one of `criteria`'s options: a map of name to description, or a list), `score` (a position on `criteria`'s ordered levels, lowest first). Up to 64 questions; body up to 1 MiB. Answers `{ answers: { <id>: ... }, model }`. A personal access token only, for an account the model is open to (`modelApi` in `/v1/me`); a few calls in flight per account at most (429 beyond). The answer streams back as the model writes it.

Body (application/json):
```json
{
  "type": "object",
  "properties": {
    "state": {
      "description": "Anything JSON: what the questions are about."
    },
    "questions": {
      "type": "object",
      "propertyNames": {
        "type": "string"
      },
      "additionalProperties": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "noul",
              "choice",
              "score"
            ]
          },
          "instructions": {
            "type": "string",
            "description": "The criterion to apply to the state."
          },
          "criteria": {}
        },
        "required": [
          "type",
          "instructions"
        ]
      }
    }
  },
  "required": [
    "state",
    "questions"
  ]
}
```

200: Success.

400: The request is not valid.

401: No valid token.

403: This model is not open to the account, or the call did not come with a token.

413: The body is too large for this model.

429: Too many calls in flight for this account.

503: The model is not running.

## GET /v1/jev/health

Is the decision model up

The shim's own health check. A personal access token only, for an account the model is open to (`modelApi` in `/v1/me`); a few calls in flight per account at most (429 beyond). The answer streams back as the model writes it.

200: Success.

401: No valid token.

403: This model is not open to the account, or the call did not come with a token.

413: The body is too large for this model.

429: Too many calls in flight for this account.

503: The model is not running.

## GET /v1/usage

What the account's model calls cost

Today and over 30 days, per model and way in, with the model server's own timings.

200: Success.
```json
{
  "type": "object",
  "properties": {
    "today": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "description": "`token` (this door) or `service` (a project's backend)."
          },
          "requests": {
            "type": "number"
          },
          "errors": {
            "type": "number"
          },
          "prompt_tokens": {
            "type": "number"
          },
          "completion_tokens": {
            "type": "number"
          },
          "ms": {
            "type": "number"
          },
          "avg_ttft_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "avg_queue_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "tokens_per_second": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "model",
          "source",
          "requests",
          "errors",
          "prompt_tokens",
          "completion_tokens",
          "ms",
          "avg_ttft_ms",
          "avg_queue_ms",
          "tokens_per_second"
        ],
        "additionalProperties": false
      }
    },
    "days30": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "description": "`token` (this door) or `service` (a project's backend)."
          },
          "requests": {
            "type": "number"
          },
          "errors": {
            "type": "number"
          },
          "prompt_tokens": {
            "type": "number"
          },
          "completion_tokens": {
            "type": "number"
          },
          "ms": {
            "type": "number"
          },
          "avg_ttft_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "avg_queue_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "tokens_per_second": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "model",
          "source",
          "requests",
          "errors",
          "prompt_tokens",
          "completion_tokens",
          "ms",
          "avg_ttft_ms",
          "avg_queue_ms",
          "tokens_per_second"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "today",
    "days30"
  ],
  "additionalProperties": false
}
```

401: No valid token.

## GET /v1/openapi.json

This document

The API's OpenAPI document, built from its routes (`?format=md` for Markdown).

Public: no token needed.

200: Success.

## GET /v1/p/{slug}/openapi.json

A project's own API document

The OpenAPI document of one of the account's projects, built from its routes with its live release (`?format=md` for the Markdown rendering). Someone else's project is a 404.

Parameters: `slug` (path, required)

200: Success.

401: No valid token.

404: No such project, or it has no document yet (a release from before routes were described).

## POST /v1/p/{slug}/{path}

A project's own API

Everything under `/p/<project>/` is the project's own backend: its routes are in its document (`/p/<project>/openapi.json`). Any method; WebSockets too.

Parameters: `slug` (path, required), `path` (path, required)

200: Success.

401: No valid token.

404: No such project, or no such route in it.

503: The project's service is not running.

