Duvet DOCS
GitHub

Attachment Resource

An attachment represents a file uploaded to Duvet, either attached to a message or used as a space emoji/sticker.

Attachment Object

FieldTypeDescription
id stringThe attachment's UUID (v7, time-ordered)
message_id string?The message this attachment belongs to (null for space emoji/sticker attachments)
filename stringOriginal filename
content_type stringMIME type of the file
size_bytes integerFile size in bytes
width integer?Image or video width in pixels
height integer?Image or video height in pixels
created_at ISO 8601When the attachment was created
deleted_at ISO 8601?Soft delete timestamp (present when attachment has been deleted)
url stringSigned download URL with HMAC signature and expiry. Only present on message attachments.

Fields r2_key, owner_id, storage_bucket_id, updated_at, and expires_at are hidden from API responses.

Message attachments require a valid signed URL (url field) to download. Non-message attachments (avatars, emojis, stickers) can be accessed directly via /attachments/{id} without a signature.

Example

{
  "id": "019cf96a-4e2a-73b1-9a0f-2b8c5d1e7f34",
  "message_id": "019cf96a-3d5b-72c5-830f-75e85355d078",
  "filename": "screenshot.png",
  "content_type": "image/png",
  "size_bytes": 245760,
  "width": 1920,
  "height": 1080,
  "created_at": "2025-10-30T00:25:00.000Z",
  "deleted_at": null,
  "url": "/attachments/019cf96a-4e2a-73b1-9a0f-2b8c5d1e7f34?sig=a1b2c3d4...&exp=1735689600"
}

Downloading Attachments

GET /attachments/{attachmentId}

Download an attachment. Message attachments require a valid signature (sig and exp query params). Non-message attachments (avatars, emojis, stickers) can be accessed without a signature. Returns a 307 redirect to a presigned R2/S3 URL (1 hour expiry) that supports Range requests.

FieldTypeDescription
sig stringHMAC-SHA256 signature (from the url field)
exp stringUnix timestamp expiry (from the url field)
stream ?stringSet to 1 to stream the file directly through the server instead of redirecting (bypasses CORS restrictions)

Re-signing Expired URLs

POST /attachments/{attachmentId}/resign

Generate a fresh signed URL for an attachment. Use when the original signed URL is expiring. Requires authentication.

Returns: {"url": "/attachments/{id}?sig=...&exp=..."} with a new 24-hour TTL.