Skip to content

Create an event

POST
/events
curl --request POST \
--url https://calendar.work.optidata.cloud/api/events \
--header 'Authorization: Bearer <token>' \
--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", "description": "Demo of the August milestone.", "organizer": "jane.doe@acme.example", "attendees": [ { "email": "john.smith@acme.example", "type": "user", "optiworkIdentifier": "9ef34ce3-3b69-4762-b5e8-045499390ce6" }, { "email": "contractor@partner.example", "type": "guest", "optiworkIdentifier": null } ], "notification": { "time_unit": "minutes", "value": 15 } }'

Creates an event and invites its attendees.

calendar_ids accepts more than one calendar. The event is written to all of them in a single operation, which is how a room or another shared resource is booked alongside the organizer’s own calendar. Invitations are sent only for the first calendar in the list, so put the organizer’s calendar first. The response returns that first event as event and the whole batch as events.

Reminders are expressed in notification, and the two allowed shapes depend on is_all_day:

  • timed event (is_all_day: false) — time_unit plus value, meaning “this long before the start”;
  • all-day event (is_all_day: true) — days_before plus trigger_hour, meaning “at this hour of the day, this many days before”.

Omit notification entirely for an event with no reminder.

Media typeapplication/json
object
title
required
string
datetime_start
required

When the event starts. Interpreted in the caller’s display time zone unless it carries an offset.

string
datetime_end
required

When the event ends. Must not be before datetime_start.

string
is_all_day
required

true for an event that spans whole days; send midnight-to-midnight bounds.

boolean
description
string | null
location
string | null
video_conference_url

Meeting room to attach to the event.

string | null format: uri
organizer
required

Address of the organizer.

string format: email
notification
One of:

A single reminder. Send time_unit and value for a timed event, or days_before and trigger_hour for an all-day one. Omit the object entirely for an event with no reminder.

object
time_unit

Unit of the lead time, for a timed event.

string
Allowed values: minutes hours days weeks
value

How many units before the start to fire the reminder.

integer
days_before

How many days before the event to fire it, for an all-day event.

integer
trigger_hour

Hour of the day the reminder fires, for an all-day event.

integer
<= 23
locale

Language for the invitation e-mails, e.g. pt_BR or en.

string | null
calendar_ids
required

Calendars to create the event in. Invitations are sent only for the first one, so put the organizer’s calendar first. The legacy single-value calendar_id is still accepted and is treated as a one-element list.

Array<string>
>= 1 items
attendees

People to invite.

Array<object>

Somebody to invite.

object
email
required
string format: email
name

Display name of the attendee. Mainly useful for somebody outside the tenant, who has no directory entry to take a name from.

string | null
type
One of:

user is somebody with an Optiwork account in the tenant, group a user group, and guest an address outside it.

string
Allowed values: user guest group
optiworkIdentifier

Optiwork identifier of the attendee, from GET /search/invitables. null for somebody outside the tenant.

string | null
recurrence_rule
One of:

How an event repeats, in iCalendar terms. frequency is the only required part; count and until are two ways of ending the series, and the by_* fields narrow which occurrences the frequency produces.

When a by_* field is omitted, it is inferred from datetime_start — a weekly series repeats on the weekday it starts on, a monthly one on the same day of the month.

object
frequency

How often the event repeats.

string
Allowed values: DAILY WEEKLY MONTHLY YEARLY
interval

Repeat every N periods: 2 with WEEKLY means fortnightly.

integer | null
>= 1 <= 99
count

End the series after this many occurrences.

integer | null
>= 1 <= 999
until

End the series at this moment.

string | null
by_day

Weekdays the event falls on.

Array<string> | null
Allowed values: SU MO TU WE TH FR SA
by_month_day

Days of the month, 1–31.

Array<integer> | null
by_month

Months, 1–12.

Array<integer> | null
by_set_pos

Which occurrence within the period: 1–4 for first through fourth, -1 for last. Combine with by_day for “the last Friday of the month”.

Array<integer> | null
Allowed values: 1 2 3 4 -1
Examples

A one-hour meeting with two guests

{
"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",
"description": "Demo of the August milestone.",
"organizer": "jane.doe@acme.example",
"attendees": [
{
"email": "john.smith@acme.example",
"type": "user",
"optiworkIdentifier": "9ef34ce3-3b69-4762-b5e8-045499390ce6"
},
{
"email": "contractor@partner.example",
"type": "guest",
"optiworkIdentifier": null
}
],
"notification": {
"time_unit": "minutes",
"value": 15
}
}

The created event. event is the copy in the first calendar — the one invitations were sent for — and events holds every copy created.

Media typeapplication/json
object
event
required

An event and its occurrences. The event itself carries the identifiers; everything about the schedule lives in instances.

object
id
string | null
uid

ICalendar UID. Unlike id, it survives the event being copied between calendars.

string | null
calendarId
required

Calendar the event lives in.

string
originalCalendarId
string | null
invId

Invitation id. Part of the concurrency triple you send back on an update.

string | null
ms

Modification sequence. Send it back unchanged on an update.

integer | null
rev

Revision. Send it back unchanged on an update.

integer | null
nextAlarm
One of:

The next reminder due for the event.

object
nextAlarmAt

When the reminder fires.

string | null
alarmInstStart

Start of the occurrence it belongs to.

string | null
invId
integer | null
instances
required
Array<object>

One occurrence of an event. A one-off event has exactly one; a recurring event has one per occurrence inside the window you asked for.

object
id
string | null
invId

Invitation id. Send it back when updating or rescheduling.

string | null
status
required

CONF confirmed, TENT tentative, CANC cancelled, NEED awaiting an answer.

string
Allowed values: CONF TENT CANC NEED
recurrenceId

Start of this occurrence, for a recurring event.

string | null
name

Title of the event.

string | null
location
string | null
start
required
string
end
required
string
isOrganizer
required

true when the caller owns the event.

boolean
isRecurring
required
boolean
isException
required

true when this occurrence was detached from its series.

boolean
isAllDay
required
boolean
videoConferenceUrl
string | null
description

Filled in on a single-event read; on a list, only when include_description was requested.

string | null
organizer
One of:

Who owns the event.

object
email
string | null
name
string | null
type
One of:

user is somebody with an Optiwork account in the tenant, group a user group, and guest an address outside it.

string
Allowed values: user guest group
avatarUrl
string | null
sentBy

Set when somebody sent the invitation for the organizer.

string | null
recurrenceRule
One of:

The repetition rule as stored. Mirrors the rule you send, in camelCase.

object
frequency
string
Allowed values: DAILY WEEKLY MONTHLY YEARLY
until
string | null
count
integer | null
interval
integer | null
byDay
Array<string> | null
byMonthDay
Array<integer> | null
byMonth
Array<integer> | null
bySetPos
Array<integer> | null
userParticipationStatus
One of:

How an attendee answered. NEEDS-ACTION means they have not answered yet.

string
Allowed values: NEEDS-ACTION ACCEPTED DECLINED TENTATIVE
originalTimezone

Time zone the event was created in, when it differs.

string | null
recurrenceBaseStart
string | null
recurrenceBaseEnd
string | null
attendees
required
Array<object>

Somebody invited to the event.

object
email
string | null
name
string | null
type
One of:

user is somebody with an Optiwork account in the tenant, group a user group, and guest an address outside it.

string
Allowed values: user guest group
avatarUrl
string | null
participationStatus
One of:

How an attendee answered. NEEDS-ACTION means they have not answered yet.

string
Allowed values: NEEDS-ACTION ACCEPTED DECLINED TENTATIVE
optiworkIdentifier
string | null
alarms
required
Array<object>

A reminder, expressed as an offset from the event. isNegative: true means the reminder fires before the moment named by related; the unit fields that do not apply are 0 or null.

object
description
string | null
related

Whether the offset is measured from the start or the end.

string | null
Allowed values: START END
isNegative
boolean
weeks
integer | null
days
integer | null
hours
integer | null
minutes
integer | null
seconds
integer | null
locale
string | null
participantsSummary

Who takes part in one occurrence, summarized for an event card. Present only on a list (GET /events) called with include_participants_summary=true; absent otherwise.

The participants are listed as the event details show them: the organizer first, when the occurrence has one, then the attendees in invitation order. The organizer is not looked up among the attendees, so an organizer who is also listed as an attendee counts twice. An exception occurrence is summarized from its own participants.

Even with the flag on, an occurrence can lack participantsSummary when its participants could not be read, for instance when the event was deleted while the list was being built. Read a missing participantsSummary as unknown, not as zero participants.

object
participantsCount
required

Number of participants of the occurrence: the organizer, when there is one, plus every attendee. 0 when it has none.

integer
participants
required

The first three participants, in the order above. Empty when the occurrence has none.

Array<object>
<= 3 items
object
name
required

The participant’s name or, when it has none, their email address. null when both are missing.

string | null
avatarUrl
required

The participant’s avatar, or null when there is none.

string | null
events
required
Array<object>

An event and its occurrences. The event itself carries the identifiers; everything about the schedule lives in instances.

object
id
string | null
uid

ICalendar UID. Unlike id, it survives the event being copied between calendars.

string | null
calendarId
required

Calendar the event lives in.

string
originalCalendarId
string | null
invId

Invitation id. Part of the concurrency triple you send back on an update.

string | null
ms

Modification sequence. Send it back unchanged on an update.

integer | null
rev

Revision. Send it back unchanged on an update.

integer | null
nextAlarm
One of:

The next reminder due for the event.

object
nextAlarmAt

When the reminder fires.

string | null
alarmInstStart

Start of the occurrence it belongs to.

string | null
invId
integer | null
instances
required
Array<object>

One occurrence of an event. A one-off event has exactly one; a recurring event has one per occurrence inside the window you asked for.

object
id
string | null
invId

Invitation id. Send it back when updating or rescheduling.

string | null
status
required

CONF confirmed, TENT tentative, CANC cancelled, NEED awaiting an answer.

string
Allowed values: CONF TENT CANC NEED
recurrenceId

Start of this occurrence, for a recurring event.

string | null
name

Title of the event.

string | null
location
string | null
start
required
string
end
required
string
isOrganizer
required

true when the caller owns the event.

boolean
isRecurring
required
boolean
isException
required

true when this occurrence was detached from its series.

boolean
isAllDay
required
boolean
videoConferenceUrl
string | null
description

Filled in on a single-event read; on a list, only when include_description was requested.

string | null
organizer
One of:

Who owns the event.

object
email
string | null
name
string | null
type
One of:

user is somebody with an Optiwork account in the tenant, group a user group, and guest an address outside it.

string
Allowed values: user guest group
avatarUrl
string | null
sentBy

Set when somebody sent the invitation for the organizer.

string | null
recurrenceRule
One of:

The repetition rule as stored. Mirrors the rule you send, in camelCase.

object
frequency
string
Allowed values: DAILY WEEKLY MONTHLY YEARLY
until
string | null
count
integer | null
interval
integer | null
byDay
Array<string> | null
byMonthDay
Array<integer> | null
byMonth
Array<integer> | null
bySetPos
Array<integer> | null
userParticipationStatus
One of:

How an attendee answered. NEEDS-ACTION means they have not answered yet.

string
Allowed values: NEEDS-ACTION ACCEPTED DECLINED TENTATIVE
originalTimezone

Time zone the event was created in, when it differs.

string | null
recurrenceBaseStart
string | null
recurrenceBaseEnd
string | null
attendees
required
Array<object>

Somebody invited to the event.

object
email
string | null
name
string | null
type
One of:

user is somebody with an Optiwork account in the tenant, group a user group, and guest an address outside it.

string
Allowed values: user guest group
avatarUrl
string | null
participationStatus
One of:

How an attendee answered. NEEDS-ACTION means they have not answered yet.

string
Allowed values: NEEDS-ACTION ACCEPTED DECLINED TENTATIVE
optiworkIdentifier
string | null
alarms
required
Array<object>

A reminder, expressed as an offset from the event. isNegative: true means the reminder fires before the moment named by related; the unit fields that do not apply are 0 or null.

object
description
string | null
related

Whether the offset is measured from the start or the end.

string | null
Allowed values: START END
isNegative
boolean
weeks
integer | null
days
integer | null
hours
integer | null
minutes
integer | null
seconds
integer | null
locale
string | null
participantsSummary

Who takes part in one occurrence, summarized for an event card. Present only on a list (GET /events) called with include_participants_summary=true; absent otherwise.

The participants are listed as the event details show them: the organizer first, when the occurrence has one, then the attendees in invitation order. The organizer is not looked up among the attendees, so an organizer who is also listed as an attendee counts twice. An exception occurrence is summarized from its own participants.

Even with the flag on, an occurrence can lack participantsSummary when its participants could not be read, for instance when the event was deleted while the list was being built. Read a missing participantsSummary as unknown, not as zero participants.

object
participantsCount
required

Number of participants of the occurrence: the organizer, when there is one, plus every attendee. 0 when it has none.

integer
participants
required

The first three participants, in the order above. Empty when the occurrence has none.

Array<object>
<= 3 items
object
name
required

The participant’s name or, when it has none, their email address. null when both are missing.

string | null
avatarUrl
required

The participant’s avatar, or null when there is none.

string | null
Examples
Examplecreated
{
"event": {
"id": "4171",
"uid": "8f1c0a6e-3c4d-4c1a-9a2e-6a5b1d0f7c33",
"calendarId": "10",
"invId": "4171-4170",
"ms": 20130,
"rev": 20130,
"nextAlarm": null,
"instances": [
{
"id": "4171",
"invId": "4171-4170",
"status": "CONF",
"name": "Sprint review",
"start": "2026-08-24T14:00:00-03:00",
"end": "2026-08-24T15:00:00-03:00",
"isOrganizer": true,
"isRecurring": false,
"isException": false,
"isAllDay": false,
"userParticipationStatus": "ACCEPTED",
"attendees": [],
"alarms": []
}
]
},
"events": [
{
"id": "4171",
"calendarId": "10",
"invId": "4171-4170",
"instances": [
{
"id": "4171",
"status": "CONF",
"name": "Sprint review",
"start": "2026-08-24T14:00:00-03:00",
"end": "2026-08-24T15:00:00-03:00",
"isOrganizer": true,
"isRecurring": false,
"isException": false,
"isAllDay": false,
"attendees": [],
"alarms": []
}
]
}
]
}

Missing, malformed or expired authentication key.

Media typeapplication/json

Authentication failures use an error key rather than message.

object
error
required
string
Examples
Examplemissing

No bearer token sent

{
"error": "JWT passport exception: Missing bearer token in request"
}

The request body or query string failed validation.

Media typeapplication/json
object
message
required

The first validation message.

string
errors
required

Validation messages keyed by field name.

object
key
additional properties
Array<string>
Examples
Exampleinvalid
{
"message": "The event title field is required.",
"errors": {
"title": [
"The event title field is required."
]
}
}

Rate limit exceeded. Retry after the window resets.

Media typeapplication/json

Generic error envelope used by policy, binding and framework failures.

object
message
required

Human-readable description of the failure.

string
Examples
Examplethrottled
{
"message": "Too Many Attempts."
}