Custom calendar
A custom app can offer the Calendar capability. Unlike actions, a calendar does not accept any shape: Opami defines eight operations and one event format, and you choose the URLs. In Opami, enter a method and an HTTPS URL template for each operation, on the host registered for the app. The templates take placeholders, filled URL-encoded at call time:
| Operation | Placeholders | Methods | Needed for |
|---|---|---|---|
calendars.list | none | GET | read |
freebusy.query | none | POST | read |
events.list | {calendar_id} {from} {to} | GET | read |
events.get | {calendar_id} {event_id} | GET | read |
events.create | {calendar_id} | POST, PUT | write |
events.update | {calendar_id} {event_id} | PATCH, PUT, POST | write |
events.cancel | {calendar_id} {event_id} | POST, DELETE, PATCH | write |
events.respond | {calendar_id} {event_id} | POST, PUT, PATCH | optional |
Each URL must contain exactly its placeholders, each once, in the path or query (never the host). Opami calls them through its locked-down executor with the app's credential; see Rules for your endpoints.
The event format
Every event is exchanged in this format. Fields outside it are ignored. start and end carry a time zone offset. updated_at must change with every change: Opami uses it to detect a stale copy.
{
"id": "evt_123",
"calendar_id": "team",
"title": "Call with Ana",
"start": "2026-11-03T14:00:00+01:00",
"end": "2026-11-03T14:30:00+01:00",
"all_day": false,
"status": "confirmed",
"attendees": [
{
"email": "ana@example.com",
"name": "Ana Silva",
"response": "accepted"
}
],
"location": null,
"description": null,
"updated_at": "2026-10-27T09:12:44Z"
}
Free/busy answers busy intervals only:
{ "calendars": { "team": [{ "start": "2026-11-03T13:00:00Z", "end": "2026-11-03T13:30:00Z" }] } }
events.get for an unknown event must answer 404, not a made-up event.
How Opami reads your statuses
| Your status | Opami treats it as |
|---|---|
401 | reauth |
403 | reauth |
404 | not_found |
409 | precondition_failed |
410 | gone |
412 | precondition_failed |
429 | unavailable (retried) |
5xx | unavailable (retried) |
| any other non-2xx, or a 2xx not in the format | invalid |
Telling Opami about changes
Push changed events to POST /v1/sources/{id}/calendar-events with the same API key as an inbox source:
curl -X POST https://app.opami.ai/v1/sources/$OPAMI_SOURCE_ID/calendar-events \
-H "Authorization: Bearer $OPAMI_API_KEY" -H "Content-Type: application/json" \
-d '{"events":[{"id":"evt_123","calendar_id":"team","title":"Call","start":"2026-11-03T14:00:00+01:00","end":"2026-11-03T14:30:00+01:00","status":"confirmed","updated_at":"2026-10-27T09:12:44Z"}]}'
If your app cannot push, Opami polls events.list on its regular schedule instead. A push is accepted only once the calendar has passed the conformance test (409 calendar_not_usable before).