-
Notifications
You must be signed in to change notification settings - Fork 2
Export Import
Portable workspace dumps. Makes the "user owns their data" ethos real.
%%{init: {"look": "handDrawn", "theme": "dark"}}%%
flowchart LR
A[(Workspace A<br/>source)] -->|GET /export| Doc[["nakatomi-<slug>-<date>.json"]]
Doc -->|POST /import| B[(Workspace B<br/>target)]
Doc -.->|stays portable| Any[any Nakatomi<br/>instance]
subgraph Rules[Import rules]
direction TB
R1[natural-key match<br/>external_id → email/domain/slug]
R2[UUIDs regenerated<br/>no pk collisions]
R3[polymorphic refs rewritten<br/>notes, tasks, activities, memory_links]
end
Owners and admins only.
curl -s http://localhost:8000/export \
-H "Authorization: Bearer nk_..." \
-o nakatomi-dump.jsonResponse is a single JSON doc:
{
"schema_version": 1,
"nakatomi_version": "0.1.0",
"exported_at": "2026-04-18T15:12:00+00:00",
"workspace": {...},
"custom_field_definitions": [...],
"pipelines": [{"stages": [...], ...}, ...],
"contacts": [...],
"companies": [...],
"deals": [...],
"activities": [...],
"notes": [...],
"tasks": [...],
"relationships": [...],
"memory_links": [...],
"files": [...], // metadata only — bytes not included
"webhooks": [{"secret": "[redacted on export]", ...}],
"counts": {"contacts": 42, "companies": 18, "deals": 7, ...}
}Pass ?include_timeline=true to also dump every timeline event. Omitted
by default because it can be very large.
-
File bytes — only metadata. Fetch via
GET /files/{id}on the source side and re-upload on the target. File bytes as a tarball are on the roadmap. - Webhook secrets — redacted. Mint fresh ones on the target.
- Operational state — audit log, webhook deliveries, ingest runs, idempotency keys. These are local to a deployment, not portable.
- Users and memberships — users are global (not workspace-scoped). Import creates/updates CRM rows only; human access is configured separately on the target.
Owners and admins only.
curl -X POST http://localhost:8000/import \
-H "Authorization: Bearer nk_..." \
-H "Content-Type: application/json" \
-d @nakatomi-dump.json...but wrap the doc in {"doc": {...}, "dry_run": false}:
jq -n --slurpfile d nakatomi-dump.json '{doc: $d[0], dry_run: false}' \
| curl -X POST http://localhost:8000/import \
-H "Authorization: Bearer nk_..." \
-H "Content-Type: application/json" \
--data @-Response:
{
"created": {"contacts": 42, "companies": 18, "deals": 7, ...},
"updated": {"contacts": 0, ...},
"skipped": {"notes": 1},
"warnings": [
"webhook 'slack-relay' imported without a secret — mint a new one via PATCH"
],
"dry_run": false
}Merge-upsert, not replace. The importer picks existing rows by stable natural keys:
| Entity | Match order |
|---|---|
| contact |
external_id → lowercased email
|
| company |
external_id → lowercased domain
|
| deal |
external_id (no fallback — deals without external_ids create) |
| activity |
external_id only |
| task |
external_id only |
| note |
(entity_type, entity_id, body) tuple — exact match or create |
| relationship | full edge tuple (source, target, relation_type)
|
| pipeline | slug |
| stage | (pipeline, slug) |
| custom_field_definition | (entity_type, name) |
| webhook | url |
| memory_link | full tuple |
| file | sha256 |
Source UUIDs are not preserved. Two reasons:
- Two imports of the same data shouldn't collide by primary key.
- Cross-workspace portability means the target can be a different workspace on the same DB.
An id_map tracks source_id → new_id as rows land. When a deal
references a contact, pipeline, or company that was just imported, the
reference gets rewritten. Same for polymorphic (entity_type, entity_id)
pairs on notes, tasks, activities, and memory_links.
{"doc": {...}, "dry_run": true}The importer runs the full pipeline inside a SAVEPOINT and rolls it back before returning. You get the counts + warnings you'd have seen for real.
The top-level schema_version gates compatibility. Current: 1.
When the schema evolves, we'll add schema_version: 2 support and carry
an upgrade path in services/importer.py. Old dumps will either continue
to work or get a 422 with a clear migration message.
-
Backup —
curl /export > /backups/nakatomi-$(date +%F).jsonon a cron. Ship to S3. - Dev environment — export prod nightly, scrub PII, import into staging.
- Migration — move from one Railway deploy to another.
- Split workspaces — export one, re-import under a different workspace slug on the same instance.
- Disaster recovery — last week's export lets you reconstruct the workspace (minus file bytes + timeline if you didn't opt in).
Repository · Issues · MIT licensed · maintained by Matt Dula