Bring your own model to Claritty Studio | Claritty docs
Written from the request Studio actually sends, in packages/control-plane/src/providers/openai.ts, and copied here unedited. A proof script in the repo fails if a configured endpoint stops receiving the run's call. Source: docs/model-endpoint.md

Pointing Studio at your own model

Settings → Model takes a base URL alongside the key. Set it and every run goes there instead of the provider's own host — your server, your hardware, your domain, no code change.

This is what Studio sends, taken from packages/control-plane/src/providers/openai.ts rather than from memory. If your server satisfies it, Studio can drive it.

The one call

POST  {your base URL}/chat/completions

The base URL is used verbatim with /chat/completions appended, so include the version segment if your server has one — https://models.example.com/v1, not https://models.example.com.

Headers

content-type: application/json
authorization: Bearer <your key>     ← only when a key is set

A missing key means no header at all, not an empty one. A local server that needs no authentication should not have to ignore Bearer .

Body

{
  "model": "whatever-you-named-it",
  "messages": [ { "role": "system" | "user" | "assistant" | "tool", "content": "…" } ],

  // Present only when the automation uses them:
  "tools":       [ { "type": "function", "function": { "name", "description", "parameters" } } ],
  "tool_choice": "auto" | { "type": "function", "function": { "name": "…" } },
  "temperature": 0.7,
  "max_tokens":  4096
}

Nothing else is sent. No streaming: Studio waits for the whole response, because a run is a batch job and a half-written answer is not useful to a workflow step.

Response

{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "…",
        // Required if you accepted `tools` — this is how an agent calls one.
        "tool_calls": [
          { "id": "call_1", "type": "function",
            "function": { "name": "app.collect_items", "arguments": "{\"since_hours\":24}" } }
        ]
      },
      "finish_reason": "stop" | "tool_calls" | "length"
    }
  ],
  "usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0 }
}

**usage is optional.** Studio fills in zeros when it is missing, so a server that does not count tokens works — you simply get no token analytics for those runs, rather than a failure.

**arguments is a JSON string**, not an object. That is the OpenAI shape, and an object there is the most common reason a working server drives agents that never call a tool.

What has to work for an agent, specifically

A tool-less workflow needs only content. An agent step needs real tool calling: it is a loop, and Studio ends it when the model calls claritty_finish. A server that ignores tools and answers in prose will loop until the step's maxIterations and then fail — the automation is not broken, the endpoint is.

Test it with tools before trusting it with an agent.

Anything else

A non-2xx response is surfaced with your server's own body, uncut, because the reason is usually in it. If Studio ever hides that, the bug is Studio's.

pnpm proof:byom is the gate that a configured base URL actually receives the run's call — it stands up a server, points Studio at it, fires a workflow and asserts the request arrived. If you change this contract, that is what should fail first.

On the hosted platform
The hosted platform runs the model for you, so there is no key to configure and no endpoint to point at. Bringing your own is a local capability the cloud does not need, not one it withholds.