Amplitude’s raw Export API is the appropriate starting point for a historical migration. It returns ZIP archives containing JSON files with one event per line. Build the export into a durable staging step, then transform each line into your GraphJSON event contract.
Read the general migration workflow before using this provider-specific mapping.
Export a bounded interval#
Use Amplitude’s official Export API and process time in UTC. The API selects data using Amplitude’s documented export-time behavior, which may differ from the occurrence-time field you ultimately preserve.
Export small intervals first. Amplitude documents a 4 GB response-size limit and a maximum request window; busy projects may need hourly requests. For every response, retain:
- requested UTC interval
- archive checksum and byte size
- file names
- decoded row count
- earliest and latest
event_time - export failure and retry log
Keep the raw archives immutable. Write transformed output to a separate path so mapping changes never destroy the original.
Map the fields#
Start with this mapping, then remove anything your product does not need:
| Amplitude field | GraphJSON target | Notes |
|---|---|---|
event_type |
event |
Normalize only with an explicit rename map |
event_time |
request timestamp |
Parse as UTC and convert to whole Unix seconds |
user_id |
user_id |
Preserve your application ID when this is one |
device_id |
anonymous_id |
Do not treat it as a known user automatically |
event_properties |
selected event fields | Allowlist, rename, and type-check |
user_properties |
selected snapshot fields | Include only if event-time semantics are useful |
groups |
account_id or named group IDs |
Map each group type deliberately |
$insert_id |
source_insert_id and/or event_id |
Preserve for duplicate analysis |
Amplitude may contain both merged identities and original identifiers. Decide whether historical GraphJSON metrics should follow the exported row identity or your current application identity map. Document the choice; GraphJSON does not automatically reproduce Amplitude’s identity resolution.
Transform one record#
function toGraphJSON(source) {
if (!source.event_type || !source.event_time) {
throw new Error("missing event_type or event_time");
}
const eventTime = Date.parse(source.event_time);
if (!Number.isFinite(eventTime)) {
throw new Error("invalid event_time");
}
const props = source.event_properties || {};
return {
timestamp: Math.floor(eventTime / 1000),
body: {
event: source.event_type,
event_id: source.$insert_id
? `amplitude:${source.$insert_id}`
: undefined,
user_id: source.user_id || undefined,
anonymous_id: source.device_id || undefined,
account_id: source.groups?.account_id || undefined,
plan: typeof props.plan === "string" ? props.plan : undefined,
amount:
typeof props.amount === "number" ? props.amount : undefined,
schema_version: 1,
migration_source: "amplitude",
},
};
}
Remove undefined values before serialization. In a real transform, validate against the same schema used by new production instrumentation and write rejected rows to quarantine with a reason code.
Avoid spreading all event_properties or user_properties into the target. That can copy sensitive data, inconsistent types, and payloads beyond GraphJSON’s 10,000-character event limit.
Handle identity explicitly#
Amplitude distinguishes user_id and device_id, and its analysis may merge activity according to Amplitude’s identity rules. GraphJSON stores those values without a hidden merge.
Choose one of these approaches:
- Keep anonymous and authenticated activity separate.
- Emit or import an
identity_linkedevent and join it in SQL. - Rewrite historical anonymous rows using a verified identity map from your own system.
The third option changes historical records and is hard to undo. Use it only when the application database provides an authoritative mapping. See Users, accounts, and identity.
Reconcile the result#
Compare:
- raw export rows versus accepted and quarantined rows
- counts by
event_typeandtoDate(event_time) - unique
$insert_idversus unique importedevent_id - missing
user_idanddevice_idrates - sums for important numeric properties
- several complete user and account histories
Do not expect an Amplitude dashboard total to match a raw import without matching its filters, project timezone, identity behavior, and historical transformation settings.
Cutover checklist#
- Direct GraphJSON instrumentation uses the approved target names and types.
- The export interval and Amplitude project region are correct.
- Event time comes from
event_time, not archive or upload time. - Event and user properties use explicit allowlists.
- Device and user identity behavior is documented.
-
$insert_idis preserved where available. - A representative pilot day reconciles before the full run.
- Staged archives have access controls and a deletion date.