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:
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.
Find a calendar to write to
Section titled “Find a calendar to write to”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:
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
Create an event
Section titled “Create an event”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.
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
List the events of a period
Section titled “List the events of a period”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.
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}
Reschedule an event
Section titled “Reschedule an event”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.
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:
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
Change anything else about an event
Section titled “Change anything else about an event”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.
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}
Answer an invitation
Section titled “Answer an invitation”Three endpoints, one shape. calendar_id says which of your calendars the
invitation landed in — it is required.
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
Cancel an event
Section titled “Cancel an event”Deleting an event cancels it and tells the attendees.
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:
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}
Errors you should expect
Section titled “Errors you should expect”| 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.