# Semaphore leases

A lease is one held slot on a semaphore. A test takes one around the part that uses the shared resource, renews it while it works and releases it when done; a lease that is not renewed ends after its TTL, so a test that dies frees its slot. Leases take the run token that starting a run, a shard or a retry returns as `semaphores.token`, not an API key, and `semaphores.url` is where their routes start. An unpublished run has no run token, so it holds none.

## The SemaphoreLease object

One held slot on a semaphore, until it is released, expires or its holder finishes.

### Attributes

- `lease` (string, required): The lease's ID. Renew and release the lease by it.
- `semaphore` (string, required): The semaphore's name.
- `expiresAt` (string, required): When the lease ends unless it is renewed, as an ISO 8601 timestamp. Its slot frees itself then.

### Example

```json
{
  "lease": "Ls4TnV8qWm2C",
  "semaphore": "payments-sandbox",
  "expiresAt": "2026-10-14T09:41:20.000Z"
}
```

## Take a lease

`POST /v1/workspaces/{slug}/semaphores/{name}/leases`

Takes a slot on the semaphore, creating it with `capacity` on first use. When every slot is held, the request waits up to 20 seconds for one to free, then fails with Busy and `retryAfter`; ask again until your own timeout. Another `capacity` than the semaphore's fails with Conflict. The lease belongs to the shard or retry whose run token took it, and finishing that shard or retry releases it.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `name` (string, in path, required): The semaphore's name, such as payments-sandbox: 1 to 100 lowercase letters, digits, dots, underscores or hyphens, starting with a letter or digit.
- `capacity` (number, required): How many leases the semaphore allows at once, 1 to 100. The first lease creates the semaphore with it; every later lease must send the same.
- `ttlSeconds` (number): How long the lease lasts from now, in whole seconds, from 5 to 600; 60 by default. Renew it every third of that.
- `test` (nullable string): The test taking the lease, as members see it on the semaphore, up to 200 characters.

### Returns

Returns the [SemaphoreLease](https://docs.clankership.com/api/semaphore-leases/object.md) object. The status is 201.

### Request

```bash
curl -X POST https://api.clankership.com/v1/workspaces/{slug}/semaphores/{name}/leases \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "capacity": 1,
    "ttlSeconds": 60,
    "test": "checkout › applies a coupon"
  }'
```

### Response

```json
{
  "lease": "Ls4TnV8qWm2C",
  "semaphore": "payments-sandbox",
  "expiresAt": "2026-10-14T09:41:20.000Z"
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Renew a lease

`POST /v1/workspaces/{slug}/semaphores/{name}/leases/{lease}/renew`

Extends a lease to `ttlSeconds` from now and returns its new expiry. A lease that was released or expired, or that another run holds, fails with NotFound: take a new one.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `name` (string, in path, required): The semaphore's name, such as payments-sandbox: 1 to 100 lowercase letters, digits, dots, underscores or hyphens, starting with a letter or digit.
- `lease` (string, in path, required): The lease's ID, from taking it.
- `ttlSeconds` (number): How long the lease lasts from now, in whole seconds, from 5 to 600; 60 by default. Renew it every third of that.

### Returns

Returns the [SemaphoreLease](https://docs.clankership.com/api/semaphore-leases/object.md) object. The status is 200.

### Request

```bash
curl -X POST https://api.clankership.com/v1/workspaces/{slug}/semaphores/{name}/leases/{lease}/renew \
  -H "Authorization: Bearer $CLANKER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ttlSeconds": 60
  }'
```

### Response

```json
{
  "lease": "Ls4TnV8qWm2C",
  "semaphore": "payments-sandbox",
  "expiresAt": "2026-10-14T09:41:20.000Z"
}
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.

## Release a lease

`DELETE /v1/workspaces/{slug}/semaphores/{name}/leases/{lease}`

Frees the lease's slot for the next test. Releasing a lease that already ended succeeds too.

### Parameters

- `slug` (string, in path, required): The workspace's slug or ID, such as acme.
- `name` (string, in path, required): The semaphore's name, such as payments-sandbox: 1 to 100 lowercase letters, digits, dots, underscores or hyphens, starting with a letter or digit.
- `lease` (string, in path, required): The lease's ID, from taking it.

### Returns

Returns no content. The status is 204.

### Request

```bash
curl -X DELETE https://api.clankership.com/v1/workspaces/{slug}/semaphores/{name}/leases/{lease} \
  -H "Authorization: Bearer $CLANKER_API_KEY"
```

### Errors

- `400` `InvalidInput`: The request is malformed, or a value is not allowed.
- `401` `Unauthorized`: The request has no valid API key.
- `403` `Forbidden`: The key's user or role may not do this.
- `404` `NotFound`: The workspace or object does not exist, or the caller cannot see it.
- `409` `Conflict`: The change conflicts with current data, such as a slug that is already in use.
- `500` `Internal`: Clanker Ship failed to handle the request. Retry, or report the response's x-request-id.
