---
title: "Preview recurring requests (dry run)"
method: POST
path: "/requests/recurring/preview"
tags: ["Requests"]
---

# Preview recurring requests (dry run)

`POST /requests/recurring/preview`

Answer what POST /requests/recurring would do with this payload without creating anything: the number of occurrences, the summed workday duration and its split by fiscal year, whether the member's allowance covers the whole series, and the per-occurrence conflicts.

**Input:** identical to POST /requests/recurring. `files` is accepted and ignored. `reason`, `admin_approval_option`, `ignoreMaximumAbsence` and `representative_member_ids` are validated exactly as on create.

**Duration:** `duration.workday_absence_duration` follows the same days-vs-minutes rule as `workday_absence_duration` on GET /requests and is the sum over all occurrences; preview and created series always agree. `per_year` splits that sum by fiscal year.

**Allowance:** `is_allowance_sufficient` is one verdict for the whole series, matching the all-or-nothing behaviour of POST /requests/recurring: `false` means creating the series fails with the allowance error and leaves nothing behind. Leave types that do not deduct return `true`.

**Conflicts:** `conflicts` lists occurrences overlapping the member's existing requests, `department_limit_conflicts` the occurrences on which a department's maximum absent limit is already reached, `representative_overlaps` the occurrences on which the member is someone else's representative (pending or accepted), and `representative_unavailable` the occurrences on which a passed representative is absent.

**Rejection:** when the payload would be rejected with 400 by POST /requests/recurring, the response is still 200 with `rejection` set and `can_create: false`. Match on `rejection.code`: neither `until` nor `count` given (RECURRENCE_END_REQUIRED), day/month/pos/weekDays not matching `repeat` (RECURRENCE_RULE_INVALID), a series reaching more than 365 days into the future (RECURRENCE_WINDOW_EXCEEDED), fewer than two occurrences (RECURRENCE_TOO_FEW_OCCURRENCES), an hour-based leave type without a valid start_time/end_time (HOUR_LEAVE_TYPE_REQUIRES_TIME_RANGE), a half_day on a full-day-only leave type (HALF_DAY_NOT_ALLOWED_FOR_FULL_DAY_LEAVE_TYPE), or an occurrence outside the member's employment period (OUTSIDE_EMPLOYMENT_PERIOD). After that the rules the create endpoint applies to every occurrence follow, in its order: a leave type disabled for the member (LEAVE_TYPE_DISABLED), a missing or too long reason (REASON_REQUIRED, REASON_TOO_LONG), an occurrence outside the bookable fiscal-year window (OUTSIDE_FISCAL_YEAR_WINDOW) or too far in the past for the caller's role (PAST_DATE_NOT_ALLOWED), an occurrence on which a non-admin is someone else's accepted representative (REPRESENTATIVE_CONFLICT), a leave type needing approval while the member has no approver (NO_APPROVER_SET), and the representative requirement (REPRESENTATIVES_INVALID, REQUIRED_REPRESENTATIVES_COUNT_NOT_MET). A workspace without the Essentials plan, or a non-admin passing `admin_approval_option: approve_request_immediately`, gets the same 403 as the create endpoint.

**Verdict:** `can_create` is true only when `rejection` is null, `conflicts` is empty, `department_limit_conflicts` is empty (or the caller is an admin passing `ignoreMaximumAbsence`) and the allowance covers the series. `representative_unavailable` is informational.

**Side effects:** no request, approver, notification, webhook or calendar sync entry is created. Safe to repeat; each call evaluates the data as it is at that moment.

## Request body

- object
  - `leave_type_id` string, uuid, required
  - `requester_member_id` string, uuid, required
  - `reason` string
  - `repeat` 'daily' | 'weekly' | 'monthly', required
  - `interval` integer, required
  - `from` string, required — Date/time in ISO 8601 format, e.g. `2026-06-01` (interpreted as UTC midnight) or `2026-06-01T09:00:00Z`. Always include a time zone designator (`Z` for UTC) when sending a time of day. Localized formats such as `01.06.2026` or `06/01/2026` are misinterpreted and must not be used.
  - `until` string, nullable, required — Date/time in ISO 8601 format, e.g. `2026-06-01` (interpreted as UTC midnight) or `2026-06-01T09:00:00Z`. Always include a time zone designator (`Z` for UTC) when sending a time of day. Localized formats such as `01.06.2026` or `06/01/2026` are misinterpreted and must not be used.
  - `count` integer, nullable, required
  - `start_time` object
    - `hour` integer, required
    - `minute` integer, required
  - `end_time` object
    - `hour` integer, required
    - `minute` integer, required
  - `half_day` 'full_day' | 'morning' | 'afternoon'
  - `weekDays` string, nullable, required
  - `day` union, required
    - integer
    - string
  - `month` integer, nullable, required
  - `pos` union, required
    - integer
    - string
  - `exDates` string[]
  - `files` string[]
  - `representative_member_ids` string[]
  - `ignoreMaximumAbsence` boolean
  - `admin_approval_option` 'approve_request_immediately' | 'go_through_the_normal_approval_process'

## Response `200`

Successful response

- object
  - `can_create` boolean, required — The one-line verdict: true means POST with this payload passes every rule the create endpoint applies, as of the data at preview time; false means it fails. False whenever `rejection` is set, a blocking conflict is listed or `is_allowance_sufficient` is false. Two things the preview cannot see: requests created in between, and changes to the member's approvers that the create call makes when approver sync from Microsoft 365 is on. A snapshot, not a reservation. `department_limit_conflicts` block unless the caller is an admin passing `ignoreMaximumAbsence`; `representative_unavailable` never blocks.
  - `occurrence_count` integer, required
  - `duration` object, nullable, required
    - `workday_absence_duration` number, required — Same field and unit rule as `workday_absence_duration` on GET /requests: days for day and half-day leave units, minutes for hour and minute units. Summed over all occurrences.
    - `leave_unit` 'days' | 'half_days' | 'hours' | 'minutes_30' | 'minutes_15' | 'minutes_10' | 'minutes_5' | 'minutes_1', required
    - `outside_of_schedule` boolean, required — True when at least one occurrence falls entirely on non-working time of the member's schedule.
    - `per_year` object[], required
      - `fiscal_year` integer, required
      - `workday_duration_in_days` number, required
      - `workday_duration_in_minutes` number, required
      - `carry_over_days_used_in_period` number, required
      - `carry_over_minutes_used_in_period` number, required
  - `takes_from_allowance` boolean, required
  - `allowance_type` object, nullable, required — The allowance the absence deducts from. Null when it deducts nothing.
    - `id` string, required
    - `name` string, required
    - `allowance_unit` 'days' | 'hours', required
  - `is_allowance_sufficient` boolean, nullable, required — Verdict for the whole series: true means the allowance covers it, false means creating it fails with the allowance error and leaves nothing behind. Always true for leave types that do not deduct. Null when `rejection` is set: the allowance was not evaluated. The conflict arrays are reported separately and do not affect this flag.
  - `conflicts` object[], required — Occurrences that overlap an existing non-declined, non-cancelled request of the member.
    - `date` string, required
    - `conflicts_with` object[], required
      - `request_id` string, required
      - `start` string, required
      - `end` string, required
      - `status` string, required
      - `leave_type_name` string, nullable, required
  - `department_limit_conflicts` object[], required
    - `date` string, required
    - `department_id` string, required
    - `department_name` string, required
    - `current_absent` integer, required
    - `max_allowed` integer, required
  - `representative_unavailable` object[], required — Only populated when representative_member_ids are passed: occurrences on which a chosen representative is absent.
    - `date` string, required
    - `representatives` object[], required
      - `member_id` string, required
      - `member_name` string, nullable, required
      - `reason` string, required
  - `representative_overlaps` object[], required — Occurrences on which the member is someone else's representative, pending or accepted. Only accepted ones reject the series for non-admins (REPRESENTATIVE_CONFLICT).
    - `date` string, required
    - `representing_for` object[], required
      - `member_id` string, required
      - `member_name` string, nullable, required
      - `request_id` string, required
      - `status` string, required
      - `start` string, required
      - `end` string, required
  - `rejection` object, nullable, required — Set when creating the same series would be rejected with 400: the first failing rule, series rules first and then the per-occurrence rules, in the order the create endpoint applies them. Overlaps, the allowance and department limits are never a rejection; see the conflict arrays and `is_allowance_sufficient`. `duration` and `is_allowance_sufficient` are null when this is set. Match on `code`, not on `message` (which is translated into the API key member's language).
    - `code` 'RECURRENCE_WINDOW_EXCEEDED' | 'RECURRENCE_END_REQUIRED' | 'RECURRENCE_RULE_INVALID' | 'RECURRENCE_TOO_FEW_OCCURRENCES' | 'HOUR_LEAVE_TYPE_REQUIRES_TIME_RANGE' | 'HALF_DAY_NOT_ALLOWED_FOR_FULL_DAY_LEAVE_TYPE' | 'OUTSIDE_EMPLOYMENT_PERIOD' | 'LEAVE_TYPE_DISABLED' | 'REASON_REQUIRED' | 'REASON_TOO_LONG' | 'OUTSIDE_FISCAL_YEAR_WINDOW' | 'PAST_DATE_NOT_ALLOWED' | 'REPRESENTATIVE_CONFLICT' | 'NO_APPROVER_SET' | 'REPRESENTATIVES_INVALID' | 'REQUIRED_REPRESENTATIVES_COUNT_NOT_MET' | 'INVALID_REQUEST', required
    - `message` string, required

## Other responses

- `400` — Invalid input data
- `401` — Authorization not provided
- `403` — Insufficient access
- `500` — Internal server error

## Changes

- **2026-09-25** `0f8b476900a8` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/absentify/apis/absentify-crud-api/changes/requests/recurring/preview/post.md)

---

[API](https://skmtc.dev/absentify/apis/absentify-crud-api.md) · [All operations](https://skmtc.dev/absentify/apis/absentify-crud-api/llms.txt) · [OpenAPI document](https://skmtc.dev/absentify/apis/absentify-crud-api/revisions/0f8b476900a8?raw)
