Build with Pinport

API reference

Build with your bookmarks. Start new integrations on API v2.

Authentication

All API requests must include your API key in the X-Api-Key header. You can create API keys in your settings.

X-Api-Key: your-api-key

Keep your API key secure! Don't share it or expose it in client-side code.

Base URL

All API requests should be made to: https://pinport.app/api/v2⁠

Requests are limited to 120 per minute per API key; above that the API answers 429 Too Many Requests.

Response Format

All responses are JSON. Single bookmarks are returned as objects, lists inside a data array, and errors carry a message.

Bookmark Object

{
    "id": "abc123",
    "url": "https://example.com",
    "title": "Example Website",
    "note": "Why I saved this",
    "tags": ["example", "demo"],
    "createdAt": "2026-06-15T12:30:45.000Z",
    "updatedAt": "2026-06-15T12:30:45.000Z"
}

note is your own text about why you saved the URL. It is never filled in automatically. updatedAt changes when you edit the bookmark; tag renames do not touch it.

Error Response Example

{
    "message": "URL is required"
}

Endpoints

List Bookmarks

GET /bookmarks
GET /bookmarks?tag=recipes&tag=vegan&limit=50
GET /bookmarks?cursor=MjAyNi...

Returns your bookmarks, newest first, one page at a time.

Query Parameters

  • tag optional, repeatable - Only bookmarks carrying every given tag
  • untagged optional - Only bookmarks without tags
  • limit optional - Page size, 1-1000 (default 100)
  • cursor optional - The nextCursor of the previous page

Response

Status: 200 OK or 400 Bad Request

{
    "data": [
        {
            "id": "abc123",
            "url": "https://example.com",
            "title": "Example Website",
            "note": "",
            "tags": ["example"],
            "createdAt": "2026-06-15T12:30:45.000Z",
            "updatedAt": "2026-06-15T12:30:45.000Z"
        }
    ],
    "nextCursor": "MjAyNi0wNi0xNVQxMjozMDo0NS4wMDBafGFiYzEyMw"
}

nextCursor is null on the last page. To fetch everything, follow the cursor until it is null; to keep a local copy up to date afterwards, use Changes below instead of listing again.

Search Bookmarks

GET /bookmarks/search?q=your+query
GET /bookmarks/search?q=github.com/sveltejs&type=keyword
GET /bookmarks/search?q=sourdough&tag=recipes

Semantic search by default: finds bookmarks by meaning and returns the closest matches first. It matches the bookmark itself (title, note, tags, URL) and, on plans with content indexing, the text of the saved page. Bookmarks containing the query words are found too and rank higher. Bookmarks below a relevance threshold that do not contain the words are left out, so an empty list means nothing relevant was found. With type=keyword only the words are matched against title, note, URL and tags: every word as a prefix, in any order, tolerating typos.

Query Parameters

  • q *required - The search query (max 500 characters)
  • type optional - semantic (default) or keyword
  • limit optional - Maximum number of results, 1-100 (default 20)
  • tag, untagged optional - Restrict the search to a tag selection, as in List Bookmarks

Response

Status: 200 OK, 400 Bad Request, or 429 Too Many Requests

data holds the matching bookmarks, best first. Semantic results carry a score (cosine similarity of the best match, higher means more similar), matchedBy (which signals matched: bookmark, summary, content, best first, then keyword when the bookmark contains the query words) and snippet (the page summary or the best-matching passage when the page content matched, otherwise null). Bookmarks imported moments ago are found by their words at once and by meaning within a few minutes; until then they have no score.

Page State

Every bookmark carries a page object with what Pinport knows about the saved URL, or null right after creation:

  • status - ok, thin (title and description only), pending, gone, unreachable, auth (login required), blocked, skipped, unsupported
  • httpStatus, checkedAt - Last fetch attempt
  • movedTo - Redirect target when the page moved elsewhere
  • attention - gone, unreachable, moved or null: whether the link needs attention
  • dismissed - The user chose to keep the link as is

List, tags and search accept attention=true to restrict to bookmarks that need attention. PATCH /bookmarks/:id with { "attentionDismissed": true } hides the current attention state until the page fails again; false shows it again. GET /bookmarks/:id/archive returns the Wayback Machine's closest snapshot as { "url": "..." }, or null when nothing is archived. Links are checked every 7 to 30 days depending on the plan, and a page counts as gone or unreachable only after two failed checks on separate days.

Changes (Sync)

GET /bookmarks/changes?since=0
GET /bookmarks/changes?since=425790

Everything that changed after a sync version, oldest change first, for keeping a local copy in sync without downloading the whole collection. Start with since=0, store the returned version, and pass it as since next time. Deleted bookmarks are reported as ids for 30 days; a client that has not synced for longer should start from 0 again.

Query Parameters

  • since optional - Version from a previous response (default 0)
  • limit optional - Page size, 1-5000 (default 1000)

Response

Status: 200 OK or 400 Bad Request

{
    "changed": [
        {
            "id": "abc123",
            "url": "https://example.com",
            "title": "Example Website",
            "note": "",
            "tags": ["reference"],
            "createdAt": "2026-06-15T12:30:45.000Z",
            "updatedAt": "2026-06-16T09:12:00.000Z"
        }
    ],
    "deleted": ["def456"],
    "version": 425790,
    "hasMore": false
}

While hasMore is true, request again with since set to the returned version.

Tags

GET /tags
GET /tags?tag=recipes

Your tags with the number of bookmarks carrying each. With a tag filter (same parameters as List Bookmarks), the counts cover only the matching bookmarks, which is what a tag sidebar needs.

Response

Status: 200 OK

{
    "data": [ { "name": "recipes", "count": 42 }, { "name": "vegan", "count": 7 } ],
    "untagged": 3,
    "total": 120
}

Topics

GET /bookmarks/topics
GET /bookmarks/topics?steps=[["topic","0c6e2b9d4f18"]]
GET /bookmarks/topics?steps=[["q","video"],["topic","3f9a1c0b7e42"],["tag","work"]]

The topics Pinport finds in a bookmark list, and the list itself once narrowed. The list is described by steps, a JSON array of [kind, value] pairs applied in order, so a topic picked after a search splits only the search results, and a search after a topic looks only within it. URL-encode the parameter.

Steps

  • ["q", query] - Keep the bookmarks matching a search, best first (at most 5)
  • ["tag", name] - Keep the bookmarks carrying the tag
  • ["untagged", ""], ["attention", ""] - Keep bookmarks without tags, or that need attention
  • ["topic", id] - Keep the bookmarks of a topic returned for the steps before it (at most 5)

Query Parameters

  • steps optional - The steps (default [], the whole library)
  • limit optional - Bookmarks returned in data, 1-100 (default 50)

Response

Status: 200 OK, 400 Bad Request, or 429 Too Many Requests

{
    "topics": [
        { "id": "3f9a1c0b7e42", "label": "Color and contrast", "count": 14 },
        { "id": "a81d55e0c913", "label": "Icon sets", "count": 9 }
    ],
    "applied": [ { "id": "0c6e2b9d4f18", "label": "Design tools" } ],
    "ids": ["abc123", "def456"],
    "data": [ { "id": "abc123", "url": "https://example.com", "title": "Example Website" } ]
}

topics splits the narrowed list, largest first, and is empty when the list has fewer than 12 bookmarks. applied lists the requested topics the list still forms, with their names; a topic id is a fingerprint of its members, so it lapses when an earlier step changes or the library regroups, and is then left out. ids (every remaining bookmark, best first) and data (the first limit of them, as in Search) are present only when a search or topic narrows the list; otherwise use List Bookmarks. Requests are limited to 30 per minute per account, and topic names count against the plan's monthly naming allowance.

Get a Bookmark

GET /bookmarks/:id

Returns a single bookmark by ID.

Response

Status: 200 OK or 404 Not Found

Returns a bookmark object or an error if the bookmark doesn't exist.

Create a Bookmark

POST /api/v2/bookmarks
Content-Type: application/json
X-Api-Key: your-api-key

{
    "url": "https://example.com",
    "title": "Example Website",
    "note": "Why I saved this",
    "tags": ["example", "demo"]
}

Creates a new bookmark.

Request Body Fields

  • url *required - The URL to bookmark
  • title optional - Title of the bookmark (taken from the page if not provided)
  • note optional - Your note, max 250 characters
  • tags optional - Array of tag strings

Response

Status: 201 Created or 400 Bad Request

Returns the created bookmark object or an error.

Update a Bookmark

PUT /api/v2/bookmarks/:id
Content-Type: application/json
X-Api-Key: your-api-key

{
    "url": "https://example.com",
    "title": "Updated Title",
    "note": "Updated note",
    "tags": ["updated", "tags"]
}

Replaces a bookmark. url and title are required; a missing note or tags clears them.

Response

Status: 200 OK, 400 Bad Request, or 404 Not Found

Returns the updated bookmark object or an error.

Partially Update a Bookmark

PATCH /api/v2/bookmarks/:id
Content-Type: application/json
X-Api-Key: your-api-key

{
    "title": "Updated Title",
    "tags": ["updated", "tags"]
}

Updates only specific fields of a bookmark. Only include the fields you want to modify.

Response

Status: 200 OK, 400 Bad Request, or 404 Not Found

Returns the updated bookmark object or an error.

Delete a Bookmark

DELETE /bookmarks/:id
X-Api-Key: your-api-key

Deletes a bookmark.

Response

Status: 200 OK or 404 Not Found

Returns the deleted bookmark or an error if the bookmark doesn't exist.

Update Several Bookmarks

PATCH /api/v2/bookmarks
Content-Type: application/json
X-Api-Key: your-api-key

[
    { "id": "abc123", "tags": ["archive"] },
    { "id": "def456", "note": "" }
]

Applies a partial update to each listed bookmark. Every id must exist, otherwise nothing is changed.

Response

Status: 200 OK, 400 Bad Request, or 404 Not Found

{
    "count": 2,
    "bookmarkIds": ["abc123", "def456"]
}

Delete Several Bookmarks

POST /api/v2/bookmarks/delete
Content-Type: application/json
X-Api-Key: your-api-key

{ "ids": ["abc123", "def456"] }

Deletes the listed bookmarks. Every id must exist, otherwise nothing is deleted.

Response

Status: 200 OK, 400 Bad Request, or 404 Not Found

Returns count and bookmarkIds as in Update Several Bookmarks.

Import Bookmarks

POST /api/v2/bookmarks/import
Content-Type: application/json
X-Api-Key: your-api-key

{
    "bookmarks": [
        { "url": "https://example.com", "title": "Example", "tags": ["imported"] },
        { "url": "https://example.org", "title": "Another", "note": "From my old manager" }
    ]
}

Creates up to 2,000 bookmarks in one request. url and title are required for each. Titles are not fetched from the pages, and semantic search over the imported bookmarks becomes available within a few minutes.

Response

Status: 201 Created or 400 Bad Request

Returns data with the created bookmark objects, in the order they were sent.

Rename a Tag

PUT /api/v2/tags/:tag
Content-Type: application/json
X-Api-Key: your-api-key

{ "newName": "cooking" }

Renames the tag on every bookmark carrying it. If the new name already exists on a bookmark, the two are merged.

Response

Status: 200 OK or 400 Bad Request

Returns count, bookmarkIds, oldTag and newTag.

Delete a Tag

DELETE /tags/:tag
X-Api-Key: your-api-key

Removes the tag from every bookmark carrying it. The bookmarks themselves are kept.

Response

Status: 200 OK

Returns count, bookmarkIds and tag.

API v1 (deprecated)

Integrations built on https://pinport.app/api/v1 keep working unchanged until September 30, 2026: the bookmark field is called description there, GET /bookmarks returns the whole collection, and search is semantic only. v1 will not receive new features and is switched off after that date. Please move integrations to v2 before then.