Backup & restore
You can export all of your organization's templates — the original files,
their categories and tags, and every version — into a single .zip, and import
that archive later to restore them. It is the supported way to snapshot your
templates, move them between organizations or instances, or keep an off‑site
backup.
This works in the web UI (Account → Data) and over the HTTP API. Like everything else, it is scoped to your active organization (see Organizations).
What's in the archive
The zip contains a root manifest.json plus one file per template version:
manifest.json
templates/<template-id>/v1.xlsx
templates/<template-id>/v2.xlsx
templates/<other-id>/v1.docx
manifest.json describes everything needed to rebuild the templates:
{
"manifest_version": 1,
"exported_at": "2026-06-23T10:00:00Z",
"categories": ["Invoices", "Reports"],
"templates": [
{
"id": "invoice-1718000000000-a1b2c3d4",
"name": "Invoice Template",
"slug": "invoice-template",
"category": "Invoices",
"tags": ["finance", "billing"],
"versions": [
{
"version": 1,
"original_filename": "invoice.xlsx",
"sha256": "…",
"size_bytes": 12345,
"content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"path": "templates/invoice-1718000000000-a1b2c3d4/v1.xlsx"
}
]
}
]
}Entry paths are keyed by the template id (not the slug), because two templates can share a slug within one org.
Exporting
In the UI, open Account → Data and choose Export all templates; your
browser downloads calco-templates-<org>.zip. Over the API:
curl -X GET https://your-instance.example.com/api/templates/export \
-H "Cookie: <your session cookie>" \
-o calco-templates.zipExport is available to any member of the organization.
Importing
In the UI, open Account → Data, pick the zip under Import templates, and upload it. Over the API:
curl -X POST https://your-instance.example.com/api/templates/import \
-H "Cookie: <your session cookie>" \
-F "[email protected]"Import is restricted to organization owners and admins. It is idempotent: templates and versions are deduplicated by content hash within the org, so a version your org already has (byte‑identical) is skipped rather than duplicated — re‑importing the same archive is safe and a no‑op for content that already exists. Categories listed in the manifest are created if missing.
The response summarizes what happened:
{
"templates_created": 2,
"versions_created": 3,
"skipped": 0,
"categories_created": 2,
"errors": []
}Import maps everything into your current active organization, regardless of
the org the archive was exported from. This is how you copy templates from one
org (or instance) into another: export there, import here.
The maximum import size is configurable with the TEMPLATE_IMPORT_MAX_MB
environment variable (default 256 MB). A non‑zip upload, or a zip with no
manifest.json, is rejected with HTTP 400.
Incomplete archives
If a version's underlying file is missing from storage at export time, the export
does not silently drop it. The manifest flags that version with
"missing": true (and writes no file for it) and lists it under a top‑level
missing[] array, so the gap is explicit:
{
"manifest_version": 1,
"templates": [ /* … the version entry has "missing": true … */ ],
"missing": [
{
"template_id": "invoice-1718000000000-a1b2c3d4",
"version": 1,
"original_filename": "invoice.xlsx",
"sha256": "…",
"path": "templates/invoice-1718000000000-a1b2c3d4/v1.xlsx",
"reason": "blob not found"
}
]
}On import, those entries are reported in the response errors as
"skipped: blob was missing at export time" rather than created.
API reference
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /api/templates/export | session | Download a zip of the org's templates + manifest.json. |
| POST | /api/templates/import | session (owner/admin) | Restore templates from a previously exported zip. |