---
title: "Enroll a worker on the WireGuard mesh with no address of its own (ADR 055)"
method: POST
path: "/internal/nodes/mesh-enroll"
tags: ["Nodes"]
---

# Enroll a worker on the WireGuard mesh with no address of its own (ADR 055)

`POST /internal/nodes/mesh-enroll`

## Request

- Base URL: `/api` (relative, and the document was published on a file host, not at the API’s origin)
- URL: `/api/internal/nodes/mesh-enroll`
- Auth: none declared

The worker sends its WireGuard public key with a join token; the control
plane reserves it a mesh address and answers with what it needs to dial
the control plane and register over the mesh node API.

## Request body

- MeshEnrollApiRequest: Body of `POST /internal/nodes/mesh-enroll` (ADR 055).
  - `join_token` string, password: A credential. Write-only: it is never returned, and the AI assistant supplies it through a secret slot instead of typing it into the conversation.
  - `labels` unknown
  - `name` string, required: The name the worker will register under.
  - `wg_public_key` string, required: The worker's base64 WireGuard public key. The private key never leaves the worker.

## Response `201`

Pairing created; register over the mesh next

- MeshEnrollResponse: What a worker that joins outbound only needs to bring its end of the mesh up and register over it (ADR 055): a pairing code without its secret.
  - `ca_fingerprint` string, required: SHA-256 fingerprint of the cluster CA the node API presents.
  - `control_plane_address` string, required: The control plane's mesh address; the node API listens there.
  - `control_plane_endpoint` string, required: `ip:port` the worker dials.
  - `control_plane_public_key` string, required
  - `expires_at` string, required: When the pairing and its token expire (RFC 3339).
  - `join_token` string, required: Single-use join token bound to `name`, accepted only over the mesh node API from `node_address`.
  - `listen_port` integer, required: The mesh UDP port.
  - `name` string, required: The name the pairing's join token is bound to.
  - `node_address` string, required: The worker's mesh address, reserved for it. It registers under it.
  - `node_api_port` integer, required: TCP port of the node API on the control plane's mesh address.
  - `pairing_id` integer, required: The pairing created for the worker.
  - `prefix_len` integer, required

## Other responses

- `400`: Invalid name or WireGuard key
- `403`: Missing, unknown or unusable join token
- `409`: Mesh off or not ready, control plane not dialable, key in use, or too many pairings
- `429`: Rate limited
- `500`: Internal server error

## Changes

> 98 revisions in range; 1 not diffed.

- **2026-10-22** `d8a717162ef5`: 1 info
  - endpoint added
- **2026-10-22** `06005615b19e`: 1 breaking
  - api path removed without deprecation
- **2026-10-22** `7d41b820a372`: 1 info
  - endpoint added
- **2026-10-22** `add7f7b41ae2`: 1 breaking
  - api path removed without deprecation
- **2026-10-22** `6195c1a88c77`: 2 breaking, 1 info
  - the request property `join_token` became not nullable
  - the `join_token` request property type changed from `string, null` to no type
  - added `#/components/schemas/SecretString, subschema #2` to the `join_token` request property `oneOf` list

[Operation history](https://skmtc.dev/gotempsh/apis/temps/changes/internal/nodes/mesh-enroll/post.md)

---

[API](https://skmtc.dev/gotempsh/apis/temps.md) - [All operations](https://skmtc.dev/gotempsh/apis/temps/llms.txt) - [OpenAPI document](https://skmtc.dev/gotempsh/apis/temps/revisions/fdfa2df06ae7?raw)
