bookmark.land
Api reference

Bookmarks

Create, read, update, search, and delete bookmarks

List Bookmarks

GET /bookmarks

Scope: bookmarks:read

Query Parameters:

ParameterTypeDescription
limitnumberMax results to return (default: 50)
collectionIdstringFilter by collection ID
domainstringFilter by domain (e.g., github.com)
typestringFilter by type: link, article, image, video, document
isFavoritebooleanFilter favorites only
sortBystringSort field: newest (default), oldest, title, domain
cursorstringOpaque pagination cursor from a previous response. Pass to fetch the next page

Response:

{
  "data": {
    "data": [
      {
        "id": "abc123",
        "url": "https://example.com",
        "title": "Example Site",
        "description": "An example website",
        "excerpt": "",
        "note": "My personal notes",
        "type": "link",
        "isFavorite": false,
        "tags": ["dev", "reference"],
        "collectionId": "col_xyz",
        "domain": "example.com",
        "favicon": "https://example.com/favicon.ico",
        "coverImage": null,
        "clickCount": 0,
        "metadataStatus": "complete",
        "linkStatus": "valid",
        "lastCheckedAt": "2026-01-15T12:00:00Z",
        "httpStatusCode": 200,
        "createdAt": "2026-01-15T10:30:00Z"
      }
    ],
    "cursor": "eyJwb3MiOjUwfQ",
    "hasMore": true,
    "truncated": false
  },
  "meta": {
    "requestId": "req_abc123"
  }
}

Pagination:

The response data object includes cursor-based pagination fields alongside the bookmark array:

FieldTypeDescription
data.dataarrayArray of bookmark objects
data.cursorstring | nullOpaque cursor to pass as the cursor query parameter for the next page. null when there are no more results
data.hasMorebooleanWhether more results exist beyond this page
data.truncatedbooleantrue when the sort covered only the newest matches and more matches exist that cannot be fetched (see below)
meta.requestIdstringUnique request identifier

All sort modes (newest, oldest, title, domain) return the same envelope shape and page with cursor, with one exception: a title or domain sort combined with isFavorite=true (and no domain, collectionId or type filter). That combination sorts only a window of the most recently saved favorites and cannot continue, so cursor is null, hasMore is false, and truncated is true when more favorites exist beyond that window. Use newest or oldest to page through every favorite.


Get Bookmark

GET /bookmarks/:id

Scope: bookmarks:read

Response: single bookmark object (same shape as list items)


Create Bookmark

POST /bookmarks

Scope: bookmarks:write

Body:

{
  "url": "https://example.com",
  "title": "Example Site",
  "description": "Optional description",
  "note": "Optional personal note",
  "tags": ["dev", "reference"],
  "collectionId": "col_xyz",
  "type": "link",
  "isFavorite": false
}
FieldTypeRequiredDescription
urlstringYesThe URL to bookmark
titlestringNoOverride the auto-detected title
descriptionstringNoOverride the auto-detected description
notestringNoPersonal notes
tagsstring[]NoTags to apply
collectionIdstringNoCollection to add to
typestringNoContent type override
isFavoritebooleanNoMark as favorite (default: false)

If the URL already exists in your trash, it will be restored instead of creating a duplicate.


Update Bookmark

PATCH /bookmarks/:id

Scope: bookmarks:write

Body: any subset of bookmark fields (partial update)

{
  "title": "Updated Title",
  "note": "New notes"
}

Delete Bookmark (Soft)

DELETE /bookmarks/:id

Scope: bookmarks:write

Moves the bookmark to trash. It can be restored within 30 days.


Restore Bookmark

POST /bookmarks/:id/restore

Scope: bookmarks:write

Restores a trashed bookmark.


Permanently Delete Bookmark

DELETE /bookmarks/:id/permanent

Scope: destructive

Permanently deletes the bookmark and all associated data (tags, highlights, uploads). This cannot be undone.


Set Favorite

PUT /bookmarks/:id/favorite

Scope: bookmarks:write

Body:

{
  "isFavorite": true
}

Move Bookmark

POST /bookmarks/:id/move

Scope: bookmarks:write

Body:

{
  "collectionId": "col_xyz"
}

Pass null for collectionId to move to Unsorted.


Search Bookmarks

GET /bookmarks/search

Scope: bookmarks:read

Query Parameters:

ParameterTypeRequiredDescription
qstringYesSearch query (alias: query). Searches title, description, and URL

Response: array of matching bookmarks


List Domains

GET /bookmarks/domains

Scope: bookmarks:read

Returns all domains with bookmark counts.

{
  "data": [
    { "domain": "github.com", "count": 42 },
    { "domain": "stackoverflow.com", "count": 15 }
  ]
}

Get Stats

GET /bookmarks/stats

Scope: bookmarks:read

{
  "data": {
    "total": 1234,
    "favorites": 89,
    "unsorted": 45,
    "typeCounts": {
      "link": 800,
      "article": 300,
      "video": 100,
      "image": 30,
      "document": 4
    },
    "noTags": 200
  }
}

Cover Image

Upload, replace, or remove a bookmark's cover image. Uses a three-step upload flow:

  1. Request an upload URL (creates a server-side reservation)
  2. Upload the image binary directly to the storage URL
  3. Confirm the upload to attach it to the bookmark

Requirements: A paid plan (Pro). Free accounts cannot upload cover images.

Limits: 10 MB per file, 500 MB total storage. Accepted types: JPEG, PNG, GIF, WebP.

Get Upload URL

POST /bookmarks/:id/cover/upload-url

Scope: bookmarks:write

Response:

{
  "data": {
    "uploadUrl": "https://storage.example.com/upload?token=...",
    "reservationId": "res_abc123"
  }
}

The uploadUrl is a temporary, single-use URL valid for 15 minutes. You may have up to 5 outstanding reservations at a time.

Upload the Image

Upload the binary file directly to the uploadUrl returned above. This request goes to the storage service, not the API:

curl -X POST "$UPLOAD_URL" \
  -H "Content-Type: image/jpeg" \
  --data-binary @photo.jpg

Response:

{
  "storageId": "kg2abc123..."
}

Save Cover Image

POST /bookmarks/:id/cover

Scope: bookmarks:write

Body:

{
  "storageId": "kg2abc123...",
  "reservationId": "res_abc123"
}
FieldTypeRequiredDescription
storageIdstringYesStorage ID returned from the upload step
reservationIdstringYesReservation ID from the upload URL step

The server validates the file type, size, and storage quota before accepting. If the bookmark already has a cover image, the old one is replaced and its storage is freed.

Response:

{
  "data": {
    "url": "https://storage.example.com/api/storage/abc-123"
  }
}

Remove Cover Image

DELETE /bookmarks/:id/cover

Scope: bookmarks:write

Removes the cover image and frees the storage quota.

Response:

{
  "data": {
    "success": true
  }
}

Cover Image Errors

CodeStatusDescription
INVALID_FILE_TYPE422File is not JPEG, PNG, GIF, or WebP
FILE_TOO_LARGE422File exceeds 10 MB
STORAGE_LIMIT_REACHED422Total storage quota (500 MB) exceeded
RESERVATION_INVALID400Reservation expired, already used, or doesn't match this bookmark
TOO_MANY_PENDING_UPLOADS4295 outstanding upload reservations already exist

Bookmark Tags

List Tags on a Bookmark

GET /bookmarks/:id/tags

Scope: bookmarks:read

Response:

{
  "data": [
    {
      "id": "tag_abc",
      "name": "development",
      "color": "blue",
      "parentId": null,
      "count": 42,
      "createdAt": "2026-01-10T08:00:00Z"
    }
  ],
  "meta": {
    "requestId": "req_abc123"
  }
}

Add Tag to Bookmark

POST /bookmarks/:id/tags

Scope: tags:write

Body:

{
  "name": "important"
}

Remove Tag from Bookmark

DELETE /bookmarks/:id/tags/:tagId

Scope: tags:write

List Bookmarks by Tag Name

Also documented under Tags.

GET /tags/:name/bookmarks

Scope: bookmarks:read

Query Parameters:

ParameterTypeDescription
limitnumberMax results to return (default: 50, max: 200)

Response:

{
  "data": {
    "bookmarks": [ ... ],
    "tag": {
      "id": "tag_abc",
      "name": "development",
      "color": "blue",
      "parentId": null,
      "count": 42,
      "createdAt": "2026-01-10T08:00:00Z"
    }
  },
  "meta": {
    "requestId": "req_abc123"
  }
}

The tag field is null if the tag name does not match any existing tag.

List Bookmarks by Tag ID

Also documented under Tags.

GET /tags/by-id/:id/bookmarks

Scope: bookmarks:read

Query Parameters:

ParameterTypeDescription
limitnumberMax results to return (default: 50, max: 200)

Same as above but uses the tag's ID instead of its name. Useful for hierarchical tags with / in their names.

Response: same shape as GET /tags/:name/bookmarks — { data: { bookmarks, tag }, meta }.

On this page