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.

Request a 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.

bash Authorization header in every request
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.

python Python: 24 doors in two requests
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

MethodPathPurpose
POST/v1/calendarsCreate a calendar.
GET/v1/calendars/{id}Read one calendar.
GET/v1/calendarsList 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}/doorsAll doors of a calendar with unlock times and content.
PUT/v1/calendars/{id}/doorsWrite 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/mediaUpload an image, video, audio file or download for a door.
GET/v1/backgroundsBackground designs, template styles and tags.
GET/v1/calendars/{id}/statsViews, door opens, clicks and entries.
GET/v1/calendars/{id}/stats/exportDaily statistics as CSV (Enterprise).
GET/v1/accountPlan, 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).

javascript Node.js: calendar with branding and background
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();
FieldTypeDescription
titlestring, requiredShown above the calendar. Up to 255 characters.
descriptionstringShort text under the title. Up to 2000 characters.
start_datedateDay of door 1. Default 1 December. Premium.
end_datedateDay of the last door. The range may cover at most 50 days. Premium.
opening_timeHH:MMLocal time at which each door opens, for example 08:00. Premium.
timezonestringIANA time zone name, for example Europe/London or America/New_York. Default Europe/Berlin.
template_stylestringOne of the more than 100 door styles from /v1/backgrounds.
background_imagestringKey of a background design from the catalogue.
custom_color#RRGGBBBrand color as hex value. Premium.
door_colorsobjectOwn 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_urlstringHTTPS URL of your logo, shown above the doors. Premium.
animationstringBackground animation: none, snow, flurry (dense snow on the wind) or stars (twinkling stars). Default snow. Free on every plan.
snow_enabledbooleanOlder shortcut, true when animation is snow or flurry. Sending false sets animation to none.
is_publicbooleanfalse hides the calendar from everyone but you; visitors of the link see a short note that it is not shared yet. Default true.
passwordstringVisitors must enter this password before they see the calendar. Stored only as a hash. Enterprise.
tracking_codestringYour own analytics or pixel snippet, loaded on the public page. Enterprise.
metadataobjectUp 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

json Response of POST /v1/calendars
{
  "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
}
FieldTypeDescription
idstringStable id, use it in all further calls.
slugstringPart 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_urlstringThe link you share. Opens the calendar on adventcalendarmaker.com.
door_countintegerNumber of doors, always equal to the number of days from start_date to end_date.
doors_filledintegerHow many doors already have content.
password_protectedbooleantrue if a password is set. The password itself is never returned.
livemodebooleanfalse 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.

bash Calendars that run on 10 December
curl -G https://api.adventcalendarmaker.com/v1/calendars \
  -H "Authorization: Bearer $ACM_API_KEY" \
  -d active_on=2026-12-10 \
  -d limit=50
json Response with cursor
{
  "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.

bash Open doors one hour earlier and switch on snowfall
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.

bash A coupon behind door 7
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"
  }'
json The door object
{
  "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:

TypePlanFields and display
textFreetitle 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.
embedFreeurl of a YouTube or Vimeo video, shown as an embedded player.
linkPremiumurl plus optional title and text, shown as a button to your page. data.button sets the button text.
imagePremiummedia_id of an uploaded image, optional title and text as caption.
videoPremiummedia_id of an uploaded MP4 video.
couponPremiumcoupon_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.
quizPremiumtext 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.
productPremiumtitle 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.
eventPremiumdata.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.
giveawayPremiumdata.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.
downloadPremiummedia_id of an uploaded file plus text. Shown with file name and a download button; data.button sets its text.
surveyPremiumtext holds the question, data.options 2 to 6 answers. Each browser votes once and then sees the results. Changing the answers resets the votes.
musicPremiummedia_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.

json Request body with four content types
{
  "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.

bash Upload a file or fetch from a URL
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" }'
json The media object
{
  "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.

bash Only backgrounds tagged corporate
curl -G https://api.adventcalendarmaker.com/v1/backgrounds \
  -H "Authorization: Bearer $ACM_API_KEY" \
  -d tag=corporate
json Response
{
  "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.

json Response
{
  "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.

bash CSV export for December
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.

json GET /v1/account
{
  "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
}
FeatureFrom planAffects
Premiumlink, image, video, coupon, quiz, product, event, giveaway, download, survey, musicDoors with links, images, videos, coupons, quizzes, products, events, giveaways, downloads, surveys and music.
PremiumtextHTML formatting in door text.
Premiumstart_date, end_date, opening_timeOwn date range and opening time instead of 1 to 24 December at midnight.
Premiumcustom_color, door_colors, custom_logo_urlOwn brand color, own door colors and logo.
Premium/statsViews and door opens.
EnterprisepasswordPassword protected calendars.
Enterprisetracking_codeOwn tracking or pixel code on the public page.
Enterprise/stats/exportStatistics 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.

EventWhen
calendar.createdA calendar was created, via API or in the editor.
calendar.updatedSettings of a calendar or the content of a door changed.
calendar.deletedA calendar was deleted.
door.unlockedA door just opened. Useful to send a push message or a newsletter at the same minute.
stats.dailyOnce a day, shortly after midnight in the calendar's time zone: the numbers of the previous day. Premium and up.
json Event door.unlocked
{
  "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.

http Signature header
ACM-Signature: t=1796626802,v1=9e4b1c7d2a...
python Python: verify the signature
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.

bash Create a calendar safely
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.

json 403 when a field needs a higher plan
{
  "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"
  }
}
TypeStatusMeaning
invalid_request400The request cannot be read, for example broken JSON or an unknown field.
authentication_error401Missing, wrong or revoked key.
plan_error403The field or endpoint needs a higher plan. required_plan says which.
not_found404Calendar, door or media does not exist in your account.
conflict409The same idempotency key was used with a different body.
validation_error422A 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_limit429Too many requests. Wait for RateLimit-Reset seconds.
api_error500Our fault. Retry later and send us the request_id if it persists.

Limits

ValueLimit
120 / minRequests per minute and key. Every response carries the current state in headers.
≤ 50Doors per calendar, given by the date range.
2 MBImage upload: JPG, PNG, GIF, WebP.
20 MBVideo upload: MP4.
50Doors per call of PUT /doors.
24 hIdempotency window: how long we remember a key.
http Rate limit headers
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.

http Pin a version
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": false and 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.

Request a key