perma.cc
Archives a URL to a permanent, court-citable snapshot. The whole skill exists because one HTTP 400 means three different things, and the message names only the least likely one.
HTTP 400 "You've reached your usage limit."
│
├── folder sent as ?query= → ignored, billed to PERSONAL quota
├── no folder at all → billed to PERSONAL quota
└── genuinely unsponsored → the only case the message describes
CLI
PC="${CLAUDE_SKILL_DIR}/scripts/permacc.py"
uv run --script "$PC" status # who the key is, and whether a sponsored folder exists
uv run --script "$PC" folders # every folder, with the sponsored one marked
uv run --script "$PC" archive URL... --auto-folder
uv run --script "$PC" archive --from-json inventory.json --out archives.json --auto-folder
uv run --script "$PC" verify --out archives.json # re-check captures on an existing record
uv run --script "$PC" delete --out archives.json --failed --private-if-undeletable
--auto-folder resolves the sponsored folder itself, which is the option to
reach for; --folder <id> pins one when an account has several. --out makes
the run resumable — already-archived URLs are skipped, so a rate limit or a
network drop costs nothing on the retry.
The key is read from --api-key, then PERMACC_API_KEY, then
PERMACC_API_KEY_FILE (the agenix convention — the environment carries a
path to the decrypted secret, not the secret).
Facts
-
foldermust be a JSON BODY field, not a query parameter.?folder=376861is accepted, silently ignored, billed against the personal quota, and 400s once that quota is spent. Verified side by side on one URL against a sponsored account: query param → 400, body field → 201. Nothing in the response distinguishes "ignored your folder" from "you are out of links", so this reads as a plan problem and sends people to the pricing page. -
Sponsorship hangs off the FOLDER, not the organization.
GET /v1/organizations/returnedregistrar: Nonefor an account that was already sponsored; the affiliation lives on a folder inGET /v1/user/carryingregistrar: 16/registrar_name: "University of Virginia School of Law Arthur J. Morris Law Library". Diagnosing a cap from the organizations endpoint concludes "not sponsored" about a sponsored account. -
The folder list is
top_level_folders, notfolders. A wrong key returns[], which this skill's own first draft reported as "no sponsored folder — ask a registrar to add you." The failure mode of guessing a response key here is a confident false negative, not an error. -
The auth scheme is
ApiKey, notBearer. A Bearer header authenticates as anonymous and fails later with a permission error rather than a 401, so the traceback points at the wrong thing. -
The free personal tier is 10 links/month. A cite-check script with no folder therefore works for the first ten footnotes and dies on the eleventh — the shape of bug that looks like a flaky API.
-
HTTP 201 means a link was MINTED, not that the page was fetched. The two come apart on any site that blocks crawlers. SSRN is behind Cloudflare, so every SSRN archive returns 201 and then captures the challenge page:
captures[role=primary].status == "failed",titleset tossrn.cominstead of a paper title. Measured on one manuscript: 22 of 28 captured, and all 6 failures were SSRN. A perma link resolving to an interstitial is worse than the live URL — it looks archived and is not, and nothing in the create response says so.archivenow polls the capture (capture is async, so the status right after 201 ispendingand a single check would call a good archive bad);verifyre-checks an existing record. -
Some sites cannot be perma'd at all, and that is the right answer. For SSRN, cite the live URL: an
abstract_idis a permanent identifier that SSRN does not recycle, so the rot perma exists to prevent barely applies. Law reviews routinely print SSRN URLs unarchived for working papers. -
A perma link is deletable for 24 HOURS and permanent after that. Past the window
DELETEreturns 403, and the remedy isis_private: true, which unpublishes it without removing it. So a cleanup that assumes DELETE works half-succeeds on any set spanning more than a day — the normal shape of a manuscript's archive set. Observed: of 6 bad links, the 4 made that day deleted (204) and the 2 from six months earlier did not. -
private_reasonis a closed enum the API will not enumerate. A wrong value 400s with a message that lists no alternatives, so it has to be guessed.useris the one meaning "I no longer want this public". -
Archiving is not idempotent. Two POSTs for one URL make two perma links. Pass
--outand let the tool skip what it already has, rather than re-running a loop and quietly doubling a manuscript's archive set.
Red flags — STOP
| Action | Why wrong | Do instead |
|---|---|---|
| About to report "your perma account is out of links" from a 400 | Three causes share that message; only one is a real cap | Run folders, then retry with --auto-folder |
| About to check sponsorship with /v1/organizations/ | It reports registrar: None for sponsored accounts | Read top_level_folders from /v1/user/ |
| About to pass the folder as ?folder= | Silently ignored, billed to the personal quota | Send it in the JSON body (the script does) |
| About to loop requests.post over a URL list | No resume, no dedupe; a mid-run failure double-archives on retry | archive --from-json … --out … |
| About to tell a user to buy a plan | A law library registrar gives faculty unlimited links free | Have them added to the registrar's org first |
| About to record a 201 as "archived" | 201 mints a link; the capture can still fail, and SSRN always does | Let archive verify, or run verify --out |
| About to script DELETE over a set of links | Only links under 24h old delete; older ones 403 and need is_private | delete --failed --private-if-undeletable |
| About to paste the key into a script or .env | It is a long-lived credential | agenix (see below), read via PERMACC_API_KEY_FILE |
Storing the key
~/nix/add-api-keys.sh permacc-api-key # encrypts to ~/nix-secrets, prints the wiring
Then in modules/shared/home-secrets.nix: an age.secrets entry, a
PERMACC_API_KEY_FILE session variable, and a get-permacc-api-key alias.
nix-secrets is a flake input pinned by revision, so the secret must be
pushed and the lock bumped (nix flake update nix-secrets) before a rebuild
can see it.
Getting a sponsored account
Perma registrars are institutions — mostly law libraries and courts. A faculty
member or student at one gets unlimited links at no cost by asking to be added
to the institution's perma.cc organization. That is the fix for a cap, and it
is usually a single email. Until it lands, folders prints [personal ] for
every row and every archive spends the 10/month allowance.