Data Downloads: CSV and JSON

Every record page on Caliper Press has two files next to it: data.json with the full record and data.csv with one row per check. Each section page has the same two files with the latest check of every record in the section, and an Atom feed of confirmed changes. There is no API, key or sign-up: the files are static, free to download and updated daily.

Where the files are

Add data.json or data.csv to the address of a record page or a section page. Each of these pages also links to its files at the bottom. One example per section:

The coverage page lists the sections and how many records each has. The renewal costs page also has a data.csv with its table.

Three formats of data.json

object_slice, plan_slice, section_slice

The schema field at the top of every data.json names its format, and each format has a JSON Schema (draft 2020-12) to validate against:

  • object_slice: a record page, such as /vpn/objects/ipvanish/. One record with all its checks. object_slice.json
  • plan_slice: a plan page, such as /saas-prices/objects/asana-starter/. One row per billing option; each row holds a full record in the object_slice format under rows[].slice, so the history of a row is in rows[].slice.history. plan_slice.json
  • section_slice: a section page, such as /saas-prices/. Every record of the section with its latest main check, without history. section_slice.json

Fields of a record (object_slice)

9 fields, and what each check holds
  • object: what is tracked, with its id (object_id, written as section:record), name, section and the provider page we read.
  • latest: the last confirmed check of the main question. Newer checks that recorded no value are listed in held_value for up to 3 days; after that latest is the newest check, with the value unknown.
  • history: every check of the record, oldest first, up to and including latest.
  • indicators: values we calculate from the checks, each with its window and sample size (n).
  • events: confirmed changes, each with the old and new value, when it was first seen and when it was confirmed.
  • raw_journal: moves that were not counted as changes, with the reason, for example a value that did not repeat in the next check.
  • quarantine: large price moves held back until a repeat check confirms them; the held number itself is not published.
  • held_value: questions where the page shows the last confirmed value because newer checks recorded none.
  • billing, on hosting, domain and VPN records: how the first term is paid, as the provider's page states it.

Each check in latest and history has:

  • question in plain words and time in ISO 8601;
  • measured: value, unit and, for money, currency; an answer in words is in text;
  • claim: what the provider's page states, with source.url, a short source.quote and the date;
  • data_status: measured, stated_by_source, calculated or unknown, shown on pages as Observed, Published by the source, Calculated and Unknown;
  • region, the two-letter code of the country the value was shown in, and region_name;
  • method_version, the edition of the method, and evidence with url, bytes and http_status.

Numbers are decimal strings, such as "2.99", so no precision is lost. An unknown value is null with a reason, never zero. Units are codes such as usd_per_month, usd_per_year or days; the schemas list every code.

Fields of a plan and a section

plan_slice and section_slice

A plan file (plan_slice) has plan_id, name, main_object_id (the row whose price is the plan's main answer), rows, the events of all rows and related answers from other records of the same plan. Each row has billing (annual or monthly), promo, object_id, purchase_payment (what is paid at purchase and how many months it covers) and slice.

A section file (section_slice) has objects, one entry per record with object_id, name, the page url, plan and latest; index, the section's index value when it has one, with its range (lowest and highest contribution) and participants (the records counted); license and generated_at.

Reading the files: an example

a short Python script

This Python script prints every check in a record file or a plan file:

import json, urllib.request
url = "https://caliperpress.com/saas-prices/objects/asana-starter/data.json"
d = json.load(urllib.request.urlopen(url))
if d["schema"] == "plan_slice":
    records = [row["slice"] for row in d["rows"]]
else:
    records = [d]
for r in records:
    for c in r["history"]:
        m = c["measured"]
        print(r["object"]["object_id"], c["time"], m["value"], m["unit"])

What data.csv contains

one row per check

A record file has one row per check, with the columns object_id, measurement_id, time, value, unit, status, method_version, source and currency. A plan file adds billing after object_id. A section file has the same columns, one row per record with its latest check. The renewal costs CSV has one row per plan, with its first-term price, renewal price and the date.

Change feeds

Atom, for the whole site and each section

changes.xml is an Atom feed of confirmed changes across the site, newest first. Each section page has its own feed: add changes.xml to its address, as in /saas-prices/changes.xml. Each entry links to the change on its record page.

Using the data

CC BY 4.0, with credit

Our numbers are licensed under CC BY 4.0 with attribution to Caliper Press. Credit the page you used and the date of the data; how to use this data shows a citation format. Quotations from provider pages are short excerpts that belong to their owners and are not covered by our license.

A record keeps its address when new checks are added, and earlier checks stay in history.

What is not available

no API keys, bulk archive or email alerts

There is no API with keys, no bulk archive of the whole site and no email alerts. The free tools on this site call small endpoints under /api/ for their own pages; those are not a data service and can change without notice.