Skip to main content
Attachments are files linked to a resource (schedule item, venue, accommodation, or guest list entry). Daysync hosts attachment files — an attachment’s link is a Daysync-owned storage key, and Daysync builds the public URL from it on read. Every attachment flow is upload, then attach. What has changed is that there are now three ways to upload and a first-class way to attach, list and download: POST /v1/attachments is unchanged and still supported; the newer endpoints mint the same kind of key, so a key from any upload source works with any attach path. All endpoints require the standard authentication headers.

Upload an attachment

Fetches the file at url and stores it in Daysync-hosted storage. Returns the storage key to use as attachment.link. Scope: schedule.write Body Example
Response data
The URL you pass is fetched once, server-side, and the bytes are copied into Daysync storage. Constraints:
  • https/http only. Other schemes are rejected.
  • No redirects. The URL must return the file directly (200); a 3xx is rejected — resolve redirects yourself and pass the final URL.
  • Public hosts only. URLs that resolve to private, loopback, link-local, or cloud-metadata addresses are rejected.
  • 25 MB maximum, 15 s fetch timeout.
A URL that violates any of these returns 400.

Upload the bytes directly

For when you have the file itself and no public URL to point at. Scope: schedule.write. Returns { key, link, fileName, fileSizeBytes, contentType }. key and link are the same value under two names, so you can use either the newer attach endpoint or the existing attachment.link field without renaming anything.
256 KB is a hard ceiling, and it is smaller than the 25 MB file limit for a reason: the request body crosses a 512 KB gateway limit and a payload-transformation limit above it, and neither can be raised from here. Over 256 KB you get a 413 naming the two alternatives. Use an upload ticket, or POST /v1/attachments with a URL.

Get a signed upload ticket

For a large file that is local to your application. Scope: schedule.write. Returns { uploadUrl, method, headers, key, expiresInSeconds, expiresAt, contentLength, contentType }. PUT the raw bytes to uploadUrl and send the returned headers exactly as given, then call the attach endpoint with key.
content-length and content-type are part of the signature, not hints. A body of a different length, or a different declared type, is rejected by storage — that is what bounds the upload. The ticket also expires; after that, PUT fails and attaching the key returns 404.

Attach an uploaded file

Scope: schedule.write. Returns { fileId, entityType, entityId, key, fileName, fileSizeBytes, alreadyAttached }. This is additive — it never touches the entity’s existing attachments — and idempotent: attaching the same key to the same entity again returns the first attachment with alreadyAttached: true, so a retry is safe.
Prefer this over re-sending an entity’s whole update body with attachments, which replaces the entity’s attachment set. If you only want to add a file, use this endpoint.

List a tour’s files

Scope: tours.read, and it also requires schedule.read, venues.read, accommodation.read and guestlist.read — the listing can surface rows from all of them. Returns { items, totalCount, returnedCount, hasMore }. Each item carries fileId, key, fileName, name, description, fileSize, fileSizeBytes, contentTypeHint, entityType, entityId, entityName, dayId and createdAt. totalCount is the total before paging, so hasMore is exact rather than inferred from a full page. Only files you are allowed to see are listed: a file on a private item you are not on does not appear, and files on other tours are not reachable. contentTypeHint is inferred from the file name and is advisory — the download endpoint reports the authoritative type. Chat files are not included here; use the chat file search.

Download a file

Scope: same as the listing. Returns { fileId, downloadUrl, expiresInSeconds, expiresAt, fileName, fileSizeBytes, contentType, entityType, entityId }.
downloadUrl is signed and short-lived — about five minutes. Fetch it or hand it to the user straight away; do not store it, embed it, or treat it as a permanent link. Call this endpoint again for a fresh URL.
Returns 404 both when the file does not exist and when you are not allowed to see it, so it cannot be used to probe for files on other tours.

Allowed content types

Applies to every upload source, including POST /v1/attachments. Images (JPEG, PNG, GIF, WebP, HEIC/HEIF, BMP, TIFF, ICO) · PDF · Word, Excel, PowerPoint, RTF · plain text, CSV, Markdown, iCalendar, JSON · ZIP · audio (MP3, M4A, AAC, WAV, WebM) · video (MP4, MOV, WebM). image/svg+xml, text/html, scripts and executables are not allowed. Anything else returns 415 and the error lists the accepted types.

Attaching the file to a resource

Use the returned link in the attachment (single) or attachments (array) field on a create or update:
Only link is required. Attachments are supported on: Bulletins do not support attachments.
These four — schedule items, venues, accommodation and pass types — are exactly the entity kinds POST /v1/tours/{tourId}/files/attach accepts and GET /v1/tours/{tourId}/files lists. Guest list entries are not among them: a attachment field sent on a guest-list body is not stored, so it will not appear in the file listing or be downloadable. Attach the file to the day’s schedule item or pass type instead.
Always obtain link from POST /v1/attachments. Don’t pass an external URL (e.g. your own CDN link) directly as attachment.link — Daysync treats link as one of its own storage keys and builds the public URL from it, so a foreign URL renders as a broken link.

See also: Schedule, Venues, Scopes & Permissions.