Start here
Conventions
The shared contract behind every endpoint — so you only have to learn it once.
Every endpoint in the API reference follows these rules. They’re documented here once instead of on every page.
JSON and field names
- Requests and responses are JSON (
Content-Type: application/json). - JSON field names are
snake_case—notebook_id,created_at,source_url. - The token endpoint additionally
accepts
application/x-www-form-urlencoded, per the OAuth 2.0 spec.
Timestamps
All timestamps are UTC epoch milliseconds (integers):
{ "created_at": 1752600000000, "updated_at": 1752600000000 }
To convert:
# bash / GNU date
date -u -d @1752600000 # seconds → readable
from datetime import datetime, timezone
datetime.fromtimestamp(1752600000000 / 1000, tz=timezone.utc)
new Date(1752600000000).toISOString();
The one exception is OAuth’s expires_in, which is a duration in seconds
(again, per the OAuth spec).
Response envelopes
Harbor uses a small, predictable set of response shapes.
A single resource
Wrapped under a data key:
{
"data": {
"id": "9c2e7f1a-0b3d-4e6f-8a12-5c7d9e0f1a2b",
"title": "Welcome to Harbor",
"created_at": 1752600000000
}
}
A collection
A data array plus a paging block:
{
"data": [
{ "id": "…", "title": "First note" },
{ "id": "…", "title": "Second note" }
],
"paging": { "limit": 100, "offset": 0, "total": 2, "has_more": false }
}
An OAuth token
The token endpoint returns a bare object (not wrapped in data), as the
OAuth spec requires:
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "notes notebooks"
}
Pagination
List endpoints take limit and offset query parameters:
| Parameter | Default | Notes |
|---|---|---|
limit | 100 | Max page size is 500. |
offset | 0 | Number of items to skip. |
The paging block tells you where you are:
{ "limit": 100, "offset": 0, "total": 342, "has_more": true }
Page through a collection until has_more is false:
offset=0
while : ; do
page=$(curl -s "https://app.harbor.my/api/v1/notes?limit=100&offset=$offset" \
-H "Authorization: Bearer $HARBOR_TOKEN")
echo "$page" | jq -r '.data[].id'
[ "$(echo "$page" | jq -r '.paging.has_more')" = "true" ] || break
offset=$((offset + 100))
done
Sorting
List endpoints accept an order parameter — a comma-separated list of fields,
each optionally prefixed with - for descending:
?order=-updated_at,title
That sorts by most-recently-updated first, then by title. The fields you can sort by are noted on each endpoint.
Filtering
Most list endpoints take resource-specific filters as query parameters — for
example notebook_id on notes, or parent_id on tags. These are documented per
endpoint.
Request IDs
Every response carries an X-Request-Id header, and errors echo it in the body
as request_id. Include it when you report a problem — it lets us find the exact
request in our logs.
Next steps
- Errors — status codes and the error envelope
- API reference — every endpoint, with examples