> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modulex.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Choose the Assistant model & behavior

> Pick a managed or BYOK model for the ModuleX Assistant, understand per-organization model availability, and configure how a chat behaves.

export const MediaEmbed = ({id, type = 'screenshot', caption = '', ext, ratio = '16 / 9'}) => {
  const isVideo = type === 'video' || type === 'app_video';
  const resolvedExt = ext || (isVideo ? 'mp4' : type === 'screenshot' ? 'webp' : 'svg');
  const src = 'https://media.modulex.dev/' + id + '.' + resolvedExt;
  const [status, setStatus] = useState('loading');
  const [isDev, setIsDev] = useState(false);
  const [inView, setInView] = useState(false);
  const boxRef = useRef(null);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const h = window.location.hostname;
    setIsDev(h === 'localhost' || h === '127.0.0.1' || h.endsWith('.mintlify.app'));
  }, []);
  useEffect(() => {
    if (inView) return;
    if (typeof IntersectionObserver === 'undefined') {
      setInView(true);
      return;
    }
    const el = boxRef.current;
    if (!el) return;
    const io = new IntersectionObserver(entries => {
      if (entries.some(e => e.isIntersecting)) {
        setInView(true);
        io.disconnect();
      }
    }, {
      rootMargin: '300px'
    });
    io.observe(el);
    return () => io.disconnect();
  }, [inView]);
  if (status === 'missing') {
    if (!isDev) return null;
    return <div style={{
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      gap: '0.4rem',
      padding: '1rem 1.25rem',
      margin: '1.25rem 0',
      width: '100%',
      aspectRatio: ratio,
      boxSizing: 'border-box',
      border: '1px dashed rgba(128,128,128,0.45)',
      borderRadius: '0.75rem',
      background: 'rgba(128,128,128,0.06)',
      color: 'currentColor',
      fontSize: '0.85rem',
      lineHeight: 1.45
    }}>
        <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '0.5rem',
      opacity: 0.75
    }}>
          <span aria-hidden="true">🎬</span>
          <code style={{
      fontSize: '0.75rem'
    }}>{id}</code>
          <span style={{
      fontSize: '0.65rem',
      textTransform: 'uppercase',
      letterSpacing: '0.04em',
      padding: '0.1rem 0.4rem',
      borderRadius: '0.4rem',
      background: 'rgba(128,128,128,0.18)'
    }}>
            {type}
          </span>
        </div>
        <div style={{
      opacity: 0.9
    }}>{caption || 'Media not uploaded yet.'}</div>
        <div style={{
      fontSize: '0.7rem',
      opacity: 0.5
    }}>
          Upload to R2 as <code>{id}.{resolvedExt}</code> — preview only, hidden in production.
        </div>
      </div>;
  }
  const mediaStyle = {
    display: status === 'loaded' ? 'block' : 'none',
    width: '100%',
    height: 'auto',
    borderRadius: '0.75rem'
  };
  const media = isVideo ? <video src={inView ? src : undefined} autoPlay loop muted playsInline preload="metadata" onLoadedData={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} /> : <img src={inView ? src : undefined} alt={caption} onLoad={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} />;
  return <figure style={{
    margin: '1.25rem 0'
  }}>
      <div ref={boxRef} style={status === 'loaded' ? {
    width: '100%'
  } : {
    width: '100%',
    aspectRatio: ratio,
    borderRadius: '0.75rem',
    background: 'rgba(128,128,128,0.06)'
  }}>
        {media}
      </div>
      {status === 'loaded' && caption ? <figcaption style={{
    marginTop: '0.5rem',
    textAlign: 'center',
    fontSize: '0.85rem',
    opacity: 0.7
  }}>
          {caption}
        </figcaption> : null}
    </figure>;
};

The model is the engine behind every Assistant reply. ModuleX lets you run the Assistant on a **managed model** (provisioned by ModuleX and billed in credits) or on a **BYOK** model (your own provider account, billed by the provider with no ModuleX markup). This page shows you how to choose, what each choice costs, what is available to your organization, and how a chat's settings behave.

If you are new to the Assistant, start with the [Assistant overview](/assistant/overview). For how the Assistant uses tools and pauses for approval, see [Using tools](/assistant/using-tools) and [Human-in-the-loop](/assistant/human-in-the-loop).

<Note>
  The Assistant shares its model picker and provider catalog with the rest of ModuleX. The same managed-vs-BYOK choice applies in the [chat surface](/platform/chat/model-selection) and the [workflow Composer](/workflow-builder/composer). Connect and manage providers once on the [LLM providers](/integrations/llm-providers/overview) page.
</Note>

## The two ways to run the Assistant

<CardGroup cols={2}>
  <Card title="Managed models" icon="sparkles" href="/integrations/llm-providers/modulexai">
    ModuleX provisions the model for you. Nothing to connect — it works the moment you open a chat. Usage is metered in **credits**. The managed provider's name on the wire is `modulexai`.
  </Card>

  <Card title="BYOK — bring your own key" icon="key" href="/integrations/llm-providers/overview">
    Connect your own OpenAI, Anthropic, Google Gemini, or xAI account. The model provider bills you directly with **no ModuleX markup**, and the usage is **not** charged in credits.
  </Card>
</CardGroup>

<MediaEmbed id="MX-MEDIA-3270" type="screenshot" caption={"the Assistant chat model picker open, showing the managed option and the connected BYOK providers"} />

### How they compare

|                                 | Managed (`modulexai`)                     | BYOK                                  |
| ------------------------------- | ----------------------------------------- | ------------------------------------- |
| Setup                           | None — available by default               | Connect a provider credential first   |
| Who bills you                   | ModuleX, in **credits**                   | The provider, directly                |
| ModuleX markup                  | Priced in credits (`100 credits = $1.00`) | **None**                              |
| Counts toward your credit limit | Yes                                       | No — tracked for analytics only       |
| Providers                       | ModuleX-provisioned models                | OpenAI, Anthropic, Google Gemini, xAI |

<Tip>
  Use managed models to get started with zero setup and predictable in-app billing. Switch to BYOK when you already have provider accounts, want to use a specific model, or want provider billing to stay outside your ModuleX credit budget. See [Credits & metering](/billing/credits) for what a credit is.
</Tip>

## Choose a model in the app

<Steps>
  <Step title="Open or start an Assistant chat">
    Go to the chat surface and open a new Assistant chat or an existing one. See the [Chat overview](/platform/chat/overview) for the full surface.
  </Step>

  <Step title="Open the model picker">
    The model selector sits in the chat. It lists the managed model first, then each provider your organization has connected.
  </Step>

  <Step title="Pick a model">
    Select a managed model for credit-based billing, or a model under a connected BYOK provider to bill through that provider. Your choice applies to the next turn you send.
  </Step>

  <Step title="Send your message">
    The Assistant runs on the selected model. Every turn records which model actually served the request, so usage and any credit charges are attributed to the served model.
  </Step>
</Steps>

<MediaEmbed id="MX-MEDIA-3271" type="app_video" caption={"switching the Assistant model mid-conversation and sending a new turn"} />

### What happens if you do not pick a model

If you do not choose a model, the Assistant uses your **organization's default model**. ModuleX resolves the default like this:

<Steps>
  <Step title="Your organization's saved default">
    If an owner or admin has saved a default model in organization settings, that model is used.
  </Step>

  <Step title="A managed fallback">
    If no default is saved, ModuleX falls back to the first available managed (`modulexai`) model. This keeps brand-new and legacy organizations working without anyone configuring a model first.
  </Step>
</Steps>

A saved default is stored as `settings.composer_llm` on the organization and is shared by the Assistant and the workflow Composer. Setting or clearing it requires the **owner** or **admin** role (see [Permissions & limits](/assistant/permissions-and-limits)).

## Per-organization model availability

The models you can pick are scoped to your organization. Two things determine what shows up in the picker.

<CardGroup cols={2}>
  <Card title="Managed models — always available" icon="circle-check">
    The managed `modulexai` provider is provisioned for every organization, so at least one managed model is always selectable. This is what powers the fallback default.
  </Card>

  <Card title="BYOK models — only when connected" icon="plug">
    A BYOK provider's models appear only after someone in your organization connects a credential for that provider. No credential means no models from that provider in the picker.
  </Card>
</CardGroup>

To make a BYOK provider available to your whole organization, connect its credential once on the [LLM providers](/integrations/llm-providers/overview) page. Connected credentials are organization-scoped, so every member who can use the Assistant sees the same provider list. For how credentials are stored and resolved, see [Credentials & OAuth2](/concepts/credentials-oauth) and [Managing credentials](/integrations/managing-credentials).

<Note>
  Only organization **owners** and **admins** can use the Assistant today. The `member` role has been retired for Assistant, Composer, and schedules. If a teammate cannot open the Assistant, check their role — see [Roles & permissions](/security/roles-permissions) and [Organizations, roles & membership](/concepts/organizations-roles).
</Note>

### When a model is retired or unavailable

Managed model catalogs change over time. ModuleX handles this for you so a chat does not break:

<AccordionGroup>
  <Accordion title="Deprecated or in-maintenance managed models">
    If a managed model is marked deprecated or in maintenance, ModuleX routes your request to its replacement model automatically. Usage and any credit charge are recorded against the model that actually served the turn, not the one you requested.
  </Accordion>

  <Accordion title="An unknown managed model id">
    If a requested managed model id can't be resolved, ModuleX softens to the managed provider's default serving model rather than failing the turn.
  </Accordion>

  <Accordion title="BYOK models">
    BYOK providers pass your model choice straight through to the provider — ModuleX does not rewrite it. If the provider no longer serves that model, the provider returns the error so you see it directly. Pick a current model from your provider, or reconnect the provider on the [LLM providers](/integrations/llm-providers/overview) page.
  </Accordion>
</AccordionGroup>

## How the model is sent on a turn

When the app sends a turn to the Assistant, the model choice travels as an `llm` object. You will see the same object if you drive the Assistant from the [API](/assistant/streaming) or an SDK. It has these fields:

| Field              | Required | What it is                                                                                         |
| ------------------ | -------- | -------------------------------------------------------------------------------------------------- |
| `integration_name` | Yes      | The provider, e.g. `modulexai` for managed, or `openai` / `anthropic` / `gemini` / `xai` for BYOK. |
| `provider_id`      | Yes      | The provider's id for the model.                                                                   |
| `model_id`         | Yes      | The model to run, e.g. `gpt-4o`.                                                                   |
| `credential_id`    | No       | A specific connected credential to use for this turn.                                              |

If the `llm` object is omitted entirely, ModuleX applies your organization's default as described above. If it is present but missing any required field, the request is rejected with a `400` validation error. Validation errors use the `{detail}` envelope — see [Errors & status codes](/api-reference/errors).

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.modulex.dev/assistant/chat \
    -H "Authorization: Bearer mx_live_a1b2c3d4e5f6a7b8c9d0e1f2" \
    -H "X-Organization-ID: org_8e7d6c5b4a39281706f5e4d3" \
    -H "Content-Type: application/json" \
    -d '{
      "message": "Summarize the latest commit on my main branch",
      "llm": {
        "integration_name": "openai",
        "provider_id": "openai",
        "model_id": "gpt-4o"
      }
    }'
  ```

  ```python Python theme={null}
  from modulex import Modulex

  client = Modulex(
      api_key="mx_live_a1b2c3d4e5f6a7b8c9d0e1f2",
      organization_id="org_8e7d6c5b4a39281706f5e4d3",
  )

  response = client.assistant.chat(
      message="Summarize the latest commit on my main branch",
      llm={
          "integration_name": "openai",
          "provider_id": "openai",
          "model_id": "gpt-4o",
      },
  )
  print(response["stream_url"])
  ```

  ```typescript JavaScript theme={null}
  import { Modulex } from "modulex-js";

  const client = new Modulex({
    apiKey: "mx_live_a1b2c3d4e5f6a7b8c9d0e1f2",
    organizationId: "org_8e7d6c5b4a39281706f5e4d3",
  });

  const response = await client.assistant.chat({
    message: "Summarize the latest commit on my main branch",
    llm: {
      integration_name: "openai",
      provider_id: "openai",
      model_id: "gpt-4o",
    },
  });
  console.log(response.stream_url);
  ```
</CodeGroup>

<Note>
  Every Assistant request authenticates with `Authorization: Bearer mx_live_…` plus the `X-Organization-ID` header. The provider keys for a BYOK model are your connected credentials — they are never put in the request body. See [Authentication](/api-reference/authentication).
</Note>

<Tip>
  To run the Assistant on a managed model from code, set `integration_name` to `modulexai` and use a managed `provider_id` / `model_id` from the [ModuleX-managed models](/integrations/llm-providers/modulexai) catalog. Omit `llm` to let ModuleX apply the organization default.
</Tip>

## Behavior settings

Beyond the model, a few settings shape how the Assistant works in a chat.

<AccordionGroup>
  <Accordion title="One question at a time">
    The Assistant can pause to ask you something or to confirm an action before it runs. A chat holds only **one pending question at a time** — answer or cancel it before sending a new message. If you try to send while a question is open, ModuleX asks you to answer it first. See [Human-in-the-loop](/assistant/human-in-the-loop).
  </Accordion>

  <Accordion title="Approval before write actions">
    For actions that change or delete data in a connected service, the Assistant shows a "Run this?" approval card and waits for you to approve or cancel. Read-only lookups run without an approval step. Learn more in [Using tools](/assistant/using-tools).
  </Accordion>

  <Accordion title="Stop a running reply">
    You can stop an in-progress turn at any time. Stopping cancels the current run; you can then send a new message. The model and provider settings you picked stay in place.
  </Accordion>

  <Accordion title="Streaming responses">
    The Assistant streams its thinking, tool steps, and text as it works, so you see progress live rather than waiting for a final answer. See [Streaming responses](/assistant/streaming).
  </Accordion>
</AccordionGroup>

<Note>
  The Assistant v1 is **text-only** — each turn is a single text message. Picking a model does not enable image or audio input in the Assistant chat. (Voice input is a separate chat feature — see [Voice messages](/platform/chat/voice-message).)
</Note>

## What your model choice costs

How a turn is billed depends on which kind of model you picked.

<CardGroup cols={2}>
  <Card title="Managed model turns" icon="coins" href="/billing/credits">
    Run on credits. ModuleX checks your plan and credit balance **before** the turn starts, then records token usage as the turn completes. If your plan credits and wallet are exhausted, the turn is refused before any work happens.
  </Card>

  <Card title="BYOK model turns" icon="receipt" href="/integrations/llm-providers/overview">
    Billed by your provider, not in credits. ModuleX still tracks the usage for your analytics, but it does not count against your credit limit.
  </Card>
</CardGroup>

Because every Assistant turn passes through the billing admission gate, a managed turn can be refused with a `402`, `403`, or `429` when credits, plan quota, or rate limits are reached. These denials use the flat `{code, layer, key, current, limit, reason}` envelope — see [Usage gating & limits](/billing/usage-gating) and [Errors & status codes](/api-reference/errors). For who can use the Assistant and the limits that apply, see [Permissions & limits](/assistant/permissions-and-limits).

<Note>
  Switching to a BYOK model does not remove the per-turn rate limit or the requirement to be an owner or admin — it only changes who pays for the model tokens.
</Note>

## Related pages

<CardGroup cols={3}>
  <Card title="LLM providers" icon="server" href="/integrations/llm-providers/overview">
    Connect and manage managed and BYOK model providers.
  </Card>

  <Card title="Model selection in chat" icon="messages-square" href="/platform/chat/model-selection">
    The same model picker, across the chat surface.
  </Card>

  <Card title="ModuleX-managed models" icon="sparkles" href="/integrations/llm-providers/modulexai">
    The default managed provider, billed in credits.
  </Card>

  <Card title="Permissions & limits" icon="shield" href="/assistant/permissions-and-limits">
    Who can use the Assistant and the limits that apply.
  </Card>

  <Card title="Streaming responses" icon="radio" href="/assistant/streaming">
    Watch the Assistant work in real time.
  </Card>

  <Card title="Credits & metering" icon="coins" href="/billing/credits">
    What a credit is and what consumes them.
  </Card>
</CardGroup>
