Skip to content
v0.8.4stable

Chat Completions

POST /v1/chat/completions  compatible  semantic operation generate  contract revision 1

State semantics: stateless

Raw contract deviations

These statements are source-contract evidence. Use the examples, field table, and Profile coverage matrix for ordinary integration, and expand this list when investigating a compatibility boundary.

  • gateway routes model names; provider-owned chat state is not exposed
  • unknown request fields are rejected before provider I/O
  • provider-specific unsupported fields are rejected before provider I/O
Terminal window
curl https://halro.example.com/v1/chat/completions \
-H "Authorization: Bearer $HALRO_GATEWAY_KEY" \
-H "Content-Type: application/json" \
-d '{ "model": "chat", "max_tokens": 256,
"messages": [{"role": "user", "content": "Hello"}] }'
  • Authorization
  • Content-Type
Request fields
FieldProvider Profiles that do not support itCombination-level constraints
model1
messages
messages[].nameopenai.responses.v1, anthropic.messages.2023-06-01, gemini.generate-content.text.v1beta, bedrock.runtime.converse.text.v1, bedrock.mantle.responses.v1, bedrock.mantle.openai.responses.v1, bedrock.mantle.anthropic.messages.v1, minimax.anthropic.messages.v1, minimax.responses.v1, kimi.anthropic.messages.v1, kimi.responses.v1
messages[].content[].image_url
messages[].content[].detailanthropic.messages.2023-06-01, bedrock.mantle.anthropic.messages.v1, minimax.anthropic.messages.v1, kimi.anthropic.messages.v1
stream2
stream_options
temperaturekimi.chat.v1, kimi.anthropic.messages.v1, kimi.responses.v13
top_pkimi.chat.v1, kimi.anthropic.messages.v1, kimi.responses.v14
max_tokensminimax.chat.v1, kimi.chat.v1, kimi.responses.v15
max_completion_tokensdeepseek.chat.v16
nopenai.responses.v1, anthropic.messages.2023-06-01, deepseek.chat.v1, bedrock.runtime.converse.text.v1, bedrock.mantle.responses.v1, bedrock.mantle.openai.responses.v1, bedrock.mantle.anthropic.messages.v1, minimax.anthropic.messages.v1, minimax.chat.v1, minimax.responses.v1, kimi.chat.v1, kimi.anthropic.messages.v1, kimi.responses.v17
stopopenai.responses.v1, bedrock.mantle.responses.v1, bedrock.mantle.openai.responses.v1, minimax.anthropic.messages.v1, minimax.chat.v1, minimax.responses.v1, kimi.chat.v1, kimi.responses.v18
seedopenai.responses.v1, anthropic.messages.2023-06-01, deepseek.chat.v1, gemini.generate-content.text.v1beta, bedrock.runtime.converse.text.v1, bedrock.mantle.responses.v1, bedrock.mantle.openai.responses.v1, bedrock.mantle.anthropic.messages.v1, minimax.anthropic.messages.v1, minimax.chat.v1, minimax.responses.v1, kimi.chat.v1, kimi.anthropic.messages.v1, kimi.responses.v1
toolsgemini.generate-content.text.v1beta, bedrock.runtime.converse.text.v1, bedrock.mantle.responses.v1, bedrock.mantle.openai.responses.v19
tool_choicegemini.generate-content.text.v1beta, bedrock.runtime.converse.text.v1, kimi.chat.v110
parallel_tool_callsdeepseek.chat.v1, gemini.generate-content.text.v1beta, bedrock.runtime.converse.text.v1, minimax.chat.v1, minimax.responses.v1, kimi.chat.v1, kimi.responses.v111
response_formatanthropic.messages.2023-06-01, deepseek.chat.v1, gemini.generate-content.text.v1beta, bedrock.runtime.converse.text.v1, bedrock.mantle.anthropic.messages.v1, minimax.anthropic.messages.v1, minimax.chat.v1, minimax.responses.v1, kimi.anthropic.messages.v112
reasoning_effortopenai.responses.v1, anthropic.messages.2023-06-01, deepseek.chat.v1, gemini.generate-content.text.v1beta, bedrock.runtime.converse.text.v1, bedrock.mantle.responses.v1, bedrock.mantle.openai.responses.v1, bedrock.mantle.anthropic.messages.v1, minimax.anthropic.messages.v1, minimax.responses.v1, kimi.chat.v1, kimi.anthropic.messages.v1, kimi.responses.v113
useranthropic.messages.2023-06-01, gemini.generate-content.text.v1beta, bedrock.runtime.converse.text.v1, bedrock.mantle.anthropic.messages.v1, minimax.anthropic.messages.v1, minimax.chat.v1, minimax.responses.v1, kimi.chat.v1, kimi.anthropic.messages.v1, kimi.responses.v1

id object created model choices usage

chat.completion.chunk [DONE] error

Provider Profile contract coverage and current availability

Section titled “Provider Profile contract coverage and current availability”
Provider Profile contract coverage and current availability
Provider ProfileMaturityCurrent buildUnsupported request fields
openai.chat-embeddings.v1compatiblecreatableundeclared
openai.responses.v1compatiblecreatablemessages[].name, n, stop, seed, reasoning_effort
anthropic.messages.2023-06-01compatiblecreatablemessages[].name, n, seed, response_format, reasoning_effort, user, messages[].content[].detail
azure-openai.chat-embeddings.v1compatiblecreatableundeclared
deepseek.chat.v1compatiblecreatablen, seed, max_completion_tokens, parallel_tool_calls, response_format, reasoning_effort
openai-compatible.chat-embeddings.v1compatiblecreatableundeclared
gemini.generate-content.text.v1betacompatiblecreatablemessages[].name, seed, tools, tool_choice, parallel_tool_calls, response_format, reasoning_effort, user
bedrock.runtime.converse.text.v1compatiblewithheld (cannot create)messages[].name, n, seed, tools, tool_choice, parallel_tool_calls, response_format, reasoning_effort, user
bedrock.mantle.chat.v1compatiblecreatableundeclared
bedrock.mantle.openai.chat.v1compatiblecreatableundeclared
bedrock.mantle.responses.v1compatiblecreatablemessages[].name, n, stop, seed, tools, reasoning_effort
bedrock.mantle.openai.responses.v1compatiblecreatablemessages[].name, n, stop, seed, tools, reasoning_effort
bedrock.mantle.anthropic.messages.v1compatiblecreatablemessages[].name, n, seed, response_format, reasoning_effort, user, messages[].content[].detail
minimax.anthropic.messages.v1compatiblecreatablemessages[].name, n, seed, stop, response_format, reasoning_effort, user, messages[].content[].detail
minimax.chat.v1compatiblecreatablen, seed, stop, response_format, parallel_tool_calls, user, max_tokens
minimax.responses.v1compatiblecreatablemessages[].name, n, seed, stop, response_format, parallel_tool_calls, reasoning_effort, user
kimi.chat.v1compatiblecreatabletemperature, top_p, n, seed, parallel_tool_calls, user, max_tokens, reasoning_effort, stop, tool_choice
kimi.anthropic.messages.v1compatiblecreatabletemperature, top_p, messages[].name, n, seed, response_format, reasoning_effort, user, messages[].content[].detail
kimi.responses.v1compatiblewithheld (cannot create)temperature, top_p, messages[].name, n, seed, stop, max_tokens, parallel_tool_calls, reasoning_effort, user
openai.responses.v1
  • Chat messages are mapped to stateless Responses input items
  • store=false is always sent upstream
  • this profile binds no stream primitive, so a streaming request is routed away rather than refused by field
anthropic.messages.2023-06-01
  • portable Chat content is mapped to Anthropic Messages blocks
  • an Anthropic image source is base64, url, or file and has no member for the OpenAI fidelity hint, so detail is rejected before provider I/O at any value other than auto rather than dropped from a request that paid for it
  • response_format and reasoning_effort are declared unsupported at field granularity because support is value-dependent: json_schema maps to output_config.format and the low/medium/high/xhigh/max ladder maps to output_config.effort, while json_object and any effort outside that ladder have no Anthropic representation and are routed away before provider I/O
deepseek.chat.v1
  • DeepSeek speaks this wire format but accepts a smaller member list, so the fields it has no place for are rejected before provider I/O rather than sent and ignored
  • user is carried as DeepSeek’s user_id
  • reasoning_effort and response_format are declared unsupported at field granularity because support is value-dependent: none maps to thinking.type=disabled and the low and high rungs map to thinking.reasoning_effort with thinking enabled, while minimal, medium and xhigh have no DeepSeek rung; json_object maps to response_format and json_schema has no DeepSeek counterpart
  • max_completion_tokens is value-dependent too: it counts reasoning tokens and DeepSeek’s max_tokens does not, so it is carried as max_tokens on a request with thinking off and rejected before provider I/O on one with thinking on, or on one that already carries max_tokens
  • n and parallel_tool_calls are value-dependent in the same way: n=1 and parallel_tool_calls=true are what omitting the member already means, and only n>1 and a request to run tools one at a time are rejected
gemini.generate-content.text.v1beta
  • developer messages are merged into Gemini system_instruction
bedrock.runtime.converse.text.v1
  • Bedrock stop reasons are normalized to OpenAI finish reasons
bedrock.mantle.responses.v1
  • Chat messages are mapped to stateless Responses input items
  • store=false is always sent upstream
  • streaming requests with tools are rejected before provider I/O
bedrock.mantle.openai.responses.v1
  • Chat messages are mapped to stateless Responses input items
  • store=false is always sent upstream
  • streaming requests with tools are rejected before provider I/O
bedrock.mantle.anthropic.messages.v1
  • portable Chat content is mapped to Bedrock Mantle Anthropic Messages blocks
  • an Anthropic image source is base64, url, or file and has no member for the OpenAI fidelity hint, so detail is rejected before provider I/O at any value other than auto rather than dropped from a request that paid for it
  • Anthropic states that only base64-encoded image sources are available on Amazon Bedrock, so an image_url naming an address to fetch is rejected before provider I/O while a data URL is carried as a base64 source
minimax.anthropic.messages.v1
  • portable Chat content is mapped to MiniMax’s Anthropic Messages blocks
  • an Anthropic image source is base64, url, or file and has no member for the OpenAI fidelity hint, so detail is rejected before provider I/O at any value other than auto rather than dropped from a request that paid for it
  • reasoning_effort is unsupported at any value because MiniMax’s reasoning switch is the thinking member and it accepts no output_config; a request that asks for no depth still reaches MiniMax with thinking disabled, because the portable mapper emits that member on its own and MiniMax spells it the same way
  • stop is rejected before provider I/O rather than sent: MiniMax documents stop_sequences as ignored, so sending it would return 200 for a completion that ran past a boundary the caller set and paid for
  • response_format has no counterpart on this face at all: MiniMax accepts no output_config, and its Chat face was later measured serving json_object while this one was not
minimax.chat.v1
  • MiniMax speaks this wire format but accepts a smaller member list, so the fields it has no place for are rejected before provider I/O rather than sent and ignored — this upstream documents its unsupported parameters as ignored, not refused
  • reasoning_effort is carried as MiniMax’s thinking member: none reaches thinking.type=disabled and every other rung reaches thinking.type=adaptive, because MiniMax’s switch has one on state and no depth ladder, so depth is not preserved
  • a request that asks for no reasoning is sent with thinking explicitly disabled rather than left to MiniMax’s own default, which is on
  • reasoning_split is sent alongside an enabled switch so reasoning returns in its own member instead of inline in the answer
  • max_completion_tokens is carried as itself and max_tokens is carried into it only while thinking is off, because MiniMax’s single output bound counts reasoning; max_tokens is value-dependent for that reason — a request that also thinks, or that carries both members, is routed away by field before provider I/O rather than refused after the budget is reserved
  • n and parallel_tool_calls are value-dependent: n=1 and parallel_tool_calls=true are what omitting the member already means
  • response_format is value-dependent: json_object was measured against a real account on 2026-08-31 and is carried, text is dropped because it means what omitting the member already means, and the schema mode has never been sent and is rejected before provider I/O
minimax.responses.v1
  • Chat messages are mapped to stateless Responses input items
  • store=false is always sent upstream
  • this profile binds no stream primitive, so a streaming request is routed away rather than refused by field — MiniMax itself documents stream on /v1/responses, so this is a Halro scope decision and not an upstream limit
  • reasoning is unsupported here and supported on the MiniMax Chat profile: the canonical response mapper cannot preserve the reasoning items this endpoint returns
kimi.chat.v1
  • Kimi speaks this wire format but does not model the sampling parameters at all: temperature, top_p and n are absent from its request schema, and its parameter reference pins each to one value per model and answers any other with an error, so they are rejected before provider I/O rather than sent or silently replaced with the pinned value
  • temperature and top_p are rejected at any value rather than only at the wrong one, because the pinned value varies by model and a field rule is keyed by profile with no target in hand; carrying the pinned value is a capability-model change rather than a platform registration
  • max_tokens is value-dependent: Kimi has one output bound and it counts reasoning, so an answer-only bound is the same tokens exactly while nothing is thinking — which is the ordinary case, because a request that names no depth is sent with reasoning switched off. A request that does ask for depth, or that carries both output members, is routed away before provider I/O
  • reasoning_effort is value-dependent: none, low and high are carried, and minimal, medium, xhigh and Kimi’s own max are routed away because the portable ladder and Kimi’s do not share those rungs
  • the reasoning member itself depends on the model named: kimi-k3 reads a top-level reasoning_effort while the K2.x line reads thinking, and the renderer follows Kimi’s own OpenAPI discriminator over exact model identifiers rather than inferring a family from a name
  • K2.x has one on state and no depth ladder, so an effort routed to those models is honoured as on rather than preserved
  • a request that names no depth is sent with reasoning explicitly switched off rather than left to Kimi’s default, which is on: measured 2026-09-01, kimi-k3 and kimi-k2.6 both accept the off switch, and leaving it out returned a 64-token budget spent entirely on reasoning with an empty answer. kimi-k2.7-code has no off state and keeps reasoning, which is a per-model fact this profile-scoped declaration cannot express
  • stop is value-dependent: Kimi documents it as honoured and it is carried, but only within its published bounds of five sequences of at most 32 bytes each, and a request outside them is routed away before provider I/O rather than refused while the body is encoded
  • tool_choice is value-dependent, and the two ways of forcing a tool call are not the same value: a named function together with a depth is refused by every Kimi model measured and is routed away before provider I/O, while required together with a depth is refused by the K2.x line and accepted by kimi-k3, which answers with a tool call and a reasoning span in one response. That second half is a per-model limit this profile-scoped declaration cannot express, so it is refused after the reservation rather than routed away with the kimi-k3 request that works
kimi.anthropic.messages.v1
  • portable Chat content is mapped to Kimi’s Anthropic Messages blocks
  • an Anthropic image source is base64, url, or file and has no member for the OpenAI fidelity hint, so detail is rejected before provider I/O at any value other than auto rather than dropped from a request that paid for it
  • reasoning_effort is unsupported at any value on this face, and that is what makes the face usable at all: a request naming no depth reaches Kimi with thinking disabled and comes back with no thinking block, which the portable decoder can read, while a request naming a depth comes back with one, which it refuses outright after the upstream has already been paid
  • temperature and top_p are rejected because Kimi pins each per model and answers any other value with an error
  • response_format is value-dependent: output_config.format takes a json_schema and enforces it, and there is no schema-less mode
  • stop is carried: measured honoured on this face, unlike MiniMax’s, which documents the member as ignored
kimi.responses.v1
  • Chat messages are mapped to stateless Responses input items
  • Kimi does not model the sampling parameters on this face either, so temperature and top_p are rejected before provider I/O
  • this profile binds no stream primitive, so a streaming request is routed away rather than refused by field — Kimi itself documents stream on /v1/responses, so this is a Halro scope decision and not an upstream limit
  • the schema-less json_object mode is unavailable here and available on the Kimi Chat profile: this endpoint models structured output as text.format and accepts the json_schema type alone, so a json_object request is routed away by the absent json_object capability rather than by field
  • reasoning is unsupported here and supported on the Kimi Chat profile: the canonical response mapper cannot preserve the reasoning items this endpoint returns
  • this endpoint serves kimi-k3 alone, which the model catalogue records per model rather than this profile-scoped declaration

Verified evidence: gateway_contract, provider_transport_fixture, sdk_blackbox

openai-go, openai-node, openai-python

See the scenario library for routing failover, budgets, and interrupted streams.


Fields, coverage, transformations, and deviations are generated from Halro 8f185de7674c (scripts/generate-api-pages-en.mjs; synchronized manifest digest 8e5e7a0a794721aa…). The synchronized copy contains 1 recorded accuracy correction(s); source manifest digest b321ee18ad2e38f3…. The contract currently carries field names, not complete types, required relationships, or value semantics.

  1. Combination constraints for model:

    • kimi.chat.v1: Kimi speaks this wire format but does not model the sampling parameters at all: temperature, top_p and n are absent from its request schema, and its parameter reference pins each to one value per model and answers any other with an error, so they are rejected before provider I/O rather than sent or silently replaced with the pinned value
    • kimi.chat.v1: temperature and top_p are rejected at any value rather than only at the wrong one, because the pinned value varies by model and a field rule is keyed by profile with no target in hand; carrying the pinned value is a capability-model change rather than a platform registration
    • kimi.chat.v1: tool_choice is value-dependent, and the two ways of forcing a tool call are not the same value: a named function together with a depth is refused by every Kimi model measured and is routed away before provider I/O, while required together with a depth is refused by the K2.x line and accepted by kimi-k3, which answers with a tool call and a reasoning span in one response. That second half is a per-model limit this profile-scoped declaration cannot express, so it is refused after the reservation rather than routed away with the kimi-k3 request that works
    • kimi.anthropic.messages.v1: temperature and top_p are rejected because Kimi pins each per model and answers any other value with an error
    • kimi.responses.v1: Kimi does not model the sampling parameters on this face either, so temperature and top_p are rejected before provider I/O
    • kimi.responses.v1: the schema-less json_object mode is unavailable here and available on the Kimi Chat profile: this endpoint models structured output as text.format and accepts the json_schema type alone, so a json_object request is routed away by the absent json_object capability rather than by field
  2. Combination constraints for stream:

    • openai.responses.v1: this profile binds no stream primitive, so a streaming request is routed away rather than refused by field
    • bedrock.mantle.responses.v1: streaming requests with tools are rejected before provider I/O
    • bedrock.mantle.openai.responses.v1: streaming requests with tools are rejected before provider I/O
    • minimax.responses.v1: this profile binds no stream primitive, so a streaming request is routed away rather than refused by field — MiniMax itself documents stream on /v1/responses, so this is a Halro scope decision and not an upstream limit
    • kimi.responses.v1: this profile binds no stream primitive, so a streaming request is routed away rather than refused by field — Kimi itself documents stream on /v1/responses, so this is a Halro scope decision and not an upstream limit
  3. Combination constraints for temperature:

    • kimi.chat.v1: Kimi speaks this wire format but does not model the sampling parameters at all: temperature, top_p and n are absent from its request schema, and its parameter reference pins each to one value per model and answers any other with an error, so they are rejected before provider I/O rather than sent or silently replaced with the pinned value
    • kimi.chat.v1: temperature and top_p are rejected at any value rather than only at the wrong one, because the pinned value varies by model and a field rule is keyed by profile with no target in hand; carrying the pinned value is a capability-model change rather than a platform registration
    • kimi.anthropic.messages.v1: temperature and top_p are rejected because Kimi pins each per model and answers any other value with an error
    • kimi.responses.v1: Kimi does not model the sampling parameters on this face either, so temperature and top_p are rejected before provider I/O
  4. Combination constraints for top_p:

    • kimi.chat.v1: Kimi speaks this wire format but does not model the sampling parameters at all: temperature, top_p and n are absent from its request schema, and its parameter reference pins each to one value per model and answers any other with an error, so they are rejected before provider I/O rather than sent or silently replaced with the pinned value
    • kimi.chat.v1: temperature and top_p are rejected at any value rather than only at the wrong one, because the pinned value varies by model and a field rule is keyed by profile with no target in hand; carrying the pinned value is a capability-model change rather than a platform registration
    • kimi.anthropic.messages.v1: temperature and top_p are rejected because Kimi pins each per model and answers any other value with an error
    • kimi.responses.v1: Kimi does not model the sampling parameters on this face either, so temperature and top_p are rejected before provider I/O
  5. Combination constraints for max_tokens:

    • deepseek.chat.v1: max_completion_tokens is value-dependent too: it counts reasoning tokens and DeepSeek’s max_tokens does not, so it is carried as max_tokens on a request with thinking off and rejected before provider I/O on one with thinking on, or on one that already carries max_tokens
    • minimax.chat.v1: max_completion_tokens is carried as itself and max_tokens is carried into it only while thinking is off, because MiniMax’s single output bound counts reasoning; max_tokens is value-dependent for that reason — a request that also thinks, or that carries both members, is routed away by field before provider I/O rather than refused after the budget is reserved
    • kimi.chat.v1: max_tokens is value-dependent: Kimi has one output bound and it counts reasoning, so an answer-only bound is the same tokens exactly while nothing is thinking — which is the ordinary case, because a request that names no depth is sent with reasoning switched off. A request that does ask for depth, or that carries both output members, is routed away before provider I/O
  6. Combination constraints for max_completion_tokens:

    • deepseek.chat.v1: max_completion_tokens is value-dependent too: it counts reasoning tokens and DeepSeek’s max_tokens does not, so it is carried as max_tokens on a request with thinking off and rejected before provider I/O on one with thinking on, or on one that already carries max_tokens
    • minimax.chat.v1: max_completion_tokens is carried as itself and max_tokens is carried into it only while thinking is off, because MiniMax’s single output bound counts reasoning; max_tokens is value-dependent for that reason — a request that also thinks, or that carries both members, is routed away by field before provider I/O rather than refused after the budget is reserved
  7. Combination constraints for n:

    • deepseek.chat.v1: n and parallel_tool_calls are value-dependent in the same way: n=1 and parallel_tool_calls=true are what omitting the member already means, and only n>1 and a request to run tools one at a time are rejected
    • kimi.chat.v1: Kimi speaks this wire format but does not model the sampling parameters at all: temperature, top_p and n are absent from its request schema, and its parameter reference pins each to one value per model and answers any other with an error, so they are rejected before provider I/O rather than sent or silently replaced with the pinned value
  8. Combination constraints for stop:

    • minimax.anthropic.messages.v1: stop is rejected before provider I/O rather than sent: MiniMax documents stop_sequences as ignored, so sending it would return 200 for a completion that ran past a boundary the caller set and paid for
    • kimi.chat.v1: stop is value-dependent: Kimi documents it as honoured and it is carried, but only within its published bounds of five sequences of at most 32 bytes each, and a request outside them is routed away before provider I/O rather than refused while the body is encoded
  9. Combination constraints for tools:

    • deepseek.chat.v1: n and parallel_tool_calls are value-dependent in the same way: n=1 and parallel_tool_calls=true are what omitting the member already means, and only n>1 and a request to run tools one at a time are rejected
    • bedrock.mantle.responses.v1: streaming requests with tools are rejected before provider I/O
    • bedrock.mantle.openai.responses.v1: streaming requests with tools are rejected before provider I/O
  10. Combination constraints for tool_choice:

    • kimi.chat.v1: tool_choice is value-dependent, and the two ways of forcing a tool call are not the same value: a named function together with a depth is refused by every Kimi model measured and is routed away before provider I/O, while required together with a depth is refused by the K2.x line and accepted by kimi-k3, which answers with a tool call and a reasoning span in one response. That second half is a per-model limit this profile-scoped declaration cannot express, so it is refused after the reservation rather than routed away with the kimi-k3 request that works
  11. Combination constraints for parallel_tool_calls:

    • deepseek.chat.v1: n and parallel_tool_calls are value-dependent in the same way: n=1 and parallel_tool_calls=true are what omitting the member already means, and only n>1 and a request to run tools one at a time are rejected
  12. Combination constraints for response_format:

    • anthropic.messages.2023-06-01: response_format and reasoning_effort are declared unsupported at field granularity because support is value-dependent: json_schema maps to output_config.format and the low/medium/high/xhigh/max ladder maps to output_config.effort, while json_object and any effort outside that ladder have no Anthropic representation and are routed away before provider I/O
    • minimax.chat.v1: response_format is value-dependent: json_object was measured against a real account on 2026-08-31 and is carried, text is dropped because it means what omitting the member already means, and the schema mode has never been sent and is rejected before provider I/O
  13. Combination constraints for reasoning_effort:

    • anthropic.messages.2023-06-01: response_format and reasoning_effort are declared unsupported at field granularity because support is value-dependent: json_schema maps to output_config.format and the low/medium/high/xhigh/max ladder maps to output_config.effort, while json_object and any effort outside that ladder have no Anthropic representation and are routed away before provider I/O
    • kimi.chat.v1: reasoning_effort is value-dependent: none, low and high are carried, and minimal, medium, xhigh and Kimi’s own max are routed away because the portable ladder and Kimi’s do not share those rungs