Shared quotas (allowances)
Fixed-window shared quotas with durable FIFO waiting. The API calls them allowances.
All paths are relative to the API base URL (https://api.getrunstate.com) and require Authorization: Bearer <api key>. See the API overview for authentication, idempotency and the error envelope.
GET /allowances
Section titled “GET /allowances”GET /v1/spaces/{spaceId}/allowancesRead-only allowance list with current-window balances.
- Key permission:
coordination_read
Path parameters
| Name | Type | Notes |
|---|---|---|
spaceId |
string (uuid) | required |
Responses
| Status | Meaning |
|---|---|
200 |
Allowances |
curl -X GET "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/allowances" \ -H "Authorization: Bearer $RUNSTATE_API_KEY"POST /allowances
Section titled “POST /allowances”POST /v1/spaces/{spaceId}/allowancesConfigure a shared quota (fixed-window consumable units)
- Key permission:
resource_config - Idempotency-Key header: required
Path parameters
| Name | Type | Notes |
|---|---|---|
spaceId |
string (uuid) | required |
Request body (JSON)
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | |
unitsLimit |
integer | yes | min 1 |
windowSeconds |
integer | yes | min 1 |
Responses
| Status | Meaning |
|---|---|
201 |
Created allowance |
409 |
Error envelope {"error":{"code","message","requestId"}} |
curl -X POST "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/allowances" \ -H "Authorization: Bearer $RUNSTATE_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"name":"<name>","unitsLimit":1,"windowSeconds":1}'GET /allowances/{id}
Section titled “GET /allowances/{id}”GET /v1/spaces/{spaceId}/allowances/{id}Current-window balance and cooldown for one allowance.
- Key permission:
coordination_read
Path parameters
| Name | Type | Notes |
|---|---|---|
spaceId |
string (uuid) | required |
id |
string (uuid) | required |
Responses
| Status | Meaning |
|---|---|
200 |
Allowance status |
404 |
Error envelope {"error":{"code","message","requestId"}} |
curl -X GET "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/allowances/<id>" \ -H "Authorization: Bearer $RUNSTATE_API_KEY"POST /allowances/{id}/consume
Section titled “POST /allowances/{id}/consume”POST /v1/spaces/{spaceId}/allowances/{id}/consumeConsume window units now (work_key dedup applies)
- Key permission:
coordination_write(keys withresource_configare also accepted) - Idempotency-Key header: required
Path parameters
| Name | Type | Notes |
|---|---|---|
spaceId |
string (uuid) | required |
id |
string (uuid) | required |
Request body (JSON)
| Field | Type | Required | Notes |
|---|---|---|---|
scopeId |
string (uuid) | yes | |
units |
integer | yes | min 1 |
holder |
string | yes | |
workKey |
string | no | length 1–512 |
Responses
| Status | Meaning |
|---|---|
201 |
Consumption recorded |
429 |
Error envelope {"error":{"code","message","requestId"}} |
curl -X POST "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/allowances/<id>/consume" \ -H "Authorization: Bearer $RUNSTATE_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"scopeId":"<run id>","units":1,"holder":"worker-1"}'POST /allowances/{id}/wait
Section titled “POST /allowances/{id}/wait”POST /v1/spaces/{spaceId}/allowances/{id}/waitEnqueue durably for units; strict FIFO, head-of-line blocking.
- Key permission:
coordination_write(keys withresource_configare also accepted) - Idempotency-Key header: required
- TypeScript SDK:
runstate.limit(name, fn), runstate.take(name) - Python SDK:
@runstate.limit(name), runstate.take(name)
Path parameters
| Name | Type | Notes |
|---|---|---|
spaceId |
string (uuid) | required |
id |
string (uuid) | required |
Request body (JSON)
| Field | Type | Required | Notes |
|---|---|---|---|
scopeId |
string (uuid) | yes | |
units |
integer | yes | min 1 |
holder |
string | yes | |
ttlSeconds |
integer | no | 5–3600 |
Responses
| Status | Meaning |
|---|---|
201 |
waiterId and state (GRANTED on fast path) |
curl -X POST "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/allowances/<id>/wait" \ -H "Authorization: Bearer $RUNSTATE_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"scopeId":"<run id>","units":1,"holder":"worker-1"}'POST /allowances/{id}/cooldown
Section titled “POST /allowances/{id}/cooldown”POST /v1/spaces/{spaceId}/allowances/{id}/cooldownProvider-reported backoff; blocks consumption and grants until expiry.
- Key permission:
coordination_write(keys withresource_configare also accepted)
Path parameters
| Name | Type | Notes |
|---|---|---|
spaceId |
string (uuid) | required |
id |
string (uuid) | required |
Request body (JSON)
| Field | Type | Required | Notes |
|---|---|---|---|
seconds |
integer | yes | 1–3600 |
reason |
string | no | max length 512 |
Responses
| Status | Meaning |
|---|---|
200 |
cooldownUntil |
404 |
Error envelope {"error":{"code","message","requestId"}} |
curl -X POST "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/allowances/<id>/cooldown" \ -H "Authorization: Bearer $RUNSTATE_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"seconds":1}'GET /allowance-waiters/{id}
Section titled “GET /allowance-waiters/{id}”GET /v1/spaces/{spaceId}/allowance-waiters/{id}Durable allowance waiter state.
- Key permission:
coordination_read - TypeScript SDK:
runstate.take(name) (while waiting) - Python SDK:
runstate.take(name) (while waiting)
Path parameters
| Name | Type | Notes |
|---|---|---|
spaceId |
string (uuid) | required |
id |
string (uuid) | required |
Responses
| Status | Meaning |
|---|---|
200 |
Waiter |
404 |
Error envelope {"error":{"code","message","requestId"}} |
curl -X GET "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/allowance-waiters/<id>" \ -H "Authorization: Bearer $RUNSTATE_API_KEY"POST /allowance-waiters/{id}/cancel
Section titled “POST /allowance-waiters/{id}/cancel”POST /v1/spaces/{spaceId}/allowance-waiters/{id}/cancelCancel a WAITING allowance waiter (cleanup, ungated)
- Key permission:
coordination_write - TypeScript SDK:
runstate.take(name) (on timeout) - Python SDK:
runstate.take(name) (on timeout)
Path parameters
| Name | Type | Notes |
|---|---|---|
spaceId |
string (uuid) | required |
id |
string (uuid) | required |
Responses
| Status | Meaning |
|---|---|
200 |
CANCELLED |
409 |
Error envelope {"error":{"code","message","requestId"}} |
curl -X POST "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/allowance-waiters/<id>/cancel" \ -H "Authorization: Bearer $RUNSTATE_API_KEY" \ -H "Idempotency-Key: $(uuidgen)"PUT /allowances/by-name/{name}
Section titled “PUT /allowances/by-name/{name}”PUT /v1/spaces/{spaceId}/allowances/by-name/{name}Look up a quota by name, or create-or-verify it when sizes are given.
Creating a missing quota also needs resource_config. Different sizes than the existing quota are a CONFLICT.
- Key permission:
coordination_read - TypeScript SDK:
runstate.limit(name, fn, { max, per }), runstate.setup() - Python SDK:
@runstate.limit(name, max=..., per=...), runstate.setup()
Path parameters
| Name | Type | Notes |
|---|---|---|
spaceId |
string (uuid) | required |
name |
string | length 1–256 |
Request body (JSON)
| Field | Type | Required | Notes |
|---|---|---|---|
unitsLimit |
integer | no | 1–1000000 |
windowSeconds |
integer | no | 1–86400 |
Responses
| Status | Meaning |
|---|---|
200 |
Existing quota |
201 |
Created |
404 |
Error envelope {"error":{"code","message","requestId"}} |
409 |
Error envelope {"error":{"code","message","requestId"}} |
curl -X PUT "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/allowances/by-name/<name>" \ -H "Authorization: Bearer $RUNSTATE_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{}'