Skip to main content
Changes to the Daysync Integration API are listed here, newest first. Partners should watch this page to know when to update their integrations.
This changelog was started on 2026-06-08. Entries below this date are documentation changes; behavioural API changes will be recorded here going forward.

2026-08-28

Idempotent writes (docs)
  • Send X-Idempotency-Key on a write and the API runs it at most once: a retry carrying the same key returns the original result instead of creating a second record. Optional and additive — a partner that sends nothing gets exactly today’s behaviour.
  • The key is any string up to 128 characters, read case-insensitively. A longer one is refused with 400 VALIDATION_ERROR rather than ignored, because answering 200 to a caller who believes they are protected is the worse failure.
  • Corrected: Retries & idempotency previously stated that the API does not support idempotency keys, and its POST guidance was built on that. Both are rewritten. Generate the key before the first attempt and reuse it across retries — a key minted per attempt protects nothing.
  • Over MCP the same protection is an optional idempotency_key on every additive write tool (catalogue). Omitted, a key is derived from the payload, so an identical retry within about ten minutes is treated as the same action — which is what stops a retrying agent from silently creating two schedule items.
Every capped list read now reports its page (docs) The six day- and tour-scoped list reads were already bounded — 1,000 rows, and shortened again when the response would be too large to serialize — but they said nothing about it, so a partner had no way to tell a complete list from a truncated one. Each now answers with a pagination object alongside data, additive and absent on unpaged reads.
  • totalCount, returnedCount, hasMore, truncated and nextOffset, on GET …/schedule, …/venues, …/accommodation, …/guestlist, …/bulletins and …/passTypes. returnedCount always equals data.length.
  • Page with nextOffset, not with returnedCount. nextOffset counts the rows the server consumed, which can exceed offset + returnedCount when rows were dropped from the page after being counted — advancing by the row count would re-read them on every iteration. A page the server consumed nothing from reports hasMore: false and nextOffset: null, so a while (hasMore) loop terminates — while returnedCount: 0 with hasMore: true is legitimate and means every row on that page was filtered out after being counted.
  • truncated: true means the response ceiling shortened the page, not your limit — a smaller limit will not lengthen it; only paging will. This is why a short page is not a reliable end-of-list signal.
  • Deliberately conservative: a response that cannot report both counts omits the object entirely rather than returning a half-filled envelope that would read as a complete page.
  • Over MCP the same six tools append a one-line [pagination] note whenever a page is incomplete, naming the offset to call back with. A complete, untruncated page carries no note, so a healthy read is unchanged.
Schedule-item writes return their reminder ids (docs)
  • POST /v1/schedule/items and PUT /v1/schedule/items/{itemId} now return the item’s reminders with their server-generated ids when the request sent a reminder list. Previously there was no way to learn those ids from a write — and because a reminder entry submitted without an id is created as a new row, a client that could not keep them duplicated its reminders on every save.
  • The array is the item’s reminders after the write, sorted by id, read inside the same transaction. It carries id, description, duration and is_sent — the same fields the list response already serves under itemReminder[].reminder.
  • Present only when the request sent reminder; a write that did not touch reminders answers { "id": … } exactly as before. POST /v1/schedule/items/batch does not echo them.
  • Over MCP, the same rows come back from create_schedule_item and update_schedule_item.

2026-08-27

95 → 102 endpoints, 97 → 106 MCP operations. Everything here is additive: no existing endpoint, field, or scope changed, and no scope was added. The one thing that changes for an existing integration is the MCP tool list — see the note below. New: a first-class file contract (docs) Files were upload-only: POST /v1/attachments fetched a public URL and handed back a key, and there was no way to list a tour’s files or turn a stored key into something fetchable. That endpoint is unchanged and still supported, and every upload source mints the same kind of key; five endpoints join it.
  • GET /v1/tours/{tourId}/files — every file on the tour, across schedule items, venues, stays and pass types, filtered to what you can see. Filter by entityTypes, dayId or a name search; paged with an exact totalCount.
  • GET /v1/tours/{tourId}/files/{fileId}/download — a short-lived signed URL (about five minutes). Not a permanent link; request a fresh one rather than storing it.
  • POST /v1/tours/{tourId}/files/content — upload the bytes inline as base64, max 256 KB, for when you have the file and no public URL to point at.
  • POST /v1/tours/{tourId}/files/upload-ticket — a signed PUT for a large local file. content-length and content-type are part of the signature, so the upload is bounded by storage itself.
  • POST /v1/tours/{tourId}/files/attach — attach an uploaded key to a schedule item, venue, stay or pass type. Additive (it never touches the entity’s existing attachments, unlike an update carrying attachments) and idempotent, so a retry is safe.
  • MCP equivalents: list_files, get_file, upload_file (three sources) and attach_file, all advertised by default.
  • Corrected: attachments were documented as supported on guest list entries. They are not — an attachment on a guest-list body is not stored. Pass types are attachable and were undocumented. The four attachable kinds are schedule items, venues, accommodation and pass types.
Behavioural change — content types are now restricted on every upload, including POST /v1/attachments
  • Uploads previously accepted whatever content type the source declared. There is now an allow-list: images, PDF, Office documents, text/CSV/Markdown/iCalendar/JSON, ZIP, audio and video. image/svg+xml, text/html, scripts and executables are rejected with 415, and the error lists what is accepted. Attachment files are served over HTTP, so an SVG or an HTML file stored among them is an active-content risk rather than a document.
  • If you were relying on uploading one of those types, this is a breaking change for that call. Everything else is unaffected.
New: the Day Sheet (docs)
  • GET /v1/tours/{tourId}/days/{dayId}/day-sheet/data returns one day assembled as a print-ready model, and POST /v1/tours/{tourId}/days/{dayId}/day-sheet renders it as a PDF behind a short-lived signed URL. Non-destructive — the render creates nothing and changes no record.
  • Both ride tours.read plus the other five read scopes, so no re-grant is needed for a partner that already holds them.
The MCP tool list is shorter — and nothing was removed
  • The server had reached 97 advertised tools / about 33,000 tokens of definitions loaded before a client had read your question, which is more than a client can usefully reason about. It now advertises a curated 25 (~6,500 tokens) by default, and the other 81 operations are reached through list_capabilitiesdescribe_operationsinvoke_operation. describe_operations returns the same JSON Schema the tool list used to carry, so nothing is harder to call correctly — it just arrives when you ask for it.
  • invoke_operation is not a generic “call any endpoint” tool: operation must name an operation in the catalogue that your connection is authorised for, and its input is validated against that operation’s real schema. An operation outside your consented scopes is refused with INSUFFICIENT_SCOPE naming the scope needed.
  • Every tool name that ever existed is still callable by name. An integration that hardcodes create_pass_type needs no change. A client that enumerates tools and wants the old list can pin it with ?tools=all on the MCP URL, or the x-daysync-mcp-tools: all header.
  • An existing MCP session must reconnect before any of this appears — tool lists are cached from session start.
Corrected documentation
  • Pagination was documented as not existing. The page said list endpoints return all matching records and that there are no limit/offset parameters. Both halves were wrong: limit/offset are accepted, and every day-scoped list read is capped server-side at 1,000 rows and shortened further if the response would be too large to serialize — whether or not you send a limit. A list response is not necessarily the whole list. The sync recipe has been corrected too, because a mirror built on the old text would delete rows that are still live.
  • The endpoint count (95) and the MCP tool count (97 in one place, 81 in another) were stale on the overview, Scopes and the MCP overview. The scope-per-endpoint table was missing the seven endpoints above.
  • Endpoints that need more than one scope listed only the whole-tour read. There are now five.
  • Pass types were described as something you configure in the Daysync app. They have had full CRUD over the API since 2026-08-21.

2026-08-21

79 → 95 endpoints, 81 → 97 MCP tools. Everything here is additive: no existing endpoint, field, or scope changed, and no scope was added — the new endpoints ride schedule.* and guestlist.*, so no partner re-grant is needed. An existing MCP session must reconnect before the new tools appear. The whole-tour read now identifies its days
  • GET /v1/tours/{tourId}/data returned days carrying only an ID, content counts, and child resources — so you could not tell which day you were holding, and had to make a second GET /v1/tours call and join on the day ID. Every day now also carries name, date, time_zone, type_id ({ id, type, description }), formattedAddress, placeFormattedAddress, place_id and viewport, using the same field names as the day object in GET /v1/tours. Day types were never unavailable — they were just absent from this endpoint.
  • The same response now also exposes venueCount, passTypeCount and truncated, so a day whose collections were capped is distinguishable from a day that is genuinely short.
  • A field the day does not have is an explicit null, never an omitted key.
New: Schedule Templates (docs)
  • Eight endpoints for saving a set of schedule items as a reusable template and applying it to another day: list, read items, save, rename, delete, restore, apply (MERGE or REPLACE), and undo an apply.
  • Templates are per user — you only ever see your own — and a template can only be applied to a day of the tour its items came from.
New: pass-type management (docs)
  • POST /v1/tours/{tourId}/passTypes, PUT/DELETE /v1/passTypes/{passTypeId}, and POST /v1/passTypes/{passTypeId}/restore. Pass types were readable but not manageable, which made the guest-list workflow impossible to set up over the API.
  • DELETE without force=true is a safe check: if any guest still holds the pass it deletes nothing and returns status: false with the blocking entries in data.guestLists.
New: the rest of the guest-list lifecycle (docs) Behavioural fixes — all of these turn a 5xx into the 4xx it should always have been, which matters because a 5xx reads as retryable and none of these are.
  • A validation failure was a 500. Any rejection from body or query validation returned 500 with "error": "VALIDATION_ERROR" in the body — the status and the body disagreed. It is now 400, and the body’s status field agrees with the HTTP status. Affected ?org_id=, guest email, schedule startTime, and every other validated field.
  • A malformed ID in the path was a 500 or an opaque error. tourId, orgId, dayId, itemId, venueId, accommodationId, roomAssignmentId, bulletinId, tagId, templateId, passTypeId and guestListId are now validated up front and answer 400 VALIDATION_ERROR naming the parameter. GET /v1/tours/{tourId}/tags/{tagId}/usage previously returned a bare 500 Internal Server Error.
  • DELETE /v1/passTypes/{passTypeId} reported success when it had deleted nothing. Every 2xx response hardcoded status: true, so the safe check above came back { "status": true, "message": "Success" }. Responses now carry the operation’s real outcome. This is the one place where reading status rather than only the HTTP code matters.
  • A day-level guest-list cut-off on a day with no venue was a 500. It is now 400 with an explanation, because the cut-off is resolved against a venue’s time zone.
  • dayIds on the whole-tour read is now scoped to the tour in the path. A day ID belonging to another tour is absent from the response instead of being served.

2026-08-10

Behavioural fix
  • A request body of literal null (or a bare number, string, or top-level array) returned 500 on write endpoints — POST /v1/tours, POST /v1/venues, PUT /v1/venues/{venueId} and POST /v1/schedule/items/batch. It now returns a 4xx from the endpoint’s own validation, as it always should have. Worth noting because a 5xx reads as retryable and a malformed body never is.
Documentation
  • New: explicitVisibility is now documented on schedule items, venues and stays. This field was always accepted but never described, and the default behaviour surprises people: naming a restricted audience merges the tour’s current admin list into it, so visibilityList: ["alice-uuid"] on a tour with four admins is visible to five people. Send explicitVisibility: true to mean the list literally — on updates as well as creates. Do not send it with isAdminsOnly: true, which is implemented by that same merge.
  • Documented six endpoints that shipped on 2026-08-07 but had no reference section: GET /v1/tours/{tourId}/data, POST /v1/schedule/items/batch, POST /v1/schedule/items/{itemId}/restore, POST /v1/tours/{tourId}/days/{dayId}/confirm-all, GET /v1/venues/{venueId} and GET /v1/accommodation/{accommodationId}.
  • Corrected: GET /v1/reference/types was documented with a limit default of 24 — the number of tables that existed at the time. The default is uncapped and returns all of them; passing a limit equal to today’s count would silently drop any table added later.
  • Corrected: the totalUnread example on Chat didn’t reconcile with its own conversation list. totalUnread counts only the conversations this API returns, so it is lower than the in-app unread badge — the app counts direct messages, which this API never exposes. That is not missing data.
  • The schedule response example now shows schedule_item_tags and the visibility flags, which the Tags page already told you to read from it.

2026-08-07

Major expansion — 29 → 79 endpoints, plus the MCP server The API now covers essentially the whole Daysync feature set. Everything here is additive: no existing endpoint, field, or scope changed, and no integration needs updating. New: MCP server New scope
  • chat.read — the first addition to the scope catalogue since launch, taking it from 14 to 15. Read-only; there is no chat.write. Existing partners are unaffected until they are granted it.
New: Tags (docs)
  • Full CRUD on the tags that label schedule items, plus usage counts and PUT /v1/schedule/items/{itemId}/tags to set an item’s tags. Rides the existing schedule.read/schedule.write scopes, so no re-grant is needed.
  • GET /v1/tours/{tourId}/days/{dayId}/schedule now returns schedule_item_tags on each item. Tags were previously invisible over the API.
New: Reference data (docs)
  • GET /v1/reference/types returns every ID lookup table in one call — status types, item types, time types, travel types, reminder types, avatar colours, day types, guest list statuses, pickup methods, pass material types, venue types and categories, and room types.
  • Plus GET /v1/reference/age-restrictions, /v1/reference/venue-field-templates, /v1/reference/stay-field-templates, and GET /v1/tours/{tourId}/requesters.
  • These close a real gap: several write endpoints required IDs that had no read endpoint at all.
New: Chat, read-only (docs)
  • Ten endpoints for reading channel and group-chat history — conversations, messages, threads, pinned messages, reactions, members, and search.
  • 1:1 direct messages are never exposed. They are filtered from every list and search, and requesting one by ID returns 404.
New: whole-tour read
  • GET /v1/tours/{tourId}/data returns every day with its schedule items, venues, stays, guest lists, bulletins, and pass types in a single response — replacing six paged calls for the common “load me this tour” case.
  • This is the first endpoint requiring more than one scope: it needs the read scope for all six resources it returns. See Scopes.
New: custom fields, contacts, and rooms
  • Custom fields on schedule items, venues, and stays — read plus per-field ADD/UPDATE/REMOVE, and premade templates on venues and stays.
  • Contacts — the organization key-contact book (list, create, update) and the contacts linked to venues and stays. The book is what makes the previously-accepted keyContactIds field usable.
  • Rooms — list, create, update, delete, and restore the rooms within a stay.
New: get-one and batch operations
  • GET /v1/venues/{venueId} and GET /v1/accommodation/{accommodationId} return the full record, including contacts, custom fields, and visibility.
  • POST /v1/schedule/items/batch creates many items in one call; POST /v1/tours/{tourId}/days/{dayId}/confirm-all confirms a whole day; POST /v1/schedule/items/{itemId}/restore restores a soft-deleted item.
Fixed
  • GET /v1/tours/{tourId}/days/{dayId}/schedule returned 500 for any day containing a tagged item, while tag support was being added. Fixed before release.
  • POST /v1/accommodation/{accommodationId}/rooms returned an unhelpful 400 when creating a room with no occupants. It now works with guestList omitted.
  • Server-side (5xx) failures are now logged with the route that produced them, so support can trace a failed call.

2026-07-09

New endpoint
  • Added POST /v1/attachments (scope schedule.write) — upload a file by URL for attaching to schedule items, venues, accommodation, and guest list entries. Daysync fetches the URL server-side (SSRF-guarded: public https/http only, no redirects, 25 MB, 15 s) and returns a hosted link to pass in the attachment/attachments field. New Attachments page.
  • Clarified that attachment.link is a Daysync storage key (from the upload endpoint), not an external URL — corrected the Schedule → Attachments guidance and the Venues note.
  • Endpoint count is now 29. (Superseded — see 2026-08-07.)

2026-06-12

Documentation
  • Documented GET /v1/day-types — live since launch but previously missing from the docs. It returns the valid day_type_id values required when creating or editing tours.
  • New pages: Apply for API Access, Callback URLs, and Environments.
  • Rewrote Authentication — clarified the two-credential model (shared OAuth Client vs per-partner API keys), added an OAuth error reference, token-type guide (access_token vs id_token vs refresh_token), refresh/revocation behaviour, and JWKS details for server-side validation.
  • Rewrote Quickstart as an end-to-end walkthrough — from received credentials to a refreshed token in five steps.
  • Corrected the endpoint count (28) on the overview and added the missing scope row on Scopes & Permissions.
  • Destructive-edit warning on Edit a tourday_list is declarative: days omitted from the list are permanently deleted along with all their content. Documented the safe single-day update pattern.
  • New Visibility and Attachments sections on the schedule page — where visibility IDs come from, the omit-everything footgun (isEveryone defaults to false), conflicting-flag 400, link-based attachment model, and attachment upsert/delete semantics on update.
  • Every numeric type field (type, startTimeTypeId, statusTypeId, itemColorId, venueTypeId, pickupMethodId, …) now links to its Field Reference value table.
  • Four previously undocumented ID catalogues added to Field Reference, verified against backend seed data: reminder types (reminder[].typeId), travel types (traveltype, 11 modes), age restrictions (ageRestrictionId), and avatar colors (avatarColorId). Also noted that venue statusTypeId shares the schedule status-type table.
  • New Retries & idempotency section — per-method retry safety, the POST-after-timeout duplicate trap, and which responses are worth retrying.
  • New Data model at a glance diagram — the org → tour → day hierarchy, which resources are day-scoped vs tour-scoped, and the ID type at each level.
  • Documented the /oauth2/userInfo endpoint for fetching the connected user’s profile without decoding JWTs.
  • Marked venue venueCategoryId as required-in-practice in the body table (was contradicting the note below it).

2026-06-08

Documentation