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

# Configure BYOK Models with an Agent

> Configure custom models with an agent and verify real responses using curl on macOS and Windows.

<div className="code-docs">
  For setup through the app interface, see [Custom Models / BYOK](/docs/code/account/byok).

  Use this guide to have your agent configure a custom model in the MiniMax Code desktop app, then send a curl request to confirm that the provider returns an actual response.

  Updated September 16, 2026. Configuration formats may differ between app versions. If your existing configuration differs from this guide, confirm the format supported by your installed version before making changes.

  ## 1. Confirm the connection details

  | Detail                    | What to confirm                                                                                                          |
  | ------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
  | Target app                | Identify the desktop installation and its data directory. Do not edit another installation or account's configuration.   |
  | Base URL                  | Use the URL confirmed by the user or provider documentation, including any required gateway path prefix.                 |
  | API format                | Choose `anthropic-messages`, `openai-completions`, or `openai-responses`. Do not infer the protocol from the model name. |
  | API key                   | Use the supplied key or preserve the existing one. A masked value is not a usable key.                                   |
  | Model ID                  | Use the exact ID accepted by the provider, not a display name.                                                           |
  | Context and output limits | Confirm the limits for this model and endpoint. Form defaults are not evidence of model capabilities.                    |
  | Reasoning and attachments | Declare only supported effort levels and input types. Omit optional capabilities that have not been confirmed.           |
  | Requested change          | Adding a model, selecting it for a conversation, and changing the global default are separate actions.                   |

  Model availability does not imply support for every protocol, reasoning level, or attachment type. Ask for missing connection details rather than inventing values.

  ## 2. Locate and preserve the configuration

  Identify the data directory using information from the app or confirmed by the user. Do not guess the path.

  The file is `<dataDir>/config.yaml` on macOS and `<dataDir>\config.yaml` on Windows. The YAML fields and API request bodies are the same on both platforms; shell commands differ. Custom installation or launch settings can change the data directory, so do not assume it is always `~/.minimax/config.yaml`.

  Read and back up the existing file before editing it. Change only the intended provider and preserve other fields, providers, and models. Avoid saving settings in the app at the same time, and do not replace the entire file with the example below.

  Preserve existing credentials when they are unchanged. Do not replace them with masks, empty strings, or placeholders. Do not print real keys or sensitive headers in conversations, logs, or shared configuration.

  Read the file again after saving and confirm that the app recognizes the model. If the app has not loaded the change, restart it when the user permits and check again. Saving a file alone does not prove the change is active.

  ## 3. Configure a custom provider

  Put custom providers under `custom_provider`. Do not put them in the managed `provider` tree or the dedicated `minimax_api` configuration.

  Merge this provider template into the existing configuration. Replace `example.invalid`, `REPLACE_WITH_REAL_API_KEY`, and `REPLACE_WITH_EXACT_MODEL_ID`. The numbers `32000` and `2048` illustrate numeric fields; replace them with limits confirmed for the provider.

  ```yaml theme={null}
  custom_provider:
    my-gateway:
      name: "My Gateway"
      kind: custom
      enabled: true
      api: openai-completions
      options:
        baseURL: "https://example.invalid/v1"
        apiKey: "REPLACE_WITH_REAL_API_KEY"
      models:
        "REPLACE_WITH_EXACT_MODEL_ID":
          name: "My Model"
          enabled: true
          limit:
            context: 32000
            output: 2048
          modalities:
            input: [text]
            output: [text]
  ```

  * Choose a unique provider key such as `my-gateway`. Change only `name` when renaming its display label. Avoid reserved keys: `minimax`, `minimax_api`, `openai-codex`, `provider`, and `custom_provider`.
  * Set `api` at provider level to one of the three supported format names. Brand names such as `openai` or `anthropic` are not format values.
  * Use the exact casing `options.baseURL` and put the key in `options.apiKey`.
  * `models` is a map keyed by exact model IDs, not an array. Preserve any `/` characters within the model ID.
  * `limit.context` and `limit.output` are positive integer token counts, not strings such as `128K` or `1M`. They describe different limits.
  * Do not infer effort levels such as `high` or `max`, or attachment support, from a model's name.
  * If tool calling is required, confirm provider support before adding `tool_call: true` to the model and verify a real tool request. Declaring a capability does not implement it upstream.
  * Quote YAML strings where appropriate. Do not assume `${API_KEY}` expands automatically: `apiKey` must contain the actual key.

  ### API format and endpoint

  | `api`                | Typical Base URL                                                       | Generation endpoint             |
  | -------------------- | ---------------------------------------------------------------------- | ------------------------------- |
  | `anthropic-messages` | `https://example.invalid` or a provider-specified compatibility prefix | `<normalized base>/v1/messages` |
  | `openai-completions` | `https://example.invalid/v1`                                           | `<base>/chat/completions`       |
  | `openai-responses`   | `https://example.invalid/v1`                                           | `<base>/responses`              |

  Change both the API format and the address when adapting the template. OpenAI-style endpoints do not automatically acquire `/v1`; include it when required. Messages normalizes trailing `/v1`, `/messages`, or `/v1/messages`. Do not append these repeatedly or remove a gateway prefix such as `/proxy/anthropic`.

  Use the documented base address rather than a generation URL with temporary authentication parameters. Check the final endpoint when troubleshooting. A 404 may indicate either an incorrect path or an unavailable model.

  ### Custom headers

  Add headers under `options.headers` only when needed:

  ```yaml theme={null}
  options:
    headers:
      X-Project-Id: "REPLACE_WITH_PROJECT_ID"
  ```

  Merge this into the existing `options`; preserve `baseURL` and `apiKey`. Header names are case-insensitive, so do not configure both `Authorization` and `authorization`. Explicit headers override protocol defaults. Do not override `Authorization`, `x-api-key`, or `content-type` without a specific provider requirement.

  ## 4. Add reasoning or attachments only when supported

  Once support is confirmed, add the appropriate fields under the model:

  ```yaml theme={null}
  reasoning: true
  thinking:
    effortOptions: [low, medium, high]
  ```

  These effort values are examples, not universal defaults. `effortOptions` stores the available choices; do not save a conversation's current choice as `thinking.effort`. For a regular custom model without a conversation override, the default is the middle entry, choosing the later entry when the list has an even length.

  Ordinary non-disabled effort values typically use `output_config.effort` for Messages, `reasoning_effort` for Completions, and `reasoning.effort` for Responses. `off` and `none` are disabled values with dedicated mappings. Some models have additional adaptations; changing the provider's display name does not select a different protocol.

  For a custom MiniMax-M3 model with confirmed binary thinking support, use `effortOptions: ["off", "on"]`. Quote these strings in YAML. The request mapping is:

  | Protocol               | `on`                       | `off`                    |
  | ---------------------- | -------------------------- | ------------------------ |
  | Messages / Completions | `thinking.type=adaptive`   | `thinking.type=disabled` |
  | Responses              | `reasoning.effort=minimal` | `reasoning.effort=none`  |

  Keep `text` in `modalities.input` and add only confirmed `image`, `pdf`, `video`, or `audio` capabilities. Keep output as `[text]`. A text connectivity request does not verify attachments or tool calling; test those separately if needed.

  ## 5. Verify an actual model response with curl

  Read the saved provider configuration and build the request from those exact values. Do not substitute another key, provider, or model. Requesting `/models` is insufficient: listing a model does not prove it can generate a response.

  ### 5.1 Build the request from the saved configuration

  | Request value    | Configuration source                                                                 |
  | ---------------- | ------------------------------------------------------------------------------------ |
  | Protocol         | Provider `api`                                                                       |
  | Endpoint         | Normalize `options.baseURL` as described in section 3 and append the generation path |
  | Key              | Actual `options.apiKey`, never a masked value                                        |
  | Custom headers   | `options.headers`, with model-level `headers` taking precedence for matching names   |
  | Request `model`  | The target model's exact key under `models`                                          |
  | Output limit     | Model `limit.output`                                                                 |
  | Reasoning fields | Configured effort choices, converted according to section 4                          |

  Check the configuration syntax and enabled states, and ensure other models were preserved. Do not silently lower the output limit, drop custom headers, or switch effort levels to make the check pass. If the configuration needs correction, save it and test those saved values again.

  The API key normally excludes the `Bearer ` prefix. Merge the default headers below with custom headers case-insensitively, retaining one final value per name:

  | Protocol                | Default headers                                                                           |
  | ----------------------- | ----------------------------------------------------------------------------------------- |
  | Messages                | `Content-Type: application/json`, `x-api-key: <API key>`, `anthropic-version: 2023-06-01` |
  | Completions / Responses | `Content-Type: application/json`, `Authorization: Bearer <API key>`                       |

  ### 5.2 Create the request body

  Choose one template matching the saved `api`. Replace the model placeholder and replace `2048` with the saved positive integer `limit.output`. Serialize JSON rather than interpolating unescaped values into a shell command.

  **Anthropic Messages:** POST to `<normalized base>/v1/messages`.

  ```json theme={null}
  {
    "model": "REPLACE_WITH_EXACT_MODEL_ID",
    "max_tokens": 2048,
    "stream": false,
    "messages": [{"role": "user", "content": "Reply with BYOK_OK only"}]
  }
  ```

  **OpenAI Chat Completions:** POST to `<normalized base>/chat/completions`.

  ```json theme={null}
  {
    "model": "REPLACE_WITH_EXACT_MODEL_ID",
    "max_tokens": 2048,
    "stream": false,
    "messages": [{"role": "user", "content": "Reply with BYOK_OK only"}]
  }
  ```

  **OpenAI Responses:** POST to `<normalized base>/responses`.

  ```json theme={null}
  {
    "model": "REPLACE_WITH_EXACT_MODEL_ID",
    "max_output_tokens": 2048,
    "stream": false,
    "input": "Reply with BYOK_OK only"
  }
  ```

  These templates omit reasoning fields. If `effortOptions` is configured, build and test a separate request for each level using section 4's mappings. Do not invent a level when none is configured. Record all results, including levels tested after a failure; a partial pass is not a full pass.

  ### 5.3 Run curl

  Use request files on both platforms to avoid shell quoting and encoding differences. Prepare the complete endpoint, merged headers (one `Name: value` per line), and a UTF-8 JSON file without a byte-order mark (BOM). Header names and values must not contain newline characters. Keep the real key in the temporary header file, not in shared commands or conversation output.

  **macOS: sh or zsh**

  Create a private temporary directory:

  ```sh theme={null}
  umask 077
  BYOK_CHECK_DIR="$(mktemp -d)"
  ```

  Write the merged headers to `$BYOK_CHECK_DIR/headers.txt` and the completed JSON body to `$BYOK_CHECK_DIR/request.json`. Set `BYOK_URL` to the complete endpoint derived from the configuration, then run:

  ```sh theme={null}
  BYOK_HTTP_STATUS="$(curl --silent --show-error \
    --connect-timeout 10 \
    --max-time 90 \
    --request POST \
    --url "$BYOK_URL" \
    --header "@$BYOK_CHECK_DIR/headers.txt" \
    --data-binary "@$BYOK_CHECK_DIR/request.json" \
    --output "$BYOK_CHECK_DIR/response.json" \
    --write-out '%{http_code}')"
  BYOK_CURL_EXIT=$?
  printf 'curl_exit=%s http_status=%s\n' "$BYOK_CURL_EXIT" "$BYOK_HTTP_STATUS"
  ```

  **Windows: Windows PowerShell 5.1 or PowerShell 7, not cmd**

  Check that `curl.exe` is available. Call it explicitly to avoid the Windows PowerShell `curl` alias:

  ```powershell theme={null}
  $null = Get-Command curl.exe -ErrorAction Stop
  $BYOK_CHECK_DIR = Join-Path ([System.IO.Path]::GetTempPath()) ("byok-check-" + [guid]::NewGuid().ToString('N'))
  $null = New-Item -ItemType Directory -Path $BYOK_CHECK_DIR -ErrorAction Stop
  ```

  Use the current user's temporary directory, not a shared folder. Set `$BYOK_URL` from the configuration, put the merged header lines in the string array `$ByokHeaderLines`, and create `$ByokBody` from the request template. Do not print these variables. Write the files as follows to avoid PowerShell 5.1 redirection encoding and UTF-8 BOM issues:

  ```powershell theme={null}
  $ByokUtf8 = [System.Text.UTF8Encoding]::new($false)
  $ByokHeaderPath = Join-Path $BYOK_CHECK_DIR 'headers.txt'
  $ByokRequestPath = Join-Path $BYOK_CHECK_DIR 'request.json'
  $ByokResponsePath = Join-Path $BYOK_CHECK_DIR 'response.json'
  $ByokRequestJson = ConvertTo-Json -InputObject $ByokBody -Depth 20 -Compress
  [System.IO.File]::WriteAllText($ByokHeaderPath, ($ByokHeaderLines -join "`r`n") + "`r`n", $ByokUtf8)
  [System.IO.File]::WriteAllText($ByokRequestPath, $ByokRequestJson, $ByokUtf8)
  ```

  Pass arguments as an array so paths containing spaces do not require manually constructed quoting:

  ```powershell theme={null}
  $ByokCurlArgs = @(
    '--silent', '--show-error',
    '--connect-timeout', '10',
    '--max-time', '90',
    '--request', 'POST',
    '--url', $BYOK_URL,
    '--header', ('@' + $ByokHeaderPath),
    '--data-binary', ('@' + $ByokRequestPath),
    '--output', $ByokResponsePath,
    '--write-out', '%{http_code}'
  )
  $BYOK_HTTP_STATUS = & curl.exe @ByokCurlArgs
  $BYOK_CURL_EXIT = $LASTEXITCODE
  Write-Output ("curl_exit={0} http_status={1}" -f $BYOK_CURL_EXIT, $BYOK_HTTP_STATUS)
  ```

  Save `$LASTEXITCODE` immediately after curl. PowerShell's `$?` is not the numeric curl exit code.

  Read the response explicitly as UTF-8, then check errors and assistant text using section 5.4:

  ```powershell theme={null}
  $ByokResponseText = [System.IO.File]::ReadAllText($ByokResponsePath, [System.Text.Encoding]::UTF8)
  $ByokResponse = ConvertFrom-Json -InputObject $ByokResponseText -ErrorAction Stop
  ```

  A missing response file or JSON parsing failure is an error, not a pass. Redact credentials before showing raw error details.

  On either platform, do not add `-k` to disable certificate verification or `-v` to print authentication headers. Do not automatically follow redirects to another host. Keep error bodies for local inspection and redact keys, sensitive headers, and authentication details before sharing them.

  ### 5.4 Decide whether the check passed

  Report an actual model response only when all of the following hold:

  1. `BYOK_CURL_EXIT=0`, the HTTP status is 2xx, and the body is valid JSON.
  2. There is no upstream `error` or nonzero business error such as `base_resp.status_code`. HTML, an empty object, and an empty body do not pass.
  3. The protocol's assistant output contains non-empty text. A request ID, usage record, or reasoning content alone is insufficient.

  | Protocol    | Where to find assistant text                                                                                    |
  | ----------- | --------------------------------------------------------------------------------------------------------------- |
  | Messages    | `text` in `content` entries with `type="text"`                                                                  |
  | Completions | Text in `choices[].message.content`; extract text blocks if content is an array                                 |
  | Responses   | `output` entries with `type="message"` and `role="assistant"`, then `content` entries with `type="output_text"` |

  The requested reply is `BYOK_OK`. Other non-empty assistant text proves a response was received, but report that it did not follow the requested wording. Do not fabricate the expected text. For Responses, inspect the raw `output` array rather than relying on an SDK's top-level `output_text` convenience property.

  Also check completion status. Messages `stop_reason=max_tokens`, Completions `finish_reason=length`, or Responses `status=incomplete/failed` or `incomplete_details` indicate truncation or an unfinished response. Record the reason rather than reporting a complete pass. If there is only reasoning and no assistant text, investigate the output budget, effort level, and protocol adaptation.

  Record results for every configured model and effort level tested. A success for one does not validate the rest. Attribute timeouts, 429 responses, and authentication failures to their actual cause; do not silently fall back to a managed model.

  ### 5.5 Report the result

  Report the configuration location, provider, model ID, protocol, redacted endpoint, tested effort level, HTTP status, and a short excerpt of the actual response. On failure, provide the redacted error and the configuration fields that need attention. Delete the temporary files after verification, especially `headers.txt` containing the real key.

  A successful curl request proves that the provider accepted these connection and request parameters and returned text directly. Confirm separately that the desktop app loaded the configuration. It does not prove full agent conversations, tools, or attachments work. If a command was only generated, label it **not yet run**.

  Saving does not automatically test connectivity, and cached test status does not prevent selecting a model. Neither “saved” nor “selectable” replaces a real request.

  ## 6. Troubleshooting

  | Symptom                                  | Check first                                                                                                                         |
  | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
  | Saved model does not appear              | Correct data directory, provider and model enabled, configuration under `custom_provider`, app loaded the change                    |
  | 401 / 403                                | Key belongs to this provider; no mask or unexpanded placeholder; custom headers did not override authentication; account has access |
  | 404                                      | Matching API format and Base URL; required `/v1` and gateway prefix preserved; exact model ID exists                                |
  | Unsupported effort                       | Effort is supported by this model; no old model's level was carried over; M3 on/off uses its dedicated mapping                      |
  | Output or context limit rejected         | Form defaults were not assumed to be model capabilities; context and output limits were not swapped                                 |
  | Text works but attachments or tools fail | Declared capability is supported by the provider and protocol, including the required input or tool format                          |
  | Failed test but model remains selectable | Test status is diagnostic. Fix the upstream error rather than inventing a successful test record.                                   |

  Keep the selected provider when a BYOK request fails and report the error. Do not silently switch to Token Plan, a managed model, or another provider. Do not hide an upstream error by disabling validation, inflating limits, or changing unknown fields.
</div>
