On this page

Agents

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.

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."}}}'
{
  "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
}

Parameters

ParameterTypeRequiredNotes
promptstringOne ofA single user message. Ignored when messages is set.
messagesarrayOne of{role, content} pairs, roles system, user, assistant (no tool role). Wins over prompt.
modelstringOptionalAn id list-models returns; a free-text name such as gpt-4o is refused. Omit for the organization's default.
max_tokensintegerOptional1 to 128,000; default 512. On a thinking model this includes its reasoning.
temperaturenumberOptional0 to 2; default 0.2.
top_pnumberOptionalNucleus sampling, 0 to 1.
stoparrayOptionalUp to four sequences that end generation.
seedintegerOptionalBest-effort determinism.
response_formatobjectOptionalFor example {"type": "json_object"}.
reasoning_effortstringOptionalnone, minimal, low, medium, high or xhigh. Omitted, Wexa asks for the lowest level the model accepts.
streambooleanOptionalNot supported; true is refused.
Errors
CodeStatusRaised when
S8:model-not-found404No such model in this organization.
S8:model-forbidden403The model's host is no longer permitted.
S8:model-not-permitted403Routing policy allows no model for this call (deployment boundary, project grants, sovereign data or governance).
S8:model-approval-required403Governance requires approval first; the body carries the approval id.
S8:model-invalid-parameters422A value is out of range, is not yours to set, or the model does not support it.
S8:model-call-limited429This key's model-call budget is full.
S8:model-rate-limited429The provider rate-limited the call.
S8:model-upstream-error502The 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 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:

{
  "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
  }
}
More details

There is no tool role and no tool calling here: to use an agent's tools, call 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 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.

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.

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

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-providedBring-your-own
SetupNone beyond choosing the modelYou supply your own provider credentials
Who bills youWexa, in creditsYour provider, at your own rates
Rate limitsWexa'sYour provider account's
Where you see spendThe credits balance, metered per callYour 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. 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.