If your LLM provider or gateway is not one of the built-in Model Proxy 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:
-
curlinstalledTo install, see Install curl.
-
jqinstalledTo install, see Install jq.
-
An Anypoint connected app that:
-
Acts on its own behalf with the
client_credentialsgrant 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.
| Field | Type | Required | Description |
|---|---|---|---|
|
String |
Yes |
Provider or platform identifier, such as |
|
String |
No |
Human-readable platform name. |
|
String |
No |
Wire format: |
|
String |
No |
Platform type, for example |
|
Array |
No |
Supported provider authentication methods. |
|
Array |
No |
Model ID strings or detailed model objects. |
|
Array |
No |
Named provider URL entries. Each entry requires both |
|
Object |
No |
Platform-wide supported features. |
|
Object |
No |
Adapter policy coordinates. Requires |
|
Object |
No |
Origin information. Supports |
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.
| Authentication Kind | Optional Configuration |
|---|---|
|
|
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
idfield is required. All other fields are optional.
| Model Field | Type | Description |
|---|---|---|
|
String |
Provider-specific model identifier. |
|
String |
Human-readable model name. |
|
String |
Summary of the model and its intended use. |
|
Object |
Context, output, and prompt-caching limits. |
|
Object |
Features supported by this specific model. |
|
Object |
Prices per million tokens. |
|
Object |
Release and retirement information. |
The nested objects support these fields:
| Object | Field | Type | Description |
|---|---|---|---|
|
|
Integer |
Maximum number of input tokens. |
|
|
Integer |
Maximum number of generated tokens. |
|
|
Boolean |
Whether prompt caching is supported. |
|
|
Integer |
Maximum number of tokens that can be cached. |
|
|
Number |
Price per million input tokens. |
|
|
Number |
Price per million output tokens. |
|
|
Number |
Price per million cached input tokens. |
|
|
Number |
Price per million reasoning tokens. |
|
|
String |
Provider-defined state, such as |
|
|
String |
Release date or timestamp. |
|
|
String or null |
Known retirement date, or |
This example shows both model forms.
{
"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:
| Field | Type | Required | Description |
|---|---|---|---|
|
Boolean |
Yes |
Whether the platform or model supports reasoning. |
|
String |
No |
The reasoning mode the provider uses, for example |
|
Array of strings |
No |
The reasoning effort levels that the provider accepts, for example |
In this example, the model supports reasoning and accepts three effort levels:
"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:
{
"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
-
Set the coordinates for the asset you want to publish:
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.
Exchange versions are immutable. If a version already exists, publish a new semantic version. -
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.
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_SECRETShell history can expose values. Avoid commands such as export ANYPOINT_CLIENT_SECRET=literal-secret. -
Obtain a bearer token. The steps use OAuth 2.0 client credentials and the production Anypoint host,
https://anypoint.mulesoft.com.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_TOKENDon’t print or log
TOKEN_RESPONSEorANYPOINT_TOKEN. -
Confirm your new provider version is available:
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
404error code.-
HTTP 404: The version is available. -
HTTP 200: The version already exists. ChangeASSET_VERSIONbefore publishing. -
HTTP 401orHTTP 403: The request wasn’t authorized. See Troubleshooting Error Codes.
-
-
From the directory that contains
llm-metadata.json, publish the asset to Exchange: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-Typeheader manually.curl --formgenerates the required boundary and header.If your file has a different name, replace @llm-metadata.jsonwith@<YOUR_FILE_NAME>. -
Verify the published asset:
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}' -
Open the asset in Exchange and confirm that the published version is visible and that its
llm-metadata.jsonclassifier is downloadable. -
After testing, unset the sensitive shell variables:
unset ANYPOINT_CLIENT_ID ANYPOINT_CLIENT_SECRET ANYPOINT_TOKEN TOKEN_RESPONSE unset ORGANIZATION_ID GROUP_ID ASSET_ID ASSET_VERSION VERSION_GROUP ASSET_NAMERotate 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, andproperties.versionGroup.