> ## Documentation Index
> Fetch the complete documentation index at: https://docs.autodetailcrm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hold or release a slot

> Convert a short-lived availability offer into the hold required for booking submission.

An availability result is an offer, not a reservation. Use the hold endpoint when the customer selects a time. The returned `hold_token` is required by the submit endpoint.

```
POST /api/public/booking/{token}/hold
```

<Warning>
  Creating a hold is rate limited to **20 requests per 10 minutes per IP address** and **500 requests per 10 minutes per tenant**. A limited response includes `Retry-After`.
</Warning>

## Path parameters

<ParamField path="token" type="string" required>
  Your booking page token (booking slug or lead token).
</ParamField>

## Create a hold

Send the `offer_token` from the selected [availability slot](/api-reference/endpoint/get-availability). If you are replacing a hold already stored by your client, also send its `hold_token` as `previous_hold_token`.

<ParamField body="offer_token" type="string" required>
  The short-lived token attached to the selected availability slot.
</ParamField>

<ParamField body="previous_hold_token" type="string">
  The current hold token when the customer changes their selection. The old hold is released before the new one is created.
</ParamField>

The API permits one active public hold per workspace and visitor IP. Creating another hold automatically releases that visitor's previous active hold, even when `previous_hold_token` is omitted.

### Create response

<ResponseField name="hold_token" type="string">The token to send to `POST /submit`.</ResponseField>
<ResponseField name="start" type="string">Held appointment start time as an ISO 8601 timestamp.</ResponseField>
<ResponseField name="end" type="string">Held appointment end time as an ISO 8601 timestamp.</ResponseField>
<ResponseField name="expires_at" type="string">When the hold expires as an ISO 8601 timestamp.</ResponseField>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://app.autodetailcrm.com/api/public/booking/your-token/hold \
    -H "Origin: https://example-detailer.com" \
    -H "Content-Type: application/json" \
    -d '{"offer_token":"8fa4bc82-0a86-48e1-88fd-2517042f6072"}'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    `https://app.autodetailcrm.com/api/public/booking/${token}/hold`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        offer_token: selectedSlot.offer_token,
        previous_hold_token: currentHoldToken || undefined,
      }),
    }
  );

  const hold = await response.json();
  ```

  ```json Response theme={null}
  {
    "hold_token": "ef36ccee-09ca-4aa2-8292-5e7ee5971dcb",
    "start": "2026-10-01T20:00:00.000Z",
    "end": "2026-10-01T23:00:00.000Z",
    "expires_at": "2026-10-01T19:55:00.000Z"
  }
  ```
</CodeGroup>

Submit before `expires_at`. Do not derive a fixed countdown in your integration because the business controls the hold duration.

## Release a hold

Release the hold when the customer clears their selected time or leaves a flow where you can run cleanup. Expired holds release capacity automatically, so client cleanup is helpful but not required for correctness.

```json Request theme={null}
{
  "action": "release",
  "previous_hold_token": "ef36ccee-09ca-4aa2-8292-5e7ee5971dcb"
}
```

A release request returns:

```json Response theme={null}
{ "released": true }
```

The response is idempotent: it also returns `released: true` when the hold was already released, expired, or omitted.

## Errors

| Status | `error` | Meaning |
| - | - | - |
| `400` | `offer_required` | A create request did not contain a valid `offer_token` |
| `403` | `origin_not_allowed` | The external origin is not active for this tenant or Native Website Booking is unavailable |
| `404` | `not_found` | No booking page was found, or booking is disabled |
| `409` | `offer_expired` | The offer expired, was already consumed, or its scheduling policy changed |
| `409` | `slot_unavailable` | The offered resources are no longer available |
| `429` | `too_many_requests` | An IP or tenant hold quota was exceeded |
| `503` | `origin_registry_unavailable` | The domain or plan registry could not be resolved safely |
| `500` | `internal_error` | Unexpected server error |

<CardGroup cols={2}>
  <Card title="Get availability" icon="calendar" href="/api-reference/endpoint/get-availability">
    Back: get short-lived slot offers.
  </Card>

  <Card title="Submit booking" icon="paper-plane" href="/api-reference/endpoint/submit-booking">
    Next: commit the held booking.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.