Skip to main content

Overview

Personal knowledge management — structured and unstructured notes with bidirectional linking. Inspired by Obsidian, Bear, and Apple Notes, but API-first. Store Markdown content, organize in notebooks, link notes to each other and to entities in other domains (contacts, tasks, events, deals). Notes are the connective layer for your knowledge graph. A note about a meeting links to the event, the attendees’ contact cards, and the follow-up tasks.

Data Model

Entities

EntityDescription
NotebookAn organizational container for notes (like a folder)
NoteA document with title, Markdown body, and metadata
Note LinkA directional link from a note to another entity
Note TagA label attached to a note for categorization
Note Tag DefinitionA tenant-scoped tag type (name + optional color)
Note AttachmentA URL reference to an external file attached to a note

Relationships

  • A Note optionally belongs to a Notebook
  • A Note can have many Links, Tags, and Attachments
  • Links point from a note to any entity type (note, contact, task, event, deal)

Key Concepts

Notebooks

Notebooks are optional organizational containers. Notes can exist without a notebook. Each notebook tracks a note count for display purposes.

Markdown body

Note content is stored as Markdown in the body field. The API stores raw Markdown — rendering is the client’s responsibility.

Frontmatter as metadata

YAML frontmatter is parsed and stored in the note’s metadata field as structured JSON. This enables structured data (tags, custom fields, dates) alongside free-form content. Notes support [[wikilink]] syntax for linking between notes. The system maintains bidirectional link awareness:
  • Outgoing links (GET /api/v1/notes/{id}/links) — what this note links to
  • Backlinks (GET /api/v1/notes/{id}/backlinks) — what notes link to this note
This creates a navigable knowledge graph. Note links aren’t limited to other notes. Link a note to any entity type:
Entity TypeExample use case
noteWiki-style links between notes
contactMeeting notes linked to attendees
taskNotes linked to tasks they describe
eventNotes linked to calendar events
dealNotes linked to CRM deals
Use GET /api/v1/backlinks/{entity_type}/{entity_id} to find all notes that link to any entity across all domains.

Tags

Tags are lightweight labels for categorizing notes. Tag definitions are tenant-scoped (created once, applied to many notes). Each tag has a name and optional color.

Attachments

Attachments are URL references to external files (images, documents, etc.). The API stores the URL and metadata — it does not host files.

Pinning and archiving

Notes have is_pinned and is_archived boolean fields. Pinned notes appear at the top of lists. Archived notes are hidden from default list results but can be filtered for. Search across note titles and body content using the q parameter on GET /api/v1/notes.

MCP Tools

ToolDescription
notes_searchFree-text search across all notes
notes_getGet a note’s full content plus backlinks
notes_createCreate a new note with optional notebook, tags, and links
notes_updateModify note content, title, tags, or notebook
notes_deleteRemove a note
notes_list_backlinksSee everything that links to a given note
See the MCP guide for setup instructions.

API Endpoints

See the API Reference for the full endpoint list with request/response details.

Examples

Create a notebook and a note

# Create a notebook
curl -X POST https://api.worktruck.app/api/v1/notebooks \
  -H "Authorization: Bearer bsk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"name": "Meeting Notes"}'

# Create a note in the notebook
curl -X POST https://api.worktruck.app/api/v1/notes \
  -H "Authorization: Bearer bsk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Q2 Planning Meeting",
    "body": "## Decisions\n\n- Launch API by April 15\n- Hire two more engineers\n\n## Action Items\n\nSee linked tasks.",
    "notebook_id": "uuid-of-notebook"
  }'
# Link to a contact
curl -X POST https://api.worktruck.app/api/v1/notes/{id}/links \
  -H "Authorization: Bearer bsk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "target_entity_type": "contact",
    "target_entity_id": "uuid-of-contact"
  }'

# Link to a task
curl -X POST https://api.worktruck.app/api/v1/notes/{id}/links \
  -H "Authorization: Bearer bsk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "target_entity_type": "task",
    "target_entity_id": "uuid-of-task"
  }'

Find all notes linked to a contact

curl https://api.worktruck.app/api/v1/backlinks/contact/{contact_id} \
  -H "Authorization: Bearer bsk_live_..."

Daily Dispatch email delivery

When a note is written with a tag that matches one of your tenant’s digest_tags (default: daily-dispatch), Worktruck renders the note to HTML + plaintext and emails it via Postmark. This is the delivery path for DEV-12’s Daily Dispatch reporter and for any note you tag into it manually.

How it works

  1. You (or a reporter blueprint) write a note with the daily-dispatch tag.
  2. A Postgres LISTEN/NOTIFY trigger on notes_changes fires.
  3. The digest service inserts a row into digest_email_deliveries with status pending and wakes the delivery worker.
  4. The worker renders the Markdown body (GitHub Flavored Markdown via pulldown-cmark) into an inline-CSS HTML email + plaintext, loads your tenant’s Postmark token from integration_keys (provider postmark), and POSTs one message per recipient.
  5. Success marks sent with the Postmark MessageIDs. Transient failures (5xx, 429, network) retry with exponential backoff (1m → 5m → 15m → 1h), giving up and marking failed_permanent after 5 attempts. Permanent failures (401, 422) mark failed_permanent on the first attempt.

Configure your tenant

Per-tenant config lives in tenant_configs:
  • digest_tags (text[], default {daily-dispatch}) — which tag(s) on a note should trigger delivery.
  • digest_from_email (text, nullable) — sender address. Must be a Postmark-verified sender signature on your tenant’s Postmark server.
  • digest_recipient_emails (text[], nullable) — the to-addresses. All recipients receive the same rendered email; the worker sends one Postmark call per address.
If the tenant has no Postmark integration key, the row is marked failed_permanent with category no_postmark_key and an activity log entry is written with a deep link to the integrations page.

List your deliveries

curl https://api.worktruck.app/api/v1/me/digest-deliveries \
  -H "Authorization: Bearer bsk_live_..."
Returns a cursor-paginated list of the tenant’s delivery rows (id, note_id, status, attempts_count, last_attempt_at, message_ids, failure_category, failure_detail, created_at). Includes all states: pending, sending, sent, failed_transient, failed_permanent.

Resend a delivery (admin)

curl -X POST \
  https://api.worktruck.app/api/v1/admin/digest-deliveries/{note_id}/resend \
  -H "Authorization: Bearer bsk_live_..."
Requires an API key with is_tenant_admin set. Creates a fresh pending row that chains back to the most recent delivery for the note via attempt_of, bypassing the initial-enqueue dedup. Returns 202 Accepted with the new row’s delivery_id.

At-most-one-per-note semantics

The initial enqueue path has a partial UNIQUE on (tenant_id, note_id) WHERE attempt_of IS NULL, so a second notification for the same note is absorbed as a no-op. Admin resends insert rows with attempt_of set and are therefore not constrained — the audit trail keeps every attempt.