When to use this endpoint

Create or patch an availability/leave event using snake-cased fields. On update, obtain the active event ID through Availability_list. For the current CRM2 management workflow, use the mapped createData/updateData routes instead; their notifications and leave-day metadata differ.

Authentication and permissions

Authenticate with signed API-key fields or a logged-in CRM session. Uses the authenticated tenant’s data. Requires pageAvailability_add for creation or pageAvailability_edit for updates. Client administrators and full-access roles are allowed. Other roleless users are denied; signed API keys use the key owner’s role. These are page/action permissions, not an employee-ownership or HR-profile approver-list restriction.

Management calls reject accounts with features2.disableHREnhancements=1, returning HR enhancements are disabled. The helper reads the account’s current global feature value before falling back to session values. Browser permission checks are not server-side authorization.

Parameters

ParameterTypeRequiredDescription
idIntegerNoInteger-converted ID, default zero. Zero creates; a nonzero value must identify a non-deleted tenant event. There is no separate positivity guard.
obj_typeStringYesOne of company, department, role, user. Case-sensitive after trimming.
obj_idIntegerFor non-company targetsCompany forces zero. Other targets require an existing tenant target row; active/deleted state is not checked. Defaults to zero.
event_typeStringNoAllowed names: absence, annual_leave, sick_leave, unpaid_leave, public_holiday, company_holiday, unavailable, available_override. Defaults to absence.
availabilityInteger flagNoZero means unavailable, any nonzero integer-converted value means available. Defaults to zero; event_type does not supply it.
statusIntegerNo0 pending, 1 approved, 2 rejected, 3 cancelled; other integer-converted values are rejected. Defaults to pending.
nameStringNoTrimmed event label; blank is accepted. Defaults to empty.
start_e / end_eUnix secondsYesStart must be positive and end later than start after optional all-day normalization. Omitted values initially become zero.
all_dayInteger flagNoTruthy integer enables account-local midnight normalization; otherwise use supplied epochs. Defaults to zero.
paidInteger flagNoTruthy integer means paid; no automatic inference from event type. Defaults to zero.
requested_byIntegerNoRequester attribution, not an authority check. Zero/omitted resolves to existing requester or authenticated user; supplied nonzero IDs are not validated as existing users.
notesStringNoTrimmed text. Defaults to empty.
metaNative array or JSON textNoMetadata defaults to an empty array. Invalid/non-array decoded JSON becomes an empty array.

Example request

$result = FM_api('Availability_save', [
	'obj_type' => 'user',
	'obj_id' => '71',
	'event_type' => 'annual_leave',
	'start_e' => '1793005200',
	'end_e' => '1793034000',
	'name' => 'Annual leave',
	'paid' => '1',
	'notes' => 'Family leave',
]);

Example response

{
  "ok": 1,
  "id": 1042,
  "event": {
    "id": 1042,
    "obj_type": "user",
    "obj_id": 71,
    "event_type": "annual_leave",
    "availability": 0,
    "status": 0,
    "status_name": "pending",
    "name": "Annual leave",
    "start_e": 1793005200,
    "end_e": 1793034000,
    "all_day": 0,
    "paid": 1,
    "requested_by": 71,
    "approved_by": 0,
    "approved_at": 0,
    "notes": "Family leave",
    "meta": [],
    "cdate": 1792584000,
    "last_edited": 1792584000,
    "created_by": 71
  }
}

Normalized event objects contain id, obj_type, obj_id, event_type, availability, status, derived status_name, name, start_e, end_e, all_day, paid, requested_by, approved_by, approved_at, notes, decoded meta, cdate, last_edited, and created_by. Numeric fields are integers, unlike raw database rows. Invalid/non-array metadata becomes []; deleted/ddate are not exposed by the normalizer.

Updates inherit omitted fields from the current row, despite the creation-oriented parameter requirements above. Raw save does not calculate meta.leave_days; that is calculated by mapped creation/update, not a guaranteed property of this event. If the post-save re-read fails, the response retains the saved id/ok, sets event=null and adds saved=1 with a no-replay warning.

Errors and unusual responses

ResponseCondition
{"error":"not logged in"}No authenticated tenant context is available. Invalid signed credentials can be rejected by the dispatcher before the handler runs.
{"error":"permission denied"}The authenticated actor lacks the required page/action permission.
{"error":"HR enhancements are disabled"}The account has disabled HR enhancements.
{"error":"invalid obj_type"}Target type is not company, department, role, or user.
{"error":"missing or invalid obj_id"}A non-company target has an integer-converted ID less than one.
{"error":"target does not exist"}No tenant row exists for the selected non-company target. This lookup does not impose an active/deleted target filter.
{"error":"invalid event_type"}The event type is outside the eight-name allowlist.
{"error":"invalid status"}The integer-converted status is outside 0–3.
{"error":"invalid start/end time"}After optional all-day normalization, start/end are nonpositive or end is not later than start.
{"error":"availability event not found"}A nonzero ID does not resolve to a non-deleted tenant event.
{"error":"Unable to save availability event"}The primary event write failed; dependent recalculation and notices are not run.

Legacy application errors may be returned with HTTP 200. Check the decoded response for error, not only the HTTP status.

Behaviour and edge cases

  • Event ranges are half-open: [start_e,end_e). Epoch values are Unix seconds, not milliseconds. With all-day enabled, the helper converts both epochs to account-local dates and replaces them with local midnights. If the normalized end is not later than the start, it becomes the next local midnight. A multi-day end date therefore remains exclusive; include the following midnight when the UI’s final date is inclusive.
  • The event type label does not force availability, status, or paid. For example, an annual-leave event may be stored as available; approved annual-leave usage still keys on event type and status, not those flags. No overlap or entitlement-limit validation is performed.
  • Entering approved status sets approved_by and approved_at to the authenticated actor/current Unix time. Saving an already-approved row does not refresh these fields, and leaving approved status does not clear prior approval attribution.
  • A save always attempts its row/timestamp write, even if content is unchanged. Creation takes its ID from the checked insert helper. A failed primary write returns an error before balances or notices are changed.
  • The primary write is checked before dependent work. After saving, failed recalculation or notification work retains the normal success fields and adds saved=1 and warning. Do not repeat the write to repair a follow-up; re-read the event and investigate balances/notification history. The event, history, balances and external queues are not one atomic transaction.

Side effects

  • Writes the tenant event and Unix last_edited; creation also sets cdate and created_by. Requests availability_events cache invalidation. It does not call Core_log or insert a tenant deletion audit.
  • When an old or new event is approved annual leave, the helper recalculates affected users’ stored annual-leave usage for touched years, using current company/department/role/user membership. Each account-local date touched counts as one whole day except dates in week-days-off (0 Sunday through 6 Saturday). Partial dates count as full days, year boundaries are split, and calendar arithmetic preserves DST dates. Overlapping leave events contribute separately; balances may become negative. Supported recalculation years are the server’s current year minus five through plus ten.
  • The legacy transition helper creates tenant notices, advances recipients’ users.meta.notices_latest, and queues direct push work in global cron type 100. A new pending request uses configured HR-profile leave approvers, otherwise managers, otherwise active users whose ID is the tenant owner or whose permissions text is nonblank; the requester is excluded from that creation notice. A status change notifies the requester plus target users. It does not send unconditional email copies or run configured notification rules.

See it in Fieldmotion

CRM1

No direct CRM1 caller for Availability_save was found in the source search. The implemented management screen is CRM2’s Availability page.

CRM2

No direct page-level CRM2 Availability_save caller was found. The Availability dialog translates its fields into camel-cased Availability_createData/Availability_updateData calls. The mobile app uses the same route name on the separate m service, not this CRM handler.

Related endpoints