curl --request POST \
--url https://api.absentify.com/api/v1/requests/preview \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: <api-key>' \
--data '
{
"start": "<string>",
"end": "<string>",
"leave_type_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requester_member_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"reason": "<string>",
"representative_member_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"ignoreRepresentativeRequirement": true
}
'import requests
url = "https://api.absentify.com/api/v1/requests/preview"
payload = {
"start": "<string>",
"end": "<string>",
"leave_type_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requester_member_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"reason": "<string>",
"representative_member_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"ignoreRepresentativeRequirement": True
}
headers = {
"X-API-KEY": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-KEY': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
start: '<string>',
end: '<string>',
leave_type_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
requester_member_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
reason: '<string>',
representative_member_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
ignoreRepresentativeRequirement: true
})
};
fetch('https://api.absentify.com/api/v1/requests/preview', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.absentify.com/api/v1/requests/preview",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'start' => '<string>',
'end' => '<string>',
'leave_type_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'requester_member_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'reason' => '<string>',
'representative_member_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'ignoreRepresentativeRequirement' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-KEY: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.absentify.com/api/v1/requests/preview"
payload := strings.NewReader("{\n \"start\": \"<string>\",\n \"end\": \"<string>\",\n \"leave_type_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"requester_member_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"reason\": \"<string>\",\n \"representative_member_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"ignoreRepresentativeRequirement\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-KEY", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.absentify.com/api/v1/requests/preview")
.header("X-API-KEY", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"start\": \"<string>\",\n \"end\": \"<string>\",\n \"leave_type_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"requester_member_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"reason\": \"<string>\",\n \"representative_member_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"ignoreRepresentativeRequirement\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.absentify.com/api/v1/requests/preview")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"start\": \"<string>\",\n \"end\": \"<string>\",\n \"leave_type_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"requester_member_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"reason\": \"<string>\",\n \"representative_member_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"ignoreRepresentativeRequirement\": true\n}"
response = http.request(request)
puts response.read_body{
"can_create": true,
"duration": {
"workday_absence_duration": 123,
"duration": 123,
"leave_unit": "days",
"outside_of_schedule": true,
"per_year": [
{
"fiscal_year": 0,
"workday_duration_in_days": 123,
"workday_duration_in_minutes": 123,
"carry_over_days_used_in_period": 123,
"carry_over_minutes_used_in_period": 123
}
]
},
"takes_from_allowance": true,
"allowance_type": {
"id": "<string>",
"name": "<string>",
"allowance_unit": "days"
},
"is_allowance_sufficient": true,
"conflicts": [
{
"request_id": "<string>",
"start": "<string>",
"end": "<string>",
"start_at": "morning",
"end_at": "lunchtime",
"status": "<string>",
"leave_type_name": "<string>"
}
],
"rejection": {
"code": "START_AFTER_END",
"message": "<string>"
}
}{
"code": "BAD_REQUEST",
"message": "Invalid input data",
"issues": []
}{
"code": "UNAUTHORIZED",
"message": "Authorization not provided",
"issues": []
}{
"code": "FORBIDDEN",
"message": "Insufficient access",
"issues": []
}{
"code": "INTERNAL_SERVER_ERROR",
"message": "Internal server error",
"issues": []
}Preview a request (dry run)
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.
curl --request POST \
--url https://api.absentify.com/api/v1/requests/preview \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: <api-key>' \
--data '
{
"start": "<string>",
"end": "<string>",
"leave_type_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requester_member_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"reason": "<string>",
"representative_member_ids": [
"3c90c3cc-0d44-4b50-8888-8dd25736052a"
],
"ignoreRepresentativeRequirement": true
}
'import requests
url = "https://api.absentify.com/api/v1/requests/preview"
payload = {
"start": "<string>",
"end": "<string>",
"leave_type_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"requester_member_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"reason": "<string>",
"representative_member_ids": ["3c90c3cc-0d44-4b50-8888-8dd25736052a"],
"ignoreRepresentativeRequirement": True
}
headers = {
"X-API-KEY": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-KEY': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
start: '<string>',
end: '<string>',
leave_type_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
requester_member_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
reason: '<string>',
representative_member_ids: ['3c90c3cc-0d44-4b50-8888-8dd25736052a'],
ignoreRepresentativeRequirement: true
})
};
fetch('https://api.absentify.com/api/v1/requests/preview', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.absentify.com/api/v1/requests/preview",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'start' => '<string>',
'end' => '<string>',
'leave_type_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'requester_member_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'reason' => '<string>',
'representative_member_ids' => [
'3c90c3cc-0d44-4b50-8888-8dd25736052a'
],
'ignoreRepresentativeRequirement' => true
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-KEY: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.absentify.com/api/v1/requests/preview"
payload := strings.NewReader("{\n \"start\": \"<string>\",\n \"end\": \"<string>\",\n \"leave_type_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"requester_member_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"reason\": \"<string>\",\n \"representative_member_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"ignoreRepresentativeRequirement\": true\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-KEY", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.absentify.com/api/v1/requests/preview")
.header("X-API-KEY", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"start\": \"<string>\",\n \"end\": \"<string>\",\n \"leave_type_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"requester_member_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"reason\": \"<string>\",\n \"representative_member_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"ignoreRepresentativeRequirement\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.absentify.com/api/v1/requests/preview")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"start\": \"<string>\",\n \"end\": \"<string>\",\n \"leave_type_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"requester_member_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"reason\": \"<string>\",\n \"representative_member_ids\": [\n \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n ],\n \"ignoreRepresentativeRequirement\": true\n}"
response = http.request(request)
puts response.read_body{
"can_create": true,
"duration": {
"workday_absence_duration": 123,
"duration": 123,
"leave_unit": "days",
"outside_of_schedule": true,
"per_year": [
{
"fiscal_year": 0,
"workday_duration_in_days": 123,
"workday_duration_in_minutes": 123,
"carry_over_days_used_in_period": 123,
"carry_over_minutes_used_in_period": 123
}
]
},
"takes_from_allowance": true,
"allowance_type": {
"id": "<string>",
"name": "<string>",
"allowance_unit": "days"
},
"is_allowance_sufficient": true,
"conflicts": [
{
"request_id": "<string>",
"start": "<string>",
"end": "<string>",
"start_at": "morning",
"end_at": "lunchtime",
"status": "<string>",
"leave_type_name": "<string>"
}
],
"rejection": {
"code": "START_AFTER_END",
"message": "<string>"
}
}{
"code": "BAD_REQUEST",
"message": "Invalid input data",
"issues": []
}{
"code": "UNAUTHORIZED",
"message": "Authorization not provided",
"issues": []
}{
"code": "FORBIDDEN",
"message": "Insufficient access",
"issues": []
}{
"code": "INTERNAL_SERVER_ERROR",
"message": "Internal server error",
"issues": []
}Authorizations
Body
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/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.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$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.
morning, afternoon 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.
lunchtime, end_of_day 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).
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$Skip the representative requirement. Only honored when the API key's member is an admin and the request is created for another member.
Response
Successful response
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.
Show child attributes
Show child attributes
The allowance the absence deducts from. Null when it deducts nothing.
Show child attributes
Show child attributes
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.
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.
Show child attributes
Show child attributes
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).
Show child attributes
Show child attributes
Was this page helpful?