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

# OpenAI

> Connect your OpenAI account to ModuleX, browse the GPT, o-series, and embedding models in the catalog, select them in workflows, the Assistant, and chat, and understand BYOK billing.

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

OpenAI is a **bring-your-own-key (BYOK) LLM provider** in ModuleX. You connect your own OpenAI API key as a [credential](/concepts/credentials-oauth), and ModuleX uses it to call OpenAI's GPT, o-series reasoning, and embedding models from your [workflows](/concepts/workflows-and-runs), the [Assistant](/concepts/assistant), the [AI Composer](/concepts/ai-composer), and [chat](/platform/chat/overview). Because OpenAI usage runs through your own key, it is **billed by OpenAI directly** and consumes **no ModuleX [credits](/concepts/credits-billing)**.

This page covers connecting an OpenAI credential, the OpenAI models ModuleX exposes in its catalog, how to select an OpenAI model on each surface, and how BYOK billing works. For the cross-provider picture see [LLM providers](/integrations/llm-providers/overview); for the credential model and OAuth see [Credentials & OAuth2](/concepts/credentials-oauth).

<Note>
  OpenAI is the integration named `openai` on the wire (`integration_type: "llm_provider"`). It exposes **one auth schema** — `api_key` — so there is no ModuleX-managed (pooled-key) option for OpenAI. For credit-metered managed models, use [ModuleX-managed models](/integrations/llm-providers/modulexai) instead.
</Note>

## Before you start

<CardGroup cols={2}>
  <Card title="An OpenAI API key" icon="key">
    Create a secret key at the [OpenAI API keys page](https://platform.openai.com/api-keys). It starts with `sk-` (for example `sk-proj-…`). ModuleX validates it against the format `^sk-[a-zA-Z0-9\-_]{20,}$`.
  </Card>

  <Card title="Owner or admin role" icon="user-shield">
    Creating and managing credentials and browsing the integration catalog require the **owner** or **admin** role in your [organization](/concepts/organizations-roles). The retired `member` role cannot perform these actions. See [Roles & permissions](/security/roles-permissions).
  </Card>

  <Card title="Your auth headers" icon="lock">
    Every API call sends `Authorization: Bearer mx_live_…` and `X-Organization-ID`. See [Authentication](/api-reference/authentication).
  </Card>

  <Card title="An OpenAI billing account" icon="credit-card">
    BYOK usage is charged by OpenAI to your OpenAI account, not by ModuleX. Make sure your OpenAI account has billing enabled and sufficient quota.
  </Card>
</CardGroup>

## Connect OpenAI

You connect OpenAI by creating an `api_key` [credential](/concepts/credentials-oauth) for the `openai` integration. ModuleX auto-detects the credential type from the body you send: an `auth_data` object containing `api_key` is stored as an `api_key` credential.

<Steps>
  <Step title="Get your OpenAI API key">
    In the OpenAI dashboard, go to [API keys](https://platform.openai.com/api-keys), choose **Create new secret key**, name it, set its permissions, and copy the value. You only see it once — store it securely until you paste it into ModuleX.
  </Step>

  <Step title="(Optional) validate it before saving">
    Call `POST /credentials/test-temporary` to check the key works **before** persisting it. ModuleX runs OpenAI's configured test — a minimal `POST https://api.openai.com/v1/chat/completions` with `gpt-5.4-mini` and `max_tokens: 10` — and reports `is_valid`.
  </Step>

  <Step title="Create the credential">
    Call `POST /credentials` with `integration_name: "openai"` and `auth_data.api_key`. Set `make_default: true` to make it the default OpenAI credential for the organization. ModuleX encrypts the key at rest and never returns it in clear text.
  </Step>

  <Step title="Select OpenAI models">
    The key is now resolvable for any surface that runs an OpenAI model — workflows, the Assistant, the Composer, and chat. See [Select an OpenAI model](#select-an-openai-model).
  </Step>
</Steps>

<MediaEmbed id="MX-MEDIA-4060" type="screenshot" caption={"The ModuleX app credential dialog for connecting OpenAI with an API key."} />

### Create the credential

`POST /credentials` returns `201`. The type is auto-detected from the body — passing `auth_data.api_key` yields an `api_key` credential. See the full credentials reference in [Credentials & OAuth2](/concepts/credentials-oauth).

<ParamField body="integration_name" type="string" required>
  Must be `openai`.
</ParamField>

<ParamField body="auth_data" type="object" required>
  The secret material. For OpenAI, `{"api_key": "sk-proj-…"}`. The presence of `api_key` selects the `api_key` auth type. To target an OpenAI-compatible endpoint, also include `base_url` (see [OpenAI-compatible endpoints](#openai-compatible-endpoints)).
</ParamField>

<ParamField body="display_name" type="string">
  A human-readable label. Defaults to the integration's display name (`OpenAI`) when omitted.
</ParamField>

<ParamField body="make_default" type="boolean" default="false">
  Set this credential as the default OpenAI credential for the organization. Only one credential per integration can be the default.
</ParamField>

<ParamField body="expires_at" type="string">
  Optional ISO-8601 datetime after which ModuleX treats the credential as expired.
</ParamField>

**Response** — `201 Created` (secret masked):

<ResponseField name="credential_id" type="string">
  The credential's UUID. Pass this as `credential_id` to pin a specific OpenAI key on a run.
</ResponseField>

<ResponseField name="integration_name" type="string">
  `openai`.
</ResponseField>

<ResponseField name="integration_type" type="string | null">
  `llm_provider`.
</ResponseField>

<ResponseField name="auth_type" type="string">
  `api_key`.
</ResponseField>

<ResponseField name="is_default" type="boolean">
  Whether this is the organization's default OpenAI credential.
</ResponseField>

<ResponseField name="last_used_at" type="string | null">
  ISO-8601 timestamp of the last resolution, or `null` if never used.
</ResponseField>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.modulex.dev/credentials \
    -H "Authorization: Bearer mx_live_xxx" \
    -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \
    -H "Content-Type: application/json" \
    -d '{
      "integration_name": "openai",
      "display_name": "Production OpenAI key",
      "make_default": true,
      "auth_data": { "api_key": "sk-proj-abc123" }
    }'
  ```

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

  async with Modulex(
      api_key="mx_live_xxx",
      organization_id="11111111-1111-1111-1111-111111111111",
  ) as client:
      credential = await client.credentials.create(
          "openai",
          display_name="Production OpenAI key",
          make_default=True,
          auth_data={"api_key": "sk-proj-abc123"},
      )
      print(credential.credential_id)
  ```

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

  const client = new Modulex({
    apiKey: "mx_live_xxx",
    organizationId: "11111111-1111-1111-1111-111111111111",
  });

  const credential = await client.credentials.create({
    integrationName: "openai",
    displayName: "Production OpenAI key",
    makeDefault: true,
    authData: { api_key: "sk-proj-abc123" },
  });
  console.log(credential.credential_id);
  ```
</CodeGroup>

### Validate a key before saving

`POST /credentials/test-temporary` validates auth data **before** you persist it. ModuleX runs OpenAI's configured test endpoint (`POST https://api.openai.com/v1/chat/completions`, model `gpt-5.4-mini`, `max_tokens: 10`) and treats HTTP `200` with `id`, `choices`, and `model` present as success.

<ParamField body="integration_name" type="string" required>
  `openai`.
</ParamField>

<ParamField body="auth_type" type="string" required>
  `api_key`.
</ParamField>

<ParamField body="auth_data" type="object" required>
  `{"api_key": "sk-proj-…"}`. Optionally include `base_url`.
</ParamField>

The response reports `is_valid`, a human-readable `message`, `tested_at`, and a `test_method` of `api_call`.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.modulex.dev/credentials/test-temporary \
    -H "Authorization: Bearer mx_live_xxx" \
    -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \
    -H "Content-Type: application/json" \
    -d '{
      "integration_name": "openai",
      "auth_type": "api_key",
      "auth_data": { "api_key": "sk-proj-abc123" }
    }'
  ```

  ```python Python theme={null}
  result = await client.credentials.test_temporary(
      "openai",
      "api_key",
      {"api_key": "sk-proj-abc123"},
  )
  print(result.is_valid, result.message)
  ```

  ```javascript JavaScript theme={null}
  const result = await client.credentials.testTemporary({
    integrationName: "openai",
    authType: "api_key",
    authData: { api_key: "sk-proj-abc123" },
  });
  console.log(result.isValid, result.message);
  ```
</CodeGroup>

### OpenAI-compatible endpoints

OpenAI's auth schema includes an optional `base_url` field, so you can point the same credential at any OpenAI-compatible API (a proxy, gateway, or self-hosted server that speaks the OpenAI wire protocol).

<ParamField body="base_url" type="string" default="https://api.openai.com/v1">
  The API base URL. Only change this for an OpenAI-compatible endpoint. ModuleX validates it against `^https?://.*`.
</ParamField>

Include `base_url` inside `auth_data` when you create the credential. Leave it unset to use OpenAI directly.

```bash cURL theme={null}
curl -X POST https://api.modulex.dev/credentials \
  -H "Authorization: Bearer mx_live_xxx" \
  -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111" \
  -H "Content-Type: application/json" \
  -d '{
    "integration_name": "openai",
    "display_name": "OpenAI-compatible gateway",
    "auth_data": {
      "api_key": "sk-proxy-abc123",
      "base_url": "https://gateway.example.com/v1"
    }
  }'
```

<Warning>
  The catalog model ids on this page (for example `gpt-5.5`) map to OpenAI's own model names via each model's `body_extra.model`. A custom `base_url` must serve those same model names, or the upstream call returns a model-not-found error from your endpoint.
</Warning>

## Available models

ModuleX serves OpenAI's models from its integration catalog. The list below is the catalog as shipped in the backend OpenAI manifest; OpenAI updates models over time, so always treat the **live catalog** ([browse it via the API](#browse-openai-in-the-catalog)) as authoritative.

Each model carries a standardized `id` (the value you pass as `model_id`), pricing in **USD per 1M tokens** (input and output), a context window (`max_input_tokens`), an output cap (`max_output_tokens`), a knowledge cutoff (`data_freshness`), `intelligence` and `speed` scores (1–5), and vision support. Pricing shown is **OpenAI's list price** that ModuleX records for analytics — your actual charge comes from OpenAI, not ModuleX.

### Chat and reasoning models

| `model_id`          | Display name      | Context (in) | Max output | Input \$/1M | Output \$/1M | Vision | Cutoff     |
| ------------------- | ----------------- | -----------: | ---------: | ----------: | -----------: | :----: | ---------- |
| `gpt-5.5`           | GPT 5.5           |    1,050,000 |    128,000 |      \$5.00 |      \$30.00 |   Yes  | 2025-12-01 |
| `gpt-5.5-pro`       | GPT 5.5 Pro       |    1,050,000 |    128,000 |     \$30.00 |     \$180.00 |   Yes  | 2025-12-01 |
| `gpt-5.4`           | GPT 5.4           |    1,050,000 |    128,000 |      \$2.50 |      \$15.00 |   Yes  | 2025-08-31 |
| `gpt-5.4-pro`       | GPT 5.4 Pro       |    1,050,000 |    128,000 |     \$30.00 |     \$180.00 |   Yes  | 2025-08-31 |
| `gpt-5.4-mini`      | GPT 5.4 mini      |      400,000 |    128,000 |      \$0.75 |       \$4.50 |   Yes  | 2024-05-31 |
| `gpt-5.4-nano`      | GPT 5 nano        |      400,000 |    128,000 |      \$0.20 |       \$1.25 |   Yes  | 2024-08-31 |
| `gpt-5-chat-latest` | GPT 5 chat latest |      128,000 |     16,384 |      \$1.25 |      \$10.00 |   Yes  | 2024-09-30 |
| `gpt-5.3-codex`     | GPT 5.3 codex     |      400,000 |    128,000 |      \$1.75 |      \$14.00 |   Yes  | 2025-08-31 |
| `o3-pro`            | o3-pro            |      200,000 |    100,000 |     \$20.00 |      \$80.00 |   Yes  | 2024-06-01 |
| `o3`                | o3                |      200,000 |    100,000 |      \$2.00 |       \$8.00 |   Yes  | 2024-06-01 |

### Embedding models

Embedding models return a vector instead of text (`max_output_tokens` is `0`). Use them to build a [knowledge base](/concepts/knowledge-rag) with OpenAI embeddings, or in a workflow that needs vectors.

| `model_id`               | Display name           | Dimensions | Max input | Input \$/1M | Output \$/1M |
| ------------------------ | ---------------------- | ---------: | --------: | ----------: | -----------: |
| `text-embedding-3-large` | Text Embedding 3 Large |       3072 |     8,191 |      \$0.13 |       \$0.00 |
| `text-embedding-3-small` | Text Embedding 3 Small |       1536 |     8,191 |      \$0.02 |       \$0.00 |

<Note>
  `text-embedding-3-small` produces 1536-dimensional vectors, which match ModuleX's managed vector storage column width. `text-embedding-3-large` produces 3072-dimensional vectors. Pick the embedding model that matches your vector store's dimension when you configure a knowledge base. See [Managed knowledge](/platform/knowledge/managed) and [Knowledge & RAG](/concepts/knowledge-rag).
</Note>

<Expandable title="Full model schema (catalog fields per model)">
  Every entry in the OpenAI `models[]` array carries these fields. ModuleX stores them verbatim and returns them on the [detail endpoint](#browse-openai-in-the-catalog).

  <ResponseField name="id" type="string">
    The standardized catalog model id you pass as `model_id` (for example `gpt-5.5`).
  </ResponseField>

  <ResponseField name="display_name" type="string">
    The human-readable model name.
  </ResponseField>

  <ResponseField name="provider" type="string">
    The display vendor — `OpenAI`.
  </ResponseField>

  <ResponseField name="provider_id" type="string">
    The routing provider slug — `openai`. Pass this as `provider_id` in an `llm` config.
  </ResponseField>

  <ResponseField name="max_input_tokens" type="integer">
    The context-window size in tokens.
  </ResponseField>

  <ResponseField name="max_output_tokens" type="integer">
    The maximum tokens the model can return. `0` for embedding models.
  </ResponseField>

  <ResponseField name="body_extra" type="object">
    The provider request-body fragment, including the real OpenAI wire `model` name. For OpenAI the catalog `id` and `body_extra.model` are the same.
  </ResponseField>

  <ResponseField name="headers_extra" type="object">
    Extra request headers. Empty (`{}`) for every OpenAI model.
  </ResponseField>

  <ResponseField name="description" type="string">
    A usage blurb describing the model's strengths.
  </ResponseField>

  <ResponseField name="data_freshness" type="string">
    The knowledge cutoff date (ISO date). There is no separate `knowledge_cutoff` field.
  </ResponseField>

  <ResponseField name="intelligence" type="integer">
    A quality score, `1`–`5`.
  </ResponseField>

  <ResponseField name="speed" type="integer">
    A latency score, `1`–`5` (higher is faster).
  </ResponseField>

  <ResponseField name="supports_vision" type="boolean">
    Whether the model accepts image input.
  </ResponseField>

  <ResponseField name="input_usd_per_1m_tokens" type="number">
    OpenAI's list input price per 1M tokens (flat field, not a nested `pricing` object).
  </ResponseField>

  <ResponseField name="output_usd_per_1m_tokens" type="number">
    OpenAI's list output price per 1M tokens. `0` for embedding models.
  </ResponseField>

  <ResponseField name="is_embedding" type="boolean">
    Present and `true` only on embedding models.
  </ResponseField>

  <ResponseField name="embedding_dimension" type="integer">
    The output vector dimension — present only on embedding models (`3072` for large, `1536` for small).
  </ResponseField>
</Expandable>

## Select an OpenAI model

Across the API and SDKs, you choose a model with an `llm` configuration object. For OpenAI, every field uses the `openai` slug:

<ParamField body="integration_name" type="string" required>
  `openai`.
</ParamField>

<ParamField body="provider_id" type="string" required>
  `openai`.
</ParamField>

<ParamField body="model_id" type="string" required>
  A catalog model id from [Available models](#available-models), for example `gpt-5.5`.
</ParamField>

<ParamField body="credential_id" type="string">
  The id of the OpenAI credential to use. Omit it to let ModuleX [resolve a credential](/concepts/credentials-oauth#credential-resolution-at-run-time): your `is_default` OpenAI credential, then the most-recent valid one. ModuleX prefers your own keys over managed keys — and OpenAI is BYOK-only, so resolution always lands on a key you connected.
</ParamField>

This is the same shape the [Assistant](/concepts/assistant) and [AI Composer](/concepts/ai-composer) accept for their `llm` field:

```json llm config theme={null}
{
  "integration_name": "openai",
  "provider_id": "openai",
  "model_id": "gpt-5.5",
  "credential_id": "c0ffee00-1111-2222-3333-444455556666"
}
```

<Tabs>
  <Tab title="In a workflow (LLM node)">
    In the [LLM node](/workflow-builder/nodes/llm) (and the [Agent node](/workflow-builder/nodes/agent)), set the node's `llm_config` to an OpenAI model. ModuleX resolves the standardized `model_id` to OpenAI's wire model via the catalog's `body_extra.model`, then calls OpenAI with your resolved credential. See [Workflow engine & nodes](/concepts/workflow-engine).
  </Tab>

  <Tab title="In the Assistant">
    Pass the `llm` object when you start an Assistant chat. The Assistant rebuilds the model from this config on every turn and on resume, so the same `llm` object must be supplied each time. See [Models & settings](/assistant/models-and-settings).
  </Tab>

  <Tab title="In the AI Composer">
    The Composer takes the same `llm` object to generate and edit your workflow graph. If you omit it, ModuleX falls back to the organization's configured Composer default model. See [AI Composer in the builder](/workflow-builder/composer).
  </Tab>

  <Tab title="In chat">
    In the app's [chat](/platform/chat/overview), pick an OpenAI model from the [model selector](/platform/chat/model-selection). The connected OpenAI credential is used automatically.
  </Tab>
</Tabs>

<Note>
  **Deprecation routing applies only to managed models.** For managed integrations (`modulexai`), a model marked `deprecated` routes to its `replacement_id` automatically, and unknown ids fall back to the integration default. OpenAI is BYOK (a passthrough integration), so its `model_id` is used as-is — an unknown id is sent straight to OpenAI and fails there. None of the OpenAI catalog models are currently marked deprecated.
</Note>

## Browse OpenAI in the catalog

The [integration catalog](/integrations/catalog) is the read-only discovery surface. Use it to fetch OpenAI's live models, pricing, and auth schema. These catalog endpoints authenticate with a **Clerk JWT** (the app token), not an `mx_live_*` API key, and still require `X-Organization-ID` and the owner/admin role.

<ParamField path="provider_name" type="string" required>
  `openai`.
</ParamField>

**Response** — `200 OK`, an `IntegrationDetail`:

<ResponseField name="name" type="string">
  `openai`.
</ResponseField>

<ResponseField name="display_name" type="string">
  `OpenAI`.
</ResponseField>

<ResponseField name="integration_type" type="string">
  `llm_provider`.
</ResponseField>

<ResponseField name="models" type="object[]">
  The OpenAI model list described in [Available models](#available-models).
</ResponseField>

<ResponseField name="auth_schemas" type="object[]">
  The OpenAI auth schemas. OpenAI ships a single `api_key` schema with `setup_instructions`, the `api_key` and `base_url` `setup_environment_variables`, and a `test_endpoint`.
</ResponseField>

<ResponseField name="docs_url" type="string | null">
  `https://platform.openai.com/docs`.
</ResponseField>

<ResponseField name="metadata" type="object | null">
  The integration's `extra_metadata` (`status`, `recommended`, timestamps, popularity score).
</ResponseField>

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.modulex.dev/integrations/llm-providers/openai \
    -H "Authorization: Bearer <clerk_jwt_token>" \
    -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111"
  ```

  ```bash cURL — list all LLM providers theme={null}
  curl https://api.modulex.dev/integrations/llm-providers \
    -H "Authorization: Bearer <clerk_jwt_token>" \
    -H "X-Organization-ID: 11111111-1111-1111-1111-111111111111"
  ```
</CodeGroup>

<Expandable title="Example detail response (truncated)">
  ```json theme={null}
  {
    "name": "openai",
    "display_name": "OpenAI",
    "description": "OpenAI's GPT models for chat, completion, and embeddings",
    "logo": "modulex:openai-themed",
    "app_url": "https://platform.openai.com",
    "docs_url": "https://platform.openai.com/docs",
    "categories": ["AI & LLM Providers", "ai", "chat", "completion"],
    "integration_type": "llm_provider",
    "version": "1.0.0",
    "auth_schemas": [
      {
        "auth_type": "api_key",
        "display_name": "API Key Authentication",
        "description": "Connect using OpenAI API Key",
        "setup_environment_variables": [
          { "name": "api_key", "required": true, "sensitive": true },
          { "name": "base_url", "required": false, "default": "https://api.openai.com/v1" }
        ],
        "test_endpoint": { "url": "https://api.openai.com/v1/chat/completions", "method": "POST" }
      }
    ],
    "models": [
      {
        "id": "gpt-5.5",
        "display_name": "GPT 5.5",
        "provider": "OpenAI",
        "provider_id": "openai",
        "max_input_tokens": 1050000,
        "max_output_tokens": 128000,
        "supports_vision": true,
        "input_usd_per_1m_tokens": 5.0,
        "output_usd_per_1m_tokens": 30.0
      }
    ],
    "metadata": { "status": "active", "recommended": true, "popularity_score": 92 }
  }
  ```
</Expandable>

<Warning>
  ModuleX maintains a backend-only `llm_max_tokens` resolver that caps output tokens per scenario (for example chat vs workflow node). Those values are **never returned by any catalog API response** — do not rely on the catalog `max_output_tokens` as the effective per-scenario output cap.
</Warning>

## BYOK billing

OpenAI is **bring-your-own-key**. Usage on your OpenAI credential is billed by OpenAI to your OpenAI account and is **not** metered in ModuleX [credits](/concepts/credits-billing).

<CardGroup cols={2}>
  <Card title="No ModuleX credit charge" icon="circle-check">
    BYOK calls — OpenAI included — carry no ModuleX credit gate. There is no markup, and these calls do not draw down your plan allowance or [wallet](/billing/wallet).
  </Card>

  <Card title="No billing-gate denials" icon="shield-check">
    Because there is no credit gate on BYOK calls, OpenAI usage does not produce the `402`/`403`/`429` `DenialEnvelope` that managed usage can. See [Usage gating & limits](/billing/usage-gating).
  </Card>

  <Card title="Billed by OpenAI" icon="building-columns">
    Token costs accrue on your OpenAI account at OpenAI's rates. Set spend limits and monitor usage in the OpenAI dashboard.
  </Card>

  <Card title="Analytics only" icon="chart-line">
    ModuleX records token counts and the catalog list price for OpenAI usage for **analytics**, so you can see consumption per workflow or run — but no credits are deducted.
  </Card>
</CardGroup>

<Note>
  **Want credit-metered, no-key-required models instead?** Use [ModuleX-managed models](/integrations/llm-providers/modulexai) (the `modulexai` provider), which run on ModuleX-provisioned keys and bill in credits — gated by your plan. There is no managed (pooled-key) path for `openai`; OpenAI is always BYOK. See [Credits & metering](/billing/credits).
</Note>

<Note>
  Rate limits, quotas, and model availability for BYOK calls are governed by **your OpenAI account**, not by ModuleX. A `429` or quota error from OpenAI surfaces as an upstream error on the run, distinct from ModuleX's own [rate limiting](/api-reference/rate-limiting).
</Note>

## Managing the credential

Day-to-day operations use the standard [credentials API](/concepts/credentials-oauth#the-credentials-api). For OpenAI specifically:

* **Rotate a key.** Secrets are immutable through `PUT /credentials/{id}` (that route updates only `display_name` and metadata). To rotate, create a new OpenAI credential with the new key and delete the old one — there is no rotate endpoint.
* **Set the default.** `POST /credentials/{id}/set-default` makes one OpenAI credential the organization default, used when no `credential_id` is supplied.
* **Test a saved key.** `POST /credentials/{id}/test` re-runs OpenAI's test endpoint against the stored key.
* **Delete.** `DELETE /credentials/{id}` removes it and returns `204`. Runs that relied on it will fail credential resolution until another valid OpenAI credential exists.

See [Managing credentials](/integrations/managing-credentials) for the app workflow and [Credentials & OAuth2](/concepts/credentials-oauth) for the complete API.

## Where to go next

<CardGroup cols={2}>
  <Card title="LLM providers" icon="layer-group" href="/integrations/llm-providers/overview">
    Compare OpenAI with the other model providers, managed and BYOK.
  </Card>

  <Card title="ModuleX-managed models" icon="server" href="/integrations/llm-providers/modulexai">
    The credit-metered, no-key-required default provider.
  </Card>

  <Card title="Credentials & OAuth2" icon="key" href="/concepts/credentials-oauth">
    The full credential model, resolution precedence, and credentials API.
  </Card>

  <Card title="LLM node" icon="diagram-project" href="/workflow-builder/nodes/llm">
    Call an OpenAI model from a workflow with prompts, variables, and structured output.
  </Card>
</CardGroup>
