Bulk audience import with import status reads, and CSV/NDJSON export of the audience. Upload routes carry their own 150 MB body ceiling.
Export the app as NDJSON
The whole app as newline-delimited JSON — settings, segments, templates, dynamic content, users, subscriptions, in-app messages and their engagement, messages, test devices, imports and deliveries — one object per line, in REST field names, streamed rather than buffered. Free, on every tier, at any time.
By default the stream holds one transaction open so every entity in the file is the same instant; the last line is a terminal record carrying per-entity counts, and the importer refuses a file without it, because a truncated stream otherwise looks exactly like a complete one.
What is not in it: journeys and journey history, custom events, and the daily usage rollups have no export entity. Nor do secrets and routing state — API keys, platform credentials, device credentials, Live Activity and push-to-start tokens, and media metadata are all deliberately withheld and named in the manifest.
path Parameters
app_idExport the app as NDJSON › Responses
A streamed NDJSON attachment ending in a terminal count record
Export one entity as CSV
One entity of the export rendered as CSV and streamed as an attachment.
Most entities contain the same records as the NDJSON archive. subscriptions joins every
device to all portable fields on its user profile, because a standalone CSV cannot express
the archive's two related streams. test_subscriptions adds the test-device name and timestamp;
importing it restores the subscription, user profile and named test-device marker. An
unrecognised entity is a 404 that names the full list.
Every cell, including the header row, is escaped against spreadsheet formula injection:
a value beginning with =, +, -, @, a tab or a carriage return comes back with a
leading apostrophe. The CSV import route strips that back off; if you parse these files
yourself, strip it too.
path Parameters
app_identityExport one entity as CSV › Responses
A streamed CSV attachment for one entity
Import subscriptions from CSV
Imports subscriptions from a CSV uploaded as a multipart file field.
Unlike the NDJSON restore this is an upsert on the push token, so it can be run against
an app that already has data.
Small files are processed inline and answer 200 with the summary. Anything larger is
spooled and handed to the job queue, answering 202 with an import_id to poll on
GET /v1/apps/{app_id}/imports/{import_id}. Uploads over the server's CSV size ceiling are
refused with 413 and the spool file is removed.
path Parameters
app_idImport subscriptions from CSV › Request Body
fileCSV subscription export to upsert by push token.
Import subscriptions from CSV › Responses
The import summary, or a 202 with an import_id to poll
Restore an NDJSON export
Reads an OpenPush NDJSON archive back in, uploaded as a multipart file
field. This is a restore into an EMPTY app, not a merge — a target that already has
subscriptions is refused with 409, which says how many it found. (409 rather than 400
on purpose: the file is fine, the target is wrong.)
Rows commit in batches as they are read, so a refusal partway through leaves the rows already written in place and says how many. There is deliberately no cleanup sweep — the empty-app precondition is what makes the remedy trivial: delete the app, make a new one, re-run the import.
path Parameters
app_idRestore an NDJSON export › Request Body
fileOpenPush NDJSON archive to restore into an empty app.
Restore an NDJSON export › Responses
Per-entity counts of the rows restored
Read import job status
Progress for one import job — status, rows done against rows total, the
summary, and the error if it failed. This is the route to poll after a 202 from the CSV
import.
path Parameters
app_idimport_idRead import job status › Responses
Status and progress for one import job