42min

    Help API

    /v1/series

    A recurring series books one repeating pattern, weekly or monthly, as a run of ordinary meetings. This page covers reading, creating, and managing the series itself: pausing it, resuming it, ending it, editing its pattern, and moving it to a new host.

    The occurrences a series creates are ordinary bookings, not a separate resource. List them with GET /v1/bookings and a series_id filter, and read or act on any one of them with the regular booking endpoints, uid for uid.

    A series' UID is its UUID, and every endpoint that takes :uid rejects a malformed or unknown one with the same 404 series_not_found, so the response never confirms which case it was.


    GET /v1/series

    List recurring series, narrowed to what you may see.

    MethodGET
    URLhttps://api.42min.us/v1/series
    Scopeseries:read
    AuthRequired

    Query parameters

    ParamTypeNotes
    limitintegerDefault 20, max 100.
    cursorstringOpaque cursor from a prior meta.next_cursor.
    statusstringComma-separated. Allowed values: active, paused, ended.
    host_user_idUUIDFilter to one host.
    event_type_idUUIDFilter to one event type.

    What you see depends on your role: your own series as a user, the series of the people you manage as a manager, and the whole organization as an admin or owner. A host_user_id outside that set comes back as an empty page, not an error, so it never confirms whether the host exists.

    Response

    {
      "data": [
        {
          "uid": "01H…",
          "version": 1,
          "event_type": { "id": "01HXXX…", "slug": "weekly-lesson", "name": "Weekly lesson" },
          "host": { "user_id": "01H…", "username": "ada", "email": "ada@example.com" },
          "attendee": { "name": "Bob Builder", "email": "bob@example.com", "phone": null },
          "frequency": "weekly",
          "interval_weeks": 1,
          "weekdays": ["mon", "wed"],
          "time": "18:30",
          "timezone": "Europe/Lisbon",
          "starts_on": "2026-09-14",
          "count": 24,
          "status": "active",
          "location_url": null,
          "notify_invitee": true,
          "source": "api",
          "created_at": "2026-09-10T09:00:00.000Z",
          "updated_at": "2026-09-10T09:00:00.000Z",
          "ended_at": null
        }
      ],
      "meta": { "request_id": "req_…", "next_cursor": null, "has_more": false }
    }
    

    Field notes:

    • version is the series' revision, bumped by every action on it. It's what you send back as If-Match on PATCH.
    • source says where the series came from: public (an invitee, from the booking page), internal (a host or admin, from the dashboard), api, or mcp.
    • Occurrences aren't included here; see GET /v1/bookings?series_id={uid}.

    curl

    curl -H "Authorization: Bearer $TOKEN" \
      "https://api.42min.us/v1/series?status=active&limit=50"
    

    GET /v1/series/:uid

    Return one series. Sets ETag for the revision.

    MethodGET
    URLhttps://api.42min.us/v1/series/{uid}
    Scopeseries:read
    AuthRequired

    Headers (response)

    ETag: "3"
    

    Response

    Same shape as a list item.

    curl

    curl -i -H "Authorization: Bearer $TOKEN" \
      https://api.42min.us/v1/series/01H…
    

    Common errors

    • 404 series_not_found: unknown UID, or the UID is not a UUID (collapsed into the same 404 to avoid leaking format details).

    POST /v1/series

    Create a recurring series: books up to count occurrences of one repeating pattern in a single call.

    MethodPOST
    URLhttps://api.42min.us/v1/series
    Scopeseries:write
    AuthRequired

    Requires an Idempotency-Key. This sends real invitee email and writes to a real calendar, so retry a failed attempt with the same key rather than reissuing the call.

    Headers

    Authorization: Bearer <token>
    Content-Type: application/json
    Idempotency-Key: <unique-per-operation>
    

    Body

    Either event_type_id or (username + event_slug) is required.

    {
      "host_user_id": "8b7c1e6f-1111-4222-8333-444455556666",
      "event_type_id": "1c2d3e4f-5555-4666-8777-888899990000",
      "attendee": {
        "email": "ada@example.com",
        "name": "Ada Lovelace"
      },
      "frequency": "weekly",
      "interval_weeks": 1,
      "weekdays": ["mon", "wed"],
      "time": "18:30",
      "timezone": "Europe/Lisbon",
      "starts_on": "2026-09-14",
      "count": 24
    }
    

    Field reference:

    FieldTypeNotes
    host_user_idUUIDRequired. Whose calendar the series is booked on; must be someone you may act for.
    event_type_idUUIDOne of event_type_id or usernameevent_slug required.
    username / event_slugstringAlternative to event_type_id.
    attendee.emailstringRequired.
    attendee.namestringIf omitted, built from first_name + last_name, else falls back to email.
    attendee.first_name / last_namestringOptional.
    attendee.phonestringOptional.
    frequencystringRequired. weekly or monthly_same_day.
    interval_weeksinteger1 to 8, default 1. Weekly patterns only.
    weekdaysarrayRequired for a weekly pattern: up to 7 of sunsat. Ignored for monthly_same_day.
    timestringRequired. HH:MM, 24-hour, the wall clock in timezone.
    timezoneIANA tzOptional. Defaults to the host's own zone.
    starts_ondateRequired. YYYY-MM-DD, the first civil day the pattern is walked from. Past instants are never booked.
    countintegerRequired, 1 to 42. Dates the host isn't free on are skipped, not shifted, so a series can come back with fewer occurrences than requested.
    location_urlstringOptional. A fixed http(s) link every occurrence carries, instead of a generated conference link.
    notify_inviteebooleanOptional, default true. false means no mail reaches the attendee for this series.

    Response

    201 Created:

    {
      "data": {
        "series": { "uid": "01H…", "version": 1, "…": "…" },
        "occurrences": [
          { "uid": "01H…", "start_at": "2026-09-14T17:30:00.000Z", "end_at": "2026-09-14T18:00:00.000Z", "status": "confirmed" }
        ],
        "skipped": []
      },
      "meta": { "request_id": "req_…" }
    }
    

    Field notes:

    • occurrences lists what was actually booked, in order. Each uid is an ordinary booking UID, usable on every /v1/bookings route.
    • skipped lists pattern dates that weren't free (reason: "conflict"); the series is still created with the rest. Compare its length against count to know whether every occurrence was booked.

    curl

    curl -X POST https://api.42min.us/v1/series \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{
        "host_user_id": "8b7c1e6f-1111-4222-8333-444455556666",
        "event_type_id": "1c2d3e4f-5555-4666-8777-888899990000",
        "attendee": {"email": "ada@example.com", "name": "Ada Lovelace"},
        "frequency": "weekly",
        "weekdays": ["mon", "wed"],
        "time": "18:30",
        "starts_on": "2026-09-14",
        "count": 24
      }'
    

    Common errors

    • 400 validation_error: required field missing or malformed.
    • 400 attendee_email_invalid: bad email format.
    • 400 missing_idempotency_key: header missing.
    • 403 host_not_allowed: host_user_id is outside who you may act for.
    • 404 event_type_not_found: bad id/slug.
    • 409 recurring_disabled: the event type's recurring mode is off.
    • 409 no_dates_free: no date in the pattern was free, so nothing was created.
    • 422 count_out_of_range: count is outside the allowed range.
    • 503 availability_unavailable: the host's conflict calendar couldn't be read, so nothing was booked on a guess. Retry with the SAME Idempotency-Key, honoring Retry-After.

    POST /v1/series/:uid/pause

    Cancel every future occurrence and record each as owed, so a later resume books a replacement for it. Occurrences the invitee already moved on their own are left alone. Nothing is emailed to the attendee.

    MethodPOST
    URLhttps://api.42min.us/v1/series/{uid}/pause
    Scopeseries:write
    AuthRequired

    No body. Safe to repeat: a re-run holds whatever the first run didn't reach.

    Response

    200 OK:

    {
      "data": { "held": 6, "superseded": false },
      "meta": { "request_id": "req_…" }
    }
    

    held is how many occurrences this call canceled and recorded as owed. See Superseded and busy responses below for what superseded: true means.

    curl

    curl -X POST https://api.42min.us/v1/series/01H…/pause \
      -H "Authorization: Bearer $TOKEN"
    

    Common errors

    • 404 series_not_found
    • 409 series_busy: another action holds the series right now (for example, its confirmation email is still sending). Read it again and retry.
    • 409 series_ended: terminal, nothing can change it.

    POST /v1/series/:uid/resume

    Book one replacement for each occurrence a pause is holding, and reactivate the series.

    MethodPOST
    URLhttps://api.42min.us/v1/series/{uid}/resume
    Scopeseries:write
    AuthRequired

    Requires an Idempotency-Key. If-Match is optional here: send the ETag from a prior GET to refuse a resume that another change overtook, or omit it to resume whatever the series is now. A value that doesn't parse as a valid revision answers 400 invalid_if_match.

    Response

    200 OK:

    {
      "data": { "created": 4, "skipped": [], "owed": 0, "superseded": false },
      "meta": { "request_id": "req_…" }
    }
    

    Field notes:

    • created is how many replacement occurrences this call booked.
    • skipped lists dates that weren't free (reason: "conflict"); those stay owed.
    • owed is how many occurrences are still waiting for a replacement. Above zero means the series is still paused, and calling resume again with the current revision converges on the rest.

    curl

    curl -X POST https://api.42min.us/v1/series/01H…/resume \
      -H "Authorization: Bearer $TOKEN" \
      -H "Idempotency-Key: $(uuidgen)"
    

    Common errors

    • 400 invalid_if_match: If-Match was sent but isn't a valid revision.
    • 400 missing_idempotency_key
    • 404 series_not_found
    • 409 series_busy / 409 series_ended
    • 503 availability_unavailable: retry with the SAME Idempotency-Key, honoring Retry-After.

    POST /v1/series/:uid/end

    Cancel every future occurrence for good and send the attendee one notice listing them. Nothing is owed afterward, and the series can never be resumed. Past occurrences are untouched.

    MethodPOST
    URLhttps://api.42min.us/v1/series/{uid}/end
    Scopeseries:write
    AuthRequired

    Requires an Idempotency-Key. Safe to repeat: a re-run cancels whatever the first run didn't reach.

    Response

    200 OK:

    {
      "data": { "canceled": 5, "superseded": false },
      "meta": { "request_id": "req_…" }
    }
    

    curl

    curl -X POST https://api.42min.us/v1/series/01H…/end \
      -H "Authorization: Bearer $TOKEN" \
      -H "Idempotency-Key: $(uuidgen)"
    

    Common errors

    • 400 missing_idempotency_key
    • 404 series_not_found
    • 409 series_busy / 409 series_ended

    POST /v1/series/:uid/host

    Move every future occurrence, then the series itself, to another host.

    MethodPOST
    URLhttps://api.42min.us/v1/series/{uid}/host
    Scopeseries:write
    AuthRequired

    Body

    { "host_user_id": "9c8d2e7a-2222-4333-9444-555566667777" }
    
    FieldTypeNotes
    host_user_idUUIDRequired. The new host; must be someone you may act for.

    Response

    200 OK:

    {
      "data": { "moved": 5, "unchanged": 0, "stopped_by": null, "superseded": false },
      "meta": { "request_id": "req_…" }
    }
    

    A transfer that can't complete (no writable calendar on the new host, or another action taking the series over) stops the run: moved says how many occurrences did move, and stopped_by names the reason. stopped_by: null means every occurrence moved and the series' own host changed too; anything else means it didn't, and the call can be repeated once the cause is fixed.

    curl

    curl -X POST https://api.42min.us/v1/series/01H…/host \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"host_user_id":"9c8d2e7a-2222-4333-9444-555566667777"}'
    

    Common errors

    • 400 validation_error
    • 403 host_not_allowed: the new host is outside who you may act for.
    • 404 series_not_found
    • 409 series_busy / 409 series_ended

    PATCH /v1/series/:uid

    Edit a series' pattern, its fixed link, or whether the attendee is notified. Requires optimistic locking.

    MethodPATCH
    URLhttps://api.42min.us/v1/series/{uid}
    Scopeseries:write
    AuthRequired

    If-Match is required: the ETag from a prior GET of this series. Omitting it returns 428 missing_if_match and nothing is changed. Unlike PATCH /v1/bookings/:uid, this route does not also require an Idempotency-Key.

    Headers

    Authorization: Bearer <token>
    Content-Type: application/json
    If-Match: "3"
    

    Body

    Send only what changes.

    { "weekdays": ["tue", "thu"], "time": "09:00" }
    
    FieldTypeNotes
    frequencystringweekly or monthly_same_day.
    interval_weeksinteger1 to 8.
    weekdaysarrayUp to 7 of sunsat.
    timestringHH:MM, 24-hour.
    location_urlstringReplaces the series' existing fixed link. A series created without one can't gain one here, and sending null to clear it is refused as a validation error rather than treated as "remove it".
    notify_inviteebooleanChanges whether future sends reach the attendee.

    starts_on, count, the attendee, and the event type are fixed for the life of a series and can't be patched.

    Changing frequency, interval_weeks, weekdays, or time holds every future occurrence the new pattern no longer generates and books a replacement for each, reported under resumed. Changing only location_url or notify_invitee restamps the existing occurrences in place and leaves every date alone, so resumed is null.

    Response

    200 OK, with headers including ETag: "<new-version>":

    {
      "data": {
        "series": { "uid": "01H…", "version": 2, "…": "…" },
        "resumed": { "created": 3, "skipped": [], "owed": 0, "superseded": false }
      },
      "meta": { "request_id": "req_…" }
    }
    

    curl

    curl -X PATCH https://api.42min.us/v1/series/01H… \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -H "If-Match: \"3\"" \
      -d '{"weekdays":["tue","thu"],"time":"09:00"}'
    

    Common errors

    • 400 validation_error: malformed field, such as a bad time format.
    • 404 series_not_found
    • 409 series_busy / 409 series_ended
    • 409 version_conflict: If-Match doesn't match the current version. Re-GET and retry.
    • 422: a well-formed but semantically rejected change, such as sending location_url: null to clear an existing link.
    • 428 missing_if_match: header missing.

    Superseded and busy responses

    Every action above can be overtaken by another one running on the same series at the same time: two requests pausing a series at once, or an end landing while a resume is still booking replacements. Two different things can happen:

    • 409 series_busy means the call couldn't get started at all, because something else currently holds the series (mid-send confirmation email, or another action's very first write). Nothing changed. Read the series again and retry.
    • superseded: true in a 200 response means the call did start, and its own work up to that point is real and already committed, but another action took the series over partway through, so it stopped early. The counts in the response (held, created, canceled, moved) are what this call actually finished, not what it was asked to do. Read the series again and repeat the same call; it converges on whatever is left.

    change_host reports the same situation through stopped_by instead of superseded, since it already has a field for "the run didn't finish."

    This matters most for resume and a pattern-changing PATCH, both of which can need several occurrence inserts to finish one call.

    Last updated September 10, 2026.