Harbor
Developer docs menu

API reference

Templates, Shortcuts & Saved Searches API

Three small resources that shape your workspace: templates you stamp into new notes, shortcuts that pin things to the sidebar, and searches you keep by name.

This page covers three sibling resources: templates (reusable note starting points, plus an apply endpoint that materializes a fresh note), shortcuts (the user’s ordered sidebar pins), and saved searches (named search queries). All three are syncable structural records — every mutation allocates a USN, deletes are tombstones, and sync conflicts resolve last-write-wins.

Every endpoint here requires a bearer token with the notes scope.

Templates

A template is a reusable note starting point. Its content is stored in the same sanitized HTML format as a note body, and templates are never encrypted. Templates with is_system: true are built-in and seeded by Harbor: they sync to every device like any other template, but they are read-only — PATCH and DELETE on one return 403 system_template_readonly, and the create body’s is_system field is ignored.

The template object

FieldTypeDescription
idstringUUID. Client-supplied on create or server-generated.
namestringDisplay name; also the default title when the template is applied.
contentstringSanitized HTML note body. Never encrypted.
is_systembooleantrue for built-in, read-only templates.
usnintegerSync sequence number, bumped on every mutation.
deletedbooleanTombstone flag.
updated_atintegerUTC epoch milliseconds.
created_atintegerUTC epoch milliseconds.
{
  "id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
  "name": "Meeting notes",
  "content": "<h1>Meeting</h1>\n<p>Attendees:</p>\n<ul><li></li></ul>",
  "is_system": false,
  "usn": 12,
  "deleted": false,
  "updated_at": 1750000000000,
  "created_at": 1749000000000
}

List templates

GET /api/v1/templates · scope: notes

Lists the user’s templates, paged. Tombstoned templates are excluded unless include_deleted=true. System templates are included by default; pass include_system=false for a user-only view.

FieldTypeRequiredDescription
limitintegerNoDefault 100, hard cap 500 (clamped).
offsetintegerNoDefault 0.
orderstringNoDefault name. Sortable: name, usn, created_at, updated_at. Prefix - for descending.
include_deletedbooleanNoDefault false. true includes tombstoned templates.
include_systembooleanNoDefault true. false hides built-in (is_system) templates.
curl "https://app.harbor.my/api/v1/templates?order=-updated_at" \
  -H "Authorization: Bearer $HARBOR_TOKEN"
{
  "data": [
    {
      "id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
      "name": "Meeting notes",
      "content": "<h1>Meeting</h1>\n<p>Attendees:</p>\n<ul><li></li></ul>",
      "is_system": false,
      "usn": 12,
      "deleted": false,
      "updated_at": 1750000000000,
      "created_at": 1749000000000
    }
  ],
  "paging": { "limit": 100, "offset": 0, "total": 1, "has_more": false }
}
  • 422 validation_failed — unknown sort field.

Create a template

POST /api/v1/templates · scope: notes

Creates a user template. content runs through the note content pipeline — Markdown is converted to HTML when content_format is markdown, then the result is sanitized. is_system is ignored if sent; clients cannot create system templates.

FieldTypeRequiredDescription
idstringNoClient UUID; server-generated when absent. Must be a valid, unused UUID.
namestringYesNon-empty after trimming.
contentstringNoHTML or Markdown per content_format; sanitized on write.
content_formatstringNohtml (default) or markdown.
curl https://app.harbor.my/api/v1/templates \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Meeting notes",
    "content": "# Meeting\n\nAttendees:",
    "content_format": "markdown"
  }'

Response is 201 Created with the template object (bare, not wrapped in data):

{
  "id": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
  "name": "Meeting notes",
  "content": "<h1>Meeting</h1>\n<p>Attendees:</p>\n<ul><li></li></ul>",
  "is_system": false,
  "usn": 12,
  "deleted": false,
  "updated_at": 1750000000000,
  "created_at": 1749000000000
}
  • 422 validation_failed — missing or blank name, malformed id, or invalid JSON.
  • 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']}"}

template = requests.post(
    f"{BASE}/templates",
    headers=headers,
    json={
        "name": "Meeting notes",
        "content": "# Meeting\n\nAttendees:",
        "content_format": "markdown",
    },
).json()

print(template["id"], template["usn"])

JavaScript

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

const res = await fetch(`${BASE}/templates`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.HARBOR_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Meeting notes",
    content: "# Meeting\n\nAttendees:",
    content_format: "markdown",
  }),
});

const template = await res.json();
console.log(template.id, template.usn);

Go

package main

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

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

func main() {
	body, _ := json.Marshal(map[string]string{
		"name":           "Meeting notes",
		"content":        "# Meeting\n\nAttendees:",
		"content_format": "markdown",
	})

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

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

	var template map[string]any
	json.NewDecoder(res.Body).Decode(&template)
	fmt.Println(template["id"], template["usn"])
}

Get a template

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

Fetches one template by id, including its content. Returns 404 when the template is missing or tombstoned, unless include_deleted=true.

FieldTypeRequiredDescription
include_deletedbooleanNotrue returns the template even if tombstoned.
curl https://app.harbor.my/api/v1/templates/3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f \
  -H "Authorization: Bearer $HARBOR_TOKEN"

Response is the template object (bare, not wrapped in data).

  • 404 not_found — missing or tombstoned.

Update a template

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

Partial update of a user template. Only fields present in the body are touched; when content is present it is re-sanitized. A fresh USN is allocated.

FieldTypeRequiredDescription
namestringNoNon-empty after trimming.
contentstringNoRe-sanitized; interpreted per content_format.
content_formatstringNohtml (default) or markdown.
curl -X PATCH https://app.harbor.my/api/v1/templates/3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content": "# Meeting\n\nAttendees:", "content_format": "markdown"}'

Response is 200 OK with the updated template object (bare).

  • 403 system_template_readonly — the template is a system template.
  • 404 not_found — missing or tombstoned.
  • 422 validation_failed — blank name or invalid JSON.

Delete a template

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

Tombstones a user template (deleted becomes true, fresh USN) so the removal propagates via sync.

curl -X DELETE https://app.harbor.my/api/v1/templates/3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f \
  -H "Authorization: Bearer $HARBOR_TOKEN"

Response is 204 No Content.

  • 403 system_template_readonly — the template is a system template.
  • 404 not_found — missing or already tombstoned.

Apply a template

POST /api/v1/templates/:id/apply · scope: notes

Instantiates a new note from a template. The template’s content is copied verbatim into the new note, the title is the supplied one or the template name, the notebook is the supplied one or the user’s default, requested tags are attached, and the note is indexed for search. The template itself is unchanged (no USN bump). Returns {note, usn} — the same shape as POST /api/v1/notes.

The body is optional; every field is an override.

FieldTypeRequiredDescription
idstringNoClient UUID for the new note; server-generated when absent.
notebook_idstringNoA live notebook id; the user’s default notebook when absent.
titlestringNoOverrides the note title; the template name when blank.
tagsstring[]NoTag ids to attach to the new note; each must be a live tag.
curl https://app.harbor.my/api/v1/templates/3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f/apply \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "notebook_id": "5b1f2c9a-3d4e-4f5a-9b8c-7d6e5f4a3b2c",
    "title": "Standup 2026-07-15",
    "tags": ["7e1d9f3b-2c4a-4e6d-b8a0-5c7e9f1b3d5a"]
  }'

Response is 201 Created:

{
  "note": {
    "id": "9c2e7b10-4d5e-4f6a-8b7c-0d1e2f3a4b5c",
    "title": "Standup 2026-07-15",
    "content": "<h1>Meeting</h1>\n<p>Attendees:</p>\n<ul><li></li></ul>",
    "content_hash": "f1d2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f70",
    "content_length": 52,
    "word_count": 2,
    "notebook_id": "5b1f2c9a-3d4e-4f5a-9b8c-7d6e5f4a3b2c",
    "is_encrypted": false,
    "usn": 88,
    "deleted": false,
    "updated_at": 1750000000000,
    "created_at": 1750000000000
  },
  "usn": 88
}

Notable edge cases:

  • No token expansion in v1. Content is copied verbatim — a placeholder like {{date}} is not expanded.
  • Encrypted-by-default notebooks are rejected with 422 — the server cannot encrypt a plaintext template. Fetch the template, encrypt locally, and create the note via the normal encrypted POST /api/v1/notes path.
  • 404 not_found — the template is missing or tombstoned.
  • 422 validation_failed — malformed id or JSON, the target notebook is missing or foreign, an unknown tag id, or the target notebook is encrypted-by-default (details.notebook_id explains).
  • 409 conflict — the supplied note id is already taken.

Python

import os
import requests

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

template_id = "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f"
result = requests.post(
    f"{BASE}/templates/{template_id}/apply",
    headers=headers,
    json={"title": "Standup 2026-07-15"},
).json()

note = result["note"]
print(note["id"], note["title"])

JavaScript

const BASE = "https://app.harbor.my/api/v1";
const templateId = "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f";

const res = await fetch(`${BASE}/templates/${templateId}/apply`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.HARBOR_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ title: "Standup 2026-07-15" }),
});

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

Go

package main

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

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

func main() {
	templateID := "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f"
	body, _ := json.Marshal(map[string]string{"title": "Standup 2026-07-15"})

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

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

	var result struct {
		Note map[string]any `json:"note"`
		USN  int            `json:"usn"`
	}
	json.NewDecoder(res.Body).Decode(&result)
	fmt.Println(result.Note["id"], result.Note["title"])
}

A shortcut is a user-curated, ordered sidebar pointer. Its type is one of note | notebook | tag | search, and the type dictates which fields it carries:

  • note / notebook / tagtarget_id is required and must resolve to a live record of that kind (a note must not be in the trash); saved_query must be empty.
  • search — exactly one of target_id (a live saved search — how the web app pins one) or saved_query (an inline query string, kept for back-compat; stored opaquely, only non-emptiness is validated).

Ordering uses position, a fractional (float) index — lower sorts higher. A new shortcut with no position is appended (max existing + 100). Lists order by position ascending, ties broken by created_at then id. The bulk reorder endpoint renumbers to clean integer gaps (100, 200, …) to avoid float drift. label is an optional display label; clients fall back to the target’s name or title when it’s empty.

The shortcut object

FieldTypeDescription
idstringUUID. Client-supplied on create or server-generated.
typestringnote, notebook, tag, or search.
target_idstringId of the target record; "" for an inline-query search shortcut.
saved_querystringInline query string for a search shortcut; "" otherwise.
labelstringOptional display label.
positionnumberFractional sort index; lower sorts higher.
usnintegerSync sequence number, bumped on every mutation.
deletedbooleanTombstone flag.
updated_atintegerUTC epoch milliseconds.
created_atintegerUTC epoch milliseconds.
{
  "id": "8d9e0f1a-2b3c-4d5e-9f6a-7b8c9d0e1f2a",
  "type": "note",
  "target_id": "9c2e7b10-4d5e-4f6a-8b7c-0d1e2f3a4b5c",
  "saved_query": "",
  "label": "Quarterly plan",
  "position": 100.0,
  "usn": 5,
  "deleted": false,
  "updated_at": 1750000000000,
  "created_at": 1749000000000
}

List shortcuts

GET /api/v1/shortcuts · scope: notes

Lists the user’s shortcuts ordered by position ascending (ties by created_at, then id), paged. Tombstoned shortcuts are excluded unless include_deleted=true.

FieldTypeRequiredDescription
limitintegerNoDefault 100, hard cap 500 (clamped).
offsetintegerNoDefault 0.
orderstringNoDefault position (then created_at, id). Sortable: position, usn, created_at, updated_at, id. Prefix - for descending.
include_deletedbooleanNoDefault false. true includes tombstoned shortcuts.
curl https://app.harbor.my/api/v1/shortcuts \
  -H "Authorization: Bearer $HARBOR_TOKEN"
{
  "data": [
    {
      "id": "8d9e0f1a-2b3c-4d5e-9f6a-7b8c9d0e1f2a",
      "type": "note",
      "target_id": "9c2e7b10-4d5e-4f6a-8b7c-0d1e2f3a4b5c",
      "saved_query": "",
      "label": "Quarterly plan",
      "position": 100.0,
      "usn": 5,
      "deleted": false,
      "updated_at": 1750000000000,
      "created_at": 1749000000000
    }
  ],
  "paging": { "limit": 100, "offset": 0, "total": 1, "has_more": false }
}
  • 422 validation_failed — unknown sort field.

Create a shortcut

POST /api/v1/shortcuts · scope: notes

Creates a shortcut. The typetarget_id/saved_query consistency rules above are validated, and a record target must resolve to a live record of its kind. When position is omitted the shortcut is appended (max existing position + 100).

FieldTypeRequiredDescription
idstringNoClient UUID; server-generated when absent. Must be a valid, unused UUID.
typestringYesnote | notebook | tag | search.
target_idstringConditionalRequired (and saved_query empty) for note/notebook/tag. For search, may reference a live saved search instead of saved_query.
saved_querystringConditionalRequired (and target_id empty) for search in its inline-query form.
labelstringNoOptional display label.
positionnumberNoExplicit fractional position; appended when omitted.
curl https://app.harbor.my/api/v1/shortcuts \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "note",
    "target_id": "9c2e7b10-4d5e-4f6a-8b7c-0d1e2f3a4b5c",
    "label": "Quarterly plan"
  }'

Response is 201 Created with the shortcut object (bare, not wrapped in data):

{
  "id": "8d9e0f1a-2b3c-4d5e-9f6a-7b8c9d0e1f2a",
  "type": "note",
  "target_id": "9c2e7b10-4d5e-4f6a-8b7c-0d1e2f3a4b5c",
  "saved_query": "",
  "label": "Quarterly plan",
  "position": 100.0,
  "usn": 5,
  "deleted": false,
  "updated_at": 1750000000000,
  "created_at": 1749000000000
}
  • 422 validation_failed — bad type; a missing or forbidden target_id/saved_query for the type; a target that is not a live record of its kind (details.target_id explains); malformed id; or invalid JSON.
  • 409 conflict — the supplied id is already taken.

Get a shortcut

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

Fetches one shortcut by id. Returns 404 when missing or tombstoned, unless include_deleted=true.

FieldTypeRequiredDescription
include_deletedbooleanNotrue returns the shortcut even if tombstoned.
curl https://app.harbor.my/api/v1/shortcuts/8d9e0f1a-2b3c-4d5e-9f6a-7b8c9d0e1f2a \
  -H "Authorization: Bearer $HARBOR_TOKEN"

Response is 200 OK with the shortcut object (bare).

  • 404 not_found — missing or tombstoned.

Update a shortcut

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

Partial update of label, saved_query (search shortcuts only), target_id (non-search shortcuts only), and/or position. The resulting record is re-validated for type consistency, and a fresh USN is allocated.

FieldTypeRequiredDescription
labelstringNoDisplay label.
saved_querystringNoOnly valid for a search shortcut.
target_idstringNoOnly valid for a non-search shortcut; must point at a live record of the kind.
positionnumberNoNew fractional position.
curl -X PATCH https://app.harbor.my/api/v1/shortcuts/8d9e0f1a-2b3c-4d5e-9f6a-7b8c9d0e1f2a \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"label": "Quarterly plan", "position": 150.0}'

Response is 200 OK with the updated shortcut object (bare).

  • 404 not_found — missing or tombstoned.
  • 422 validation_failed — a field not valid for the shortcut’s type, a resulting record that violates type consistency, a target that is not a live record of its kind, or invalid JSON.

Delete a shortcut

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

Tombstones a shortcut (deleted becomes true, fresh USN) so the removal propagates via sync.

curl -X DELETE https://app.harbor.my/api/v1/shortcuts/8d9e0f1a-2b3c-4d5e-9f6a-7b8c9d0e1f2a \
  -H "Authorization: Bearer $HARBOR_TOKEN"

Response is 204 No Content.

  • 404 not_found — missing or already tombstoned.

Reorder shortcuts

PUT /api/v1/shortcuts/order · scope: notes

Bulk reorder. Assigns evenly spaced positions (100, 200, …) to the shortcuts in the given order, bumping the USN of every row whose position actually changes (unchanged rows are not rewritten). The request must reference each live shortcut id exactly once — an unknown, duplicated, or missing id is a 422, since a partial renumber would interleave unpredictably with untouched rows.

FieldTypeRequiredDescription
orderstring[]YesThe full ordered list of live shortcut ids, each exactly once.
curl -X PUT https://app.harbor.my/api/v1/shortcuts/order \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "order": [
      "8d9e0f1a-2b3c-4d5e-9f6a-7b8c9d0e1f2a",
      "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
      "f0e1d2c3-b4a5-4968-8776-655443322110"
    ]
  }'

Response is 200 OK with the reordered list as a collection (position ascending); paging reflects the returned set (limit and total equal the number of shortcuts, offset is 0):

{
  "data": [
    {
      "id": "8d9e0f1a-2b3c-4d5e-9f6a-7b8c9d0e1f2a",
      "type": "note",
      "target_id": "9c2e7b10-4d5e-4f6a-8b7c-0d1e2f3a4b5c",
      "saved_query": "",
      "label": "Quarterly plan",
      "position": 100.0,
      "usn": 6,
      "deleted": false,
      "updated_at": 1750000060000,
      "created_at": 1749000000000
    }
  ],
  "paging": { "limit": 3, "offset": 0, "total": 3, "has_more": false }
}
  • 422 validation_failed — a duplicate, unknown, or missing id (details.order explains), or invalid JSON.
  • The static /order route is matched literally and registered before the /:id routes, so order is never treated as a shortcut id.

Saved searches

A saved search is a user-named search query — a first-class entity distinct from a sidebar shortcut. Creating one does not add it to the sidebar; pinning is a separate action that creates a type: "search" shortcut referencing the saved search by target_id. The query is the search DSL string; the server stores it opaquely (the grammar belongs to the search endpoint) and validates only non-emptiness.

The saved search object

FieldTypeDescription
idstringUUID. Client-supplied on create or server-generated.
namestringUser-chosen display name.
querystringThe search DSL string, stored opaquely.
usnintegerSync sequence number, bumped on every mutation.
deletedbooleanTombstone flag.
updated_atintegerUTC epoch milliseconds.
created_atintegerUTC epoch milliseconds.
{
  "id": "9b1ec4d2-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
  "name": "Overdue invoices",
  "query": "tag:invoice updated:week",
  "usn": 42,
  "deleted": false,
  "updated_at": 1750000000000,
  "created_at": 1749000000000
}

List saved searches

GET /api/v1/saved-searches · scope: notes

Lists the user’s saved searches. Tombstoned rows are excluded unless include_deleted=true.

FieldTypeRequiredDescription
limitintegerNoDefault 100, hard cap 500 (clamped).
offsetintegerNoDefault 0.
orderstringNoDefault name. Sortable: name, usn, created_at, updated_at, id. Prefix - for descending.
include_deletedbooleanNoDefault false. true includes tombstones.
curl https://app.harbor.my/api/v1/saved-searches \
  -H "Authorization: Bearer $HARBOR_TOKEN"
{
  "data": [
    {
      "id": "9b1ec4d2-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
      "name": "Overdue invoices",
      "query": "tag:invoice updated:week",
      "usn": 42,
      "deleted": false,
      "updated_at": 1750000000000,
      "created_at": 1749000000000
    }
  ],
  "paging": { "limit": 100, "offset": 0, "total": 1, "has_more": false }
}
  • 422 validation_failed — unknown sort field.

POST /api/v1/saved-searches · scope: notes

Creates a saved search.

FieldTypeRequiredDescription
idstringNoClient UUID; server-generated when absent. Must be valid and unused.
namestringYesTrimmed; non-empty.
querystringYesTrimmed; non-empty. The search DSL string, stored opaquely.
curl https://app.harbor.my/api/v1/saved-searches \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Overdue invoices", "query": "tag:invoice updated:week"}'

Response is 201 Created with the saved-search object (bare, not wrapped in data):

{
  "id": "9b1ec4d2-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
  "name": "Overdue invoices",
  "query": "tag:invoice updated:week",
  "usn": 42,
  "deleted": false,
  "updated_at": 1750000000000,
  "created_at": 1749000000000
}
  • 422 validation_failedname or query missing, malformed id, or invalid JSON.
  • 409 conflict — the supplied id is already taken.

GET /api/v1/saved-searches/:id · scope: notes

Fetches one saved search. Returns 404 when missing or tombstoned, unless include_deleted=true.

FieldTypeRequiredDescription
include_deletedbooleanNotrue returns a tombstoned row instead of 404.
curl https://app.harbor.my/api/v1/saved-searches/9b1ec4d2-5f6a-4b7c-8d9e-0f1a2b3c4d5e \
  -H "Authorization: Bearer $HARBOR_TOKEN"

Response is 200 OK with the saved-search object (bare).

  • 404 not_found — missing or tombstoned.

PATCH /api/v1/saved-searches/:id · scope: notes

Renames a saved search and/or edits its query. Only fields present are touched; a fresh USN is allocated.

FieldTypeRequiredDescription
namestringNoTrimmed; must be non-empty when sent.
querystringNoTrimmed; must be non-empty when sent.
curl -X PATCH https://app.harbor.my/api/v1/saved-searches/9b1ec4d2-5f6a-4b7c-8d9e-0f1a2b3c4d5e \
  -H "Authorization: Bearer $HARBOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Overdue invoices (30d)", "query": "tag:invoice updated:month"}'

Response is 200 OK with the updated saved-search object (bare):

{
  "id": "9b1ec4d2-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
  "name": "Overdue invoices (30d)",
  "query": "tag:invoice updated:month",
  "usn": 43,
  "deleted": false,
  "updated_at": 1750000000000,
  "created_at": 1749000000000
}
  • 404 not_found — missing or tombstoned.
  • 422 validation_failed — a sent name or query is empty, or invalid JSON.

DELETE /api/v1/saved-searches/:id · scope: notes

Tombstones a saved search (deleted becomes true, bumped USN) so the removal propagates via sync. Any sidebar shortcut pinning it is not cascaded — the dangling shortcut renders gracefully and can be removed on its own.

curl -X DELETE https://app.harbor.my/api/v1/saved-searches/9b1ec4d2-5f6a-4b7c-8d9e-0f1a2b3c4d5e \
  -H "Authorization: Bearer $HARBOR_TOKEN"

Response is 204 No Content.

  • 404 not_found — missing or already tombstoned.
  • Notes API — the note objects that templates create and shortcuts point at.
  • Search API — the query DSL that saved searches store.
  • Conventions — envelopes, pagination, errors, USNs, and tombstones.