Positive
Experience
Archive

Positive Experience Format

P.E.A. is designed to hold all your positive experiences. That also means it's been designed to bring in any data that you would like to capture inside the archive, such as Swarm/Foursquare, Day One, or anything else you'd like to add via a spreadsheet.

This data is kept absolutely private on your devices and personal 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. This page describes format v3.6 (exportSchemaVersion 3) and was last updated on October 8, 2026. The version history is at the bottom.

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 sets the rules for how P.E.A. reads 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. (concepts holds the Topics. The app called them Concepts when the format was published, and a key in a public format never changes, so your older backups keep working.) Each data point for them carries a unique identifier or uuid so 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-level places array gives you the list of places as the user organized them. People and topics work the other way around — entries reference them by uuid (peopleUUIDs, conceptUUIDs). The top-level people / concepts lists are simpler and hold all records.

  • Optional fields are not stored when the data isn't there. Some other apps carry null or empty fields. 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 uuid

String

This number identifies each entry uniquely / Canonical UUID (8-4-4-4-12 hex).

R archiveNumber

Integer

This is the number P.E. #X you see in the header of an experience in the app. It gets set the first time you rate the experience with the green marker. Imports from other apps come in with 0, show as P.E. #NaN and render in gray on the map until you rate them. Once set (> 0) the number never changes.

R text

String

Text you entered or dictated about the experience. This is optional and can be empty.

R intensity

Integer 1–7

Strength of the experience. 7 being life changing.

R intensityLabel

String

Labels that accompany the intensity number: "Minimum", "Light", "Mild", "Medium", "Strong", "High", "Maximum".

Time

Field

Type

Notes

R createdAt

String (ISO 8601)

When the dot was released.

R updatedAt

String (ISO 8601)

Last time an experience entry was edited.

R timeOfDay

String

"Morning" (midnight to 11:59), "Afternoon" (noon to 16:59), "Evening" (17:00 to 20:59), "Night" (21:00 to 23:59), on the local clock.

R dayOfWeek

String

"Monday" through "Sunday".

R season

String

"Spring" (March to May), "Summer" (June to August), "Autumn" (September to November), "Winter" (December to February).

timeZoneID

String

The time zone the experience happened in, as an IANA name ("America/New_York", "Asia/Tokyo"). It lets the entry keep the clock of the place where it happened. When it's there, P.E.A. reads timeOfDay and dayOfWeek off that clock. Include it if your source knows the zone. If not, leave it out: P.E.A. works it out from the coordinates after import, and an entry with neither shows in the device's zone. Never guess it.

Your physical location at capture

This metadata comes from the device. They are testimony, not opinion. P.E.A. never edits a captured value. It does fill in missing ones: when an imported entry has coordinates and leaves the fields below empty, P.E.A. looks up the city, neighbourhood, county, postal code, region, country and time zone after import, and the day's weather and moon from the historical record where it exists. So bring the coordinates, leave out what you don't know, and never guess.

Field

Type

Notes

latitude

Number

Raw GPS, decimal degrees.

longitude

Number

Raw GPS, decimal degrees.

locationName

String

Reverse-geocoded name ("Café Vita").

locationLocality

String

City or town. Omitted? P.E.A. derives it from the coordinates after import.

locationAdministrativeArea

String

Region or state, exactly as the map data gives it ("WA", "Bavaria"). Same completion as the city.

locationSubLocality

String

Neighbourhood or district ("Ballard"). Same completion as the city.

locationSubAdministrativeArea

String

County, or whatever the country's equivalent is called ("King County"). Same completion as the city.

locationPostalCode

String

Postal or ZIP code ("98117"), exactly as the map data gives it. Same completion as the city.

locationCountry

String

Country. Same completion as the city.

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, later experiences within its radius (100 m unless you change it) get that place automatically. The fields below are optional. They mirror a row in the top-level places collection.

Field

Type

Notes

placeName

String

The label you chose ("home", "Mom's house").

placeDetail

String

Locality context for that place.

placeSource

String

"appleMaps", "custom", or "inferred".

placeLatitude

Number

Place coordinate.

placeLongitude

Number

Place coordinate.

placeMapItemIdentifier

String

Apple Maps identifier, when assigned from POI.

placeCategory

String

Apple Maps point-of-interest category, human-readable ("Cafe", "Amusement Park"). Only present for real POIs — plain addresses and custom places have none.

isUserConfirmedPlace

Boolean

True if you explicitly assigned this place.

Weather and sky

Omit these when your source doesn't have them: for entries with coordinates, P.E.A. recovers the hour's condition and temperature and the day's moon phase from the historical weather record after import, where coverage exists. (This lookup honors the in-app weather capture setting — users who turned weather capture off get no lookups.)

Field

Type

Notes

weatherCondition

String

"Clear", "Cloudy", "Rain", …

temperatureCelsius

Number

Captured in metric; both units shown in-app.

moonPhase

String

"Full Moon", "Waxing Crescent", …

Activity, motion, environment

Field

Type

Notes

activityType

String

"stationary", "walking", "running", "cycling", "driving".

speedMPS

Number

Meters per second.

altitudeMeters

Number

Elevation in meters.

stepCountToday

Integer

Pedometer count for the day of capture.

noiseDecibelLevel

Number

Ambient noise in dB.

nearbyPOI

String

Comma-separated nearby points of interest.

Music

Field

Type

Notes

musicTrack

String

Song title playing at capture (Apple Music).

musicArtist

String

Artist name.

musicStoreID

String

Apple Music catalog ID, for deep linking.

Capture source device

Field

Type

Notes

deviceName

String

"Timothy's iPhone", "Siri", "Watch" etc.

deviceType

String

Semantic device category: "iphone", "ipad", "mac", "watch", "vision", "tv", or "siri". Older archives may contain other device tokens; they import safely.

Source provenance and identity (v2)

Which app or format the entry came from, plus a stable identifier from that source. Together they keep re-imports from creating duplicates: when you bring data in through Add data to P.E.A., a row with the same (source, sourceID) as one already in the archive is skipped. So is a row whose createdAt and text both match an entry in the archive or an earlier row in the same file, whatever its ids. One moment recorded twice lands once, and the preview tells you before you confirm.

P.E.A. keeps these values forever. Once an entry is stamped ("swarm", "5c1b…"), it stays that way through every export, even if you re-import the archive on another device.

Field

Type

Notes

source

String

"pea" for entries captured by P.E.A. directly (iOS, macOS, watch, widget, Siri). Foreign-format imports stamp with the originating service — "swarm", "foursquare", "dayone", "csv", etc. Lowercase, no spaces.

sourceID

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, (source, sourceID) makes the entry deduplicable across re-imports.

importPayload

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.

sourceURL

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

isIntensityUnset

Boolean

Only on rows that came in from another source through Add data to P.E.A. and haven't been rated yet. When it's true, P.E.A. shows the experience as a solid light-gray dot at the smallest size. (A dashed gray dot means Neutralized, which is a different thing.) Entries made in P.E.A. never have this field.

People links (v2)

Field

Type

Notes

peopleUUIDs

Array of Strings

The UUIDs of the people associated with this experience. Each one matches one row in the top-level people collection. Leave it out when nobody is associated.

Topic links (v3)

Field

Type

Notes

conceptUUIDs

Array of Strings

The UUIDs of the topics associated with this experience (sports, travel, first time, …). Each one matches one row in the top-level concepts collection, which is where the topics live. Leave it out when no topic is associated. Many experiences to many topics, just like peopleUUIDs.

Soft-deletion (Neutralization)

When you neutralize a positive experience it's removed from the list and available in the Index via the Neutralized filter for 30 days. After that time, or when you delete it yourself, it's gone from P.E.A.

Field

Type

Notes

isNeutralized

Boolean

Present and true only for soft-deleted entries.

neutralizedAt

String (ISO 8601)

When you neutralized it.

neutralizedBy

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 uuid

String

Identifier (Canonical UUID) for places to match them across exports.

R name

String

The name you gave the place ("Home", "Jan's Haus").

R createdAt

String (ISO 8601)

When you saved the place.

detail

String

Locality context ("Helsinki", "Seattle", "Prague").

latitude

Number

Place coordinate, decimal degrees.

longitude

Number

Place coordinate, decimal degrees.

mapItemIdentifier

String

Apple Maps identifier, when selected from a point of interest (POI).

websiteURL

String

The venue's own web address ("https://www.cafevita.com").

phoneNumber

String

The venue's phone number, exactly as the listing gives it.

street

String

Street line — house number and street ("1005 E Pike St").

locality

String

The town the venue stands in ("Seattle").

administrativeArea

String

Region or state, as the map data spells it ("WA", "Bavaria").

postalCode

String

Postal code ("98122").

country

String

Country, in English ("United States").

The last seven are the place's own listing (v3.6): what Apple Maps knows about the venue, so the place card can show an address, a website and a phone number without going online. They are separate from detail, which stays the short "City, Country" line. All of them are optional. A place P.E.A. never looked up has none, and a listing that only had an address has no website or phone. Once recorded, P.E.A. doesn't update them. A listing is what the place said when you linked it, not what it says today.

Experiences carry their place inline, with placeName / placeLatitude / placeLongitude / placeMapItemIdentifier (see the Entry Schema above). The entry's own place fields say what's assigned to that experience. The top-level places array is your list of saved places. (An earlier version of this page said a future version "may add a foreign-key placeUUID". It didn't. Place identity below is what happened instead.)

On Add data to P.E.A. (merge), a place is a duplicate when the uuid matches, or when the name and both coordinates match to five decimals (about a meter). The content match is there because the same place can have different UUIDs on different devices, so a uuid-only check would double every place on a cross-device re-import. Two places with the same name at different coordinates stay two places, and both keep their name. Merge used to rename the incoming one ("Home 2"). It doesn't anymore. Names come through exactly as they are in the file, in both modes.

Place names are not unique. One archive can hold several places called "Home". Three offices of one company are three places with the same label. If you write code against this format, don't key places by name. Key by uuid, or by identityKey where it's there.

Place identity (v3.3)

A place has a stable key that doesn't depend on its name. These fields are in every build since August 2026. exportSchemaVersion stays 3, because they are all optional.

Field

On

Type

Meaning

placeIdentityKey

entries

String, optional

Which place this experience belongs to. Starts with apple: (an Apple Maps place) or local: (a place P.E.A. created).

identityKey

places

String, optional

The same key on the saved place row.

providerName

places

String, optional

What Apple Maps called the place when you linked it. P.E.A. doesn't refresh it.

radiusMeters

places

Number, optional

How far the place reaches, if you changed it. Absent means the app's default (100 m).

symbol

places

String, optional

The SF Symbol you picked for the place. Absent means the app picks one by category.

associationState

places

String, optional

Whether the place may associate itself with new experiences automatically: "notLicensed", "disabled" or "enabled". Absent means something too: the place was saved before this field existed and behaves as enabled. If you convert a file, keep it absent. Don't fill in a value.

role

places

String, optional

"home", "work" or "other". Absent means no role.

roleUpdatedAt

places

ISO-8601 string, optional

When the role was last set. Copy it as is; import doesn't re-stamp it.

If you write archive files:

  • Treat identity keys as opaque. Don't read a coordinate or an Apple ID out of them, and don't make one up. Leave the field out and P.E.A. works out the identity on import.

  • Never copy a key onto a different place. Two places with one key are one place.

  • A file without any of these fields is still a valid v3 file.

Motto Schema

A motto is three words you entered or picked that show before archiving, on every device, to make it your own. Every motto is one object in the top-level mottos array.

Field

Type

Notes

R uuid

String

Identifier (Canonical UUID) for Mottos to match them across exports.

R line1

String

First word/line of the motto.

R line2

String

Second word/line.

R line3

String

Third word/line.

R createdAt

String (ISO 8601)

When the motto was saved.

inactive

Boolean

true keeps the motto in the archive but stops showing it, on every device, without deleting it. Left out when the motto is active, which is the usual case.

Mottos are off until you turn them on in the app. Importing an archive with mottos turns them on, so the words you brought in show up.

On Add data to P.E.A. (merge), a motto is a duplicate when the uuid matches or when all three lines match exactly. Mottos are typed by hand and have no other identity, so the same three lines are the same motto even with different UUIDs (which happens across devices). inactive is a state, not an identity. It's not part of the match, and a merge never flips it on a motto you already have.

People Schema (v2)

People you can associate with an experience: friends, family, the person you ran into at the coffee shop. You add and associate them in the entry editor's People field. A converter can fill the same table: supply a top-level people array and point at its UUIDs from each entry's peopleUUIDs (a spreadsheet with a people column converts cleanly this way). One note: the Swarm converter creates no people, because Foursquare's export leaves out who you were with. The only trace is an @mention in the shout text.

Every person is one object in the top-level people array. Required fields are marked R.

Field

Type

Notes

R uuid

String

Unique ID (Canonical UUID (8-4-4-4-12 hex)). Stable across imports/exports.

R name

String

Display name ("Sawa", "Murat", "Fabian").

R createdAt

String (ISO 8601)

When this person was first added.

R updatedAt

String (ISO 8601)

Last time this person record changed.

source

String

"pea" for people added inside P.E.A., or the originating service name when imported from foreign data.

sourceID

String

Stable identifier from the originating system, when a converter supplies one. With source, forms the dedupe key. Native (editor-created) people leave it nil.

photoURL

String

URL or local-asset path for a photo. P.E.A. doesn't download or display these yet — they round-trip only.

notes

String

Free-form notes the user might keep on a person. Round-trip only — P.E.A. doesn't surface them yet.

Entries point at people with a peopleUUIDs array (see Entry Schema → People links). One person can be on many experiences, and one experience can list many people. On import, P.E.A. creates every person first and then connects the entries, so every UUID finds its row.

People are created and associated in the entry editor: tap a suggestion or confirm a typed name. Archiving on the iPhone, the Mac, the watch, the widget or through Siri never creates a person, but when the text names someone already in People, P.E.A. associates them on arrival. People have their own page under Manage, a filter in the Index and on the Map, and a grouping in the Map's list, and they round-trip through every export.

On Add data to P.E.A. (merge), a person is a duplicate when the uuid matches, or when source and sourceID both match. People made in P.E.A. have no sourceID, so for them only the uuid counts.

P.E.A./Topic Schema (v3)

A topic is a word or two that says what an experience was about: a vocabulary you build as you archive ("soccer", "made", "noticed", "first time"). People answer who, places answer where, topics answer what. You create and associate them in the entry editor's Topics field: tap a suggestion, a default seed, or type a new one.

On screen they are Topics. In the file they are concepts. The app called them Concepts until September 2026, the key was published under that name, and a key in a public format doesn't change. Your older backups keep restoring, and a converter writes concepts, conceptUUIDs and conceptCount as before.

Every topic is one object in the top-level concepts array. Required fields are marked R. The shape is the same as People, so a converter or a reader can treat the two the same way.

Field

Type

Notes

R uuid

String

Canonical UUID (8-4-4-4-12 hex). Stable across exports.

R name

String

Display label, in the user's original casing ("soccer", "First Time").

R createdAt

String (ISO 8601)

When this topic was first added.

R updatedAt

String (ISO 8601)

Last time this topic changed.

source

String

"pea" for topics created in the editor, or the name of the service the data came from.

sourceID

String

Stable identifier from the originating system. With source, forms the dedupe key. Native rows leave it nil.

Entries point at topics with a conceptUUIDs array (see Entry Schema → Topic links). One topic can be on many experiences, and one experience can list many topics. On import, P.E.A. creates every topic first and then connects the entries.

On Add data to P.E.A. (merge), a topic is a duplicate when the uuid matches, or when source and sourceID both match. Topics made in P.E.A. have no sourceID, so for them only the uuid counts. Two archives that each made a "made" topic end up with two.

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-15T23:32:00Z",
      "updatedAt": "2026-01-15T23:32:00Z",
      "timeOfDay": "Evening",
      "dayOfWeek": "Thursday",
      "season": "Winter",
      "timeZoneID": "America/New_York",
      "latitude": 35.2271,
      "longitude": -80.8431,
      "locationName": "Walmart Supercenter",
      "locationLocality": "Charlotte",
      "locationAdministrativeArea": "NC",
      "locationSubLocality": "Wesley Heights",
      "locationSubAdministrativeArea": "Mecklenburg County",
      "locationPostalCode": "28208",
      "locationCountry": "United States",
      "placeName": "the Walmart by the airport",
      "placeDetail": "Charlotte, NC",
      "placeSource": "custom",
      "placeLatitude": 35.2270,
      "placeLongitude": -80.8430,
      "placeMapItemIdentifier": "I3F4A2C8B9D0E1F2A3B4C5D6E7F8A9B0",
      "placeCategory": "Store",
      "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",
      "websiteURL": "https://www.example-store.com/charlotte",
      "phoneNumber": "(704) 555-0148",
      "street": "3850 E Independence Blvd",
      "locality": "Charlotte",
      "administrativeArea": "NC",
      "postalCode": "28205",
      "country": "United States"
    }
  ],
  "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. placeName is 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 into a single PEA-Export-….json file and then bring it in via Manage → Import & Export → Add data to P.E.A. (on the Mac, File → Add Data to P.E.A.… opens the same picker). That's a merge. Everything you already have stays 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 rate it yourself by dragging the dot in the app, and that's the moment it gets its permanent number. So the prompts below don't ask the model to invent a strength. They set every row to Unset and leave the rating to you.

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 topic (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 people fields: uuid, name, createdAt, updatedAt.
- Required topic fields (in "concepts"): 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, using the LOCAL
  time where the entry happened (not UTC).
- If the source data records the time zone an entry was written in,
  emit it as "timeZoneID" using the IANA name (e.g. "Asia/Tokyo").
  If the source only has a UTC offset or no zone at all, omit the
  field entirely — never guess a zone from an offset.
- Omit any optional field you don't have data for. Do not write null.
  Do NOT try to infer city, neighbourhood, county, postal code, region,
  country, time zone, weather, or moon from
  other fields — for entries that carry latitude/longitude, P.E.A.
  completes those from the coordinates and the historical record after
  import. Bringing accurate coordinates is worth more than guessed
  metadata.
- The same rule for a place's listing fields ("websiteURL",
  "phoneNumber", "street", "locality", "administrativeArea",
  "postalCode", "country"): write them only where the source actually
  records the venue's address or contact details. Never look one up and
  never guess one from the name — P.E.A. resolves them itself after
  import for any place that carries coordinates or an Apple Maps
  identifier.

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 Manage → Import & Export → Add data to P.E.A.

Short prompt for Siri and Apple Intelligence

Siri, Apple Intelligence and other on-device models can't open links and don't have much room. This prompt carries the rules with it, so nothing needs to be fetched. Keep it short: a dozen entries at a time on-device, more through Private Cloud Compute or a chat model. In Shortcuts, add the Use Model action, pick On-Device or Private Cloud Compute, paste the prompt with your entries, and save what comes back as a .json file. It works the same way pasted into any chat.

Turn the entries below into one P.E.A. archive file. Answer with the
JSON only.

The file is one object with exactly these keys:
"exportSchemaVersion": 3, "exportDate" (now, ISO 8601, like
"2026-10-07T09:00:00Z"), "appVersion": "1.0", "readme": "Converted
for P.E.A.", "entryCount", "placeCount": 0, "mottoCount": 0,
"peopleCount", "conceptCount", "entries", "places": [], "mottos": [],
"people", "concepts".

One object in "entries" per entry:
- "uuid": a new random UUID (8-4-4-4-12 hex), different for every entry
- "archiveNumber": 0
- "text": the entry's own words, or "" if there are none
- "intensity": 1, "intensityLabel": "Minimum", "isIntensityUnset": true
- "createdAt" and "updatedAt": the entry's date and time, ISO 8601 with
  the time zone (like "2025-04-29T20:05:00-07:00"). The same value in
  both. If only the day is known, use 12:00.
- "timeOfDay" from the local clock: "Morning" before 12:00, "Afternoon"
  from 12:00, "Evening" from 17:00, "Night" from 21:00
- "dayOfWeek": "Monday" to "Sunday"
- "season": "Spring" for March to May, "Summer" for June to August,
  "Autumn" for September to November, "Winter" for December to February
- "source": "notes"
- Only when the entry says so: "locationName" (the place's name),
  "locationLocality" (the town), "locationAdministrativeArea" (the
  state or region), "locationCountry", "latitude" and "longitude" as
  numbers. If an entry names a place, put the place's name in
  "locationName" too.

People and topics: for every person named, one object in "people" with
"uuid", "name", "createdAt", "updatedAt" (same moment as "exportDate")
and "source": "notes". Put that person's uuid in the entry's
"peopleUUIDs". Topics go the same way into "concepts" and
"conceptUUIDs". Use one uuid per name, however often it comes up.

Leave out every field you don't have. Never write null. Don't guess a
place, a time zone, the weather or a rating. "entryCount",
"peopleCount" and "conceptCount" are the lengths of their arrays.

Entries:
[paste your notes, or rows from the P.E.A. spreadsheet template]

Load the file through Manage → Import & Export → Add data to P.E.A. Every entry arrives Unset. Rate it, and it gets its number.

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.

Don't worry about how thin the venue data looks — Foursquare's export ships only the venue's name and a coordinate, no city or country. That's enough: after import, P.E.A. completes the city, neighbourhood, county, postal code, region, country, and time zone from each check-in's coordinates (and the day's weather and moon from the historical record, where available), so the entries fill out on their own.

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:

swarm_to_pea.py →

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. archive next to the originals (format v2, which P.E.A. still reads):

That writes data-export-NNNN/pea-swarm.json. Then Manage → Import & Export → 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 have no number. Every row arrives with archiveNumber: 0 and shows in the app as P.E. #NaN. The app gives it a permanent P.E. #N the moment you rate it in the editor (drag the dot to any green step). Until then a check-in is a candidate, not a P.E. Setting it back to Unset later keeps the number it got. A row you never rate stays numberless, and if you neutralize it, it's deleted after 30 days without ever using a number.

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:

  • createdAt is 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 under venue. The venue block 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 no shout at all.

  • timeZoneOffset is minutes from UTC at the time of the check-in (e.g. -420 is Pacific Daylight Time).

The corresponding P.E.A. 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.

Note

id

sourceID

Half of the (source, sourceID) dedupe key on merge import. Stable forever — exports preserve it. A check-in whose second and shout match one already archived is skipped too.

id

sourceURL

Constructed as https://swarmapp.com/checkin/<id>. The app surfaces this as a tappable "View external experience" row that deep-links into Swarm or Safari.

createdAt + timeZoneOffset

createdAt, updatedAt

Treat the string as UTC, shift by timeZoneOffset minutes, emit as ISO 8601 with the local offset (e.g. 2024-07-21T16:22:37-07:00). Same value into both.

shout (or "")

text

Empty shouts are fine; text: "" is valid.

lat, lng (top level)

latitude, longitude

Copy verbatim.

venue.name

locationName

Omitted if the check-in has no venue (off-grid check-ins exist; <1 % of typical exports).

derived from local time

timeOfDay, dayOfWeek, season

Compute from the local time (after the timezone shift), not from UTC.

derived per-row

uuid

Fresh random UUID, one per check-in.

constant

archiveNumber: 0

Always 0. P.E.A. assigns the real P.E. #N when the user first rates a row in the app — imported rows are deliberately numberless (P.E. #NaN) until then.

constant

intensity: 1, intensityLabel: "Minimum", isIntensityUnset: true

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

source: "swarm"

Stamp every row. Lets the app tell native captures from imported ones; sticky forever in round-trips.

raw check-in (stringified)

importPayload

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.

venue.url, entities, event, hacc, visibility, private, comments

—

Dropped. No P.E.A. equivalent today. Preserved inside importPayload for future revivals.

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. 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:

PEA-Import-Template.csv →

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 topics you want to associate, 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 person (Felix) and two topics, captured on an Apple Watch.

  • Row 2 — Bunched and finished so many annoying tasks today. The most. No-coordinate case: street name + city + state + weather only, with one topic. 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:

  1. Open the CSV in your spreadsheet of choice.

  2. 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.

  3. To associate people or topics, type their names in the people and topics cells. Separate several with semicolons ("Felix; Mara"). Don't worry about UUIDs, the conversion step makes them.

  4. Save as CSV.

  5. Convert CSV → P.E.A. JSON. The simplest path is to paste the CSV plus this prompt into a local LLM (the short Siri prompt above takes the rows too):

 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 "topics" 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". Topics
   go into "concepts"; that is the key's name. 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]
  1. Save the resulting JSON file and load it through Manage → Import & Export → 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 leaves out what the conversion fills in for you (the uuid, the timeOfDay / dayOfWeek / season labels) and the strength columns (every imported row arrives Unset, so there's nothing to rate in advance). If you need saved places, moon phase, speed/elevation/steps, the Apple Music store ID or a sourceURL, edit the JSON 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 merge. New rows are added. Rows that already exist (same uuid, same (source, sourceID) for imported data, or the same createdAt and text) are skipped, so one moment under two ids lands once. This is for converted imports, Swarm, Day One, a spreadsheet, and also for adding an existing P.E.A. backup to what you have.

  • Restore P.E.A. from backup — This replaces your current data. It's 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 P.E.A. exported.

Schema Version History

exportSchemaVersion is 3 and has been since June 2026. The point releases below added optional fields and changed nothing for files that don't have them. The build numbers are TestFlight builds.

  • Page revision, October 8, 2026. No format change. Concepts are called Topics in the app since September 27, 2026; the keys (concepts, conceptUUIDs, conceptCount) stay as they are. Every "not shipped yet" note below is gone, because everything shipped. New on the page: the short prompt for Siri and Apple Intelligence, and the hour bands for timeOfDay and the months for season in the Time table. The spreadsheet template's concepts column is now topics, it gained locationAdministrativeArea, and its watch row says watch.

  • v3.6 — Region, neighbourhood, county and postal code on entries, and the listing on places. (September 2026, in every build since 91.)

    • Per-entry locationAdministrativeArea: the state or region, exactly as the map data gives it: "WA" in one country, "Bavaria" in another. P.E.A. shows it next to the town ("Seattle, WA") and falls back to the two-letter country code when there is no region. There is no table that expands or shortens these, so copy your source's spelling. Leave it out when you don't know it, and P.E.A. looks it up from the coordinates after import, like the city and the country.

    • Per-entry locationSubLocality, locationSubAdministrativeArea and locationPostalCode: the neighbourhood ("Ballard"), the county or its local equivalent ("King County") and the postal code ("98117"). Same rules as the region. Nothing in the app shows these three yet. They are kept so an archive written today already has them.

    • Per-place websiteURL, phoneNumber, street, locality, administrativeArea, postalCode and country: the place's own listing, so the place card can show an address, a website and a phone number without going online. detail is unchanged and still the short "City, Country" line. Write these if your source knows the venue's address. If it only knows a coordinate, leave them out and P.E.A. looks the place up after import. Once recorded, P.E.A. doesn't overwrite them.

    • All eleven are optional. exportSchemaVersion stays 3, older files import unchanged, older builds ignore the fields.

  • v3.5 — Place association and role. (August 2026, in every build since 70.)

    • Per-place associationState: whether the place may associate itself with new experiences automatically ("notLicensed" / "disabled" / "enabled"). Absent means something: the place was saved before the field existed and behaves as enabled. Keep it absent when you convert a file.

    • Per-place role ("home" / "work" / "other") and roleUpdatedAt (ISO 8601): the place's role in your life and when you last set it. There is at most one home and one work. The timestamp is there so a sync conflict can be settled by a person. Imports copy it as is.

    • All three are optional. exportSchemaVersion stays 3.

  • v3.4 — Motto active state. (August 2026, in every build since 70.)

    • Per-motto inactive: true keeps a motto in the archive but stops showing it, on every device. Left out when the motto is active. Not part of the motto's identity, so the merge still matches by uuid or by the three lines. Optional. exportSchemaVersion stays 3.

  • v3.3 — Place identity. (July 2026, in every build since 70. See Place identity above.)

    • Per-entry placeIdentityKey: which place an experience belongs to, as a stable key. apple: for a place backed by an Apple Maps Place ID, local: for one P.E.A. created. The key, not the name, says "these two experiences happened at the same place", which is why one archive can hold several places called "Home".

    • Per-place identityKey, providerName, radiusMeters, symbol: the same key on the saved place, what Apple Maps called the place when you linked it (not refreshed), how far the place reaches in meters, and the SF Symbol you picked. No radiusMeters means the app's default. No symbol means the app picks one by category.

    • Identity keys are opaque. Don't parse one, don't make one up, don't copy one onto another place. Leave them out and P.E.A. works out identity itself.

    • All five are optional. exportSchemaVersion stays 3.

  • v3.2 — Time zone. (July 2026, in every build since 70.)

    • Per-entry timeZoneID: the IANA time zone the experience happened in ("Asia/Tokyo"), set at creation. When it's there, timeOfDay and dayOfWeek are read on that clock, so an experience archived in another zone keeps its wall clock instead of drifting with the device. Converters write it only when the source really records a zone (Day One's timeZone, for example), never from a bare UTC offset. Optional. exportSchemaVersion stays 3.

  • v3.1 — Place category. (July 2026, in every build since 70.)

    • Per-entry placeCategory: the Apple Maps point-of-interest category of an assigned place, human-readable ("Cafe", "Amusement Park"). Set when a place is picked from Apple Maps search. Plain addresses and custom places have none. Optional. exportSchemaVersion stays 3.

  • v3 — Concepts, which are Topics now. (June 2026.)

    • concepts top-level collection + conceptCount. A word or two that says what an experience was about ("soccer", "made", "noticed"), created and associated in the entry editor. Native rows are stamped source: "pea" with no sourceID. The app renamed them Topics in September 2026; the keys didn't change.

    • Per-entry conceptUUIDs array, linking each entry to its topics. Many-to-many, same shape as peopleUUIDs.

    • On Add data to P.E.A. (merge), topics match by uuid or (source, sourceID). Native topics (no sourceID) match by uuid only.

    • People reached the editor. People came in at v2 as a round trip only. From v3 you create and associate them in the entry editor's People field. The wire format is unchanged.

    • Both additions are optional, so a v1 or v2 archive imports unchanged on a v3 build. An older build refuses a v3 archive with a "please update" message rather than dropping the topics.

  • v2 — Add data + Unset + People (schema only). (May 2026.)

    • people top-level collection + peopleCount. People round-trip through export and import. The in-app surfaces came with v3.

    • Per-entry peopleUUIDs array linking each entry to people in the top-level people array.

    • Per-entry sourceID: a stable identifier from the originating system. With source, it's the merge key for "Add data to P.E.A.". The same createdAt and text also mark a duplicate.

    • Per-entry importPayload: the original record as a string. P.E.A. doesn't read it. It's there so a future import can reach back into fields P.E.A. didn't model the first time.

    • Per-entry sourceURL: a link back to the original record (Swarm check-in, Day One entry, tweet). Shows as a tappable "View external experience" row. Native captures have none.

    • Per-entry isIntensityUnset: true on rows that came in through merge from another source and haven't been rated. Shows as a solid light-gray dot at the smallest size (the dashed gray dot is Neutralized, a different thing). Never on native entries.

    • All five are optional, so a v1 archive imports unchanged on a v2 build.

  • v1 — The first public schema. (May 2026.)

    • One JSON file, no zip.

    • Top-level entries, places, mottos.

    • A stable uuid on every row, for round trips.

    • A permanent archiveNumber on every entry (the "P.E. #X").

    • A readme field, so a person can read the file.

    • Place fields on the entry next to the top-level places array, so an entry is self-describing without cross-references.

    • Per-entry source: "pea" for native captures, the service name for converted imports. Optional. A file without it is treated as all native.

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 update the app.

P.E.A. is designed to hold all your positive experiences. That also means it's been designed to bring in any data that you would like to capture inside the archive, such as Swarm/Foursquare, Day One, or anything else you'd like to add via a spreadsheet.

This data is kept absolutely private on your devices and personal 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. This page describes format v3.6 (exportSchemaVersion 3) and was last updated on October 8, 2026. The version history is at the bottom.

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 sets the rules for how P.E.A. reads 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. (concepts holds the Topics. The app called them Concepts when the format was published, and a key in a public format never changes, so your older backups keep working.) Each data point for them carries a unique identifier or uuid so 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-level places array gives you the list of places as the user organized them. People and topics work the other way around — entries reference them by uuid (peopleUUIDs, conceptUUIDs). The top-level people / concepts lists are simpler and hold all records.

  • Optional fields are not stored when the data isn't there. Some other apps carry null or empty fields. 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 uuid

String

This number identifies each entry uniquely / Canonical UUID (8-4-4-4-12 hex).

R archiveNumber

Integer

This is the number P.E. #X you see in the header of an experience in the app. It gets set the first time you rate the experience with the green marker. Imports from other apps come in with 0, show as P.E. #NaN and render in gray on the map until you rate them. Once set (> 0) the number never changes.

R text

String

Text you entered or dictated about the experience. This is optional and can be empty.

R intensity

Integer 1–7

Strength of the experience. 7 being life changing.

R intensityLabel

String

Labels that accompany the intensity number: "Minimum", "Light", "Mild", "Medium", "Strong", "High", "Maximum".

Time

Field

Type

Notes

R createdAt

String (ISO 8601)

When the dot was released.

R updatedAt

String (ISO 8601)

Last time an experience entry was edited.

R timeOfDay

String

"Morning" (midnight to 11:59), "Afternoon" (noon to 16:59), "Evening" (17:00 to 20:59), "Night" (21:00 to 23:59), on the local clock.

R dayOfWeek

String

"Monday" through "Sunday".

R season

String

"Spring" (March to May), "Summer" (June to August), "Autumn" (September to November), "Winter" (December to February).

timeZoneID

String

The time zone the experience happened in, as an IANA name ("America/New_York", "Asia/Tokyo"). It lets the entry keep the clock of the place where it happened. When it's there, P.E.A. reads timeOfDay and dayOfWeek off that clock. Include it if your source knows the zone. If not, leave it out: P.E.A. works it out from the coordinates after import, and an entry with neither shows in the device's zone. Never guess it.

Your physical location at capture

This metadata comes from the device. They are testimony, not opinion. P.E.A. never edits a captured value. It does fill in missing ones: when an imported entry has coordinates and leaves the fields below empty, P.E.A. looks up the city, neighbourhood, county, postal code, region, country and time zone after import, and the day's weather and moon from the historical record where it exists. So bring the coordinates, leave out what you don't know, and never guess.

Field

Type

Notes

latitude

Number

Raw GPS, decimal degrees.

longitude

Number

Raw GPS, decimal degrees.

locationName

String

Reverse-geocoded name ("Café Vita").

locationLocality

String

City or town. Omitted? P.E.A. derives it from the coordinates after import.

locationAdministrativeArea

String

Region or state, exactly as the map data gives it ("WA", "Bavaria"). Same completion as the city.

locationSubLocality

String

Neighbourhood or district ("Ballard"). Same completion as the city.

locationSubAdministrativeArea

String

County, or whatever the country's equivalent is called ("King County"). Same completion as the city.

locationPostalCode

String

Postal or ZIP code ("98117"), exactly as the map data gives it. Same completion as the city.

locationCountry

String

Country. Same completion as the city.

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, later experiences within its radius (100 m unless you change it) get that place automatically. The fields below are optional. They mirror a row in the top-level places collection.

Field

Type

Notes

placeName

String

The label you chose ("home", "Mom's house").

placeDetail

String

Locality context for that place.

placeSource

String

"appleMaps", "custom", or "inferred".

placeLatitude

Number

Place coordinate.

placeLongitude

Number

Place coordinate.

placeMapItemIdentifier

String

Apple Maps identifier, when assigned from POI.

placeCategory

String

Apple Maps point-of-interest category, human-readable ("Cafe", "Amusement Park"). Only present for real POIs — plain addresses and custom places have none.

isUserConfirmedPlace

Boolean

True if you explicitly assigned this place.

Weather and sky

Omit these when your source doesn't have them: for entries with coordinates, P.E.A. recovers the hour's condition and temperature and the day's moon phase from the historical weather record after import, where coverage exists. (This lookup honors the in-app weather capture setting — users who turned weather capture off get no lookups.)

Field

Type

Notes

weatherCondition

String

"Clear", "Cloudy", "Rain", …

temperatureCelsius

Number

Captured in metric; both units shown in-app.

moonPhase

String

"Full Moon", "Waxing Crescent", …

Activity, motion, environment

Field

Type

Notes

activityType

String

"stationary", "walking", "running", "cycling", "driving".

speedMPS

Number

Meters per second.

altitudeMeters

Number

Elevation in meters.

stepCountToday

Integer

Pedometer count for the day of capture.

noiseDecibelLevel

Number

Ambient noise in dB.

nearbyPOI

String

Comma-separated nearby points of interest.

Music

Field

Type

Notes

musicTrack

String

Song title playing at capture (Apple Music).

musicArtist

String

Artist name.

musicStoreID

String

Apple Music catalog ID, for deep linking.

Capture source device

Field

Type

Notes

deviceName

String

"Timothy's iPhone", "Siri", "Watch" etc.

deviceType

String

Semantic device category: "iphone", "ipad", "mac", "watch", "vision", "tv", or "siri". Older archives may contain other device tokens; they import safely.

Source provenance and identity (v2)

Which app or format the entry came from, plus a stable identifier from that source. Together they keep re-imports from creating duplicates: when you bring data in through Add data to P.E.A., a row with the same (source, sourceID) as one already in the archive is skipped. So is a row whose createdAt and text both match an entry in the archive or an earlier row in the same file, whatever its ids. One moment recorded twice lands once, and the preview tells you before you confirm.

P.E.A. keeps these values forever. Once an entry is stamped ("swarm", "5c1b…"), it stays that way through every export, even if you re-import the archive on another device.

Field

Type

Notes

source

String

"pea" for entries captured by P.E.A. directly (iOS, macOS, watch, widget, Siri). Foreign-format imports stamp with the originating service — "swarm", "foursquare", "dayone", "csv", etc. Lowercase, no spaces.

sourceID

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, (source, sourceID) makes the entry deduplicable across re-imports.

importPayload

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.

sourceURL

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

isIntensityUnset

Boolean

Only on rows that came in from another source through Add data to P.E.A. and haven't been rated yet. When it's true, P.E.A. shows the experience as a solid light-gray dot at the smallest size. (A dashed gray dot means Neutralized, which is a different thing.) Entries made in P.E.A. never have this field.

People links (v2)

Field

Type

Notes

peopleUUIDs

Array of Strings

The UUIDs of the people associated with this experience. Each one matches one row in the top-level people collection. Leave it out when nobody is associated.

Topic links (v3)

Field

Type

Notes

conceptUUIDs

Array of Strings

The UUIDs of the topics associated with this experience (sports, travel, first time, …). Each one matches one row in the top-level concepts collection, which is where the topics live. Leave it out when no topic is associated. Many experiences to many topics, just like peopleUUIDs.

Soft-deletion (Neutralization)

When you neutralize a positive experience it's removed from the list and available in the Index via the Neutralized filter for 30 days. After that time, or when you delete it yourself, it's gone from P.E.A.

Field

Type

Notes

isNeutralized

Boolean

Present and true only for soft-deleted entries.

neutralizedAt

String (ISO 8601)

When you neutralized it.

neutralizedBy

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 uuid

String

Identifier (Canonical UUID) for places to match them across exports.

R name

String

The name you gave the place ("Home", "Jan's Haus").

R createdAt

String (ISO 8601)

When you saved the place.

detail

String

Locality context ("Helsinki", "Seattle", "Prague").

latitude

Number

Place coordinate, decimal degrees.

longitude

Number

Place coordinate, decimal degrees.

mapItemIdentifier

String

Apple Maps identifier, when selected from a point of interest (POI).

websiteURL

String

The venue's own web address ("https://www.cafevita.com").

phoneNumber

String

The venue's phone number, exactly as the listing gives it.

street

String

Street line — house number and street ("1005 E Pike St").

locality

String

The town the venue stands in ("Seattle").

administrativeArea

String

Region or state, as the map data spells it ("WA", "Bavaria").

postalCode

String

Postal code ("98122").

country

String

Country, in English ("United States").

The last seven are the place's own listing (v3.6): what Apple Maps knows about the venue, so the place card can show an address, a website and a phone number without going online. They are separate from detail, which stays the short "City, Country" line. All of them are optional. A place P.E.A. never looked up has none, and a listing that only had an address has no website or phone. Once recorded, P.E.A. doesn't update them. A listing is what the place said when you linked it, not what it says today.

Experiences carry their place inline, with placeName / placeLatitude / placeLongitude / placeMapItemIdentifier (see the Entry Schema above). The entry's own place fields say what's assigned to that experience. The top-level places array is your list of saved places. (An earlier version of this page said a future version "may add a foreign-key placeUUID". It didn't. Place identity below is what happened instead.)

On Add data to P.E.A. (merge), a place is a duplicate when the uuid matches, or when the name and both coordinates match to five decimals (about a meter). The content match is there because the same place can have different UUIDs on different devices, so a uuid-only check would double every place on a cross-device re-import. Two places with the same name at different coordinates stay two places, and both keep their name. Merge used to rename the incoming one ("Home 2"). It doesn't anymore. Names come through exactly as they are in the file, in both modes.

Place names are not unique. One archive can hold several places called "Home". Three offices of one company are three places with the same label. If you write code against this format, don't key places by name. Key by uuid, or by identityKey where it's there.

Place identity (v3.3)

A place has a stable key that doesn't depend on its name. These fields are in every build since August 2026. exportSchemaVersion stays 3, because they are all optional.

Field

On

Type

Meaning

placeIdentityKey

entries

String, optional

Which place this experience belongs to. Starts with apple: (an Apple Maps place) or local: (a place P.E.A. created).

identityKey

places

String, optional

The same key on the saved place row.

providerName

places

String, optional

What Apple Maps called the place when you linked it. P.E.A. doesn't refresh it.

radiusMeters

places

Number, optional

How far the place reaches, if you changed it. Absent means the app's default (100 m).

symbol

places

String, optional

The SF Symbol you picked for the place. Absent means the app picks one by category.

associationState

places

String, optional

Whether the place may associate itself with new experiences automatically: "notLicensed", "disabled" or "enabled". Absent means something too: the place was saved before this field existed and behaves as enabled. If you convert a file, keep it absent. Don't fill in a value.

role

places

String, optional

"home", "work" or "other". Absent means no role.

roleUpdatedAt

places

ISO-8601 string, optional

When the role was last set. Copy it as is; import doesn't re-stamp it.

If you write archive files:

  • Treat identity keys as opaque. Don't read a coordinate or an Apple ID out of them, and don't make one up. Leave the field out and P.E.A. works out the identity on import.

  • Never copy a key onto a different place. Two places with one key are one place.

  • A file without any of these fields is still a valid v3 file.

Motto Schema

A motto is three words you entered or picked that show before archiving, on every device, to make it your own. Every motto is one object in the top-level mottos array.

Field

Type

Notes

R uuid

String

Identifier (Canonical UUID) for Mottos to match them across exports.

R line1

String

First word/line of the motto.

R line2

String

Second word/line.

R line3

String

Third word/line.

R createdAt

String (ISO 8601)

When the motto was saved.

inactive

Boolean

true keeps the motto in the archive but stops showing it, on every device, without deleting it. Left out when the motto is active, which is the usual case.

Mottos are off until you turn them on in the app. Importing an archive with mottos turns them on, so the words you brought in show up.

On Add data to P.E.A. (merge), a motto is a duplicate when the uuid matches or when all three lines match exactly. Mottos are typed by hand and have no other identity, so the same three lines are the same motto even with different UUIDs (which happens across devices). inactive is a state, not an identity. It's not part of the match, and a merge never flips it on a motto you already have.

People Schema (v2)

People you can associate with an experience: friends, family, the person you ran into at the coffee shop. You add and associate them in the entry editor's People field. A converter can fill the same table: supply a top-level people array and point at its UUIDs from each entry's peopleUUIDs (a spreadsheet with a people column converts cleanly this way). One note: the Swarm converter creates no people, because Foursquare's export leaves out who you were with. The only trace is an @mention in the shout text.

Every person is one object in the top-level people array. Required fields are marked R.

Field

Type

Notes

R uuid

String

Unique ID (Canonical UUID (8-4-4-4-12 hex)). Stable across imports/exports.

R name

String

Display name ("Sawa", "Murat", "Fabian").

R createdAt

String (ISO 8601)

When this person was first added.

R updatedAt

String (ISO 8601)

Last time this person record changed.

source

String

"pea" for people added inside P.E.A., or the originating service name when imported from foreign data.

sourceID

String

Stable identifier from the originating system, when a converter supplies one. With source, forms the dedupe key. Native (editor-created) people leave it nil.

photoURL

String

URL or local-asset path for a photo. P.E.A. doesn't download or display these yet — they round-trip only.

notes

String

Free-form notes the user might keep on a person. Round-trip only — P.E.A. doesn't surface them yet.

Entries point at people with a peopleUUIDs array (see Entry Schema → People links). One person can be on many experiences, and one experience can list many people. On import, P.E.A. creates every person first and then connects the entries, so every UUID finds its row.

People are created and associated in the entry editor: tap a suggestion or confirm a typed name. Archiving on the iPhone, the Mac, the watch, the widget or through Siri never creates a person, but when the text names someone already in People, P.E.A. associates them on arrival. People have their own page under Manage, a filter in the Index and on the Map, and a grouping in the Map's list, and they round-trip through every export.

On Add data to P.E.A. (merge), a person is a duplicate when the uuid matches, or when source and sourceID both match. People made in P.E.A. have no sourceID, so for them only the uuid counts.

P.E.A./Topic Schema (v3)

A topic is a word or two that says what an experience was about: a vocabulary you build as you archive ("soccer", "made", "noticed", "first time"). People answer who, places answer where, topics answer what. You create and associate them in the entry editor's Topics field: tap a suggestion, a default seed, or type a new one.

On screen they are Topics. In the file they are concepts. The app called them Concepts until September 2026, the key was published under that name, and a key in a public format doesn't change. Your older backups keep restoring, and a converter writes concepts, conceptUUIDs and conceptCount as before.

Every topic is one object in the top-level concepts array. Required fields are marked R. The shape is the same as People, so a converter or a reader can treat the two the same way.

Field

Type

Notes

R uuid

String

Canonical UUID (8-4-4-4-12 hex). Stable across exports.

R name

String

Display label, in the user's original casing ("soccer", "First Time").

R createdAt

String (ISO 8601)

When this topic was first added.

R updatedAt

String (ISO 8601)

Last time this topic changed.

source

String

"pea" for topics created in the editor, or the name of the service the data came from.

sourceID

String

Stable identifier from the originating system. With source, forms the dedupe key. Native rows leave it nil.

Entries point at topics with a conceptUUIDs array (see Entry Schema → Topic links). One topic can be on many experiences, and one experience can list many topics. On import, P.E.A. creates every topic first and then connects the entries.

On Add data to P.E.A. (merge), a topic is a duplicate when the uuid matches, or when source and sourceID both match. Topics made in P.E.A. have no sourceID, so for them only the uuid counts. Two archives that each made a "made" topic end up with two.

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-15T23:32:00Z",
      "updatedAt": "2026-01-15T23:32:00Z",
      "timeOfDay": "Evening",
      "dayOfWeek": "Thursday",
      "season": "Winter",
      "timeZoneID": "America/New_York",
      "latitude": 35.2271,
      "longitude": -80.8431,
      "locationName": "Walmart Supercenter",
      "locationLocality": "Charlotte",
      "locationAdministrativeArea": "NC",
      "locationSubLocality": "Wesley Heights",
      "locationSubAdministrativeArea": "Mecklenburg County",
      "locationPostalCode": "28208",
      "locationCountry": "United States",
      "placeName": "the Walmart by the airport",
      "placeDetail": "Charlotte, NC",
      "placeSource": "custom",
      "placeLatitude": 35.2270,
      "placeLongitude": -80.8430,
      "placeMapItemIdentifier": "I3F4A2C8B9D0E1F2A3B4C5D6E7F8A9B0",
      "placeCategory": "Store",
      "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",
      "websiteURL": "https://www.example-store.com/charlotte",
      "phoneNumber": "(704) 555-0148",
      "street": "3850 E Independence Blvd",
      "locality": "Charlotte",
      "administrativeArea": "NC",
      "postalCode": "28205",
      "country": "United States"
    }
  ],
  "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. placeName is 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 into a single PEA-Export-….json file and then bring it in via Manage → Import & Export → Add data to P.E.A. (on the Mac, File → Add Data to P.E.A.… opens the same picker). That's a merge. Everything you already have stays 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 rate it yourself by dragging the dot in the app, and that's the moment it gets its permanent number. So the prompts below don't ask the model to invent a strength. They set every row to Unset and leave the rating to you.

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 topic (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 people fields: uuid, name, createdAt, updatedAt.
- Required topic fields (in "concepts"): 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, using the LOCAL
  time where the entry happened (not UTC).
- If the source data records the time zone an entry was written in,
  emit it as "timeZoneID" using the IANA name (e.g. "Asia/Tokyo").
  If the source only has a UTC offset or no zone at all, omit the
  field entirely — never guess a zone from an offset.
- Omit any optional field you don't have data for. Do not write null.
  Do NOT try to infer city, neighbourhood, county, postal code, region,
  country, time zone, weather, or moon from
  other fields — for entries that carry latitude/longitude, P.E.A.
  completes those from the coordinates and the historical record after
  import. Bringing accurate coordinates is worth more than guessed
  metadata.
- The same rule for a place's listing fields ("websiteURL",
  "phoneNumber", "street", "locality", "administrativeArea",
  "postalCode", "country"): write them only where the source actually
  records the venue's address or contact details. Never look one up and
  never guess one from the name — P.E.A. resolves them itself after
  import for any place that carries coordinates or an Apple Maps
  identifier.

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 Manage → Import & Export → Add data to P.E.A.

Short prompt for Siri and Apple Intelligence

Siri, Apple Intelligence and other on-device models can't open links and don't have much room. This prompt carries the rules with it, so nothing needs to be fetched. Keep it short: a dozen entries at a time on-device, more through Private Cloud Compute or a chat model. In Shortcuts, add the Use Model action, pick On-Device or Private Cloud Compute, paste the prompt with your entries, and save what comes back as a .json file. It works the same way pasted into any chat.

Turn the entries below into one P.E.A. archive file. Answer with the
JSON only.

The file is one object with exactly these keys:
"exportSchemaVersion": 3, "exportDate" (now, ISO 8601, like
"2026-10-07T09:00:00Z"), "appVersion": "1.0", "readme": "Converted
for P.E.A.", "entryCount", "placeCount": 0, "mottoCount": 0,
"peopleCount", "conceptCount", "entries", "places": [], "mottos": [],
"people", "concepts".

One object in "entries" per entry:
- "uuid": a new random UUID (8-4-4-4-12 hex), different for every entry
- "archiveNumber": 0
- "text": the entry's own words, or "" if there are none
- "intensity": 1, "intensityLabel": "Minimum", "isIntensityUnset": true
- "createdAt" and "updatedAt": the entry's date and time, ISO 8601 with
  the time zone (like "2025-04-29T20:05:00-07:00"). The same value in
  both. If only the day is known, use 12:00.
- "timeOfDay" from the local clock: "Morning" before 12:00, "Afternoon"
  from 12:00, "Evening" from 17:00, "Night" from 21:00
- "dayOfWeek": "Monday" to "Sunday"
- "season": "Spring" for March to May, "Summer" for June to August,
  "Autumn" for September to November, "Winter" for December to February
- "source": "notes"
- Only when the entry says so: "locationName" (the place's name),
  "locationLocality" (the town), "locationAdministrativeArea" (the
  state or region), "locationCountry", "latitude" and "longitude" as
  numbers. If an entry names a place, put the place's name in
  "locationName" too.

People and topics: for every person named, one object in "people" with
"uuid", "name", "createdAt", "updatedAt" (same moment as "exportDate")
and "source": "notes". Put that person's uuid in the entry's
"peopleUUIDs". Topics go the same way into "concepts" and
"conceptUUIDs". Use one uuid per name, however often it comes up.

Leave out every field you don't have. Never write null. Don't guess a
place, a time zone, the weather or a rating. "entryCount",
"peopleCount" and "conceptCount" are the lengths of their arrays.

Entries:
[paste your notes, or rows from the P.E.A. spreadsheet template]

Load the file through Manage → Import & Export → Add data to P.E.A. Every entry arrives Unset. Rate it, and it gets its number.

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.

Don't worry about how thin the venue data looks — Foursquare's export ships only the venue's name and a coordinate, no city or country. That's enough: after import, P.E.A. completes the city, neighbourhood, county, postal code, region, country, and time zone from each check-in's coordinates (and the day's weather and moon from the historical record, where available), so the entries fill out on their own.

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:

swarm_to_pea.py →

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. archive next to the originals (format v2, which P.E.A. still reads):

That writes data-export-NNNN/pea-swarm.json. Then Manage → Import & Export → 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 have no number. Every row arrives with archiveNumber: 0 and shows in the app as P.E. #NaN. The app gives it a permanent P.E. #N the moment you rate it in the editor (drag the dot to any green step). Until then a check-in is a candidate, not a P.E. Setting it back to Unset later keeps the number it got. A row you never rate stays numberless, and if you neutralize it, it's deleted after 30 days without ever using a number.

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:

  • createdAt is 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 under venue. The venue block 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 no shout at all.

  • timeZoneOffset is minutes from UTC at the time of the check-in (e.g. -420 is Pacific Daylight Time).

The corresponding P.E.A. 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.

Note

id

sourceID

Half of the (source, sourceID) dedupe key on merge import. Stable forever — exports preserve it. A check-in whose second and shout match one already archived is skipped too.

id

sourceURL

Constructed as https://swarmapp.com/checkin/<id>. The app surfaces this as a tappable "View external experience" row that deep-links into Swarm or Safari.

createdAt + timeZoneOffset

createdAt, updatedAt

Treat the string as UTC, shift by timeZoneOffset minutes, emit as ISO 8601 with the local offset (e.g. 2024-07-21T16:22:37-07:00). Same value into both.

shout (or "")

text

Empty shouts are fine; text: "" is valid.

lat, lng (top level)

latitude, longitude

Copy verbatim.

venue.name

locationName

Omitted if the check-in has no venue (off-grid check-ins exist; <1 % of typical exports).

derived from local time

timeOfDay, dayOfWeek, season

Compute from the local time (after the timezone shift), not from UTC.

derived per-row

uuid

Fresh random UUID, one per check-in.

constant

archiveNumber: 0

Always 0. P.E.A. assigns the real P.E. #N when the user first rates a row in the app — imported rows are deliberately numberless (P.E. #NaN) until then.

constant

intensity: 1, intensityLabel: "Minimum", isIntensityUnset: true

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

source: "swarm"

Stamp every row. Lets the app tell native captures from imported ones; sticky forever in round-trips.

raw check-in (stringified)

importPayload

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.

venue.url, entities, event, hacc, visibility, private, comments

—

Dropped. No P.E.A. equivalent today. Preserved inside importPayload for future revivals.

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. 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:

PEA-Import-Template.csv →

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 topics you want to associate, 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 person (Felix) and two topics, captured on an Apple Watch.

  • Row 2 — Bunched and finished so many annoying tasks today. The most. No-coordinate case: street name + city + state + weather only, with one topic. 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:

  1. Open the CSV in your spreadsheet of choice.

  2. 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.

  3. To associate people or topics, type their names in the people and topics cells. Separate several with semicolons ("Felix; Mara"). Don't worry about UUIDs, the conversion step makes them.

  4. Save as CSV.

  5. Convert CSV → P.E.A. JSON. The simplest path is to paste the CSV plus this prompt into a local LLM (the short Siri prompt above takes the rows too):

 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 "topics" 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". Topics
   go into "concepts"; that is the key's name. 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]
  1. Save the resulting JSON file and load it through Manage → Import & Export → 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 leaves out what the conversion fills in for you (the uuid, the timeOfDay / dayOfWeek / season labels) and the strength columns (every imported row arrives Unset, so there's nothing to rate in advance). If you need saved places, moon phase, speed/elevation/steps, the Apple Music store ID or a sourceURL, edit the JSON 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 merge. New rows are added. Rows that already exist (same uuid, same (source, sourceID) for imported data, or the same createdAt and text) are skipped, so one moment under two ids lands once. This is for converted imports, Swarm, Day One, a spreadsheet, and also for adding an existing P.E.A. backup to what you have.

  • Restore P.E.A. from backup — This replaces your current data. It's 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 P.E.A. exported.

Schema Version History

exportSchemaVersion is 3 and has been since June 2026. The point releases below added optional fields and changed nothing for files that don't have them. The build numbers are TestFlight builds.

  • Page revision, October 8, 2026. No format change. Concepts are called Topics in the app since September 27, 2026; the keys (concepts, conceptUUIDs, conceptCount) stay as they are. Every "not shipped yet" note below is gone, because everything shipped. New on the page: the short prompt for Siri and Apple Intelligence, and the hour bands for timeOfDay and the months for season in the Time table. The spreadsheet template's concepts column is now topics, it gained locationAdministrativeArea, and its watch row says watch.

  • v3.6 — Region, neighbourhood, county and postal code on entries, and the listing on places. (September 2026, in every build since 91.)

    • Per-entry locationAdministrativeArea: the state or region, exactly as the map data gives it: "WA" in one country, "Bavaria" in another. P.E.A. shows it next to the town ("Seattle, WA") and falls back to the two-letter country code when there is no region. There is no table that expands or shortens these, so copy your source's spelling. Leave it out when you don't know it, and P.E.A. looks it up from the coordinates after import, like the city and the country.

    • Per-entry locationSubLocality, locationSubAdministrativeArea and locationPostalCode: the neighbourhood ("Ballard"), the county or its local equivalent ("King County") and the postal code ("98117"). Same rules as the region. Nothing in the app shows these three yet. They are kept so an archive written today already has them.

    • Per-place websiteURL, phoneNumber, street, locality, administrativeArea, postalCode and country: the place's own listing, so the place card can show an address, a website and a phone number without going online. detail is unchanged and still the short "City, Country" line. Write these if your source knows the venue's address. If it only knows a coordinate, leave them out and P.E.A. looks the place up after import. Once recorded, P.E.A. doesn't overwrite them.

    • All eleven are optional. exportSchemaVersion stays 3, older files import unchanged, older builds ignore the fields.

  • v3.5 — Place association and role. (August 2026, in every build since 70.)

    • Per-place associationState: whether the place may associate itself with new experiences automatically ("notLicensed" / "disabled" / "enabled"). Absent means something: the place was saved before the field existed and behaves as enabled. Keep it absent when you convert a file.

    • Per-place role ("home" / "work" / "other") and roleUpdatedAt (ISO 8601): the place's role in your life and when you last set it. There is at most one home and one work. The timestamp is there so a sync conflict can be settled by a person. Imports copy it as is.

    • All three are optional. exportSchemaVersion stays 3.

  • v3.4 — Motto active state. (August 2026, in every build since 70.)

    • Per-motto inactive: true keeps a motto in the archive but stops showing it, on every device. Left out when the motto is active. Not part of the motto's identity, so the merge still matches by uuid or by the three lines. Optional. exportSchemaVersion stays 3.

  • v3.3 — Place identity. (July 2026, in every build since 70. See Place identity above.)

    • Per-entry placeIdentityKey: which place an experience belongs to, as a stable key. apple: for a place backed by an Apple Maps Place ID, local: for one P.E.A. created. The key, not the name, says "these two experiences happened at the same place", which is why one archive can hold several places called "Home".

    • Per-place identityKey, providerName, radiusMeters, symbol: the same key on the saved place, what Apple Maps called the place when you linked it (not refreshed), how far the place reaches in meters, and the SF Symbol you picked. No radiusMeters means the app's default. No symbol means the app picks one by category.

    • Identity keys are opaque. Don't parse one, don't make one up, don't copy one onto another place. Leave them out and P.E.A. works out identity itself.

    • All five are optional. exportSchemaVersion stays 3.

  • v3.2 — Time zone. (July 2026, in every build since 70.)

    • Per-entry timeZoneID: the IANA time zone the experience happened in ("Asia/Tokyo"), set at creation. When it's there, timeOfDay and dayOfWeek are read on that clock, so an experience archived in another zone keeps its wall clock instead of drifting with the device. Converters write it only when the source really records a zone (Day One's timeZone, for example), never from a bare UTC offset. Optional. exportSchemaVersion stays 3.

  • v3.1 — Place category. (July 2026, in every build since 70.)

    • Per-entry placeCategory: the Apple Maps point-of-interest category of an assigned place, human-readable ("Cafe", "Amusement Park"). Set when a place is picked from Apple Maps search. Plain addresses and custom places have none. Optional. exportSchemaVersion stays 3.

  • v3 — Concepts, which are Topics now. (June 2026.)

    • concepts top-level collection + conceptCount. A word or two that says what an experience was about ("soccer", "made", "noticed"), created and associated in the entry editor. Native rows are stamped source: "pea" with no sourceID. The app renamed them Topics in September 2026; the keys didn't change.

    • Per-entry conceptUUIDs array, linking each entry to its topics. Many-to-many, same shape as peopleUUIDs.

    • On Add data to P.E.A. (merge), topics match by uuid or (source, sourceID). Native topics (no sourceID) match by uuid only.

    • People reached the editor. People came in at v2 as a round trip only. From v3 you create and associate them in the entry editor's People field. The wire format is unchanged.

    • Both additions are optional, so a v1 or v2 archive imports unchanged on a v3 build. An older build refuses a v3 archive with a "please update" message rather than dropping the topics.

  • v2 — Add data + Unset + People (schema only). (May 2026.)

    • people top-level collection + peopleCount. People round-trip through export and import. The in-app surfaces came with v3.

    • Per-entry peopleUUIDs array linking each entry to people in the top-level people array.

    • Per-entry sourceID: a stable identifier from the originating system. With source, it's the merge key for "Add data to P.E.A.". The same createdAt and text also mark a duplicate.

    • Per-entry importPayload: the original record as a string. P.E.A. doesn't read it. It's there so a future import can reach back into fields P.E.A. didn't model the first time.

    • Per-entry sourceURL: a link back to the original record (Swarm check-in, Day One entry, tweet). Shows as a tappable "View external experience" row. Native captures have none.

    • Per-entry isIntensityUnset: true on rows that came in through merge from another source and haven't been rated. Shows as a solid light-gray dot at the smallest size (the dashed gray dot is Neutralized, a different thing). Never on native entries.

    • All five are optional, so a v1 archive imports unchanged on a v2 build.

  • v1 — The first public schema. (May 2026.)

    • One JSON file, no zip.

    • Top-level entries, places, mottos.

    • A stable uuid on every row, for round trips.

    • A permanent archiveNumber on every entry (the "P.E. #X").

    • A readme field, so a person can read the file.

    • Place fields on the entry next to the top-level places array, so an entry is self-describing without cross-references.

    • Per-entry source: "pea" for native captures, the service name for converted imports. Optional. A file without it is treated as all native.

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 update the app.

P.E.A. is designed to hold all your positive experiences. That also means it's been designed to bring in any data that you would like to capture inside the archive, such as Swarm/Foursquare, Day One, or anything else you'd like to add via a spreadsheet.

This data is kept absolutely private on your devices and personal 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. This page describes format v3.6 (exportSchemaVersion 3) and was last updated on October 8, 2026. The version history is at the bottom.

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 sets the rules for how P.E.A. reads 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. (concepts holds the Topics. The app called them Concepts when the format was published, and a key in a public format never changes, so your older backups keep working.) Each data point for them carries a unique identifier or uuid so 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-level places array gives you the list of places as the user organized them. People and topics work the other way around — entries reference them by uuid (peopleUUIDs, conceptUUIDs). The top-level people / concepts lists are simpler and hold all records.

  • Optional fields are not stored when the data isn't there. Some other apps carry null or empty fields. 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 uuid

String

This number identifies each entry uniquely / Canonical UUID (8-4-4-4-12 hex).

R archiveNumber

Integer

This is the number P.E. #X you see in the header of an experience in the app. It gets set the first time you rate the experience with the green marker. Imports from other apps come in with 0, show as P.E. #NaN and render in gray on the map until you rate them. Once set (> 0) the number never changes.

R text

String

Text you entered or dictated about the experience. This is optional and can be empty.

R intensity

Integer 1–7

Strength of the experience. 7 being life changing.

R intensityLabel

String

Labels that accompany the intensity number: "Minimum", "Light", "Mild", "Medium", "Strong", "High", "Maximum".

Time

Field

Type

Notes

R createdAt

String (ISO 8601)

When the dot was released.

R updatedAt

String (ISO 8601)

Last time an experience entry was edited.

R timeOfDay

String

"Morning" (midnight to 11:59), "Afternoon" (noon to 16:59), "Evening" (17:00 to 20:59), "Night" (21:00 to 23:59), on the local clock.

R dayOfWeek

String

"Monday" through "Sunday".

R season

String

"Spring" (March to May), "Summer" (June to August), "Autumn" (September to November), "Winter" (December to February).

timeZoneID

String

The time zone the experience happened in, as an IANA name ("America/New_York", "Asia/Tokyo"). It lets the entry keep the clock of the place where it happened. When it's there, P.E.A. reads timeOfDay and dayOfWeek off that clock. Include it if your source knows the zone. If not, leave it out: P.E.A. works it out from the coordinates after import, and an entry with neither shows in the device's zone. Never guess it.

Your physical location at capture

This metadata comes from the device. They are testimony, not opinion. P.E.A. never edits a captured value. It does fill in missing ones: when an imported entry has coordinates and leaves the fields below empty, P.E.A. looks up the city, neighbourhood, county, postal code, region, country and time zone after import, and the day's weather and moon from the historical record where it exists. So bring the coordinates, leave out what you don't know, and never guess.

Field

Type

Notes

latitude

Number

Raw GPS, decimal degrees.

longitude

Number

Raw GPS, decimal degrees.

locationName

String

Reverse-geocoded name ("Café Vita").

locationLocality

String

City or town. Omitted? P.E.A. derives it from the coordinates after import.

locationAdministrativeArea

String

Region or state, exactly as the map data gives it ("WA", "Bavaria"). Same completion as the city.

locationSubLocality

String

Neighbourhood or district ("Ballard"). Same completion as the city.

locationSubAdministrativeArea

String

County, or whatever the country's equivalent is called ("King County"). Same completion as the city.

locationPostalCode

String

Postal or ZIP code ("98117"), exactly as the map data gives it. Same completion as the city.

locationCountry

String

Country. Same completion as the city.

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, later experiences within its radius (100 m unless you change it) get that place automatically. The fields below are optional. They mirror a row in the top-level places collection.

Field

Type

Notes

placeName

String

The label you chose ("home", "Mom's house").

placeDetail

String

Locality context for that place.

placeSource

String

"appleMaps", "custom", or "inferred".

placeLatitude

Number

Place coordinate.

placeLongitude

Number

Place coordinate.

placeMapItemIdentifier

String

Apple Maps identifier, when assigned from POI.

placeCategory

String

Apple Maps point-of-interest category, human-readable ("Cafe", "Amusement Park"). Only present for real POIs — plain addresses and custom places have none.

isUserConfirmedPlace

Boolean

True if you explicitly assigned this place.

Weather and sky

Omit these when your source doesn't have them: for entries with coordinates, P.E.A. recovers the hour's condition and temperature and the day's moon phase from the historical weather record after import, where coverage exists. (This lookup honors the in-app weather capture setting — users who turned weather capture off get no lookups.)

Field

Type

Notes

weatherCondition

String

"Clear", "Cloudy", "Rain", …

temperatureCelsius

Number

Captured in metric; both units shown in-app.

moonPhase

String

"Full Moon", "Waxing Crescent", …

Activity, motion, environment

Field

Type

Notes

activityType

String

"stationary", "walking", "running", "cycling", "driving".

speedMPS

Number

Meters per second.

altitudeMeters

Number

Elevation in meters.

stepCountToday

Integer

Pedometer count for the day of capture.

noiseDecibelLevel

Number

Ambient noise in dB.

nearbyPOI

String

Comma-separated nearby points of interest.

Music

Field

Type

Notes

musicTrack

String

Song title playing at capture (Apple Music).

musicArtist

String

Artist name.

musicStoreID

String

Apple Music catalog ID, for deep linking.

Capture source device

Field

Type

Notes

deviceName

String

"Timothy's iPhone", "Siri", "Watch" etc.

deviceType

String

Semantic device category: "iphone", "ipad", "mac", "watch", "vision", "tv", or "siri". Older archives may contain other device tokens; they import safely.

Source provenance and identity (v2)

Which app or format the entry came from, plus a stable identifier from that source. Together they keep re-imports from creating duplicates: when you bring data in through Add data to P.E.A., a row with the same (source, sourceID) as one already in the archive is skipped. So is a row whose createdAt and text both match an entry in the archive or an earlier row in the same file, whatever its ids. One moment recorded twice lands once, and the preview tells you before you confirm.

P.E.A. keeps these values forever. Once an entry is stamped ("swarm", "5c1b…"), it stays that way through every export, even if you re-import the archive on another device.

Field

Type

Notes

source

String

"pea" for entries captured by P.E.A. directly (iOS, macOS, watch, widget, Siri). Foreign-format imports stamp with the originating service — "swarm", "foursquare", "dayone", "csv", etc. Lowercase, no spaces.

sourceID

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, (source, sourceID) makes the entry deduplicable across re-imports.

importPayload

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.

sourceURL

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

isIntensityUnset

Boolean

Only on rows that came in from another source through Add data to P.E.A. and haven't been rated yet. When it's true, P.E.A. shows the experience as a solid light-gray dot at the smallest size. (A dashed gray dot means Neutralized, which is a different thing.) Entries made in P.E.A. never have this field.

People links (v2)

Field

Type

Notes

peopleUUIDs

Array of Strings

The UUIDs of the people associated with this experience. Each one matches one row in the top-level people collection. Leave it out when nobody is associated.

Topic links (v3)

Field

Type

Notes

conceptUUIDs

Array of Strings

The UUIDs of the topics associated with this experience (sports, travel, first time, …). Each one matches one row in the top-level concepts collection, which is where the topics live. Leave it out when no topic is associated. Many experiences to many topics, just like peopleUUIDs.

Soft-deletion (Neutralization)

When you neutralize a positive experience it's removed from the list and available in the Index via the Neutralized filter for 30 days. After that time, or when you delete it yourself, it's gone from P.E.A.

Field

Type

Notes

isNeutralized

Boolean

Present and true only for soft-deleted entries.

neutralizedAt

String (ISO 8601)

When you neutralized it.

neutralizedBy

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 uuid

String

Identifier (Canonical UUID) for places to match them across exports.

R name

String

The name you gave the place ("Home", "Jan's Haus").

R createdAt

String (ISO 8601)

When you saved the place.

detail

String

Locality context ("Helsinki", "Seattle", "Prague").

latitude

Number

Place coordinate, decimal degrees.

longitude

Number

Place coordinate, decimal degrees.

mapItemIdentifier

String

Apple Maps identifier, when selected from a point of interest (POI).

websiteURL

String

The venue's own web address ("https://www.cafevita.com").

phoneNumber

String

The venue's phone number, exactly as the listing gives it.

street

String

Street line — house number and street ("1005 E Pike St").

locality

String

The town the venue stands in ("Seattle").

administrativeArea

String

Region or state, as the map data spells it ("WA", "Bavaria").

postalCode

String

Postal code ("98122").

country

String

Country, in English ("United States").

The last seven are the place's own listing (v3.6): what Apple Maps knows about the venue, so the place card can show an address, a website and a phone number without going online. They are separate from detail, which stays the short "City, Country" line. All of them are optional. A place P.E.A. never looked up has none, and a listing that only had an address has no website or phone. Once recorded, P.E.A. doesn't update them. A listing is what the place said when you linked it, not what it says today.

Experiences carry their place inline, with placeName / placeLatitude / placeLongitude / placeMapItemIdentifier (see the Entry Schema above). The entry's own place fields say what's assigned to that experience. The top-level places array is your list of saved places. (An earlier version of this page said a future version "may add a foreign-key placeUUID". It didn't. Place identity below is what happened instead.)

On Add data to P.E.A. (merge), a place is a duplicate when the uuid matches, or when the name and both coordinates match to five decimals (about a meter). The content match is there because the same place can have different UUIDs on different devices, so a uuid-only check would double every place on a cross-device re-import. Two places with the same name at different coordinates stay two places, and both keep their name. Merge used to rename the incoming one ("Home 2"). It doesn't anymore. Names come through exactly as they are in the file, in both modes.

Place names are not unique. One archive can hold several places called "Home". Three offices of one company are three places with the same label. If you write code against this format, don't key places by name. Key by uuid, or by identityKey where it's there.

Place identity (v3.3)

A place has a stable key that doesn't depend on its name. These fields are in every build since August 2026. exportSchemaVersion stays 3, because they are all optional.

Field

On

Type

Meaning

placeIdentityKey

entries

String, optional

Which place this experience belongs to. Starts with apple: (an Apple Maps place) or local: (a place P.E.A. created).

identityKey

places

String, optional

The same key on the saved place row.

providerName

places

String, optional

What Apple Maps called the place when you linked it. P.E.A. doesn't refresh it.

radiusMeters

places

Number, optional

How far the place reaches, if you changed it. Absent means the app's default (100 m).

symbol

places

String, optional

The SF Symbol you picked for the place. Absent means the app picks one by category.

associationState

places

String, optional

Whether the place may associate itself with new experiences automatically: "notLicensed", "disabled" or "enabled". Absent means something too: the place was saved before this field existed and behaves as enabled. If you convert a file, keep it absent. Don't fill in a value.

role

places

String, optional

"home", "work" or "other". Absent means no role.

roleUpdatedAt

places

ISO-8601 string, optional

When the role was last set. Copy it as is; import doesn't re-stamp it.

If you write archive files:

  • Treat identity keys as opaque. Don't read a coordinate or an Apple ID out of them, and don't make one up. Leave the field out and P.E.A. works out the identity on import.

  • Never copy a key onto a different place. Two places with one key are one place.

  • A file without any of these fields is still a valid v3 file.

Motto Schema

A motto is three words you entered or picked that show before archiving, on every device, to make it your own. Every motto is one object in the top-level mottos array.

Field

Type

Notes

R uuid

String

Identifier (Canonical UUID) for Mottos to match them across exports.

R line1

String

First word/line of the motto.

R line2

String

Second word/line.

R line3

String

Third word/line.

R createdAt

String (ISO 8601)

When the motto was saved.

inactive

Boolean

true keeps the motto in the archive but stops showing it, on every device, without deleting it. Left out when the motto is active, which is the usual case.

Mottos are off until you turn them on in the app. Importing an archive with mottos turns them on, so the words you brought in show up.

On Add data to P.E.A. (merge), a motto is a duplicate when the uuid matches or when all three lines match exactly. Mottos are typed by hand and have no other identity, so the same three lines are the same motto even with different UUIDs (which happens across devices). inactive is a state, not an identity. It's not part of the match, and a merge never flips it on a motto you already have.

People Schema (v2)

People you can associate with an experience: friends, family, the person you ran into at the coffee shop. You add and associate them in the entry editor's People field. A converter can fill the same table: supply a top-level people array and point at its UUIDs from each entry's peopleUUIDs (a spreadsheet with a people column converts cleanly this way). One note: the Swarm converter creates no people, because Foursquare's export leaves out who you were with. The only trace is an @mention in the shout text.

Every person is one object in the top-level people array. Required fields are marked R.

Field

Type

Notes

R uuid

String

Unique ID (Canonical UUID (8-4-4-4-12 hex)). Stable across imports/exports.

R name

String

Display name ("Sawa", "Murat", "Fabian").

R createdAt

String (ISO 8601)

When this person was first added.

R updatedAt

String (ISO 8601)

Last time this person record changed.

source

String

"pea" for people added inside P.E.A., or the originating service name when imported from foreign data.

sourceID

String

Stable identifier from the originating system, when a converter supplies one. With source, forms the dedupe key. Native (editor-created) people leave it nil.

photoURL

String

URL or local-asset path for a photo. P.E.A. doesn't download or display these yet — they round-trip only.

notes

String

Free-form notes the user might keep on a person. Round-trip only — P.E.A. doesn't surface them yet.

Entries point at people with a peopleUUIDs array (see Entry Schema → People links). One person can be on many experiences, and one experience can list many people. On import, P.E.A. creates every person first and then connects the entries, so every UUID finds its row.

People are created and associated in the entry editor: tap a suggestion or confirm a typed name. Archiving on the iPhone, the Mac, the watch, the widget or through Siri never creates a person, but when the text names someone already in People, P.E.A. associates them on arrival. People have their own page under Manage, a filter in the Index and on the Map, and a grouping in the Map's list, and they round-trip through every export.

On Add data to P.E.A. (merge), a person is a duplicate when the uuid matches, or when source and sourceID both match. People made in P.E.A. have no sourceID, so for them only the uuid counts.

P.E.A./Topic Schema (v3)

A topic is a word or two that says what an experience was about: a vocabulary you build as you archive ("soccer", "made", "noticed", "first time"). People answer who, places answer where, topics answer what. You create and associate them in the entry editor's Topics field: tap a suggestion, a default seed, or type a new one.

On screen they are Topics. In the file they are concepts. The app called them Concepts until September 2026, the key was published under that name, and a key in a public format doesn't change. Your older backups keep restoring, and a converter writes concepts, conceptUUIDs and conceptCount as before.

Every topic is one object in the top-level concepts array. Required fields are marked R. The shape is the same as People, so a converter or a reader can treat the two the same way.

Field

Type

Notes

R uuid

String

Canonical UUID (8-4-4-4-12 hex). Stable across exports.

R name

String

Display label, in the user's original casing ("soccer", "First Time").

R createdAt

String (ISO 8601)

When this topic was first added.

R updatedAt

String (ISO 8601)

Last time this topic changed.

source

String

"pea" for topics created in the editor, or the name of the service the data came from.

sourceID

String

Stable identifier from the originating system. With source, forms the dedupe key. Native rows leave it nil.

Entries point at topics with a conceptUUIDs array (see Entry Schema → Topic links). One topic can be on many experiences, and one experience can list many topics. On import, P.E.A. creates every topic first and then connects the entries.

On Add data to P.E.A. (merge), a topic is a duplicate when the uuid matches, or when source and sourceID both match. Topics made in P.E.A. have no sourceID, so for them only the uuid counts. Two archives that each made a "made" topic end up with two.

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-15T23:32:00Z",
      "updatedAt": "2026-01-15T23:32:00Z",
      "timeOfDay": "Evening",
      "dayOfWeek": "Thursday",
      "season": "Winter",
      "timeZoneID": "America/New_York",
      "latitude": 35.2271,
      "longitude": -80.8431,
      "locationName": "Walmart Supercenter",
      "locationLocality": "Charlotte",
      "locationAdministrativeArea": "NC",
      "locationSubLocality": "Wesley Heights",
      "locationSubAdministrativeArea": "Mecklenburg County",
      "locationPostalCode": "28208",
      "locationCountry": "United States",
      "placeName": "the Walmart by the airport",
      "placeDetail": "Charlotte, NC",
      "placeSource": "custom",
      "placeLatitude": 35.2270,
      "placeLongitude": -80.8430,
      "placeMapItemIdentifier": "I3F4A2C8B9D0E1F2A3B4C5D6E7F8A9B0",
      "placeCategory": "Store",
      "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",
      "websiteURL": "https://www.example-store.com/charlotte",
      "phoneNumber": "(704) 555-0148",
      "street": "3850 E Independence Blvd",
      "locality": "Charlotte",
      "administrativeArea": "NC",
      "postalCode": "28205",
      "country": "United States"
    }
  ],
  "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. placeName is 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 into a single PEA-Export-….json file and then bring it in via Manage → Import & Export → Add data to P.E.A. (on the Mac, File → Add Data to P.E.A.… opens the same picker). That's a merge. Everything you already have stays 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 rate it yourself by dragging the dot in the app, and that's the moment it gets its permanent number. So the prompts below don't ask the model to invent a strength. They set every row to Unset and leave the rating to you.

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 topic (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 people fields: uuid, name, createdAt, updatedAt.
- Required topic fields (in "concepts"): 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, using the LOCAL
  time where the entry happened (not UTC).
- If the source data records the time zone an entry was written in,
  emit it as "timeZoneID" using the IANA name (e.g. "Asia/Tokyo").
  If the source only has a UTC offset or no zone at all, omit the
  field entirely — never guess a zone from an offset.
- Omit any optional field you don't have data for. Do not write null.
  Do NOT try to infer city, neighbourhood, county, postal code, region,
  country, time zone, weather, or moon from
  other fields — for entries that carry latitude/longitude, P.E.A.
  completes those from the coordinates and the historical record after
  import. Bringing accurate coordinates is worth more than guessed
  metadata.
- The same rule for a place's listing fields ("websiteURL",
  "phoneNumber", "street", "locality", "administrativeArea",
  "postalCode", "country"): write them only where the source actually
  records the venue's address or contact details. Never look one up and
  never guess one from the name — P.E.A. resolves them itself after
  import for any place that carries coordinates or an Apple Maps
  identifier.

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 Manage → Import & Export → Add data to P.E.A.

Short prompt for Siri and Apple Intelligence

Siri, Apple Intelligence and other on-device models can't open links and don't have much room. This prompt carries the rules with it, so nothing needs to be fetched. Keep it short: a dozen entries at a time on-device, more through Private Cloud Compute or a chat model. In Shortcuts, add the Use Model action, pick On-Device or Private Cloud Compute, paste the prompt with your entries, and save what comes back as a .json file. It works the same way pasted into any chat.

Turn the entries below into one P.E.A. archive file. Answer with the
JSON only.

The file is one object with exactly these keys:
"exportSchemaVersion": 3, "exportDate" (now, ISO 8601, like
"2026-10-07T09:00:00Z"), "appVersion": "1.0", "readme": "Converted
for P.E.A.", "entryCount", "placeCount": 0, "mottoCount": 0,
"peopleCount", "conceptCount", "entries", "places": [], "mottos": [],
"people", "concepts".

One object in "entries" per entry:
- "uuid": a new random UUID (8-4-4-4-12 hex), different for every entry
- "archiveNumber": 0
- "text": the entry's own words, or "" if there are none
- "intensity": 1, "intensityLabel": "Minimum", "isIntensityUnset": true
- "createdAt" and "updatedAt": the entry's date and time, ISO 8601 with
  the time zone (like "2025-04-29T20:05:00-07:00"). The same value in
  both. If only the day is known, use 12:00.
- "timeOfDay" from the local clock: "Morning" before 12:00, "Afternoon"
  from 12:00, "Evening" from 17:00, "Night" from 21:00
- "dayOfWeek": "Monday" to "Sunday"
- "season": "Spring" for March to May, "Summer" for June to August,
  "Autumn" for September to November, "Winter" for December to February
- "source": "notes"
- Only when the entry says so: "locationName" (the place's name),
  "locationLocality" (the town), "locationAdministrativeArea" (the
  state or region), "locationCountry", "latitude" and "longitude" as
  numbers. If an entry names a place, put the place's name in
  "locationName" too.

People and topics: for every person named, one object in "people" with
"uuid", "name", "createdAt", "updatedAt" (same moment as "exportDate")
and "source": "notes". Put that person's uuid in the entry's
"peopleUUIDs". Topics go the same way into "concepts" and
"conceptUUIDs". Use one uuid per name, however often it comes up.

Leave out every field you don't have. Never write null. Don't guess a
place, a time zone, the weather or a rating. "entryCount",
"peopleCount" and "conceptCount" are the lengths of their arrays.

Entries:
[paste your notes, or rows from the P.E.A. spreadsheet template]

Load the file through Manage → Import & Export → Add data to P.E.A. Every entry arrives Unset. Rate it, and it gets its number.

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.

Don't worry about how thin the venue data looks — Foursquare's export ships only the venue's name and a coordinate, no city or country. That's enough: after import, P.E.A. completes the city, neighbourhood, county, postal code, region, country, and time zone from each check-in's coordinates (and the day's weather and moon from the historical record, where available), so the entries fill out on their own.

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:

swarm_to_pea.py →

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. archive next to the originals (format v2, which P.E.A. still reads):

That writes data-export-NNNN/pea-swarm.json. Then Manage → Import & Export → 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 have no number. Every row arrives with archiveNumber: 0 and shows in the app as P.E. #NaN. The app gives it a permanent P.E. #N the moment you rate it in the editor (drag the dot to any green step). Until then a check-in is a candidate, not a P.E. Setting it back to Unset later keeps the number it got. A row you never rate stays numberless, and if you neutralize it, it's deleted after 30 days without ever using a number.

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:

  • createdAt is 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 under venue. The venue block 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 no shout at all.

  • timeZoneOffset is minutes from UTC at the time of the check-in (e.g. -420 is Pacific Daylight Time).

The corresponding P.E.A. 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.

Note

id

sourceID

Half of the (source, sourceID) dedupe key on merge import. Stable forever — exports preserve it. A check-in whose second and shout match one already archived is skipped too.

id

sourceURL

Constructed as https://swarmapp.com/checkin/<id>. The app surfaces this as a tappable "View external experience" row that deep-links into Swarm or Safari.

createdAt + timeZoneOffset

createdAt, updatedAt

Treat the string as UTC, shift by timeZoneOffset minutes, emit as ISO 8601 with the local offset (e.g. 2024-07-21T16:22:37-07:00). Same value into both.

shout (or "")

text

Empty shouts are fine; text: "" is valid.

lat, lng (top level)

latitude, longitude

Copy verbatim.

venue.name

locationName

Omitted if the check-in has no venue (off-grid check-ins exist; <1 % of typical exports).

derived from local time

timeOfDay, dayOfWeek, season

Compute from the local time (after the timezone shift), not from UTC.

derived per-row

uuid

Fresh random UUID, one per check-in.

constant

archiveNumber: 0

Always 0. P.E.A. assigns the real P.E. #N when the user first rates a row in the app — imported rows are deliberately numberless (P.E. #NaN) until then.

constant

intensity: 1, intensityLabel: "Minimum", isIntensityUnset: true

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

source: "swarm"

Stamp every row. Lets the app tell native captures from imported ones; sticky forever in round-trips.

raw check-in (stringified)

importPayload

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.

venue.url, entities, event, hacc, visibility, private, comments

—

Dropped. No P.E.A. equivalent today. Preserved inside importPayload for future revivals.

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. 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:

PEA-Import-Template.csv →

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 topics you want to associate, 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 person (Felix) and two topics, captured on an Apple Watch.

  • Row 2 — Bunched and finished so many annoying tasks today. The most. No-coordinate case: street name + city + state + weather only, with one topic. 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:

  1. Open the CSV in your spreadsheet of choice.

  2. 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.

  3. To associate people or topics, type their names in the people and topics cells. Separate several with semicolons ("Felix; Mara"). Don't worry about UUIDs, the conversion step makes them.

  4. Save as CSV.

  5. Convert CSV → P.E.A. JSON. The simplest path is to paste the CSV plus this prompt into a local LLM (the short Siri prompt above takes the rows too):

 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 "topics" 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". Topics
   go into "concepts"; that is the key's name. 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]
  1. Save the resulting JSON file and load it through Manage → Import & Export → 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 leaves out what the conversion fills in for you (the uuid, the timeOfDay / dayOfWeek / season labels) and the strength columns (every imported row arrives Unset, so there's nothing to rate in advance). If you need saved places, moon phase, speed/elevation/steps, the Apple Music store ID or a sourceURL, edit the JSON 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 merge. New rows are added. Rows that already exist (same uuid, same (source, sourceID) for imported data, or the same createdAt and text) are skipped, so one moment under two ids lands once. This is for converted imports, Swarm, Day One, a spreadsheet, and also for adding an existing P.E.A. backup to what you have.

  • Restore P.E.A. from backup — This replaces your current data. It's 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 P.E.A. exported.

Schema Version History

exportSchemaVersion is 3 and has been since June 2026. The point releases below added optional fields and changed nothing for files that don't have them. The build numbers are TestFlight builds.

  • Page revision, October 8, 2026. No format change. Concepts are called Topics in the app since September 27, 2026; the keys (concepts, conceptUUIDs, conceptCount) stay as they are. Every "not shipped yet" note below is gone, because everything shipped. New on the page: the short prompt for Siri and Apple Intelligence, and the hour bands for timeOfDay and the months for season in the Time table. The spreadsheet template's concepts column is now topics, it gained locationAdministrativeArea, and its watch row says watch.

  • v3.6 — Region, neighbourhood, county and postal code on entries, and the listing on places. (September 2026, in every build since 91.)

    • Per-entry locationAdministrativeArea: the state or region, exactly as the map data gives it: "WA" in one country, "Bavaria" in another. P.E.A. shows it next to the town ("Seattle, WA") and falls back to the two-letter country code when there is no region. There is no table that expands or shortens these, so copy your source's spelling. Leave it out when you don't know it, and P.E.A. looks it up from the coordinates after import, like the city and the country.

    • Per-entry locationSubLocality, locationSubAdministrativeArea and locationPostalCode: the neighbourhood ("Ballard"), the county or its local equivalent ("King County") and the postal code ("98117"). Same rules as the region. Nothing in the app shows these three yet. They are kept so an archive written today already has them.

    • Per-place websiteURL, phoneNumber, street, locality, administrativeArea, postalCode and country: the place's own listing, so the place card can show an address, a website and a phone number without going online. detail is unchanged and still the short "City, Country" line. Write these if your source knows the venue's address. If it only knows a coordinate, leave them out and P.E.A. looks the place up after import. Once recorded, P.E.A. doesn't overwrite them.

    • All eleven are optional. exportSchemaVersion stays 3, older files import unchanged, older builds ignore the fields.

  • v3.5 — Place association and role. (August 2026, in every build since 70.)

    • Per-place associationState: whether the place may associate itself with new experiences automatically ("notLicensed" / "disabled" / "enabled"). Absent means something: the place was saved before the field existed and behaves as enabled. Keep it absent when you convert a file.

    • Per-place role ("home" / "work" / "other") and roleUpdatedAt (ISO 8601): the place's role in your life and when you last set it. There is at most one home and one work. The timestamp is there so a sync conflict can be settled by a person. Imports copy it as is.

    • All three are optional. exportSchemaVersion stays 3.

  • v3.4 — Motto active state. (August 2026, in every build since 70.)

    • Per-motto inactive: true keeps a motto in the archive but stops showing it, on every device. Left out when the motto is active. Not part of the motto's identity, so the merge still matches by uuid or by the three lines. Optional. exportSchemaVersion stays 3.

  • v3.3 — Place identity. (July 2026, in every build since 70. See Place identity above.)

    • Per-entry placeIdentityKey: which place an experience belongs to, as a stable key. apple: for a place backed by an Apple Maps Place ID, local: for one P.E.A. created. The key, not the name, says "these two experiences happened at the same place", which is why one archive can hold several places called "Home".

    • Per-place identityKey, providerName, radiusMeters, symbol: the same key on the saved place, what Apple Maps called the place when you linked it (not refreshed), how far the place reaches in meters, and the SF Symbol you picked. No radiusMeters means the app's default. No symbol means the app picks one by category.

    • Identity keys are opaque. Don't parse one, don't make one up, don't copy one onto another place. Leave them out and P.E.A. works out identity itself.

    • All five are optional. exportSchemaVersion stays 3.

  • v3.2 — Time zone. (July 2026, in every build since 70.)

    • Per-entry timeZoneID: the IANA time zone the experience happened in ("Asia/Tokyo"), set at creation. When it's there, timeOfDay and dayOfWeek are read on that clock, so an experience archived in another zone keeps its wall clock instead of drifting with the device. Converters write it only when the source really records a zone (Day One's timeZone, for example), never from a bare UTC offset. Optional. exportSchemaVersion stays 3.

  • v3.1 — Place category. (July 2026, in every build since 70.)

    • Per-entry placeCategory: the Apple Maps point-of-interest category of an assigned place, human-readable ("Cafe", "Amusement Park"). Set when a place is picked from Apple Maps search. Plain addresses and custom places have none. Optional. exportSchemaVersion stays 3.

  • v3 — Concepts, which are Topics now. (June 2026.)

    • concepts top-level collection + conceptCount. A word or two that says what an experience was about ("soccer", "made", "noticed"), created and associated in the entry editor. Native rows are stamped source: "pea" with no sourceID. The app renamed them Topics in September 2026; the keys didn't change.

    • Per-entry conceptUUIDs array, linking each entry to its topics. Many-to-many, same shape as peopleUUIDs.

    • On Add data to P.E.A. (merge), topics match by uuid or (source, sourceID). Native topics (no sourceID) match by uuid only.

    • People reached the editor. People came in at v2 as a round trip only. From v3 you create and associate them in the entry editor's People field. The wire format is unchanged.

    • Both additions are optional, so a v1 or v2 archive imports unchanged on a v3 build. An older build refuses a v3 archive with a "please update" message rather than dropping the topics.

  • v2 — Add data + Unset + People (schema only). (May 2026.)

    • people top-level collection + peopleCount. People round-trip through export and import. The in-app surfaces came with v3.

    • Per-entry peopleUUIDs array linking each entry to people in the top-level people array.

    • Per-entry sourceID: a stable identifier from the originating system. With source, it's the merge key for "Add data to P.E.A.". The same createdAt and text also mark a duplicate.

    • Per-entry importPayload: the original record as a string. P.E.A. doesn't read it. It's there so a future import can reach back into fields P.E.A. didn't model the first time.

    • Per-entry sourceURL: a link back to the original record (Swarm check-in, Day One entry, tweet). Shows as a tappable "View external experience" row. Native captures have none.

    • Per-entry isIntensityUnset: true on rows that came in through merge from another source and haven't been rated. Shows as a solid light-gray dot at the smallest size (the dashed gray dot is Neutralized, a different thing). Never on native entries.

    • All five are optional, so a v1 archive imports unchanged on a v2 build.

  • v1 — The first public schema. (May 2026.)

    • One JSON file, no zip.

    • Top-level entries, places, mottos.

    • A stable uuid on every row, for round trips.

    • A permanent archiveNumber on every entry (the "P.E. #X").

    • A readme field, so a person can read the file.

    • Place fields on the entry next to the top-level places array, so an entry is self-describing without cross-references.

    • Per-entry source: "pea" for native captures, the service name for converted imports. Optional. A file without it is treated as all native.

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 update the app.

Questions? Found a problem?

Email Florian with questions.

Email data questions →

Colophon

Positive Experience Archive



is made by Florian in Seattle with care and good intentions.


Made with



Ableton / Acorn / Apple Watch / Darkroom / BBEdit / Claude Code / CleanShot X / Darkroom / Framer / FUJIFILM X100T+V / GitHub / HandBrake / iCloud / Icon Designer / iPhone / Keynote / Linear / JavaScript / Mac Mini / MacBook Pro / Paper / Pencil / Pixelmator Pro / Photos app / Python / SF Rounded Regular, Medium, & Bold / Sketch / SwiftUI / Terminal / Xcode

2163 P.E.s archived in the process.



When we lose focus,

we forget who we are.

No animals harmed,

human-tested only.

”Support people, not corporations.”
—David Watanabe

Positive
Experience
Archive

©2026 Fangohr, LLC

Colophon

Positive Experience Archive



is made by Florian in Seattle with care and good intentions.


Made with



Ableton / Acorn / Apple Watch / Darkroom / BBEdit / Claude Code / CleanShot X / Darkroom / Framer / FUJIFILM X100T+V / GitHub / HandBrake / iCloud / Icon Designer / iPhone / Keynote / Linear / JavaScript / Mac Mini / MacBook Pro / Paper / Pencil / Pixelmator Pro / Photos app / Python / SF Rounded Regular, Medium, & Bold / Sketch / SwiftUI / Terminal / Xcode

2163 P.E.s archived in the process.



When we lose focus,

we forget who we are.

No animals harmed,

human-tested only.

”Support people, not corporations.”
—David Watanabe

Positive
Experience
Archive

©2026 Fangohr, LLC

Create a free website with Framer, the website builder loved by startups, designers and agencies.