API reference

Bundles (export, import, transfer)

A bundle is a ZIP file with the extension .icb (older versions: .zip, still accepted) containing the complete content of an instance: all websites and pages, CSS/JS assets including assignments, snippets, folders and files (including all image variants), collections with entries, REST sources, redirects and import profiles. Not included are accounts, sessions, API tokens, the 404 log and the version history.

When importing, the content of the target instance is replaced. All IDs are preserved – templates reference content by ID (asset_url(12), image(34), children(5)), and these references stay correct.

  • Backup: Before every import, the server saves its previous content as a bundle under <data>/backups/ (the last 3).
  • Domains: If a website in the bundle has no domains (typical for websites built locally) and there is a website with the same ID on the target, it keeps the target’s domains. If no default website is marked in the bundle, the target’s flag remains.
  • Live data: Entries of collections that have live: true in the bundle stay on the target (matched via the collection’s slug, count in live_kept); the entries of these collections from the bundle are discarded. If a kept entry occupies an ID that an entry from the bundle already has, it gets a new one. With live=bundle (restoring a backup, importing a bundle from the live server locally), the bundle wins, as for all other collections.
  • Keep collection entries (keep_items=1): The target’s entries stay and are matched to the collections from the bundle via the slug of their collection; entries from the bundle are then not taken over. Entries of collections that don’t exist in the bundle are dropped (dropped_items).
  • Files: Unchanged files (same size and CRC32) are not rewritten; files that are not in the bundle are deleted.
  • Versions: Bundles from older IllusionCMS versions can be imported (missing fields get default values). The server rejects bundles from a newer version with 400 – update the server first.
  • While a website import is running or another bundle is being imported: 409.

Structure of the ZIP: manifest.json (format: "illusioncms-bundle", version, schema, created_at, counts, media_files, media_bytes, for bundles without media no_media: true – the media folder then stays unchanged on import), tables/<tabelle>.json (rows as JSON objects), media/… (the media folder, stored uncompressed).

GET /bundle/info ✏️

Size and version of the instance – for checking token and compatibility before a transfer.

{ "schema": 20, "build": "df06fd6a7e73", "commit": "6ce6102090ec", "version": "v0.1.9", "headless": false,
  "content_fingerprint": "3f1c…",
  "counts": { "pages": 377, "assets": 21, "files": 1840, "collections": 2, "collection_items": 48, … },
  "media_files": 5520, "media_bytes": 4830000000 }

content_fingerprint is a hash over the content that a sync transfers – without the entries of collections with live data and without the hit counters of redirects, which change on their own during operation. If it changes, something in the content was changed.

GET /bundle ✏️

Downloads the bundle (application/zip, Content-Disposition: attachment; filename="illusioncms-<host>-<datum>.icb"). It is streamed; the size roughly equals media_bytes.

POST /bundle ✏️

Imports a bundle. Body: the raw ZIP file (not multipart), Content-Type: application/zip. Query keep_items=1 keeps the server’s collection entries, live=bundle also takes over the entries of collections with live data from the bundle. With a write token, you can script a deployment this way:

curl -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/zip" \
     --data-binary @illusioncms-localhost-2026-10-05-1722.icb \
     https://www.firma.de/api/v1/bundle

Response 200:

{
  "manifest": { "format": "illusioncms-bundle", "version": 1, "schema": 9, … },
  "imported": { "pages": 377, "assets": 21, "files": 1840, … },
  "kept_items": 0, "dropped_items": 0, "live_kept": 14, "domains_kept": [1],
  "media_written": 12, "media_skipped": 5508, "media_removed": 3,
  "backup": "backup-20261005-172236-467.icb"
}

Errors: 400 not a ZIP, not an IllusionCMS bundle, newer version, illegal file paths or faulty data (the content then stays unchanged); 409 see above.

Behind a reverse proxy, the allowed body size must fit the bundle (nginx: client_max_body_size; Caddy has no limit by default).

GET /bundle/backups ✏️ · GET /bundle/backups/{name} ✏️

List of automatic backups (newest first: [{ "name", "type", "media", "size", "created_at" }]) or download of a backup. type: nightly (every night from 3 a.m. server time, only when there are changes, without media files, the last 7; name auto-…) or import (before every import, with media, the last 3; name backup-…). Restore = import the backup via POST /bundle.

POST /bundle/push 👤 · GET /bundle/push 👤

Transfers the content of this instance directly to another one – the same as downloading and running POST /bundle there, but in a single step from the server. Body with a saved connection or with address and token:

{ "connection_id": 1, "keep_items": false }
{ "url": "https://www.firma.de", "token": "ic_…", "keep_items": false }

token is a token of the target server with write permission; without a connection, it is not stored. With a connection, the instance remembers the fingerprints of both sides after uploading (for the warnings at the next sync). Runs in the background (202); GET /bundle/push returns the progress:

{ "status": "running", "phase": "upload", "direction": "push", "connection": 1, "target": "https://www.firma.de", "sent": 1048576000, "total": 4830000000, "started_at": 1791213700 }

status: idle, running, done (with result = response of the target), failed (with error, plus error_code/error_params if available). phase: check (target reachable, token valid, version compatible), export, upload, import. One transfer at a time (409) – uploading and pulling share the state.

POST /bundle/pull 👤

Pulls the content of a server here via a saved connection: downloads its bundle and imports it – including live data (like live=bundle), with a backup beforehand. Accounts, API tokens and connections of this instance are kept. Body { "connection_id": 1 }, response 202, progress via GET /bundle/push (direction: "pull", phases check, download, import; total is only an estimate). If the server is newer than this instance, it fails – update here first.