# Update business schedule by ID

> PUT /v1/schedules/{id} · Core resources API

Source: https://developer.kustomer.com/api/core-resources/update-business-schedule-by-id

```http
PUT https://{orgname}.api.kustomerapp.com/v1/schedules/{id}
Authorization: Bearer <API key>
```

## Authorization

Requires any one of these roles: `org.permission.schedule.update`; or legacy `org.admin.schedule.write`. The API key or user must hold at least one of them. Legacy roles predate permission sets and still work.

Modifies a business schedule based on the schedule ID.

## About Business schedules

A business schedule defines your support team's working hours for each day of the week in one time zone, plus holidays when you're closed. Kustomer uses it to measure time in business hours, for SLAs, reporting, chat availability and workflow conditions.

Admins set business schedules in the Kustomer admin settings, usually one per support region or team. Exactly one schedule is the org's default (`default: true`). Making another schedule the default, on create or update, clears the flag on the old one. Deleting a schedule is a soft delete: the response returns it with `deleted: true` and `deletedAt`, and it stops being the default.

#### Hours and time zone

`hours` has one key per weekday, `"0"` for Sunday through `"6"` for Saturday. Each day is either `null` (closed all day) or a list of `[start, end]` intervals in minutes after local midnight, from `0` to `1440`. Intervals must be sorted, can't overlap or touch, and must end after they start. A Monday to Friday, 8:00 to 17:00 schedule looks like this:

```json
{ "0": null, "1": [[480, 1020]], "2": [[480, 1020]], "3": [[480, 1020]], "4": [[480, 1020]], "5": [[480, 1020]], "6": null }
```

Split shifts are just more intervals, such as `[[480, 720], [780, 1020]]`. `timezone` is an IANA time zone name such as `America/New_York`, so the hours follow local time, including daylight saving changes.

#### Holidays

Holidays are separate objects attached to a schedule. Each has a `name`, `description`, `startDate`, `endDate` and `enabled`. Enabled holidays count as closed time even when they fall inside the weekly hours, and disabled ones are ignored. Holidays aren't returned by default: add `include=holidays` to the read endpoints and they appear in the top-level `included` array, each linked back to its schedule.

#### How schedules affect SLAs, routing and automation

- **SLAs.** An SLA can count time in `calendar` hours (around the clock) or `business` hours. With business hours, deadlines only advance while the schedule is open and pause outside hours and on holidays. An SLA can name a specific schedule; otherwise the default schedule applies.
- **Business-time metrics.** The default schedule is what Kustomer uses for business-time measurements, such as the `businessTime` totals on routing work sessions and conversation timing.
- **Chat.** Chat availability follows a business schedule, the org default unless chat settings choose another.
- **Workflows.** The `inBusinessHours` function and the `in_business_hours` criteria operator check a date against a schedule ID.

#### Which endpoint to use

| To | Call |
| --- | --- |
| List schedules | [`GET /v1/schedules`](https://developer.kustomer.com/api/core-resources/get-all-business-schedules), with `enabled=true` for enabled schedules only |
| Get the default schedule, for example to check opening hours | [`GET /v1/schedules/default`](https://developer.kustomer.com/api/core-resources/get-default-business-schedule), with `include=holidays` |
| Create a schedule | [`POST /v1/schedules`](https://developer.kustomer.com/api/core-resources/create-a-business-schedule) |
| Read, replace or delete a schedule | [`GET`](https://developer.kustomer.com/api/core-resources/get-business-schedule-by-id), [`PUT`](https://developer.kustomer.com/api/core-resources/update-business-schedule-by-id) or [`DELETE /v1/schedules/{id}`](https://developer.kustomer.com/api/core-resources/delete-a-business-schedule-by-id) |

`PUT` replaces the schedule: send `name`, all seven days of `hours`, `timezone` and `default` every time, using `null` for closed days. Business-hour calculations elsewhere in Kustomer may take a short time to pick up a change.

Related: [SLAs](https://developer.kustomer.com/api/core-resources#sl-as), [Expressions reference](https://developer.kustomer.com/docs/workflows/expressions-reference), [Work sessions](https://developer.kustomer.com/api/queues-and-routing#work-sessions)

### Recipes

#### 1. Check whether support is open right now

Decide whether to show "we're open" or "we'll reply tomorrow" on your site or in an IVR, using the same hours and holidays Kustomer uses.

**Step 1.** [`GET /v1/schedules/default`](https://developer.kustomer.com/api/core-resources/get-default-business-schedule.md). Fetch the default schedule with `include=holidays`. Holidays come back in the top-level `included` array, which kusty doesn't print (it keeps only `data`), so use cURL here and save the whole response.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/schedules/default?include=holidays" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" > schedule.json
```

**Step 2.** Work out the weekday and minutes after midnight in the schedule's `timezone`, then check two things: the time falls inside one of that day's intervals, and no enabled holiday covers the current moment. Holiday `startDate` and `endDate` are UTC timestamps, so compare them with the current UTC time.

```bash title="kusty"
TZNAME=$(jq -r '.data.attributes.timezone' schedule.json)
DAY=$(TZ=$TZNAME date +%w)
MIN=$(( 10#$(TZ=$TZNAME date +%H) * 60 + 10#$(TZ=$TZNAME date +%M) ))
NOW=$(date -u +%Y-%m-%dT%H:%M:%S.000Z)
jq --arg d "$DAY" --argjson m "$MIN" --arg now "$NOW" '
  any(.data.attributes.hours[$d][]?; .[0] <= $m and $m < .[1])
  and (any(.included[]?; .attributes.enabled and .attributes.startDate <= $now and $now <= .attributes.endDate) | not)' schedule.json
```

> Hours change rarely, so cache the schedule for a few minutes and do the check locally on each request.

#### 2. Change one day's hours without touching the rest

Update a single weekday, for example closing early on Fridays, when the update endpoint needs the whole schedule.

**Step 1.** [`GET /v1/schedules/{id}`](https://developer.kustomer.com/api/core-resources/get-business-schedule-by-id.md). Read the schedule and build the update body from it. `PUT` requires `name`, all seven days of `hours`, `timezone` and `default`, so copy them over and change only the day you mean to. Day keys are strings, `"0"` for Sunday to `"6"` for Saturday.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/schedules/$SCHEDULE_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/schedules/$SCHEDULE_ID \
  | jq '.data.attributes | {name, hours, timezone, default} | .hours["5"] = [[480, 900]]' > schedule.json
```

**Step 2.** [`PUT /v1/schedules/{id}`](https://developer.kustomer.com/api/core-resources/update-business-schedule-by-id.md). Send the full body. Intervals must be sorted, end after they start, and not overlap or touch.

```bash title="cURL"
curl --request PUT "https://api.kustomerapp.com/v1/schedules/$SCHEDULE_ID" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data @schedule.json
```

```bash title="kusty"
kusty api PUT /v1/schedules/$SCHEDULE_ID --data @schedule.json
```

> Keep `default` as it was. Sending `true` makes this schedule the org default and clears the flag on the old one. Business-hour calculations elsewhere in Kustomer can take a short time to pick up the change.

#### 3. Add a schedule for another region

Create a business schedule for a new support region if it doesn't exist yet, without making it the org default.

**Step 1.** [`GET /v1/schedules`](https://developer.kustomer.com/api/core-resources/get-all-business-schedules.md). List schedules with `enabled=true`, which leaves out deleted ones, and check whether one with your name already exists.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/schedules?enabled=true&pageSize=100" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/schedules --query enabled=true --all \
  | jq -r 'select(.data.attributes.name == "EMEA support") | .data.id'
```

**Step 2.** [`POST /v1/schedules`](https://developer.kustomer.com/api/core-resources/create-a-business-schedule.md). If there's no match, create it with all seven days, the region's `timezone` and `default: false`. Days you're closed are `null`.

```bash title="cURL"
curl --request POST "https://api.kustomerapp.com/v1/schedules" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"name": "EMEA support", "timezone": "Europe/London", "default": false,
    "hours": {"0": null, "1": [[540, 1050]], "2": [[540, 1050]], "3": [[540, 1050]], "4": [[540, 1050]], "5": [[540, 1050]], "6": null}}'
```

```bash title="kusty"
kusty api POST /v1/schedules --data '{"name": "EMEA support", "timezone": "Europe/London", "default": false,
  "hours": {"0": null, "1": [[540, 1050]], "2": [[540, 1050]], "3": [[540, 1050]], "4": [[540, 1050]], "5": [[540, 1050]], "6": null}}'
```

> Deleting a schedule only marks it `deleted`, and the list endpoint still returns it unless you pass `enabled=true`.

### Example object

A real record from [`GET /v1/schedules/{id}`](https://developer.kustomer.com/api/core-resources/get-business-schedule-by-id.md):

```json
{
  "type": "schedule",
  "id": "5e21e0f9520ec30019df3dcf",
  "attributes": {
    "name": "Biz Hours",
    "hours": {
      "0": null,
      "1": [
        [
          480,
          1020
        ]
      ],
      "2": [
        [
          480,
          1020
        ]
      ],
      "3": [
        [
          480,
          1020
        ]
      ],
      "4": [
        [
          480,
          1020
        ]
      ],
      "5": [
        [
          480,
          1020
        ]
      ],
      "6": null
    },
    "timezone": "America/New_York",
    "default": true,
    "createdAt": "2020-01-17T16:29:45.369Z",
    "modifiedAt": "2020-01-17T17:58:27.569Z",
    "updatedAt": "2020-01-17T17:58:27.569Z"
  },
  "relationships": {
    "org": {
      "links": {
        "self": "/v1/orgs/5e15fa33623f88001a8e0bb5"
      },
      "data": {
        "type": "org",
        "id": "5e15fa33623f88001a8e0bb5"
      }
    },
    "createdBy": {
      "links": {
        "self": "/v1/users/5e15fa45623f88001a8e0bbb"
      },
      "data": {
        "type": "user",
        "id": "5e15fa45623f88001a8e0bbb"
      }
    },
    "modifiedBy": {
      "links": {
        "self": "/v1/users/5e15fa45623f88001a8e0bbb"
      },
      "data": {
        "type": "user",
        "id": "5e15fa45623f88001a8e0bbb"
      }
    }
  },
  "links": {
    "self": "/v1/schedules/5e21e0f9520ec30019df3dcf"
  }
}
```

Fields:

- `attributes.name` (string): The schedule's name, up to 256 characters. Required on create and on every `PUT`.
- `attributes.hours` (object): Opening hours by weekday: keys `"0"` (Sunday) to `"6"` (Saturday), each `null` or a list of `[start, end]` intervals in minutes after local midnight. `PUT` must send all seven days.
- `attributes.hours.0` (string): Sunday. `null` means closed all day.
- `attributes.hours.1` (array): Monday, open 480 to 1020 minutes after midnight, which is 8:00 to 17:00 local time. Add intervals for split shifts, sorted and not touching.
- `attributes.timezone` (string): An IANA time zone name such as `America/New_York`. The hours are local to it, so they follow daylight saving changes.
- `attributes.default` (boolean): Whether this is the org's default schedule, used when nothing names another one. Setting it to `true` on one schedule clears it on the old default.

### Permissions

Fewest roles for a read-only integration: `org.permission.schedule.read`. For every endpoint: `org.permission.schedule.read`, `org.permission.schedule.create`, `org.permission.schedule.delete`, `org.permission.schedule.update`.

## Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Schedule ID. |

## Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `include` | string | no | Set to `holidays` to include schedule holiday data. |

## Request body

Content type: `application/json` (required)

- `name` (string, required): (max length 256)
- `hours` (object, required)
  - `0` (anyOf, required, nullable)
    - Any of:
      - hour_tuples
  - `1` (anyOf, required, nullable)
    - Any of:
      - hour_tuples
  - `2` (anyOf, required, nullable)
    - Any of:
      - hour_tuples
  - `3` (anyOf, required, nullable)
    - Any of:
      - hour_tuples
  - `4` (anyOf, required, nullable)
    - Any of:
      - hour_tuples
  - `5` (anyOf, required, nullable)
    - Any of:
      - hour_tuples
  - `6` (anyOf, required, nullable)
    - Any of:
      - hour_tuples
- `timezone` (string, required)
- `default` (boolean, required)

Example:

```json
{
  "name": "example_name",
  "hours": {
    "0": [
      [
        0
      ]
    ],
    "1": [
      [
        0
      ]
    ],
    "2": [
      [
        0
      ]
    ],
    "3": [
      [
        0
      ]
    ],
    "4": [
      [
        0
      ]
    ],
    "5": [
      [
        0
      ]
    ],
    "6": [
      [
        0
      ]
    ]
  },
  "timezone": "string",
  "default": true
}
```

## Responses

### 200

OK

- `data` (object, required)
  - `type` (string, required)
  - `id` (string, required)
  - `attributes` (object, required)
    - `name` (string, required)
    - `hours` (object, required)
    - `timezone` (string, required)
    - `default` (boolean, required)
    - `createdAt` (string, required)
    - `modifiedAt` (string, required)
    - `updatedAt` (string, required)
  - `relationships` (object, required)
    - `org` (object, required)
    - `createdBy` (object, required)
    - `modifiedBy` (object, required)
  - `links` (object, required)
    - `self` (string, required)

```json
{
  "data": {
    "type": "schedule",
    "id": "5e21e0f9520ec30019df3dcf",
    "attributes": {
      "name": "Biz Hours",
      "hours": {
        "0": null,
        "1": [
          [
            480,
            965
          ]
        ],
        "2": [
          [
            480,
            1020
          ]
        ],
        "3": [
          [
            480,
            1020
          ]
        ],
        "4": [
          [
            480,
            1020
          ]
        ],
        "5": [
          [
            480,
            1020
          ]
        ],
        "6": null
      },
      "timezone": "America/New_York",
      "default": true,
      "createdAt": "2020-01-17T16:29:45.369Z",
      "modifiedAt": "2020-01-17T19:43:03.657Z",
      "updatedAt": "2020-01-17T19:43:03.657Z"
    },
    "relationships": {
      "org": {
        "links": {
          "self": "/v1/orgs/5e15fa33623f88001a8e0bb5"
        },
        "data": {
          "type": "org",
          "id": "5e15fa33623f88001a8e0bb5"
        }
      },
      "createdBy": {
        "links": {
          "self": "/v1/users/5e15fa45623f88001a8e0bbb"
        },
        "data": {
          "type": "user",
          "id": "5e15fa45623f88001a8e0bbb"
        }
      },
      "modifiedBy": {
        "links": {
          "self": "/v1/users/5e15fa45623f88001a8e0bbb"
        },
        "data": {
          "type": "user",
          "id": "5e15fa45623f88001a8e0bbb"
        }
      }
    },
    "links": {
      "self": "/v1/schedules/5e21e0f9520ec30019df3dcf"
    }
  }
}
```

### 400

Bad Request

### 401

Unauthorized

### 403

Forbidden

### 404

Not Found

## Example request

```bash
curl --request PUT \
  --url 'https://api.kustomerapp.com/v1/schedules/60254e3ec413ae00571e3c9b' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY" \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "example_name",
  "hours": {
    "0": [
      [
        0
      ]
    ],
    "1": [
      [
        0
      ]
    ],
    "2": [
      [
        0
      ]
    ],
    "3": [
      [
        0
      ]
    ],
    "4": [
      [
        0
      ]
    ],
    "5": [
      [
        0
      ]
    ],
    "6": [
      [
        0
      ]
    ]
  },
  "timezone": "string",
  "default": true
}'
```
