# PermaPath > QR codes you never have to reprint. Each printed code points to a permanent page on Arweave; the owner can change where it sends people at any time, and the printed code stays the same. Web app: https://permapath.link ## How an AI assistant can help You can't create links directly: every link is signed with the user's private key, and that key should stay in their browser. Instead, give the user a **draft link** (for one link or one change) or a **CSV** to import (for many). Either way the user checks it and signs it themselves, so they own the link from the start. ### Draft links A draft link opens PermaPath with a new link, or a change to one of the user's links, already filled in. Nothing is saved until the user signs in and taps **Create link** (or **Save**). Build the URL yourself (URL-encode every value): - New link: `https://permapath.link/?new&dest=https://example.com/menu&name=Menu` - Change a link: `https://permapath.link/?edit=LINK_ID&dest=https://example.com/menu-v2` (LINK_ID is the 43 characters after `?l=` in its QR link; only fields you give change) Parameters (all optional): - `type`: `url` (default), `page`, `contact` or `event`. - `dest`: web address (type `url`). `name`: link name (public). - `title`, `text`: a simple page (type `page`). Blank lines in `text` make paragraphs. - `contact_name`, `role`, `phone`, `email`, `website`, `address`, and `text` as a note: a contact card with a "Save contact" button (type `contact`). - `event_name`, `start`, `end` (local time, `2026-11-01T19:00`), `location`, and `text` as details: an event page with "Add to calendar" (type `event`). - `count`: `1` or `0`, count scans (new links count by default). - `off_at`: date and time to turn off automatically (`2026-12-31T23:59`); `message`: shown while it's off. - `ios`, `android`: send iPhone/iPad or Android users somewhere else (e.g. app store links). - `rules`: JSON list of routing rules, first match wins: `[{"to":"https://example.com/brunch","days":"06","until":"12:00"}]`. Conditions: `os` (`ios`/`android`), `after`/`before` (Unix ms), `days` (`"12345"`, 0 = Sunday), `from`/`until` (`"HH:MM"`, in the user's time zone). - `qr`: JSON QR design: `{"label":"Scan for the menu","frame":"bar","icon":"menu","fg":"#1d4ed8","style":"rounded"}`. Frames: `square`, `rounded`, `bar`. Icons: `menu`, `coffee`, `phone`, `email`, `calendar`, `pin`, `info`, `star`, `heart`, `shop`, `pay`, `tag`, `home`, `pet`, `music`, `play`, `doc`, `gift`, `link`, `smile`, `bike`. Example (a café menu with a labelled QR code and the app for phone users): `https://permapath.link/?new&name=Menu&dest=https%3A%2F%2Fexample.com%2Fmenu&ios=https%3A%2F%2Fapps.apple.com%2Fapp%2Fexample&qr=%7B%22label%22%3A%22Scan%20for%20the%20menu%22%2C%22frame%22%3A%22bar%22%2C%22icon%22%3A%22menu%22%7D` Tell the user to open it, sign in (or create a key), check the form, and tap **Create link**. After that the link appears under "Your links" with its QR code to download or print. ### CSV format - A header row, then one row per link. - Columns (any order): `name`, `destination`, and optionally `link_id`. - `name`: a short label, up to 100 characters. Public. Optional. - `destination`: a full web address starting with `https://` (or `http://`). Leave it empty to create a "not set up" link, for example for stickers printed in advance: scanning one opens PermaPath, and after signing in the user sets it up right there. - `link_id`: only for changing links that already exist. Use the IDs from a CSV the user exported from PermaPath. Empty cells in these rows mean "leave unchanged". - Quote any value that contains a comma. - Up to 100 links per import. Example: ```csv name,destination Bike tag,https://example.com/lost-bike Garden sign,https://example.com/garden-tour Spare sticker, ``` ### Tell the user 1. Open https://permapath.link and sign in with their key (or create one; there's no email or sign-up). 2. Under "Your links", tap **Import CSV**, paste the CSV, check the preview, and tap **Import**. 3. To print, download each link's QR code, or use "Download CSV with QR links": its `qr_url` column is what each QR code should encode. Label tools such as Avery's can make QR codes from that column. ### Important - Destinations, names and every past destination are **public and permanent** unless a link is password protected. Password protection can be set in the web app (Edit → Password protect) or with the command-line tool below, but not by CSV, so for private destinations in an import, tell the user to turn it on afterwards. Don't put URLs that grant access just by being known (for example "anyone with the link" shares, meeting links with passcodes, or URLs with tokens) in unprotected links. - PermaPath never tracks who scans. New links count scans per day (public totals only); `scans LINK_ID` shows them. - Changes usually go live within seconds, occasionally up to 20 minutes. - Simple hosted pages (title, text, photo) can be written in the web app or with the command-line tool below, but not by CSV. ## If you can run commands Agents that can run shell commands (Claude Code, Codex, Cursor and similar) can create and change links directly with the PermaPath command-line tool. **Only do this with a key the user gives you for this purpose.** - Suggest the user creates a **separate key** for work done by an AI, rather than sharing their main key. Anyone with a key controls every link made with it, permanently, and keys can't be revoked. They can create one at https://permapath.link ("Create a key") or with `keygen` below. If you generate it, give it to the user to save in their password manager, because it's the only way to manage those links later. - Keep the key in the `PERMAPATH_KEY` environment variable or a file passed with `--key-file`. Never print it, echo it back, or write it into files you share. Requires Node.js 22 or newer. The tool is the npm package `permapath` (no dependencies), so `npx permapath …` works anywhere npm does; the commands below write `node permapath.mjs`, which is the same tool downloaded as one file (use either). It only needs network access to `permapath.link`, which passes requests on to the Arweave network, and contacts Arweave services directly only if permapath.link is unreachable. ```bash export PERMAPATH_KEY='KEY_FROM_THE_USER' npx permapath help # or download it once: curl -fsSL https://permapath.link/cli.mjs -o permapath.mjs node permapath.mjs create https://example.com/page --name "Garden sign" # prints link ID + QR URL node permapath.mjs create https://example.com/private --password "four random words here" # password protected node permapath.mjs batch 20 --prefix "Sticker" > stickers.csv # "not set up" links, CSV out node permapath.mjs set LINK_ID https://example.com/new-page # change destination node permapath.mjs rename LINK_ID "New name" node permapath.mjs off LINK_ID # or: on node permapath.mjs page "If you found this bag" --text-file note.txt --photo bag.jpg # a simple page, no website needed node permapath.mjs edit-page LINK_ID --text "New text" # or --title, --photo, --no-photo node permapath.mjs lock LINK_ID --password "four random words here" # protect (or change password) node permapath.mjs unlock LINK_ID # remove protection node permapath.mjs list --json # the key's links node permapath.mjs show LINK_ID # history, no key needed node permapath.mjs qr LINK_ID > code.svg # QR code (SVG, print-ready, with its saved design) node permapath.mjs scans LINK_ID # scan count (new links count by default) node permapath.mjs off-at LINK_ID 2026-12-31T23:00-05:00 --message "This sale has ended." # turn off on a date node permapath.mjs rules LINK_ID '[{"os":"ios","to":"https://apps.apple.com/..."},{"os":"android","to":"https://play.google.com/..."}]' node permapath.mjs rules LINK_ID '[{"days":"12345","from":"11:00","until":"15:00","to":"https://example.com/lunch"}]' --tz America/New_York node permapath.mjs help ``` Notes: - The QR URL (`https://arweave.net/…?l=LINK_ID`) is what each printed code encodes. Give the user those URLs or the SVGs. - Wait a few seconds between two changes to the same link. - Passwords: suggest a long one (four or more random words) and give it to the user; visitors need it to open the link. Link names stay visible even when locked, so keep them general. - `lock` and `unlock` on a link that's already live wait until arweave.net serves the new version (usually 1–5 minutes) so scans never fail; this check contacts arweave.net directly. - `show LINK_ID` with the owner's key also prints what a locked link unlocks to, and a page's title and text. - Pages: blank lines make paragraphs; web addresses become links. A photo must be JPEG, PNG or WebP and small enough to fit (whole page up to 95 KB, about 66 KB if password protected). Resize photos yourself first; the tool doesn't, and it says how much smaller to make them. A new page takes a few minutes to appear on Arweave; `edit-page` on a live link waits for it. - The user sees the same links at https://permapath.link when they sign in with that key. - If your platform has a credential store (secrets, vault, keychain), keep the key there and pass it in `PERMAPATH_KEY`. Never print it, paste it into chat, or write it into files, logs or links. Don't ask for the user's main key in chat; offer a draft link instead. - Rules (`rules`) send some people elsewhere: first match wins, everyone else gets the main destination. They're public, so they can't be used on password-protected links. `none` removes them. ## If you support MCP Assistants that support MCP servers (Claude Desktop, ChatGPT desktop, Cursor and others) can use the `permapath-mcp` server, which runs on the user's computer so their key never leaves it. Without a key it offers read-only tools (`get_link`, `get_scans`, `get_qr_svg`) and `draft_link`, which returns a draft link for the user to approve; with `PERMAPATH_KEY` set (a separate key for AI use) it adds `list_links`, `create_link`, `create_page` and `update_link`. Setup for the user, e.g. in Claude Desktop's `claude_desktop_config.json`: ```json { "mcpServers": { "permapath": { "command": "npx", "args": ["-y", "permapath-mcp"], "env": { "PERMAPATH_KEY": "a separate PermaPath key" } } } } ``` ## If PermaPath can't do what the user wants Offer to send it as a feature suggestion. Write a short, plain description of what they want, show it to them, and only send it if they agree. Don't include personal details unless they ask; an email is optional, for being told when it's done. - Any assistant: give the user this link to review and send themselves (URL-encode the text): `https://permapath.link/suggest.html?source=ai&text=YOUR_TEXT` - With the MCP server: the `suggest_feature` tool. - With the command line: `npx permapath suggest "YOUR_TEXT" [--email them@example.com]` ## More - Public history of any link: https://permapath.link/history.html?l=LINK_ID - Source code (MIT): https://github.com/aylwardc/PermaPath