---
title: "Create an event in a calendar"
method: POST
path: "/v1/calendars/{cal_id}/events"
tags: ["Events"]
---

# Create an event in a calendar

`POST /v1/calendars/{cal_id}/events`

## Request

- Base URL: `https://api.chronary.ai`
- URL: `https://api.chronary.ai/v1/calendars/{cal_id}/events`
- Auth: HTTP bearer

Create a confirmed event, a tentative event, or a temporal hold. When creating a hold (`status: "hold"`), `hold_expires_at` is required and must be between 30 seconds and 15 minutes in the future. Holds auto-release at `hold_expires_at`; promote a hold to a confirmed event with `PUT /v1/events/{id}/confirm` before expiry.

## Path parameters

- `cal_id` string, required: Calendar ID

## Request body

- CreateEvent
  - `title` string, required
  - `start_time` string, date-time, required
  - `end_time` string, date-time, required
  - `description` string
  - `all_day` boolean
  - `status` 'confirmed' | 'tentative' | 'cancelled' | 'hold'
  - `metadata` object
  - `reminders` integer[], nullable
  - `recurrence_rule` string
  - `hold_expires_at` string, date-time
  - `hold_priority` integer

## Response `201`

Event created

- Event
  - `id` string, required
  - `calendarId` string, required
  - `orgId` string, required
  - `title` string, required
  - `description` string, nullable, required
  - `startTime` string, date-time, required
  - `endTime` string, date-time, required
  - `allDay` boolean, required
  - `status` 'confirmed' | 'tentative' | 'cancelled' | 'hold', required
  - `source` 'internal' | 'external_ical', required
  - `metadata` object, required
  - `reminders` integer[], nullable, required: Reminder offsets (minutes before start). Each fires an event.reminder webhook and emits a VALARM in the iCal feed. null = inherit the calendar default (then the system default of 10 min); [] = no reminders.
  - `holdExpiresAt` string, date-time, nullable, required: When set, the event is a tentative hold that auto-expires at this timestamp. Must be 30 seconds to 15 minutes in the future when creating a hold; the cap exists to keep held slots short enough that they do not block real scheduling for long.
  - `holdPriority` integer, nullable, required: Priority for conflict resolution. Higher values pre-empt lower-priority overlapping holds.
  - `recurrenceRule` string, nullable, required: RFC 5545 RRULE subset (no "RRULE:" prefix) when this event is a recurring series master, e.g. "FREQ=WEEKLY;BYDAY=MO,WE;COUNT=12". Expansion is UTC-only. null = one-off event.
  - `recurrenceExdates` string[], required: ISO 8601 starts of individually cancelled occurrences (EXDATE).
  - `recurringEventId` string: Present only on expanded instances (expand=true): the id of the recurring series master this instance belongs to.
  - `originalStartTime` string, date-time: Present only on expanded instances: the occurrence start this instance was generated for.
  - `external_sync` object: Present only on create/update/hold mutation responses — the outcome of publishing this event to a connected human calendar (opt-in). Absent on read/list responses.
    - `status` string, required: not_configured | queued | published | skipped | failed
  - `deletedAt` string, date-time, nullable, required
  - `createdAt` string, date-time, required
  - `updatedAt` string, date-time, required

## Other responses

- `400`: Validation error — invalid request body or query parameters
- `401`: Unauthorized — missing or invalid API key
- `403`: Forbidden — quota exceeded or operation not permitted
- `404`: Resource not found
- `429`: Rate limited — 10 requests per second per key exceeded
- `500`: Internal server error

---

[API](https://skmtc.dev/chronary/apis/chronary-api.md) · [All operations](https://skmtc.dev/chronary/apis/chronary-api/llms.txt) · [OpenAPI document](https://skmtc.dev/chronary/apis/chronary-api/revisions/0d1a330294ae?raw)
