Calendar Events
Reference for the calendar_events resource. Requests follow the conventions in
Requests and Responses; fields you
receive and may write are filtered by your user's permissions, so responses can
contain a subset of the fields below.
Operations
| Operation | Request |
|---|---|
| List | GET /{profile}/user/v4/calendar_events |
| Schedule a tech or sales appointment for a customer | POST /{profile}/user/v4/calendar_events/schedule |
| Reschedule an appointment (new start time, optional audit record) | POST /{profile}/user/v4/calendar_events/reschedule |
| Cancel (delete) an appointment | POST /{profile}/user/v4/calendar_events/cancel |
| List users who can take an appointment, with optional occupancy | POST /{profile}/user/v4/calendar_events/available_users |
| List configured tech time slots that are open for a customer | POST /{profile}/user/v4/calendar_events/available_slots |
Not available as a standard REST operation for this resource: create, update, delete —
such requests return 402 feature_not_enabled or 403 permission_denied (see the actions above for this resource's write path).
How it works
calendar_events are appointments — one table holding both tech appointments
(calendar_category: tech_schedule: installs, service visits, removals) and sales appointments
(calendar_category: sale_schedule). The table has a composite key (id, calendar_category), so
records are not addressed at /{id}; instead, appointments of both kinds are managed through
collection actions. schedule, reschedule, and cancel take the appointment id (or,
for schedule, the calendar_category) in the body. available_users and available_slots
are read-only lookups (HTTP 200) for who can take a job and, when the company uses time-slot
scheduling, which configured slots are open. Resolve calendar_event_type_id from
calendar event types before scheduling. Record a
confirmation through calendar event confirmations.
Permissions follow the SecurityTrax scheduler:
- Tech appointments need the Scheduler permission at the customer's location (create / modify / delete for schedule / reschedule / cancel). Your own appointments are always visible.
- Sales appointments need the Sale Scheduler permission, or Assign Sale Schedule To Self — in which case you may only schedule appointments assigned to yourself.
Scheduling a tech appointment
curl -X POST "https://portal.securitytrax.com/acme/user/v4/calendar_events/schedule" \
-H "Authorization: Bearer stx_acme_..." \
-H "Content-Type: application/json" \
-d '{"data":{"attributes":{"customer_id":1001,"user_id":42,"calendar_event_type_id":1,"customer_note_id":5005,"start_time":1767290400,"duration":7200,"notes":"Bring the replacement panel"}}}'
calendar_category defaults to tech_schedule.
| Field | Required | Notes |
|---|---|---|
customer_id |
Yes | The customer. Leads are rejected for tech appointments — leads use the sales scheduler. |
calendar_event_type_id |
Yes | An active tech service type from calendar event types (install / service / removal as configured by your company). Time Off cannot be scheduled through the API. |
start_time |
Yes | Unix timestamp or a date-time string (e.g. 2026-08-03 10:00:00). |
duration |
Yes | Seconds; must be a multiple of 1800 (30-minute increments) and may not run past 11:30 PM. |
user_id |
No | The tech to assign; the user must hold the tech role. Omit to leave unassigned. |
calendar_event_detail_type_id |
No | Service detail type (must belong to the tech scheduler). |
customer_note_id |
No* | The work order to link — must be a work order belonging to this customer. *Required when the company setting "appointments require a work order" is on. |
notes, user_notes |
No | Appointment notes / notes for the assigned tech. |
Scheduling a sales appointment
Pass calendar_category: sale_schedule:
curl -X POST "https://portal.securitytrax.com/acme/user/v4/calendar_events/schedule" \
-H "Authorization: Bearer stx_acme_..." \
-H "Content-Type: application/json" \
-d '{"data":{"attributes":{"calendar_category":"sale_schedule","customer_id":1001,"user_id":42,"calendar_event_type_id":1,"start_time":1767290400,"duration":3600}}}'
| Field | Required | Notes |
|---|---|---|
customer_id |
Yes | A lead always, or a customer when your company shows the sales scheduler on customers. |
calendar_event_type_id |
Yes | An active sale visit type from calendar event types. |
start_time, duration |
Yes | Same rules as tech appointments. |
user_id |
No | The sales rep to assign. With only Assign Sale Schedule To Self, this must be your own user id (and an unassigned appointment is not allowed). |
notes, user_notes |
No | Appointment notes / notes for the assigned rep. |
Sales appointments cannot link a work order (customer_note_id is rejected).
Scheduling through the API runs the same platform side effects as scheduling in SecurityTrax (appointment notifications, remote system sync where configured).
Note.
scheduleandrescheduleperform no double-booking check — like the other integration surfaces, they will happily schedule someone into an occupied window. Useavailable_userswith a time window (orGET /calendar_events?user_id=42&start_from=...&start_to=...) if overlap matters to your flow.
Rescheduling
curl -X POST "https://portal.securitytrax.com/acme/user/v4/calendar_events/reschedule" \
-H "Authorization: Bearer stx_acme_..." \
-H "Content-Type: application/json" \
-d '{"data":{"attributes":{"id":314,"start_time":1767376800,"initiated_by":"c","reschedule_reason_id":2}}}'
Works for both tech and sales appointments (the id resolves the kind). start_time is required (unix
timestamp or date-time string); pass duration only to change the length. initiated_by (c =
customer, d = dealer/company) is optional — when provided and the company tracks reschedules, a
reschedule audit record (old time, new time, reason, who initiated) is written alongside the move,
exactly as the SecurityTrax scheduler does.
Canceling
curl -X POST "https://portal.securitytrax.com/acme/user/v4/calendar_events/cancel" \
-H "Authorization: Bearer stx_acme_..." \
-H "Content-Type: application/json" \
-d '{"data":{"attributes":{"id":314}}}'
Deletes the appointment from the schedule. A linked work order is not closed or unlinked — it simply no longer has this appointment.
Listing appointments
Filters: customer_id, user_id (the assigned tech/rep), calendar_category
(tech_schedule / sale_schedule), calendar_event_type_id, customer_note_id (work order), and
a start-time window via start_from / start_to (unix timestamps). Sort by id, customer_id,
user_id, start_time, duration, or created_at (default start_time, newest first);
?count_only=true for the total.
# A tech's appointments for one day
curl "https://portal.securitytrax.com/acme/user/v4/calendar_events?user_id=42&start_from=1767250800&start_to=1767337199" \
-H "Authorization: Bearer stx_acme_..."
Who can take the job
POST /calendar_events/available_users returns the techs or sales reps who can be assigned for
this customer (the same lists the SecurityTrax scheduler uses), as a slim { user_id, name, fname, lname, eligible } row — not a full users resource. name is Last, First (the same as a
user's display name in SecurityTrax). This is a query, not a create: HTTP 200, and the
payload is in data (same envelope as a fetch, not meta.data).
# Eligible techs at the customer's office location
curl -X POST "https://portal.securitytrax.com/acme/user/v4/calendar_events/available_users" \
-H "Authorization: Bearer stx_acme_..." \
-H "Content-Type: application/json" \
-d '{"data":{"attributes":{"customer_id":1001}}}'
{
"ok": true,
"data": {
"users": [
{"user_id": 42, "name": "Doe, Jane", "fname": "Jane", "lname": "Doe", "eligible": true}
],
"list_mode": "location"
},
"summary": null,
"breadcrumbs": [],
"meta": {"surface": "api", "action": "available_users"},
"errors": []
}
# Specific users, with occupancy for a day
curl -X POST "https://portal.securitytrax.com/acme/user/v4/calendar_events/available_users" \
-H "Authorization: Bearer stx_acme_..." \
-H "Content-Type: application/json" \
-d '{"data":{"attributes":{"customer_id":1001,"user_ids":[42,87],"list_mode":"in_coverage_area","start_from":1767250800,"start_to":1767337199}}}'
| Field | Required | Notes |
|---|---|---|
customer_id |
Yes | The customer. For sales (sale_schedule), a lead always, or a customer when your company shows the sales scheduler on customers. A customer you cannot see is 404. |
calendar_category |
No | tech_schedule (default) or sale_schedule. Tech + lead is rejected. |
user_ids |
No | Array of user ids. Omit or send [] for the full eligible list. When present, only those ids are returned, each with eligible true/false. Unknown / deleted ids are 422. Max 100. |
list_mode |
No | Tech only. location, in_coverage_area, or out_coverage_area (v3 aliases all / coverage_radius / max_coverage_radius also accepted). Omit for the company default. Coverage modes require coverage-area views and a geocoded customer — otherwise 422 (not silently coerced). Ignored for sales. |
start_from + start_to |
No | Occupancy window (unix or date-time string). Both required if either is sent. Max 31 days. |
start_time + duration |
No | Alternative occupancy window: start (unix or date-time string) plus length in seconds. Both required if either is sent. Same 31-day cap. |
When a window is sent, each user also has busy, includes_time_off, and conflicts (id, category,
start, duration, type — no customer_id). Occupancy looks at the assignee's whole calendar,
including time off. No window → those keys are omitted. The response echoes the resolved list_mode
(tech only) and the window when one was sent. Timestamps out are unix ints.
Permissions match scheduling: Scheduler create at the customer for tech; Sale Scheduler or
Assign Sale Schedule To Self for sales. With only self-assign, omitted user_ids returns you
alone; a foreign id is 422.
Open time slots (time-slot scheduling only)
POST /calendar_events/available_slots lists the company's configured tech windows that are
open for this customer — the same capacity math as one page of the slot picker. A
start_from + start_to window is required and may not be longer than the company's
days-to-show-per-page setting (hard-capped at 31). Companies that do not use time-slot scheduling
get 422 slot_scheduler_not_enabled (this does not invent 30-minute slots). Sales appointments
do not use slots.
curl -X POST "https://portal.securitytrax.com/acme/user/v4/calendar_events/available_slots" \
-H "Authorization: Bearer stx_acme_..." \
-H "Content-Type: application/json" \
-d '{"data":{"attributes":{"customer_id":1001,"start_from":1767250800,"start_to":1767855600}}}'
| Field | Required | Notes |
|---|---|---|
customer_id |
Yes | A geocoded customer (not a lead). Hidden → 404; ungeocoded → 422. |
calendar_category |
No | Must be tech_schedule (default). sale_schedule is 422. |
include_unavailable |
No | Default false (available-only). true also returns configured slots that are already full, each with available: false. |
start_from + start_to |
Yes | Slice of the bookable horizon (unix or date-time string). End must be after start. May not exceed this company's days to show per page (total_days_to_show, hard-capped at 31). Capacity is checked only for configured windows that overlap this range. Days past the booking horizon still return no slots. The response echoes the window. |
Response data is { slots: [{ start_time, end_time, available }] } (HTTP 200, same query
envelope as available_users). Same-day-disabled windows and days past the booking horizon are
omitted entirely. Needs Scheduler create at the customer.
Fields
Fields are grouped by the permission that gates them. A group you may not view
is absent from responses; a group you may not write is rejected with 422 when
sent in a write.
Tech Appointment
View: customers — the standard canViewTechSchedule rules; leads — Lead Scheduler. Create: customers — Scheduler at the record's location. Update: customers — Scheduler at the record's location.
| Field | Type | Writable | Validation |
|---|---|---|---|
calendar_category |
string (nullable) | Create only | one of: tech_schedule, sale_schedule |
user_id |
integer (nullable) | Create, update | min 0 |
customer_note_id |
integer (nullable) | Create, update | min 0 |
customer_id |
integer (nullable) | Create only | min 0 |
calendar_event_type_id |
integer (nullable) | Create, update | min 0 |
calendar_event_detail_type_id |
integer (nullable) | Create, update | min 0 |
recurring_id |
integer (nullable) | Create, update | min 0 |
start_time |
string (nullable) | Create, update | min length 0 |
duration |
integer (nullable) | Create, update | min 0 |
notes |
string (nullable) | Create, update | — |
user_notes |
string (nullable) | Create, update | — |
remote_unique_id |
string (nullable) | Create, update | max length 255 |
work_order_number |
string (nullable) | Create, update | max length 255 |
dry_run_reason_id |
integer (nullable) | Create, update | min 0 |
interactive_services_id |
integer (nullable) | Create, update | max 255 |
status |
string (nullable) | Read-only | one of: active, deleted |
created_at |
string (nullable) | Read-only | — |
created_by |
integer (nullable) | Read-only | — |
updated_at |
string (nullable) | Read-only | — |
updated_by |
integer (nullable) | Read-only | — |
Sales Appointment
View: any of: Sale Scheduler; Assign Sale Schedule To Self. Create: any of: Sale Scheduler; Assign Sale Schedule To Self. Update: the standard canModifySaleSchedulerAppointment rules.
| Field | Type | Writable | Validation |
|---|---|---|---|
calendar_category |
string (nullable) | Create only | one of: tech_schedule, sale_schedule |
user_id |
integer (nullable) | Create, update | min 0 |
customer_note_id |
integer (nullable) | Create, update | min 0 |
customer_id |
integer (nullable) | Create only | min 0 |
calendar_event_type_id |
integer (nullable) | Create, update | min 0 |
calendar_event_detail_type_id |
integer (nullable) | Create, update | min 0 |
recurring_id |
integer (nullable) | Create, update | min 0 |
start_time |
string (nullable) | Create, update | min length 0 |
duration |
integer (nullable) | Create, update | min 0 |
notes |
string (nullable) | Create, update | — |
user_notes |
string (nullable) | Create, update | — |
remote_unique_id |
string (nullable) | Create, update | max length 255 |
work_order_number |
string (nullable) | Create, update | max length 255 |
dry_run_reason_id |
integer (nullable) | Create, update | min 0 |
interactive_services_id |
integer (nullable) | Create, update | max 255 |
status |
string (nullable) | Read-only | one of: active, deleted |
created_at |
string (nullable) | Read-only | — |
created_by |
integer (nullable) | Read-only | — |
updated_at |
string (nullable) | Read-only | — |
updated_by |
integer (nullable) | Read-only | — |
Pagination
The list endpoint uses client-controlled offset pagination: ?page= (1-based) and
?per_page= (default 25, max 100). The response mirrors
meta.pagination (page, per_page, total, last_page) and sends an RFC5988
Link header; follow rel="next" to walk pages. See
Pagination.
Filters
The list endpoint accepts these query-param filters: customer_id, user_id, calendar_category, calendar_event_type_id, customer_note_id, start_from, start_to.
An unsupported filter parameter returns 422. See
Filtering collections
for matching semantics.
Sorting
GET .../calendar_events?sort= orders the list by: id, customer_id, user_id, start_time, duration, created_at.
Prefix a field with - for descending; comma-separate for tie-breakers. An
unsupported field returns 422. See
Sorting collections.
Related
- Requests and Responses — envelope, errors, pagination, and rate limits.
- Authentication — API access tokens and the
Authorizationheader.