Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cryguy/hashboard/llms.txt

Use this file to discover all available pages before exploring further.

Comments and attachments let you annotate any card or standalone document with threaded text notes and uploaded files. Comments are markdown bodies attributed to the posting principal; attachments are binary files stored on disk whose bytes are served back over a dedicated download endpoint. Both resources follow the visibility of their subject — there is no separate access control to configure.

Comments

A comment belongs to exactly one card or exactly one document (never both). The database enforces this with a CHECK constraint. Comments are returned oldest-first by default and are never paginated — use the ?limit= parameter on the activity feeds if you need a bounded window of recent events instead.

List comments on a card

id
string
required
The card ID.
GET /api/v1/cards/{id}/comments
Returns all comments on the card, oldest first. Response — array of Comment objects:
id
string
UUID of the comment.
cardId
string | null
The card this comment belongs to.
docId
string | null
Always null for card comments.
authorId
string
Principal ID of the author.
body
string
Markdown body of the comment.
createdAt
string
ISO 8601 timestamp.
updatedAt
string
ISO 8601 timestamp.

Add a comment to a card

POST /api/v1/cards/{id}/comments
id
string
required
The card ID.
Request body:
body
string
required
Markdown body of the comment.
Returns 201 with the created Comment object.

List comments on a document

GET /api/v1/docs/{id}/comments
id
string
required
The document ID.
Returns all comments on the standalone document, oldest first.

Add a comment to a document

POST /api/v1/docs/{id}/comments
id
string
required
The document ID.
Request body:
body
string
required
Markdown body of the comment.
Returns 201 with the created Comment object.

Delete a comment

DELETE /api/v1/comments/{id}
id
string
required
The comment ID.
Only the comment author’s household may delete it. The creator of the subject (card or document) may also delete any comment on their resource as a moderation action. Returns { "ok": true }.

Attachments

Attachments are binary files uploaded to a card or a standalone document. The file bytes live on disk in ATTACHMENTS_DIRnot in SQLite — so a complete backup must capture both the database file and the attachments directory. The path on disk is derived from the attachment id and is never stored in the database; this prevents path-injection and means a row without its bytes is impossible by construction (writes go file-then-row; deletes go row-then-file).
Uploads must be sent as raw bytes in the request body with a binary Content-Type such as application/octet-stream. Do not use multipart/form-data or curl -F. SvelteKit treats multipart/form-data, application/x-www-form-urlencoded, and text/plain as form submissions and rejects cross-site ones with 403 before the route ever runs. This check is skipped in the development server, so the failure only surfaces against a production build — always test uploads against a production build before deploying.
The ?filename= query parameter is required. It is stored as-is for display purposes and its extension determines the Content-Type served on download — the Content-Type header you send on upload is ignored entirely. This prevents a malicious upload from claiming a dangerous MIME type.
A card description doc is not a valid attachment subject. Files pasted or dropped into a card’s description belong to the card, not to the underlying doc. Attempting to attach to a card-description doc returns 400. Always attach files to the card itself when working with card descriptions.

List attachments on a card

GET /api/v1/cards/{id}/attachments
id
string
required
The card ID.
Returns all files attached to the card, oldest first. Response — array of Attachment objects:
id
string
UUID of the attachment. Use this in /attachments/{id} to download the bytes.
cardId
string | null
The card this file belongs to.
docId
string | null
Always null for card attachments.
filename
string
Original filename as uploaded, sanitized for display.
contentType
string
Server-resolved MIME type based on file extension.
size
number
File size in bytes.
sha256
string
Hex-encoded SHA-256 of the file bytes. Used as the ETag for cache validation.
createdBy
string
Principal ID of the uploader.
createdAt
string
ISO 8601 timestamp.

Upload a file to a card

POST /api/v1/cards/{id}/attachments?filename=FILENAME
id
string
required
The card ID.
filename
string
required
The filename including extension. The extension determines the served Content-Type.
Request body: raw file bytes. Set Content-Type: application/octet-stream. Returns 201 with the created Attachment object.
curl -X POST https://hashboard.example.com/api/v1/cards/CARD_ID/attachments?filename=screenshot.png \
  -H 'Authorization: Bearer hb_TOKEN' \
  -H 'Content-Type: application/octet-stream' \
  --data-binary @screenshot.png

List attachments on a document

GET /api/v1/docs/{id}/attachments
id
string
required
The standalone document ID.
Returns all files attached to the document, oldest first.

Upload a file to a document

POST /api/v1/docs/{id}/attachments?filename=FILENAME
id
string
required
The standalone document ID. Returns 400 if this document is a card description doc — attach to the card instead.
filename
string
required
The filename including extension.
Request body: raw file bytes. Set Content-Type: application/octet-stream. Returns 201 with the created Attachment object.

Download an attachment

GET /attachments/{id}
id
string
required
The attachment ID.
Returns the raw file bytes. The response carries:
  • Content-Type resolved server-side from the stored filename extension (never the uploader’s claim).
  • Content-Disposition: attachment for all file types except a small allowlist of inline image formats. SVG is explicitly excluded from inline serving — it can carry script and this endpoint is on the app’s own origin.
  • ETag derived from the sha256 hash for cache revalidation.
The attachment download path deliberately has no filename segment (e.g. /attachments/{id}, not /attachments/{id}/screenshot.png). A trailing .md anywhere in the path would be intercepted by hooks.server.ts as a markdown rendition request before the route handler runs. To embed an attachment inline in a markdown document, use ![alt text](/attachments/{id}).
Read access to this endpoint requires read access to the attachment’s subject (card or document).

Delete an attachment

DELETE /api/v1/attachments/{id}
id
string
required
The attachment ID.
The uploader’s household or the subject’s creator (moderation) may delete the attachment. Returns { "ok": true }.

Embedding attachments in markdown

Once uploaded, any attachment can be embedded inline in a card description or standalone document:
![Screenshot of the dashboard](/attachments/ATTACHMENT_ID)
The sha256 field on the attachment record is used as the HTTP ETag, so browsers cache the bytes and only re-fetch when the file changes.

Build docs developers (and LLMs) love