How-to

Update data and files without creating duplicates

Re-running an import or a publish script should give you new versions, not a second copy of everything.

The rule

POST /api/v1/{collection} always creates a new document. PUT adds a new version to an existing one. Scripts that POST on every run leave a trail of orphaned documents, each with a different id.

So every writer needs a way to find "its" document again. In order of preference: a natural key, a tree path, or an id you stored yourself.

JSON documents: upsert by natural key

Once, as an org admin: declare which field identifies a document. Schema writes need admin access on purpose, because a schema is a contract for every writer.

http
PUT /api/v1/events/_schema
{ "naturalKey": "slug", "schema": { "type": "object", "required": ["slug"] } }

Then, from any key with write access: one idempotent call, which creates the document the first time and adds a version every time after:

bash
curl -X PUT -H "Authorization: Bearer $WREN_API_KEY" -H "Content-Type: application/json" \
  "$WREN_URL/api/v1/events/by-key/swiss-open-2026" \
  -d '{"slug":"swiss-open-2026","name":"Swiss Open 2026","date":"2026-09-26"}'
  • URL-encode the key (encodeURIComponent, or urllib.parse.quote(key, safe="")). Prefer keys with letters, digits, - and :. A raw / in a key splits the URL.
  • Read with GET …/by-key/{key} and delete with DELETE …/by-key/{key}.
  • Reference by key from other documents: {"$ref":"events","$key":"swiss-open-2026"}.
  • Can't get an admin? The workaround is a _query on the key field, then PUT /{id} or POST. It takes two calls and isn't atomic, so declaring the natural key is worth the one-time ask.

Files: replace in place

Binary documents (images, HTML, PDFs) aren't addressable by natural key. Keep their id, or look it up from the tree path they're published at, then upload the new bytes with a multipart PUT:

bash
# find the document behind a tree path
ID=$(curl -s -H "Authorization: Bearer $WREN_API_KEY" -H "Accept: application/json" \
  "$WREN_URL/api/v1/tree/reports/trophy-2026/index.html" | jq -r .document.id)

# new version of the same document; the URL doesn't change
curl -X PUT -H "Authorization: Bearer $WREN_API_KEY" \
  -F "[email protected];type=text/html" "$WREN_URL/api/v1/reports-assets/$ID"

The response includes a sha256 of the stored bytes. Compare it with your local file and skip the upload when nothing changed.

Whole sites: let wren deploy do it

bash
WREN_API_KEY=wren_… wren deploy ./dist --tree reports --label preview

For each file it looks up the existing document by tree path, compares SHA-256 hashes, and only uploads what changed, as a new version. New files get a new document plus a tree path. Add --clean to drop paths for files you deleted locally.

Two machines deploying different folders into one tree: deploy each folder with its own command and leave out --clean, so one machine doesn't remove the other's paths.

Cleaning up orphans

If an old script left duplicates, list the collection and delete the documents that no tree path points at:

bash
# ids that are published somewhere
curl -s -H "Authorization: Bearer $WREN_API_KEY" "$WREN_URL/api/v1/tree/reports?full=true" \
  | jq -r '.nodes[].documentId' | sort -u > used.txt
# all ids in the collection (page with ?offset= for more than 200)
curl -s -H "Authorization: Bearer $WREN_API_KEY" "$WREN_URL/api/v1/reports-assets?limit=200" \
  | jq -r '.items[].id' | sort -u > all.txt
comm -23 all.txt used.txt   # review this list, then DELETE /api/v1/reports-assets/{id} for each

Deletes are soft: the document and its history stay in the database, so a mistake can be recovered.