Gateway configuration reference
The gateway-config/v1 document, the named refusals with their statuses, the builder operations, and the pinning vocabulary.
On this page
A managed gateway’s configuration is one JSON document per gateway and environment, plus a closed list of names the control plane answers with when it declines to store or compile one. This page describes both, and the operations that read and write them.
It mirrors the shipped contract: the document port
(internal/control/foundation/gatewayconfig), the lane’s problem map, and the operation
table the SDK and the CLI pin. For the task flow that produces a document, read
Configure, test and promote a gateway.
The gateway-config/v1 document
One document describes one gateway in one environment. The same gateway has a different document in QA than in Production, and the environment is part of the document rather than only part of its address.
The document carries references and dials, not content: inventory target ids, pinned
(policy_id, policy_revision) pairs, guardrail profile revisions, and slot bindings. The
policy content itself lives in the policy ledger, which is why two gateways can pin the
same revision without copying it.
| Field | Type | What it holds |
|---|---|---|
schema_version |
string | gateway-config/v1, the one accepted value |
gateway_id |
string | the gateway this document configures |
environment_id |
string | the environment this document configures |
model_choices |
array | the models this gateway may call, with their dials |
routes |
array | ordered first-match branches, default route last |
input_protection |
object | the guardrail attachment that sees the request |
output_protection |
object | the guardrail attachment that sees the answer |
connection_bindings |
array | one provider connection per upstream slot |
Model choices
A model choice names one entry of the operator inventory the workspace may point at. A workspace can name a target; it does not create one.
| Field | Type | What it holds |
|---|---|---|
id |
string | mc_ plus twelve base58 characters, gateway-private |
label |
string, optional | the name a colleague reads |
target_id |
string | an inventory target id, checked for eligibility by the service |
settings.temperature |
number, optional | absent and zero are different documents |
settings.max_output_tokens |
integer, optional | above zero when present |
settings.timeout_ms |
integer, optional | above zero when present |
Routes
Routes evaluate in slice order and the first match wins, so the order of the array is the routing semantics rather than a rendering detail.
| Field | Type | What it holds |
|---|---|---|
id |
string | rt_ plus twelve base58 characters, gateway-private |
label |
string, optional | the name the route list shows |
predicate |
object or null |
null marks the default route |
policy_id |
string | a policy in the caller’s own workspace |
policy_revision |
integer | a revision above zero, not a head |
model_choice_id |
string | a model_choices[].id in this document |
fallback_model_choice_id |
string, optional | a second choice, different from the primary |
A predicate is one registered fact, one operator and one value from that fact’s closed set. v1 registers one fact.
fact |
op |
value |
|---|---|---|
request.stream |
is |
true or false |
A fact outside that table is declined by name instead of becoming a branch that matches nothing.
Protection attachments
input_protection and output_protection carry the same three fields, and both pin a
revision.
| Field | Type | What it holds |
|---|---|---|
profile_id |
string | the gateway-local guardrail profile |
profile_revision |
integer | a revision above zero, not a head |
disabled_rule_ids |
array of string, optional | inherited rules turned off here; duplicates are declined |
A required company or workspace rule is not removable through that list. The composer
puts mandatory bindings first in each phase, and the more restrictive action wins, ordered
block, modify, monitor, simulate.
Connection bindings
A slot id is the inventory upstream’s name, which is why a slot maps to a connection and not to a model: several models of one upstream share one credential.
| Field | Type | What it holds |
|---|---|---|
slot_id |
string | the upstream name of the targets bound here |
connection_id |
string | a saved provider connection this environment may bind |
purpose |
string | application, playground, comparison, judge or connection_validation |
A release binds the application purpose. The other four name the same closed set the
connection grants use.
Constraints
The document port validates offline: the same bytes are accepted or declined on any machine, with no store, clock or network involved.
schema_versionequalsgateway-config/v1, andgateway_idandenvironment_idare both set.model_choicesis non-empty, its ids are unique, and everytarget_idis set.routesis non-empty, its ids are unique, and no two predicates are identical.- One route has
predicate: null, and it is the last element. - Every
model_choice_idandfallback_model_choice_idresolves inside this document, and a fallback differs from its own primary. - Every pinned revision is above zero.
- Each
slot_idappears in at most one binding, and eachpurposeis in the closed set.
The first problem is returned, naming the field and the array index that produced it.
Identifier grammar
Model-choice and route ids are minted inside one draft and mean nothing outside it. Both
follow the platform’s id discipline: a prefix plus twelve characters from the base58
alphabet 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz, which omits 0,
O, I and l.
| Prefix | Names | Scope |
|---|---|---|
mc_ |
a model choice | one gateway draft |
rt_ |
a route | one gateway draft |
Ids the document points at are minted elsewhere and carry their own prefixes: gw_ a
gateway, env_ an environment, pc_ a provider connection, rtp_ a routing policy.
Canonical bytes
The canonical encoding sorts object members by key byte order, keeps arrays in place, and emits no insignificant whitespace. It validates first, so a document the port declines has no canonical form and cannot reach a digest through the door a valid one uses. Reordering the struct that describes the document cannot move a digest.
Named refusals and their statuses
Each named answer below is an RFC 7807 problem body whose type is the slug and whose
status is the one in this table. The pairing is closed: a handler producing a slug whose
status disagrees with the table serves a 500 instead, so the two cannot drift apart.
| Slug | Status | What it reports |
|---|---|---|
draft_revision_stale |
412 | somebody saved first; the body carries the winner’s revision and document |
head_revision_stale |
412 | the deployment head or the policy head moved since the form read it |
draft_conflict |
409 | two writers changed one draft in a way a single save cannot represent |
model_choice_referenced |
409 | a deleted model choice is still selected by routes, which are listed |
environment_not_active |
409 | the environment is retiring or retired, so drafts, releases and deploys are closed |
environment_has_references |
409 | retirement is blocked; the body carries the reference report |
provider_test_required |
409 | the destination requires provider evidence and none is recorded |
provider_test_stale |
409 | a test is recorded against a different effective digest |
rollback_incompatible |
409 | the prior release no longer validates under today’s controls and grants |
connection_context_changed |
409 | a bound connection’s provider, endpoint or account label moved |
mandatory_generation_stale |
409 | the required controls changed between the review and the apply |
validation_failed |
422 | the compiler’s diagnostics, each at its own JSON pointer |
policy_document_not_composable |
422 | a shared policy’s document is not a single tier leaf or one cascade |
inventory_target_ineligible |
422 | a model choice points at a target this environment may not use |
connection_slot_unbound |
422 | a slot the compiled artifact dispatches to has no connection |
default_route_missing |
422 | the compiled artifact has no unconditional route |
connection_not_eligible |
403 | the caller or the environment may not bind that connection |
environment_protected_deploy_denied |
403 | the caller holds deploy but not deploy.protected here |
policy_revision_not_found |
404 | a revision nobody published and one in another company read alike |
release_digest_mismatch |
503 | the two compile paths produced different digests, so nothing is served |
Which diagnostics stop a save and which are warnings
default_route_missing, default_route_not_last, duplicate_route_condition,
policy_revision_not_found, policy_reference_cycle and inventory_target_ineligible
are validation errors wherever they appear. unreachable_route_condition and
model_choice_unreferenced are warnings when you inspect a draft and errors when you
create a release from it, so an experiment may carry an unused model and a release may
not.
Operations
Every path below is relative to the lane base /portal/api/v1/customer/v1/, and
{gw-path} stands for
organizations/{org}/workspaces/{ws}/applications/{app}/gateways/{gw}. The action column
names the grant the operation requires.
Drafts and the two catalogues
| Operation | Method | Path | Action |
|---|---|---|---|
customer.gateways.draft.get |
GET | {gw-path}/environments/{env}/draft |
gateway.read |
customer.gateways.draft.put |
PUT | {gw-path}/environments/{env}/draft |
config.edit |
customer.gateways.draft.history |
GET | {gw-path}/environments/{env}/draft/history |
gateway.read |
customer.gateways.components |
GET | {gw-path}/components |
gateway.read |
customer.gateways.targets |
GET | {gw-path}/environments/{env}/targets |
gateway.read |
draft.get seeds a gateway nobody has edited yet, so it is a read that can write, and it
seeds only for a caller who also holds config.edit. The palette names no environment: a
card’s availability follows the gateway profile and the caller’s grants.
Explanations
| Operation | Method | Path | Action |
|---|---|---|---|
customer.gateways.inspect |
POST | {gw-path}/environments/{env}/inspect |
gateway.read |
customer.gateways.diff |
POST | {gw-path}/environments/{env}/diff |
gateway.read |
Both are POSTs because a subject and a request description do not belong in a query string. Neither contacts a provider.
Shared policies
| Operation | Method | Path | Action |
|---|---|---|---|
customer.policies.list |
GET | organizations/{org}/workspaces/{ws}/policies |
gateway.read |
customer.policies.get |
GET | organizations/{org}/workspaces/{ws}/policies/{p} |
gateway.read |
customer.policies.publish |
POST | organizations/{org}/workspaces/{ws}/policies/{p}/revisions |
config.edit |
customer.policies.copy |
POST | organizations/{org}/workspaces/{ws}/policies/{p}/copy |
config.edit |
customer.policies.impact |
GET | organizations/{org}/workspaces/{ws}/policies/{p}/revisions/{rev}/impact |
gateway.read |
customer.policies.adopt |
POST | {gw-path}/environments/{env}/draft/adopt |
config.edit |
Publishing appends a revision and moves nobody: each consumer stays pinned until its own draft adopts the new one. The impact report is filtered to the scopes the caller may read, and consumers outside them appear as counts.
Environments
| Operation | Method | Path | Action |
|---|---|---|---|
customer.environments.list |
GET | organizations/{org}/workspaces/{ws}/environments |
workspace.read |
customer.environments.create |
POST | organizations/{org}/workspaces/{ws}/environments |
workspace.manage |
customer.environments.get |
GET | organizations/{org}/environments/{env} |
workspace.read |
customer.environments.update |
PUT | organizations/{org}/environments/{env} |
workspace.manage |
customer.environments.references |
GET | organizations/{org}/environments/{env}/references |
workspace.manage |
The update body carries display_name, state, require_fresh_provider_test and
expected_revision. It has no field for selector or protected: both are fixed at
creation, and the store’s update statement cannot reach either column.
Releases and deployments
| Operation | Method | Path | Action |
|---|---|---|---|
customer.releases.create |
POST | {gw-path}/environments/{env}/releases |
config.edit |
customer.releases.list |
GET | {gw-path}/environments/{env}/releases |
gateway.read |
customer.releases.get |
GET | {gw-path}/releases/{rel} |
gateway.read |
customer.deployments.deploy |
POST | {gw-path}/environments/{env}/deployments |
deploy |
customer.deployments.rollback |
POST | {gw-path}/environments/{env}/deployments/rollback |
deploy |
customer.deployments.head |
GET | {gw-path}/environments/{env}/deployments/head |
gateway.read |
customer.gateways.promote.review |
POST | {gw-path}/environments/{env}/promotion/review |
config.edit |
customer.gateways.promote.apply |
POST | {gw-path}/environments/{env}/promotion/apply |
deploy |
Creating a release is authoring a pinning under config.edit; deploying one spends money
under deploy, and a protected environment adds deploy.protected. A repeated create
mints a second release rather than collapsing into the first, so a client that retried a
timed-out create lists and compares instead of assuming absorption.
promote.apply states three expectations about the destination, all required, and each
one loses with the slug that names what moved. The apply recomputes the plan rather than
trusting the caller’s: the caller states expectations and the service states facts.
| Member | What it echoes | Slug when it is stale |
|---|---|---|
expected_mandatory_generation |
the destination’s required-controls generation at review time | mandatory_generation_stale |
expected_draft_revision |
the destination’s current draft revision at review time | draft_revision_stale |
expected_head_revision |
the destination’s deployment head revision, 0 for a target nobody has deployed |
head_revision_stale |
They are checked in that order, and the draft revision is settled before anything is
derived or deployed. A concurrent edit of the destination therefore loses as
draft_revision_stale and never reaches the head.
A release read publishes the release’s identity rather than its document: the revisions it
pins, its effective digest, its mandatory generation and its resolved connection bindings.
It carries no routes and no model_choices, which is why a client comparing a draft
against what is applied calls customer.gateways.diff rather than rendering a release.
Availability. These eight rows are declared in the lane’s registry, and the promotion,
release and deployment handlers behind them are landing across this private test.
Promotion review and apply, the deployment head read and the diff are the four the shipped
portal calls today. A deployment that composes no release ledger answers 503 dependency_unavailable with “deployments not configured” rather than 404, so a wired
path and a misspelled one stay distinguishable.
Words a release pins
Seven words carry the lifecycle, and readers get the last three wrong most often.
| Term | What it means |
|---|---|
| draft | the editable gateway-config/v1 document of one gateway in one environment; saving one reroutes no traffic |
| draft revision | the append-only counter of that document’s saves, echoed back as expected_revision |
| release | one immutable pinning derived from a draft: the settings revision, the derived policy and profile revisions, the mandatory generation, every slot’s connection, and the effective digest |
| deployment head | the one mutable pointer per gateway and environment: desired release, applied release, derived state, and the revision a change must echo |
| effective digest | the compiler’s identity for what will execute, compared between the control plane and the serving cell |
| mandatory generation | the version of the required controls a release was compiled under, detectable later without recompiling |
| slot | an inventory upstream’s name, and the unit a connection binds |
Creating a release changes nothing about what a gateway serves; only the deployment head
does that. A target nobody has deployed has no head, which is a different fact from a head
in draft state.
The digest is why a release cannot drift: the control plane derives and compiles it, the
serving cell resolves the same pinned revisions under the same compiler id, and a mismatch
is release_digest_mismatch rather than a request served from a guess. The mandatory
generation is what makes mandatory_generation_stale and rollback_incompatible
answerable. A slot maps to a connection because several models of one upstream share one
credential, and an unknown upstream stops the request before dispatch.
What this page does not cover
- The policy language inside a pinned revision: Routing policy language.
- The environment lifecycle and promotion review as a task: Move a gateway between environments.
- The grants named in the action column: Who can do what in your company.