# Booking

> Find open time slots and book a visit into one.

- Canonical URL: https://couplrfsm.com/developers/api-reference/booking
- Markdown: https://couplrfsm.com/developers/api-reference/booking.md
- API version: v1 (base URL https://couplrfsm.com/api/v1)
- OpenAPI specification: https://couplrfsm.com/developers/openapi.yaml
- All documentation pages: https://couplrfsm.com/llms.txt

> **Flip:** Ask me when a visit can happen and I give you the same openings the company's online booking page shows. Pick one and I put the visit on the schedule. If the company books that kind of job automatically it is confirmed straight away. Otherwise it goes on as tentative and I ask the office to confirm it.

Base URL: `https://couplrfsm.com/api/v1`. Conventions shared by every endpoint (examples, ids, dates, money) are on the [API reference](https://couplrfsm.com/developers/api-reference) page. Errors, pagination and idempotency are in [Errors, limits and retries](https://couplrfsm.com/developers/errors-and-limits).

## List open time slots

`GET /api/v1/availability` · requires the `availability:read` ability

The times a visit can be booked for a location, soonest day first.
These are the slots the company's own online booking page would
offer: inside business hours, with a technician free, starting
tomorrow. Times are in the company's timezone, given in `timezone`.

A `job_type_id` that is not one of the company's is a `404`. One the
company has not opened for booking is a `422`.

When the location is outside the company's service area,
`in_service_area` is `false` and `data` is empty. Not paginated: use
`limit`.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `zip` | string | Yes | ZIP code of the place to be visited. |
| `address` | string | No | Street address. Lets Couplr favor times when a technician is already nearby. |
| `job_type_id` | integer | No | A job type the company has opened for booking. Slots are sized to its usual length. |
| `limit` | integer | No | How many slots to return. Default 24, at most 100. Default: `24`. |

**Example request**

```bash
curl "https://couplrfsm.com/api/v1/availability" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

**Example response** (`200`)

```json
{
    "data": [
        {
            "date": "2026-11-16",
            "time": "09:00",
            "arrival_window_end": "11:00"
        },
        {
            "date": "2026-11-16",
            "time": "13:00",
            "arrival_window_end": "15:00"
        }
    ],
    "in_service_area": true,
    "timezone": "America/Chicago"
}
```

**Errors**

| Status | When |
| --- | --- |
| `401` | No key (`api_key_missing`), the key is unknown or revoked (`api_key_invalid`), the key had an end date that has passed (`api_key_expired`), or the key belongs to a private trial sandbox whose access window has ended (`sandbox_expired`). |
| `403` | The key lacks the ability this endpoint needs (`ability_missing`), no admin user is left to act for the key (`no_acting_admin`), or the company's account is not yet verified. |
| `404` | No such record in this company (`not_found`). |
| `422` | A parameter or field is invalid (`validation_failed`). `errors` maps each field to its messages. |
| `429` | Too many requests (`rate_limited`). Wait `Retry-After` seconds. |

## Book a visit

`POST /api/v1/bookings` · requires the `bookings:write` ability · accepts `Idempotency-Key`

Books a visit for an existing customer into an open slot. Send a
`date` and `time` from `GET /availability`. The visit is created as a
job at the customer's address and given to a technician.

What happens next depends on the job type, as it does when a
customer books online:

- A job type the company books automatically is `confirmed`. The
  `booking.created` event is delivered, and the customer receives
  the company's booking confirmation text if the company has that
  text switched on.
- Any other job type, or none, is `needs_review`: it is on the
  schedule as tentative and the office is alerted to confirm the
  time. The customer receives the company's appointment text if the
  company has that text switched on, as for any new job.

Refused with `422` when the slot is no longer open (on `time`), the
customer has no address with a street and ZIP code or the address is
outside the service area (on `address_id`), or the job type is not
open for booking (on `job_type_id`).

**Headers**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | No | Makes a retry of this request safe. See "Retries and idempotency". |

**Request body** (`application/json`)

Fields not listed are ignored.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `customer_id` | integer | Yes |  |
| `address_id` | integer or null | No | One of the customer's addresses. Omit to use their main address. |
| `job_type_id` | integer or null | No | A job type the company has opened for booking. Omit for a general visit. |
| `date` | string (date) | Yes | A date from `GET /availability`. |
| `time` | string | Yes | A time from `GET /availability`, 24-hour `HH:MM`. |
| `notes` | string or null | No |  |

**Example request**

```bash
curl -X POST "https://couplrfsm.com/api/v1/bookings" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -d '{"customer_id":1842,"job_type_id":31,"date":"2026-11-16","time":"09:00","notes":"No cooling upstairs."}'
```

**Example response** (`201`)

```json
{
    "data": {
        "job_id": 9312,
        "customer_id": 1842,
        "address_id": 2210,
        "job_type": "Repair",
        "status": "confirmed",
        "date": "2026-11-16",
        "time": "09:00",
        "arrival_window_end": "11:00",
        "timezone": "America/Chicago",
        "created_at": "2026-11-14T15:02:11+00:00"
    }
}
```

**Errors**

| Status | When |
| --- | --- |
| `401` | No key (`api_key_missing`), the key is unknown or revoked (`api_key_invalid`), the key had an end date that has passed (`api_key_expired`), or the key belongs to a private trial sandbox whose access window has ended (`sandbox_expired`). |
| `403` | The key lacks the ability this endpoint needs (`ability_missing`), no admin user is left to act for the key (`no_acting_admin`), or the company's account is not yet verified. |
| `422` | A parameter or field is invalid (`validation_failed`). `errors` maps each field to its messages. |
| `429` | Too many requests (`rate_limited`). Wait `Retry-After` seconds. |
