---
schema_version: "1"
id: 47822f0b029a465be428f4aa284900d30b8e746e793464d97fa0d2588a746830
title: Publishing Custom LLM Providers to Anypoint Exchange
collection: mulesoft
version: current
locale: en-us
portal_url: https://docs.salesforce.com/mulesoft/current/en-us/general/model-proxy-publish-custom-llm-metadata.md
canonical_url: https://docs.mulesoft.com/general/model-proxy-publish-custom-llm-metadata
source_site: docs.mulesoft.com
content_type: documentation
content_hash: af94a8a18afa5cc80a01d191d97017976e940b44d37a59d95b7f27c8a0cb7adb
description: MuleSoft Documentation Site
latest: true
---
---
product: Getting Started
---

# Publishing Custom LLM Providers to Anypoint Exchange

> For the full documentation index, see: https://docs.mulesoft.com/llms.txt

If your LLM provider or gateway is not one of the built-in
[Model Proxy providers](https://docs.mulesoft.com/general/model-proxy#supported-llm-providers), publish it to Anypoint Exchange as an
llm-metadata.json classifier. Model Proxy can then route traffic to that provider. You create the
classifier, confirm the asset version is free, and publish it with the Exchange API.

## Before You Begin

Before getting started, make sure you have:

- `curl` installed

  To install, see [Install curl](https://curl.se/download.html).

- `jq` installed

  To install, see [Install jq](https://jqlang.org/download/).

- An Anypoint [connected app](https://docs.mulesoft.com/access-management/creating-connected-apps-dev) that:

  - Acts on its own behalf with the `client_credentials` grant type.

  - Has the **Exchange Contributor** or **Exchange Administrator** scope assigned to the target
    business group.

## Create the Classifier

The `llm-metadata.json` classifier is a JSON object. Only `platform` is required. Unknown properties
aren’t accepted, so use only the documented fields.

<table><colgroup><col> <col> <col> <col></colgroup><thead><tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td><p><code>platform</code></p></td><td><p>String</p></td><td><p>Yes</p></td><td><p>Provider or platform identifier, such as <code>Gemini</code>, <code>OpenAI</code>, or a custom name.</p></td></tr><tr><td><p><code>name</code></p></td><td><p>String</p></td><td><p>No</p></td><td><p>Human-readable platform name.</p></td></tr><tr><td><p><code>inputFormat</code></p></td><td><p>String</p></td><td><p>No</p></td><td><p>Wire format: <code>openai</code>, <code>anthropic</code>, or <code>gemini</code>.</p></td></tr><tr><td><p><code>kind</code></p></td><td><p>String</p></td><td><p>No</p></td><td><p>Platform type, for example <code>llm-gw</code>.</p></td></tr><tr><td><p><code>authentication</code></p></td><td><p>Array</p></td><td><p>No</p></td><td><p>Supported provider authentication methods.</p></td></tr><tr><td><p><code>models</code></p></td><td><p>Array</p></td><td><p>No</p></td><td><p>Model ID strings or detailed model objects.</p></td></tr><tr><td><p><code>defaultUrl</code></p></td><td><p>Array</p></td><td><p>No</p></td><td><p>Named provider URL entries. Each entry requires both <code>name</code> and <code>url</code>.</p></td></tr><tr><td><p><code>capabilities</code></p></td><td><p>Object</p></td><td><p>No</p></td><td><p>Platform-wide supported features.</p></td></tr><tr><td><p><code>policyRef</code></p></td><td><p>Object</p></td><td><p>No</p></td><td><p>Adapter policy coordinates. Requires <code>groupId</code> and <code>assetId</code>. <code>version</code> is optional.</p></td></tr><tr><td><p><code>provenance</code></p></td><td><p>Object</p></td><td><p>No</p></td><td><p>Origin information. Supports <code>id</code>, <code>createdAt</code>, <code>updatedAt</code>, <code>version</code>, <code>status</code>, and <code>url</code>. Use RFC 3339 date-time strings for timestamps.</p></td></tr></tbody></table>

### Authentication

Each `authentication` entry requires `kind`. Supported values are:

- `none`

- `apiKey`

- `amazonSigv4`

Authentication entries can include non-secret defaults such as header names, token URLs, scopes, or
timeouts. Never include actual API keys, passwords, client secrets, or tokens. If `kind` is `none`,
it must be the only authentication entry.

<table><colgroup><col> <col></colgroup><thead><tr><th>Authentication Kind</th><th>Optional Configuration</th></tr></thead><tbody><tr><td><p><code>apiKey</code></p></td><td><p><code>headerName</code> (required when a configuration is present), <code>apiKeyPrefix</code>, <code>headersToRemove</code>, and <code>additionalHeaders</code></p></td></tr></tbody></table>

### Models

The `models` field is an array. Each item can be either of these forms:

- A string containing only the provider’s model identifier, such as `"gemini-2.5-flash"`.

- A detailed object. The `id` field is required. All other fields are optional.

<table><colgroup><col> <col> <col></colgroup><thead><tr><th>Model Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><p><code>id</code></p></td><td><p>String</p></td><td><p>Provider-specific model identifier.</p></td></tr><tr><td><p><code>label</code></p></td><td><p>String</p></td><td><p>Human-readable model name.</p></td></tr><tr><td><p><code>description</code></p></td><td><p>String</p></td><td><p>Summary of the model and its intended use.</p></td></tr><tr><td><p><code>limits</code></p></td><td><p>Object</p></td><td><p>Context, output, and prompt-caching limits.</p></td></tr><tr><td><p><code>capabilities</code></p></td><td><p>Object</p></td><td><p>Features supported by this specific model.</p></td></tr><tr><td><p><code>pricing</code></p></td><td><p>Object</p></td><td><p>Prices per million tokens.</p></td></tr><tr><td><p><code>lifecycle</code></p></td><td><p>Object</p></td><td><p>Release and retirement information.</p></td></tr></tbody></table>

The nested objects support these fields:

<table><colgroup><col> <col> <col> <col></colgroup><thead><tr><th>Object</th><th>Field</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td><p><code>limits</code></p></td><td><p><code>maxInputTokens</code></p></td><td><p>Integer</p></td><td><p>Maximum number of input tokens.</p></td></tr><tr><td><p><code>limits</code></p></td><td><p><code>maxOutputTokens</code></p></td><td><p>Integer</p></td><td><p>Maximum number of generated tokens.</p></td></tr><tr><td><p><code>limits</code></p></td><td><p><code>supportsPromptCaching</code></p></td><td><p>Boolean</p></td><td><p>Whether prompt caching is supported.</p></td></tr><tr><td><p><code>limits</code></p></td><td><p><code>maxCachedTokens</code></p></td><td><p>Integer</p></td><td><p>Maximum number of tokens that can be cached.</p></td></tr><tr><td><p><code>pricing</code></p></td><td><p><code>inputPerMillion</code></p></td><td><p>Number</p></td><td><p>Price per million input tokens.</p></td></tr><tr><td><p><code>pricing</code></p></td><td><p><code>outputPerMillion</code></p></td><td><p>Number</p></td><td><p>Price per million output tokens.</p></td></tr><tr><td><p><code>pricing</code></p></td><td><p><code>cachedInputPerMillion</code></p></td><td><p>Number</p></td><td><p>Price per million cached input tokens.</p></td></tr><tr><td><p><code>pricing</code></p></td><td><p><code>reasoningPerMillion</code></p></td><td><p>Number</p></td><td><p>Price per million reasoning tokens.</p></td></tr><tr><td><p><code>lifecycle</code></p></td><td><p><code>status</code></p></td><td><p>String</p></td><td><p>Provider-defined state, such as <code>active</code>, <code>deprecated</code>, or <code>sunset</code>.</p></td></tr><tr><td><p><code>lifecycle</code></p></td><td><p><code>releasedAt</code></p></td><td><p>String</p></td><td><p>Release date or timestamp.</p></td></tr><tr><td><p><code>lifecycle</code></p></td><td><p><code>sunsetAt</code></p></td><td><p>String or null</p></td><td><p>Known retirement date, or <code>null</code>.</p></td></tr></tbody></table>

This example shows both model forms.

```json
{
  "models": [
    "gemini-2.5-flash",
    {
      "id": "gemini-2.5-pro",
      "label": "Gemini 2.5 Pro",
      "description": "A multimodal model for complex reasoning tasks.",
      "limits": {
        "maxInputTokens": 1000000,
        "maxOutputTokens": 8192,
        "supportsPromptCaching": true,
        "maxCachedTokens": 500000
      },
      "capabilities": {
        "toolCalling": true,
        "structuredOutput": true,
        "imageInput": true,
        "reasoning": {
          "supported": true,
          "type": "adaptive",
          "effortLevels": ["low", "medium", "high"]
        }
      },
      "pricing": {
        "inputPerMillion": 1.25,
        "outputPerMillion": 5.0,
        "cachedInputPerMillion": 0.3125,
        "reasoningPerMillion": 5.0
      },
      "lifecycle": {
        "status": "active",
        "releasedAt": "2025-03-25T00:00:00Z",
        "sunsetAt": null
      }
    }
  ]
}
```

### Capabilities

Declare capabilities at the platform level or within a detailed model. The boolean flags are:

- `embeddings`

- `toolCalling`

- `structuredOutput`

- `codeExecution`

- `citations`

- `batchProcessing`

- `imageInput`

- `pdfInput`

Reasoning support isn’t a boolean flag. Declare it with a nested `reasoning` object that describes
whether the platform or model supports reasoning and how it’s controlled:

<table><colgroup><col> <col> <col> <col></colgroup><thead><tr><th>Field</th><th>Type</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td><p><code>supported</code></p></td><td><p>Boolean</p></td><td><p>Yes</p></td><td><p>Whether the platform or model supports reasoning.</p></td></tr><tr><td><p><code>type</code></p></td><td><p>String</p></td><td><p>No</p></td><td><p>The reasoning mode the provider uses, for example <code>adaptive</code>.</p></td></tr><tr><td><p><code>effortLevels</code></p></td><td><p>Array of strings</p></td><td><p>No</p></td><td><p>The reasoning effort levels that the provider accepts, for example <code>low</code>, <code>medium</code>, and <code>high</code>.</p></td></tr></tbody></table>

In this example, the model supports reasoning and accepts three effort levels:

```json
"capabilities": {
  "reasoning": {
    "supported": true,
    "type": "adaptive",
    "effortLevels": ["low", "medium", "high"]
  }
}
```

If the platform or model doesn’t support reasoning, omit the `reasoning` object or set `supported`
to `false`.

With the exception of a custom authentication method’s provider-specific configuration, nested
objects don’t accept undocumented fields.

### Example LLM Provider Asset

Create a file named `llm-metadata.json`:

```json
{
  "platform": "Geminiano",
  "name": "Geminiano",
  "inputFormat": "gemini",
  "kind": "llm-gw",
  "authentication": [
    {
      "kind": "apiKey",
      "defaultConfiguration": {
        "headerName": "X-goog-api-key",
        "headersToRemove": ["Authorization"]
      }
    }
  ],
  "models": ["gemini-2.5-flash"],
  "defaultUrl": [
    {
      "name": "default",
      "url": "https://generativelanguage.googleapis.com/v1beta/"
    }
  ],
  "capabilities": {
    "toolCalling": true,
    "structuredOutput": true,
    "imageInput": true,
    "pdfInput": true,
    "reasoning": {
      "supported": true
    }
  }
}
```

## Publish the LLM Asset with the Exchange API

1.  Set the coordinates for the asset you want to publish:

    ```bash
    export ORGANIZATION_ID="YOUR_ORGANIZATION_ID"
    export GROUP_ID="YOUR_GROUP_ID"
    export ASSET_ID="my-custom-llm"
    export ASSET_VERSION="1.0.0"
    export VERSION_GROUP="v1"
    export ASSET_NAME="My Custom LLM"
    ```

    The organization ID identifies the business group that receives the asset. The group ID is the
    asset’s Maven group ID and is commonly the same value. Use a lowercase, URL-safe asset ID.

    > [!NOTE] Exchange versions are immutable. If a version already exists, publish a new semantic
    > version.

2.  Export your credentials in the current shell or retrieve them from a secret manager. Don’t
    commit them to source control or paste them into documents.

    ```bash
    read -r -p 'Anypoint client ID: ' ANYPOINT_CLIENT_ID
    read -r -s -p 'Anypoint client secret: ' ANYPOINT_CLIENT_SECRET
    echo
    export ANYPOINT_CLIENT_ID ANYPOINT_CLIENT_SECRET
    ```

    > [!WARNING] Shell history can expose values. Avoid commands such as
    > `export ANYPOINT_CLIENT_SECRET=literal-secret`.

3.  Obtain a bearer token. The steps use OAuth 2.0 client credentials and the production Anypoint
    host, `[https://anypoint.mulesoft.com](https://anypoint.mulesoft.com)`.

    ```bash
    TOKEN_RESPONSE=$(curl --fail-with-body --silent --show-error --location \
      --request POST \
      'https://anypoint.mulesoft.com/accounts/api/v2/oauth2/token' \
      --header 'Content-Type: application/x-www-form-urlencoded' \
      --data-urlencode "client_id=$ANYPOINT_CLIENT_ID" \
      --data-urlencode "client_secret=$ANYPOINT_CLIENT_SECRET" \
      --data-urlencode 'grant_type=client_credentials')

    ANYPOINT_TOKEN=$(printf '%s' "$TOKEN_RESPONSE" | jq -er '.access_token')
    export ANYPOINT_TOKEN
    ```

    Don’t print or log `TOKEN_RESPONSE` or `ANYPOINT_TOKEN`.

4.  Confirm your new provider version is available:

    ```bash
    curl --silent --show-error \
      --output /dev/null \
      --write-out 'HTTP %{http_code}\n' \
      --header "Authorization: Bearer $ANYPOINT_TOKEN" \
      "https://anypoint.mulesoft.com/exchange/api/v2/assets/$GROUP_ID/$ASSET_ID/$ASSET_VERSION"
    ```

    Make sure you receive a `404` error code.

    - `HTTP 404`: The version is available.

    - `HTTP 200`: The version already exists. Change `ASSET_VERSION` before publishing.

    - `HTTP 401` or `HTTP 403`: The request wasn’t authorized. See
      [Troubleshooting Error Codes](#troubleshooting).

5.  From the directory that contains `llm-metadata.json`, publish the asset to Exchange:

    ```bash
    curl --fail-with-body --silent --show-error --location \
      --request POST \
      "https://anypoint.mulesoft.com/exchange/api/v2/organizations/$ORGANIZATION_ID/assets/$GROUP_ID/$ASSET_ID/$ASSET_VERSION" \
      --header 'Accept: application/json' \
      --header 'x-sync-publication: true' \
      --header "Authorization: Bearer $ANYPOINT_TOKEN" \
      --form 'files.llm-metadata.json=@llm-metadata.json;type=application/json' \
      --form "properties.versionGroup=$VERSION_GROUP" \
      --form 'type=llm' \
      --form "name=$ASSET_NAME"
    ```

    Don’t set the multipart `Content-Type` header manually. `curl --form` generates the required
    boundary and header.

    > [!NOTE] If your file has a different name, replace `@llm-metadata.json` with
    > `@<YOUR_FILE_NAME>`.

6.  Verify the published asset:

    ```bash
    curl --fail-with-body --silent --show-error \
      --header 'Accept: application/json' \
      --header "Authorization: Bearer $ANYPOINT_TOKEN" \
      "https://anypoint.mulesoft.com/exchange/api/v2/assets/$GROUP_ID/$ASSET_ID/$ASSET_VERSION" \
      | jq '{groupId, assetId, version, name, type, status}'
    ```

7.  Open the asset in Exchange and confirm that the published version is visible and that its
    `llm-metadata.json` classifier is downloadable.

8.  After testing, unset the sensitive shell variables:

    ```bash
    unset ANYPOINT_CLIENT_ID ANYPOINT_CLIENT_SECRET ANYPOINT_TOKEN TOKEN_RESPONSE
    unset ORGANIZATION_ID GROUP_ID ASSET_ID ASSET_VERSION VERSION_GROUP ASSET_NAME
    ```

    Rotate any client secret you pasted into chat, logs, tickets, or shell history before using this
    process in automation.

## Troubleshooting Error Codes

`401 Unauthorized`

Obtain a new token and verify the client ID and secret.

`403 Forbidden`

In **Access Management** > **Connected Apps**, add **Exchange Contributor** or **Exchange
Administrator** for the target business group. Save the app, obtain a new token, and retry.

`409 Conflict`

The version already exists. Publish a new semantic version.

`400 Bad Request`

Check the JSON field names and types, `type=llm`, and `properties.versionGroup`.

## See Also

- [Creating Model Proxies](https://docs.mulesoft.com/general/model-proxy-create-model-proxy)

- [Connected Apps in Anypoint Platform](https://docs.mulesoft.com/access-management/connected-apps-overview)

- [Publishing Assets](https://docs.mulesoft.com/exchange/about-sharing-assets)
