Voxbi Cockpit APIs
All endpoints

Cancel an absence request, or take back its cancellation (user)

The single endpoint an employee uses to undo an absence, at whatever stage it has reached. What it does depends on the current status of the request:

Current status What happens Response status
pending Cancelled outright. Pending approvals are deleted and no validator is asked. canceled
accepted (internally approved) Cannot be deleted directly. A removal request (a credit transaction, sub_type: removal) is created and the original moves to pending_removal; a validator must approve the removal before the days come back. pending (of the new removal row)
pending_removal The open removal is revoked: it is cancelled and the absence returns to accepted. The days stay booked, exactly as if a validator had rejected the removal. accepted
anything else (rejected, removed, canceled) Nothing. 400. n/a

Taking back a cancellation

The pending_removal row is what makes this endpoint reversible. An employee who asked for an approved holiday to be given back - either through this endpoint or through POST /{pbx_id}/user/{employee_id}/absence-removal-requests - can change their mind for as long as no validator has ruled, and keep the days.

You may address that revocation with either id:

  • the original absence id (status pending_removal) - the row the employee sees in their calendar, and the id a client normally holds; or
  • the removal request id (status pending, sub_type: removal) - the credit row created when the removal was opened.

Both land on the same state and both answer with the restored original absence, never with the withdrawn credit row. Revoking also deletes the removal's pending approvals, so no validator is left holding a decision that no longer matters.

Revocation is not one-way: once the absence is back to accepted, calling this endpoint again simply opens a fresh removal request.

Emails

A withdrawal the employee performs themselves is confirmed by email to the employee the absence belongs to, and to whoever filed it when that is someone else. Two different messages are sent:

  • request cancelled - a pending request was withdrawn;
  • cancellation withdrawn - a pending_removal was revoked and the days stay booked.

Opening a removal (the accepted branch) sends no such confirmation: nothing has been withdrawn yet, and the validators are notified instead.

Authorization

Owner-based: only the employee the request belongs to may cancel it, regardless of who created it on their behalf. The request is resolved by its own id, so the {employee_id} path segment is not used for authorization - a request that genuinely does not exist returns 404, one that exists but is not the caller's returns 403.

HTTP: bearerAuth

User bearer token. Default authentication for customer-facing endpoints. Obtain a token by calling POST /login with your credentials, then send it on every subsequent request as Authorization: Bearer <token>. The token inherits the permissions and PBX scope of the authenticated user.

HTTP Authorization Scheme
bearer
Bearer format
Bearer <token>
pbx_id path · string · uuid *
UUID of the PBX tenant.
employee_id path · string · uuid *
UUID of the employee (path segment only; not used for authorization - the request is resolved by its own id and cancel is owner-based).
absence_request_id path · string · uuid *
UUID of the absence request transaction. To revoke a cancellation this may be either the original absence (status `pending_removal`) or its pending removal request (`sub_type: removal`) - both are accepted.

Request body

Request schema
No request body schema.

Responses

The affected absence request, serialized by the V1 transaction resource (unwrapped - the object is the response body, there is no `data` envelope). Which row you get back depends on the branch taken: the cancelled request, the newly created pending removal, or the restored original absence. Switch on `status` and `sub_type` rather than assuming.
Response schema
id*string · uuid
Unique absence request (transaction) identifier.
read-only
example: 550e8400-e29b-41d4-a716-446655440000
mxvp_user_id*string | null · uuid
Identifier of the Pbx (tenant) that owns this absence request. Mirrors the transaction's `pbx_id`. Null for records not bound to a specific tenant.
example: 550e8400-e29b-41d4-a716-446655440001
employee_id*string · uuid
Canonical identifier of the Tempus employee this request belongs to. This is the value the `filter[employee_id]` query parameter matches against.
example: 550e8400-e29b-41d4-a716-446655440002
requester_id*string · uuid
Identifier of the Tempus employee the absence belongs to. Returns the same value as `employee_id` and `creator_id`.
example: 550e8400-e29b-41d4-a716-446655440002
creator_id*string · uuid
Identifier of the Tempus employee who owns the absence. Returns the same value as `requester_id` (the owning employee, not the acting principal).
example: 550e8400-e29b-41d4-a716-446655440002
ext_id*string | null
External system identifier of the employee, resolved from the linked employee record. Null when the employee has no external mapping.
example: EXT-10293
company_id*string | null · uuid
Identifier of the Tempus company the absence is attributed to. Null when the request is not scoped to a specific company.
example: 550e8400-e29b-41d4-a716-446655440003
absence_request_id*string · uuid
Alias of `id`, provided for clients that reference absence requests by this field name. Always equal to `id`.
read-only
example: 550e8400-e29b-41d4-a716-446655440000
holiday_id*string | null · uuid
Reserved for a future link to a public-holiday record. Always null in the current API version.
status*string | null
Approval status of the absence request, remapped for the API: internal `approved` is returned as `accepted` and internal `cancelled` as `canceled`. `pending` awaits validation, `pending_removal` and `removed` track removal of a previously approved absence. Null only for records with no status set.
enum: pending accepted rejected canceled pending_removal removed
example: pending
type*string
Category of the absence. The internal `absence` type is remapped to `legal` (legal holiday). `home_office` is a remote-work day, `illness` is sick leave, `special` is special leave, and `overtime_balance` is an overtime-balance adjustment.
enum: legal home_office illness special overtime_balance
example: legal
sub_type*string | null
Optional sub-classification of the absence (for example `removal` for a removal request, or a computed/rollover marker for ledger adjustments). Null when the request carries no sub-type.
example: removal
start*string | null · date-time
Start of the absence period as an ISO 8601 timestamp. Null when the request has no start date.
example: 2024-03-01T00:00:00+00:00
end*string | null · date-time
End of the absence period as an ISO 8601 timestamp. Null when the request has no end date.
example: 2024-03-05T00:00:00+00:00
requested_time*object | null
Per-day breakdown of the requested time, stored as a map of date strings to durations in seconds. Used to compute the effective amount while the request is pending. Null when no per-day breakdown was supplied. Seconds are the employee's own scheduled day, not a nominal 8h: a part-timer on a 4h day books `14400` for a full day off, and a half of that day is `7200`. Only working days appear: a weekend, public holiday or company special holiday inside the range is omitted. Rows written before that was true carry a `0` entry for each such day, and rows created over this API before it carried a flat `28800` regardless of shift. So do **not** count these keys to get the length of a request and do not assume the seconds match the shift on historical rows: read `working_days` and `workshift_seconds_per_day`, which normalize both shapes.
example: {"2024-03-01":28800,"2024-03-04":28800,"2024-03-05":14400}
working_days*number
How long the request is, in working days the employee actually consumes: weekends, public holidays, company special holidays and days already taken by another absence are excluded, and each remaining day is measured against that employee's own scheduled day. A part-timer's 4h day therefore counts as `1`, while 4h out of an 8h day counts as `0.5`. This is the same figure the approval debits from the employee's balance, so it never disagrees with the amount charged. Prefer it over deriving a length from `start`/`end`: that difference is exclusive (Aug 3 → Aug 7 is 5 days, not 4) and counts days nobody works. Whole counts serialize without a fractional part (`5`, not `5.0`); half days appear as `.5`.
example: 4.5
total_seconds*integer · int32
Sum of the booked seconds in `requested_time`, i.e. the same period expressed as time rather than days. `0` when the request has no per-day breakdown.
example: 129600
workshift_seconds_per_day*integer · int32
What one full working day is worth for this employee, in seconds. It is the yardstick every booked day is measured against. `28800` on a standard 8h shift, `14400` for somebody contracted to 4h days. Use it to render a booking as a fraction of a day without assuming 8h.
example: 28800
is_half_day*boolean
True when either end of the request is a half day. The single flag a list badge needs; `starts_half_day` / `ends_half_day` say which end.
example: 1
starts_half_day*boolean
True when the first day is a half day: the employee works that morning and leaves. `start_time` then carries the hour they leave.
example: 1
ends_half_day*boolean
True when the last day is a half day: the absence covers that morning and the employee is back after it. `end_time` then carries the hour they return. On a single-day half day both this and `starts_half_day` are true, and both hours are filled in.
example:
start_time*string | null
Local wall-clock hour (`HH:MM`) the absence begins on a half-day start. Null when the request does not start on a half day. Taken from the request's own stored time when it has one, otherwise the tenant's configured AM/PM split (`TEMPUS_HALF_DAY_SPLIT`, default `12:00`).
example: 14:00
end_time*string | null
Local wall-clock hour (`HH:MM`) the absence ends on a half-day end. Null when the request does not end on a half day. Same source as `start_time`.
example: 18:00
working_day_details*array<object>
Per-day breakdown of the request, working days only, in date order. The same days as `requested_time` with the half-day verdict and hours resolved per day. Hours only ever land on the two ends: a day in the middle of a range has no clock time of its own, and a single-day request is both ends at once.
date*string · date
The day, as `YYYY-MM-DD`.
example: 2026-08-03
seconds*integer · int32
Seconds booked off on this day, scaled to the employee's own scheduled day.
example: 14400
is_half_day*boolean
True when this day's booked seconds fall short of a full day for this employee (see `workshift_seconds_per_day`), allowing 15 minutes of tolerance for lunch and rounding.
example: 1
start_time*string | null
Hour (`HH:MM`) the absence starts on this day. Non-null only on a half-day first day (or a single-day half day).
example: 12:00
end_time*string | null
Hour (`HH:MM`) the absence ends on this day. Non-null only on a half-day last day (or a single-day half day).
comment*string | null
Free-form note attached to the absence request (mirrors the transaction's `note`). Null when no comment was provided.
example: Family event, returning Wednesday afternoon.
accepted_by*array<object>
Every validator that must sign off on this request, with their individual decision. One entry per approval record; empty when the request has no assigned validators yet. A request is fully accepted only once every entry reports `accepted`.
employee_id*string | null · uuid
Identifier of the Tempus employee acting as validator for this approval.
example: 550e8400-e29b-41d4-a716-446655440004
status*string
This validator's decision. `pending` awaits their action; `accepted` and `rejected` record the outcome.
enum: pending accepted rejected
example: pending
next_availabilityobject
When an employee is next at work **if the request carrying this object goes through**, next to what the general next-availability answer says today. The validator's view of a request: no second call, and no cache between the queue and the date. It is deliberately not the same question as `GET /user/{employee_id}/next-availability`: - **Anchored at the request, not at today**, so it reports the day *this* booking frees rather than the next free day counting from now. - **Counts what has been decided plus this one request.** The base is what has been signed off (accepted, plus an accepted absence somebody has asked to cancel — still time off until that cancellation is approved). Other queued requests are left out: they may be refused, and a date that moves because of a request this validator has not seen is not one they can act on. (`current` does count every pending row — that difference is intentional.) - **A removal reads as a reduction**, so its answer moves *earlier*, often into the past (`in_past`), which is the signal that the employee is already back. Absence, illness, special leave and legal holiday only. Home office never carries it: a home office day is governed by a weekly limit, not time off that moves an availability date. Neither does a request that can no longer move a date — `rejected`, `canceled` and `removed`. **Where it appears:** the absence lists (`/absences`, `/user/{employee_id}/absences`), the absence-request reads and writes (`/absence-requests`, `/user/{employee_id}/absence-requests` and its `POST`/`PUT`), `POST /user/{employee_id}/absence-removal-requests`, and both validator queues (`/validator/pending-requests` — on each `absence_requests[]` / `absence_removals[]` entry — and v2 `pending-requests/absences`). It is **not** on the cancel responses (`POST .../absence-requests/{id}/cancel`), which report the outcome of an employee's own action rather than something a validator is weighing up. On the resource-backed payloads the key is **omitted** when there is nothing to say; on the v1 validator queue, whose entries are built by hand, it is always present and `null` in that case. Both mean the same thing.
if_approved*object
Availability as it would stand with this request applied.
absent_from*string | null · date
First day, today or later, that this request itself takes off. Null when it takes nothing off in the future: a removal request, or days that have all been and gone.
example: 2026-08-13
next_available_date*string | null · date
The day the employee is back at work under this scenario. Null only when no working day was found within the search horizon — an employee with no work shift, or one whose contract has ended.
example: 2026-08-17
next_available_at*string | null
Exact moment they are back (`YYYY-MM-DD HH:MM:SS`) when a half day pins one down; null when only the date is meaningful.
available_now*boolean
Whether the employee is at work right now under this scenario. True together with a future `absent_from` is the ordinary case for a request that starts later.
example: 1
in_past*boolean
True when `next_available_date` is already behind us, i.e. approving this would not keep anybody away from now on. Render it as "already available" rather than as a return date.
example:
extends_current_absence*boolean
True when this request starts on the very day the employee would otherwise have come back, so it lengthens the absence already in place rather than opening a separate one. False for a request with working days in between — show `absent_from` as well as the return date in that case, or the return date reads as a contradiction of `available_now`.
example:
message*string | null
Ready-to-display line, already covering the three cases: `Available on 2026-08-17`, `Off from 2026-08-13, available on 2026-08-17`, or `Already available (was back on 2026-08-03)`. Show it as returned.
example: Off from 2026-08-13, available on 2026-08-17
current*object
Today's general answer, unchanged — the same figure `GET /user/{employee_id}/next-availability` returns, so the two screens never disagree.
next_available_date*string | null · date
Next day the employee is available, counting from now. Null when they are available right now.
example: 2026-08-12
message*string | null
Ready-to-display line, or null while the employee is available.
example: Back on 2026-08-12
created*string | null · date-time
Timestamp when the absence request was created, as an ISO 8601 string. Null when not set.
read-only
example: 2024-02-15T10:00:00+00:00
modified*string | null · date-time
Timestamp when the absence request was last updated, as an ISO 8601 string. Null when not set.
read-only
example: 2024-02-16T09:30:00+00:00
The request is in a status that cannot be cancelled - `rejected`, `removed` or already `canceled`. Only `pending`, `accepted` and `pending_removal` rows can be acted on.
Response schema
messagestring
example: Only pending, approved or pending-removal absence requests can be cancelled.
Authorization Token Missing. This error is returned when the authorization token is missing.
Response schema
errorstring
Error message
example: Authorization Token is missing
The request exists but does not belong to the authenticated user (only its owner may cancel it).
Response schema
No response body.
Data Not Found. This error is returned when the requested data is not found.
Response schema
messagearray<string>
[]string
post https://cockpit.voxbi.com/api/v1/{pbx_id}/user/{employee_id}/absence-requests/{absence_request_id}/cancel
Base URL
Request sample
curl -X POST 'https://cockpit.voxbi.com/api/v1/{pbx_id}/user/{employee_id}/absence-requests/{absence_request_id}/cancel' \
  -H 'Authorization: Bearer YOUR_TOKEN'
const response = await fetch('https://cockpit.voxbi.com/api/v1/{pbx_id}/user/{employee_id}/absence-requests/{absence_request_id}/cancel', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${YOUR_TOKEN}`,
  },
});

const data = await response.json();
console.log(data);
import requests

response = requests.post('https://cockpit.voxbi.com/api/v1/{pbx_id}/user/{employee_id}/absence-requests/{absence_request_id}/cancel',
    headers={'Authorization': f'Bearer {YOUR_TOKEN}'}
)
response.raise_for_status()
data = response.json()
print(data)
<?php
$context = stream_context_create([
    'http' => [
        'method'  => 'POST',
        'header'  => "Content-Type: application/json\r\nAuthorization: Bearer YOUR_TOKEN",
    ],
]);

$response = file_get_contents('https://cockpit.voxbi.com/api/v1/{pbx_id}/user/{employee_id}/absence-requests/{absence_request_id}/cancel', false, $context);
$data = json_decode($response, true);
print_r($data);
Sample request
No request example provided.
{}
"id": "550e8400-e29b-41d4-a716-446655440030",
"mxvp_user_id": "550e8400-e29b-41d4-a716-446655440000",
"employee_id": "550e8400-e29b-41d4-a716-446655440001",
"requester_id": "550e8400-e29b-41d4-a716-446655440001",
"creator_id": "550e8400-e29b-41d4-a716-446655440001",
"ext_id": "EXT-10293",
"company_id": "550e8400-e29b-41d4-a716-446655440003",
"absence_request_id": "550e8400-e29b-41d4-a716-446655440030",
"holiday_id": null,
"status": "canceled",
"type": "legal",
"sub_type": null,
"start": "2026-09-14T00:00:00+00:00",
"end": "2026-09-15T00:00:00+00:00",
"requested_time": {},
"2026-09-14": 28800,
"2026-09-15": 28800
},
"working_days": 2,
"total_seconds": 57600,
"workshift_seconds_per_day": 28800,
"is_half_day": false,
"starts_half_day": false,
"ends_half_day": false,
"start_time": null,
"end_time": null,
"working_day_details": [],
{},
"date": "2026-09-14",
"seconds": 28800,
"is_half_day": false,
"start_time": null,
"end_time": null
},
{}
"date": "2026-09-15",
"seconds": 28800,
"is_half_day": false,
"start_time": null,
"end_time": null
}
],
"comment": "Changed my plans.",
"accepted_by": [],
"created": "2026-09-01T10:00:00+00:00",
"modified": "2026-09-02T08:15:00+00:00"
}
{}
"message": "Only pending, approved or pending-removal absence requests can be cancelled."
}
{}
"error": "Authorization Token is missing"
}
Cache-Control string
example: private, must-revalidate
Connection string
example: keep-alive
Content-Type string
example: application/json
Vary string
example: Origin
X-RateLimit-Limit integer
Max requests allowed in the current rate-limit window.
example: 60
X-RateLimit-Remaining integer
Requests remaining in the current rate-limit window.
example: 57
No example for this status.
{}
"message": []
"Data not found"
]
}
Cache-Control string
example: private, must-revalidate
Connection string
example: keep-alive
Content-Type string
example: application/json
Vary string
example: Origin
X-RateLimit-Limit integer
Max requests allowed in the current rate-limit window.
example: 60
X-RateLimit-Remaining integer
Requests remaining in the current rate-limit window.
example: 57