---
title: "Create a calendar event (free on every tier; daily-create cap)"
method: POST
path: "/api/calendar-events"
tags: ["Calendar"]
---

# Create a calendar event (free on every tier; daily-create cap)

`POST /api/calendar-events`

## Request

- Base URL: `https://www.oxfordledge.com` (the document declares no server; this is the origin it was published from)
- URL: `https://www.oxfordledge.com/api/calendar-events`
- Auth: none declared

Create a calendar event. No tier gate (free). One gate: a
rolling-24h create cap enforced inside the helper → 429.

Body (JSON):
  {
    "title": "Quarterly portfolio review",   # required, <=120
    "event_date": "2026-06-30",              # required, ISO YYYY-MM-DD
    "note": "rebalance + tax-loss harvest",  # optional, <=2000
    "ticker": "NVDA",                        # optional, <=16
    "event_time": "09:30",                   # optional HH:MM[:SS]
    "recurrence": "monthly",                 # optional none|daily|weekly|monthly|yearly
    "recurrence_until": "2027-06-30",        # optional ISO
    "remind_offset_minutes": 1440            # optional 0..10080
  }

## Response `200`

Successful Response

- CalendarEventCreateResponse: `POST /api/calendar-events` + `PATCH /api/calendar-events/{id}` response. Frontend reads `data.event` to add/replace the row in the calendar without a re-fetch.
  - `event` UserCalendarEvent, required: A single user_calendar_events (v86) row. Required fields are populated by every helper code path (id, user_id, title, event_date, recurrence, status, created_at, updated_at). Nullable schema columns (note, ticker, event_time, recurrence_until, remind_offset_minutes, last_reminded_for) are Optional. Dates/times are ISO strings (DATE -> 'YYYY-MM-DD', TIME -> 'HH:MM:SS', TIMESTAMPTZ -> ISO-8601) — the helper's _row_to_dict coerces them so the wire shape stays JSON-friendly.
    - `id` integer, required: Primary key.
    - `user_id` integer, required: Owning user's id.
    - `title` string, required: Event title (<=120 chars).
    - `note` string, nullable: Optional free-text note.
    - `ticker` string, nullable: Optional associated ticker (uppercased).
    - `event_date` string, required: Anchor occurrence date, ISO 'YYYY-MM-DD'.
    - `event_time` string, nullable: Optional time-of-day 'HH:MM:SS'; null = all-day.
    - `recurrence` string, required: One of none|daily|weekly|monthly|yearly.
    - `recurrence_until` string, nullable: Recurrence end date 'YYYY-MM-DD'; null = open-ended.
    - `remind_offset_minutes` integer, nullable: Minutes-before-occurrence to remind (0 = day-of, 1440 = 1 day before); null = no reminder.
    - `status` string, required: One of active|cancelled.
    - `last_reminded_for` string, nullable: Occurrence-date a reminder last fired (dedupe).
    - `created_at` string, required: ISO-8601 timestamp.
    - `updated_at` string, required: ISO-8601 timestamp.

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `429`: Too Many Requests
- `500`: Internal Server Error

---

[API](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api.md) · [All operations](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api/llms.txt) · [OpenAPI document](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api/revisions/e832a1260f0b?raw)
