# Once

> Work done once per run, with the result stored for every caller.

Source: https://docs.getrunstate.com/api/once/

<!-- Generated by apps/docs/scripts/gen-api.mjs from packages/contracts/openapi.yaml. Do not edit. -->

All paths are relative to the API base URL (`https://api.getrunstate.com`) and require `Authorization: Bearer <api key>`. See [the API overview](https://docs.getrunstate.com/api/) for authentication, idempotency and the error envelope.

## POST /once/acquire

```http
POST /v1/spaces/{spaceId}/once/acquire
```

Start once-per-run work, or learn its state.

ACQUIRED (201) hands this caller a fenced lease and token. DONE returns the stored result, FAILED the last error (lastError), HELD means another holder is working on it (wait with GET /once).

- **Key permission:** `coordination_write`
- **TypeScript SDK:** `runstate.once(fn)`
- **Python SDK:** `@runstate.once`

**Path parameters**

| Name | Type | Notes |
| --- | --- | --- |
| `spaceId` | string (uuid) | required |

**Request body** (JSON)

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `scopeId` | string (uuid) | yes |  |
| `key` | string | yes | length 1–512 |
| `holder` | string | yes |  |
| `holderSession` | string | yes |  |
| `leaseSeconds` | integer | no | 5–600; default `30` |

**Responses**

| Status | Meaning |
| --- | --- |
| `200` | HELD, DONE or FAILED |
| `201` | ACQUIRED: {state, token, generation, leaseExpiresAt, attempts} |
| `409` | Error envelope `{"error":{"code","message","requestId"}}` |

```bash
curl -X POST "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/once/acquire" \
  -H "Authorization: Bearer $RUNSTATE_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"scopeId":"<run id>","key":"<key>","holder":"worker-1","holderSession":"<session id>"}'
```

## POST /once/renew

```http
POST /v1/spaces/{spaceId}/once/renew
```

Extend the lease on once work.

- **Key permission:** `coordination_write`
- **TypeScript SDK:** `runstate.once(fn) (automatic while fn runs)`
- **Python SDK:** `@runstate.once (automatic while fn runs)`

**Path parameters**

| Name | Type | Notes |
| --- | --- | --- |
| `spaceId` | string (uuid) | required |

**Request body** (JSON)

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `scopeId` | string (uuid) | yes |  |
| `key` | string | yes |  |
| `token` | string | yes |  |
| `leaseSeconds` | integer | no | 5–600; default `30` |

**Responses**

| Status | Meaning |
| --- | --- |
| `200` | New lease expiry |
| `409` | Error envelope `{"error":{"code","message","requestId"}}` |

```bash
curl -X POST "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/once/renew" \
  -H "Authorization: Bearer $RUNSTATE_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"scopeId":"<run id>","key":"<key>","token":"<lease token>"}'
```

## POST /once/complete

```http
POST /v1/spaces/{spaceId}/once/complete
```

Store the result of once work (64 KiB max); allowed after the run stops.

- **Key permission:** `coordination_write`
- **TypeScript SDK:** `runstate.once(fn)`
- **Python SDK:** `@runstate.once`

**Path parameters**

| Name | Type | Notes |
| --- | --- | --- |
| `spaceId` | string (uuid) | required |

**Request body** (JSON)

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `scopeId` | string (uuid) | yes |  |
| `key` | string | yes |  |
| `token` | string | yes |  |
| `result` | any JSON | no | JSON result |

**Responses**

| Status | Meaning |
| --- | --- |
| `200` | DONE |
| `409` | Error envelope `{"error":{"code","message","requestId"}}` |

```bash
curl -X POST "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/once/complete" \
  -H "Authorization: Bearer $RUNSTATE_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"scopeId":"<run id>","key":"<key>","token":"<lease token>"}'
```

## POST /once/fail

```http
POST /v1/spaces/{spaceId}/once/fail
```

Record a failed attempt; FAILED for everyone once maxAttempts is reached.

- **Key permission:** `coordination_write`
- **TypeScript SDK:** `runstate.once(fn) (when fn throws)`
- **Python SDK:** `@runstate.once (when fn raises)`

**Path parameters**

| Name | Type | Notes |
| --- | --- | --- |
| `spaceId` | string (uuid) | required |

**Request body** (JSON)

| Field | Type | Required | Notes |
| --- | --- | --- | --- |
| `scopeId` | string (uuid) | yes |  |
| `key` | string | yes |  |
| `token` | string | yes |  |
| `error` | string | yes | max length 2000 |
| `maxAttempts` | integer | no | 1–100; default `3` |

**Responses**

| Status | Meaning |
| --- | --- |
| `200` | OPEN or FAILED, with attempts |
| `409` | Error envelope `{"error":{"code","message","requestId"}}` |

```bash
curl -X POST "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/once/fail" \
  -H "Authorization: Bearer $RUNSTATE_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"scopeId":"<run id>","key":"<key>","token":"<lease token>","error":"<error>"}'
```

## GET /once

```http
GET /v1/spaces/{spaceId}/once
```

Read once work; with waitMs, hold until it is no longer HELD.

- **Key permission:** `coordination_read`
- **TypeScript SDK:** `runstate.once(fn) (waiting for another worker)`
- **Python SDK:** `@runstate.once (waiting for another worker)`

**Path parameters**

| Name | Type | Notes |
| --- | --- | --- |
| `spaceId` | string (uuid) | required |

**Query parameters**

| Name | Type | Notes |
| --- | --- | --- |
| `scopeId` | string (uuid) |  |
| `key` | string |  |
| `waitMs` | integer | 0–120000 |

**Responses**

| Status | Meaning |
| --- | --- |
| `200` | {key, scopeId, state: OPEN\|HELD\|DONE\|FAILED, generation, holder, leaseExpiresAt, attempts, result, lastError} |
| `404` | Error envelope `{"error":{"code","message","requestId"}}` |

```bash
curl -X GET "$RUNSTATE_BASE_URL/v1/spaces/$RUNSTATE_SPACE_ID/once" \
  -H "Authorization: Bearer $RUNSTATE_API_KEY"
```
