Concurrency locks

Base URL

Lock routes live only on https://api.nthpool.cloud/pools/{pool}/lock, never on ‹pool›.nthpool.cloud or a custom domain. {pool} is your pool subdomain (for example prod-acme). The Examples section on the pool Overview page in the dashboard includes acquire, renew, and release.

All lock endpoints are POST and return JSON.

Authentication

Same caller auth as the pool:

  • Pool key (X-Nthpool-Key, or Authorization: Bearer / Nthbouncer when allowed)
  • Optional HMAC, JWT, Cloudflare Access, and IP policies from Access

The lock API never emits managed upstream credentials. You authenticate to the pool for a slot, then call the upstream yourself with whatever auth the origin expects.

First request

export POOL_KEY='nb_live_…'
export LOCK_API='https://api.nthpool.cloud/pools/your-pool/lock'

curl -sS -X POST "$LOCK_API/acquire" \
  -H "X-Nthpool-Key: $POOL_KEY" \
  -H "Content-Type: application/json"

A 200 body looks like:

{
  "slotId": "…",
  "leaseDeadline": 1710000030000,
  "requestDeadline": 1710000060000,
  "termMs": 30000,
  "renewIntervalMs": 10000
}

Hold slotId privately. Renew before leaseDeadline (about every renewIntervalMs, typically 10s). Renewal never extends past requestDeadline (the pool request/lease timeout). A vanished client's slot is reclaimed within one term (termMs, typically 30s).

Endpoints

POST /pools/{pool}/lock/acquire

Body (optional JSON):

Field Meaning
waitMs Opt-in queue wait in milliseconds (clamped to the pool queue timeout). Absent → fail fast

You can also set wait with X-Nthpool-Queue-Timeout-Ms. The JSON body wins if both are present. X-Nthpool-No-Queue forces fail-fast even when waitMs is set.

Success 200: Lock grant fields above, plus the usual governor response headers (x-nthpool-remaining-slots, x-nthpool-effective-limit, x-nthpool-queued-ms, …).

Errors:

Status error When
401 unauthorized Missing/invalid auth or nonce replay
403 unauthorized IP allowlist miss
413 payload_too_large Body too large
429 too_many_requests No free slot (fail-fast or queue full). May include Retry-After
504 queue_timeout Waited waitMs (or header budget) and no slot opened

Acquire attempts are written to the pool access log. Renew and release are not.

POST /pools/{pool}/lock/{slotId}/renew

Empty body. Call on a timer while your upstream work runs (every renewIntervalMs).

Status Body When
200 { "leaseDeadline": … } Term extended
404 not_found Slot already released or reclaimed
409 capped Slot hit the request-timeout cap. Release it.

POST /pools/{pool}/lock/{slotId}/release

Body (optional JSON). Outcome report that feeds adaptive concurrency / RPM learning (same rate-header dialects as the proxy):

Field Meaning
status Final upstream HTTP status (100-599)
throttled Count of upstream 429s you observed
retryAfterMs Upstream retry hint in milliseconds
rate Pre-parsed rate fields (limit, remaining, resetMs, tokenLimit, …)
headers Raw upstream response headers to parse (x-ratelimit-*, etc.)

If both rate and headers are sent, rate wins.

Success 200: { "released": true } when the slot was held, or { "released": false } if it was already gone (idempotent).

curl -sS -X POST "$LOCK_API/$SLOT_ID/release" \
  -H "X-Nthpool-Key: $POOL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":200}'

Throttle report example:

curl -sS -X POST "$LOCK_API/$SLOT_ID/release" \
  -H "X-Nthpool-Key: $POOL_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":429,"throttled":1,"retryAfterMs":2000}'

Lifecycle

  1. Acquire a slot.
  2. Call the upstream from your process.
  3. Renew on the recommended interval while the call runs.
  4. Release when finished (include outcome fields when you saw throttling).

Acquire shares the pool's concurrency limit, queue, and RPM gate with proxy traffic. See Concurrency for those settings.

See also