Skip to content

Examples

Practical, copy-paste examples for the tasks most integrations need first. Every call goes to the fixed Calendar host and carries your authentication key as a bearer token:

Terminal window
export OPTIWORK_KEY="opk_xxxxxxxxxxxxxxxxxxxxxxxx"
export CALENDAR_API="https://calendar.work.optidata.cloud/api"

Get the key in Optiwork at work.optidata.cloud/user/settings/security (My account > Profile > Security). Keys are short-lived — when a call returns 401, fetch a fresh one from that page and retry.

The examples use calendar 10, the primary calendar of a standard account. Read the real ids from GET /calendars rather than hard-coding them. Ids, names and addresses below are fictional; responses are trimmed to the fields each example is about.

Not every calendar you can see is one you can schedule in — a calendar shared with you at viewer cannot take new events. Ask for the writable ones:

Terminal window
curl --request GET \
--url "$CALENDAR_API/calendars/selectable" \
--header "Authorization: Bearer $OPTIWORK_KEY" \
--header "Accept: application/json"
{
"calendars": [
{ "id": "10", "text": "Calendar" },
{ "id": "454", "text": "Team planning" }
]
}

Reference: GET /calendars/selectable · GET /calendars

A one-hour meeting with one guest and a reminder fifteen minutes before. organizer is the address the invitation is sent from, and each attendee is identified by their e-mail address.

Terminal window
curl --request POST \
--url "$CALENDAR_API/events" \
--header "Authorization: Bearer $OPTIWORK_KEY" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '{
"calendar_ids": ["10"],
"title": "Sprint review",
"datetime_start": "2026-08-24 14:00:00",
"datetime_end": "2026-08-24 15:00:00",
"is_all_day": false,
"location": "Meeting room 2",
"organizer": "jane.doe@acme.example",
"attendees": [
{ "email": "john.smith@acme.example", "type": "user",
"optiworkIdentifier": "9ef34ce3-3b69-4762-b5e8-045499390ce6" }
],
"notification": { "time_unit": "minutes", "value": 15 }
}'
{
"event": {
"id": "4171",
"uid": "8f1c0a6e-3c4d-4c1a-9a2e-6a5b1d0f7c33",
"calendarId": "10",
"invId": "4171-4170",
"ms": 20130,
"rev": 20130,
"instances": [
{
"name": "Sprint review",
"start": "2026-08-24T14:00:00-03:00",
"end": "2026-08-24T15:00:00-03:00",
"isOrganizer": true,
"userParticipationStatus": "ACCEPTED"
}
]
}
}

Keep id, invId, ms and rev — every later change to the event sends them back.

To find the optiworkIdentifier of a colleague, or to invite somebody you only know by name, search the directory first with GET /search/invitables?search=john.

Reference: POST /events

What a month view asks for. Pass the window; a repeating event inside it comes back once, with one entry in instances per occurrence that falls in the window.

Terminal window
curl --request GET \
--url "$CALENDAR_API/events?datetime_start=2026-08-01%2000:00:00&datetime_end=2026-08-31%2023:59:59" \
--header "Authorization: Bearer $OPTIWORK_KEY" \
--header "Accept: application/json"
{
"events": [
{
"id": "4171",
"calendarId": "10",
"invId": "4171-4170",
"instances": [
{
"name": "Sprint review",
"start": "2026-08-24T14:00:00-03:00",
"end": "2026-08-24T15:00:00-03:00",
"isAllDay": false,
"userParticipationStatus": "ACCEPTED"
}
]
}
]
}

Without filters the search covers every calendar the caller can see. Restrict it by repeating calendar_ids[] once per calendar. Descriptions are left out of search results unless you ask for include_description=true, which costs an extra round trip — fetch the single event instead when you only need one.

Reference: GET /events · GET /events/{eventId}

Moving an event needs far less than a full update: the invitation id and the new bounds. Everything else about the event stays as it was, and the attendees are told it moved.

Terminal window
curl --request POST \
--url "$CALENDAR_API/events/4171/reschedule" \
--header "Authorization: Bearer $OPTIWORK_KEY" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '{
"inv_id": "4171-4170",
"datetime_start": "2026-08-24 15:00:00",
"datetime_end": "2026-08-24 16:00:00",
"is_all_day": false
}'

Only the organizer can reschedule, and only with write access to the calendar. Anyone else gets 403 with Only the event organizer can change this event.

To move a single occurrence of a repeating event, add its recurrence_id and post to the occurrence endpoint instead — the occurrence then becomes an exception and the rest of the series stays put:

Terminal window
curl --request POST \
--url "$CALENDAR_API/events/4180/exceptions/reschedule" \
--header "Authorization: Bearer $OPTIWORK_KEY" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '{
"inv_id": "4180-4179",
"recurrence_id": "2026-09-15T16:00:00-03:00",
"datetime_start": "2026-09-15 18:00:00",
"datetime_end": "2026-09-15 19:00:00"
}'

Reference: POST /events/{eventId}/reschedule · POST /events/{eventId}/exceptions/reschedule

Renaming, moving rooms or editing the guest list goes through the update endpoint, which rewrites the record — read the event, change what you need and send the whole thing back, including the id / inv_id / ms / rev you read.

Terminal window
curl --request PATCH \
--url "$CALENDAR_API/events/4171" \
--header "Authorization: Bearer $OPTIWORK_KEY" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '{
"id": "4171",
"inv_id": "4171-4170",
"ms": 20130,
"rev": 20130,
"calendar_id": "10",
"title": "Sprint review — August",
"datetime_start": "2026-08-24 14:00:00",
"datetime_end": "2026-08-24 15:00:00",
"is_all_day": false,
"organizer": "jane.doe@acme.example",
"attendees_to_add": [
{ "email": "sam.lee@acme.example", "type": "user",
"optiworkIdentifier": "9f0021bb-5c2f-4a71-9c0e-6f2ad4c1e777" }
]
}'

Guests are a delta: attendees_to_add invites, attendees_to_remove uninvites, and should_notify_all_attendees: false makes the change quietly for the people already on the event.

Reference: PATCH /events/{eventId}

Three endpoints, one shape. calendar_id says which of your calendars the invitation landed in — it is required.

Terminal window
curl --request POST \
--url "$CALENDAR_API/events/4167/invitations/accept" \
--header "Authorization: Bearer $OPTIWORK_KEY" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '{ "calendar_id": 10 }'
{
"event": {
"id": "4167",
"instances": [
{ "name": "Sprint review", "userParticipationStatus": "ACCEPTED" }
]
}
}

Swap the last path segment for tentative to answer maybe, or decline to answer no. For a repeating event, add the occurrence’s recurrence_id to answer for that occurrence only; leaving it out answers for the whole series.

If the calendar is one shared with you and your role there is manager or admin, the reply is recorded on behalf of the calendar’s owner — that is how a delegate answers for the person whose calendar they manage.

Reference: accept · tentative · decline

Deleting an event cancels it and tells the attendees.

Terminal window
curl --request DELETE \
--url "$CALENDAR_API/events/4171" \
--header "Authorization: Bearer $OPTIWORK_KEY" \
--header "Accept: application/json"

For a repeating event, say how much to cancel. Dropping one occurrence:

Terminal window
curl --request DELETE \
--url "$CALENDAR_API/events/4180" \
--header "Authorization: Bearer $OPTIWORK_KEY" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '{
"recurrence_id": "2026-09-15T16:00:00-03:00",
"recurrence_scope": "occurrence"
}'

Sending "recurrence_scope": "series", or no body at all, cancels every occurrence.

Reference: DELETE /events/{eventId}

Status Shape When
401 {"error": "…"} Missing, malformed or expired authentication key
403 {"error": "…", "status": 403, "help": "…"} Not the organizer, or not enough access to the calendar
422 {"message": "…", "errors": { … }} Validation failed; errors is keyed by field
422 {"error": "…", "status": 422, "help": ""} The calendar or event could not be read
429 {"message": "Too Many Attempts."} Rate limit exceeded — retry after the window resets

Note the two 422s. A malformed request answers with message and a per-field errors map; a request that was well-formed but could not be carried out — an event id that does not resolve, for instance — answers with the error / status / help envelope instead. An unknown event is reported this way rather than as a 404.