Advent Calendar API
Create digital advent calendars from your own software, fill their doors, and read how often each door was opened. Everything you can do in the editor on adventcalendarmaker.com, you can do with a few HTTP calls.
Last updated: 2026-09-29
Get an API key
API and MCP access is included in Premium ($199 per year) and Enterprise ($1,299 per year). Write to [email protected] with your account email and we send you a test key and a live key.
Overview
The Advent Calendar Maker API is a JSON REST API. You create a calendar with a date range, an opening time and a design, then put content behind each door. The calendar is live at its public link right away, and every door opens by itself at the configured time in the calendar's time zone.
Typical uses:
- Shops and agencies that run a coupon calendar for many brands every December and do not want to fill 24 doors by hand each time.
- Newsletter and CRM tools that offer an advent calendar as a campaign type.
- HR and internal communication teams that fill a team calendar from a spreadsheet.
- Developers who want an AI agent to build the calendar; for that there is the MCP server.
Base URL: https://api.adventcalendarmaker.com/v1. All requests use HTTPS, all bodies are UTF-8 JSON, all timestamps are ISO 8601.
Access and pricing
API access is part of the paid plans and costs nothing extra. There are no per-call fees.
- Premium ($199 per year): full API with all content types, custom dates, branding and statistics.
- Enterprise ($1,299 per year): additionally password protection, tracking codes and CSV export.
- Free: no API key. Personal calendars stay free in the editor on adventcalendarmaker.com.
Keys are issued by hand. Write to [email protected] from the email address of your account and tell us briefly what you are building. You usually get a test key and a live key on the same working day.
Paid plans are meant for businesses. If you build a product on top of the API that serves many end customers, tell us in your request so we can size the limits for you.
Authentication
Send your key as a bearer token in the Authorization header. Keys starting with sk_test_ work in test mode, keys starting with sk_live_ create real, public calendars.
curl https://api.adventcalendarmaker.com/v1/calendars \
-H "Authorization: Bearer $ACM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Northwind Coffee Advent Calendar",
"start_date": "2026-12-01",
"end_date": "2026-12-24",
"opening_time": "08:00",
"timezone": "Europe/Berlin",
"template_style": "cozy"
}'
Keep keys on the server. A key has the rights of the whole account, so never put it in browser code or a mobile app. If a key leaks, write to us and we revoke it within minutes and send you a new one.
Quick start
The shortest way to a working calendar: create it, fill the doors in one call, share the link.
import os, requests
API = "https://api.adventcalendarmaker.com/v1"
HEAD = {"Authorization": "Bearer " + os.environ["ACM_API_KEY"]}
cal = requests.post(f"{API}/calendars", headers=HEAD, json={
"title": "Team Advent Calendar 2026",
"start_date": "2026-12-01",
"end_date": "2026-12-24",
"opening_time": "09:00",
"timezone": "Europe/Copenhagen",
}).json()
doors = [{"day": d, "content_type": "text", "title": f"Day {d}",
"text": f"Surprise number {d} is waiting in the kitchen."}
for d in range(1, cal["door_count"] + 1)]
requests.put(f"{API}/calendars/{cal['id']}/doors", headers=HEAD,
json={"doors": doors}).raise_for_status()
print(cal["public_url"]) # share this link
The response of the first call already contains door_count (derived from the date range) and public_url. Doors that have no content yet show a friendly placeholder until you fill them.
Endpoints
| Method | Path | Purpose |
|---|---|---|
| POST | /v1/calendars | Create a calendar. |
| GET | /v1/calendars/{id} | Read one calendar. |
| GET | /v1/calendars | List your calendars, newest first. |
| PATCH | /v1/calendars/{id} | Change settings of a calendar. Only the fields you send are changed. |
| DELETE | /v1/calendars/{id} | Delete a calendar with all doors, media and statistics. |
| GET | /v1/calendars/{id}/doors | All doors of a calendar with unlock times and content. |
| PUT | /v1/calendars/{id}/doors | Write up to 50 doors in one call. |
| GET | /v1/calendars/{id}/doors/{day} | Read a single door. |
| PUT | /v1/calendars/{id}/doors/{day} | Write or replace the content of a single door. |
| POST | /v1/media | Upload an image, video, audio file or download for a door. |
| GET | /v1/backgrounds | Background designs, template styles and tags. |
| GET | /v1/calendars/{id}/stats | Views, door opens, clicks and entries. |
| GET | /v1/calendars/{id}/stats/export | Daily statistics as CSV (Enterprise). |
| GET | /v1/account | Plan, quotas and enabled features of the key. |
Create a calendar
POST /v1/calendars creates a calendar and returns it. Only title is required; everything else has sensible defaults (1 to 24 December of the coming season (after 24 December that is next year), doors open at 00:00, time zone Europe/Berlin, style classic).
import { randomUUID } from "node:crypto";
const res = await fetch("https://api.adventcalendarmaker.com/v1/calendars", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.ACM_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": randomUUID(),
},
body: JSON.stringify({
title: "Northwind Coffee Advent Calendar",
description: "A new offer behind every door until Christmas Eve.",
start_date: "2026-12-01",
end_date: "2026-12-24",
opening_time: "08:00",
timezone: "Europe/Berlin",
template_style: "corporate",
background_image: "golden-baubles",
custom_color: "#0F766E",
door_colors: { door: "#0F766E", accent: "#F5C451", number: "#FFFFFF" },
custom_logo_url: "https://cdn.northwind.example/logo.png",
animation: "stars",
metadata: { campaign: "xmas-2026" },
}),
});
const calendar = await res.json();
| Field | Type | Description |
|---|---|---|
| title | string, required | Shown above the calendar. Up to 255 characters. |
| description | string | Short text under the title. Up to 2000 characters. |
| start_date | date | Day of door 1. Default 1 December. Premium. |
| end_date | date | Day of the last door. The range may cover at most 50 days. Premium. |
| opening_time | HH:MM | Local time at which each door opens, for example 08:00. Premium. |
| timezone | string | IANA time zone name, for example Europe/London or America/New_York. Default Europe/Berlin. |
| template_style | string | One of the more than 100 door styles from /v1/backgrounds. |
| background_image | string | Key of a background design from the catalogue. |
| custom_color | #RRGGBB | Brand color as hex value. Premium. |
| door_colors | object | Own door colors on top of the chosen style: door, accent and number as hex values. null keeps the colors of the style. Premium. |
| custom_logo_url | string | HTTPS URL of your logo, shown above the doors. Premium. |
| animation | string | Background animation: none, snow, flurry (dense snow on the wind) or stars (twinkling stars). Default snow. Free on every plan. |
| snow_enabled | boolean | Older shortcut, true when animation is snow or flurry. Sending false sets animation to none. |
| is_public | boolean | false hides the calendar from everyone but you; visitors of the link see a short note that it is not shared yet. Default true. |
| password | string | Visitors must enter this password before they see the calendar. Stored only as a hash. Enterprise. |
| tracking_code | string | Your own analytics or pixel snippet, loaded on the public page. Enterprise. |
| metadata | object | Up to 20 key value pairs of your own, returned unchanged and in every webhook. |
If you send a field that your plan does not include, the whole request fails with 403 plan_error and nothing is created. See Plans and features.
The calendar object
{
"id": "cal_4Hq8nW2kLp",
"object": "calendar",
"slug": "k7x2mq9t",
"public_url": "https://adventcalendarmaker.com/c/k7x2mq9t/",
"title": "Northwind Coffee Advent Calendar",
"description": "A new offer behind every door until Christmas Eve.",
"start_date": "2026-12-01",
"end_date": "2026-12-24",
"door_count": 24,
"opening_time": "08:00",
"timezone": "Europe/Berlin",
"template_style": "corporate",
"background_image": "golden-baubles",
"custom_color": "#0F766E",
"door_colors": { "door": "#0F766E", "accent": "#F5C451", "number": "#FFFFFF" },
"custom_logo_url": "https://cdn.northwind.example/logo.png",
"animation": "stars",
"snow_enabled": false,
"is_public": true,
"password_protected": false,
"tracking_code": null,
"doors_filled": 0,
"created_at": "2026-09-29T09:14:03Z",
"metadata": { "campaign": "xmas-2026" },
"livemode": true
}
| Field | Type | Description |
|---|---|---|
| id | string | Stable id, use it in all further calls. |
| slug | string | Part of the public link, random at creation. Read only via the API; you can change it in the editor (lowercase letters, digits and hyphens, 3 to 60 characters). |
| public_url | string | The link you share. Opens the calendar on adventcalendarmaker.com. |
| door_count | integer | Number of doors, always equal to the number of days from start_date to end_date. |
| doors_filled | integer | How many doors already have content. |
| password_protected | boolean | true if a password is set. The password itself is never returned. |
| livemode | boolean | false for calendars created with a test key. |
List, change and delete
GET /v1/calendars returns your calendars in pages of up to 100. With active_on you get only calendars whose date range contains that day; with metadata[campaign]=xmas-2026 you filter by your own metadata.
curl -G https://api.adventcalendarmaker.com/v1/calendars \
-H "Authorization: Bearer $ACM_API_KEY" \
-d active_on=2026-12-10 \
-d limit=50
{
"object": "list",
"data": [
{ "id": "cal_4Hq8nW2kLp", "title": "Northwind Coffee Advent Calendar", "...": "..." },
{ "id": "cal_9Tz3pR6wQs", "title": "Team Advent Calendar 2026", "...": "..." }
],
"has_more": false,
"next_cursor": null
}
If has_more is true, pass next_cursor as cursor to get the next page.
PATCH /v1/calendars/{id} changes only the fields you send. If you shorten the date range, doors beyond the new end are kept but hidden; if you extend it again, they come back with their content.
curl -X PATCH https://api.adventcalendarmaker.com/v1/calendars/cal_4Hq8nW2kLp \
-H "Authorization: Bearer $ACM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "opening_time": "07:00", "animation": "snow" }'
DELETE /v1/calendars/{id} removes the calendar, all doors, uploaded media and statistics for good. The public link then returns 404.
Doors
A calendar has exactly one door per day in its date range, numbered from 1. Doors are not created or deleted on their own; you only write their content.
PUT /v1/calendars/{id}/doors/{day} sets the content of one door and replaces whatever was there before.
curl -X PUT https://api.adventcalendarmaker.com/v1/calendars/cal_4Hq8nW2kLp/doors/7 \
-H "Authorization: Bearer $ACM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content_type": "coupon",
"title": "Door 7: 20 % off all filter coffee",
"text": "Valid today only, online and in store.",
"coupon_code": "ADVENT07"
}'
{
"object": "door",
"calendar_id": "cal_4Hq8nW2kLp",
"day": 7,
"date": "2026-12-07",
"unlocks_at": "2026-12-07T08:00:00+01:00",
"unlocked": false,
"unlocked_override": false,
"content_type": "coupon",
"title": "Door 7: 20 % off all filter coffee",
"text": "Valid today only, online and in store.",
"url": null,
"coupon_code": "ADVENT07",
"data": {},
"media": null,
"opens": 0
}
unlocks_at is always given with the offset of the calendar's time zone. opens counts how often visitors opened this door; it is only filled on Premium and Enterprise.
Content types
Each door has one content_type. Which fields it uses:
| Type | Plan | Fields and display |
|---|---|---|
| text | Free | title and text. On Premium and up, text may contain simple HTML (paragraphs, headings, bold, italic, lists, links, images); scripts, styles and event attributes are removed. On Free it is plain text. |
| embed | Free | url of a YouTube or Vimeo video, shown as an embedded player. |
| link | Premium | url plus optional title and text, shown as a button to your page. data.button sets the button text. |
| image | Premium | media_id of an uploaded image, optional title and text as caption. |
| video | Premium | media_id of an uploaded MP4 video. |
| coupon | Premium | coupon_code plus title and text. The code is shown with a copy button. We never generate or validate codes; they come from your shop system. |
| quiz | Premium | text holds the question or riddle, data.answer the answer. With data.options (up to 6 choices) visitors pick an answer and see right away whether it is correct; without choices they reveal the answer with a tap. |
| product | Premium | title as product name, text as description, optional media_id of a product image, data.price as display text and url of the product page. data.button sets the button text. |
| event | Premium | data.date (YYYY-MM-DD, required), optional data.time (HH:MM) and data.location, plus text and an optional registration url. Visitors can save the event to their own calendar as an .ics file. |
| giveaway | Premium | data.prize, optional data.deadline (YYYY-MM-DD), text with the rules and url of your entry form. We do not collect entries or draw winners; that runs through your form. |
| download | Premium | media_id of an uploaded file plus text. Shown with file name and a download button; data.button sets its text. |
| survey | Premium | text holds the question, data.options 2 to 6 answers. Each browser votes once and then sees the results. Changing the answers resets the votes. |
| music | Premium | media_id of an uploaded audio file or url of a Spotify or SoundCloud page, optional data.artist. |
When doors open
Door n opens on start_date + (n - 1) days at opening_time in the calendar's timezone. The time zone of the visitor does not matter: a calendar set to 08:00 Europe/Berlin opens door 1 at 07:00 in London and at 02:00 in New York.
Before that moment the public page shows the door as closed and the API for the public page does not reveal its content. You as the owner always see all doors.
To open a door early (for a preview, a press release or a bonus day), set "unlocked_override": true on the door. Setting it back to false closes the door again until its regular time.
Changes to opening_time or timezone apply immediately to all doors that are not open yet. Doors that are already open stay open.
Fill many doors at once
PUT /v1/calendars/{id}/doors takes a list of up to 50 doors. Doors you do not list stay unchanged. The call is all or nothing: if one door is invalid, no door is written and the error names the day and field.
{
"doors": [
{ "day": 1, "content_type": "text", "title": "Welcome", "text": "<p>Glad you are here.</p>" },
{ "day": 2, "content_type": "embed", "url": "https://www.youtube.com/watch?v=abc123" },
{ "day": 3, "content_type": "link", "title": "Our winter blend", "url": "https://northwind.example/winter" },
{ "day": 4, "content_type": "image", "media_id": "med_2Kd7sF1vXp" }
]
}
This is the call to use when you fill a calendar from a spreadsheet or a product feed. It counts as one request against the rate limit.
Images and videos
Upload files with POST /v1/media, then reference the returned id as media_id in a door. You can send the file as multipart form data or give us a public source_url and we fetch it.
curl https://api.adventcalendarmaker.com/v1/media \
-H "Authorization: Bearer $ACM_API_KEY" \
-F "[email protected]" \
-F "calendar_id=cal_4Hq8nW2kLp"
# or let us fetch it from a public URL
curl https://api.adventcalendarmaker.com/v1/media \
-H "Authorization: Bearer $ACM_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "calendar_id": "cal_4Hq8nW2kLp", "source_url": "https://cdn.northwind.example/door-04.webp" }'
{
"id": "med_2Kd7sF1vXp",
"object": "media",
"calendar_id": "cal_4Hq8nW2kLp",
"kind": "image",
"mime_type": "image/webp",
"bytes": 184322,
"url": "https://adventcalendarmaker.com/media/4711/door-04.webp/",
"created_at": "2026-09-29T09:20:41Z"
}
- Images: JPG, PNG, GIF or WebP, up to 2 MB.
- Videos: MP4, up to 20 MB.
- Audio: MP3, M4A or OGG, up to 10 MB.
- Downloads: PDF, ZIP, EPUB, DOCX, XLSX, PPTX or TXT, up to 10 MB.
- Media belong to one calendar and are deleted together with it.
- Media uploads need Premium or Enterprise.
Designs and backgrounds
GET /v1/backgrounds returns the background catalogue, the more than 100 door styles and the tags you can filter by. Use the key of a background as background_image when you create a calendar.
curl -G https://api.adventcalendarmaker.com/v1/backgrounds \
-H "Authorization: Bearer $ACM_API_KEY" \
-d tag=corporate
{
"object": "list",
"data": [
{ "key": "golden-baubles", "name": "Golden Baubles", "tags": ["festive", "elegant", "corporate"],
"thumb_url": "https://adventcalendarmaker.com/backgrounds/thumbs/golden-baubles.webp/" },
{ "key": "nordic-office", "name": "Nordic Office", "tags": ["minimalist", "corporate"],
"thumb_url": "https://adventcalendarmaker.com/backgrounds/thumbs/nordic-office.webp/" }
],
"template_styles": ["classic", "winter", "modern", "cozy", "corporate", "snowfall",
"candycane", "forest", "starlight", "gingerbread", "nordic", "midnight",
"plaid-red", "knit-red", "window-red", "bauble-red", "polka-red", "gift-kraft",
"frost-mint", "flat-retro", "starlight-teal", "knit-cream", "window-blue", "bauble-gold",
"gift-cranberry", "gift-frosted", "gift-pastel", "gift-emerald", "frost-lavender", "frost-blush",
"frost-glacier", "frost-silver", "flat-sorbet", "flat-ocean", "flat-mono", "flat-xmas",
"cabin-birch", "cabin-red", "cabin-green", "cabin-blue", "card-teal", "card-navy",
"card-dark", "card-cream", "card-green", "snowfall-teal", "snowfall-plum", "snowfall-red",
"snowfall-green", "snowfall-grey", "stripes-mint", "stripes-gold", "stripes-blue", "stripes-berry",
"stripes-cocoa", "forest-dusk", "forest-snow", "forest-red", "forest-midnight", "forest-pine",
"starlight-silver", "starlight-rose", "starlight-red", "starlight-green", "iced-pink", "iced-choc",
"iced-mint", "iced-honey", "linen-blue", "linen-green", "linen-red", "linen-dark",
"lacquer-silver", "lacquer-red", "lacquer-green", "lacquer-blue", "lacquer-rose", "plaid-green",
"plaid-blue", "plaid-buffalo", "plaid-grey", "plaid-camel", "plaid-mix", "knit-green",
"knit-navy", "knit-grey", "knit-mix", "window-green", "window-wood", "window-white",
"window-mix", "bauble-silver", "bauble-blue", "bauble-mix", "bauble-pink", "polka-green",
"polka-navy", "polka-pink", "polka-cream", "polka-mix", "knit-teal", "bauble-plum",
"stripes-frost", "card-red"],
"tags": ["festive", "nature", "cozy", "elegant", "minimalist", "cartoon",
"kids", "dogs", "cats", "corporate", "romantic"]
}
Statistics
GET /v1/calendars/{id}/stats returns the views of the calendar page, the views of the last seven days, views and opens per day, and per door the opens and the actions inside it (actions: button clicks, copied coupon codes, quiz answers, giveaway entries and survey votes, each counted once per visitor). Statistics are part of Premium and Enterprise.
{
"object": "calendar_stats",
"calendar_id": "cal_4Hq8nW2kLp",
"views": 18342,
"views_last_7_days": 6120,
"door_opens": 41877,
"daily": [
{ "date": "2026-12-01", "views": 2410, "opens": 3904 },
{ "date": "2026-12-02", "views": 2265, "opens": 3517 }
],
"doors": [
{ "day": 1, "type": "coupon", "opens": 3904, "actions": 1288 },
{ "day": 2, "type": "quiz", "opens": 3517, "actions": 2140 },
{ "day": 3, "type": "text", "opens": 3288, "actions": 0 }
],
"generated_at": "2026-12-03T10:00:00Z"
}
On Enterprise, /stats/export returns the same data per day as CSV, ready for a spreadsheet or your BI tool.
curl -L -o opens.csv \
"https://api.adventcalendarmaker.com/v1/calendars/cal_4Hq8nW2kLp/stats/export?from=2026-12-01&to=2026-12-24" \
-H "Authorization: Bearer $ACM_API_KEY"
# date,day,views,unique_visitors,door_opens,actions
# 2026-12-01,1,2410,1873,3904,612
We count views and opens without cookies and without storing IP addresses in clear text. See Privacy.
Plans and features
Your key has the features of your plan. GET /v1/account shows them.
{
"object": "account",
"plan": "enterprise",
"renews_at": "2027-09-12",
"calendars": { "used": 14, "limit": null },
"features": {
"media": true, "html": true, "custom_dates": true, "branding": true,
"analytics": true, "password": true, "tracking_code": true, "csv_export": true
},
"livemode": true
}
| Feature | From plan | Affects |
|---|---|---|
| Premium | link, image, video, coupon, quiz, product, event, giveaway, download, survey, music | Doors with links, images, videos, coupons, quizzes, products, events, giveaways, downloads, surveys and music. |
| Premium | text | HTML formatting in door text. |
| Premium | start_date, end_date, opening_time | Own date range and opening time instead of 1 to 24 December at midnight. |
| Premium | custom_color, door_colors, custom_logo_url | Own brand color, own door colors and logo. |
| Premium | /stats | Views and door opens. |
| Enterprise | password | Password protected calendars. |
| Enterprise | tracking_code | Own tracking or pixel code on the public page. |
| Enterprise | /stats/export | Statistics as CSV. |
The number of calendars is not limited on paid plans.
Webhooks
Tell us a HTTPS endpoint and we send an event there whenever something happens in your account. Each event is a POST with a JSON body.
| Event | When |
|---|---|
| calendar.created | A calendar was created, via API or in the editor. |
| calendar.updated | Settings of a calendar or the content of a door changed. |
| calendar.deleted | A calendar was deleted. |
| door.unlocked | A door just opened. Useful to send a push message or a newsletter at the same minute. |
| stats.daily | Once a day, shortly after midnight in the calendar's time zone: the numbers of the previous day. Premium and up. |
{
"id": "evt_7Nc2vB5xRt",
"type": "door.unlocked",
"created_at": "2026-12-07T07:00:02Z",
"livemode": true,
"data": {
"calendar_id": "cal_4Hq8nW2kLp",
"day": 7,
"unlocks_at": "2026-12-07T08:00:00+01:00",
"public_url": "https://adventcalendarmaker.com/c/k7x2mq9t/",
"metadata": { "campaign": "xmas-2026" }
}
}
Every request carries an ACM-Signature header with a timestamp and an HMAC SHA256 of timestamp.body, signed with your webhook secret.
ACM-Signature: t=1796626802,v1=9e4b1c7d2a...
import hmac, hashlib, os, time
SECRET = os.environ["ACM_WEBHOOK_SECRET"].encode()
def verify(header: str, raw_body: bytes) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
ts, sig = parts["t"], parts["v1"]
if abs(time.time() - int(ts)) > 300:
return False
expected = hmac.new(SECRET, ts.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
Answer with any 2xx status within 10 seconds. If we get no 2xx, we retry with growing gaps for 24 hours. Events can arrive more than once; use id to ignore duplicates.
Idempotency
Send an Idempotency-Key header with every POST. If the connection drops and you send the same request again with the same key within 24 hours, you get the original response back instead of a second calendar.
curl https://api.adventcalendarmaker.com/v1/calendars \
-H "Authorization: Bearer $ACM_API_KEY" \
-H "Idempotency-Key: 3f6c1a2e-8d4b-4e0f-9b7a-5c2d1e0f9a88" \
-H "Content-Type: application/json" \
-d @calendar.json
PUT and PATCH are idempotent by nature, so they do not need the header.
Errors
Errors come with a matching HTTP status and a body that tells you what to fix.
{
"error": {
"type": "plan_error",
"code": "feature_not_in_plan",
"message": "Password protection is part of the Enterprise plan.",
"field": "password",
"required_plan": "enterprise",
"request_id": "req_5Xk1dM8pWq"
}
}
| Type | Status | Meaning |
|---|---|---|
| invalid_request | 400 | The request cannot be read, for example broken JSON or an unknown field. |
| authentication_error | 401 | Missing, wrong or revoked key. |
| plan_error | 403 | The field or endpoint needs a higher plan. required_plan says which. |
| not_found | 404 | Calendar, door or media does not exist in your account. |
| conflict | 409 | The same idempotency key was used with a different body. |
| validation_error | 422 | A value is not allowed, for example a date range over 50 days, a door number outside the range or an image over 2 MB. field names it. |
| rate_limit | 429 | Too many requests. Wait for RateLimit-Reset seconds. |
| api_error | 500 | Our fault. Retry later and send us the request_id if it persists. |
Limits
| Value | Limit |
|---|---|
| 120 / min | Requests per minute and key. Every response carries the current state in headers. |
| ≤ 50 | Doors per calendar, given by the date range. |
| 2 MB | Image upload: JPG, PNG, GIF, WebP. |
| 20 MB | Video upload: MP4. |
| 50 | Doors per call of PUT /doors. |
| 24 h | Idempotency window: how long we remember a key. |
RateLimit-Limit: 120
RateLimit-Remaining: 117
RateLimit-Reset: 41
If you need more for a large campaign in late November, write to us before it starts.
Versioning
The version is part of the path (/v1). Within v1 we only add things: new endpoints, new optional fields, new event types. Your code should ignore fields it does not know.
Changes that could break integrations get a new date version. You can pin one with a header; without it, your key uses the version that was current when it was issued.
ACM-Version: 2026-09-01
We announce breaking changes at least six months ahead by email and never ship them between 15 November and 6 January, while calendars are running.
Test mode
With a sk_test_ key everything works like in live mode, with three differences:
- Test calendars have
"livemode": falseand their public link shows a "test" banner. - Test calendars are not indexed by search engines and are deleted after 30 days.
- You can send
"simulate_date": "2026-12-07T08:00:00Z"with a GET on a door or calendar to see which doors would be open at that moment.
Webhooks from test mode have "livemode": false, so one endpoint can take both.
Privacy
Your calendars and their content belong to you. We use them only to show the calendar and never for anything else.
- Visitors do not need an account and we set no tracking cookies on public calendars.
- For views and opens we store only a hash of the IP address, never the address itself.
- Passwords of protected calendars are stored only as hashes.
- Hosting and data processing take place in the EU. A data processing agreement under GDPR is available on request.
- A tracking code you add yourself (Enterprise) runs under your responsibility; the consent your visitors give for it is your task.
Support
Questions about the API, a key or a webhook go to [email protected]. Always include the request_id from the error or the response headers; it lets us find your request in seconds.
Enterprise customers get priority support, and in the weeks before 1 December we answer API questions within one working day.
Get an API key
API and MCP access is included in Premium ($199 per year) and Enterprise ($1,299 per year). Write to [email protected] with your account email and we send you a test key and a live key.