> Source: https://wexa.ai/docs/tools/run-model

# run-model

Send a prompt to one of this organization's AI models and get the answer back, with the tokens
consumed, the cost and the latency. `model` takes any id `list-models` returns and is optional: omit
it to run the organization's default. Every response echoes the model that actually ran and a
`source` saying why it was chosen.

## What it is called on each surface

The wire name is `run-model` everywhere: it is what the Wexa MCP server advertises, the REST path is
`POST /v1/run-model`, and each SDK exposes it under its own language's naming convention.

| Surface | Name |
| --- | --- |
| Wexa MCP server | `run-model` |
| REST API | `POST /v1/run-model` |
| TypeScript SDK | `fabric.runModel()` |
| Python SDK | `fabric.run_model()` |

## Arguments

Send either `prompt` or `messages`. Everything else is optional.

| Argument | Type | Required | Notes |
| --- | --- | --- | --- |
| `prompt` | string | One of | The prompt, treated as a single user message. Ignored when `messages` is set. |
| `messages` | array | One of | A conversation as `{role, content}` pairs. Roles are `system`, `user`, `assistant`. Wins over `prompt`. |
| `model` | string | Optional | Any id [`list-models`](/docs/tools/list-models) returns. Omit to run the organization's default. |
| `max_tokens` | integer | Optional | Ceiling on the tokens generated. Defaults to 512. |
| `temperature` | number | Optional | 0 to 2. Defaults to 0.2. |
| `top_p` | number | Optional | Nucleus sampling, 0 to 1. |
| `stop` | array | Optional | Up to four sequences that end generation. |
| `seed` | integer | Optional | Best-effort determinism, where the provider supports it. |
| `response_format` | object | Optional | e.g. `{"type": "json_object"}`. |
| `stream` | boolean | Optional | Not supported. `true` is refused — see [no streaming](#no-streaming). |

A value outside its range is **refused, not clamped**. Silently adjusting a number you chose would
hand you a call you did not ask for.

`project_id`, `projectID`, `organization_id` and `executed_by` are not arguments you pass: the
platform binds all four from the credential you authenticated with, and both SDKs refuse them
before the request leaves your process — see
[server-bound arguments](/docs/get-started/scope-and-server-bound-arguments).

### Arguments bound to the model

`run-model` refuses a further set, bound to the **model** rather than to your credential: `endpoint`,
`api_base`, `api_version`, `secret_ref`, `secret_param`, `api_key`, `provider` and `extra_params`.
Wexa fills all of them from the model's own registry entry, and both SDKs refuse them before the
request leaves your process.

This is deliberate rather than incidental. The provider host is checked against your organization's
allow-list **at call time**, not when the model was registered, so withdrawing permission for a host
immediately blocks a model registered while it was still permitted. Provider pass-through is
administered per model and checked before it reaches execution. A per-request version of either would
be an unchecked route into provider routing and credential placement. Your provider credential is
resolved at the point of execution and never reaches you.

## Which models you can name

`run-model` accepts exactly the identifiers [`list-models`](/docs/tools/list-models) returns — a
registry id from `settable`, or a composite from `available`. Discovery and invocation are one story:
list, then call. There is no second naming scheme.

A free-text provider name such as `gpt-4o` is refused. A bare provider name has no credential to
resolve and no host to check, and two registered entries may share a provider model id with different
credentials, so there is no correct one to pick. See
[which identifier goes where](/docs/tools/identifiers).

## What it returns

The three surfaces wrap this differently, and code written against one will not read another
correctly — see [return shape](/docs/tools/overview#return-shape).

A real response, captured from a live call to `run-model`:

```json
{
  "lifecycle_id": "qlc_000023",
  "result": {
    "output": "pong",
    "model": "mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8",
    "source": "requested",
    "tokens": 28,
    "cost_usd": 0.000028,
    "latency_ms": 621
  }
}
```

`model` is the model that **actually ran**, not the one you asked for. `source` says why it was
chosen:

| `source` | Meaning |
| --- | --- |
| `requested` | You named this model |
| `organization default` | You named none, and this is the default the organization chose |
| `platform default` | You named none and the organization has chosen none, so a platform fallback ran |

Read both before attributing a charge. A `platform default` you did not expect is the signal that
nobody has chosen an organization default. On an enterprise deployment the call is refused outright in
that state rather than routed to a fallback, so no work runs on a model nobody selected.

## Calling it

The same call on all four surfaces. Pick a tab once and every code block in the documentation follows it.

**Call run-model**

**Wexa MCP server**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "run-model",
    "arguments": {
      "model": "mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8",
      "prompt": "Summarize Q3 in one line."
    }
  }
}
```

**REST API**

```bash
curl -sS https://fabric.wexa.ai/v1/run-model \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "content-type: application/json" \
  -d '{"model":"mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8","prompt":"Summarize Q3 in one line."}'
```

**TypeScript SDK**

```ts
import { Fabric } from '@wexa-fabric/sdk'

const fabric = new Fabric()
const answer = await fabric.runModel({
  model: 'mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8',
  prompt: 'Summarize Q3 in one line.',
})

// `model` is what ACTUALLY ran; `source` is why.
if (answer.source !== 'requested') {
  console.log(`ran on ${answer.model} (${answer.source})`)
}
```

**Python SDK**

```python
from wexa import Fabric

fabric = Fabric()
answer = fabric.run_model(
    model="mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8",
    prompt="Summarize Q3 in one line.",
)

# `model` is what ACTUALLY ran; `source` is why.
if answer["source"] != "requested":
    print(f"ran on {answer['model']} ({answer['source']})")
```

### A conversation

`messages` carries a system message and as many turns as you like.

**Multi-turn run-model**

**REST API**

```bash
curl -sS https://fabric.wexa.ai/v1/run-model \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "model": "mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8",
    "messages": [
      {"role": "system", "content": "Answer in one sentence."},
      {"role": "user", "content": "What changed in Q3?"},
      {"role": "assistant", "content": "Revenue grew 12%."},
      {"role": "user", "content": "On what headcount?"}
    ],
    "temperature": 0.2
  }'
```

**Wexa MCP server**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "run-model",
    "arguments": {
      "model": "mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8",
      "messages": [
        { "role": "system", "content": "Answer in one sentence." },
        { "role": "user", "content": "What changed in Q3?" }
      ],
      "temperature": 0.2
    }
  }
}
```

**TypeScript SDK**

```ts
const answer = await fabric.runModel({
  model: 'mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8',
  messages: [
    { role: 'system', content: 'Answer in one sentence.' },
    { role: 'user', content: 'What changed in Q3?' },
    { role: 'assistant', content: 'Revenue grew 12%.' },
    { role: 'user', content: 'On what headcount?' },
  ],
  temperature: 0.2,
})
```

**Python SDK**

```python
answer = fabric.run_model(
    model="mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8",
    messages=[
        {"role": "system", "content": "Answer in one sentence."},
        {"role": "user", "content": "What changed in Q3?"},
        {"role": "assistant", "content": "Revenue grew 12%."},
        {"role": "user", "content": "On what headcount?"},
    ],
    temperature=0.2,
)
```

There is no `tool` role, and no tool calling. Call [`run-agent`](/docs/tools/run-agent) when you want
an agent's tools.

Each call is independent: Wexa keeps no conversation for you. Send the turns you want the model to
see.

## Errors

Both SDKs raise the same typed errors, chosen from the gateway's own error string first and its HTTP
status second. `run-model` checks the `fabric:model.run` grant, and reaches the seven classes every
tool reaches — listed under [the common set](/docs/tools/errors#the-common-set). These are the ones
specific to it:

| Code | Status | Raised when | Retry? |
| --- | --- | --- | --- |
| `S8:model-not-found` | 404 | No such model in this organization | No — fix the id |
| `S8:model-forbidden` | 403 | The model's host is no longer permitted | No — an administrator must act |
| `S8:model-invalid-parameters` | 422 | A parameter was outside its range, or is not yours to set | No — fix the request |
| `S8:model-call-limited` | 429 | This key's own model-call budget is full | Yes, shortly |
| `S8:model-rate-limited` | 429 | The **provider** rate-limited the call | Yes, with backoff |
| `S8:model-upstream-error` | 502 | The provider failed | Yes |

The two 429s are separate on purpose: one means you are asking Wexa for too much at once, the other
means your provider is. The provider's own message is passed through, because it is what makes a
credential or access failure diagnosable.

Both SDKs classify these for you and stop retrying the terminal ones, so a revoked model does not cost
you a full backoff cycle on every call.

### When the model registry is briefly unreachable

Calls naming a composite keep working — a registry interruption is not a total model-calling outage.
Calls naming a registry id fail with a reason that says the registry could not be read, deliberately
**not** with `S8:model-not-found`, which is terminal and would read as a typo. Retry those.

[`list-models`](/docs/tools/list-models) reports the same condition, so you can tell a short model
list from a truncated one.

## Limits and billing

Model calls are limited **separately** from ordinary API calls, and by how many are in flight rather
than by how many per minute — a long generation and a listing are not the same load. Exhausting your
model-call budget does not block your other calls, and exhausting the ordinary budget does not block
your model calls.

Every call is metered against the API key that made it, so per-key spend answers "which of my
integrations is spending this" before the invoice does. The spend draws down the same balance as every
other metered use; there is not a second one to watch. The response's `tokens` and `cost_usd` let you
meter yourself without waiting for a billing report.

## No streaming

`run-model` does not stream. `stream: true` is refused with a clear message rather than served the
whole answer framed as a single chunk.

A stream that does not stream invites interfaces built against timing and chunk boundaries that would
change the moment real streaming arrived, with no way to tell who depended on the old behaviour. An
honest refusal is a smaller promise than one that has to be kept.

## Governance

Every call to `run-model` runs through the same ten-stage lifecycle as every other Wexa tool: the
credential is resolved to a scope, the `fabric:model.run` grant is checked, quota is drawn down, the
arguments are validated, policy rules are evaluated, the call is executed, the result is shaped and
redacted, and an audit record is written. The `lifecycle_id` in the response is the handle to that
record.

`run-model` is **not** marked consequential, so it does not enter the approval workflow. It does spend
money, as running an agent does, but a human approval per model call would make the capability
unusable in a request path; spend is controlled by metering and the separate rate-limit bucket
instead.

`fabric:model.run` is its own grant, deliberately not `fabric:model.write`: reading or editing a
model's configuration and spending money through it are different privileges, so an application key
that calls models cannot reconfigure the registry.

## See also

[Models](/docs/concepts/models) for how a model is chosen and what platform-provided versus
bring-your-own means for the bill, [`list-models`](/docs/tools/list-models),
[`set-model`](/docs/tools/set-model), [the tool index](/docs/tools/overview),
and [errors](/docs/tools/errors).
