# Services

Reusable elements that can be included in the booking forms of units

Endpoint: `https://api.bookingmood.com/v1/services`, supporting `GET`, `POST`, `DELETE`, `PATCH`. Authenticate with the header `Authorization: Bearer YOUR_API_KEY` (see [authentication](https://www.bookingmood.com/nl-NL/api-reference/authentication.md)).

## The service object

| Attribute | Type | Description |
| --- | --- | --- |
| `id` | `uuid` | Unique identifier. |
| `organization_id` | `uuid` | Identifier of the organization this service is defined in. References [`organizations.id`](https://www.bookingmood.com/nl-NL/api-reference/organizations.md). |
| `parent_id` | `uuid \| null` | Identifier of the parent service. Only applies to services of type `multiple-choice-option`. References [`services.id`](https://www.bookingmood.com/nl-NL/api-reference/services.md). |
| `created_at` | `timestamp with time zone` | Creation timestamp. Read-only: set by the database. |
| `updated_at` | `timestamp with time zone` | Last modification timestamp. Read-only: set by the database. |
| `attachment` | `text \| null` | URL of a file attached to the service. |
| `deposit` | `ServiceDeposit \| null` | Deposit charged for the service. |
| `description` | `MultiLanguageString` | Localized description of the service. |
| `fee` | `ServiceFee \| null` | Fee charged for the service. |
| `fts` | `tsvector` | Full text search index. Read-only: set by the database. |
| `label` | `MultiLanguageString` | Localized label of the service. |
| `meta` | `Partial<ServiceMeta>` | Metadata of the service. |
| `order` | `smallint` | Order of the option within its parent service. Only applies to services of type `multiple-choice-option`. |
| `type` | `ServiceType` | Type of the service. |

```json
{
  "id": "00000000-0000-0000-0000-000000000000",
  "organization_id": "00000000-0000-0000-0000-000000000000",
  "parent_id": "00000000-0000-0000-0000-000000000000",
  "created_at": "2026-10-30T23:37:06.265Z",
  "updated_at": "2026-10-30T23:37:06.265Z",
  "attachment": "",
  "deposit": {
    "installments": [
      {
        "id": "00000000-0000-0000-0000-000000000000",
        "moment": {
          "offset": 0,
          "type": "check-in"
        },
        "value": {
          "restrictions": [],
          "type": "absolute",
          "value": [
            {
              "occupancy_group_id": null,
              "period": null,
              "value": 100
            }
          ]
        }
      }
    ],
    "price": {
      "restrictions": [],
      "type": "absolute",
      "value": [
        {
          "occupancy_group_id": null,
          "period": null,
          "value": 200
        }
      ]
    },
    "refund_moment": {
      "offset": 0,
      "type": "checkout"
    },
    "refund_policy": {
      "default": "Full refund",
      "nl-NL": "Volledige terugbetaling"
    },
    "tax_ids": []
  },
  "description": {},
  "fee": {
    "discounts": [
      {
        "definition": {
          "restrictions": [],
          "type": "relative",
          "value": -10
        },
        "id": "00000000-0000-0000-0000-000000000000",
        "name": {
          "default": "Seasonal Discount"
        }
      }
    ],
    "installments": [
      {
        "id": "00000000-0000-0000-0000-000000000000",
        "moment": {
          "offset": 0,
          "type": "check-in"
        },
        "value": {
          "restrictions": [],
          "type": "absolute",
          "value": [
            {
              "occupancy_group_id": null,
              "period": null,
              "value": 50
            }
          ]
        }
      }
    ],
    "price": {
      "restrictions": [],
      "type": "absolute",
      "value": [
        {
          "occupancy_group_id": null,
          "period": null,
          "value": 150
        }
      ]
    },
    "tax_ids": []
  },
  "label": {},
  "meta": {},
  "order": 0,
  "type": "address"
}
```

### Types

```typescript
type AbsolutePriceExpression = {
  occupancy_group_id: string | null;
  period: PeriodExpression | null;
  value: number;
};

type AddressSegment = 
  | "street"
  | "street2"
  | "zip"
  | "city"
  | "province"
  | "state"
  | "country";

type BookingDateVariable = "arrival_date" | "departure_date";

type BooleanExpression = 
  | {
      max: number | null;
      min: number | null;
      operator: "duration";
      variable: DurationVariable;
    }
  | {
      operator: "implies";
      then: BooleanExpression;
      when: BooleanExpression;
    }
  | {
      max: number | null;
      min: number | null;
      occupancy_group_id: string;
      operator: "occupancy";
    }
  | {
      operator: "season";
      season_id: string;
      variable: BookingDateVariable;
    }
  | {
      operator: "weekday";
      variable: BookingDateVariable;
      weekdays: Array<Day>;
    };

type Day = 6 | 0 | 1 | 2 | 3 | 4 | 5;

type DurationVariable = "advance_days" | "booking_days";

type Installment = {
  id: string;
  moment: MomentExpression;
  value: PriceExpression;
};

type Language = 
  | "be-BY"
  | "bg-BG"
  | "cs-CZ"
  | "da-DK"
  | "de-DE"
  | "en-GB"
  | "en-NZ"
  | "en-US"
  | "es-ES"
  | "eu-ES"
  | "fr-FR"
  | "gr-EL"
  | "he-IL"
  | "hr-HR"
  | "hu-HU"
  | "it-IT"
  | "ja-JP"
  | "lt-LT"
  | "lv-LV"
  | "nl-NL"
  | "nn-NO"
  | "pl-PL"
  | "pt-BR"
  | "ro-RO"
  | "ru-RU"
  | "sk-SK"
  | "sl-SI"
  | "sr-Cyrl"
  | "sr-Latn"
  | "sv-SE"
  | "tr-TR"
  | "uk-UA";

type MomentExpression = {
  offset: number;
  type: "check-in" | "checkout" | "confirmation" | "on-site" | "request";
};

type MultiLanguageString = {
  default: string;
} & Partial<Record<Language, string>>;

type PeriodExpression = {
  duration: number;
  start: Array<Day> | null;
};

type PriceExpression = 
  | {
      restrictions: Array<BooleanExpression>;
      type: "on-request";
    }
  | {
      restrictions: Array<BooleanExpression>;
      type: "absolute";
      value: Array<AbsolutePriceExpression>;
    }
  | {
      restrictions: Array<BooleanExpression>;
      type: "relative";
      value: number;
    }
  | {
      groups: Array<{
        max: number | null;
        min: number | null;
        value: PriceExpression;
      }>;
      occupancy_group_id: string;
      restrictions: Array<BooleanExpression>;
      type: "occupancy-discrimination";
    }
  | {
      groups: Array<{
        season_id: string;
        value: PriceExpression;
      }>;
      restrictions: Array<BooleanExpression>;
      type: "season-discrimination";
      variable: "arrival_date";
    }
  | {
      groups: Array<{
        max: number | null;
        min: number | null;
        value: PriceExpression;
      }>;
      restrictions: Array<BooleanExpression>;
      type: "duration-discrimination";
      variable: DurationVariable;
    };

type ServiceDeposit = {
  installments: Array<Installment>;
  name?: MultiLanguageString;
  price: PriceExpression;
  refund_moment: MomentExpression | null;
  refund_policy: MultiLanguageString;
  tax_ids: Array<string>;
};

type ServiceDiscount = {
  definition: PriceExpression;
  id: string;
  name: MultiLanguageString;
};

type ServiceFee = {
  discounts: Array<ServiceDiscount>;
  installments: Array<Installment>;
  name?: MultiLanguageString;
  price: PriceExpression;
  tax_ids: Array<string>;
};

type ServiceMeta = {
  address_display: "single" | "split";
  address_segments: Record<AddressSegment, "hidden" | "optional" | "required">;
  max: number | null;
  min: number | null;
  multiple_choice_display: "dropdown" | "list";
  name_display: "single" | "split";
  phone_number_display: "international" | "international-sms-compliant" | "unrestricted";
  required: boolean;
};

type ServiceType = 
  | "address"
  | "checkbox"
  | "contact-meta"
  | "email"
  | "long-text"
  | "multiple-choice"
  | "multiple-choice-option"
  | "name"
  | "number"
  | "phone"
  | "short-text"
  | "statement"
  | "time";
```

### Related resources

Embed related resources with the [`select` parameter](https://www.bookingmood.com/nl-NL/api-reference/selecting.md), e.g. `select=*,organizations(*)`. Resources related through more than one column need the column as a hint, like `table!column(*)`.

| Embed | Type | Description |
| --- | --- | --- |
| `organizations(*)` | `object \| null` | The [organization](https://www.bookingmood.com/nl-NL/api-reference/organizations.md) referenced by `organization_id`. |
| `booking_details!option_id(*)` | `array` | The [booking details](https://www.bookingmood.com/nl-NL/api-reference/booking_details.md) referencing this service through `booking_details.option_id`. |
| `booking_details!service_id(*)` | `array` | The [booking details](https://www.bookingmood.com/nl-NL/api-reference/booking_details.md) referencing this service through `booking_details.service_id`. |
| `coupon_services(*)` | `array` | The [coupon services](https://www.bookingmood.com/nl-NL/api-reference/coupon_services.md) referencing this service through `coupon_services.service_id`. |
| `line_items(*)` | `array` | The [line items](https://www.bookingmood.com/nl-NL/api-reference/line_items.md) referencing this service through `line_items.service_id`. |
| `product_services(*)` | `array` | The [product services](https://www.bookingmood.com/nl-NL/api-reference/product_services.md) referencing this service through `product_services.service_id`. |

## List services

`GET /v1/services`

Filter on any attribute with `column=operator.value` (see [filtering](https://www.bookingmood.com/nl-NL/api-reference/filtering.md)). Choose columns and embed related resources with `select` (see [selecting](https://www.bookingmood.com/nl-NL/api-reference/selecting.md)), and sort and paginate with `order`, `limit` and `offset` (see [sorting and pagination](https://www.bookingmood.com/nl-NL/api-reference/sorting-pagination.md)).

```bash
curl -X GET "https://api.bookingmood.com/v1/services?select=*&limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Responses:

- `200`: A list of services with the selected columns. `206` when the list is a partial range.
- `400`: The request is malformed, for example an unknown column or an invalid filter.
- `401`: The API key is missing or invalid.

## Create services

`POST /v1/services`

Insert one service or an array of services. The body is a `Partial` service object, so columns with a default can be left out. Read-only attributes can't be written.

```bash
curl -X POST "https://api.bookingmood.com/v1/services" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "organization_id": "00000000-0000-0000-0000-000000000000",
    "description": {},
    "label": {},
    "meta": {},
    "order": 0,
    "type": "address"
  }'
```

Responses:

- `201`: The newly created services with the selected columns.
- `400`: The request is malformed, for example an unknown column, an invalid filter or an invalid body.
- `401`: The API key is missing or invalid.
- `403`: The API key has no permission for this change, for example when writing rows of another organization.
- `409`: The change conflicts with existing data, for example a duplicate key or a reference to a row that does not exist.

## Delete services

`DELETE /v1/services`

Filter on any attribute with `column=operator.value` (see [filtering](https://www.bookingmood.com/nl-NL/api-reference/filtering.md)). Without filters, this request deletes all services you have access to. Always filter on at least one column, e.g. `id=eq.{id}`.

```bash
curl -X DELETE "https://api.bookingmood.com/v1/services?id=eq.00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Responses:

- `200`: The deleted services with the selected columns. `204` without a body when sending the header `Prefer: return=minimal`.
- `400`: The request is malformed, for example an unknown column or an invalid filter.
- `401`: The API key is missing or invalid.
- `403`: The API key has no permission for this change, for example when writing rows of another organization.

## Update services

`PATCH /v1/services`

Filter on any attribute with `column=operator.value` (see [filtering](https://www.bookingmood.com/nl-NL/api-reference/filtering.md)). Without filters, this request updates all services you have access to. Always filter on at least one column, e.g. `id=eq.{id}`. The body holds the attributes to change.

```bash
curl -X PATCH "https://api.bookingmood.com/v1/services?id=eq.00000000-0000-0000-0000-000000000000" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "description": {}
  }'
```

Responses:

- `200`: The updated services with the selected columns. `204` without a body when sending the header `Prefer: return=minimal`.
- `400`: The request is malformed, for example an unknown column, an invalid filter or an invalid body.
- `401`: The API key is missing or invalid.
- `403`: The API key has no permission for this change, for example when writing rows of another organization.
- `409`: The change conflicts with existing data, for example a duplicate key or a reference to a row that does not exist.

Errors have a JSON body with a `code`, `message`, `details` and `hint`. See the [introduction](https://www.bookingmood.com/nl-NL/api-reference.md) for more on errors. The full API is described by the OpenAPI spec at https://api.bookingmood.com/v1/openapi.json.
