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

# Preview a request (dry run)

`POST /requests/preview`

Answer what POST /requests would do with this payload without creating anything: the workday duration, its split by fiscal year, whether the member's allowance covers it, the existing requests it would collide with, and a single `can_create` verdict.

**Input:** identical to POST /requests, so one payload can be sent to both URLs. `reason`, `representative_member_ids` and `ignoreRepresentativeRequirement` are evaluated exactly as on create.

**Duration:** `duration.workday_absence_duration` uses the same days-vs-minutes rule as `workday_absence_duration` on GET /requests (days for day and half-day leave units, minutes for hour units); preview and created request always agree. For day-based leave types the time of day in `start` / `end` is ignored, exactly as on create.

**Allowance:** `is_allowance_sufficient: false` means POST /requests with this payload fails with the allowance error; `true` means it passes that check. Leave types that do not deduct return `takes_from_allowance: false`, `allowance_type: null` and `is_allowance_sufficient: true`.

**Conflicts:** `conflicts` lists the member's existing requests that overlap this absence and would block creation. Every leave type counts, including informational ones such as home office; only declined or cancelled requests are ignored.

**Rejection:** the preview runs the same validations as POST /requests, in the same order, with one difference: an overlapping request is not a rejection but goes to `conflicts`, so where POST /requests would stop with the overlap error the preview keeps going and may name a later rule in `rejection` while the overlap sits in `conflicts`. When a rule fails, the response is still 200 with `rejection` set instead of a 400, and `can_create` is false. Match on `rejection.code`: START_AFTER_END, HALF_DAY_NOT_ALLOWED_FOR_FULL_DAY_LEAVE_TYPE, LEAVE_TYPE_DISABLED (disabled for this member), REASON_REQUIRED, REASON_TOO_LONG, OUTSIDE_FISCAL_YEAR_WINDOW, PAST_DATE_NOT_ALLOWED (manager or self-service retroactive limits), OUTSIDE_EMPLOYMENT_PERIOD, REPRESENTATIVE_CONFLICT (the member is someone else's accepted representative in that period), MAX_ABSENT_REACHED (department limit), NO_APPROVER_SET (the leave type needs approval and the member has no approver), REPRESENTATIVES_INVALID, REQUIRED_REPRESENTATIVES_COUNT_NOT_MET. Permission and scoping failures stay real 403 / 404 responses.

**Verdict:** `can_create` is true only when `rejection` is null, `conflicts` is empty and the allowance covers the absence. It is the one field to gate a create call on.

**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
  - `start` string, required — Start/end of the request in ISO 8601. For day-based leave types (`leave_unit: days`) send the date at midnight UTC (e.g. `2026-06-01T00:00:00Z`) and use `start_at` / `end_at` to set half-days. For hour-based leave types include the exact time of day (e.g. `2026-06-01T09:00:00Z`); `start_at` / `end_at` are then ignored.
  - `end` string, required — Start/end of the request in ISO 8601. For day-based leave types (`leave_unit: days`) send the date at midnight UTC (e.g. `2026-06-01T00:00:00Z`) and use `start_at` / `end_at` to set half-days. For hour-based leave types include the exact time of day (e.g. `2026-06-01T09:00:00Z`); `start_at` / `end_at` are then ignored.
  - `start_at` 'morning' | 'afternoon' — For day-based leave types: whether the absence starts in the `morning` (the whole first day counts) or `afternoon` (only the second half of the first day). Ignored for hour-based leave types.
  - `end_at` 'lunchtime' | 'end_of_day' — For day-based leave types: whether the absence ends at `lunchtime` (only the first half of the last day) or `end_of_day` (the whole last day counts). Ignored for hour-based leave types.
  - `leave_type_id` string, uuid, required
  - `reason` string
  - `requester_member_id` string, uuid, required
  - `representative_member_ids` string[] — Member IDs to assign as representatives. Required for leave types that mandate representatives; omitting them returns REQUIRED_REPRESENTATIVES_COUNT_NOT_MET (as a 400 on create, as `rejection.code` on preview).
  - `ignoreRepresentativeRequirement` boolean — Skip the representative requirement. Only honored when the API key's member is an admin and the request is created for another member.

## 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.
  - `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.
    - `duration` number, required — The span as it would be stored on the request: calendar days for day units, minutes for hour units. Use `workday_absence_duration` for what is deducted.
    - `leave_unit` 'days' | 'half_days' | 'hours' | 'minutes_30' | 'minutes_15' | 'minutes_10' | 'minutes_5' | 'minutes_1', required
    - `outside_of_schedule` boolean, required — True when the whole absence falls 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 — True means the allowance covers this payload, false means creating it fails with the allowance error. Always true for leave types that do not deduct. Null when `rejection` is set: the allowance was not evaluated. `conflicts` is reported separately and does not affect this flag.
  - `conflicts` object[], nullable, required — Existing non-declined, non-cancelled requests of the member that overlap the absence, regardless of their leave type. Any entry blocks creation. Null when the payload was rejected before the overlap check ran (START_AFTER_END through OUTSIDE_EMPLOYMENT_PERIOD); populated for every later rejection so both problems are visible at once.
    - `request_id` string, required
    - `start` string, required
    - `end` string, required
    - `start_at` 'morning' | 'afternoon', required
    - `end_at` 'lunchtime' | 'end_of_day', required
    - `status` string, required
    - `leave_type_name` string, nullable, required
  - `rejection` object, nullable, required — Set when creating the same payload would be rejected with 400: the first failing rule, in the order the create endpoint applies them. Overlapping requests and the allowance are never a rejection; see `conflicts` 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` 'START_AFTER_END' | 'HALF_DAY_NOT_ALLOWED_FOR_FULL_DAY_LEAVE_TYPE' | 'LEAVE_TYPE_DISABLED' | 'REASON_REQUIRED' | 'REASON_TOO_LONG' | 'OUTSIDE_FISCAL_YEAR_WINDOW' | 'PAST_DATE_NOT_ALLOWED' | 'OUTSIDE_EMPLOYMENT_PERIOD' | 'REPRESENTATIVE_CONFLICT' | 'MAX_ABSENT_REACHED' | '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/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)
