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
| Entity | Description |
|---|---|
| Notebook | An organizational container for notes (like a folder) |
| Note | A document with title, Markdown body, and metadata |
| Note Link | A directional link from a note to another entity |
| Note Tag | A label attached to a note for categorization |
| Note Tag Definition | A tenant-scoped tag type (name + optional color) |
| Note Attachment | A 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 thebody field. The API stores raw Markdown — rendering is the client’s responsibility.
Frontmatter as metadata
YAML frontmatter is parsed and stored in the note’smetadata field as structured JSON. This enables structured data (tags, custom fields, dates) alongside free-form content.
Wikilinks and backlinks
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
Cross-domain links
Note links aren’t limited to other notes. Link a note to any entity type:| Entity Type | Example use case |
|---|---|
note | Wiki-style links between notes |
contact | Meeting notes linked to attendees |
task | Notes linked to tasks they describe |
event | Notes linked to calendar events |
deal | Notes linked to CRM deals |
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 haveis_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.
Full-text search
Search across note titles and body content using theq parameter on GET /api/v1/notes.
MCP Tools
| Tool | Description |
|---|---|
notes_search | Free-text search across all notes |
notes_get | Get a note’s full content plus backlinks |
notes_create | Create a new note with optional notebook, tags, and links |
notes_update | Modify note content, title, tags, or notebook |
notes_delete | Remove a note |
notes_list_backlinks | See everything that links to a given note |
API Endpoints
See the API Reference for the full endpoint list with request/response details.Examples
Create a notebook and a note
Link a note to a contact and a task
Find all notes linked to a contact
Daily Dispatch email delivery
When a note is written with a tag that matches one of your tenant’sdigest_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
- You (or a reporter blueprint) write a note with the
daily-dispatchtag. - A Postgres LISTEN/NOTIFY trigger on
notes_changesfires. - The digest service inserts a row into
digest_email_deliverieswith statuspendingand wakes the delivery worker. - 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 fromintegration_keys(providerpostmark), and POSTs one message per recipient. - Success marks
sentwith the PostmarkMessageIDs. Transient failures (5xx, 429, network) retry with exponential backoff (1m → 5m → 15m → 1h), giving up and markingfailed_permanentafter 5 attempts. Permanent failures (401, 422) markfailed_permanenton the first attempt.
Configure your tenant
Per-tenant config lives intenant_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.
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
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)
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.