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:

ParameterTypeDescription
keystringLock key (alphanumeric, dashes, underscores, colons)

Body Parameters:

ParameterTypeRequiredDefaultMaxDescription
ttlnumberNo10000900000Lock lifetime in ms. Auto-releases after expiry.
timeoutnumberNo10000900000Max wait time in ms. Returns 408 if exceeded.
tokenstringNoExisting 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:

ParameterTypeDescription
keystringLock key (must match the key from acquire)

Body Parameters:

ParameterTypeRequiredDescription
tokenstringYesToken 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:

ParameterTypeDescription
keystringLock 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.