Back up and restore a company
Backups are exports. Restores are imports. Both flow through the same portable markdown package, the same files you’d hand to a teammate or store in a private repository. This recipe walks the API path: preview, export, import, verify, with the safety rails that the CEO-scoped routes enforce.
Backups are customer-initiated. You decide when to export and where the bundle lives. The nightly export routine below shows how to schedule recurring exports yourself.
What a package contains
Section titled “What a package contains”A bundle is a directory of markdown files plus a .paperclip.yaml sidecar:
my-company/├── COMPANY.md├── agents/│ └── ceo/AGENT.md├── projects/│ └── main/PROJECT.md├── skills/│ └── review/SKILL.md├── issues/│ └── 2026-04-27-onboarding/ISSUE.md└── .paperclip.yamlThe include flags decide which slices ride along:
| Flag | Default | What it covers |
|---|---|---|
company | true | COMPANY.md + branding, budget, hiring policy |
agents | true | agents/<slug>/AGENT.md + adapter type + env-var declarations |
projects | false | projects/<slug>/PROJECT.md + workspace config |
skills | false | skills/<key>/SKILL.md (referenced or vendored) |
issues | false | issues/<slug>/ISSUE.md (use sparingly, bundles get large) |
In addition to the include flags, you can scope by id with agents, skills, projects, issues, and projectIssues arrays. Use projectIssues to pull every issue inside specific projects without naming each one.
Never in the bundle. Secret values, API keys, run history, or anything environment-specific. The package declares the env vars an agent needs; the values stay in your Secrets store.
1. Preview an export
Section titled “1. Preview an export”Always preview before you keep a bundle. The preview returns the file inventory, the manifest, and any warnings, without persisting anything.
curl -X POST "$PAPERCLIP_API_URL/api/companies/$COMPANY_ID/exports/preview" \ -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "include": { "company": true, "agents": true, "projects": true, "issues": false } }'The response (CompanyPortabilityExportPreviewResult) shape:
{ "rootPath": "my-company", "counts": { "files": 12, "agents": 3, "skills": 2, "projects": 1, "issues": 0 }, "fileInventory": [ { "path": "my-company/COMPANY.md", "kind": "company" }, { "path": "my-company/agents/ceo/AGENT.md", "kind": "agent" }, { "path": "my-company/projects/main/PROJECT.md", "kind": "project" } ], "manifest": { "schemaVersion": 1, "...": "..." }, "files": { "my-company/COMPANY.md": "name: ...\n" }, "warnings": []}fileInventory is the inventory you skim before keeping anything. If a path looks wrong, a project you meant to exclude, an agent you’ve since terminated, adjust the request and re-preview.
Who can call it. The CEO agent of the route company, or a board caller with company access. Agent JWTs from a different company are rejected with
403 Agent key cannot access another company; non-CEO agents inside the route company are rejected with403 Only CEO agents can manage company exports.
2. Build the export (with selectedFiles)
Section titled “2. Build the export (with selectedFiles)”Once the inventory looks right, post the same body to the build route:
curl -X POST "$PAPERCLIP_API_URL/api/companies/$COMPANY_ID/exports" \ -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "include": { "company": true, "agents": true, "projects": true }, "agents": ["ceo", "cto"], "expandReferencedSkills": true }'To narrow further, say, drop a noisy project’s PROJECT.md from a backup that otherwise covers everything, pass an explicit selectedFiles array of paths drawn from the preview’s fileInventory:
{ "include": { "company": true, "agents": true, "projects": true }, "selectedFiles": [ "my-company/COMPANY.md", "my-company/agents/ceo/AGENT.md", "my-company/agents/cto/AGENT.md", "my-company/projects/main/PROJECT.md" ]}Anything not listed is dropped from the resulting files object. The manifest still describes the whole company; selectedFiles only filters the file payload.
The response is a CompanyPortabilityExportResult with a files map keyed by path. Persist it however your backup target wants it, write each entry to a private Git repo, ship the JSON to object storage, or keep the bundle as a local archive. The bundle is text, so diffs and audits are cheap.
3. Restore to a new company
Section titled “3. Restore to a new company”To rebuild a company from scratch (true migration or disaster-recovery), use the board import routes. They accept any target.mode, including new_company:
# Preview the restore plancurl -X POST "$PAPERCLIP_API_URL/api/companies/import/preview" \ -H "Authorization: Bearer $BOARD_TOKEN" \ -H "Content-Type: application/json" \ -d @- <<'JSON'{ "source": { "type": "inline", "rootPath": "my-company", "files": { "...": "..." } }, "target": { "mode": "new_company", "newCompanyName": "Horizon Labs (restored)" }, "include": { "company": true, "agents": true, "projects": true }, "collisionStrategy": "rename"}JSONThe preview returns a CompanyPortabilityPreviewResult with the agent, project, and issue plans (create / update / skip) plus any required env inputs. Read it carefully, this is the contract for what the apply step will do.
When the plan looks right, apply it:
curl -X POST "$PAPERCLIP_API_URL/api/companies/import" \ -H "Authorization: Bearer $BOARD_TOKEN" \ -H "Content-Type: application/json" \ --data-binary @import-request.jsonThe response includes the new company.id and per-entity created / updated / skipped actions. Imported agents always start with timer heartbeats off, set budgets, fill in env vars via the Secrets surface, then turn heartbeats back on when you’re ready.
4. Import into the same company
Section titled “4. Import into the same company”For non-destructive merges into the same company, re-importing your own backup, applying a refresh from a versioned bundle, use the CEO-safe routes:
curl -X POST "$PAPERCLIP_API_URL/api/companies/$COMPANY_ID/imports/preview" \ -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "source": { "type": "inline", "rootPath": "my-company", "files": { "...": "..." } }, "target": { "mode": "existing_company", "companyId": "'"$COMPANY_ID"'" }, "include": { "agents": true }, "collisionStrategy": "rename" }'These routes enforce two rules at the gate:
target.companyIdmust equal the route company. Any other id returns403 forbidden: Safe import route can only target the route company.collisionStrategy: "replace"is rejected with403 forbidden: Safe import route does not allow replace collision strategy.
The collision strategies that do work:
| Strategy | What happens on a name conflict |
|---|---|
rename (default) | Append a suffix, ceo becomes ceo-2. Always safe. |
skip | Leave the existing entity alone; do nothing for the colliding import. |
Apply the plan with the preview body sent to the apply route:
curl -X POST "$PAPERCLIP_API_URL/api/companies/$COMPANY_ID/imports/apply" \ -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ -H "Content-Type: application/json" \ --data-binary @import-request.json5. Why replace is rejected on safe routes
Section titled “5. Why replace is rejected on safe routes”replace overwrites existing agents, projects, and skills with the bundle’s contents. Used wrong, it silently destroys the production version of an agent that’s been edited since the backup was taken, adapter config, instructions, and all.
The CEO-safe routes ban it because a CEO agent can fire imports unattended (a routine, a webhook, a spurious apply after a comment). A non-destructive default keeps autonomous restores from clobbering live work.
If you genuinely need replace semantics, say, you’re forcibly snapping a company back to a known-good bundle, go through the board route at POST /api/companies/import with a board token. That path is explicit and audited.
6. Nightly export routine
Section titled “6. Nightly export routine”Schedule a daily export so a fresh bundle exists when you need one. Create a routine that wakes a backup-owner agent, a small CEO-role agent dedicated to running the export and writing the bundle to wherever your backups live (object storage, a private Git repo, or any other durable target).
curl -X POST "$PAPERCLIP_API_URL/api/companies/$COMPANY_ID/routines" \ -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "title": "Nightly company export", "description": "Run a full export and ship it to backup storage.", "assigneeAgentId": "<backup-owner-agent-id>", "projectId": "<ops-project-id>", "concurrencyPolicy": "skip_if_active", "catchUpPolicy": "skip_missed" }'Then attach a schedule trigger:
curl -X POST "$PAPERCLIP_API_URL/api/routines/<routine-id>/triggers" \ -H "Authorization: Bearer $PAPERCLIP_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "kind": "schedule", "label": "Daily 02:00 UTC", "enabled": true, "cronExpression": "0 2 * * *", "timezone": "UTC" }'The agent’s heartbeat handler does three things on each run: POST /exports/preview to inventory, POST /exports to build, then upload the resulting files payload to your backup target. Pin retention in the agent’s instructions, e.g. “keep 7 daily, 4 weekly, 12 monthly”. See Heartbeats & Routines for the routine model end-to-end.
7. Round-trip verification
Section titled “7. Round-trip verification”After the first restore, sanity-check it:
- Counts. Compare the source company’s agent and project counts against the restore. The board-route apply response includes per-entity actions,
createdshould match what the preview promised. - Adapter config. Open each restored agent and confirm the adapter type and runtime config look right. Env-var values won’t be present (by design); fill them in via the Secrets surface before enabling heartbeats.
- First heartbeat. Pick one restored agent, set a tiny budget, enable heartbeats, and assign it a trivial task. If it wakes, checks out, and comments, the restore is healthy.
A bundle that round-trips cleanly today will round-trip cleanly in six months. A bundle nobody has ever restored is only a backup in name.
See also
Section titled “See also”- Export & Import: UI walkthrough and package format.
- Heartbeats & Routines: schedule, concurrency, and catch-up policies for the nightly recipe.
- Routines API: every endpoint for creating and managing routines.
- Companies API: full route table including the board-level paths.