P.E.A. is designed to hold all your positive eperiences. That also means it's been designed to bring in any data that you would like to capture inside the archive, such as Swarm/Fourrsquare/Day 1/or anything else you'd like to add via a spreadsheet.
This data is kept absolutely private on your devices and presonal iCloud account.
What this page does
It documents the data stored in P.E.A. backup files (.json), and it provides tools and information so you can:
Read your experience data in any text editor
Move your data between iCloud accounts
Merge two archives
Bring data in from another app — by hand, script, or by using AI
Move your data into another app
The P.E.A. format is versioned and publicly accessible.
Format structure
Every P.E.A. backup/export is a single JSON file. Usually called something like:
The file contains plain text (a JSON object) that looks like this:
{ "exportSchemaVersion": 3, "exportDate": "2026-05-23T17:30:42Z", "appVersion": "1.0", "entryCount": 247, "placeCount": 12, "mottoCount": 1, "peopleCount": 18, "conceptCount": 34, "readme": "P.E.A. — Positive Experience Archive\n…", "entries": [ … ], "places": [ … ], "mottos": [ … ], "people": [ … ], "concepts": [ … ] }
Notes on the format:
**exportSchemaVersion**set the rules how P.E.A. understands your data. It won't import a file it doesn't know how to read, rather than guess.**entries**,**places**,**mottos**,**people**,**concepts**are the building blocks of data inside P.E.A. so they are independent top-level collections. Each data point for them carries a unique identifier oruuidso that a future import can recognize them again to avoid creating duplicate entries.Every entry that's associated with a saved place ALSO carries that place's data inline via
placeName,placeDetail,placeSource, etc. So you don't have to cross-reference: an entry on its own is self-describing. The top-levelplacesarray gives you the list of places as the user organized them. People and concepts work the other way around — entries reference them by uuid (peopleUUIDs,conceptUUIDs). The top-levelpeople/conceptsloops are more simple and hold all records.Optional fields are not stored when the data isn't there. Some other apps carry
null, or empty fileds. P.E.A. doesn't do this so the data is easier to read and edit.
Positive Experience Archive Schema
Every Positive Experience is one object in the entries array. Required fields are marked R.
Identity and content
Field | Type | Notes |
|---|---|---|
R | String | This number identifies each entry uniquely / Canonical UUID (8-4-4-4-12 hex). |
R | Integer | This is the number |
R | String | Text you entered or dictated about the experience. This is optional and can be empty. |
R | Integer 1–7 | Strength of the experience. 7 being life changing. |
R | String | Labels that accompany the intensity number: "Minimum", "Light", "Mild", "Medium", "Strong", "High", "Maximum". |
Time
Field | Type | Notes |
|---|---|---|
R | String (ISO 8601) | When the dot was released. |
R | String (ISO 8601) | Last time an experience entry was edited. |
R | String | "Morning", "Afternoon", "Evening", "Night". |
R | String | "Monday" through "Sunday". |
R | String | "Spring", "Summer", "Autumn", "Winter". |
Your physical location at capture
This metadata comes from the device. They are testimony, not opinion. P.E.A. doesn't edit these after capture.
Field | Type | Notes |
|---|---|---|
| Number | Raw GPS, decimal degrees. |
| Number | Raw GPS, decimal degrees. |
| String | Reverse-geocoded name ("Café Vita"). |
| String | City or town. |
| String | Country. |
A place you want to reference (semantic place)
You can assign a place. This can be because it was a place you were thinking about later, or because you wanted to correct the map pin. These saved places annotate entries without overwriting the location where the experience was captured. When you save a place, subsquent experiences close by (a hundred meters) automatcally enter the location you saved. Saved places are optional set, they mirror a row in the top-level places collection.
Field | Type | Notes |
|---|---|---|
| String | The label you chose ("home", "Mom's house"). |
| String | Locality context for that place. |
| String |
|
| Number | Place coordinate. |
| Number | Place coordinate. |
| String | Apple Maps identifier, when assigned from POI. |
| Boolean | True if you explicitly assigned this place. |
Weather and sky
Field | Type | Notes |
|---|---|---|
| String | "Clear", "Cloudy", "Rain", … |
| Number | Captured in metric; both units shown in-app. |
| String | "Full Moon", "Waxing Crescent", … |
Activity, motion, environment
Field | Type | Notes |
|---|---|---|
| String | "stationary", "walking", "running", "cycling", "driving". |
| Number | Meters per second. |
| Number | Elevation in meters. |
| Integer | Pedometer count for the day of capture. |
| Number | Ambient noise in dB. |
| String | Comma-separated nearby points of interest. |
Music
Field | Type | Notes |
|---|---|---|
| String | Song title playing at capture (Apple Music). |
| String | Artist name. |
| String | Apple Music catalog ID, for deep linking. |
Capture source device
Field | Type | Notes |
|---|---|---|
| String | "Timothy's iPhone", "Siri", "Watch" etc. |
| String | Semantic device category: |
Source provenance and identity (v2)
Which app/format originally produced the entry, plus a stable identifier from that source. The two fields together form the merge-deduplication key when you bring data in through Add data to P.E.A. — (source, sourceID) keeps re-imports from creating duplicates.
P.E.A. preserves these values forever: once an entry is stamped ("swarm", "5c1b…"), it stays that way through every future export, even if you re-import the archive on another device.
Field | Type | Notes |
|---|---|---|
| String |
|
| String | Stable identifier from the originating system. Swarm check-in id, Day One entry id, CSV row id. Omit on native PEA rows. When present, |
| String | The original record verbatim (stringified JSON of the source's raw data). With this P.E.A. can reach back into fields it didn't model. |
| String | Permalink to the original website of foreign datasets (like the Swarm check-in page, a Day One entry URL, an original tweet, etc). When this field contains a link, the app shows "View external experience" on the detail screen. |
When you're hand-converting data into P.E.A. format, set source to a short lowercase identifier, like csv. Recommended values for common sources are above. Set sourceID to the original row's stable identifier when that exists — it's how P.E.A. knows "this Swarm check-in is the same from the import last week".
Intensity state (v2)
Field | Type | Notes |
|---|---|---|
| Boolean | Only shows on rows that were brought in from a foreign source via add to P.E.A. and haven't been rated yet by the user. If this value is 1 P.E.A. renders it as a solid light-gray dot at the smallest size. (Dashed-gray is reserved for Neutralized rows; Unset is a separate state with its own solid-fill visual so the two stay distinguishable at a glance.) Native P.E.A. entries should never show this field. |
People links (v2)
Field | Type | Notes |
|---|---|---|
| Array of Strings | Unique identifier for each person so data for them can come from more than one source (Canonical UUIDs of people associated with this entry.) Each idenftifier (UUID) matches one row in the top-level |
Concept links (v3)
Field | Type | Notes |
|---|---|---|
| Array of Strings | Unique identifier (Canonical UUIDs) for each concept, like sports, travel, etc associated with an entry. Each UUID matches one row in the top-level |
Soft-deletion (Neutralization)
When you neutralize a positive experience it's removed from the list and avaible in the Index via the Neutralized filter for 30 days. After that time, or when manually triggered, neutral experiences are deleted from P.E.A.
Field | Type | Notes |
|---|---|---|
| Boolean | Present and |
| String (ISO 8601) | When you neutralized it. |
| String | Device that performed the neutralization. |
Place Schema
Places you save and reuse, eg: "Home", "Jan's Haus", "Papa's".
Every place is one object in the top-level places array. Required fields are marked R.
Field | Type | Notes |
|---|---|---|
R | String | Identifier (Canonical UUID) for places to match them across exports. |
R | String | The name you gave the place ("Home", "Jan's Haus"). |
R | String (ISO 8601) | When you saved the place. |
| String | Locality context ("Helsinki", "Seattle", "Prague"). |
| Number | Place coordinate, decimal degrees. |
| Number | Place coordinate, decimal degrees. |
| String | Apple Maps identifier, when selected from a point of interest (POI). |
Experiences reference a place with placeName / placeLatitude / placeLongitude / placeMapItemIdentifier fields inline (see Entry Schema above). A future schema version may add a foreign-key placeUUID field on entries; today the entry-level place fields are the source of truth for what's been assigned to an entry, and the top-level places array is the source of truth for the user's curated list.
On Add data to P.E.A. (merge), the system makes sure that places don't duplicate through the uuid or by content — it uses the name plus both coordinates matching at five-decimal (~1 m) precision to identify duplicates. The content match exists because the same place can carry different UUIDs on different devices (UUIDs were backfilled onto pre-existing rows independently per device), so a uuid-only check would duplicate every patch on a cross-device re-import. Places with the same name at different coordinates remain distinct — the newly imported one is renamed by adding a number like "Home 2" because place names are unique within a library (entries reference places by name).
Motto Schema
A motto is three words to you entered or picked that show first on every device to make it your own. Every motto is one object in the top-level mottos array.
Field | Type | Notes |
|---|---|---|
R | String | Identifier (Canonical UUID) for Mottos to match them across exports. |
R | String | First word/line of the motto. |
R | String | Second word/line. |
R | String | Third word/line. |
R | String (ISO 8601) | When the motto was saved. |
Mottos are gated behind a setting in the app. Importing an archive that contains mottos will enable that surface automatically so the words you brought in are visible.
On Add data to P.E.A. (merge), mottos dedupe by uuid or by exact (line1, line2, line3) content match. Mottos are typed by hand and carry no other identity, so an identical three-line match is treated as the same motto even when the UUIDs differ (which happens when UUIDs were backfilled independently on different devices).
People Schema (v2)
People you can associate with an experience: friends, family, the person you ran into at the coffee shop. You add and link them in the entry editor's People field. A foreign-source converter can also populate the same table — supply a top-level people array and reference its UUIDs from each entry's peopleUUIDs (a CSV with a people column converts cleanly this way). Note: the bundled Swarm converter creates no people, because Foursquare's downloadable export strips the companions/with list (it lives only in the live Swarm API); the only trace of who you were with is an @mention left in the shout text.
Every person is one object in the top-level people array. Required fields are marked R.
Field | Type | Notes |
|---|---|---|
R | String | Unique ID (Canonical UUID (8-4-4-4-12 hex)). Stable across imports/exports. |
R | String | Display name ("Sawa", "Murat", "Fabian"). |
R | String (ISO 8601) | When this person was first added. |
R | String (ISO 8601) | Last time this person record changed. |
| String |
|
| String | Stable identifier from the originating system, when a converter supplies one. With |
| String | URL or local-asset path for a photo. P.E.A. doesn't download or display these yet — they round-trip only. |
| String | Free-form notes the user might keep on a person. Round-trip only — P.E.A. doesn't surface them yet. |
Entries reference people by carrying a peopleUUIDs array (see Entry Schema → People links). The relationship is many-to-many: one person can appear on many entries, and one entry can list many people. On import, the link pass runs after every PeaPerson row is inserted so the entries can resolve each UUID to a live row.
People are created and linked in the entry editor — tap a suggestion or confirm a typed name. Native captures (iPhone, Mac, watch, widget, Siri) never create people; they take no people-aware input. Lists, search, and map don't yet surface people, but the editor reads and writes them, and they round-trip through every export.
On Add data to P.E.A. (merge), people dedupe by uuid or by (source, sourceID) when both are present. Native rows carry no sourceID, so they dedupe by uuid only.
P.E.A./Concept Schema (v3)
A concept is a lightweight keyword tag you attach to an experience — a vocabulary you build as you archive ("soccer", "made", "noticed", "first time"). Concepts are created and linked in the entry editor's Concepts field: tap a suggestion, a default seed, or type a new one.
Every concept is one object in the top-level concepts array. Required fields are marked R. The shape mirrors People exactly so a converter (or a reader) can treat the two the same way.
Field | Type | Notes |
|---|---|---|
R | String | Canonical UUID (8-4-4-4-12 hex). Stable across exports. |
R | String | Display label, in the user's original casing ("soccer", "First Time"). |
R | String (ISO 8601) | When this concept was first added. |
R | String (ISO 8601) | Last time this concept record changed. |
| String |
|
| String | Stable identifier from the originating system. With |
Entries reference concepts by carrying a conceptUUIDs array (see Entry Schema → Concept links). The relationship is many-to-many: one concept can appear on many entries, and one entry can list many concepts. On import, the concept link pass runs after every PeaConcept row is inserted so the entries can resolve each UUID to a live row.
On Add data to P.E.A. (merge), concepts dedupe by uuid or by (source, sourceID) when both are present. Native concepts carry no sourceID, so they dedupe by uuid only — two libraries' independently-created "made" concepts can both land.
P.E.F./One Experience example in detail
This is a native P.E.A. capture (source: "pea"), so it carries a real archiveNumber and a user-chosen intensity — exactly what an entry you made in the app looks like on the way out.
{ "exportSchemaVersion": 3, "exportDate": "2026-05-12T17:30:42Z", "appVersion": "1.0", "entryCount": 1, "placeCount": 1, "mottoCount": 0, "peopleCount": 0, "conceptCount": 0, "readme": "P.E.A. — Positive Experience Archive\n=====================================\n…", "entries": [ { "uuid": "1A2B3C4D-5E6F-7081-9202-A1B2C3D4E5F6", "archiveNumber": 412, "text": "Falco on Walmart radio.", "intensity": 2, "intensityLabel": "Light", "createdAt": "2026-01-15T18:32:00Z", "updatedAt": "2026-01-15T18:32:00Z", "timeOfDay": "Evening", "dayOfWeek": "Thursday", "season": "Winter", "latitude": 35.2271, "longitude": -80.8431, "locationName": "Walmart Supercenter", "locationLocality": "Charlotte", "locationCountry": "United States", "placeName": "the Walmart by the airport", "placeDetail": "Charlotte, NC", "placeSource": "custom", "placeLatitude": 35.2270, "placeLongitude": -80.8430, "placeMapItemIdentifier": "I3F4A2C8B9D0E1F2A3B4C5D6E7F8A9B0", "isUserConfirmedPlace": true, "weatherCondition": "Clear", "temperatureCelsius": 7, "moonPhase": "Waxing Crescent", "activityType": "walking", "speedMPS": 0.6, "altitudeMeters": 226, "stepCountToday": 6428, "noiseDecibelLevel": 68, "nearbyPOI": "Walmart Supercenter, Bojangles, ExxonMobil", "musicTrack": "REAL ONES NEVER DIE", "musicArtist": "Kid Cudi", "musicStoreID": "1886092613", "deviceName": "iPhone 15 Pro", "deviceType": "iphone", "source": "pea" } ], "places": [ { "uuid": "9F8E7D6C-5B4A-3928-1706-D5C4B3A29180", "name": "Airport Walmart", "detail": "Charlotte, NC", "latitude": 35.2270, "longitude": -80.8430, "mapItemIdentifier": "I3F4A2C8B9D0E1F2A3B4C5D6E7F8A9B0", "createdAt": "2025-12-30T14:10:00Z" } ], "mottos": [], "people": [], "concepts": [] }
What happened at this Walmart when a person archived their Experience:
Music. Even though the person was listening to Kid Cudi, Walmart radio was playing Falco. It must have piqued the person's curiosity enough to pop out the headphones and make a note. Then P.E.A. captured the music that was actually being listened to (Cudi) as well as the note the person left mentioning Falco.
Metadata comes from the device or the person. Coordinates, weather, moon phase, steps, and the song are captured from the iPhone.
placeNameis a place they saved manually. P.E.A. never edits these pieces of information after they are captured, and when a piece of data isn't available (no music playing, no place assigned), the Archive leaves it out. When you create or edit archive files, follow the same rule.Experience Intensity 2. The person dragged the marker to the light setting. Falco at Walmart is not earth shattering but it is weird and fun and something that P.E.A. was built to save.
P.E.F./Import + export
P.E.A. only imports P.E.F. format (json). But you can use any tool you like to convert your data into P.E.A. format — including a chat with an LLM.
The schema above is everything an LLM needs to bring data into the Archive. So, whether you want to move or copy your existing journal or tracking app, you can ask an LLM to rewrite the data to a single PEA-Export-….json file and then bring it in via Settings → Add data to P.E.A. — a non-destructive merge that leaves everything you already have untouched.
Imported entries arrive Unset. P.E.A. never guesses how strong a moment was. Every converted row comes in as a light-gray "Unset" dot with no P.E. # number; you assign the real strength yourself by dragging the dot in the app, and that's the moment the row earns its permanent number. So the prompts below deliberately do not ask the model to invent an intensity — they pin every row to the Unset state and let you do the rating.
Data import prompt
A note on privacy: Make sure you trust the large language model you use and review the company data policy. If the service is free, it's likely they keep your info. If you are running a local or on-device model that can't fetch URLs (Ollama, LM Studio, Apple Intelligence on-device), paste the schema sections from the top of this page into the prompt instead of leaving the link.
You are converting my journal entries into the P.E.A. archive format.
The full schema is here: https://archive.green/format
Please produce a single JSON file matching the structure exactly:
- Top-level "exportSchemaVersion" must be 3.
- Top-level keys: "exportSchemaVersion", "exportDate", "appVersion",
"entryCount", "placeCount", "mottoCount", "peopleCount",
"conceptCount", "readme", "entries", "places", "mottos", "people",
"concepts". No other top-level keys.
- "entries" gets one object per experience.
- "places" gets one object per saved place (may be []).
- "mottos" gets one object per motto (may be []).
- "people" gets one object per person (may be []).
- "concepts" gets one object per keyword tag (may be []).
- Required entry fields: uuid, archiveNumber, text, intensity,
intensityLabel, createdAt, updatedAt, timeOfDay, dayOfWeek, season.
- Required place fields: uuid, name, createdAt.
- Required motto fields: uuid, line1, line2, line3, createdAt.
- Required concept fields: uuid, name, createdAt, updatedAt.
- "uuid" must be a canonical 8-4-4-4-12 UUID string. Generate fresh
random UUIDs unless you are reusing IDs from a P.E.A. archive.
- "archiveNumber" must be 0 for every row. P.E.A. assigns the permanent
`P.E. #N` identifier the moment a user first rates a row in the
editor (Unset → any green step). Until then the app renders the row
as `P.E. #NaN`. Do NOT pre-number imported rows; doing so makes them
indistinguishable from user-rated native captures.
- Imported rows arrive UNRATED. Do NOT guess a strength from the text.
For EVERY entry set "intensity": 1, "intensityLabel": "Minimum", and
"isIntensityUnset": true. (P.E.A. forces this state on any non-"pea"
row at import time anyway, so a guessed number would just be thrown
away — and the whole point is that I rate each moment myself.)
- "source": a short lowercase tag for where the data came from
("dayone", "notes", "import", …). Never "pea" — that's reserved for
moments captured inside the app. This tag is what marks the row as
Unset on import and keeps re-imports from duplicating it.
- Use ISO 8601 for all dates with timezone (e.g. 2026-05-12T08:14:03Z).
- Derive timeOfDay/dayOfWeek/season from createdAt.
- Omit any optional field you don't have data for. Do not write null.
Here is my data:
[paste your existing journal export here]The output should be a single JSON file matching the schema above. Save it, then load it through Settings → Add data to P.E.A.
Keep your data private
With P.E.A., your data stays yours. If you are converting or importing data, that should be no different.
Recommended (on-device only):
Apple Intelligence on iOS 26 / macOS 26 — Foundation Models, Writing Tools, or the Shortcuts "Use Model" action. Runs on-device when it can; Private Cloud Compute fallback for longer inputs. Strongest non-local privacy guarantee available today (cryptographic attestation, no persistence, no operator access).
Local LLMs via Ollama, LM Studio, or any MLX-based runner. A 7B–8B model (Llama 3.1, Qwen 2.5, Gemma 3, Mistral) on a Mac with 16 GB RAM converts a year of journal entries in a few minutes — no account, no API key, no network traffic.
Hosted APIs (OpenAI, Anthropic) offer no-training and no-retention modes that work for this task, but they require trust in the vendor's policy. Make sure you set your settings for the provider not to train on your data and that you trust them.
What to avoid: Free consumer chat UIs (ChatGPT/Gemini/Claude on the web) where data-handling settings are easy to misconfigure. P.E.A. entries can include exact locations and emotional context — they are not the right thing to paste into a chat you haven't audited.
Sources we expect people to bring
Day One JSON — exports include rich timestamp + location data; LLMs convert this cleanly.
Swarm / Foursquare check-ins — coordinates and timestamps map well; intensity is your call. Worked example below.
Apple Notes / Markdown journals — text + filename dates is enough to start; everything else is optional.
Plain CSVs — works. See import template below.
If you have a method for a conversion of a popular data source, send it to Florian. He'll add it here.
Worked example — Swarm / Foursquare
First, get your data. Go to foursquare.com/settings/privacy and hit Initiate Data Download Request. Foursquare emails you a download link when the export is ready — this can take up to 72 hours. The email links a data-export-NNNN.zip; download and unzip it, and that folder of checkinsN.json files is what you feed to the converter below.
Every Swarm check-in maps to a P.E.A. entry. Imported check-ins arrive in the Unset intensity state — solid light-gray dots at the smallest size — and you assign a strength later by dragging the dot in the editor or swiping the row in the timeline.
There are two paths into P.E.A. from a Swarm/Foursquare export:
1. Run the converter script (recommended; works for any size export)
A small Python script does this conversion deterministically — no LLM, no chat-window pasting, no per-row prompt cost:
It's Python 3 stdlib only (no pip install). Open the link above, copy the whole script into a file named swarm_to_pea.py, point it at your unzipped Swarm export, and it writes a P.E.A. v2 archive next to the originals:
That writes data-export-NNNN/pea-swarm.json. Then Settings → Add data to P.E.A. in the app, pick that file, confirm Merge.
Useful flags:
--since YYYY-MM-DD— convert only check-ins on/after this local date--seed N— deterministic UUIDs for testing (not needed in normal use)
Run python3 swarm_to_pea.py --help for the full reference, including a built-in FAQ.
Imported rows are numberless. Every row arrives at archiveNumber: 0 and renders in the app as P.E. #NaN. The app assigns a permanent P.E. #N the moment you rate a row in the editor (drag the dot to any green step). Until then a check-in is a candidate, not a P.E. — clearing back to Unset later keeps the earned number; never rating a row leaves it numberless forever (and the freezer can hard-delete it after 30 days without ever burning an archive slot).
2. Convert by hand or with an LLM (good for understanding the mapping, or for tiny exports)
The rest of this section explains the mapping the script implements. If you want to roll your own converter, or you have ten check-ins and don't want to download anything, the JSON example and starter prompt below produce equivalent output.
What a real Swarm check-in looks like
Swarm exports ship as a folder of checkinsN.json files. Each one looks like this (top-level wrapper omitted; one row from the items array shown):
{ "id": "abcdef0123456789abcdef01", "createdAt": "2024-07-21 23:22:37.000000", "type": "checkin", "timeZoneOffset": -420, "lat": 47.700571, "lng": -122.378029, "venue": { "id": "...", "name": "Botanica Bar", "url": "https://app.foursquare.com/v/..." }, "shout": "Saw a friend.", "comments": { "count": 0 } }
Notes on the shape:
createdAtis a quoted string with UTC wall-clock time and microsecond resolution (YYYY-MM-DD HH:MM:SS.ffffff), not a unix timestamp.Coordinates are top-level
lat/lng, not nested undervenue. Thevenueblock carries only{id, name, url}— no city or country.shout(the user's prose) is present on a small minority of rows; most check-ins have noshoutat all.timeZoneOffsetis minutes from UTC at the time of the check-in (e.g.-420is Pacific Daylight Time).
The corresponding P.E.A. v2 entry
{ "uuid": "550e8400-e29b-41d4-a716-446655440000", "archiveNumber": 0, "text": "Saw a friend.", "intensity": 1, "intensityLabel": "Minimum", "isIntensityUnset": true, "createdAt": "2024-07-21T16:22:37-07:00", "updatedAt": "2024-07-21T16:22:37-07:00", "timeOfDay": "Afternoon", "dayOfWeek": "Sunday", "season": "Summer", "latitude": 47.700571, "longitude": -122.378029, "locationName": "Botanica Bar", "source": "swarm", "sourceID": "abcdef0123456789abcdef01", "sourceURL": "https://swarmapp.com/checkin/abcdef0123456789abcdef01", "importPayload": "{\"id\":\"abcdef0123456789abcdef01\", ... }" }
Mapping table
Swarm | P.E.A. v2 | Note |
|---|---|---|
|
| Half of the |
|
| Constructed as |
|
| Treat the string as UTC, shift by |
|
| Empty shouts are fine; |
|
| Copy verbatim. |
|
| Omitted if the check-in has no |
derived from local time |
| Compute from the local time (after the timezone shift), not from UTC. |
derived per-row |
| Fresh random UUID, one per check-in. |
constant |
| Always |
constant |
| Imported rows arrive in the Unset state. The user assigns a real strength later. The merge importer enforces this regardless of what the JSON file says. |
constant |
| Stamp every row. Lets the app tell native captures from imported ones; sticky forever in round-trips. |
raw check-in (stringified) |
| Compact JSON of the original Swarm row, embedded as a string. The app doesn't read it today; future schema versions can backfill from it. |
| — | Dropped. No P.E.A. equivalent today. Preserved inside |
Starter prompt for an LLM (small exports only)
For larger exports use the script. For ≲100 check-ins, you can paste your data into an LLM with this prompt and skip the script entirely:
Convert this Swarm/Foursquare export into a P.E.A. v2 archive JSON file. Follow the schema at https://archive.green/format. For each Swarm check-in: - uuid: generate a fresh random UUID (8-4-4-4-12 hex) - archiveNumber: 0 for every row. P.E.A. assigns the permanent P.E. #N when the user first rates a row in the app; imported rows are deliberately numberless until then. Do NOT pre-number them. - text: copy from the Swarm "shout"; use "" if blank - intensity: 1 - intensityLabel: "Minimum" - isIntensityUnset: true - createdAt, updatedAt: parse the Swarm "createdAt" as UTC, shift by "timeZoneOffset" minutes, emit ISO 8601 with the local offset (e.g. "2024-07-21T16:22:37-07:00"). Use the same value for both. - timeOfDay, dayOfWeek, season: derive from the LOCAL time (after the shift) - latitude, longitude: copy from the top-level "lat" / "lng" - locationName: copy from "venue.name" (omit if no venue) - source: "swarm" on every entry - sourceID: copy the Swarm "id" verbatim - sourceURL: build "https://swarmapp.com/checkin/<id>" (omit if no venue) - importPayload: the raw Swarm row, JSON-stringified (compact, no indent) - Omit every other optional field Top-level wrapper: { "exportSchemaVersion": 3, "readme": "Converted from Swarm.", "exportDate": "{today, ISO 8601 with Z}", "appVersion": "1.0", "entryCount": {number of entries}, "placeCount": 0, "mottoCount": 0, "peopleCount": 0, "conceptCount": 0, "entries": [ ... ]
Using the CSV template
If you have data in a spreadsheet (or you'd rather sketch your archive by hand in Numbers / Excel / Google Sheets than write JSON), grab the template:
Open the link, copy the rows into a file named PEA-Import-Template.csv, and open it in your spreadsheet of choice.
The columns are ordered by how likely you are to have the data. The first ones — the date and what you wrote — are all most entries need; then where you were, the people and concepts you want to tag, and finally the device-captured context (weather, activity, music, …) that usually only exists if you're converting from another app's export. Leave any column blank.
The bare minimum is two columns:
createdAt,text 2025-01-16T18:32:00Z,"Falco on Walmart radio."
The template ships with three example rows lifted from the app's bundled sample archive so you can see the spread:
Row 1 — Frankies 457 olive oil on Pasta e Piselli. Rich case: GPS, weather, activity, music (Pavarotti), nearby POI, ambient noise, a tagged person (Felix) and concepts, captured on an Apple Watch.
Row 2 — Bunched and finished so many annoying tasks today. The most. No-coordinate case: street name + city + weather only, tagged with one concept. Shows you don't need GPS to log an experience.
Row 3 — Falco on Walmart radio. The signal case: the user wrote about Falco, but the device captured September by Earth, Wind & Fire — both are preserved. This is what P.E.A. is built for.
To use it:
Open the CSV in your spreadsheet of choice.
Replace the example rows with your own. Leave any column blank if you don't have data for it — really only the date and text matter.
To tag people or concepts, type their names in those cells; separate several with semicolons ("Felix; Mara"). Don't worry about UUIDs — the conversion step mints them.
Save as CSV.
Convert CSV → P.E.A. JSON. The simplest path is to paste the CSV plus this prompt into a local LLM:
Convert this CSV into a P.E.A. archive JSON file matching the v3 schema at https://archive.green/format. - One CSV row = one entry in the "entries" array. - Generate a fresh random UUID (8-4-4-4-12 hex) for every entry. - Empty cells should be omitted from the JSON, not written as null or empty strings (except "text", which is required and may be empty). - Every row arrives unrated: set "archiveNumber": 0, "intensity": 1, "intensityLabel": "Minimum", and "isIntensityUnset": true on EVERY entry. Do NOT guess a strength — I assign it later in the app. - Use the "createdAt" cell for both "createdAt" and "updatedAt", and derive "timeOfDay", "dayOfWeek", and "season" from it. - If the "source" cell is blank, use "csv". Never "pea". - The "people" and "concepts" cells hold semicolon-separated names. For each distinct name, create one object in the top-level "people" / "concepts" array (fresh UUID, "source": "csv"), and put those UUIDs in the entry's "peopleUUIDs" / "conceptUUIDs". Reuse the same UUID when a name repeats across rows. - Wrap the entries in the top-level object with exportSchemaVersion 3, exportDate set to today (ISO 8601 with Z), appVersion "1.0", empty "places" and "mottos" arrays, and "people" / "concepts" arrays built from the columns above. Every *Count field must match the length of its array. Here is the CSV: [paste]Save the resulting JSON file and load it through Settings → Add data to P.E.A. (the non-destructive merge — "Restore P.E.A. from backup" is for your own P.E.A. exports and would replace your whole archive).
The template deliberately omits fields the converter fills for you (the uuid, the timeOfDay / dayOfWeek / season labels) and the forced intensity columns (every imported row arrives Unset, so there's nothing to pre-rate). If you need semantic places, moon phase, speed/elevation/steps, the Apple Music store ID, or an external sourceURL, edit the JSON directly after conversion — the full field reference is in the Entry Schema section above.
Restoring Safely
P.E.A. has two ways to bring data in:
Add data to P.E.A. — a non-destructive merge. New database rows are added and data that exists (matched by
uuid, or by(source, sourceID)for foreign data) are skipped. This is the case for converted imports — Swarm, Day One, CSV, or you can also add an existing P.E.A. backup to merge it with the current database set.Restore P.E.A. from backup — This replaces your current data. This is for your own P.E.A. exports (moving to a new iCloud account, recovering after a wipe). It is destructive. It only accepts archives that were exported by P.E.A..
Schema Version History
v3 — Concepts. (June 2026.)
**conceptstop-level collection** +conceptCountfield. Concepts are lightweight keyword tags ("soccer", "made", "noticed") created and linked in the entry editor's Concepts field. Native rows are stampedsource: "pea"with nosourceID.Per-entry
**conceptUUIDs** array linking each entry to one or more concepts in the top-levelconceptsarray. Many-to-many, the same shape aspeopleUUIDs.On Add data to P.E.A. (merge), concepts dedupe by
uuidor(source, sourceID); native concepts (nosourceID) dedupe byuuidonly.People graduated to the editor. People shipped at v2 as a schema-only round-trip; as of v3 they're created and linked in the entry editor's People field too — not just brought in from foreign imports. The wire format is unchanged.
Both additions are optional, so a v1 or v2 archive imports unchanged on a v3 build. A v3 archive is refused by an older build (with a "please update" message) rather than silently dropping the concept data.
v2 — Add data + Unset + People (schema-only). (May 2026.)
**peopletop-level collection** +peopleCountfield. People round-trip through export and import; the in-app UI surfaces will follow in a later release.Per-entry
**peopleUUIDs** array linking each entry to one or more people in the top-levelpeoplearray.Per-entry
**sourceID** — stable identifier from the originating system. Withsource, forms the merge-dedupe key for "Add data to P.E.A.".Per-entry
**importPayload** — the original record verbatim (stringified JSON of the source's raw row). P.E.A. doesn't read this field; it exists so a future import can reach back into fields P.E.A. didn't model the first time.Per-entry
**sourceURL** — canonical permalink back to the original record (Swarm check-in, Day One entry, tweet). Surfaces as a tappable "View external experience" row on the detail and editor screens. Native captures emit nil.Per-entry
**isIntensityUnset** flag — true on rows that arrived through merge mode from a foreign source and haven't been rated by the user yet. Renders the row's dot as solid light-gray at the smallest size everywhere it appears (distinct from the dashed-gray treatment reserved for Neutralized rows). Never present on native P.E.A. entries.All five entry-level additions are optional, so a v1 archive imports unchanged on a v2 build.
v1 — initial public schema. (May 2026.)
Single JSON file (no zip container).
Top-level
entries,places,mottoscollections.Per-row stable
uuidfor round-trip imports.Permanent per-entry
archiveNumber(the "P.E. #X" identity).Embedded
readmefield for human readability.Entry-level place fields preserved alongside the top-level
placesarray, so an individual entry is still self-describing without cross-references.Per-entry
sourceprovenance —"pea"for native captures, originating-service name for converted imports. Additive optional in v1; an archive without the field is treated as fully native ("pea"for every row).
The format version is independent of the P.E.A. app version — it's a plain integer that only changes when the archive's shape changes. P.E.A. always reads older versions. Newer files are refused with a clear message until you upgrade the app.
P.E.A. is designed to hold all your positive eperiences. That also means it's been designed to bring in any data that you would like to capture inside the archive, such as Swarm/Fourrsquare/Day 1/or anything else you'd like to add via a spreadsheet.
This data is kept absolutely private on your devices and presonal iCloud account.
What this page does
It documents the data stored in P.E.A. backup files (.json), and it provides tools and information so you can:
Read your experience data in any text editor
Move your data between iCloud accounts
Merge two archives
Bring data in from another app — by hand, script, or by using AI
Move your data into another app
The P.E.A. format is versioned and publicly accessible.
Format structure
Every P.E.A. backup/export is a single JSON file. Usually called something like:
The file contains plain text (a JSON object) that looks like this:
{ "exportSchemaVersion": 3, "exportDate": "2026-05-23T17:30:42Z", "appVersion": "1.0", "entryCount": 247, "placeCount": 12, "mottoCount": 1, "peopleCount": 18, "conceptCount": 34, "readme": "P.E.A. — Positive Experience Archive\n…", "entries": [ … ], "places": [ … ], "mottos": [ … ], "people": [ … ], "concepts": [ … ] }
Notes on the format:
**exportSchemaVersion**set the rules how P.E.A. understands your data. It won't import a file it doesn't know how to read, rather than guess.**entries**,**places**,**mottos**,**people**,**concepts**are the building blocks of data inside P.E.A. so they are independent top-level collections. Each data point for them carries a unique identifier oruuidso that a future import can recognize them again to avoid creating duplicate entries.Every entry that's associated with a saved place ALSO carries that place's data inline via
placeName,placeDetail,placeSource, etc. So you don't have to cross-reference: an entry on its own is self-describing. The top-levelplacesarray gives you the list of places as the user organized them. People and concepts work the other way around — entries reference them by uuid (peopleUUIDs,conceptUUIDs). The top-levelpeople/conceptsloops are more simple and hold all records.Optional fields are not stored when the data isn't there. Some other apps carry
null, or empty fileds. P.E.A. doesn't do this so the data is easier to read and edit.
Positive Experience Archive Schema
Every Positive Experience is one object in the entries array. Required fields are marked R.
Identity and content
Field | Type | Notes |
|---|---|---|
R | String | This number identifies each entry uniquely / Canonical UUID (8-4-4-4-12 hex). |
R | Integer | This is the number |
R | String | Text you entered or dictated about the experience. This is optional and can be empty. |
R | Integer 1–7 | Strength of the experience. 7 being life changing. |
R | String | Labels that accompany the intensity number: "Minimum", "Light", "Mild", "Medium", "Strong", "High", "Maximum". |
Time
Field | Type | Notes |
|---|---|---|
R | String (ISO 8601) | When the dot was released. |
R | String (ISO 8601) | Last time an experience entry was edited. |
R | String | "Morning", "Afternoon", "Evening", "Night". |
R | String | "Monday" through "Sunday". |
R | String | "Spring", "Summer", "Autumn", "Winter". |
Your physical location at capture
This metadata comes from the device. They are testimony, not opinion. P.E.A. doesn't edit these after capture.
Field | Type | Notes |
|---|---|---|
| Number | Raw GPS, decimal degrees. |
| Number | Raw GPS, decimal degrees. |
| String | Reverse-geocoded name ("Café Vita"). |
| String | City or town. |
| String | Country. |
A place you want to reference (semantic place)
You can assign a place. This can be because it was a place you were thinking about later, or because you wanted to correct the map pin. These saved places annotate entries without overwriting the location where the experience was captured. When you save a place, subsquent experiences close by (a hundred meters) automatcally enter the location you saved. Saved places are optional set, they mirror a row in the top-level places collection.
Field | Type | Notes |
|---|---|---|
| String | The label you chose ("home", "Mom's house"). |
| String | Locality context for that place. |
| String |
|
| Number | Place coordinate. |
| Number | Place coordinate. |
| String | Apple Maps identifier, when assigned from POI. |
| Boolean | True if you explicitly assigned this place. |
Weather and sky
Field | Type | Notes |
|---|---|---|
| String | "Clear", "Cloudy", "Rain", … |
| Number | Captured in metric; both units shown in-app. |
| String | "Full Moon", "Waxing Crescent", … |
Activity, motion, environment
Field | Type | Notes |
|---|---|---|
| String | "stationary", "walking", "running", "cycling", "driving". |
| Number | Meters per second. |
| Number | Elevation in meters. |
| Integer | Pedometer count for the day of capture. |
| Number | Ambient noise in dB. |
| String | Comma-separated nearby points of interest. |
Music
Field | Type | Notes |
|---|---|---|
| String | Song title playing at capture (Apple Music). |
| String | Artist name. |
| String | Apple Music catalog ID, for deep linking. |
Capture source device
Field | Type | Notes |
|---|---|---|
| String | "Timothy's iPhone", "Siri", "Watch" etc. |
| String | Semantic device category: |
Source provenance and identity (v2)
Which app/format originally produced the entry, plus a stable identifier from that source. The two fields together form the merge-deduplication key when you bring data in through Add data to P.E.A. — (source, sourceID) keeps re-imports from creating duplicates.
P.E.A. preserves these values forever: once an entry is stamped ("swarm", "5c1b…"), it stays that way through every future export, even if you re-import the archive on another device.
Field | Type | Notes |
|---|---|---|
| String |
|
| String | Stable identifier from the originating system. Swarm check-in id, Day One entry id, CSV row id. Omit on native PEA rows. When present, |
| String | The original record verbatim (stringified JSON of the source's raw data). With this P.E.A. can reach back into fields it didn't model. |
| String | Permalink to the original website of foreign datasets (like the Swarm check-in page, a Day One entry URL, an original tweet, etc). When this field contains a link, the app shows "View external experience" on the detail screen. |
When you're hand-converting data into P.E.A. format, set source to a short lowercase identifier, like csv. Recommended values for common sources are above. Set sourceID to the original row's stable identifier when that exists — it's how P.E.A. knows "this Swarm check-in is the same from the import last week".
Intensity state (v2)
Field | Type | Notes |
|---|---|---|
| Boolean | Only shows on rows that were brought in from a foreign source via add to P.E.A. and haven't been rated yet by the user. If this value is 1 P.E.A. renders it as a solid light-gray dot at the smallest size. (Dashed-gray is reserved for Neutralized rows; Unset is a separate state with its own solid-fill visual so the two stay distinguishable at a glance.) Native P.E.A. entries should never show this field. |
People links (v2)
Field | Type | Notes |
|---|---|---|
| Array of Strings | Unique identifier for each person so data for them can come from more than one source (Canonical UUIDs of people associated with this entry.) Each idenftifier (UUID) matches one row in the top-level |
Concept links (v3)
Field | Type | Notes |
|---|---|---|
| Array of Strings | Unique identifier (Canonical UUIDs) for each concept, like sports, travel, etc associated with an entry. Each UUID matches one row in the top-level |
Soft-deletion (Neutralization)
When you neutralize a positive experience it's removed from the list and avaible in the Index via the Neutralized filter for 30 days. After that time, or when manually triggered, neutral experiences are deleted from P.E.A.
Field | Type | Notes |
|---|---|---|
| Boolean | Present and |
| String (ISO 8601) | When you neutralized it. |
| String | Device that performed the neutralization. |
Place Schema
Places you save and reuse, eg: "Home", "Jan's Haus", "Papa's".
Every place is one object in the top-level places array. Required fields are marked R.
Field | Type | Notes |
|---|---|---|
R | String | Identifier (Canonical UUID) for places to match them across exports. |
R | String | The name you gave the place ("Home", "Jan's Haus"). |
R | String (ISO 8601) | When you saved the place. |
| String | Locality context ("Helsinki", "Seattle", "Prague"). |
| Number | Place coordinate, decimal degrees. |
| Number | Place coordinate, decimal degrees. |
| String | Apple Maps identifier, when selected from a point of interest (POI). |
Experiences reference a place with placeName / placeLatitude / placeLongitude / placeMapItemIdentifier fields inline (see Entry Schema above). A future schema version may add a foreign-key placeUUID field on entries; today the entry-level place fields are the source of truth for what's been assigned to an entry, and the top-level places array is the source of truth for the user's curated list.
On Add data to P.E.A. (merge), the system makes sure that places don't duplicate through the uuid or by content — it uses the name plus both coordinates matching at five-decimal (~1 m) precision to identify duplicates. The content match exists because the same place can carry different UUIDs on different devices (UUIDs were backfilled onto pre-existing rows independently per device), so a uuid-only check would duplicate every patch on a cross-device re-import. Places with the same name at different coordinates remain distinct — the newly imported one is renamed by adding a number like "Home 2" because place names are unique within a library (entries reference places by name).
Motto Schema
A motto is three words to you entered or picked that show first on every device to make it your own. Every motto is one object in the top-level mottos array.
Field | Type | Notes |
|---|---|---|
R | String | Identifier (Canonical UUID) for Mottos to match them across exports. |
R | String | First word/line of the motto. |
R | String | Second word/line. |
R | String | Third word/line. |
R | String (ISO 8601) | When the motto was saved. |
Mottos are gated behind a setting in the app. Importing an archive that contains mottos will enable that surface automatically so the words you brought in are visible.
On Add data to P.E.A. (merge), mottos dedupe by uuid or by exact (line1, line2, line3) content match. Mottos are typed by hand and carry no other identity, so an identical three-line match is treated as the same motto even when the UUIDs differ (which happens when UUIDs were backfilled independently on different devices).
People Schema (v2)
People you can associate with an experience: friends, family, the person you ran into at the coffee shop. You add and link them in the entry editor's People field. A foreign-source converter can also populate the same table — supply a top-level people array and reference its UUIDs from each entry's peopleUUIDs (a CSV with a people column converts cleanly this way). Note: the bundled Swarm converter creates no people, because Foursquare's downloadable export strips the companions/with list (it lives only in the live Swarm API); the only trace of who you were with is an @mention left in the shout text.
Every person is one object in the top-level people array. Required fields are marked R.
Field | Type | Notes |
|---|---|---|
R | String | Unique ID (Canonical UUID (8-4-4-4-12 hex)). Stable across imports/exports. |
R | String | Display name ("Sawa", "Murat", "Fabian"). |
R | String (ISO 8601) | When this person was first added. |
R | String (ISO 8601) | Last time this person record changed. |
| String |
|
| String | Stable identifier from the originating system, when a converter supplies one. With |
| String | URL or local-asset path for a photo. P.E.A. doesn't download or display these yet — they round-trip only. |
| String | Free-form notes the user might keep on a person. Round-trip only — P.E.A. doesn't surface them yet. |
Entries reference people by carrying a peopleUUIDs array (see Entry Schema → People links). The relationship is many-to-many: one person can appear on many entries, and one entry can list many people. On import, the link pass runs after every PeaPerson row is inserted so the entries can resolve each UUID to a live row.
People are created and linked in the entry editor — tap a suggestion or confirm a typed name. Native captures (iPhone, Mac, watch, widget, Siri) never create people; they take no people-aware input. Lists, search, and map don't yet surface people, but the editor reads and writes them, and they round-trip through every export.
On Add data to P.E.A. (merge), people dedupe by uuid or by (source, sourceID) when both are present. Native rows carry no sourceID, so they dedupe by uuid only.
P.E.A./Concept Schema (v3)
A concept is a lightweight keyword tag you attach to an experience — a vocabulary you build as you archive ("soccer", "made", "noticed", "first time"). Concepts are created and linked in the entry editor's Concepts field: tap a suggestion, a default seed, or type a new one.
Every concept is one object in the top-level concepts array. Required fields are marked R. The shape mirrors People exactly so a converter (or a reader) can treat the two the same way.
Field | Type | Notes |
|---|---|---|
R | String | Canonical UUID (8-4-4-4-12 hex). Stable across exports. |
R | String | Display label, in the user's original casing ("soccer", "First Time"). |
R | String (ISO 8601) | When this concept was first added. |
R | String (ISO 8601) | Last time this concept record changed. |
| String |
|
| String | Stable identifier from the originating system. With |
Entries reference concepts by carrying a conceptUUIDs array (see Entry Schema → Concept links). The relationship is many-to-many: one concept can appear on many entries, and one entry can list many concepts. On import, the concept link pass runs after every PeaConcept row is inserted so the entries can resolve each UUID to a live row.
On Add data to P.E.A. (merge), concepts dedupe by uuid or by (source, sourceID) when both are present. Native concepts carry no sourceID, so they dedupe by uuid only — two libraries' independently-created "made" concepts can both land.
P.E.F./One Experience example in detail
This is a native P.E.A. capture (source: "pea"), so it carries a real archiveNumber and a user-chosen intensity — exactly what an entry you made in the app looks like on the way out.
{ "exportSchemaVersion": 3, "exportDate": "2026-05-12T17:30:42Z", "appVersion": "1.0", "entryCount": 1, "placeCount": 1, "mottoCount": 0, "peopleCount": 0, "conceptCount": 0, "readme": "P.E.A. — Positive Experience Archive\n=====================================\n…", "entries": [ { "uuid": "1A2B3C4D-5E6F-7081-9202-A1B2C3D4E5F6", "archiveNumber": 412, "text": "Falco on Walmart radio.", "intensity": 2, "intensityLabel": "Light", "createdAt": "2026-01-15T18:32:00Z", "updatedAt": "2026-01-15T18:32:00Z", "timeOfDay": "Evening", "dayOfWeek": "Thursday", "season": "Winter", "latitude": 35.2271, "longitude": -80.8431, "locationName": "Walmart Supercenter", "locationLocality": "Charlotte", "locationCountry": "United States", "placeName": "the Walmart by the airport", "placeDetail": "Charlotte, NC", "placeSource": "custom", "placeLatitude": 35.2270, "placeLongitude": -80.8430, "placeMapItemIdentifier": "I3F4A2C8B9D0E1F2A3B4C5D6E7F8A9B0", "isUserConfirmedPlace": true, "weatherCondition": "Clear", "temperatureCelsius": 7, "moonPhase": "Waxing Crescent", "activityType": "walking", "speedMPS": 0.6, "altitudeMeters": 226, "stepCountToday": 6428, "noiseDecibelLevel": 68, "nearbyPOI": "Walmart Supercenter, Bojangles, ExxonMobil", "musicTrack": "REAL ONES NEVER DIE", "musicArtist": "Kid Cudi", "musicStoreID": "1886092613", "deviceName": "iPhone 15 Pro", "deviceType": "iphone", "source": "pea" } ], "places": [ { "uuid": "9F8E7D6C-5B4A-3928-1706-D5C4B3A29180", "name": "Airport Walmart", "detail": "Charlotte, NC", "latitude": 35.2270, "longitude": -80.8430, "mapItemIdentifier": "I3F4A2C8B9D0E1F2A3B4C5D6E7F8A9B0", "createdAt": "2025-12-30T14:10:00Z" } ], "mottos": [], "people": [], "concepts": [] }
What happened at this Walmart when a person archived their Experience:
Music. Even though the person was listening to Kid Cudi, Walmart radio was playing Falco. It must have piqued the person's curiosity enough to pop out the headphones and make a note. Then P.E.A. captured the music that was actually being listened to (Cudi) as well as the note the person left mentioning Falco.
Metadata comes from the device or the person. Coordinates, weather, moon phase, steps, and the song are captured from the iPhone.
placeNameis a place they saved manually. P.E.A. never edits these pieces of information after they are captured, and when a piece of data isn't available (no music playing, no place assigned), the Archive leaves it out. When you create or edit archive files, follow the same rule.Experience Intensity 2. The person dragged the marker to the light setting. Falco at Walmart is not earth shattering but it is weird and fun and something that P.E.A. was built to save.
P.E.F./Import + export
P.E.A. only imports P.E.F. format (json). But you can use any tool you like to convert your data into P.E.A. format — including a chat with an LLM.
The schema above is everything an LLM needs to bring data into the Archive. So, whether you want to move or copy your existing journal or tracking app, you can ask an LLM to rewrite the data to a single PEA-Export-….json file and then bring it in via Settings → Add data to P.E.A. — a non-destructive merge that leaves everything you already have untouched.
Imported entries arrive Unset. P.E.A. never guesses how strong a moment was. Every converted row comes in as a light-gray "Unset" dot with no P.E. # number; you assign the real strength yourself by dragging the dot in the app, and that's the moment the row earns its permanent number. So the prompts below deliberately do not ask the model to invent an intensity — they pin every row to the Unset state and let you do the rating.
Data import prompt
A note on privacy: Make sure you trust the large language model you use and review the company data policy. If the service is free, it's likely they keep your info. If you are running a local or on-device model that can't fetch URLs (Ollama, LM Studio, Apple Intelligence on-device), paste the schema sections from the top of this page into the prompt instead of leaving the link.
You are converting my journal entries into the P.E.A. archive format.
The full schema is here: https://archive.green/format
Please produce a single JSON file matching the structure exactly:
- Top-level "exportSchemaVersion" must be 3.
- Top-level keys: "exportSchemaVersion", "exportDate", "appVersion",
"entryCount", "placeCount", "mottoCount", "peopleCount",
"conceptCount", "readme", "entries", "places", "mottos", "people",
"concepts". No other top-level keys.
- "entries" gets one object per experience.
- "places" gets one object per saved place (may be []).
- "mottos" gets one object per motto (may be []).
- "people" gets one object per person (may be []).
- "concepts" gets one object per keyword tag (may be []).
- Required entry fields: uuid, archiveNumber, text, intensity,
intensityLabel, createdAt, updatedAt, timeOfDay, dayOfWeek, season.
- Required place fields: uuid, name, createdAt.
- Required motto fields: uuid, line1, line2, line3, createdAt.
- Required concept fields: uuid, name, createdAt, updatedAt.
- "uuid" must be a canonical 8-4-4-4-12 UUID string. Generate fresh
random UUIDs unless you are reusing IDs from a P.E.A. archive.
- "archiveNumber" must be 0 for every row. P.E.A. assigns the permanent
`P.E. #N` identifier the moment a user first rates a row in the
editor (Unset → any green step). Until then the app renders the row
as `P.E. #NaN`. Do NOT pre-number imported rows; doing so makes them
indistinguishable from user-rated native captures.
- Imported rows arrive UNRATED. Do NOT guess a strength from the text.
For EVERY entry set "intensity": 1, "intensityLabel": "Minimum", and
"isIntensityUnset": true. (P.E.A. forces this state on any non-"pea"
row at import time anyway, so a guessed number would just be thrown
away — and the whole point is that I rate each moment myself.)
- "source": a short lowercase tag for where the data came from
("dayone", "notes", "import", …). Never "pea" — that's reserved for
moments captured inside the app. This tag is what marks the row as
Unset on import and keeps re-imports from duplicating it.
- Use ISO 8601 for all dates with timezone (e.g. 2026-05-12T08:14:03Z).
- Derive timeOfDay/dayOfWeek/season from createdAt.
- Omit any optional field you don't have data for. Do not write null.
Here is my data:
[paste your existing journal export here]The output should be a single JSON file matching the schema above. Save it, then load it through Settings → Add data to P.E.A.
Keep your data private
With P.E.A., your data stays yours. If you are converting or importing data, that should be no different.
Recommended (on-device only):
Apple Intelligence on iOS 26 / macOS 26 — Foundation Models, Writing Tools, or the Shortcuts "Use Model" action. Runs on-device when it can; Private Cloud Compute fallback for longer inputs. Strongest non-local privacy guarantee available today (cryptographic attestation, no persistence, no operator access).
Local LLMs via Ollama, LM Studio, or any MLX-based runner. A 7B–8B model (Llama 3.1, Qwen 2.5, Gemma 3, Mistral) on a Mac with 16 GB RAM converts a year of journal entries in a few minutes — no account, no API key, no network traffic.
Hosted APIs (OpenAI, Anthropic) offer no-training and no-retention modes that work for this task, but they require trust in the vendor's policy. Make sure you set your settings for the provider not to train on your data and that you trust them.
What to avoid: Free consumer chat UIs (ChatGPT/Gemini/Claude on the web) where data-handling settings are easy to misconfigure. P.E.A. entries can include exact locations and emotional context — they are not the right thing to paste into a chat you haven't audited.
Sources we expect people to bring
Day One JSON — exports include rich timestamp + location data; LLMs convert this cleanly.
Swarm / Foursquare check-ins — coordinates and timestamps map well; intensity is your call. Worked example below.
Apple Notes / Markdown journals — text + filename dates is enough to start; everything else is optional.
Plain CSVs — works. See import template below.
If you have a method for a conversion of a popular data source, send it to Florian. He'll add it here.
Worked example — Swarm / Foursquare
First, get your data. Go to foursquare.com/settings/privacy and hit Initiate Data Download Request. Foursquare emails you a download link when the export is ready — this can take up to 72 hours. The email links a data-export-NNNN.zip; download and unzip it, and that folder of checkinsN.json files is what you feed to the converter below.
Every Swarm check-in maps to a P.E.A. entry. Imported check-ins arrive in the Unset intensity state — solid light-gray dots at the smallest size — and you assign a strength later by dragging the dot in the editor or swiping the row in the timeline.
There are two paths into P.E.A. from a Swarm/Foursquare export:
1. Run the converter script (recommended; works for any size export)
A small Python script does this conversion deterministically — no LLM, no chat-window pasting, no per-row prompt cost:
It's Python 3 stdlib only (no pip install). Open the link above, copy the whole script into a file named swarm_to_pea.py, point it at your unzipped Swarm export, and it writes a P.E.A. v2 archive next to the originals:
That writes data-export-NNNN/pea-swarm.json. Then Settings → Add data to P.E.A. in the app, pick that file, confirm Merge.
Useful flags:
--since YYYY-MM-DD— convert only check-ins on/after this local date--seed N— deterministic UUIDs for testing (not needed in normal use)
Run python3 swarm_to_pea.py --help for the full reference, including a built-in FAQ.
Imported rows are numberless. Every row arrives at archiveNumber: 0 and renders in the app as P.E. #NaN. The app assigns a permanent P.E. #N the moment you rate a row in the editor (drag the dot to any green step). Until then a check-in is a candidate, not a P.E. — clearing back to Unset later keeps the earned number; never rating a row leaves it numberless forever (and the freezer can hard-delete it after 30 days without ever burning an archive slot).
2. Convert by hand or with an LLM (good for understanding the mapping, or for tiny exports)
The rest of this section explains the mapping the script implements. If you want to roll your own converter, or you have ten check-ins and don't want to download anything, the JSON example and starter prompt below produce equivalent output.
What a real Swarm check-in looks like
Swarm exports ship as a folder of checkinsN.json files. Each one looks like this (top-level wrapper omitted; one row from the items array shown):
{ "id": "abcdef0123456789abcdef01", "createdAt": "2024-07-21 23:22:37.000000", "type": "checkin", "timeZoneOffset": -420, "lat": 47.700571, "lng": -122.378029, "venue": { "id": "...", "name": "Botanica Bar", "url": "https://app.foursquare.com/v/..." }, "shout": "Saw a friend.", "comments": { "count": 0 } }
Notes on the shape:
createdAtis a quoted string with UTC wall-clock time and microsecond resolution (YYYY-MM-DD HH:MM:SS.ffffff), not a unix timestamp.Coordinates are top-level
lat/lng, not nested undervenue. Thevenueblock carries only{id, name, url}— no city or country.shout(the user's prose) is present on a small minority of rows; most check-ins have noshoutat all.timeZoneOffsetis minutes from UTC at the time of the check-in (e.g.-420is Pacific Daylight Time).
The corresponding P.E.A. v2 entry
{ "uuid": "550e8400-e29b-41d4-a716-446655440000", "archiveNumber": 0, "text": "Saw a friend.", "intensity": 1, "intensityLabel": "Minimum", "isIntensityUnset": true, "createdAt": "2024-07-21T16:22:37-07:00", "updatedAt": "2024-07-21T16:22:37-07:00", "timeOfDay": "Afternoon", "dayOfWeek": "Sunday", "season": "Summer", "latitude": 47.700571, "longitude": -122.378029, "locationName": "Botanica Bar", "source": "swarm", "sourceID": "abcdef0123456789abcdef01", "sourceURL": "https://swarmapp.com/checkin/abcdef0123456789abcdef01", "importPayload": "{\"id\":\"abcdef0123456789abcdef01\", ... }" }
Mapping table
Swarm | P.E.A. v2 | Note |
|---|---|---|
|
| Half of the |
|
| Constructed as |
|
| Treat the string as UTC, shift by |
|
| Empty shouts are fine; |
|
| Copy verbatim. |
|
| Omitted if the check-in has no |
derived from local time |
| Compute from the local time (after the timezone shift), not from UTC. |
derived per-row |
| Fresh random UUID, one per check-in. |
constant |
| Always |
constant |
| Imported rows arrive in the Unset state. The user assigns a real strength later. The merge importer enforces this regardless of what the JSON file says. |
constant |
| Stamp every row. Lets the app tell native captures from imported ones; sticky forever in round-trips. |
raw check-in (stringified) |
| Compact JSON of the original Swarm row, embedded as a string. The app doesn't read it today; future schema versions can backfill from it. |
| — | Dropped. No P.E.A. equivalent today. Preserved inside |
Starter prompt for an LLM (small exports only)
For larger exports use the script. For ≲100 check-ins, you can paste your data into an LLM with this prompt and skip the script entirely:
Convert this Swarm/Foursquare export into a P.E.A. v2 archive JSON file. Follow the schema at https://archive.green/format. For each Swarm check-in: - uuid: generate a fresh random UUID (8-4-4-4-12 hex) - archiveNumber: 0 for every row. P.E.A. assigns the permanent P.E. #N when the user first rates a row in the app; imported rows are deliberately numberless until then. Do NOT pre-number them. - text: copy from the Swarm "shout"; use "" if blank - intensity: 1 - intensityLabel: "Minimum" - isIntensityUnset: true - createdAt, updatedAt: parse the Swarm "createdAt" as UTC, shift by "timeZoneOffset" minutes, emit ISO 8601 with the local offset (e.g. "2024-07-21T16:22:37-07:00"). Use the same value for both. - timeOfDay, dayOfWeek, season: derive from the LOCAL time (after the shift) - latitude, longitude: copy from the top-level "lat" / "lng" - locationName: copy from "venue.name" (omit if no venue) - source: "swarm" on every entry - sourceID: copy the Swarm "id" verbatim - sourceURL: build "https://swarmapp.com/checkin/<id>" (omit if no venue) - importPayload: the raw Swarm row, JSON-stringified (compact, no indent) - Omit every other optional field Top-level wrapper: { "exportSchemaVersion": 3, "readme": "Converted from Swarm.", "exportDate": "{today, ISO 8601 with Z}", "appVersion": "1.0", "entryCount": {number of entries}, "placeCount": 0, "mottoCount": 0, "peopleCount": 0, "conceptCount": 0, "entries": [ ... ]
Using the CSV template
If you have data in a spreadsheet (or you'd rather sketch your archive by hand in Numbers / Excel / Google Sheets than write JSON), grab the template:
Open the link, copy the rows into a file named PEA-Import-Template.csv, and open it in your spreadsheet of choice.
The columns are ordered by how likely you are to have the data. The first ones — the date and what you wrote — are all most entries need; then where you were, the people and concepts you want to tag, and finally the device-captured context (weather, activity, music, …) that usually only exists if you're converting from another app's export. Leave any column blank.
The bare minimum is two columns:
createdAt,text 2025-01-16T18:32:00Z,"Falco on Walmart radio."
The template ships with three example rows lifted from the app's bundled sample archive so you can see the spread:
Row 1 — Frankies 457 olive oil on Pasta e Piselli. Rich case: GPS, weather, activity, music (Pavarotti), nearby POI, ambient noise, a tagged person (Felix) and concepts, captured on an Apple Watch.
Row 2 — Bunched and finished so many annoying tasks today. The most. No-coordinate case: street name + city + weather only, tagged with one concept. Shows you don't need GPS to log an experience.
Row 3 — Falco on Walmart radio. The signal case: the user wrote about Falco, but the device captured September by Earth, Wind & Fire — both are preserved. This is what P.E.A. is built for.
To use it:
Open the CSV in your spreadsheet of choice.
Replace the example rows with your own. Leave any column blank if you don't have data for it — really only the date and text matter.
To tag people or concepts, type their names in those cells; separate several with semicolons ("Felix; Mara"). Don't worry about UUIDs — the conversion step mints them.
Save as CSV.
Convert CSV → P.E.A. JSON. The simplest path is to paste the CSV plus this prompt into a local LLM:
Convert this CSV into a P.E.A. archive JSON file matching the v3 schema at https://archive.green/format. - One CSV row = one entry in the "entries" array. - Generate a fresh random UUID (8-4-4-4-12 hex) for every entry. - Empty cells should be omitted from the JSON, not written as null or empty strings (except "text", which is required and may be empty). - Every row arrives unrated: set "archiveNumber": 0, "intensity": 1, "intensityLabel": "Minimum", and "isIntensityUnset": true on EVERY entry. Do NOT guess a strength — I assign it later in the app. - Use the "createdAt" cell for both "createdAt" and "updatedAt", and derive "timeOfDay", "dayOfWeek", and "season" from it. - If the "source" cell is blank, use "csv". Never "pea". - The "people" and "concepts" cells hold semicolon-separated names. For each distinct name, create one object in the top-level "people" / "concepts" array (fresh UUID, "source": "csv"), and put those UUIDs in the entry's "peopleUUIDs" / "conceptUUIDs". Reuse the same UUID when a name repeats across rows. - Wrap the entries in the top-level object with exportSchemaVersion 3, exportDate set to today (ISO 8601 with Z), appVersion "1.0", empty "places" and "mottos" arrays, and "people" / "concepts" arrays built from the columns above. Every *Count field must match the length of its array. Here is the CSV: [paste]Save the resulting JSON file and load it through Settings → Add data to P.E.A. (the non-destructive merge — "Restore P.E.A. from backup" is for your own P.E.A. exports and would replace your whole archive).
The template deliberately omits fields the converter fills for you (the uuid, the timeOfDay / dayOfWeek / season labels) and the forced intensity columns (every imported row arrives Unset, so there's nothing to pre-rate). If you need semantic places, moon phase, speed/elevation/steps, the Apple Music store ID, or an external sourceURL, edit the JSON directly after conversion — the full field reference is in the Entry Schema section above.
Restoring Safely
P.E.A. has two ways to bring data in:
Add data to P.E.A. — a non-destructive merge. New database rows are added and data that exists (matched by
uuid, or by(source, sourceID)for foreign data) are skipped. This is the case for converted imports — Swarm, Day One, CSV, or you can also add an existing P.E.A. backup to merge it with the current database set.Restore P.E.A. from backup — This replaces your current data. This is for your own P.E.A. exports (moving to a new iCloud account, recovering after a wipe). It is destructive. It only accepts archives that were exported by P.E.A..
Schema Version History
v3 — Concepts. (June 2026.)
**conceptstop-level collection** +conceptCountfield. Concepts are lightweight keyword tags ("soccer", "made", "noticed") created and linked in the entry editor's Concepts field. Native rows are stampedsource: "pea"with nosourceID.Per-entry
**conceptUUIDs** array linking each entry to one or more concepts in the top-levelconceptsarray. Many-to-many, the same shape aspeopleUUIDs.On Add data to P.E.A. (merge), concepts dedupe by
uuidor(source, sourceID); native concepts (nosourceID) dedupe byuuidonly.People graduated to the editor. People shipped at v2 as a schema-only round-trip; as of v3 they're created and linked in the entry editor's People field too — not just brought in from foreign imports. The wire format is unchanged.
Both additions are optional, so a v1 or v2 archive imports unchanged on a v3 build. A v3 archive is refused by an older build (with a "please update" message) rather than silently dropping the concept data.
v2 — Add data + Unset + People (schema-only). (May 2026.)
**peopletop-level collection** +peopleCountfield. People round-trip through export and import; the in-app UI surfaces will follow in a later release.Per-entry
**peopleUUIDs** array linking each entry to one or more people in the top-levelpeoplearray.Per-entry
**sourceID** — stable identifier from the originating system. Withsource, forms the merge-dedupe key for "Add data to P.E.A.".Per-entry
**importPayload** — the original record verbatim (stringified JSON of the source's raw row). P.E.A. doesn't read this field; it exists so a future import can reach back into fields P.E.A. didn't model the first time.Per-entry
**sourceURL** — canonical permalink back to the original record (Swarm check-in, Day One entry, tweet). Surfaces as a tappable "View external experience" row on the detail and editor screens. Native captures emit nil.Per-entry
**isIntensityUnset** flag — true on rows that arrived through merge mode from a foreign source and haven't been rated by the user yet. Renders the row's dot as solid light-gray at the smallest size everywhere it appears (distinct from the dashed-gray treatment reserved for Neutralized rows). Never present on native P.E.A. entries.All five entry-level additions are optional, so a v1 archive imports unchanged on a v2 build.
v1 — initial public schema. (May 2026.)
Single JSON file (no zip container).
Top-level
entries,places,mottoscollections.Per-row stable
uuidfor round-trip imports.Permanent per-entry
archiveNumber(the "P.E. #X" identity).Embedded
readmefield for human readability.Entry-level place fields preserved alongside the top-level
placesarray, so an individual entry is still self-describing without cross-references.Per-entry
sourceprovenance —"pea"for native captures, originating-service name for converted imports. Additive optional in v1; an archive without the field is treated as fully native ("pea"for every row).
The format version is independent of the P.E.A. app version — it's a plain integer that only changes when the archive's shape changes. P.E.A. always reads older versions. Newer files are refused with a clear message until you upgrade the app.
P.E.A. is designed to hold all your positive eperiences. That also means it's been designed to bring in any data that you would like to capture inside the archive, such as Swarm/Fourrsquare/Day 1/or anything else you'd like to add via a spreadsheet.
This data is kept absolutely private on your devices and presonal iCloud account.
What this page does
It documents the data stored in P.E.A. backup files (.json), and it provides tools and information so you can:
Read your experience data in any text editor
Move your data between iCloud accounts
Merge two archives
Bring data in from another app — by hand, script, or by using AI
Move your data into another app
The P.E.A. format is versioned and publicly accessible.
Format structure
Every P.E.A. backup/export is a single JSON file. Usually called something like:
The file contains plain text (a JSON object) that looks like this:
{ "exportSchemaVersion": 3, "exportDate": "2026-05-23T17:30:42Z", "appVersion": "1.0", "entryCount": 247, "placeCount": 12, "mottoCount": 1, "peopleCount": 18, "conceptCount": 34, "readme": "P.E.A. — Positive Experience Archive\n…", "entries": [ … ], "places": [ … ], "mottos": [ … ], "people": [ … ], "concepts": [ … ] }
Notes on the format:
**exportSchemaVersion**set the rules how P.E.A. understands your data. It won't import a file it doesn't know how to read, rather than guess.**entries**,**places**,**mottos**,**people**,**concepts**are the building blocks of data inside P.E.A. so they are independent top-level collections. Each data point for them carries a unique identifier oruuidso that a future import can recognize them again to avoid creating duplicate entries.Every entry that's associated with a saved place ALSO carries that place's data inline via
placeName,placeDetail,placeSource, etc. So you don't have to cross-reference: an entry on its own is self-describing. The top-levelplacesarray gives you the list of places as the user organized them. People and concepts work the other way around — entries reference them by uuid (peopleUUIDs,conceptUUIDs). The top-levelpeople/conceptsloops are more simple and hold all records.Optional fields are not stored when the data isn't there. Some other apps carry
null, or empty fileds. P.E.A. doesn't do this so the data is easier to read and edit.
Positive Experience Archive Schema
Every Positive Experience is one object in the entries array. Required fields are marked R.
Identity and content
Field | Type | Notes |
|---|---|---|
R | String | This number identifies each entry uniquely / Canonical UUID (8-4-4-4-12 hex). |
R | Integer | This is the number |
R | String | Text you entered or dictated about the experience. This is optional and can be empty. |
R | Integer 1–7 | Strength of the experience. 7 being life changing. |
R | String | Labels that accompany the intensity number: "Minimum", "Light", "Mild", "Medium", "Strong", "High", "Maximum". |
Time
Field | Type | Notes |
|---|---|---|
R | String (ISO 8601) | When the dot was released. |
R | String (ISO 8601) | Last time an experience entry was edited. |
R | String | "Morning", "Afternoon", "Evening", "Night". |
R | String | "Monday" through "Sunday". |
R | String | "Spring", "Summer", "Autumn", "Winter". |
Your physical location at capture
This metadata comes from the device. They are testimony, not opinion. P.E.A. doesn't edit these after capture.
Field | Type | Notes |
|---|---|---|
| Number | Raw GPS, decimal degrees. |
| Number | Raw GPS, decimal degrees. |
| String | Reverse-geocoded name ("Café Vita"). |
| String | City or town. |
| String | Country. |
A place you want to reference (semantic place)
You can assign a place. This can be because it was a place you were thinking about later, or because you wanted to correct the map pin. These saved places annotate entries without overwriting the location where the experience was captured. When you save a place, subsquent experiences close by (a hundred meters) automatcally enter the location you saved. Saved places are optional set, they mirror a row in the top-level places collection.
Field | Type | Notes |
|---|---|---|
| String | The label you chose ("home", "Mom's house"). |
| String | Locality context for that place. |
| String |
|
| Number | Place coordinate. |
| Number | Place coordinate. |
| String | Apple Maps identifier, when assigned from POI. |
| Boolean | True if you explicitly assigned this place. |
Weather and sky
Field | Type | Notes |
|---|---|---|
| String | "Clear", "Cloudy", "Rain", … |
| Number | Captured in metric; both units shown in-app. |
| String | "Full Moon", "Waxing Crescent", … |
Activity, motion, environment
Field | Type | Notes |
|---|---|---|
| String | "stationary", "walking", "running", "cycling", "driving". |
| Number | Meters per second. |
| Number | Elevation in meters. |
| Integer | Pedometer count for the day of capture. |
| Number | Ambient noise in dB. |
| String | Comma-separated nearby points of interest. |
Music
Field | Type | Notes |
|---|---|---|
| String | Song title playing at capture (Apple Music). |
| String | Artist name. |
| String | Apple Music catalog ID, for deep linking. |
Capture source device
Field | Type | Notes |
|---|---|---|
| String | "Timothy's iPhone", "Siri", "Watch" etc. |
| String | Semantic device category: |
Source provenance and identity (v2)
Which app/format originally produced the entry, plus a stable identifier from that source. The two fields together form the merge-deduplication key when you bring data in through Add data to P.E.A. — (source, sourceID) keeps re-imports from creating duplicates.
P.E.A. preserves these values forever: once an entry is stamped ("swarm", "5c1b…"), it stays that way through every future export, even if you re-import the archive on another device.
Field | Type | Notes |
|---|---|---|
| String |
|
| String | Stable identifier from the originating system. Swarm check-in id, Day One entry id, CSV row id. Omit on native PEA rows. When present, |
| String | The original record verbatim (stringified JSON of the source's raw data). With this P.E.A. can reach back into fields it didn't model. |
| String | Permalink to the original website of foreign datasets (like the Swarm check-in page, a Day One entry URL, an original tweet, etc). When this field contains a link, the app shows "View external experience" on the detail screen. |
When you're hand-converting data into P.E.A. format, set source to a short lowercase identifier, like csv. Recommended values for common sources are above. Set sourceID to the original row's stable identifier when that exists — it's how P.E.A. knows "this Swarm check-in is the same from the import last week".
Intensity state (v2)
Field | Type | Notes |
|---|---|---|
| Boolean | Only shows on rows that were brought in from a foreign source via add to P.E.A. and haven't been rated yet by the user. If this value is 1 P.E.A. renders it as a solid light-gray dot at the smallest size. (Dashed-gray is reserved for Neutralized rows; Unset is a separate state with its own solid-fill visual so the two stay distinguishable at a glance.) Native P.E.A. entries should never show this field. |
People links (v2)
Field | Type | Notes |
|---|---|---|
| Array of Strings | Unique identifier for each person so data for them can come from more than one source (Canonical UUIDs of people associated with this entry.) Each idenftifier (UUID) matches one row in the top-level |
Concept links (v3)
Field | Type | Notes |
|---|---|---|
| Array of Strings | Unique identifier (Canonical UUIDs) for each concept, like sports, travel, etc associated with an entry. Each UUID matches one row in the top-level |
Soft-deletion (Neutralization)
When you neutralize a positive experience it's removed from the list and avaible in the Index via the Neutralized filter for 30 days. After that time, or when manually triggered, neutral experiences are deleted from P.E.A.
Field | Type | Notes |
|---|---|---|
| Boolean | Present and |
| String (ISO 8601) | When you neutralized it. |
| String | Device that performed the neutralization. |
Place Schema
Places you save and reuse, eg: "Home", "Jan's Haus", "Papa's".
Every place is one object in the top-level places array. Required fields are marked R.
Field | Type | Notes |
|---|---|---|
R | String | Identifier (Canonical UUID) for places to match them across exports. |
R | String | The name you gave the place ("Home", "Jan's Haus"). |
R | String (ISO 8601) | When you saved the place. |
| String | Locality context ("Helsinki", "Seattle", "Prague"). |
| Number | Place coordinate, decimal degrees. |
| Number | Place coordinate, decimal degrees. |
| String | Apple Maps identifier, when selected from a point of interest (POI). |
Experiences reference a place with placeName / placeLatitude / placeLongitude / placeMapItemIdentifier fields inline (see Entry Schema above). A future schema version may add a foreign-key placeUUID field on entries; today the entry-level place fields are the source of truth for what's been assigned to an entry, and the top-level places array is the source of truth for the user's curated list.
On Add data to P.E.A. (merge), the system makes sure that places don't duplicate through the uuid or by content — it uses the name plus both coordinates matching at five-decimal (~1 m) precision to identify duplicates. The content match exists because the same place can carry different UUIDs on different devices (UUIDs were backfilled onto pre-existing rows independently per device), so a uuid-only check would duplicate every patch on a cross-device re-import. Places with the same name at different coordinates remain distinct — the newly imported one is renamed by adding a number like "Home 2" because place names are unique within a library (entries reference places by name).
Motto Schema
A motto is three words to you entered or picked that show first on every device to make it your own. Every motto is one object in the top-level mottos array.
Field | Type | Notes |
|---|---|---|
R | String | Identifier (Canonical UUID) for Mottos to match them across exports. |
R | String | First word/line of the motto. |
R | String | Second word/line. |
R | String | Third word/line. |
R | String (ISO 8601) | When the motto was saved. |
Mottos are gated behind a setting in the app. Importing an archive that contains mottos will enable that surface automatically so the words you brought in are visible.
On Add data to P.E.A. (merge), mottos dedupe by uuid or by exact (line1, line2, line3) content match. Mottos are typed by hand and carry no other identity, so an identical three-line match is treated as the same motto even when the UUIDs differ (which happens when UUIDs were backfilled independently on different devices).
People Schema (v2)
People you can associate with an experience: friends, family, the person you ran into at the coffee shop. You add and link them in the entry editor's People field. A foreign-source converter can also populate the same table — supply a top-level people array and reference its UUIDs from each entry's peopleUUIDs (a CSV with a people column converts cleanly this way). Note: the bundled Swarm converter creates no people, because Foursquare's downloadable export strips the companions/with list (it lives only in the live Swarm API); the only trace of who you were with is an @mention left in the shout text.
Every person is one object in the top-level people array. Required fields are marked R.
Field | Type | Notes |
|---|---|---|
R | String | Unique ID (Canonical UUID (8-4-4-4-12 hex)). Stable across imports/exports. |
R | String | Display name ("Sawa", "Murat", "Fabian"). |
R | String (ISO 8601) | When this person was first added. |
R | String (ISO 8601) | Last time this person record changed. |
| String |
|
| String | Stable identifier from the originating system, when a converter supplies one. With |
| String | URL or local-asset path for a photo. P.E.A. doesn't download or display these yet — they round-trip only. |
| String | Free-form notes the user might keep on a person. Round-trip only — P.E.A. doesn't surface them yet. |
Entries reference people by carrying a peopleUUIDs array (see Entry Schema → People links). The relationship is many-to-many: one person can appear on many entries, and one entry can list many people. On import, the link pass runs after every PeaPerson row is inserted so the entries can resolve each UUID to a live row.
People are created and linked in the entry editor — tap a suggestion or confirm a typed name. Native captures (iPhone, Mac, watch, widget, Siri) never create people; they take no people-aware input. Lists, search, and map don't yet surface people, but the editor reads and writes them, and they round-trip through every export.
On Add data to P.E.A. (merge), people dedupe by uuid or by (source, sourceID) when both are present. Native rows carry no sourceID, so they dedupe by uuid only.
P.E.A./Concept Schema (v3)
A concept is a lightweight keyword tag you attach to an experience — a vocabulary you build as you archive ("soccer", "made", "noticed", "first time"). Concepts are created and linked in the entry editor's Concepts field: tap a suggestion, a default seed, or type a new one.
Every concept is one object in the top-level concepts array. Required fields are marked R. The shape mirrors People exactly so a converter (or a reader) can treat the two the same way.
Field | Type | Notes |
|---|---|---|
R | String | Canonical UUID (8-4-4-4-12 hex). Stable across exports. |
R | String | Display label, in the user's original casing ("soccer", "First Time"). |
R | String (ISO 8601) | When this concept was first added. |
R | String (ISO 8601) | Last time this concept record changed. |
| String |
|
| String | Stable identifier from the originating system. With |
Entries reference concepts by carrying a conceptUUIDs array (see Entry Schema → Concept links). The relationship is many-to-many: one concept can appear on many entries, and one entry can list many concepts. On import, the concept link pass runs after every PeaConcept row is inserted so the entries can resolve each UUID to a live row.
On Add data to P.E.A. (merge), concepts dedupe by uuid or by (source, sourceID) when both are present. Native concepts carry no sourceID, so they dedupe by uuid only — two libraries' independently-created "made" concepts can both land.
P.E.F./One Experience example in detail
This is a native P.E.A. capture (source: "pea"), so it carries a real archiveNumber and a user-chosen intensity — exactly what an entry you made in the app looks like on the way out.
{ "exportSchemaVersion": 3, "exportDate": "2026-05-12T17:30:42Z", "appVersion": "1.0", "entryCount": 1, "placeCount": 1, "mottoCount": 0, "peopleCount": 0, "conceptCount": 0, "readme": "P.E.A. — Positive Experience Archive\n=====================================\n…", "entries": [ { "uuid": "1A2B3C4D-5E6F-7081-9202-A1B2C3D4E5F6", "archiveNumber": 412, "text": "Falco on Walmart radio.", "intensity": 2, "intensityLabel": "Light", "createdAt": "2026-01-15T18:32:00Z", "updatedAt": "2026-01-15T18:32:00Z", "timeOfDay": "Evening", "dayOfWeek": "Thursday", "season": "Winter", "latitude": 35.2271, "longitude": -80.8431, "locationName": "Walmart Supercenter", "locationLocality": "Charlotte", "locationCountry": "United States", "placeName": "the Walmart by the airport", "placeDetail": "Charlotte, NC", "placeSource": "custom", "placeLatitude": 35.2270, "placeLongitude": -80.8430, "placeMapItemIdentifier": "I3F4A2C8B9D0E1F2A3B4C5D6E7F8A9B0", "isUserConfirmedPlace": true, "weatherCondition": "Clear", "temperatureCelsius": 7, "moonPhase": "Waxing Crescent", "activityType": "walking", "speedMPS": 0.6, "altitudeMeters": 226, "stepCountToday": 6428, "noiseDecibelLevel": 68, "nearbyPOI": "Walmart Supercenter, Bojangles, ExxonMobil", "musicTrack": "REAL ONES NEVER DIE", "musicArtist": "Kid Cudi", "musicStoreID": "1886092613", "deviceName": "iPhone 15 Pro", "deviceType": "iphone", "source": "pea" } ], "places": [ { "uuid": "9F8E7D6C-5B4A-3928-1706-D5C4B3A29180", "name": "Airport Walmart", "detail": "Charlotte, NC", "latitude": 35.2270, "longitude": -80.8430, "mapItemIdentifier": "I3F4A2C8B9D0E1F2A3B4C5D6E7F8A9B0", "createdAt": "2025-12-30T14:10:00Z" } ], "mottos": [], "people": [], "concepts": [] }
What happened at this Walmart when a person archived their Experience:
Music. Even though the person was listening to Kid Cudi, Walmart radio was playing Falco. It must have piqued the person's curiosity enough to pop out the headphones and make a note. Then P.E.A. captured the music that was actually being listened to (Cudi) as well as the note the person left mentioning Falco.
Metadata comes from the device or the person. Coordinates, weather, moon phase, steps, and the song are captured from the iPhone.
placeNameis a place they saved manually. P.E.A. never edits these pieces of information after they are captured, and when a piece of data isn't available (no music playing, no place assigned), the Archive leaves it out. When you create or edit archive files, follow the same rule.Experience Intensity 2. The person dragged the marker to the light setting. Falco at Walmart is not earth shattering but it is weird and fun and something that P.E.A. was built to save.
P.E.F./Import + export
P.E.A. only imports P.E.F. format (json). But you can use any tool you like to convert your data into P.E.A. format — including a chat with an LLM.
The schema above is everything an LLM needs to bring data into the Archive. So, whether you want to move or copy your existing journal or tracking app, you can ask an LLM to rewrite the data to a single PEA-Export-….json file and then bring it in via Settings → Add data to P.E.A. — a non-destructive merge that leaves everything you already have untouched.
Imported entries arrive Unset. P.E.A. never guesses how strong a moment was. Every converted row comes in as a light-gray "Unset" dot with no P.E. # number; you assign the real strength yourself by dragging the dot in the app, and that's the moment the row earns its permanent number. So the prompts below deliberately do not ask the model to invent an intensity — they pin every row to the Unset state and let you do the rating.
Data import prompt
A note on privacy: Make sure you trust the large language model you use and review the company data policy. If the service is free, it's likely they keep your info. If you are running a local or on-device model that can't fetch URLs (Ollama, LM Studio, Apple Intelligence on-device), paste the schema sections from the top of this page into the prompt instead of leaving the link.
You are converting my journal entries into the P.E.A. archive format.
The full schema is here: https://archive.green/format
Please produce a single JSON file matching the structure exactly:
- Top-level "exportSchemaVersion" must be 3.
- Top-level keys: "exportSchemaVersion", "exportDate", "appVersion",
"entryCount", "placeCount", "mottoCount", "peopleCount",
"conceptCount", "readme", "entries", "places", "mottos", "people",
"concepts". No other top-level keys.
- "entries" gets one object per experience.
- "places" gets one object per saved place (may be []).
- "mottos" gets one object per motto (may be []).
- "people" gets one object per person (may be []).
- "concepts" gets one object per keyword tag (may be []).
- Required entry fields: uuid, archiveNumber, text, intensity,
intensityLabel, createdAt, updatedAt, timeOfDay, dayOfWeek, season.
- Required place fields: uuid, name, createdAt.
- Required motto fields: uuid, line1, line2, line3, createdAt.
- Required concept fields: uuid, name, createdAt, updatedAt.
- "uuid" must be a canonical 8-4-4-4-12 UUID string. Generate fresh
random UUIDs unless you are reusing IDs from a P.E.A. archive.
- "archiveNumber" must be 0 for every row. P.E.A. assigns the permanent
`P.E. #N` identifier the moment a user first rates a row in the
editor (Unset → any green step). Until then the app renders the row
as `P.E. #NaN`. Do NOT pre-number imported rows; doing so makes them
indistinguishable from user-rated native captures.
- Imported rows arrive UNRATED. Do NOT guess a strength from the text.
For EVERY entry set "intensity": 1, "intensityLabel": "Minimum", and
"isIntensityUnset": true. (P.E.A. forces this state on any non-"pea"
row at import time anyway, so a guessed number would just be thrown
away — and the whole point is that I rate each moment myself.)
- "source": a short lowercase tag for where the data came from
("dayone", "notes", "import", …). Never "pea" — that's reserved for
moments captured inside the app. This tag is what marks the row as
Unset on import and keeps re-imports from duplicating it.
- Use ISO 8601 for all dates with timezone (e.g. 2026-05-12T08:14:03Z).
- Derive timeOfDay/dayOfWeek/season from createdAt.
- Omit any optional field you don't have data for. Do not write null.
Here is my data:
[paste your existing journal export here]The output should be a single JSON file matching the schema above. Save it, then load it through Settings → Add data to P.E.A.
Keep your data private
With P.E.A., your data stays yours. If you are converting or importing data, that should be no different.
Recommended (on-device only):
Apple Intelligence on iOS 26 / macOS 26 — Foundation Models, Writing Tools, or the Shortcuts "Use Model" action. Runs on-device when it can; Private Cloud Compute fallback for longer inputs. Strongest non-local privacy guarantee available today (cryptographic attestation, no persistence, no operator access).
Local LLMs via Ollama, LM Studio, or any MLX-based runner. A 7B–8B model (Llama 3.1, Qwen 2.5, Gemma 3, Mistral) on a Mac with 16 GB RAM converts a year of journal entries in a few minutes — no account, no API key, no network traffic.
Hosted APIs (OpenAI, Anthropic) offer no-training and no-retention modes that work for this task, but they require trust in the vendor's policy. Make sure you set your settings for the provider not to train on your data and that you trust them.
What to avoid: Free consumer chat UIs (ChatGPT/Gemini/Claude on the web) where data-handling settings are easy to misconfigure. P.E.A. entries can include exact locations and emotional context — they are not the right thing to paste into a chat you haven't audited.
Sources we expect people to bring
Day One JSON — exports include rich timestamp + location data; LLMs convert this cleanly.
Swarm / Foursquare check-ins — coordinates and timestamps map well; intensity is your call. Worked example below.
Apple Notes / Markdown journals — text + filename dates is enough to start; everything else is optional.
Plain CSVs — works. See import template below.
If you have a method for a conversion of a popular data source, send it to Florian. He'll add it here.
Worked example — Swarm / Foursquare
First, get your data. Go to foursquare.com/settings/privacy and hit Initiate Data Download Request. Foursquare emails you a download link when the export is ready — this can take up to 72 hours. The email links a data-export-NNNN.zip; download and unzip it, and that folder of checkinsN.json files is what you feed to the converter below.
Every Swarm check-in maps to a P.E.A. entry. Imported check-ins arrive in the Unset intensity state — solid light-gray dots at the smallest size — and you assign a strength later by dragging the dot in the editor or swiping the row in the timeline.
There are two paths into P.E.A. from a Swarm/Foursquare export:
1. Run the converter script (recommended; works for any size export)
A small Python script does this conversion deterministically — no LLM, no chat-window pasting, no per-row prompt cost:
It's Python 3 stdlib only (no pip install). Open the link above, copy the whole script into a file named swarm_to_pea.py, point it at your unzipped Swarm export, and it writes a P.E.A. v2 archive next to the originals:
That writes data-export-NNNN/pea-swarm.json. Then Settings → Add data to P.E.A. in the app, pick that file, confirm Merge.
Useful flags:
--since YYYY-MM-DD— convert only check-ins on/after this local date--seed N— deterministic UUIDs for testing (not needed in normal use)
Run python3 swarm_to_pea.py --help for the full reference, including a built-in FAQ.
Imported rows are numberless. Every row arrives at archiveNumber: 0 and renders in the app as P.E. #NaN. The app assigns a permanent P.E. #N the moment you rate a row in the editor (drag the dot to any green step). Until then a check-in is a candidate, not a P.E. — clearing back to Unset later keeps the earned number; never rating a row leaves it numberless forever (and the freezer can hard-delete it after 30 days without ever burning an archive slot).
2. Convert by hand or with an LLM (good for understanding the mapping, or for tiny exports)
The rest of this section explains the mapping the script implements. If you want to roll your own converter, or you have ten check-ins and don't want to download anything, the JSON example and starter prompt below produce equivalent output.
What a real Swarm check-in looks like
Swarm exports ship as a folder of checkinsN.json files. Each one looks like this (top-level wrapper omitted; one row from the items array shown):
{ "id": "abcdef0123456789abcdef01", "createdAt": "2024-07-21 23:22:37.000000", "type": "checkin", "timeZoneOffset": -420, "lat": 47.700571, "lng": -122.378029, "venue": { "id": "...", "name": "Botanica Bar", "url": "https://app.foursquare.com/v/..." }, "shout": "Saw a friend.", "comments": { "count": 0 } }
Notes on the shape:
createdAtis a quoted string with UTC wall-clock time and microsecond resolution (YYYY-MM-DD HH:MM:SS.ffffff), not a unix timestamp.Coordinates are top-level
lat/lng, not nested undervenue. Thevenueblock carries only{id, name, url}— no city or country.shout(the user's prose) is present on a small minority of rows; most check-ins have noshoutat all.timeZoneOffsetis minutes from UTC at the time of the check-in (e.g.-420is Pacific Daylight Time).
The corresponding P.E.A. v2 entry
{ "uuid": "550e8400-e29b-41d4-a716-446655440000", "archiveNumber": 0, "text": "Saw a friend.", "intensity": 1, "intensityLabel": "Minimum", "isIntensityUnset": true, "createdAt": "2024-07-21T16:22:37-07:00", "updatedAt": "2024-07-21T16:22:37-07:00", "timeOfDay": "Afternoon", "dayOfWeek": "Sunday", "season": "Summer", "latitude": 47.700571, "longitude": -122.378029, "locationName": "Botanica Bar", "source": "swarm", "sourceID": "abcdef0123456789abcdef01", "sourceURL": "https://swarmapp.com/checkin/abcdef0123456789abcdef01", "importPayload": "{\"id\":\"abcdef0123456789abcdef01\", ... }" }
Mapping table
Swarm | P.E.A. v2 | Note |
|---|---|---|
|
| Half of the |
|
| Constructed as |
|
| Treat the string as UTC, shift by |
|
| Empty shouts are fine; |
|
| Copy verbatim. |
|
| Omitted if the check-in has no |
derived from local time |
| Compute from the local time (after the timezone shift), not from UTC. |
derived per-row |
| Fresh random UUID, one per check-in. |
constant |
| Always |
constant |
| Imported rows arrive in the Unset state. The user assigns a real strength later. The merge importer enforces this regardless of what the JSON file says. |
constant |
| Stamp every row. Lets the app tell native captures from imported ones; sticky forever in round-trips. |
raw check-in (stringified) |
| Compact JSON of the original Swarm row, embedded as a string. The app doesn't read it today; future schema versions can backfill from it. |
| — | Dropped. No P.E.A. equivalent today. Preserved inside |
Starter prompt for an LLM (small exports only)
For larger exports use the script. For ≲100 check-ins, you can paste your data into an LLM with this prompt and skip the script entirely:
Convert this Swarm/Foursquare export into a P.E.A. v2 archive JSON file. Follow the schema at https://archive.green/format. For each Swarm check-in: - uuid: generate a fresh random UUID (8-4-4-4-12 hex) - archiveNumber: 0 for every row. P.E.A. assigns the permanent P.E. #N when the user first rates a row in the app; imported rows are deliberately numberless until then. Do NOT pre-number them. - text: copy from the Swarm "shout"; use "" if blank - intensity: 1 - intensityLabel: "Minimum" - isIntensityUnset: true - createdAt, updatedAt: parse the Swarm "createdAt" as UTC, shift by "timeZoneOffset" minutes, emit ISO 8601 with the local offset (e.g. "2024-07-21T16:22:37-07:00"). Use the same value for both. - timeOfDay, dayOfWeek, season: derive from the LOCAL time (after the shift) - latitude, longitude: copy from the top-level "lat" / "lng" - locationName: copy from "venue.name" (omit if no venue) - source: "swarm" on every entry - sourceID: copy the Swarm "id" verbatim - sourceURL: build "https://swarmapp.com/checkin/<id>" (omit if no venue) - importPayload: the raw Swarm row, JSON-stringified (compact, no indent) - Omit every other optional field Top-level wrapper: { "exportSchemaVersion": 3, "readme": "Converted from Swarm.", "exportDate": "{today, ISO 8601 with Z}", "appVersion": "1.0", "entryCount": {number of entries}, "placeCount": 0, "mottoCount": 0, "peopleCount": 0, "conceptCount": 0, "entries": [ ... ]
Using the CSV template
If you have data in a spreadsheet (or you'd rather sketch your archive by hand in Numbers / Excel / Google Sheets than write JSON), grab the template:
Open the link, copy the rows into a file named PEA-Import-Template.csv, and open it in your spreadsheet of choice.
The columns are ordered by how likely you are to have the data. The first ones — the date and what you wrote — are all most entries need; then where you were, the people and concepts you want to tag, and finally the device-captured context (weather, activity, music, …) that usually only exists if you're converting from another app's export. Leave any column blank.
The bare minimum is two columns:
createdAt,text 2025-01-16T18:32:00Z,"Falco on Walmart radio."
The template ships with three example rows lifted from the app's bundled sample archive so you can see the spread:
Row 1 — Frankies 457 olive oil on Pasta e Piselli. Rich case: GPS, weather, activity, music (Pavarotti), nearby POI, ambient noise, a tagged person (Felix) and concepts, captured on an Apple Watch.
Row 2 — Bunched and finished so many annoying tasks today. The most. No-coordinate case: street name + city + weather only, tagged with one concept. Shows you don't need GPS to log an experience.
Row 3 — Falco on Walmart radio. The signal case: the user wrote about Falco, but the device captured September by Earth, Wind & Fire — both are preserved. This is what P.E.A. is built for.
To use it:
Open the CSV in your spreadsheet of choice.
Replace the example rows with your own. Leave any column blank if you don't have data for it — really only the date and text matter.
To tag people or concepts, type their names in those cells; separate several with semicolons ("Felix; Mara"). Don't worry about UUIDs — the conversion step mints them.
Save as CSV.
Convert CSV → P.E.A. JSON. The simplest path is to paste the CSV plus this prompt into a local LLM:
Convert this CSV into a P.E.A. archive JSON file matching the v3 schema at https://archive.green/format. - One CSV row = one entry in the "entries" array. - Generate a fresh random UUID (8-4-4-4-12 hex) for every entry. - Empty cells should be omitted from the JSON, not written as null or empty strings (except "text", which is required and may be empty). - Every row arrives unrated: set "archiveNumber": 0, "intensity": 1, "intensityLabel": "Minimum", and "isIntensityUnset": true on EVERY entry. Do NOT guess a strength — I assign it later in the app. - Use the "createdAt" cell for both "createdAt" and "updatedAt", and derive "timeOfDay", "dayOfWeek", and "season" from it. - If the "source" cell is blank, use "csv". Never "pea". - The "people" and "concepts" cells hold semicolon-separated names. For each distinct name, create one object in the top-level "people" / "concepts" array (fresh UUID, "source": "csv"), and put those UUIDs in the entry's "peopleUUIDs" / "conceptUUIDs". Reuse the same UUID when a name repeats across rows. - Wrap the entries in the top-level object with exportSchemaVersion 3, exportDate set to today (ISO 8601 with Z), appVersion "1.0", empty "places" and "mottos" arrays, and "people" / "concepts" arrays built from the columns above. Every *Count field must match the length of its array. Here is the CSV: [paste]Save the resulting JSON file and load it through Settings → Add data to P.E.A. (the non-destructive merge — "Restore P.E.A. from backup" is for your own P.E.A. exports and would replace your whole archive).
The template deliberately omits fields the converter fills for you (the uuid, the timeOfDay / dayOfWeek / season labels) and the forced intensity columns (every imported row arrives Unset, so there's nothing to pre-rate). If you need semantic places, moon phase, speed/elevation/steps, the Apple Music store ID, or an external sourceURL, edit the JSON directly after conversion — the full field reference is in the Entry Schema section above.
Restoring Safely
P.E.A. has two ways to bring data in:
Add data to P.E.A. — a non-destructive merge. New database rows are added and data that exists (matched by
uuid, or by(source, sourceID)for foreign data) are skipped. This is the case for converted imports — Swarm, Day One, CSV, or you can also add an existing P.E.A. backup to merge it with the current database set.Restore P.E.A. from backup — This replaces your current data. This is for your own P.E.A. exports (moving to a new iCloud account, recovering after a wipe). It is destructive. It only accepts archives that were exported by P.E.A..
Schema Version History
v3 — Concepts. (June 2026.)
**conceptstop-level collection** +conceptCountfield. Concepts are lightweight keyword tags ("soccer", "made", "noticed") created and linked in the entry editor's Concepts field. Native rows are stampedsource: "pea"with nosourceID.Per-entry
**conceptUUIDs** array linking each entry to one or more concepts in the top-levelconceptsarray. Many-to-many, the same shape aspeopleUUIDs.On Add data to P.E.A. (merge), concepts dedupe by
uuidor(source, sourceID); native concepts (nosourceID) dedupe byuuidonly.People graduated to the editor. People shipped at v2 as a schema-only round-trip; as of v3 they're created and linked in the entry editor's People field too — not just brought in from foreign imports. The wire format is unchanged.
Both additions are optional, so a v1 or v2 archive imports unchanged on a v3 build. A v3 archive is refused by an older build (with a "please update" message) rather than silently dropping the concept data.
v2 — Add data + Unset + People (schema-only). (May 2026.)
**peopletop-level collection** +peopleCountfield. People round-trip through export and import; the in-app UI surfaces will follow in a later release.Per-entry
**peopleUUIDs** array linking each entry to one or more people in the top-levelpeoplearray.Per-entry
**sourceID** — stable identifier from the originating system. Withsource, forms the merge-dedupe key for "Add data to P.E.A.".Per-entry
**importPayload** — the original record verbatim (stringified JSON of the source's raw row). P.E.A. doesn't read this field; it exists so a future import can reach back into fields P.E.A. didn't model the first time.Per-entry
**sourceURL** — canonical permalink back to the original record (Swarm check-in, Day One entry, tweet). Surfaces as a tappable "View external experience" row on the detail and editor screens. Native captures emit nil.Per-entry
**isIntensityUnset** flag — true on rows that arrived through merge mode from a foreign source and haven't been rated by the user yet. Renders the row's dot as solid light-gray at the smallest size everywhere it appears (distinct from the dashed-gray treatment reserved for Neutralized rows). Never present on native P.E.A. entries.All five entry-level additions are optional, so a v1 archive imports unchanged on a v2 build.
v1 — initial public schema. (May 2026.)
Single JSON file (no zip container).
Top-level
entries,places,mottoscollections.Per-row stable
uuidfor round-trip imports.Permanent per-entry
archiveNumber(the "P.E. #X" identity).Embedded
readmefield for human readability.Entry-level place fields preserved alongside the top-level
placesarray, so an individual entry is still self-describing without cross-references.Per-entry
sourceprovenance —"pea"for native captures, originating-service name for converted imports. Additive optional in v1; an archive without the field is treated as fully native ("pea"for every row).
The format version is independent of the P.E.A. app version — it's a plain integer that only changes when the archive's shape changes. P.E.A. always reads older versions. Newer files are refused with a clear message until you upgrade the app.
Questions? Found a problem?
Email Florian with questions.
Email Florian with data questions →
It’s All Good -> P.E.A.
Made by Florian
Support people, not corporations.
—David Watanabe
It’s All Good -> P.E.A.
Made by Florian
Support people, not corporations.
—David Watanabe