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
| Field | Type | Description |
|---|---|---|
id | string | UUID. Client-supplied on create or server-generated. |
name | string | Display name; also the default title when the template is applied. |
content | string | Sanitized HTML note body. Never encrypted. |
is_system | boolean | true for built-in, read-only templates. |
usn | integer | Sync sequence number, bumped on every mutation. |
deleted | boolean | Tombstone flag. |
updated_at | integer | UTC epoch milliseconds. |
created_at | integer | UTC 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.
| Field | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Default 100, hard cap 500 (clamped). |
offset | integer | No | Default 0. |
order | string | No | Default name. Sortable: name, usn, created_at, updated_at. Prefix - for descending. |
include_deleted | boolean | No | Default false. true includes tombstoned templates. |
include_system | boolean | No | Default 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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No | Client UUID; server-generated when absent. Must be a valid, unused UUID. |
name | string | Yes | Non-empty after trimming. |
content | string | No | HTML or Markdown per content_format; sanitized on write. |
content_format | string | No | html (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 blankname, malformedid, or invalid JSON.409 conflict— the suppliedidis 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.
| Field | Type | Required | Description |
|---|---|---|---|
include_deleted | boolean | No | true 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.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Non-empty after trimming. |
content | string | No | Re-sanitized; interpreted per content_format. |
content_format | string | No | html (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— blanknameor 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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No | Client UUID for the new note; server-generated when absent. |
notebook_id | string | No | A live notebook id; the user’s default notebook when absent. |
title | string | No | Overrides the note title; the template name when blank. |
tags | string[] | No | Tag 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 encryptedPOST /api/v1/notespath. 404 not_found— the template is missing or tombstoned.422 validation_failed— malformedidor JSON, the target notebook is missing or foreign, an unknown tag id, or the target notebook is encrypted-by-default (details.notebook_idexplains).409 conflict— the supplied noteidis 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"])
}
Sidebar shortcuts
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/tag—target_idis required and must resolve to a live record of that kind (a note must not be in the trash);saved_querymust be empty.search— exactly one oftarget_id(a live saved search — how the web app pins one) orsaved_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
| Field | Type | Description |
|---|---|---|
id | string | UUID. Client-supplied on create or server-generated. |
type | string | note, notebook, tag, or search. |
target_id | string | Id of the target record; "" for an inline-query search shortcut. |
saved_query | string | Inline query string for a search shortcut; "" otherwise. |
label | string | Optional display label. |
position | number | Fractional sort index; lower sorts higher. |
usn | integer | Sync sequence number, bumped on every mutation. |
deleted | boolean | Tombstone flag. |
updated_at | integer | UTC epoch milliseconds. |
created_at | integer | UTC 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.
| Field | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Default 100, hard cap 500 (clamped). |
offset | integer | No | Default 0. |
order | string | No | Default position (then created_at, id). Sortable: position, usn, created_at, updated_at, id. Prefix - for descending. |
include_deleted | boolean | No | Default 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 type ↔ target_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).
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No | Client UUID; server-generated when absent. Must be a valid, unused UUID. |
type | string | Yes | note | notebook | tag | search. |
target_id | string | Conditional | Required (and saved_query empty) for note/notebook/tag. For search, may reference a live saved search instead of saved_query. |
saved_query | string | Conditional | Required (and target_id empty) for search in its inline-query form. |
label | string | No | Optional display label. |
position | number | No | Explicit 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— badtype; a missing or forbiddentarget_id/saved_queryfor the type; a target that is not a live record of its kind (details.target_idexplains); malformedid; or invalid JSON.409 conflict— the suppliedidis 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.
| Field | Type | Required | Description |
|---|---|---|---|
include_deleted | boolean | No | true 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.
| Field | Type | Required | Description |
|---|---|---|---|
label | string | No | Display label. |
saved_query | string | No | Only valid for a search shortcut. |
target_id | string | No | Only valid for a non-search shortcut; must point at a live record of the kind. |
position | number | No | New 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.
| Field | Type | Required | Description |
|---|---|---|---|
order | string[] | Yes | The 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.orderexplains), or invalid JSON.- The static
/orderroute is matched literally and registered before the/:idroutes, soorderis 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
| Field | Type | Description |
|---|---|---|
id | string | UUID. Client-supplied on create or server-generated. |
name | string | User-chosen display name. |
query | string | The search DSL string, stored opaquely. |
usn | integer | Sync sequence number, bumped on every mutation. |
deleted | boolean | Tombstone flag. |
updated_at | integer | UTC epoch milliseconds. |
created_at | integer | UTC 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.
| Field | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Default 100, hard cap 500 (clamped). |
offset | integer | No | Default 0. |
order | string | No | Default name. Sortable: name, usn, created_at, updated_at, id. Prefix - for descending. |
include_deleted | boolean | No | Default 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.
Create a saved search
POST /api/v1/saved-searches · scope: notes
Creates a saved search.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No | Client UUID; server-generated when absent. Must be valid and unused. |
name | string | Yes | Trimmed; non-empty. |
query | string | Yes | Trimmed; 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_failed—nameorquerymissing, malformedid, or invalid JSON.409 conflict— the suppliedidis already taken.
Get a saved search
GET /api/v1/saved-searches/:id · scope: notes
Fetches one saved search. Returns 404 when missing or tombstoned, unless
include_deleted=true.
| Field | Type | Required | Description |
|---|---|---|---|
include_deleted | boolean | No | true 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.
Update a saved search
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.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Trimmed; must be non-empty when sent. |
query | string | No | Trimmed; 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 sentnameorqueryis empty, or invalid JSON.
Delete a saved search
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.
Related
- 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.