Guide

Automate Your Reference Image Library With the Local API

By refernLast updated October 20268 min read

Some library jobs are one small edit repeated a thousand times. You add a tag that the filename already states. You list every item that has no creator. You move a tagged batch out of an inbox folder. A short script can do these jobs through refern's Local API: JSON endpoints under /v1 that refern 1.10 serves on your own computer.

This guide shows which jobs suit a script, gives three short Python examples, and sets out the habits that keep a script safe. The examples use Python with the requests package. The same calls work from curl or any language that can send HTTP. While the API is on, refern serves its full reference at http://127.0.0.1:7733/llms-full.txt.

Which library jobs suit a script?

Use a script when you can write the rule down and it is the same for every item. Do the work by hand when each item needs your eye.

JobScript or by handToken level
Tag items from a naming scheme such as env_ or char_ScriptWrite
List untagged or uncredited items to review laterScriptRead
Move a tagged batch into a folderScript, after a checkOrganize
Choose which of five similar poses to keepBy handNot needed
Credit an artist whose name is not in any file dataBy handNot needed

A script only copies the rule you give it. If your filenames are not consistent, fix the mapping before you run anything. For help with a tag scheme that is worth automating, see the tagging taxonomy guide.

Turn on the Local API and create a token

  1. Open refern. Go to Settings > Local API.
  2. Switch on Allow apps on this computer to access the open workspace.
  3. Select Create token. Give it a name and an access level. Copy the token now, because refern shows it only once.
  4. Use the address that Settings shows, normally http://127.0.0.1:7733.

The access levels build on each other:

  • Read: browse, search, similar images, color search, thumbnails, and original files.
  • Write: everything Read allows, plus item metadata and tags, creating tags and creators, creating folders, imports, undo, and more.
  • Organize: everything Write allows, plus rename, move, copy, linked copies, Trash and restore, and editing or deleting tags and creators.

No level can delete files from disk, empty the Trash, or delete items permanently. You can also set a token to hide items marked NSFW. A script with that token never sees them.

The API works only while refern runs with a workspace open. refern can stay in the tray. It listens on 127.0.0.1 only and refuses requests from web pages. If port 7733 is taken, refern uses the next free port up to 7743, and Settings shows the address in use. Requests are refused when the trial has expired and no license is active.

Every script below starts with the same lines. GET /v1 returns the open workspace. Every write must send its workspaceId, so a write fails with workspace_changed if you switch workspaces during a run. Nothing is written in that case.

import requests

API = "http://127.0.0.1:7733/v1"
H = {"Authorization": "Bearer rfn_..."}
ws = requests.get(API, headers=H).json()["workspace"]["id"]

def items_in(folder_id):
    cursor = None
    while True:
        params = {"folderId": folder_id, "limit": 100}
        if cursor:
            params["cursor"] = cursor
        page = requests.get(f"{API}/items", headers=H, params=params).json()
        yield from page["items"]
        cursor = page["nextCursor"]
        if not cursor:
            break

GET /v1/items lists the items directly inside one folder, one page at a time. To find a folder's id, call GET /v1/folders and match the name. Collect the ids you need before you write anything. If you move items while you page through a folder, the listing shifts.

Start with a Read-only report

A report is the safest first script. It needs only a Read token, so it cannot change anything. It also shows you how your library looks to the API before you trust a write.

To list untagged items in a folder and its subfolders, use POST /v1/search with tagMode set to untagged:

hits = requests.post(f"{API}/search", headers=H, json={
    "tagMode": "untagged", "folderId": FOLDER, "recursive": True, "limit": 200,
}).json()
for item in hits["items"]:
    print(item["folderPath"], item["name"])
if hits["truncated"]:
    print("More items matched. Run this on a smaller folder.")

A search returns at most 200 items. truncated tells you when more matched.

To list items with no creator, read each item's creatorIds and creator fields and write the gaps to a CSV file. The sourceUrl column helps you find the artist later.

import csv

with open("uncredited.csv", "w", newline="", encoding="utf-8") as f:
    out = csv.writer(f)
    out.writerow(["name", "folder", "source"])
    for item in items_in(FOLDER):
        if item["kind"] in ("image", "video") and not item["creatorIds"] and not item["creator"]:
            out.writerow([item["name"], item["folderPath"], item["sourceUrl"]])

Inside the app, the no:creator search filter finds the same gaps. See the search operators guide. For what to record once you find the source, see how to track where references came from.

Tag items from an existing naming scheme

Many libraries already hold their categories in filenames, such as env_forest_012.jpg or char_knight_03.png. This script reads the prefix and adds a matching tag. It sends dryRun: True first, so refern shows the changes without saving them.

from collections import defaultdict

PREFIX_TAGS = {"env": "environment", "char": "character", "prop": "prop"}

by_tag = defaultdict(list)
for item in items_in(FOLDER):
    prefix = item["name"].split("_")[0].lower()
    if item["kind"] in ("image", "video") and prefix in PREFIX_TAGS:
        by_tag[PREFIX_TAGS[prefix]].append(item["id"])

for tag, ids in by_tag.items():
    for start in range(0, len(ids), 500):
        body = {"workspaceId": ws, "itemIds": ids[start:start + 500],
                "tags": {"add": [tag]}, "dryRun": True}
        r = requests.patch(f"{API}/items", headers=H, json=body)
        r.raise_for_status()
        print(tag, r.json())

Read the dry-run output. When it is correct, remove dryRun and run the script again with a Write token. Some points to know:

  • PATCH /v1/items takes at most 500 item ids per request, so the script sends batches.
  • A tag can be its id, its exact name, or one of its aliases.
  • An unknown tag name fails with unknown_tags unless you send createMissing: True. Leave it off. Then a typo fails the request instead of creating a new tag. Create the tags in the app first.
  • Tag links, parent rules, and NSFW rules apply, as they do in the app.
  • On a linked copy, the edit goes to the original.
  • For the saving run, add an Idempotency-Key header to each request, such as {**H, "Idempotency-Key": f"prefix-{tag}-{start}"}. If a request times out and you send it again with the same key within one hour, refern returns the first result and does not apply the change twice.

Move a tagged batch into a folder

Moving needs an Organize token. Moves are disk operations. refern records them, but you cannot undo them from the change list. So tag first, because tags can be undone. Check the tags. Move only after that.

folders = requests.get(f"{API}/folders", headers=H).json()["folders"]
dest = next(f for f in folders if f["name"] == "Environments")

hits = requests.post(f"{API}/search", headers=H, json={
    "tagNames": ["environment"], "folderId": INBOX, "recursive": False,
    "kinds": ["image", "video"], "limit": 200,
}).json()
r = requests.post(f"{API}/items/move", headers=H, json={
    "workspaceId": ws, "itemIds": [i["id"] for i in hits["items"]], "folderId": dest["id"],
})
r.raise_for_status()
print(r.json())
  • A move takes at most 200 items. If truncated is true, run the script again. The moved items have left the inbox, so the next search finds the rest.
  • If a name already exists in the destination, refern keeps both items.
  • A move that fails partway still returns 200, with partial: true, the counts done so far, and an error. Print the response and read it.
  • A hidden folder cannot be a destination. Show it first.
  • Create the destination in the app, or with POST /v1/folders and a Write token. That call creates the folder on disk.
  • POST /v1/items/link-copy takes the same body and adds linked copies instead. The files stay where they are.

Check the change list, and undo when needed

Every write appears in Settings > Local API > Recent API changes. refern also writes it to local-api-journal.ndjson in the app data folder, and GET /v1/changes lists recent API changes, newest first. Open the app and look at a few changed items before you run a script on more folders.

You can undo these changes from Settings or with POST /v1/changes/{id}/undo:

  • Item metadata and tags, appearance, and an item's creators
  • Tag edits, creator edits, and tag structure renames and recolors
  • Trash

Items that changed after the script ran are skipped. Undo needs the same access level as the change.

These changes are recorded but cannot be undone there: rename, move, copy, linked copies, make original, imports, restore, folder creation, folder and root hiding, groups and links, Auto Tagger runs and review, smart folders, custom tag colors, tag structure members, and creating or deleting tags, structures, and creators.

The journal keeps undo data for the newest 100 changes, up to 32 MB in all. A single change over 4 MB is saved but cannot be undone. Each batch request is one change. A large run of many batches can push its first batches out of the undo window, which is one more reason to test on a small folder first.

A safe order for any script

  1. Back up the workspace before a large run. See how to back up your reference library.
  2. Write the report version first, with a Read token.
  3. Pick a small folder with a few dozen items to test on.
  4. Run writes with dryRun: True and read the output.
  5. Run the real write with a Write token and an Idempotency-Key on each request.
  6. Check Recent API changes and a few items in the app. Undo if the result is wrong.
  7. Create an Organize token only for scripts that move or rename. Revoke tokens you no longer use in Settings.

When refern answers busy (503) or too_many_requests (429), wait and try again. Only one write runs at a time. A timeout or result_unknown (504) means refern did not finish in time, so read the items again before you retry.

Use MCP when an assistant app does the work

The same operations are also MCP tools at /mcp. MCP (Model Context Protocol) is the way assistant apps such as Claude Code, Codex, and Claude Desktop call tools. They use the same tokens and rules, and an app sees only the tools its token's level allows. Choose a script when the same rule must run the same way every time. Choose an assistant app when you want to describe a one-off job in words and review each step. To set one up, see how to connect Claude Code or Codex to your reference library.

The Local API is not a plugin system. Scripts and assistant apps run outside refern and work with the open workspace. They do not add features to the app.

If you do not use refern yet, download the 30-day trial and try the Read-only report on one small folder first.

Frequently asked questions

Can a script delete my reference files through the Local API?

No. No access level can delete files from disk, empty the Trash, or delete items permanently. An Organize token can move items to the Trash, but the files stay on disk and you can restore them.

Which access level should a script's token have?

Give each script the lowest level its job needs. Read is enough for reports and lists. Write adds tags and other item metadata. Organize adds rename, move, copy, and Trash, so use it only for scripts that change folders.

Can I undo what a script changed?

Partly. Changes to item metadata and tags, appearance, an item's creators, tag edits, creator edits, and Trash can be undone in Settings > Local API. Disk operations such as move, rename, and copy, plus imports and folder creation, are recorded but cannot be undone there. Undo data is kept for the newest 100 changes.

Can a web page or a cloud service call the Local API?

No. The server listens only on 127.0.0.1 and refuses requests from web pages. Scripts and assistant apps must run on the same computer as refern, and refern must be open with a workspace.

Do I need a license to use the Local API?

The Local API works during the 30-day free trial. After the trial ends, it refuses requests until a license is active. A license is $35 one-time for one user on up to three devices.

Sources

  1. 1.refern 1.10 release notes: Local API and MCP server, released October 10, 2026; reviewed 2026-10-11
  2. 2.Local API access levels, localhost-only listening, and change list; reviewed 2026-10-11
  3. 3.Price and trial terms; checked 2026-10-11