Latch API Reference
All endpoints require a Bearer token. See Authentication.
POST /v1/latch/:key/acquire
Acquire a lock with FIFO fairness. The request hangs until the lock is freed or timeout is reached.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
key | string | Lock key (alphanumeric, dashes, underscores, colons) |
Body Parameters:
| Parameter | Type | Required | Default | Max | Description |
|---|---|---|---|---|---|
ttl | number | No | 10000 | 900000 | Lock lifetime in ms. Auto-releases after expiry. |
timeout | number | No | 10000 | 900000 | Max wait time in ms. Returns 408 if exceeded. |
token | string | No | — | — | Existing token for reentrant acquire (refreshes TTL). |
Request:
curl -X POST "https://api.solenoid.systems/v1/latch/deploy:prod/acquire" \
-H "Authorization: Bearer sm_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"ttl": 10000, "timeout": 10000}'
Success (200):
{
"status": "acquired",
"token": "550e8400-e29b-41d4-a716-446655440000",
"expires_at": 1234567890,
"key": "deploy:prod",
"reentrant": false
}
token— UUID for releasing the lock. Keep secret.expires_at— Unix timestamp (seconds) when the lock auto-releases.reentrant— true if this refreshed an existing hold.
Error responses: 400 validation error, 402 insufficient balance, 408 timeout, 429 queue full. See Errors.
POST /v1/latch/:key/release
Release a lock using the token from acquire.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
key | string | Lock key (must match the key from acquire) |
Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Token received from acquire |
Request:
curl -X POST "https://api.solenoid.systems/v1/latch/deploy:prod/release" \
-H "Authorization: Bearer sm_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"token": "550e8400-e29b-41d4-a716-446655440000"}'
Success (200):
{ "status": "released" }
Error responses: 402 insufficient balance, 403 wrong token, 404 lock not found. See Errors.
GET /v1/latch/:key
Check lock status without modifying it. Free operation (no credit cost).
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
key | string | Lock key to check |
Request:
curl "https://api.solenoid.systems/v1/latch/deploy:prod" \
-H "Authorization: Bearer sm_your_api_key_here"
Response (200):
{
"locked": true,
"queue_depth": 3,
"ttl_remaining": 25000
}
locked— true if the lock is currently held.queue_depth— number of waiting requests (0 if unlocked).ttl_remaining— milliseconds until auto-release (null if unlocked).
FIFO Ordering
Locks are granted in strict first-in-first-out order.
T=0s: Client A acquires lock (200)
T=1s: Client B requests lock (hangs)
T=2s: Client C requests lock (hangs)
T=5s: Client A releases lock
T=5s: Client B gets 200 (next in queue)
T=8s: Client B releases lock
T=8s: Client C gets 200 (next in queue)
Auto-Release
Every lock has a TTL. When the TTL expires, a stateful storage alarm fires and releases the lock automatically. The next waiter in the queue receives it.
Ephemeral Queue
Waiter queues exist in memory on stateful storage. If the storage instance is evicted:
- The active lock holder keeps their lock until TTL expires.
- Waiting requests receive a 408 timeout.
- Clients should retry acquire on 408.
Eviction is rare for active locks.
Rate Limiting
See Rate Limits.
Metering
- Acquire — free
- Release — free
Latch is exempt from metering at the gateway (/v1/latch/ is in
METERING_EXEMPT_PREFIXES), so no lock operation deducts credits. The
per-minute rate limit for your tier still applies.
- Status — free
See Pricing for tier details.
Best Practices
Always release in a finally block:
let token = null
try {
const res = await fetch(url + '/acquire', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + key, 'Content-Type': 'application/json' },
body: JSON.stringify({ ttl: 10000, timeout: 10000 })
})
token = (await res.json()).token
await doWork()
} finally {
if (token) {
await fetch(url + '/release', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + key, 'Content-Type': 'application/json' },
body: JSON.stringify({ token })
})
}
}
Set TTL based on work duration. Too low and the lock auto-releases mid-work. Too high and crashed clients block others longer.
Set timeout slightly above expected wait time. Too low causes unnecessary 408s. Too high means clients wait too long for leaked locks.
Handle 408 gracefully. A timeout means you did not get the lock — retry or skip.