Skip to content

Migrating with import bundles

A migration is not many independent imports. Leases reference units, units reference properties, payments reference charges — import them separately and you get orphans.

An import bundle takes every CSV at once, resolves the relationships between them, shows you what it intends to do, and only then writes.

create ──> upload files ──> validate ──> review candidates ──> start ──> results

Nothing is written to your portfolio until start.

Terminal window
curl "https://api.getvespy.com/api/import-bundles/templates/appfolio" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
--output vespy-templates.zip

provider is generic, appfolio, or turbotenant. The provider-specific templates match the export column names of that system, so you can usually feed its export straight in. Use generic for anything else.

Terminal window
curl -X POST https://api.getvespy.com/api/import-bundles \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"name": "AppFolio migration — March 2026",
"provider": "appfolio",
"sourceNamespace": "appfolio-prod",
"sourceAccountLabel": "Northside Property Group"
}'

sourceNamespace is what makes a migration re-runnable. External ids from the old system are recorded under it, so re-importing an updated export updates the records it created before rather than duplicating them. Keep it stable across runs of the same migration; use a different one for a genuinely different source.

Terminal window
curl -X POST "https://api.getvespy.com/api/import-bundles/$BUNDLE_ID/files" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-F 'file=@properties.csv' \
-F 'file=@units.csv' \
-F 'file=@tenants.csv' \
-F 'file=@leases.csv'

Upload them all before validating. Column mapping is inferred; correct it with PATCH /api/import-bundles/{id}/files/{fileId}, and save a mapping you will reuse with POST /api/import-mapping-profiles.

Terminal window
curl -X POST "https://api.getvespy.com/api/import-bundles/$BUNDLE_ID/validate" \
-H "Authorization: Bearer $ACCESS_TOKEN"

Validation is a dry run. It resolves cross-file references, matches rows against records you already have, and produces one candidate per row. The bundle moves through inspectingmappingvalidating and lands on ready.

Terminal window
curl "https://api.getvespy.com/api/import-bundles/$BUNDLE_ID/candidates?status=ambiguous" \
-H "Authorization: Bearer $ACCESS_TOKEN"
Candidate status What it means
ready_create Will be inserted
ready_update Matched an existing record; will be updated
unchanged Matched, nothing differs
duplicate_merged Two source rows resolved to one record
ambiguous Matched more than one record — needs your decision
missing_source_data A required field is empty
invalid Failed validation
blocked A record it depends on is not importable
excluded You chose to skip it

Work through ambiguous and invalid first. Resolve a candidate — pick the right match, or exclude it — with PATCH /api/import-bundles/{id}/candidates/{candidateId}.

blocked usually clears on its own once the record it depends on is fixed, since the dependency is what was blocking it.

Terminal window
curl -X POST "https://api.getvespy.com/api/import-bundles/$BUNDLE_ID/start" \
-H "Authorization: Bearer $ACCESS_TOKEN"

The bundle goes queuedprocessingcompleted, or completed_with_errors if some rows failed while the rest were written. Poll GET /api/import-bundles/{id} for status, or watch the event stream.

POST /api/import-bundles/{id}/cancel stops a bundle that has not started writing.

Terminal window
curl "https://api.getvespy.com/api/import-bundles/$BUNDLE_ID/failures.csv" \
-H "Authorization: Bearer $ACCESS_TOKEN" --output failures.csv
curl "https://api.getvespy.com/api/import-bundles/$BUNDLE_ID/identity-map.csv" \
-H "Authorization: Bearer $ACCESS_TOKEN" --output identity-map.csv

failures.csv is the rows that did not import, with the reason on each — fix and re-upload under the same sourceNamespace.

identity-map.csv maps each source id to the Vespy id it became. Keep it: it is how you reconcile against the old system, and how anything still pointing at the old ids catches up.