# joinRequests (/docs/reference/api/join-requests)

> Join requests are the approval queue for joins an attestor deferred: a machine with placeholder hardware identifiers, a machine the `discovery` attestor did not know, or a TPM whose endorsement key is on no allow-list. An approval admits exactly one join, which the machine's retry with the same CSR consumes. The group is decided on `compute-pool/{poolId}` with the join-token actions. See [Joining nodes](/docs/guides/nodes/joining).



Join requests are the approval queue for joins an attestor deferred: a machine with placeholder hardware identifiers, a machine the `discovery` attestor did not know, or a TPM whose endorsement key is on no allow-list. An approval admits exactly one join, which the machine's retry with the same CSR consumes. The group is decided on `compute-pool/{poolId}` with the join-token actions. See [Joining nodes](/docs/guides/nodes/joining).

All 3 methods are served by this release.

| Method                | Kind     | Authorization                                 | Phase | Status     |
| --------------------- | -------- | --------------------------------------------- | ----- | ---------- |
| [`list`](#list)       | read     | `compute:join-tokens:read` @ `compute-pool`   | v0.2  | registered |
| [`approve`](#approve) | mutation | `compute:join-tokens:create` @ `compute-pool` | v0.2  | registered |
| [`reject`](#reject)   | mutation | `compute:join-tokens:create` @ `compute-pool` | v0.2  | registered |

## list [#list]

`POST /api/compute/joinRequests/list`

Lists the join requests a pool can decide: its own, and its site's requests that no pool has claimed yet.

Decided as `compute:join-tokens:read`. `state` defaults to `pending`; `all` lists every state. A pending or approved request past its expiry reads as `expired`, whether or not retention has marked it yet. Pages hold up to 200 requests (100 by default). A pool the tenant does not own is `NOT_FOUND`.

* **Kind:** read
* **Authorization:** `compute:join-tokens:read` @ `compute-pool`
* **Module:** `server`
* **Phase:** v0.2 (registered)

**Input**

| Field      | Type                                                                   | Required | Constraints                                                           |
| ---------- | ---------------------------------------------------------------------- | -------- | --------------------------------------------------------------------- |
| `tenantId` | string                                                                 | yes      | at most 128 characters; matches `^[A-Za-z0-9._:-]{1,128}$`            |
| `poolId`   | string                                                                 | yes      | at most 128 characters; matches `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$` |
| `state`    | one of `pending`, `approved`, `rejected`, `expired`, `consumed`, `all` |          | default `"pending"`                                                   |
| `limit`    | integer                                                                |          | 1-200; default `100`                                                  |
| `cursor`   | string                                                                 |          | at most 64 characters; matches `^jreq_[0-9a-hjkmnp-tv-z]{26,52}$`     |

## approve [#approve]

`POST /api/compute/joinRequests/approve`

Approves a pending join request, assigns it to the pool and returns it with a new expiry of `ttlSeconds` (60 to 86,400, one hour by default).

Decided as `compute:join-tokens:create`. Approving a request that is already approved for this pool returns the approval unchanged. Any other decided state (`rejected`, `expired`, `consumed`) is `INVALID_STATE`, and so is an archived pool. A pool the tenant does not own, or a request the pool cannot see, is `NOT_FOUND`.

Publishes `security.join-request.approved` with the new `expiresAt`.

* **Kind:** mutation
* **Authorization:** `compute:join-tokens:create` @ `compute-pool`
* **Module:** `server`
* **Phase:** v0.2 (registered)

**Input**

| Field        | Type    | Required | Constraints                                                           |
| ------------ | ------- | -------- | --------------------------------------------------------------------- |
| `tenantId`   | string  | yes      | at most 128 characters; matches `^[A-Za-z0-9._:-]{1,128}$`            |
| `poolId`     | string  | yes      | at most 128 characters; matches `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$` |
| `requestId`  | string  | yes      | at most 64 characters; matches `^jreq_[0-9a-hjkmnp-tv-z]{26,52}$`     |
| `ttlSeconds` | integer |          | 60-86400; default `3600`                                              |
| `reason`     | string  |          | at most 256 characters; matches `^[^\u0000-\u001f\u007f]*$`           |

## reject [#reject]

`POST /api/compute/joinRequests/reject`

Rejects a pending join request, or withdraws an approval the machine has not used yet, and returns the request.

Decided as `compute:join-tokens:create`. Rejecting a request that is already rejected returns it unchanged. An `expired` or `consumed` request is `INVALID_STATE`. A later retry with the request's CSR fails.

Publishes `security.join-request.rejected`, with `reason` when one is given.

* **Kind:** mutation
* **Authorization:** `compute:join-tokens:create` @ `compute-pool`
* **Module:** `server`
* **Phase:** v0.2 (registered)

**Input**

| Field       | Type   | Required | Constraints                                                           |
| ----------- | ------ | -------- | --------------------------------------------------------------------- |
| `tenantId`  | string | yes      | at most 128 characters; matches `^[A-Za-z0-9._:-]{1,128}$`            |
| `poolId`    | string | yes      | at most 128 characters; matches `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$` |
| `requestId` | string | yes      | at most 64 characters; matches `^jreq_[0-9a-hjkmnp-tv-z]{26,52}$`     |
| `reason`    | string |          | at most 256 characters; matches `^[^\u0000-\u001f\u007f]*$`           |
