Uploads

Anchorify has two multi-file upload surfaces: the web upload page and the CLI upload-folder subcommand. Both let an admin publish a batch of files to a single project under one access setting. For a single file — text or binary — anchorify <file> and the upload API cover it.

Web — /<org>/<project>/upload#

Admin-only. The page is a file picker (<input type=file multiple>) plus a Who can access select and a List publicly checkbox that apply to the whole batch. Submit POSTs the multipart form to the same path.

Behavior per file:

  • Size limit: 1 MiB per file on Free (MAX_SHARE_BYTES), 10 MiB on Pro (MAX_SHARE_BYTES_PRO). Oversize files are rejected with a per-row error; the rest of the batch continues.
  • Slug: kebab-cased basename. Q1 Report.mdq1-report. Slug collisions auto-suffix -2, -3, … up to 50 attempts.
  • Content type: filename extension (see content types).
  • Binary detection: a NUL byte in the first 1KB routes the file to the binary path — bytes go to R2 storage under shares/<share_id>/<filename> and the share's content column gets the sentinel "@@blob". The content type is image, pdf, or binary based on MIME + extension.

The success page lists one row per file with a status (✓ created, ✓ created as <slug> for auto-renames, ✗ <error> for failures) and the new URL.

CLI — anchorify upload-folder <dir>#

Recursive: walks the directory tree, flattens nested paths into slugs (docs/intro.mddocs-intro), and publishes each file — text files via POST /, binaries via POST /api/v1/shares/upload.

anchorify upload-folder ./drafts
anchorify upload-folder ./drafts --project q1-reports
anchorify upload-folder ./drafts --access restricted
anchorify upload-folder ./drafts --dry-run    # list slugs, no POST

Flags:

  • --project <slug> — required when the org has 2+ projects. Without it, the server returns a 400 with the list of projects + the exact retry command.
  • --access <tier>restricted, view, comment, or suggest; applies to every file in the batch. Default comment.
  • --index / --no-index — public listing for the whole batch. Default --no-index.
  • --dry-run — prints <slug>\t<rel-path> per file and exits without hitting the server.

Skipped at walk time: dotfiles (.git, .DS_Store) and node_modules.

Binaries publish alongside text ([F-CLI-BINARY]) — a folder of markdown, PDFs and screenshots goes up in one pass. Each file is read as bytes and probed: a NUL anywhere routes it to the blob tier, everything else publishes as text. Re-running upload-folder over the same directory is idempotent for both: every file, text or binary, updates the share already at its slug rather than erroring ([F-CLI-BINARY-UPDATE]).

Exit code is 1 if any file errored, 0 otherwise. The first error is printed; the loop continues so a single broken file doesn't sink the batch.

CLI — a single file#

anchorify <file> publishes one file and prints its URL. Binaries take the same route as the folder walk:

anchorify ./q1-deck.pdf --project q1-acme
anchorify ./architecture.png --slug system-diagram

Three things differ from a text publish:

  • --type is ignored. The tier (image / pdf / binary) comes from the bytes and the filename, not from a flag. The CLI prints a note if you pass one.
  • --password is refused. Publish first, then anchorify password set <slug>.
  • Re-publishing replaces in place. The CLI remembers the file→share mapping, so a second anchorify ./q1-deck.pdf swaps the bytes of the share you already published — same id, same slug, same URL, so the link in the client's inbox keeps working. Use --new when you want a separate copy instead. The same swap is available in the browser — see replacing a share's file.

Web — replacing a share's file#

Open the share's Share settings page ( → Share settings, or the Replace file… entry in the share page's menu) and use the File block. Pick a file, submit, and the bytes swap in place: same id, same slug, same URL, same comments, same access — the link you already sent keeps working.

The in-browser editor is text-only and always will be; there is nothing in a PDF for a textarea to edit. The file input is what replaces it.

Two things the page tells you, and they are both true:

  • The old file is deleted once the new one is in place.
  • Replacing is not versioned. share_versions stores content, and a file-backed share's content column holds a pointer, not content — so there is no previous version to roll back to. Keep your own copy.

Uploading a text file to a file-backed share converts it back to a text share (and the editor starts applying again); uploading a binary to a text share does the reverse.

API — POST /api/v1/shares/upload#

The multipart publish endpoint that both CLI paths call. Auth is a per-user token or a cookie session; the legacy admin bearer is rejected (it has no home org). Accepts file, plus optional filename, slug, access, indexed, type, org, project.

curl -sX POST https://anchorify.io/api/v1/shares/upload \
  -H "Authorization: Bearer $ANCHORIFY_TOKEN" \
  -F "file=@./q1-deck.pdf" \
  -F "slug=q1-deck"

The stored MIME is sniffed from the bytes, never taken from the request — a PNG declared as text/html is still stored and served as image/png, and an HTML payload is served as an attachment. Full field list, response shape and error table: API reference.

MCP — anchorify_publish_file#

Agents get the same capability through the MCP server: anchorify_publish_file takes a local path, reads the bytes, and uploads them. Use it instead of anchorify_publish whenever the file isn't text — anchorify_publish carries content as a JSON string and can't represent a PDF.

Moving shares between projects#

A share can move to a different project in the same org without re-uploading. Old URLs 301 to the new canonical URL via the K4 redirect chain.

  • Share page: the menu in its header → Move to… → pick a project.
  • Dashboard or project page: open the share's actions menu () → Move to… → pick a project.
  • CLI: anchorify move <slug-or-id> --to <project>.
  • API: POST /api/v1/shares/<id>/move {project_id|project_slug}.

Moving is gated on share.update, which is wider than most per-share admin actions: org admins, project editors and share-level editors can all do it, on every one of those surfaces.

Cross-org moves are blocked — use invites to bring the recipient into your org first.