Purl

API

Save, list and organize your Purl links and folders from your own code with the REST API and an API key.

Purl's REST API lets your own scripts and tools save links, list them, and keep them organized in folders, with the same rules as the app. Every request is scoped to the account that owns the API key.

The base URL is https://purl.live/api/v1. Every request needs an API key in the Authorization header.

Authentication

Create a key in Purl under Settings โ†’ Integrations. Keys start with purl_ and are shown once, so copy it somewhere safe. You can revoke a key from the same place at any time.

Send it as a bearer token:

Bash
curl https://purl.live/api/v1/links \
  -H "Authorization: Bearer purl_your_key"

Requests and responses are JSON. Send Content-Type: application/json with a body.

A link looks like this:

JSON
{
  "id": "cm2x8k1q00001",
  "url": "https://example.com/article",
  "title": "An article worth keeping",
  "description": "The page's description, when it has one.",
  "favicon": "https://example.com/favicon.ico",
  "thumbnail": "https://example.com/cover.jpg",
  "domain": "example.com",
  "contentType": "WEB",
  "createdAt": "2026-10-10T09:41:00.000Z",
  "folderId": null
}

contentType is one of WEB, YOUTUBE, PDF or AUDIO. folderId is null when the link isn't in a folder.

GET /links returns your links, newest first.

Query parameterDescription
limitHow many to return, 1โ€“100. Defaults to 50.
cursorThe nextCursor from the previous page.
contentTypeOnly links of this type: WEB, YOUTUBE, PDF or AUDIO.
folderIdOnly links in this folder.
Bash
curl "https://purl.live/api/v1/links?limit=20" \
  -H "Authorization: Bearer purl_your_key"
JSON
{
  "data": [{ "id": "cm2x8k1q00001", "url": "https://example.com/article", "...": "..." }],
  "nextCursor": "cm2x8k1q00000"
}

Keep passing nextCursor as cursor until it comes back null.

POST /links saves a URL. Purl reads its title, description, favicon and thumbnail before it answers, so the response is the finished link. Pass folderId to save it straight into a folder.

Bash
curl https://purl.live/api/v1/links \
  -H "Authorization: Bearer purl_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/article", "folderId": "cm2x8f0a00002" }'

It answers 201 with the link. Saving a URL you already have doesn't create a copy: the existing link is refreshed, and when you pass a folderId it's moved there, with "moved": true in the response.

GET /links/{id} returns one link, or 404 if it isn't yours.

PATCH /links/{id} changes any of url, title, description and folderId. Send only the fields you want to change. "folderId": null takes the link out of its folder.

Bash
curl -X PATCH https://purl.live/api/v1/links/cm2x8k1q00001 \
  -H "Authorization: Bearer purl_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "title": "A better title", "folderId": "cm2x8f0a00002" }'

DELETE /links/{id} deletes it for good and answers 204.

PATCH /links/bulk moves several links into a folder at once ("folderId": null takes them out of their folders):

Bash
curl -X PATCH https://purl.live/api/v1/links/bulk \
  -H "Authorization: Bearer purl_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["cm2x8k1q00001", "cm2x8k1q00003"], "folderId": "cm2x8f0a00002" }'
JSON
{
  "moved": [{ "id": "cm2x8k1q00001", "previousFolderId": null }],
  "notFound": ["cm2x8k1q00003"]
}

DELETE /links/bulk with { "ids": [...] } deletes them and answers { "deleted": 2 }.

Ids you don't own are skipped, not errors: they come back in notFound (or simply aren't counted). Repeated ids count once.

Folders

A folder looks like this:

JSON
{
  "id": "cm2x8f0a00002",
  "name": "Reading list",
  "slug": "reading-list",
  "emoji": "๐Ÿ“š",
  "description": "Long reads for the weekend",
  "isPublic": false,
  "position": 1,
  "linkCount": 12
}

emoji is always set (๐Ÿฆช when you haven't picked one). description is null when empty. position is your order: the one you see in Purl's folder menu.

List folders

GET /folders returns all your folders, in your order.

Create a folder

POST /folders with name (up to 60 characters, unique for you, ignoring case), and optionally emoji (a single emoji) and description (up to 160 characters). It answers 201 with the folder, which goes last in your order.

Bash
curl https://purl.live/api/v1/folders \
  -H "Authorization: Bearer purl_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Reading list", "emoji": "๐Ÿ“š" }'

Update a folder

PATCH /folders/{id} changes any of name, emoji, description and isPublic. null clears emoji or description. "isPublic": true shares the folder: anyone with the link can read it at purl.live/@your-username/folder-slug (search engines don't index it). false makes it private again.

Delete a folder

DELETE /folders/{id} deletes the folder and keeps its links, now outside any folder. Add ?withLinks=true to delete its links too. It answers { "deletedLinks": 0 } with the number of links deleted.

Reorder folders

PUT /folders/order sets your order. Send every folder id, first to last:

Bash
curl -X PUT https://purl.live/api/v1/folders/order \
  -H "Authorization: Bearer purl_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "ids": ["cm2x8f0a00002", "cm2x8f0a00005", "cm2x8f0a00004"] }'

It answers { "folders": [...] } in the new order. A list that's missing a folder, has an extra or repeated one, or one that isn't yours changes nothing and answers 400 with INVALID_ORDER. Fetch the folders again and retry.

Errors

Errors are JSON with an error message, and a code when there's something specific to act on:

JSON
{ "error": "You already have a folder with that name. Choose another.", "code": "NAME_TAKEN" }
StatusMeaning
400The request isn't valid: a bad URL or JSON body, or a field out of bounds. Codes include NAME_EMPTY, NAME_TOO_LONG, INVALID_EMOJI, INVALID_DESCRIPTION, INVALID_ORDER, INVALID_IDS, TOO_MANY_IDS and INVALID_FOLDER.
401The API key is missing, wrong or revoked.
403A limit is reached (LIMIT_REACHED): 1,000 saved links or 100 folders per account.
404The link or folder doesn't exist or isn't yours.
409You already have a folder with that name (NAME_TAKEN).
429Too many requests. Wait the number of seconds in the Retry-After header.

Limits

  • Each account can save up to 1,000 links and create up to 100 folders. Everyone gets the same limits, and Purl is free.
  • Up to 120 requests a minute per account, of which 60 can be POSTs.
  • The API allows cross-origin requests, so you can call it from a browser extension or a web page. Keep your key out of anything you publish.

Prefer to work from a chat? The MCP server gives Claude and other AI assistants the same tools.