Data export
A copy of everything your organization holds on plugsync, as one ZIP package: connector configurations, plugins and their storage, identity mappings, sync history, recorded payloads, agent conversations, event source samples, conflicts, the outcomes of bulk imports and verifications, the reports of file sources with the rows they discarded, and usage figures. Use it to keep your own archive, to audit what the platform stores about your records, or to take your data with you. What a package leaves out is listed in its manifest, with the reason (see What is not in the package).
An export is asked for, prepared in the background, and then downloaded through a signed link:
POST /api/data-exportsanswers202with the export and itsid.GET /api/data-exports/{id}shows itsstatus. Preparation usually starts within a minute; how long it takes depends on how much data the organization holds.- Once the status is
completed,GET /api/data-exports/{id}/downloadreturns a signed link to the ZIP.
The same flow is in the dashboard under Settings > Organization > Data export.
Who can export
The owners of the organization, and the organization’s API keys. A member
gets 403. An export, like a connector, belongs to one organization: the id
of an export or of a connector of another organization answers 404.
A package holds payloads, plugin code and agent conversations, exactly as you wrote or sent them (see Secrets you wrote yourself). Treat the package, and a key that can ask for one, like a credential.
Scope
| Request body | What is exported |
|---|---|
{} or no body | The whole organization: every connector, every plugin, every agent conversation |
{"connector_id": "<id>"} | One connector: its configuration, mappings, history, recorded payloads, plugin storage, source samples, conflicts, bulk imports, verifications, file source reports, daily usage and the agent conversations about it. Plugins belong to the organization, so all of them are included. The monthly event count belongs to the organization, so usage/monthly_events.jsonl is empty |
One export per scope can be in preparation at a time: asking again while one
is queued or running answers 409 data_export_in_progress with the
export_id of the one in progress. An organization can ask for 10 exports
per 24 hours; past that the answer is 429 data_export_limit_reached.
| Answer | When |
|---|---|
404 connector_not_found | The connector does not exist in this organization, or was deleted while the request was being handled |
409 data_export_in_progress | An export of the same scope is queued or running |
409 organization_closed | The organization was closed and its recovery period is over |
429 data_export_limit_reached | 10 exports were requested in the last 24 hours |
The API error catalog collects these codes with the errors of every other public route.
API
All routes take Authorization: Bearer <token or API key>.
POST /api/data-exports
curl -X POST https://app.plugsync.com/api/data-exports \
-H "Authorization: Bearer $PLUGSYNC_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "5b0e6a52-7a0f-4d53-9c1e-2f4f0a1f7c11",
"scope": "organization",
"connector_id": null,
"status": "queued",
"requested_by": "api-key:backup",
"created_at": "2026-10-08T09:00:00Z",
"completed_at": null,
"expires_at": null,
"size_bytes": null,
"error": null,
"categories": []
}GET /api/data-exports and GET /api/data-exports/{id}
The list returns the 20 most recent exports of the organization, newest
first, as {"exports": [...]}. Each has the shape above.
| Field | Meaning |
|---|---|
status | queued, running, completed, failed or expired |
requested_by | The email of the owner, or api-key:<label> |
completed_at | When the package was ready |
expires_at | Until when the package can be downloaded: 7 days after completed_at |
size_bytes | Size of the ZIP |
error | On a failed export, why (see below); otherwise null |
categories | On a completed export, the categories and files of the package with their record counts (the same list as in manifest.json) |
| Status | What it means | What to do |
|---|---|---|
queued | Waiting to be prepared | Poll |
running | Being prepared | Poll |
completed | The package is ready until expires_at | Download |
failed | The export stopped for good | Read error |
expired | The package has been deleted | Ask for a new export |
error | What happened | What to do |
|---|---|---|
export_failed | Preparation failed three times. The attempts are spaced out: the second starts 2 minutes after the first failure, the third 10 minutes after the second | Ask for a new export |
scope_not_found | The connector of the export was deleted before the package was ready | Nothing: its data is gone |
organization_closed | The organization reached the end of its recovery period before the package was ready | Nothing |
GET /api/data-exports/{id}/download
{
"url": "https://...",
"filename": "plugsync-export-20261008-5b0e6a52.zip",
"expires_in_seconds": 300
}url is a signed link valid for 5 minutes: fetch it right away, without the
Authorization header. Ask for a new link whenever you need one, for as
long as the package exists.
| Answer | When |
|---|---|
409 data_export_not_ready | The export is still queued or running |
410 data_export_expired | The package is past expires_at, or was deleted earlier (see below) |
410 data_export_failed | The export failed |
503 data_export_unavailable | Data export is temporarily unavailable; retry later |
How long a package is kept
A package can be downloaded for 7 days after it is ready, then it is deleted
and the export shows as expired.
Deleting a connector expires every package of its organization, because a
package may hold that connector’s data: the exports show as expired and
their download answers 410 as soon as the connector is deleted, and the
files are removed right after. An export that was still being prepared at
that moment is prepared again without the deleted connector, so it can take
longer than usual; a package requested after the deletion is not affected.
Closing the organization deletes the packages with everything else at the
end of the recovery period; until then exports can still be requested and
downloaded. At the end of the period an export still in preparation fails
with organization_closed.
A package is a snapshot taken while syncs keep running. Each file is read once, front to back; records written while it is being read may or may not be in it.
The package
manifest.json
connectors.jsonl
configuration/versions.jsonl
configuration/drafts.jsonl
plugins/plugins.jsonl
plugins/storage.jsonl
plugins/<plugin name>/source.ts
plugins/<plugin name>/bundle.js
identity_mappings.jsonl
sync_history.jsonl
recorded_payloads.jsonl
agent_conversations.jsonl
source_samples.jsonl
conflicts.jsonl
bulk_imports/jobs.jsonl
bulk_imports/rows.jsonl
verifications/jobs.jsonl
verifications/rows.jsonl
file_sources/files.jsonl
file_sources/discarded_rows.jsonl
usage/monthly_events.jsonl
usage/daily_counters.jsonlEvery category is always present: a file with no records is an empty file,
and its entry in the manifest says "records": 0.
.jsonl files are JSON Lines : UTF-8, one JSON
object per line, no enclosing array. Read them line by line; the larger
files (identity_mappings.jsonl, sync_history.jsonl,
bulk_imports/rows.jsonl) can hold millions of lines. Timestamps are ISO 8601 in UTC. A field with no value is null.
The ZIP uses the ZIP64 extensions when it needs them (a file over 4 GB, or a
package over 4 GB). Current versions of unzip, 7-Zip, Python’s zipfile
and the archive tools of macOS and Windows read it.
manifest.json
The index of the package. Start here.
{
"format_version": 1,
"export_id": "5b0e6a52-7a0f-4d53-9c1e-2f4f0a1f7c11",
"organization_id": "0d0c9a3e-3a67-4b0e-b1a5-1f1f6a2b9d10",
"scope": "organization",
"connector_ids": ["9c7f0b5e-0a55-4b71-8f3b-6f1c2b7a4e02"],
"generated_at": "2026-10-08T09:01:12.412301+00:00",
"categories": [
{
"name": "identity_mappings",
"files": [
{ "path": "identity_mappings.jsonl", "format": "jsonl", "records": 18304 }
]
},
{
"name": "plugins",
"files": [
{ "path": "plugins/plugins.jsonl", "format": "jsonl", "records": 1 },
{ "path": "plugins/bank-check/source.ts", "format": "typescript", "plugin": "bank-check", "bytes": 2210 },
{ "path": "plugins/bank-check/bundle.js", "format": "javascript", "plugin": "bank-check", "bytes": 48113 }
]
}
],
"exclusions": [
{
"name": "credentials",
"description": "The credentials saved on the platform: connector credentials (OAuth tokens, API keys, passwords), the secrets of the event sources (such as webhook signing secrets) and the API keys of the organization.",
"reason": "They are secrets. They are stored encrypted and never leave the platform; the configuration refers to them by name, and the name is exported."
}
]
}| Field | Meaning |
|---|---|
format_version | Version of the layout described on this page. It changes only when a file is removed or renamed, or a field changes meaning. New files and new fields can appear without a new version: ignore what you do not know |
scope | organization or connector |
connector_ids | The connectors the package covers |
categories[].name | connectors, configuration, plugins, plugin_storage, identity_mappings, sync_history, recorded_payloads, agent_conversations, source_samples, conflicts, bulk_imports, verifications, file_sources, usage |
categories[].files[] | path in the ZIP and format; records for a .jsonl file; bytes and plugin for a plugin code file |
exclusions[] | What the package leaves out: a stable name, a description of what is left out and the reason. The list is the same for every package (see What is not in the package) |
connectors.jsonl
One line per connector.
| Field | Meaning |
|---|---|
id, name | The connector |
status | Its status when the export was taken |
mode | live or test |
default_store_id | The default store of a multi-store connector |
created_at, updated_at |
The credentials of a connector are never exported: the tokens, API keys and passwords you saved for it are not in the package in any form.
configuration/versions.jsonl
One line per published version of each connector, the full history.
| Field | Meaning |
|---|---|
connector_id, version | Which connector, which version number |
is_active | true on the version that is running |
published_at, published_by | When, and who: the email of the member who published, or a fixed name such as agent, system or api-key@system when the agent, the platform or an API key did it |
change_summary | The note attached at publish, if any |
config | The whole configuration of that version, as in Config V3 |
configuration/drafts.jsonl
One line per connector: the draft being edited, published or not.
| Field | Meaning |
|---|---|
connector_id | |
base_version | The published version the draft started from |
updated_at, updated_by | When, and who last saved it (same values as published_by) |
config | The draft configuration |
plugins/plugins.jsonl and the code files
One line per plugin of the organization.
| Field | Meaning |
|---|---|
id, name | The plugin |
status | Its lifecycle status (see Plugins) |
capabilities | The capabilities it declares |
staging_version, live_version | The promoted versions |
bundle_sha256 | SHA-256 of the bundle |
has_source, has_bundle | Whether source.ts and bundle.js are in the package for this plugin |
last_deployed_at, created_at, updated_at |
The code is next to it: plugins/<plugin name>/source.ts is the TypeScript
you pushed, plugins/<plugin name>/bundle.js the bundle built from it. Take
the exact paths from the manifest: characters that are not letters, digits,
., _ or - are replaced with _ in the folder name.
plugins/storage.jsonl
One line per key in the key-value store of a plugin on a connector (see the
store capability).
| Field | Meaning |
|---|---|
connector_id, plugin | Whose storage |
key | |
value | The stored value, as JSON |
updated_at | |
expires_at | When the key expires, or null. Keys already expired are not exported |
identity_mappings.jsonl
One line per record the platform has linked: the id in your system and the id of the HubSpot object.
| Field | Meaning |
|---|---|
connector_id | |
entity_type | The entity of your configuration |
store_id | The store, on a multi-store connector; otherwise null |
external_id | The id in your system |
hubspot_id | The id of the HubSpot object |
hubspot_object_type | The HubSpot object type |
metadata | Extra data kept with the mapping, if any |
last_synced_at, created_at, updated_at |
Other fields can be present on some lines (for example the timestamps used to resolve conflicts on bidirectional connectors).
sync_history.jsonl
One line per synced record, as listed by
GET /api/connectors/{connector_id}/sync-events. Lines are in no particular
order: sort on created_at if you need one.
| Field | Meaning |
|---|---|
id, connector_id | |
entity_name, event_type, action | What was synced |
direction | inbound or outbound |
status | The outcome (see Reliability) |
source_id, external_id, hubspot_id, store_id | The record |
occurred_at | When the event happened in the source system |
retry_count | |
error_detail, error | The last error, as text and structured |
step_trace | The steps the flow executed |
payload | The payload of the event, kept only when the event did not succeed (so that it can be replayed). null for an event that succeeded, and once the payload has been pruned |
payload_pruned_at | When the payload was pruned, if it was |
created_at, updated_at |
Only the history still kept is exported: rows and payloads already removed by data retention are not in the package.
recorded_payloads.jsonl
One line per event archived by record mode, with the payload exactly as it was received. Empty when record mode was never enabled.
| Field | Meaning |
|---|---|
connector_id, event_id | |
direction | inbound or outbound |
source_name, event_type, external_id | |
occurred_at, recorded_at | |
payload | The raw payload |
agent_conversations.jsonl
One line per conversation with the AI agent.
| Field | Meaning |
|---|---|
id | |
connector_id | The connector the conversation was about, or null |
title, status | |
messages | The messages, in order |
created_at, updated_at |
A connector export holds only the conversations about that connector.
source_samples.jsonl
One line per event source: the last payload that source received, the one the mapping editor shows as a sample. A source that has received nothing yet has no line.
| Field | Meaning |
|---|---|
id, connector_id | |
source_name | The event source. event_api for events sent to the Event API |
event_type | The event type of the sample, if it had one |
received_at | When it was received |
payload | The payload, as received |
conflicts.jsonl
One line per conflict detected on a bidirectional connector (see Loop prevention and conflicts).
| Field | Meaning |
|---|---|
id, connector_id | |
entity_type, external_id, store_id | The record |
status | pending, resolved or expired |
resolution | How it was resolved (source_wins, target_wins or merged), or null |
resolved_by, resolved_at | Who or what resolved it, and when |
reconciliation_status | Whether the resolved values were sent back to your system: none, pending or done |
source_data, target_data | The two versions of the conflicting properties: yours and the one on HubSpot |
resolved_data | The values kept, once resolved |
source_modified_at, target_modified_at | When each side last changed |
created_at | When the conflict was detected |
bulk_imports/jobs.jsonl and bulk_imports/rows.jsonl
jobs.jsonl has one line per bulk import, including the ones a file source
started.
| Field | Meaning |
|---|---|
id, connector_id, store_id | |
mode | raw or flows |
status | queued, running, completed, partial_failure or failed |
entities_total, entities_succeeded, entities_failed, entities_skipped | The totals of the import |
last_error | The last error of the import as a whole, if any |
created_at, started_at, completed_at |
rows.jsonl has one line per record of an import whose outcome was kept.
| Field | Meaning |
|---|---|
id | |
job_id | The id of the import in jobs.jsonl |
reference | The reference of the record in your request or file |
status | succeeded, failed or skipped |
error_category, error_message | Why it failed, if it did |
created_at |
verifications/jobs.jsonl and verifications/rows.jsonl
jobs.jsonl has one line per verification: a comparison between a batch of
your records and what is on HubSpot.
| Field | Meaning |
|---|---|
id, connector_id, store_id | |
entity_type, external_id_property | What was compared, and the property that identifies a record |
source_batch_id | The batch the verification refers to, if any |
status | queued, running, completed, partial_failure or failed |
records_total, records_ok, records_diff, records_absent, records_error | The totals: identical, different, not found, not checked |
last_error | The last error of the verification as a whole, if any |
created_at, started_at, completed_at |
rows.jsonl has one line per record whose outcome was kept.
| Field | Meaning |
|---|---|
id | |
job_id | The id of the verification in jobs.jsonl |
reference | The reference of the record |
status | ok, diff, absent or error |
differences | Per property, the value expected and the actual value found |
error_message | Why the record could not be checked, if so |
created_at |
file_sources/files.jsonl and file_sources/discarded_rows.jsonl
files.jsonl has one line per file a file source has read (see
Event sources), with the report
of its last processing. The files themselves are not in the package: they are
on your server.
| Field | Meaning |
|---|---|
connector_id, source_name, filename | Which file, of which source |
status | processed, processed_with_errors, failed or changed_blocked |
size, mtime, sha256 | The file as it was read: size in bytes, modification time, SHA-256 of its content |
processed_at | |
job_id | The id of the bulk import the file started, in bulk_imports/jobs.jsonl, or null |
rows_total, rows_discarded, rows_repaired, rows_reconciled | The row counts of the file |
entities_failed | Records that failed downstream, when known |
error | Why the file failed, if it did |
post_action_pending, post_action_error | The state of the action configured to run on the file after processing, if one is pending or failed |
A field the report of a file does not carry is null.
discarded_rows.jsonl has one line per discarded row the platform has kept.
It keeps the first 20 discarded rows of each file, not all of them:
rows_discarded in files.jsonl is the full count. A raw longer than
2,000 characters is cut at that length. The rows are kept for the files the
source still tracks: a processed file that has left the remote directory
stops being tracked after 90 days, or sooner once the source tracks more
than 1,000 files, and its rows go with it. When a file is processed again,
the rows it discards replace the ones kept before.
| Field | Meaning |
|---|---|
connector_id, source_name, filename | Which file |
line | The line number in the file |
reason | Why the row was discarded |
raw | The row, as it was in the file |
usage/monthly_events.jsonl and usage/daily_counters.jsonl
monthly_events.jsonl has one line per billing month: the events counted
against the plan of the organization. It is empty in a connector export.
| Field | Meaning |
|---|---|
period_start, period_end | The month |
events_count | Events counted in that month |
daily_counters.jsonl has one line per connector, day, outcome, entity and
direction: how many records ended with that outcome that day. These are the
figures behind the charts of the connector’s overview.
| Field | Meaning |
|---|---|
connector_id | |
date | The day, in UTC (YYYY-MM-DD) |
status | The outcome counted |
entity, direction | The entity and inbound or outbound; null when the count is not attributed to one |
count |
What is not in the package
Every package says this itself: exclusions in manifest.json carries the
same entries under the same names, each with a description and a reason.
name | What is left out | Why |
|---|---|---|
credentials | The credentials you saved on the platform: connector credentials (OAuth tokens, API keys, passwords), the secrets of the event sources (such as webhook signing secrets) and the API keys of the organization | They are secrets. They are stored encrypted and never leave the platform. A configuration refers to them by name, and only the name is exported |
export_files | The files written by export_file steps | They are already yours to download from the connector’s Exports tab (see Scheduled exports) |
account_data | Account data of the people in the organization: members, invitations, billing | It describes the account, not the data your connectors process, and it is managed from the dashboard settings |
discarded_rows_beyond_sample | The rows discarded from a file beyond the first 20 of that file | The platform keeps a sample of the discarded rows of each file, not all of them |
data_past_retention | Records and payloads already deleted at the end of their retention period | Data is deleted periodically after its retention period ends. What the platform still holds when the export runs is included |
processing_state | Working data of the processing: the copy of each event as it was received, the values read ahead of an import, the messages held back by a limit | It is what the platform keeps while it works. The outcome of every event is in sync_history.jsonl. The payload is there only for an event that did not succeed. When record mode is on, the payloads are in recorded_payloads.jsonl |
platform_logs_and_backups | The logs and the backups of the platform | They are not kept per organization, so an export cannot package them. A request about them goes through support |
The one trace of a member in a package is the email recorded where that
member acted: published_by and updated_by in the configuration files,
resolved_by in the conflicts.
Secrets you wrote yourself
The package is a faithful copy: nothing in it is masked or rewritten. The platform keeps the credentials you saved out of it, but it cannot tell a secret from any other text in content you wrote or sent. If one of these holds a secret, the package holds it too:
| Where | What can be there |
|---|---|
config in configuration/versions.jsonl and configuration/drafts.jsonl | A token typed as a literal in a header, a URL or a body of a step or of an event source, instead of a reference to a saved credential. Every published version is exported, so a literal removed later is still in the older versions |
error_detail, error and step_trace in sync_history.jsonl | The URL that was called and the answer of the target system, as text. A key passed in a query string, or echoed back by the target, is in that text. Output traced by a plugin is there too |
payload in sync_history.jsonl, the recorded payloads and the source samples | Whatever the source system sent |
source_data, target_data and resolved_data in conflicts.jsonl, differences in verifications/rows.jsonl, raw in file_sources/discarded_rows.jsonl | Values of your records, as they were sent or read |
error_message and last_error in the bulk import and verification files, error in file_sources/files.jsonl | The answer of the target system or of your server, as text |
messages in the agent conversations | Whatever was typed or pasted in the chat |
plugins/storage.jsonl and the plugin code | Whatever the plugin stored, and any constant in its source |
To keep a secret out of future packages, save it as a credential and refer to it from the configuration.
Reading a package
import json, zipfile
with zipfile.ZipFile("plugsync-export-20261008-5b0e6a52.zip") as package:
manifest = json.loads(package.read("manifest.json"))
for category in manifest["categories"]:
for entry in category["files"]:
print(entry["path"], entry.get("records", entry.get("bytes")))
with package.open("identity_mappings.jsonl") as lines:
for line in lines:
mapping = json.loads(line)
...