API reference
Read and add to your library from scripts, MCP clients and other tools. Version 1. Machine-readable: /v1/openapi.json.
Authentication
Create a personal access token in Settings → API tokens and send it as a bearer token. Read-only tokens can list and search; read-and-write tokens can also save and organise. 120 requests a minute per token; responses carry RateLimit-Remaining.
curl https://refna.app/v1/refs?q=palette \ -H "Authorization: Bearer refna_pat_…"
Save links from a run
# 1. find a run and its links
curl https://refna.app/v1/runs -H "Authorization: Bearer $REFNA_TOKEN"
curl "https://refna.app/v1/runs/RUN_ID/links?limit=200" -H "Authorization: Bearer $REFNA_TOKEN"
# 2. save some of them, straight into a collection
curl -X POST https://refna.app/v1/refs \
-H "Authorization: Bearer $REFNA_TOKEN" -H "Content-Type: application/json" \
-d '{"runId":"RUN_ID","linkIds":["LINK_ID"],"collectionId":"COLLECTION_ID"}'Use with Claude (MCP)
The Refna MCP server lets Claude and other MCP clients search and organise your library with your token. Add it to your client’s MCP configuration:
{
"mcpServers": {
"refna": {
"command": "node",
"args": ["/path/to/refna/packages/mcp/dist/index.js"],
"env": { "REFNA_TOKEN": "refna_pat_…" }
}
}
}Endpoints
GET/v1/me— Check the connection
Who the token acts as, and the plan's library allowance.
Scope: library:read
GET/v1/refs— List or search references
Newest first; collection order with collectionId; relevance order with q (every word must match, as a prefix).
Scope: library:read
| q | Search notes, tags, titles, summaries and where links were found |
|---|---|
| tag | Only references with this tag |
| collectionId | Only references in this collection, in its order |
| archived | true lists archived references instead |
| cursor | nextCursor from the previous page |
| limit | 1–100 |
POST/v1/refs— Save links from a run
One reference per URL: saving a URL again adds the new run's evidence. Up to 500 links per call.
Scope: library:write
Body — Links of one of your runs, and optionally a collection to add them to
{
"runId": "3f6c1d2e-8a4b-4c7d-9e1f-2a3b4c5d6e7f",
"linkIds": [
"3f6c1d2e-8a4b-4c7d-9e1f-2a3b4c5d6e7f"
],
"collectionId": "3f6c1d2e-8a4b-4c7d-9e1f-2a3b4c5d6e7f"
}GET/v1/refs/lookup— Look up a page address
Whether a page is in the library. The address is normalised (fragment and tracking parameters dropped) before matching. Returns { ref: null } when it isn't saved.
Scope: library:read
| url | Full http(s) address |
|---|
GET/v1/refs/{id}— Get a reference
Scope: library:read
| id | UUID |
|---|
PATCH/v1/refs/{id}— Edit a note or tags
Only the annotation layer is writable; evidence and observation never change.
Scope: library:write
| id | UUID |
|---|
Body — note (null clears it) and/or tags (replaces them)
{
"note": "Use for wireframes",
"tags": [
"design",
"tools"
]
}GET/v1/collections— List collections
Scope: library:read
POST/v1/collections— Create a collection
Scope: library:write
Body — title (1–120) and optional description
{
"title": "Design",
"description": null
}GET/v1/collections/{id}— Get a collection with its references
The versioned CollectionExport document (schemaVersion 1.0.0), references in collection order.
Scope: library:read
| id | UUID |
|---|
POST/v1/collections/{id}/items— Add references to a collection
Appends in the order given; references already in it are skipped.
Scope: library:write
| id | UUID |
|---|
Body — Up to 500 reference ids
{
"refIds": [
"3f6c1d2e-8a4b-4c7d-9e1f-2a3b4c5d6e7f"
]
}GET/v1/runs— List your recent runs
Scope: library:read
GET/v1/runs/{id}/links— Page through a run's links
Discovery order. Use the ids with POST /v1/refs.
Scope: library:read
| id | UUID |
|---|---|
| after | Last position seen (start at -1) |
| limit | 1–500 |
GET/v1/watches— List watched pages
Pages Refna re-checks on a schedule, with the last check and the last check that found changes.
Scope: library:read
| archived | true lists archived watches instead |
|---|
POST/v1/watches— Watch a page
Checks list the page's links (no descriptions) and don't use the monthly run allowance; the plan's watch limit and schedules are the budget. Watching the same page again returns the existing watch (200). Free: 2 watches, weekly. Pro: 25, daily or weekly.
Scope: library:write
Body — url, mode (page | site), cadence (daily | weekly)
{
"url": "https://tools.example/",
"mode": "page",
"cadence": "weekly"
}GET/v1/watches/{id}— Get a watch and its recent checks
Scope: library:read
| id | UUID |
|---|
PATCH/v1/watches/{id}— Change, pause, archive or check now
checkNow runs a check at the next scheduler tick (within about a minute), at most once an hour.
Scope: library:write
| id | UUID |
|---|
Body — Any of cadence, paused, archived, checkNow
{
"paused": true
}GET/v1/watches/{id}/changes— What changed
Links added and removed in one check — by default the latest check that found changes. removalsChecked is false when discovery was cut short; then only additions are reported. Up to 1000 per kind; counts on the check are exact.
Scope: library:read
| id | UUID |
|---|---|
| check | A check id from GET /v1/watches/{id} |
| kind | added or removed |
Errors
Every error is { "error": { "code", "message" } }.
| 400 | invalid_request | The body or query didn't validate; the message names the field. |
|---|---|---|
| 401 | missing_token / invalid_token | No token, or it's malformed, revoked or expired. |
| 402 | allowance_used | Saving would go over the plan's library allowance. |
| 403 | insufficient_scope / watch_limit / cadence_not_in_plan | The token lacks the scope this endpoint needs; or (watches) the plan's active-watch limit or a schedule it doesn't include. |
| 404 | not_found | It doesn't exist, or isn't yours. |
| 409 | too_many_tokens | (Token creation) 20 active tokens per account. |
| 429 | rate_limited / too_soon | 120 requests a minute per token; see Retry-After. too_soon: a watch was checked in the last hour. |