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
| Param | Type | Required | Description |
|---|
cursor | string | No | Opaque cursor from previous response. Omit for initial sync. |
limit | integer | No | Max 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
- Store the last
nextCursor locally
- On sync, request
/bookmarks/delta?cursor=<stored>
- Apply
upserts (insert or update local records)
- Apply
tombstones (mark as deleted locally)
- Store the new
nextCursor
- 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
| Param | Type | Description |
|---|
clientBookmarkId | string | Client-generated unique ID |
Request Body
| Field | Type | Required | Description |
|---|
url | string | Yes | The URL to save. Must be http: or https:. Max 2048 chars. |
clientSavedAt | string | No | ISO 8601 timestamp when the user saved it. |
title | string | No | Client-provided title (max 1000 chars). Overwritten when enrichment completes. |
description | string | No | Client-provided description. Overwritten when enrichment completes. |
thumbnailUrl | string | No | Client-provided thumbnail URL. Overwritten when enrichment completes. |
isFavorite | boolean | No | Mark as favorite. |
notes | string | No | User notes (max 10000 chars). |
lastOpenedAt | string | No | ISO 8601 timestamp of last open. |
collectionIds | string[] | No | Full 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
| Field | Type | Required | Description |
|---|
collectionIds | string[] | Yes | Full 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)