Harbor
Developer docs menu

API reference

Notes API

Notes are Harbor's core resource: syncable records with a sanitized HTML body you can write in Markdown or HTML.

The notes endpoints cover the full lifecycle of a note: create, list with filters, fetch, partial update, append, PDF export, trash/delete, and the attachment-hash swap that powers client-side encryption. Every endpoint on this page requires a bearer token with the notes scope.

Notes are syncable records: every mutation allocates an update sequence number (USN) in the same transaction that writes the row, deletes are tombstones, and writes are last-write-wins (no locking — cross-device divergence is resolved by sync’s conflict copies). Mutations return {"note": {...}, "usn": n} so clients can advance their sync cursor; reads return the bare note object.

The note object

FieldTypeDescription
idstringClient-supplied UUID (server-generated when omitted at create). To get one for a note you already have, copy it from the note’s Info panel in any Harbor app, or read it from harbor notes list.
titlestringUp to 255 characters (runes). Opaque ciphertext when is_encrypted.
contentstringSanitized HTML fragment — an allowlist subset of standard HTML, not ENML. Opaque ciphertext when is_encrypted.
content_hashstringHash over the final stored bytes (the sanitized HTML, or the ciphertext).
content_lengthintLength of the final stored bytes. Capped at 5 MiB (note_too_large).
word_countintWord count of the stored body. Always 0 for encrypted notes.
notebook_idstringThe owning notebook.
is_encryptedboolClient-authoritative encryption flag — see Encryption below.
thumbnailstringExplicit list thumbnail: "" or a sha256:<64-hex> resource reference. Always present.
source_urlstringURL the note came from, if any.
sourcestringProvenance marker, ≤ 64 chars (e.g. "web.clip").
is_web_clipbooltrue when the body is a read-only web-page snapshot (rendered in a sandboxed viewer, not re-editable).
authorstringFree-form author string.
latitude / longitude / altitudenumberLocation metadata. Omitted from responses when unset.
reminder_timeintReminder time, epoch ms. Omitted when unset.
reminder_done_timeintWhen the reminder was completed, epoch ms. Omitted when unset.
is_publicboolPublic-share marker. Always present.
public_tokenstringShare token for building the public URL; "" when private.
public_shared_atintEpoch ms of the most recent publish. Omitted when never shared.
usnintServer-assigned update sequence number (sync ordering signal).
deletedboolTombstone flag.
updated_at / created_atintUTC epoch milliseconds.
{
  "id": "9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68",
  "title": "Quarterly plan",
  "content": "<p>Hello <strong>world</strong></p>",
  "content_hash": "f1d2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
  "content_length": 35,
  "word_count": 2,
  "notebook_id": "5b1f2c9a-77d4-4e21-b9c3-2a8f6d4e1c05",
  "is_encrypted": false,
  "thumbnail": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "source_url": "",
  "source": "",
  "is_web_clip": false,
  "author": "",
  "is_public": false,
  "public_token": "",
  "usn": 88,
  "deleted": false,
  "updated_at": 1784000000000,
  "created_at": 1781200000000
}

Content format: Markdown in, HTML out

The canonical note body is a sanitized HTML fragment. On any write, the content_format field controls how content is interpreted: html (the default) or markdown. Markdown (CommonMark + GFM) is converted to HTML server-side, so every client ends up editing one format. On reads, ?format=markdown returns the body as best-effort Markdown.

Attachments and rich media are typed <harbor-embed> elements with a fixed attribute set (type, resource — a content-addressed sha256: reference, src, title, width, height, align); plain <img> with an http(s) src is also tolerated. The body is capped at 5 MiB of final stored bytes (note_too_large when exceeded).

Encryption

is_encrypted is client-authoritative: when true, the client sends opaque ciphertext for both title and content, and the server stores it verbatim — never sanitized, converted, indexed, or searchable. word_count is 0, and content_hash / content_length are computed over the ciphertext. Attached files follow the note: the client re-encrypts each file, uploads it under a new content hash, and calls POST /notes/:id/attachments/swap to re-point the note and purge the old plaintext blobs.

Moving a note between notebooks

A note moves by PATCHing its notebook_id. If the destination notebook has default_encrypt and the note is still plaintext, the encryption and the move have to happen in the same request. The server refuses the half-move:

{
  "code": "cannot_move_plaintext_into_encrypted",
  "message": "That notebook keeps its notes encrypted; encrypt the note and move it in the same request."
}

That is a 422, and nothing is written and no USN is spent when it fires — the note is untouched, so a client can seal it and retry.

The request a correct client sends carries the ciphertext, the flag and the destination together, in one write:

curl -X PATCH https://app.harbor.my/api/v1/notes/9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68 \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "HRBC2.…",
    "content": "HRBC2.…",
    "content_format": "html",
    "is_encrypted": true,
    "notebook_id": "b9c1e0d7-4f2a-4c8b-9e15-3a7d6c2f0b48"
  }'

The client does the encrypting — the server holds no keys and cannot do it for you. A complete move therefore means sealing title and content, re-encrypting each embedded attachment and uploading it under a new hash, then calling POST /notes/:id/attachments/swap once the note is saved.

Four scoping facts, because these are the edges integrators hit:

  • The guard fires only on an actual move, compared against the resolved destination. Re-saving a plaintext note that already lives in the encrypting notebook is fine, and so is echoing the same notebook_id back.
  • An empty notebook_id means the user’s default notebook — and if that notebook has default_encrypt, the rule applies to it.
  • The guard is on PATCH /notes/:id only. It is deliberately not on POST /notes and not on POST /sync/push: the native apps move notes through the sync queue, and rejecting one record mid-push would wedge a client’s whole queue. Both absences are permanent. Sealing on the way in is the client’s job; the 422 is a backstop for the public API and third-party scripts, not the mechanism.
  • Moving a note out changes nothing. An encrypted note stays encrypted wherever it goes, stays out of the search index, and is never decrypted for you. Removing encryption is a separate, explicit write (is_encrypted: false with the plaintext body).

List notes

GET /api/v1/notes · scope: notes

List the user’s notes. Trashed notes are excluded unless deleted=true.

FieldTypeRequiredDescription
limitintnoDefault 100, hard cap 500 (clamped).
offsetintnoDefault 0.
orderstringnoDefault -updated_at. Sortable: updated_at, created_at, title, usn (- prefix = descending).
notebook_idstringnoFilter to one notebook.
tagstringnoFilter to notes carrying this tag id.
stackstringnoNotes in any live notebook under this stack label.
updated_sinceintnoEpoch ms; returns notes with updated_at >= this value.
deletedboolnoDefault false; true includes trashed notes.
fieldsstringnometa omits content from each row (lighter list payloads).

Each row adds two list-only conveniences, so a list view needs no per-row fetches:

  • tags — the note’s live tags as {id, name}, sorted by name. Omitted when the note has none. Tag names are plaintext, so encrypted notes still list their tags.
  • cover — the first thumbnail-eligible attachment: hash, mime, is_encrypted, thumb_status (pending | processing | done | failed | skipped), and thumb_small_key / thumb_medium_key when done. Omitted when the note has no attachments.

Both are omitted from single-note reads and are unaffected by fields=meta (only content is blanked).

curl "https://app.harbor.my/api/v1/notes?notebook_id=5b1f2c9a-77d4-4e21-b9c3-2a8f6d4e1c05&fields=meta&limit=50" \
  -H "Authorization: Bearer $HARBOR_TOKEN"
{
  "data": [
    {
      "id": "9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68",
      "title": "Quarterly plan",
      "notebook_id": "5b1f2c9a-77d4-4e21-b9c3-2a8f6d4e1c05",
      "is_encrypted": false,
      "tags": [
        { "id": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "name": "not-reviewed" }
      ],
      "cover": {
        "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
        "mime": "application/pdf",
        "is_encrypted": false,
        "thumb_status": "done",
        "thumb_small_key": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855_thumb_s",
        "thumb_medium_key": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855_thumb_m"
      },
      "usn": 88,
      "deleted": false,
      "updated_at": 1784000000000,
      "created_at": 1781200000000
    }
  ],
  "paging": { "limit": 50, "offset": 0, "total": 1, "has_more": false }
}

Errors:

  • 422 validation_failed — unknown sort field in order.

Python

import os
import requests

BASE = "https://app.harbor.my/api/v1"
headers = {"Authorization": f"Bearer {os.environ['HARBOR_TOKEN']}"}

resp = requests.get(
    f"{BASE}/notes",
    headers=headers,
    params={"order": "-updated_at", "fields": "meta", "limit": 50},
)
resp.raise_for_status()

for note in resp.json()["data"]:
    print(note["id"], note["title"])

JavaScript

const BASE = "https://app.harbor.my/api/v1";

const params = new URLSearchParams({ order: "-updated_at", fields: "meta", limit: "50" });
const res = await fetch(`${BASE}/notes?${params}`, {
  headers: { Authorization: `Bearer ${process.env.HARBOR_TOKEN}` },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);

const { data, paging } = await res.json();
for (const note of data) console.log(note.id, note.title);

Go

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

const base = "https://app.harbor.my/api/v1"

func main() {
	req, _ := http.NewRequest("GET", base+"/notes?order=-updated_at&fields=meta&limit=50", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("HARBOR_TOKEN"))

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	var out struct {
		Data []struct {
			ID    string `json:"id"`
			Title string `json:"title"`
		} `json:"data"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		panic(err)
	}
	for _, n := range out.Data {
		fmt.Println(n.ID, n.Title)
	}
}

Create a note

POST /api/v1/notes · scope: notes

Create a note. The server resolves and validates the notebook, derives the content fields (sanitize + hash/length/word-count, or stored opaque when encrypted), and allocates a USN in the writing transaction.

FieldTypeRequiredDescription
idstringnoClient UUID; server-generated when absent. Must be valid and unused.
notebook_idstringnoA live notebook id. Defaults to the user’s default notebook.
titlestringno≤ 255 chars (runes); ciphertext when is_encrypted.
contentstringnoHTML or Markdown per content_format; ciphertext when is_encrypted.
content_formatstringnohtml (default) or markdown.
is_encryptedboolnoDefault false. Client-authoritative.
thumbnailstringno"" or sha256:<64-hex> (format-validated; the blob is not verified).
source_urlstringno
sourcestringnoProvenance marker, ≤ 64 chars (e.g. "web.clip").
is_web_clipboolnoDefault false. true stores a read-only page snapshot via the layout-preserving clip allowlist.
authorstringno
latitude / longitude / altitudenumberno
reminder_timeintnoEpoch ms.
curl https://app.harbor.my/api/v1/notes \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "notebook_id": "5b1f2c9a-77d4-4e21-b9c3-2a8f6d4e1c05",
    "title": "Quarterly plan",
    "content": "# Hello\n\nworld",
    "content_format": "markdown"
  }'

Response — 201 Created:

{
  "note": {
    "id": "9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68",
    "title": "Quarterly plan",
    "content": "<h1>Hello</h1>\n<p>world</p>",
    "content_hash": "f1d2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
    "content_length": 27,
    "word_count": 2,
    "notebook_id": "5b1f2c9a-77d4-4e21-b9c3-2a8f6d4e1c05",
    "is_encrypted": false,
    "thumbnail": "",
    "usn": 88,
    "deleted": false,
    "updated_at": 1784000000000,
    "created_at": 1784000000000
  },
  "usn": 88
}

Errors:

  • 422 validation_failed — malformed id or JSON, notebook_id not a live notebook, or a malformed thumbnail.
  • 422 note_title_too_long — title over 255 characters.
  • 422 note_too_large — final stored body over the 5 MiB cap.
  • 409 conflict — the supplied id is already taken.

Python

import os
import requests

BASE = "https://app.harbor.my/api/v1"
headers = {"Authorization": f"Bearer {os.environ['HARBOR_TOKEN']}"}

resp = requests.post(
    f"{BASE}/notes",
    headers=headers,
    json={
        "title": "Quarterly plan",
        "content": "# Hello\n\nworld",
        "content_format": "markdown",
    },
)
resp.raise_for_status()

body = resp.json()
print(body["note"]["id"], "usn:", body["usn"])

JavaScript

const BASE = "https://app.harbor.my/api/v1";

const res = await fetch(`${BASE}/notes`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.HARBOR_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "Quarterly plan",
    content: "# Hello\n\nworld",
    content_format: "markdown",
  }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);

const { note, usn } = await res.json();
console.log(note.id, "usn:", usn);

Go

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

const base = "https://app.harbor.my/api/v1"

func main() {
	payload, _ := json.Marshal(map[string]string{
		"title":          "Quarterly plan",
		"content":        "# Hello\n\nworld",
		"content_format": "markdown",
	})

	req, _ := http.NewRequest("POST", base+"/notes", bytes.NewReader(payload))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("HARBOR_TOKEN"))
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	var out struct {
		Note struct {
			ID string `json:"id"`
		} `json:"note"`
		USN int64 `json:"usn"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		panic(err)
	}
	fmt.Println(out.Note.ID, "usn:", out.USN)
}

Get a note

GET /api/v1/notes/:id · scope: notes

Fetch one note. Returns 404 when the note is missing or trashed (unless ?deleted=true).

FieldTypeRequiredDescription
deletedboolnotrue returns the note even if trashed.
formatstringnomarkdown returns content as best-effort Markdown. Ignored for encrypted notes.

The response is the bare note object — no usn wrapper, and no tags or cover (those are list-only; fetch a note’s tags via GET /notes/:id/tags). latitude, longitude, altitude, reminder_time, and reminder_done_time are omitted when unset; public_shared_at is omitted when never shared; is_public is always present with public_token "" when private; thumbnail is always present ("" when unset).

curl https://app.harbor.my/api/v1/notes/9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68 \
  -H "Authorization: Bearer $HARBOR_TOKEN"
{
  "id": "9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68",
  "title": "Quarterly plan",
  "content": "<p>Hello <strong>world</strong></p>",
  "content_hash": "f1d2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
  "content_length": 35,
  "word_count": 2,
  "notebook_id": "5b1f2c9a-77d4-4e21-b9c3-2a8f6d4e1c05",
  "is_encrypted": false,
  "thumbnail": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "source": "",
  "is_web_clip": false,
  "is_public": false,
  "public_token": "",
  "usn": 88,
  "deleted": false,
  "updated_at": 1784000000000,
  "created_at": 1781200000000
}

Errors:

  • 404 not_found — no such note, or it’s in the Trash and deleted=true was not passed.

Export a note as PDF

GET /api/v1/notes/:id/export.pdf · scope: notes

Export one note as a PDF, synchronously. The rendered output contains the note body, a title heading, a created/updated metadata line, and inline images. Embedded PDF attachments are merged into the document in order, each behind a labeled separator page, so the download is one self-contained file; other non-image attachments render as labeled references.

curl -L "https://app.harbor.my/api/v1/notes/9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68/export.pdf" \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -o quarterly-plan.pdf

The response is raw PDF bytes, not a JSON envelope:

  • Content-Type: application/pdf
  • Content-Disposition: attachment; filename="<note title>.pdf"
  • X-Skipped-Attachments: <n> (optional) — the count of embedded PDFs that could not be merged (e.g. corrupt or locked) and were skipped so the rest of the export still succeeded.

Export caps are configurable server-side: a per-image inline byte ceiling (oversized images degrade to a placeholder), a maximum number of embedded PDFs merged, and a maximum total output size.

Errors:

  • 404 not_found
  • 422 encrypted_not_exportable — encrypted notes are ciphertext on the server, which cannot be rendered.
  • 422 too_many_embedded_pdfs — over the embedded-PDF merge cap.
  • 422 export_too_large — over the total output size cap.
  • 500 pdf_export_failed — the render itself failed.

Export a note as Markdown

GET /api/v1/notes/:id/export.md · scope: notes

Export one note as Markdown, synchronously. The body is the same file the whole-account Markdown export writes — one serializer, so the file you get here and the file inside the archive are identical: YAML frontmatter (title, tags, created, updated, source), the rendered body, and any linked task with no anchor in the body appended under a ## Tasks heading.

The response has two shapes, decided by the note. Read the filename from Content-Disposition rather than assuming an extension.

The noteContent-TypeBody
No attachmentstext/markdown; charset=utf-8the .md file
Has attachmentsapplication/zip<Note Title>.md plus a files/ folder its links resolve into
QueryTypeDescription
zipbool?zip=1 always returns the archive form, so a script gets one predictable shape.
curl -L "https://app.harbor.my/api/v1/notes/9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68/export.md" \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -o quarterly-plan.md

Errors:

  • 404 not_found — no such note, or it is trashed.
  • 422 encrypted_not_exportable — encrypted notes are ciphertext on the server, so there is nothing to render.

Update a note

PATCH /api/v1/notes/:id · scope: notes

Partial update, last-write-wins (no locking). Only the fields present in the body are touched; when content is present it is re-derived per content_format. A fresh USN is allocated.

FieldTypeRequiredDescription
titlestringno≤ 255 chars (runes).
contentstringnoRe-derived; interpreted per content_format.
content_formatstringnohtml (default) or markdown.
notebook_idstringnoMust be a live notebook. Moving into a default_encrypt notebook requires the note to be encrypted in the same request — see Moving a note between notebooks.
is_encryptedboolnoToggles opaque storage for this write.
thumbnailstringnoSet (sha256:<64-hex>) or clear (""); omit to leave unchanged.
source_urlstringno
authorstringno
latitude / longitude / altitudenumberno
reminder_timeintnoEpoch ms.
reminder_done_timeintnoEpoch ms.
curl -X PATCH https://app.harbor.my/api/v1/notes/9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68 \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "Quarterly plan (final)", "content": "<p>Updated</p>"}'
{
  "note": {
    "id": "9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68",
    "title": "Quarterly plan (final)",
    "content": "<p>Updated</p>",
    "usn": 89,
    "deleted": false,
    "updated_at": 1784000000000,
    "created_at": 1781200000000
  },
  "usn": 89
}

Errors:

  • 404 not_found
  • 422 validation_failed — invalid JSON, notebook_id not a live notebook, or a malformed thumbnail.
  • 422 cannot_move_plaintext_into_encrypted — the note is plaintext and notebook_id names a different notebook with default_encrypt. Nothing is written and no USN is spent; encrypt the note in the same request instead. See Moving a note between notebooks.
  • 422 note_title_too_long
  • 422 note_too_large

Delete a note

DELETE /api/v1/notes/:id · scope: notes

Move a note to the Trash — soft and recoverable — by default. Pass ?permanent=true to expunge directly instead (the same operation as POST /notes/:id/expunge): a permanent deleted=1 tombstone whose attachment junctions are tombstoned, whose orphaned blobs are reclaimed, and whose OCR text is removed from search.

FieldTypeRequiredDescription
permanentboolnoDefault false. true skips the Trash and expunges permanently.
curl -X DELETE "https://app.harbor.my/api/v1/notes/9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68" \
  -H "Authorization: Bearer $HARBOR_TOKEN"

Response — 204 No Content.

Errors:

  • 404 not_found — no live note with that id.

Append to a note

POST /api/v1/notes/:id/append · scope: notes

Append a sanitized fragment to the end of a note’s body without sending the whole body — ideal for quick capture. The note is re-derived and a fresh USN is allocated. Encrypted notes are rejected: the server cannot splice ciphertext.

FieldTypeRequiredDescription
contentstringyesFragment to append.
content_formatstringnohtml (default) or markdown.
curl -X POST https://app.harbor.my/api/v1/notes/9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68/append \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content": "- one more thing", "content_format": "markdown"}'
{
  "note": {
    "id": "9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68",
    "usn": 90,
    "deleted": false
  },
  "usn": 90
}

Errors:

  • 404 not_found
  • 422 append_not_supported_encrypted — the note is encrypted.
  • 422 note_too_large — the appended body would exceed the 5 MiB cap.

Swap attachment hashes

POST /api/v1/notes/:id/attachments/swap · scope: notes

Re-point a note’s inline attachment junctions from old content hashes to new ones and reclaim the superseded blobs. This is the server half of client-side attachment encryption: the client re-encrypts (or decrypts) each attached file, uploads the result under a new content hash, saves the note with its <harbor-embed> references rewritten, then calls this endpoint.

For each pair the server links the to hash (role:"inline"), detaches the from hash, and — after the transaction commits — reclaims each now-orphaned from blob: the blob and its thumbnails are deleted, the resource and its OCR result are tombstoned, and the OCR plaintext is removed from the search index. A from blob still referenced by another live note is kept, so nothing leaks across notes. The operation is idempotent — safe to retry after a partial failure.

FieldTypeRequiredDescription
swapsarrayyes1–500 {from, to} pairs. Each hash is a bare 64-hex sha256 (no sha256: scheme). The to blob must already be committed.
curl -X POST https://app.harbor.my/api/v1/notes/9c2e6f4a-8b31-4c6e-9d2a-7f5e0c1b3a68/attachments/swap \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "swaps": [
      {
        "from": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
        "to": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"
      }
    ]
  }'
{
  "swapped": 1,
  "reclaimed": ["e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"]
}

swapped is the number of pairs applied; reclaimed lists the distinct old hashes whose blobs were reclamation candidates (evict their cached bytes client-side).

Errors:

  • 404 not_found — unknown or trashed note.
  • 422 validation_failed — malformed hash, or an empty or oversized swaps array.
  • 422 swap_blob_missing — a to blob has not been committed yet.
  • Notebooks API — organize notes into notebooks and stacks.
  • Tags API — tag notes and filter lists by tag.
  • Files API — upload and download the content-addressed attachments notes embed.