---
title: "Catalog"
description: "Agenda's services, offerings, resources, locations and schedule rules, what each one is, and how to read and change them through the API."
---

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

# Catalog

Agenda's catalog is how the clinic is set up: the services it offers, the offerings that make each one bookable, the resources that provide them, the sites where they are, and the hours each resource works. Availability is computed from it, so a change here changes the times patients can book.

## Who can read and change it

Reading the catalog is open to people and machines holding the `:read` scope of what they list, such as `agenda:services:read`; a list accepts no other scope. Changing it accepts only people: a signed-in member whose roles grant the `:write` scope, such as `agenda:services:write`, typically through a staff tool. API keys and machine tokens can't change the catalog.

| Operation | Request | Callers | Effect |
| --- | --- | --- | --- |
| [`agenda.services.list`](/api/agenda/services/list/) | `GET /v1/agenda/services` | People and machines | Reads |
| [`agenda.services.create`](/api/agenda/services/create/) | `POST /v1/agenda/services` | People only | Writes; not idempotent |
| [`agenda.services.update`](/api/agenda/services/update/) | `PATCH /v1/agenda/services/{serviceId}` | People only | Writes; idempotent |
| [`agenda.offerings.list`](/api/agenda/offerings/list/) | `GET /v1/agenda/offerings` | People and machines | Reads |
| [`agenda.offerings.create`](/api/agenda/offerings/create/) | `POST /v1/agenda/offerings` | People only | Writes; not idempotent |
| [`agenda.offerings.update`](/api/agenda/offerings/update/) | `PATCH /v1/agenda/offerings/{offeringId}` | People only | Writes; idempotent |
| [`agenda.resources.list`](/api/agenda/resources/list/) | `GET /v1/agenda/resources` | People and machines | Reads |
| [`agenda.resources.create`](/api/agenda/resources/create/) | `POST /v1/agenda/resources` | People only | Writes; not idempotent |
| [`agenda.resources.update`](/api/agenda/resources/update/) | `PATCH /v1/agenda/resources/{resourceId}` | People only | Writes; idempotent |
| [`agenda.scheduleRules.list`](/api/agenda/schedule-rules/list/) | `GET /v1/agenda/schedule-rules` | People and machines | Reads |
| [`agenda.scheduleRules.create`](/api/agenda/schedule-rules/create/) | `POST /v1/agenda/schedule-rules` | People only | Writes; not idempotent |
| [`agenda.scheduleRules.update`](/api/agenda/schedule-rules/update/) | `PATCH /v1/agenda/schedule-rules/{scheduleRuleId}` | People only | Writes; not idempotent |
| [`agenda.resourceSchedules.update`](/api/agenda/resource-schedules/update/) | `PATCH /v1/agenda/resource-schedules/{resourceId}` | People only | Writes; idempotent |
| [`agenda.locations.list`](/api/agenda/locations/list/) | `GET /v1/agenda/locations` | People and machines | Reads |

The lists of services, offerings, resources and locations answer a page at a time, 25 items by default and up to 100: see [Pagination](/en/developers/concepts/pagination/). Creating isn't idempotent, so after a create whose outcome is unknown, list before you create again. There's no delete operation: set a record's `status` to `inactive` to stop using it. The booking operations offer only active offerings.

## Services

A service is what a patient asks for, under the clinic's name for it, such as a neurology consultation. [`agenda.services.list`](/api/agenda/services/list/) filters by `status`.

| Field | Meaning |
| --- | --- |
| `name` | Required. |
| `defaultDurationMinutes` | Required, 1 to 1440. The default length of its appointments; each offering can set its own. |
| `status` | `active` (the default) or `inactive`. |

## Offerings

An offering attaches a service to an agenda, with what it takes to book it. [`agenda.offerings.list`](/api/agenda/offerings/list/) filters by `serviceId` and `status`.

| Field | Default | Meaning |
| --- | --- | --- |
| `serviceId`, `name` | Required | The service it offers, and the offering's name. |
| `durationMinutes` | Required | The visible appointment's length, 1 to 1440 minutes. |
| `slotIntervalMinutes` | `15` | Minutes between possible start times, 5 to 240. |
| `bufferBeforeMinutes`, `bufferAfterMinutes` | `0` | Minutes the resources stay occupied before and after the visible appointment. |
| `minNoticeMinutes` | `0` | The least advance notice a booking needs. |
| `bookingHorizonDays` | `90` | How many days ahead it can be booked, up to 730. |
| `resourceRequirements` | `[]` | Up to 30 requirements: the resources each booking needs. |
| `status` | `active` | `active` or `inactive`. |

Each requirement names a `kind` of resource and a `quantity`, 1 by default. `alternatives` lists the resources that can fill it; when it's empty, any resource of that kind can. `required`, true by default, says whether the requirement must be met for the booking to go ahead. In an update, `resourceRequirements` replaces the whole list for future bookings; existing appointments keep their resources.

An example body for [`agenda.offerings.create`](/api/agenda/offerings/create/), a 30-minute consultation with one named professional and any room:

```json
{
  "serviceId": "svc_00000000-0000-4000-8000-000000000001",
  "name": "Neurology consultation, Dr. Example",
  "durationMinutes": 30,
  "slotIntervalMinutes": 30,
  "minNoticeMinutes": 120,
  "resourceRequirements": [
    { "kind": "practitioner", "alternatives": ["srs_00000000-0000-4000-8000-000000000003"] },
    { "kind": "room" }
  ]
}
```

## Resources

A resource is anything with its own calendar that a booking uses. [`agenda.resources.list`](/api/agenda/resources/list/) filters by `kind` and `status`.

| Field | Meaning |
| --- | --- |
| `kind` | Required: `practitioner`, `assistant`, `room`, `chair`, `equipment`, `location`, `team` or `generic`. Offerings' requirements match it. |
| `name` | Required. |
| `timezone` | Required. The IANA time zone of its working hours, appointment dates and age rules. |
| `locationId` | The site it belongs to. `null`, the default, ties it to no site, so it covers every site. |
| `capacity` | How many units it can take at once, 1 by default; each booking uses its share. |
| `status` | `active` (the default) or `inactive`. |

## Locations

Locations are the clinic's sites, each with its `name`, `address`, `city`, `timezone` and `status`. The API reads them, with [`agenda.locations.list`](/api/agenda/locations/list/) (`agenda:locations:read`), or with [`agenda.bookingLocations.list`](/api/agenda/booking-locations/list/) when you book (`agenda:bookings:read`). It has no operation that creates or changes a location.

## Schedule rules

Schedule rules say when a resource works. Each rule is a band of local time, from `from` to `to` in `HH:MM`, read in the resource's time zone:

| Field | Meaning |
| --- | --- |
| `kind` | `weekly`, `date_override`, `time_off` or `block`. |
| `weekdays` | For a weekly band, the days it applies, from Sunday `0` to Saturday `6`. |
| `effectiveFrom`, `effectiveTo` | For a weekly band, the first and last dates it applies; `null` for no bound. |
| `date` | For a dated band or closure, its local date; `null` for a weekly band. |

- [`agenda.scheduleRules.list`](/api/agenda/schedule-rules/list/) filters by `resourceId` and answers the complete list, without pages. Each rule has its `id`, its `resourceId` and its `version`.
- [`agenda.scheduleRules.create`](/api/agenda/schedule-rules/create/) adds one rule with `resourceId`, `kind`, `timezone`, `from` and `to`.
- [`agenda.scheduleRules.update`](/api/agenda/schedule-rules/update/) changes a rule identified by `scheduleRuleId`; send its `expectedVersion`. An update without it is refused with `SCHEDULE_RULE_EXPECTED_VERSION_REQUIRED`.
- [`agenda.resourceSchedules.update`](/api/agenda/resource-schedules/update/) replaces the resource's whole schedule at once: `rules` is the new set, up to 400 rules, and `expectedRules` holds the `id` and `version` of every rule you loaded. If the schedule changed since, the replacement is refused and nothing changes.

An example body for `agenda.resourceSchedules.update`: Monday to Friday, 07:00 to 12:00 and 14:00 to 17:00, replacing one rule loaded at version 3:

```json
{
  "expectedRules": [{ "id": "sch_00000000-0000-4000-8000-000000000006", "version": 3 }],
  "rules": [
    { "kind": "weekly", "weekdays": [1, 2, 3, 4, 5], "from": "07:00", "to": "12:00" },
    { "kind": "weekly", "weekdays": [1, 2, 3, 4, 5], "from": "14:00", "to": "17:00" }
  ]
}
```

## Versions

Every catalog record has a `version`. An update requires the version you read as `expectedVersion`, and is refused with `409` when someone changed the record since:

| Record | `error.code` |
| --- | --- |
| Service | `SERVICE_VERSION_MISMATCH` |
| Offering | `OFFERING_VERSION_MISMATCH` |
| Resource | `RESOURCE_VERSION_MISMATCH` |
| Schedule rule | `SCHEDULE_RULE_VERSION_MISMATCH` |
| A resource's schedule, on replace | `SCHEDULE_RULES_CHANGED` |

Read the record again, apply your change to what you read, and send it with the new version. See [Handle errors and retries](/en/developers/guides/handle-errors-and-retries/#a-version-conflict-on-an-update).

Source: https://docs.getarbol.com/en/developers/agenda/catalog/index.mdx
