Latch / Docs
guides

Multi-mount latches

One token, many upstream services, one audit trail.

A multi-mount latch fronts several upstream services behind a single token. Instead of one upstream and one policy, the latch carries a mount table: a list of path-prefixed mounts, each routing to its own service. One lat_… token, many services, one audit trail.

Each mount is configured exactly like a standalone (single-upstream) latch, with its own upstream URL, its own bound secret, and its own policy pipeline, reached through a path prefix rather than at the root. Everything you already know about configuring a latch applies per mount; see Core Concepts: Secret, Latch, and Pipeline in the Core concepts.

Create a multi-mount latch from scratch

Mounts are enabled from the latch editor, but the control is easy to miss: in the left pipeline panel, below the filter list, click the small Convert to multi-mount button. That switches the latch from a single upstream to a mount table.

Then add one mount per upstream service, each with its own path prefix, upstream URL, secret, and pipeline. A two-mount latch behaves like two independent latches that happen to share one token and one audit trail:

Mount Upstream Secret Pipeline
/openai https://api.openai.com OpenAI key method + endpoint + rate limit
/stripe https://api.stripe.com Stripe key GET-only

Mount paths must be unique within a latch, and a request that matches no mount is rejected (404) with the list of available mount paths.

Some settings live on each mount, others on the latch as a whole:

Setting Scope
Upstream URL per mount
Secret per mount
Policy pipeline per mount
Name per latch
Timeout per latch
Expiry per latch

The timeout and expiry are defined once at the latch level and apply across every mount. There is no per-mount timeout or expiry.

Editing after creation

A single-upstream latch's upstream URL and secret are fixed once created; only its pipeline and metadata can change afterward. Mount-level fields are the exception: a mount's upstream URL and secret can be edited after creation, alongside its pipeline. Changes are saved in place.

Converting a single-upstream latch

Using the same Convert to multi-mount button on an already-configured latch moves the latch's own upstream URL, secret, and pipeline into the first mount (at path /). The latch keeps its name, timeout, and expiry. Add further mounts from there.

Simulating a mount

The Simulate tool builds its dry-run scenarios per mount, prefixing each scenario's path with the mount it targets, so you can exercise each service's pipeline independently against the live latch.

Calling a mount

Callers hit /proxy/<mount-path>/<rest>. Latch matches the request path against the mount table (longest prefix wins), strips the mount prefix, evaluates that mount's pipeline, injects that mount's secret, and forwards the remainder to that mount's upstream:

POST /proxy/openai/v1/chat/completions   ->   /openai mount   ->   POST https://api.openai.com/v1/chat/completions

Extending a latch

Any latch can be extended into a child that inherits its policy and can only narrow it: one credential fanning out into progressively more restricted tokens. The child resolves its parent chain live at request time (see Dynamic Inheritance in the Core concepts).

An extension inherits the mount table from its chain if an ancestor defines one. It routes through that table at request time and can narrow it: the editor shows each inherited mount read-only (its upstream, secret, and pipeline all belong to that ancestor) and lets you remove a mount or append extra filters on top of its pipeline, narrowed further by any overrides added in between. You cannot change an inherited mount's upstream or secret. The extension can also define mounts of its own (via Convert to multi-mount), bringing their own upstreams and secrets, on paths that do not collide with the inherited ones. Any latch-level filters it adds apply on top of every mount it routes, inherited or its own.

How the policies compose. Every latch in the chain contributes its latch-level pipeline in order (root to child), and the matched mount's pipeline runs last. First deny wins:

grandparent latch-level pipeline
  -> parent latch-level pipeline
    -> child latch-level pipeline
      -> the matched mount's own pipeline

The matched mount can belong to any latch in the chain (grandparent, parent, or child) and wherever it lives, it is evaluated last. Note this means ordering follows the pipeline stage, not parent-versus-child: a child's latch-level filters run before the matched mount's filters even when that mount belongs to the parent.

Examples:

  • Single-upstream parent, multi-mount child. Extend a single-upstream latch carrying rate_limit + method: GET,POST into a multi-mount child with /github and /slack mounts. Every request to either mount is first checked against the parent's rate limit and method rule, then the mount's own pipeline.
  • Multi-mount parent, inheriting child. Extend a multi-mount latch (/github, /slack) with a child that adds no mounts of its own but adds a latch-level rate_limit. The child inherits both mounts, and its rate limit applies across both, evaluated before each inherited mount's own pipeline.

Temporary restrictions

Four restrictions apply today while the extension path is still being built out.

  1. A child that inherits the parent's mounts (adding none of its own) reuses the parent's sealed policy as-is, so any filter it adds, whether latch-level or appended to an inherited mount, can be declarative only (method, endpoint, rate limit, payload, time window, and so on). The editor's Add-filter menu omits custom-code and Cedar in both places, since those would need a reseal of a WASM the child does not own.

  2. A child that defines its own mounts gets its own sealed policy, so the parent's latch-level custom-code filter does not carry into it. The parent's declarative filters (including Cedar) still apply through the chain. If you rely on a parent's custom-code policy to constrain descendants, be aware it will not reach a child that introduces its own mounts.

  3. Only one latch in a chain may define mounts. You can convert a child of a single-upstream latch into a multi-mount latch, but you cannot give a child its own mount table when an ancestor already has one; the child inherits and narrows that table instead. Structurally, a separate mount table would replace the ancestor's wholesale rather than restrict it (the deepest mounted latch wins), so it is held back until an extension's policy can be compiled from the full chain.

  4. A latch with its own mounts carries no separate latch-level pipeline. Converting a latch to multi-mount folds its existing latch-level pipeline into the first mount (at /); a multi-mount latch's policy then lives entirely in its mounts. So a filter meant to apply across all of a latch's own mounts has to be repeated in each for now. (A child that inherits a mount table can still add latch-level filters across every inherited mount, subject to restriction 1.)

The full build-out compiles every extension's policy from the full parent chain. At that point a child can freely add custom-code and Cedar, a parent's custom-code always carries over, a latch can carry a latch-level pipeline alongside its own mounts, and a child can add its own mounts next to the inherited ones, as long as their paths do not overlap.