> ## Documentation Index
> Fetch the complete documentation index at: https://terrascout.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Versions and changes

> Snapshots, changes files and how to apply them

<Info>
  Data Feeds is in early access. [Contact us](mailto:support@ploti.ai) to set up
  a subscription; the API and sync client described here open to early access
  customers first.
</Info>

Each time Ploti reads a new state of a source, it publishes a new version of the dataset. A version has:

* a **snapshot**: the whole dataset as of that version
* a **changes file**: the records added, changed and removed since the version before it
* its **schema**

## Listing versions

```bash theme={null}
curl "https://api.ploti.ai/v1/datasets/ds_orange_fl_building_permits/versions?after=40" \
  -H "Authorization: Bearer $PLOTI_API_KEY"
```

```json theme={null}
{
  "versions": [
    {
      "datasetId": "ds_orange_fl_building_permits",
      "version": 42,
      "previousVersion": 41,
      "schemaVersion": "2.1",
      "publishedAt": "2026-10-18T06:12:40Z",
      "sourceFetchedAt": "2026-10-18T06:01:12Z",
      "rowCount": 184220,
      "counts": { "added": 212, "changed": 38, "removed": 3 },
      "resyncRequired": false
    }
  ],
  "nextCursor": null
}
```

## Getting a version's files

`GET /v1/datasets/{id}/versions/{version}` returns the version with signed links to its files. Links expire after `files.expiresAt`; ask again for fresh ones. Pass `?format=csv` for CSV; GeoParquet is the default.

```json theme={null}
{
  "version": 42,
  "schemaVersion": "2.1",
  "counts": { "added": 212, "changed": 38, "removed": 3 },
  "files": {
    "snapshot": [
      {
        "url": "https://files.ploti.ai/...",
        "format": "geoparquet",
        "rows": 184220,
        "bytes": 21933012,
        "sha256": "9b1f..."
      }
    ],
    "changes": [
      {
        "url": "https://files.ploti.ai/...",
        "format": "geoparquet",
        "rows": 253,
        "bytes": 48210,
        "sha256": "11ad..."
      }
    ],
    "schema": {
      "url": "https://files.ploti.ai/...",
      "format": "json",
      "rows": 0,
      "bytes": 4120,
      "sha256": "c03a..."
    },
    "expiresAt": "2026-10-18T15:02:11Z"
  }
}
```

Check each file's `sha256` after download.

## Applying changes

A changes file holds one row per record that changed, with the [envelope columns](/docs/platforms/datasets#envelope-columns) and the record's columns as of this version:

| `_ploti_op` | `_ploti_change` | Apply |
| - | - | - |
| `upsert` | `added` | Insert the record. |
| `upsert` | `changed` | Replace the record with the same `_ploti_record_id`. |
| `delete` | `removed` | Delete the record with that `_ploti_record_id`. The other columns are null. |

Apply versions in order. If you have version 40 and the current version is 42, apply 41's changes and then 42's.

For small versions, `GET /v1/datasets/{id}/versions/{version}/changes` returns the same changes as JSON pages, without downloading a file.

## When to reload instead

Reload a dataset from a version's snapshot when:

* the version has `resyncRequired: true` (its key columns changed, so record ids did too)
* you receive `dataset.resync_required`
* the version you need is past retention (`410`)

## Retention

| What | Kept |
| - | - |
| Changes files | 90 days |
| Snapshots | The current version, the last 90 days of versions, and the last version of every MAJOR schema version |
| Events | 90 days |

A subscriber who stays within 90 days can always catch up by changes alone.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.