---
title: "Authorize a mablnet Host to join a Link Label's tunnel"
method: POST
path: "/link/mablnet/authorize/host"
tags: ["LinkMablnet"]
---

# Authorize a mablnet Host to join a Link Label's tunnel

`POST /link/mablnet/authorize/host`

Validates a `HostCredentials` blob presented by a mablnet client (Link Agent, execution Host, or future desktop-app Host) and returns the `tenantId` / `hostIdentity` / `validUntil` triple the mablnet protocol needs to admit the client. Internally called by Link Router on every carrier-join — see `mablnet-client-authorization.md in link/mablnet/docs` for the full design.
Three caller types are accepted, all auth'd against the standard mabl-api chain: `link_agent` API keys (workspace-scoped Link Agent keys), `execution` API keys (workspace-scoped, TTL'd, minted per test run), or user OAuth tokens (workspace members with write permission, mabl support admins included). The request body's `workspace_id` is cross-checked against the auth principal.
Failure semantics: `401` for bad credential / malformed body / stale `issued_at` / replayed `nonce` / (link_agent callers only) `host_id` not matching any active Link Agent; `402` for billing-suspended or LINK feature disabled; `403` for auth principal not authorised on `workspace_id`; `5xx` for transient backend failures including Redis-unavailable (fail-closed on the nonce cache).

## Request body

- LinkLabelAuthorizationRequest — Authorization request Link Router forwards to this endpoint for a mablnet carrier-join. Spec and rationale: `mablnet-client-authorization.md in link/mablnet/docs`. Two credential types share this schema, discriminated by the presence of `peer_credential`: 1. `peer_credential` absent — the legacy api-key path. `api_key` flows in the `Authorization: Basic` header; the body carries `owner_type`, `owner_id`, `label`, `host_id`, `issued_at`, `nonce` (all required for this path). Validated against the owner's API keys. 2. `peer_credential` present — an Ed25519-signed credential minted by `/link/mablnet/authorize/operator/tenant` or `/link/mablnet/authorize/peer`, forwarded verbatim as base64 of the exact `HostCredentials` bytes the peer presented at JOIN. The signature is verified over the blob's own canonical body — never over fields reconstructed from this request — and every peer attribute (workspace, label, host id, expiry) is read from the verified blob; the other properties in this schema are ignored for this path. (Fleet operator peer credentials are verified Router-side, not here — they never reach this endpoint.) See `mablnet-client-authorization.md in link/mablnet/docs` § Operator credentials.
  - `peer_credential` string — Base64 of the verbatim Ed25519-signed peer JOIN credential blob (`{role: "peer", sub, workspace_id, label, host_id, issued_at, expires_at, nonce, signature}` as minted). Present only on the peer path; its presence selects that path.
  - `owner_type` 'workspace' | 'company' | 'user' — The kind of entity a mablnet tunnel is scoped to — its "owner". `workspace` is the classic scope: one tunnel per workspace. `company` scopes a single tunnel to an entire company, so every workspace under that company shares one mablnet tenant (and one Link Agent fleet) instead of needing a tunnel each. `user` is a personal tunnel scoped to a single user, private to that user — its egress is the user's own machine, used to route a cloud test run through it. All three are mablnet-only: legacy Link Tunnel is always workspace-scoped.
  - `owner_id` string — The id of the owning entity — a workspace id when `owner_type` is `workspace`, a company id when `company`. Cross-checked against the auth principal: an api-key caller must be scoped to this workspace (or to a company that owns it, or — for a company owner — to a workspace within that company); a user-OAuth caller must be able to modify it. Required on the api-key path.
  - `label` string — The Link Label name within the owner (the customer-facing "tunnel name"). `(owner_type, owner_id, label)` uniquely identifies a tunnel; multiple Hosts can be in the same tunnel concurrently. Required on the api-key path.
  - `host_id` string — Per-Host-instance identifier the caller advertises. For `link_agent` keys this is composite — `<link-agent-id>:<host-bundle-version>` — and the `LinkAgent.id` prefix (the segment before the first `:`, or the whole value when there is none) MUST match an active `LinkAgent.id` in `(workspace_id, label)`; the version suffix is accepted and echoed so each host-bundle generation is a distinct Host. For `execution` keys and user OAuth callers this is validated for shape only (UUID-like or `la-`-prefixed mabl uid, ≤128 chars). Echoed back as `host_identity` in the response.
  - `issued_at` string, date-time — RFC 3339 timestamp when the caller constructed this blob. Rejected if more than ±5 minutes from server time (sliding-window replay bound).
  - `nonce` string — 16 random bytes from `SecureRandom`, hex-encoded (32 chars). One-shot uniqueness guard; the server-side cache rejects repeats within a 10 minute window per auth principal.
  - `router_infrastructure_key` string — The link infrastructure key of the Router forwarding this JOIN (its LINK_ROUTER_INFRA_KEY — `mabl` for the shared cluster, the customer key for a dedicated cluster). When present, the api enforces cluster exclusivity for BOTH credential paths: the caller-tenant's resolved key must equal this value or the JOIN is denied (a dedicated company's tunnels cannot ride the shared cluster, and nobody outside the company can join its dedicated cluster). Absent on Routers that predate key-reporting — the check is skipped, which is correct only while such Routers exist; see dedicated-link-infra.md in link/mablnet/docs.

## Response `200`

Authorization granted

- LinkLabelAuthorizationResponse — Authorization decision returned to Link Router on a successful call. Maps directly to the mablnet protocol's `AuthenticatedHost` value.
  - `tenant_id` integer, required — Unsigned 32-bit mablnet tenant identifier for this tunnel (`[0x00000001, 0xFEFFFFFF]`; system-tenant range `0xFFxxxxxx` reserved). Derived from the LinkLabel; see `mablnet-tenant-id-derivation.md in link/mablnet/docs`.
  - `host_identity` string, required — The `host_id` the caller supplied, echoed back after caller-type-branched validation. Used by the mablnet library for reconnect-grace bookkeeping.
  - `valid_until` string, date-time, required — RFC 3339 timestamp marking when this authorization expires. Fixed 1h window (`now + 1h`) regardless of caller type. The caller refreshes before this point via another `POST /link/mablnet/authorize/host`.
  - `access` 'full' | 'control_plane' — How much of the tenant this peer may reach once admitted. `full` — every service, subject to the usual tenant and visibility rules. `control_plane` — only services the Host registered as control-plane (the operator RPC surface); a stream to anything the Host advertises on its owner's behalf, such as SOCKS or probes, is refused by the Host at accept. Read off the peer credential's signed `access` claim, so the decision is the one made at mint time. Always present on this response — unlike the credential claim itself, which is omitted when `full` to keep ordinary credentials byte-identical. **An absent value still means `full`**: it is what an api predating this field returns, and what every api-key join carries. Restriction is opted into, never inferred from absence. The Router records this with the Host's registration and serves it to other Hosts asking `PeerAccessQuery`; see `support-admin-tunnel-access.md in link/mablnet/docs`.

## Other responses

- `400` — Invalid or missing parameter
- `401` — User not authenticated
- `402` — Credential is valid but the workspace is billing-suspended or the LINK feature is disabled — the customer-facing churn signal Link Tunnel uses today.
- `403` — User not authorized
- `default` — Unknown error

---

[API](https://skmtc.net/mabl/apis/mabl-api.md) · [All operations](https://skmtc.net/mabl/apis/mabl-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/mabl/mabl-api/revisions/d0fc0114da66/schema)
