All bookmark endpoints require authentication via Authorization: Bearer <accessToken>.

Delta Sync

Fetch bookmarks that changed since your last sync. Returns both upserted records and tombstones (soft-deleted IDs).
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://api.unisave.io/v1/bookmarks/delta?cursor=&limit=100"
Query Parameters
ParamTypeRequiredDescription
cursorstringNoOpaque cursor from previous response. Omit for initial sync.
limitintegerNoMax records per page. Default: 200.
Response 200
{
  "data": {
    "cursor": "previous-cursor-or-null",
    "nextCursor": "opaque-next-cursor",
    "upserts": [
      {
        "ownerUid": "user-uuid",
        "clientBookmarkId": "abc-123",
        "url": "https://example.com/article",
        "normalizedUrl": "https://example.com/article",
        "domain": "example.com",
        "title": "Great Article",
        "description": "An in-depth look at...",
        "thumbnailUrl": "https://example.com/thumb.jpg",
        "summary": "AI-generated summary of the article.",
        "saveWhy": "Reference for project research",
        "tags": ["tech", "tutorial"],
        "enrichmentStatus": "completed",
        "snapshotStatus": "ready",
        "lastErrorCode": null,
        "isFavorite": false,
        "collectionIds": ["col-1"],
        "notes": null,
        "clientSavedAt": "2026-04-12T10:30:00Z",
        "lastOpenedAt": null,
        "createdAt": "2026-04-12T10:30:00Z",
        "updatedAt": "2026-04-12T10:35:00Z",
        "enrichedAt": "2026-04-12T10:35:00Z"
      }
    ],
    "tombstones": [
      {
        "clientBookmarkId": "def-456",
        "deletedAt": "2026-04-12T11:00:00Z"
      }
    ]
  }
}
Cursor behavior: The cursor is opaque — don’t parse it. Internally it encodes (updated_at, client_id) for stable ordering. When nextCursor is null, you’ve reached the end.

Sync Algorithm

  1. Store the last nextCursor locally
  2. On sync, request /bookmarks/delta?cursor=<stored>
  3. Apply upserts (insert or update local records)
  4. Apply tombstones (mark as deleted locally)
  5. Store the new nextCursor
  6. Repeat until nextCursor is null

Upsert Bookmark

Create or update a bookmark. The clientBookmarkId is a client-generated UUID — if it already exists, the record is updated.
curl -X PUT \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  "https://api.unisave.io/v1/bookmarks/abc-123" \
  -d '{
    "url": "https://example.com/article",
    "clientSavedAt": "2026-04-12T10:30:00Z"
  }'
Path Parameters
ParamTypeDescription
clientBookmarkIdstringClient-generated unique ID
Request Body
FieldTypeRequiredDescription
urlstringYesThe URL to save. Must be http: or https:. Max 2048 chars.
clientSavedAtstringNoISO 8601 timestamp when the user saved it.
titlestringNoClient-provided title (max 1000 chars). Overwritten when enrichment completes.
descriptionstringNoClient-provided description. Overwritten when enrichment completes.
thumbnailUrlstringNoClient-provided thumbnail URL. Overwritten when enrichment completes.
isFavoritebooleanNoMark as favorite.
notesstringNoUser notes (max 10000 chars).
lastOpenedAtstringNoISO 8601 timestamp of last open.
collectionIdsstring[]NoFull list of collection IDs. Replaces existing memberships when provided.
Response 200 Returns the full bookmark object (same shape as delta sync upserts).
{
  "data": {
    "ownerUid": "user-uuid",
    "clientBookmarkId": "abc-123",
    "url": "https://example.com/article",
    "normalizedUrl": "https://example.com/article",
    "domain": "example.com",
    "title": null,
    "description": null,
    "thumbnailUrl": null,
    "summary": null,
    "saveWhy": null,
    "tags": [],
    "enrichmentStatus": "pending",
    "snapshotStatus": "pending",
    "lastErrorCode": null,
    "isFavorite": false,
    "collectionIds": [],
    "notes": null,
    "clientSavedAt": "2026-04-12T10:30:00Z",
    "lastOpenedAt": null,
    "createdAt": "2026-04-12T10:30:01Z",
    "updatedAt": "2026-04-12T10:30:01Z",
    "enrichedAt": null
  }
}
Server-managed fields (normalizedUrl, domain, tags, summary, saveWhy, enrichmentStatus) are set by the server during enrichment. Client-provided title, description, and thumbnailUrl are accepted as initial values but will be overwritten when enrichment completes.

Delete Bookmark

Soft-deletes a bookmark. It becomes a tombstone visible in delta sync.
curl -X DELETE \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://api.unisave.io/v1/bookmarks/abc-123"
Response 204 No Content

Set Collection Membership

Replace the set of collections a bookmark belongs to. Pass the full list of collection IDs — any missing IDs are removed.
curl -X PUT \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  "https://api.unisave.io/v1/bookmarks/abc-123/collections" \
  -d '{
    "collectionIds": ["col-1", "col-2"]
  }'
Request Body
FieldTypeRequiredDescription
collectionIdsstring[]YesFull list of collection IDs. Pass [] to remove all.
Response 204 No Content

Conflict Resolution

UniSave uses server last-write-wins:
  • The server sets updated_at = now() on every successful write
  • Delta sync returns records ordered by (updated_at, client_id) ascending
  • If two clients update the same bookmark, the most recent write wins
  • Clients should merge enrichment data from the server even when the local copy is “newer” (enrichment is always server-authoritative)