Marked Share just took another big step toward full folder sync. The API can now take a folder full of Markdown in bulk, process it in the background, and keep it in step with your local copy in both directions.

What that means in practice: apps like nvUltra, The Archive, and FSNotes can build an online index of your notes, one folder at a time. Each synced folder stays its own little library, subfolders included, and you can sync as many of them as you like. Work notes in one, a Zettelkasten in another, recipes in a third.

This update also brings a couple of web UI niceties: rename a folder by double-clicking it, and scope a search to a single folder.

Batch uploads

Seeding a folder used to mean one request per note, with a rate limit that would have taken hours for a decent-sized notes collection. Now there’s a batch endpoint that takes up to 200 notes per request:

curl https://share.markedapp.com/api/v1/documents/import \
  --header "Authorization: Bearer $MARKED_SHARE_TOKEN" \
  --header "X-Device-Key: $MARKED_SHARE_DEVICE_KEY" \
  --header "Content-Type: application/json" \
  --data '{"documents":[{"title":"Meeting notes","body_markdown":"# Meeting notes","library_path":"Work"}]}'

library_path is the folder (subfolders like Work/Clients are fine, and they’re created if they don’t exist). Every note comes back with an id and a content_hash, which is all a sync client needs to remember. Imported notes are private by default.

Background processing

To keep imports fast, Share stores the Markdown right away and renders the HTML in a background job. If you open a note before the job gets to it, it renders on the spot, so you’ll never see a blank page. Big imports spend their time on the network instead of waiting for rendering.

How two-way sync works

The whole loop runs on a handful of endpoints:

  1. GET /documents?view=index&folder=Work returns a lean snapshot of the folder: every note’s id, path, and content hash, plus “tombstones” for notes that were deleted.
  2. New local files go up with the batch import.
  3. Local edits go up with PATCH, sending the last hash you saw in an If-Match header. If the note changed on Share since then, you get a 409 instead of silently clobbering it.
  4. Notes that are new or changed on Share come down with GET /documents/:id.
  5. Local deletes use DELETE, also with If-Match.

One detail that tripped me up while writing the example client: only a tombstone means “deleted.” If a note you know about isn’t in the index and isn’t a tombstone, it was moved to another folder on Share. It’s still there.

Conflicts are left to the client. The approach I recommend (and the example script uses) is to keep the Share version at the original path and upload your local edits as a new note with “(conflicted)” in the title. Nothing gets lost, and you can merge at your leisure.

The full details are in the API docs.

Rename folders with a double click

In the library sidebar, double-click any folder name and it turns into a text field. Hit Enter to rename, Escape to back out. Everything inside comes along for the ride, subfolders included.

If you rename a folder you’re syncing, just point your sync client at the new name. With the example script, delete the state file and run it with the new --folder. It’ll match your local files to the notes on Share by path and content without uploading anything again.

Search a single folder

When you’re browsing a folder, there’s now a “Current folder only” checkbox under the search bar. Check it and search only looks in that folder and its subfolders. It remembers your choice, so if you mostly work in one folder you can leave it on.

Try it with the example script

I wrote a sync client while building all this, and I’ve cleaned it up into a working example of the whole procedure. It’s a single Ruby file with no dependencies beyond the standard library:

share_sync on GitHub Gist

It pushes new and edited files, pulls edits and new notes from Share, follows renames and moves in both directions, and makes “(conflicted)” copies when both sides changed. Deletes only happen if you ask for them.

To try it:

  1. Download the script and make it executable:

    chmod +x share_sync
  2. In Marked Share, go to Settings > Connect an app to get an API token and a device key, then export them:

    export MARKED_SHARE_TOKEN="your-token"
    export MARKED_SHARE_DEVICE_KEY="your-device-key"
  3. Generate some sample notes to play with (or point it at a copy of real notes):

    ./share_sync generate ~/Desktop/sync-test 50
  4. Preview what it’ll do, then run it for real:

    ./share_sync sync ~/Desktop/sync-test --folder "Sync Test" --dry-run --verbose
    ./share_sync sync ~/Desktop/sync-test --folder "Sync Test"
  5. Now mess with it. Edit a note on the web, add a new one to the folder, rename one, move one to a subfolder. Then sync again and watch the changes land locally:

    ./share_sync sync ~/Desktop/sync-test --folder "Sync Test" --verbose
  6. To test pushes, append a line to a few random local files and sync:

    ./share_sync mutate ~/Desktop/sync-test 5
    ./share_sync sync ~/Desktop/sync-test --folder "Sync Test" --verbose

A few flags worth knowing:

  • --delete applies deletes in both directions. Even then, a file with local edits that haven’t synced yet is uploaded again instead of being deleted.
  • --restore uploads a local file again if its note was deleted on Share.
  • --no-pull is push-only and never touches your local files.
  • --dry-run reports everything it would do without doing it.

The script keeps its state in a hidden .marked-share-sync.json file inside the synced folder. One quirk to know about: moving or deleting notes leaves empty folders behind locally, because the script doesn’t sync empty folders. Clean them up by hand if they bug you.

Limits for now: 200 notes per import request, 500 KB per note, and 5,000 notes per synced folder. If you have more than that, split them across folders (which is probably a good idea for organization anyway).

What about security?

Here’s where things stand. All traffic between your apps and Marked Share is encrypted with HTTPS. Notes are private by default, which means they’re only visible to you when you’re signed in. That includes anything that comes in through the API. When you do share a note, you can put a password and an expiration date on the link.

What it’s not is end-to-end encrypted. Share has to read your Markdown to render it and search it, so your notes are stored on the server in a form the server can read. That’s the same deal you get with most cloud notes services, and it’s fine for most purposes. It’s not where I’d keep passwords, keys, or anything you’d be in real trouble over if it leaked.

For personal stuff like journals, family notes, or private project plans, there’s a new safety net: locked folders. Select a folder in the library sidebar and click the padlock. Everything in that folder and its subfolders is forced private and can’t be shared until you unlock it. That covers publishing to Micro.blog too. It doesn’t matter how a note gets there, whether you move it on the web, a sync client uploads it, or an app tries to set it to public through the API. It’s private, period. If the folder already has shared notes, Share tells you how many and makes them private when you confirm.

So a reasonable setup for syncing a folder of personal notes looks like this:

  1. Create the folder on Share and lock it before syncing anything into it.
  2. Create a separate token and device key for each app that syncs, under Settings > Connect an app. If a machine is lost or you stop using an app, revoke its token in Settings and that app loses access immediately.
  3. Point your sync client at the locked folder:

    ./share_sync sync ~/Journal --folder "Personal/Journal"

Here’s what happens technically:

  • Your notes travel as JSON over HTTPS.
  • They’re stored as Markdown on the server, marked private and locked.
  • Reading them requires your login, either a web session or an API token.
  • Every change is recorded in the note’s version history, along with the name of the device that made it.

A locked folder isn’t a vault. It’s a guardrail against the “oops, I shared my journal” click. For most personal notes, that’s the level of protection that actually matters.

What’s next

The pieces for full folder sync are mostly in place now. Next up is getting it into apps. If you build a notes app and want to hook into this, the API docs have everything, and the example script shows the whole loop end to end. I’d love to hear what you build with it.