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

# run-model

Send a prompt to one of this organization's AI models and get the answer back, with the tokens used, the cost and the latency. Needs the `fabric:model.run` grant.

**Call a model**

**Wexa MCP server**

```bash
curl -sS -X POST https://fabric.wexa.ai/mcp/$FABRIC_PROJECT_ID \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "Mcp-Session-Id: $MCP_SESSION_ID" \
  -H "content-type: application/json" -H "accept: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"run-model","arguments":{"model":"mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8","prompt":"Reply with the word pong."}}}'
```

```json
{
  "content": [
    {
      "type": "text",
      "text": "{\"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}}"
    }
  ],
  "isError": false
}
```

**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":"Reply with the word pong."}'
```

```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
  }
}
```

**TypeScript SDK**

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

const fabric = new Fabric()
const result = await fabric.runModel({
  model: 'mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8',
  prompt: 'Reply with the word pong.'
})
```

```ts
// result is the payload itself, with no `.result` to read
{
  output: 'pong',
  model: 'mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8',
  source: 'requested',
  tokens: 28,
  cost_usd: 0.000028,
  latency_ms: 621
}
```

**Python SDK**

```python
from wexa import Fabric

fabric = Fabric()
out = fabric.run_model(
    model="mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8",
    prompt="Reply with the word pong.",
)
```

```python
# out is the result, already unwrapped
{
    "output": "pong",
    "model": "mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8",
    "source": "requested",
    "tokens": 28,
    "cost_usd": 0.000028,
    "latency_ms": 621
}
```

## Parameters

| Parameter | Type | Required | Notes |
| --- | --- | --- | --- |
| `prompt` | string | One of | A single user message. Ignored when `messages` is set. |
| `messages` | array | One of | `{role, content}` pairs, roles `system`, `user`, `assistant` (no `tool` role). Wins over `prompt`. |
| `model` | string | Optional | An id `list-models` returns; a free-text name such as `gpt-4o` is refused. Omit for the organization's default. |
| `max_tokens` | integer | Optional | 1 to 128,000; default 512. On a thinking model this includes its reasoning. |
| `temperature` | number | Optional | 0 to 2; default 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. |
| `response_format` | object | Optional | For example `{"type": "json_object"}`. |
| `reasoning_effort` | string | Optional | `none`, `minimal`, `low`, `medium`, `high` or `xhigh`. Omitted, Wexa asks for the lowest level the model accepts. |
| `stream` | boolean | Optional | Not supported; `true` is refused. |

**Errors**

| Code | Status | Raised when |
| --- | --- | --- |
| `S8:model-not-found` | 404 | No such model in this organization. |
| `S8:model-forbidden` | 403 | The model's host is no longer permitted. |
| `S8:model-not-permitted` | 403 | Routing policy allows no model for this call (deployment boundary, project grants, sovereign data or governance). |
| `S8:model-approval-required` | 403 | Governance requires approval first; the body carries the approval id. |
| `S8:model-invalid-parameters` | 422 | A value is out of range, is not yours to set, or the model does not support it. |
| `S8:model-call-limited` | 429 | This key's model-call budget is full. |
| `S8:model-rate-limited` | 429 | The provider rate-limited the call. |
| `S8:model-upstream-error` | 502 | The provider failed. A policy the platform could not read (`policy-unavailable`) is reported with this code too, with status 503; it is transient, so retry. |

Retry the two 429s and the 502, with backoff; do not retry the other codes, they will not succeed until you fix the id, the request or the policy, or an administrator acts. 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. 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/agents/list-models) reports the same condition, so you can tell a short model list from a truncated one.

**Show full response**

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

**Result of run-model**

**REST API**

```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
  }
}
```

**TypeScript SDK**

```ts
// the object you get back IS the result: there is no `.result` to read
{
  output: 'pong',
  model: 'mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8',
  source: 'requested',
  tokens: 28,
  cost_usd: 0.000028,
  latency_ms: 621
}
```

**Python SDK**

```python
# a dict subclass; `result` is already unwrapped
{
    "output": "pong",
    "model": "mdl_bx_anthropic-claude-haiku-4-5-20251001-v1_28ccc24bfca8",
    "source": "requested",
    "tokens": 28,
    "cost_usd": 0.000028,
    "latency_ms": 621
}
```

**More details**

There is no `tool` role and no tool calling here: to use an agent's tools, call [run-agent](/docs/agents/run-agent). Each call is independent and Wexa keeps no conversation, so send the turns you want the model to see in `messages`.

`run-model` is not marked consequential, but it still spends money. A human approval per model call would make it unusable in a request path, so spend is controlled by metering and rate limiting instead. `fabric:model.run` is deliberately separate from `fabric:model.write`, so a key that calls models cannot reconfigure the registry.

`model` in the result is the model that actually ran, not the one you asked for. `source` says why it was chosen: `requested` (you named it), `organization default` (you named none and this is the organization's default) or `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.

A value outside its range is refused, not clamped, and an argument `run-model` does not support is refused, not ignored, with `S4:validate-input` (422) and a message that names it and lists what you may set. A misspelt `max_token` therefore fails instead of quietly running at the default budget.

`run-model` accepts exactly the identifiers [list-models](/docs/agents/list-models) returns: a registry id from `settable`, or a composite from `available`. 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. See [which identifier goes where](/docs/sdks-and-apis/identifiers).

`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.

### A conversation

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

**Multi-turn run-model**

**Wexa MCP server**

```bash
curl -sS -X POST https://fabric.wexa.ai/mcp/$FABRIC_PROJECT_ID \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "Mcp-Session-Id: $MCP_SESSION_ID" \
  -H "content-type: application/json" -H "accept: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"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?"},{"role":"assistant","content":"Revenue grew 12%."},{"role":"user","content":"On what headcount?"}],"temperature":0.2}}}'
```

**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}'
```

**TypeScript SDK**

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

const fabric = new Fabric()
const result = 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
from wexa import Fabric

fabric = Fabric()
out = 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,
)
```

### Thinking models

A thinking model (Gemini 2.5 and later, Claude Sonnet and Opus 4, Qwen3 and similar) reasons before it answers, and its reasoning tokens count against `max_tokens`, alongside the answer. If the model reasons past the whole budget, the call returns an empty or cut-off answer; this is most likely at the default of 512 and on a hard prompt. `tokens` and `cost_usd` include the reasoning, so a call can cost more than its answer looks.

`reasoning_effort` controls it. When you leave it out, Wexa asks for the lowest level the model accepts: `none` on most models, `low` on Gemini 3.7 and later, and nothing at all on GPT-OSS. That is a floor, not a guarantee: on a hard prompt a model reasons anyway, so a small `max_tokens` can still truncate.

For a short, cheap answer leave `reasoning_effort` out, or set `none` or `low`. For a hard problem set `medium` or `high` and raise `max_tokens` to several thousand. The level you set is sent to the model as given, and not every model supports every level: Gemini 3.7 and later refuse `none` and `minimal`, for example, and GPT-OSS refuses `none`. Wexa does not substitute another level: the provider's refusal comes back as `S8:model-invalid-parameters` with its own message. A model that does not reason at all ignores the setting or refuses it.

### 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. 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. 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.

### 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. 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 result's `tokens` and `cost_usd` let you meter yourself without waiting for a billing report.

### Platform-provided or bring-your-own

|  | Platform-provided | Bring-your-own |
| --- | --- | --- |
| Setup | None beyond choosing the model | You supply your own provider credentials |
| Who bills you | Wexa, in credits | Your provider, at your own rates |
| Rate limits | Wexa's | Your provider account's |
| Where you see spend | The credits balance, metered per call | Your existing provider invoice |

Platform-provided usage draws down credits, the balance your organization holds, the same balance as every other metered use. An estimate is held against the balance before the call and reconciled afterwards against the tokens used, for input and output separately. A call that fails after the hold gives the hold back. Running out of credits stops calls rather than building a debt.

Three levers decide the cost of an agent. Which model is the largest lever in both cases: an agent's bill is its tokens times the model's price, and models differ by more than the work usually does. Platform-provided credits are one balance for the whole organization, so one team's runs can exhaust the headroom another team counted on, as with a [quota](/docs/get-started/core-ideas#know-where-your-work-lives). Where you set the model sets the blast radius: a per-agent `llm.model` moves one agent, and `set-model` moves every agent that has not been overridden, in every project.

Every tool call works the same way: [How every tool call works](/docs/sdks-and-apis/how-every-tool-call-works).
