Add model providers and implement new protocols
Check whether the supplier's API is already listed under XHub's upstream protocols. For an existing protocol, add the provider and deployment in the UI. Otherwise, implement it in internal/provider/<supplier>/ and connect it to gateway execution. This guide covers NewAPI page operations and the source changes for a new protocol.
1. Choose the integration level
| Actual upstream behavior | Work required | Code changes |
|---|---|---|
| NewAPI implements the OpenAI Chat request, response and SSE contract | Create a custom_openai credential and bind the deployment to registered bypass_openai_chat |
No |
| Supported Responses, Messages, Gemini or registered media protocol | Choose its executor and verify each upstream operation | Usually no |
| Existing protocol with different authentication, paths or fixed models | Register a Transport; add fields/models where necessary | Yes |
| Suno-specific music creation, polling or callbacks | Public endpoint, executor, task lifecycle, measurement and UI | Yes, not just a directory |
Create NewAPI as a model provider in the XHub UI, then select that provider and an implemented upstream protocol for the deployment. The provider stores the address and authentication; the deployment stores the model and execution choice. Do not repeat addresses or keys in deployments. A NewAPI Chat route does not prove support for Responses, images, audio or music.
2. Connect NewAPI without writing code
Prerequisites: the actual API address, a NewAPI-issued key and a model that key can call. NewAPI keys and XHub virtual keys are not interchangeable.
- Add
company-newapion the Model providers page and choose the provider type matching the actual Chat API. Enter its service address and NewAPI-issued key in the provider form, not in a deployment or startup configuration. Do not enter a console URL or a full operation URL. - Fetch models. Discovery does not create deployments. If discovery fails, preserve the error and manually enter an ID confirmed by the provider.
- Add a deployment: public name
company-chat, upstream modelgpt-4o-mini(example only; use an account-authorized model), saved connection, and OpenAI Chat as the upstream protocol. - Its saved
model_info.transportmust be the registeredbypass_openai_chat. Herebypassis XHub's upstream execution mode; it does not bypass XHub for the client.endpoint_types: [chat]exposes XHub's standard/v1/chat/completions. Declarebypass:openai-chatonly when clients must use/bypass/openai/v1/chat/completionsand preserve the native request. - Configure the actual contract price, save, reload and verify model, connection, protocol, price and enabled state. Relay prices are not automatically official prices.
- Create an XHub virtual key authorized for
company-chat, make a real gateway call, and inspect deployment, status, usage and the recorded price snapshot.
curl -i http://localhost:4000/v1/chat/completions \
-H "Authorization: Bearer $XHUB_VIRTUAL_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"company-chat","messages":[{"role":"user","content":"Reply with the word ready."}],"stream":false}'Connection Test probes a saved dialogue deployment and may incur upstream charges. It bypasses the full virtual-key quota, routing and accounting chain, so it does not replace step 6. Verify stream:true, tools and application-specific parameters separately after a non-streaming call succeeds.
What to enter on each page
A. Save the provider connection
Open Model providers → Add provider. For this Chat example, choose Custom Openai, enter Provider name company-newapi, and fill API Base and API Key on this page only. Leave Provider catalog ID blank for ordinary OpenAI Chat models; it is for registered dedicated model catalogs, not the connection name.

The screenshot uses a fictional address and a masked demonstration key. Save your own connection before creating a deployment. It does not show a tested NewAPI account.
B. Add the model deployment
Open Models + Endpoints → Add Model. Select the saved provider in step 1 to unlock step 2. Enter the upstream model ID and public model name, choose the upstream protocol, then select catalog pricing or enter your actual manual rates in step 3. Save and refresh to check the result.

This is the initial, unselected form: disabled model fields mean a provider has not been selected yet. Do not enter an address or key here, and do not edit model_list.
| Page | Enter | Verify after saving |
|---|---|---|
| Model providers | Name, type, service address and authentication | company-newapi remains listed with the correct type |
| Model deployment | Public name company-chat, provider company-newapi, upstream model ID and protocol |
Provider, model and protocol survive reload |
| Model pricing | Select an existing price or enter contract rates | Units are explicit; missing prices do not mean free |
| Virtual keys | Authorize public model company-chat |
XHub calls succeed and logs show deployment and usage |
Fetching and importing a provider model list only adds model and price information to the AI model marketplace. Confirm the model, provider, protocol and pricing in the model form and add a deployment before gateway routing can use it. If discovery fails, manually enter an upstream ID confirmed by the provider.
Addresses, discovery and credentials
- Base
https://newapi.example.com/proxy/v1plus the registeredbypass_openai_chataction/v1/chat/completionsbecomes/proxy/v1/chat/completions: XHub avoids duplicating/v1and preserves/proxy. Clients use XHub's address. Seeinternal/provider/openai/native.goandinternal/dataplane/transport/bypass_url.go. - Compatible discovery merges the complete upstream list with local specialized models; it does not filter ordinary models through a local execution whitelist. Failures permit manual IDs. Discovery v1 fallback does not change inference addresses or protocols. See
internal/gateway/models. - The normal UI flow resolves address and authentication from the referenced model provider. Deployment-level addresses take precedence; runtime provider resolution fills empty addresses. See
internal/llm/credentials/credential.go. - An unregistered
catalog_idmay remain relay metadata but creates no executor. Registered specialized catalogs validate models and TransportIDs. The backend reads the trusted catalog from the saved connection, not a forged request value.
3. Suno: obtain the actual contract first
Determine whether the target is an authorized official service, a NewAPI plugin or a third-party relay. Paths, authentication, statuses and results can differ. There is no suno transport or music endpoint in the current code. Collect redacted success and failure samples for:
- Creation method/path/authentication, model/version, prompt, lyrics, style, instrumental flag, songs per request and charging point.
- Task ID field and type, business failures inside HTTP 200, and absolute polling URLs.
- Path versus query task IDs, pending/success/failure/cancellation states, audio URLs versus binary or multiple songs.
- Required callbacks, signatures and replay protection, authenticated downloads, URL expiry, cancellation and listing.
- Billing units: requests, songs, measured audio seconds or credits, failed-task charges and missing usage.
Reuse an existing protocol only for an operation that actually implements it. Music JSON is not /v1/audio/speech; video seconds are not automatically music usage. Numeric IDs, multiple results, OAuth/signatures and mandatory callbacks may require execution-layer changes. Uploading JSON cannot install an arbitrary protocol.
4. Development checklist and source map
Add your own provider
Example: reuse OpenAI Chat payloads with a new transport. File: internal/provider/acme/native.go. Replace the example supplier and paths with your API contract. This is not a Suno implementation.
package acme
import (
contract "github.com/sunqirui1987/xhub/internal/provider/contract"
measurement "github.com/sunqirui1987/xhub/internal/provider/measurement"
registry "github.com/sunqirui1987/xhub/internal/provider/registry"
)
// init 登记 Acme 的 Chat 执行方式;无参数、无返回值。
// 由包加载调用,写入注册表;仅适用于 OpenAI Chat 请求与响应结构。
func init() {
registry.RegisterTransport(contract.Transport{
ID: "acme_chat", Label: "Acme Chat",
Kind: contract.KindBypass,
EndpointID: "bypass:openai-chat",
Protocol: "openai-chat", Family: "chat",
Providers: []string{"custom_openai"},
ModelField: "model",
Auth: contract.AuthConfig{Header: "Authorization", Prefix: "Bearer"},
ResponseUsage: measurement.NativeUsage("openai-chat"),
Actions: []contract.Action{{
Name: "create", Method: "POST",
PublicPath: "/bypass/acme/v1/chat/completions",
UpstreamPath: "/v1/chat/completions",
}},
})
}Load the package
Add this import to internal/provider/all/all.go:
import (
_ "github.com/sunqirui1987/xhub/internal/provider/acme"
)Rebuild and start the gateway → add the provider in the UI → create a deployment selecting Acme Chat → call with an XHub virtual key → check logs and usage. Enter the address and secret in the provider form.
Different payloads: where to change code
| Difference | Source |
|---|---|
| Authentication header or fixed path | Auth and Actions above; signatures/OAuth need an authentication implementation |
| Request/response structure | internal/dataplane/transport; see internal/dataplane/execution/official.go |
| New client protocol | internal/provider/capability/endpoints.go + internal/gateway; renaming Protocol is insufficient |
| Specialized credential form | internal/provider/registry/suppliers.go + internal/catalog/credential_providers.go |
| Specialized model defaults | RegisterModel in internal/provider/registry/models.go |
| Usage and pricing | internal/provider/measurement + internal/catalog |
| Parameter editing and results | frontend/src/components/chat; maintain en/zh text |
Asynchronous protocols: existing task example
Registration excerpt from internal/provider/qiniu/seedance.go (Ark video, not Suno):
TaskID: "id",
Billing: providermeasurement.SeedanceBilling(),
Actions: []providercontract.Action{
{Name: "create", Method: "POST",
PublicPath: "/v3/contents/generations/tasks",
UpstreamPath: "/v3/contents/generations/tasks"},
{Name: "get", Method: "GET",
PublicPath: "/v3/contents/generations/tasks/{id}",
UpstreamPath: "/v3/contents/generations/tasks/{id}"},
},Create → read string task ID → pin identity and deployment → poll → extract successful usage → settle once. Implement Suno IDs, states, song results and billing units from its actual contract, not video billing. Creation timeouts are not automatically replayed. Background polling, budget reservation and Suno callbacks require separate implementation.
5. Acceptance
go test ./internal/provider/... ./internal/llm/credentials ./internal/dataplane/transport
bash scripts/regression.sh 'TestProvider(Transport|Forms)Contract'
bash scripts/e2e.sh scenarios/legacy/provider-forms scenarios/legacy/docs-centerUse an isolated database and local upstream; see testing. Cover save/reload, invalid configuration, authentication, success/failure payloads and bilingual selection. Tasks additionally need pending states, multiple results, identity isolation and repeated-query settlement checks. Validate external suppliers separately.
6. Troubleshooting
| Symptom | Check and action |
|---|---|
| Discovered model fails inference | Key permissions, saved transport, actual path and error body; discovery is not inference |
| unregistered transport | all import, new build and restart; changing provider labels cannot bypass it |
| transport rejects provider | Providers allowlist versus connection type; use the actual protocol or extend with tests |
| Catalog rejects model | catalog_id, Official and TransportID; register actual model or use a compatible generic protocol |
| Connection update still calls old host | Remove stale deployment api_base and verify the destination |
| Creation works but polling fails | Original identity/deployment, ID field/type and path/query; do not send IDs to another service |
| Success has no price | Measured usage, unit and rates; retain unpriced status, never invent tokens or assume free |
Related: credential forms, models/endpoints, provider source guide. New resources and OAuth APIs in the provider redesign proposal are not current operational APIs.