Exports & Imports
Two complementary data movement pipelines:
- Exports — owner-initiated CSV exports of organization data (members today, more entities planned). Async, R2-hosted, emailed when ready.
- Imports — async ingestion of member/plan data from external systems. Two sources today: Arbox (FitKit’s primary competitor migration path) and generic CSV (header-mapped, preview-confirm workflow).
What
| Surface | Endpoint root | Source |
|---|---|---|
| Members export | POST /organizations/:orgId/export/members | apps/api/src/export/ |
| Analytics export | GET /organizations/:orgId/analytics/export?tab=... | Synchronous CSV download from apps/api/src/analytics/. |
| Arbox import | POST /organizations/:orgId/import | apps/api/src/import/ (Business API and Management API paths) |
| CSV import | POST /organizations/:orgId/import/csv/upload → preview → confirm | apps/api/src/import/ |
| Provider credentials | POST /organizations/:orgId/import/provider-config | Encrypted at rest. |
Why
- Customers leaving Excel / Squarespace / Arbox need a one-click way to bring their members in.
- Owners need to push member data into accounting, marketing, or compliance tools and expect CSV.
- Imports must be fault-tolerant — a 1500-row Arbox import shouldn’t fail because one row had a malformed email.
Who
- Owner / admin — initiates imports + exports. Restricted by membership role inside the service (only
owner/admin). - Member rows — created or updated by imports. No member action required for imports.
- Email service — receives “Your export is ready” when an export completes.
Persona impact
| Persona | Impact |
|---|---|
| Owner | Migrates from Arbox or a CSV in <10 min with idempotent retry. Exports member list to send to accountants. |
| Member | A correctly-imported row means their first login lands them in the right plan / membership state. |
Capabilities
Exports
- Asynchronous via BullMQ
exportqueue →ExportProcessor. - Per-tab filtering for members (
roles,statuses, customfields). - CSV is UTF-8 with BOM (Excel-friendly Hebrew rendering).
- Stored at
exports/<orgId>/<jobId>.csvin R2. - Presigned download URL emailed to the requester when the job finishes.
- Rate limit: 10 exports per minute per IP via
@Throttle.
Arbox import
- Two API surfaces: Business API (api key only) and Management API (email+password, fuller data).
- Management API path runs both active + inactive reports in parallel and merges by
user_fk. - Member rows fan out to a
import-memberqueue (3 attempts, exponential backoff). - Optional enrichment phase (
import-enrich) re-fetches Business API detail per member to fill fields the Management report doesn’t carry. - Whitelist support — restrict an import to specific emails (used in staged migrations).
- Plan import runs inline (small batches) before member fan-out.
CSV import
- Two-step UX: upload → header mapping preview → confirm + run.
- 5 MB upload cap on the controller via
MaxFileSizeValidator. - Heuristic header suggestion via
CsvParseService.suggestMapping. - Plans + members + leads as separate entity targets.
Encryption at rest
- Arbox credentials persisted via
CredentialEncryptionService(shared with payments). Read-back returns onlyhasCredentials: true— never the plaintext.
Related features
r2— file storage for exports and (indirectly) CSV uploads.users-auth,memberships— imports create users and memberships.plans— imports create plans linked to member rows.notifications— email service delivers the export-ready notification.leads-crm— CSV imports can target leads.
Status
- Members CSV export: shipped.
- Analytics tab CSV exports: shipped (synchronous).
- Arbox import (Business + Management API): shipped.
- CSV import (members + plans + leads): shipped.
Gaps
- FIT-120 — Arbox import’s id-encryption flow has a known bug where credentials encrypted under an old key cannot be decrypted after a key rotation. Mitigation: re-enter credentials post-rotation.
- Exports for non-member entities (workouts, payments) are CSV-only via the analytics tabs; no async export path.
- No incremental imports — every Arbox run is a full pull-and-upsert.
- No rollback / dry-run mode for imports. Owners must validate via the whitelist + a small batch first.
- Failed member rows surface inside
import_jobs.results.errors, but there’s no UI table to retry individual failures. - Export download URL TTL is 1h; if the email is opened later, the user must re-trigger the export. No long-lived signed link option.