Skip to main content

Responses Proxy provider registration

Register providers through the Responses page in the Morph Cloud console or through the authenticated /admin/* API. The browser should never send provider secrets directly to the backend service; the frontend BFF validates the active org and obtains Morph service delegation auth server-side.

For direct operator API calls:

export RESPONSES_PROXY_BASE_URL="https://agent-responses-proxy.svc.cloud.morph.so"
export ADMIN_HEADER="Authorization: Bearer $SEELE_ORG_ADMIN_MORPH_API_KEY"

Providers, model aliases, and proxy keys are scoped to the caller's Morph organization.

Validate before saving

Use presets to populate provider forms:

curl -sS -H "$ADMIN_HEADER" "$RESPONSES_PROXY_BASE_URL/admin/provider-presets"

Validate unsaved provider settings:

curl -sS -X POST "$RESPONSES_PROXY_BASE_URL/admin/providers/test" \
-H "$ADMIN_HEADER" \
-H "content-type: application/json" \
-d '{
"id": "kimi-main",
"name": "Kimi Main",
"provider_type": "custom",
"base_url": "https://api.moonshot.ai/v1",
"wire_api": "chat_completions",
"auth_type": "bearer",
"api_key": "replace-with-provider-key"
}'

After saving a provider, validate an upstream model id:

curl -sS -X POST "$RESPONSES_PROXY_BASE_URL/admin/models/test" \
-H "$ADMIN_HEADER" \
-H "content-type: application/json" \
-d '{"provider_id":"kimi-main","upstream_id":"kimi-k2.6"}'

Credential options

Provider credentials can come from provider credential rows or legacy provider-level settings.

Preferred BYOS credentials live in rows under /admin/providers/{provider_id}/credentials. Use these rows when the dashboard needs to add, validate, rotate, disable, or delete credentials without changing the provider or its aliases.

curl -sS -X POST "$RESPONSES_PROXY_BASE_URL/admin/providers/kimi-main/credentials" \
-H "$ADMIN_HEADER" \
-H "content-type: application/json" \
-d '{"label":"primary","kind":"api_key","secret":"replace-with-provider-key"}'

Credential row kind values are api_key, bearer_token, external_ref, and feature-gated claude_code_oauth. Rows can hold encrypted inline secret material or credential_ref="env:NAME".

If at least one enabled credential row resolves, routing uses those rows. If no enabled credential rows exist, the provider falls back to legacy provider-level sources: env-sourced key slots, encrypted inline api_key, then provider-level credential_ref.

Env-sourced multi-key pools

Register one provider row, then add physical keys to the deployment environment using the provider id as the prefix. The prefix is uppercase with non-alphanumeric characters replaced by _: openai -> OPENAI_API_KEY, openai-prod -> OPENAI_PROD_API_KEY, and kimi.k2 -> KIMI_K2_API_KEY.

OPENAI_API_KEY=sk-primary
OPENAI_API_KEY_1=sk-secondary
OPENAI_API_KEY_3=sk-fourth

The base variable is slot env0; numbered slots may have gaps. Every slot shares the provider's pool, so load balancing, health, and affinity apply automatically. Admin responses expose resolved_key_count and pool_size, not raw values.

Env variables are deployment-global. Runtime env keys resolve only for PROXY_BOOTSTRAP_ORG_ID by default, or for org ids listed in PROXY_ENV_CREDENTIAL_OWNER_ORG_IDS. If multiple orgs need separate physical keys, use org-distinct provider ids such as openai-seele and openai-acme.

Secret references

In the console provider or credential form, enter $NAME in the API-key field to reference an organization secret. For example, $ZAI_API_KEY is sent to the backend as credential_ref=env:ZAI_API_KEY. The BFF validates that the org secret exists before forwarding the request.

The proxy can resolve the reference only when the same environment variable exists in the proxy runtime. *_API_KEY variables already match the deploy forwarding rules unless the name is sensitive (MORPH_*, PROXY_*, DATABASE_*, or SERVICE_*). credential_ref names must match *_API_KEY or *_API_KEY_<number> unless listed in PROXY_CREDENTIAL_REF_ENV_ALLOWLIST. When switching an existing provider-level pasted key to $NAME, send clear_api_key=true so the reference is used.

URL and header validation

Provider base_url must be an HTTPS public host in production. For local-only provider tests, the backend must explicitly set PROXY_ALLOW_HTTP_PROVIDER_URLS=true or PROXY_ALLOW_PRIVATE_PROVIDER_URLS=true.

Custom auth_header values must be valid HTTP header names and cannot be reserved transport or client-credential headers such as cookie, host, content-length, or proxy-authorization.

OpenAI Responses

Register an OpenAI Responses provider:

curl -sS -X POST "$RESPONSES_PROXY_BASE_URL/admin/providers" \
-H "$ADMIN_HEADER" \
-H "content-type: application/json" \
-d '{
"id": "openai-main",
"name": "OpenAI Main",
"provider_type": "openai",
"base_url": "https://api.openai.com/v1",
"wire_api": "responses",
"auth_type": "bearer",
"api_key": "replace-with-provider-key"
}'

Register a client-facing alias:

curl -sS -X POST "$RESPONSES_PROXY_BASE_URL/admin/models" \
-H "$ADMIN_HEADER" \
-H "content-type: application/json" \
-d '{
"alias": "openai/gpt-mini",
"provider_id": "openai-main",
"upstream_id": "gpt-4.1-mini"
}'

Clients then send:

{"model": "openai/gpt-mini", "input": "hello"}

Kimi and OpenAI-compatible Chat

Kimi, OpenRouter, and many gateways use the OpenAI-compatible Chat Completions protocol.

curl -sS -X POST "$RESPONSES_PROXY_BASE_URL/admin/providers" \
-H "$ADMIN_HEADER" \
-H "content-type: application/json" \
-d '{
"id": "kimi-main",
"name": "Kimi Main",
"provider_type": "custom",
"base_url": "https://api.moonshot.ai/v1",
"wire_api": "chat_completions",
"auth_type": "bearer",
"api_key": "replace-with-provider-key",
"config": {"endpoint_path": "chat/completions"}
}'

Register an alias:

curl -sS -X POST "$RESPONSES_PROXY_BASE_URL/admin/models" \
-H "$ADMIN_HEADER" \
-H "content-type: application/json" \
-d '{
"alias":"kimi/k2",
"provider_id":"kimi-main",
"upstream_id":"kimi-k2.6",
"capabilities": {
"claude_code": {
"profile":"openai_chat",
"documents":false,
"hosted_tools":[]
}
}
}'

For OpenRouter, use its base URL and upstream model id:

{
"id": "openrouter-main",
"base_url": "https://openrouter.ai/api/v1",
"wire_api": "chat_completions",
"upstream_id_example": "replace-with-openrouter-model-id"
}

Anthropic Messages

Anthropic uses the Messages API with an API-key header:

curl -sS -X POST "$RESPONSES_PROXY_BASE_URL/admin/providers" \
-H "$ADMIN_HEADER" \
-H "content-type: application/json" \
-d '{
"id": "anthropic-main",
"name": "Anthropic Main",
"provider_type": "anthropic",
"base_url": "https://api.anthropic.com/v1",
"wire_api": "anthropic_messages",
"auth_type": "api_key",
"auth_header": "x-api-key",
"health_path": null,
"api_key": "replace-with-provider-key",
"config": {
"endpoint_path": "messages",
"anthropic_version": "2023-06-01",
"default_max_tokens": 4096
}
}'

Register an alias:

curl -sS -X POST "$RESPONSES_PROXY_BASE_URL/admin/models" \
-H "$ADMIN_HEADER" \
-H "content-type: application/json" \
-d '{
"alias": "anthropic/sonnet",
"provider_id": "anthropic-main",
"upstream_id": "replace-with-anthropic-model-id"
}'

Use aliases from Codex

Codex still points at Responses Proxy as a Responses provider. Choose a registered alias as the model:

codex \
-c model_provider='"proxy"' \
-c model='"kimi/k2"' \
-c model_providers.proxy.name='"Responses Proxy"' \
-c model_providers.proxy.base_url='"https://agent-responses-proxy.svc.cloud.morph.so/v1"' \
-c model_providers.proxy.wire_api='"responses"' \
-c 'model_providers.proxy.http_headers={"Authorization"="Bearer rprx-..."}'

For aliases Codex does not know, add them to codex-model-catalog.json or provide your own catalog with MODEL_CATALOG_JSON=/path/to/models.json.

Use aliases from Claude Code

Claude Code uses the Anthropic Messages-compatible gateway. Set the Anthropic base URL to the proxy and send the proxy key in X-Responses-Proxy-Key:

export RESPONSES_PROXY_KEY="rprx-..."
export ANTHROPIC_BASE_URL="https://agent-responses-proxy.svc.cloud.morph.so/v1"
export ANTHROPIC_CUSTOM_HEADERS="X-Responses-Proxy-Key: $RESPONSES_PROXY_KEY"
export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1

claude --model "anthropic/sonnet"

Claude Code-compatible aliases can point at wire_api="anthropic_messages", wire_api="responses", or wire_api="chat_completions" providers. The alias capabilities.claude_code object controls the compatibility profile, media, client tools, parallel tools, and hosted tools. Omitted fields use permissive compatibility defaults except hosted_tools, which defaults to an empty list.

Use POST /admin/models/test with surface="claude_code" to test the Messages conversion path rather than only the Responses facade. See Claude Code BYOS for the full schema and mapping rules.

For stored Claude Code subscription credentials, use Claude Code BYOS.

Current limits

  • responses, chat_completions, and anthropic_messages providers are routable.
  • wire_api="custom" providers are stored but not routable until a custom adapter exists.
  • Claude Code gateway aliases support anthropic_messages, responses, and deployment-enabled chat_completions providers.
  • Non-Responses adapters support text, streaming text, function tools, Codex custom tools, and namespace tool containers. Custom tools are wrapped upstream as JSON tools with an input string and converted back to custom_tool_call items for Codex.
  • Nested namespace tool containers are flattened into provider-native JSON tools.
  • On the Claude Code surface, translated aliases support text, portable image/document input, and client function tools according to alias capabilities. Chat Completions rejects document URLs; use base64 file data.
  • web_search is the only portable hosted-tool capability. Responses aliases can map it, Anthropic-native aliases keep native behavior, and Chat Completions aliases require an empty hosted-tool list. Other translated server-tool types fail explicitly.
  • Translated Claude output currently maps text and client tool calls, not generated image, document, audio, computer-use, or arbitrary provider-specific blocks.
  • Chat Completions and Anthropic are stateless upstream protocols, so the proxy stores reconstructed response state in SQL for previous_response_id continuations.

Delete and re-create

DELETE /admin/providers/{id} and DELETE /admin/models/{alias} remove registry rows. Use the enabled field when you need a reversible runtime pause while keeping the row. Disabled provider and model rows are purged on proxy boot, while historical telemetry keeps provider and alias string fields for audit history.