Prospex docs

Export a list

Turn a built list into rows: one page, then every page, then the same list as a spreadsheet, with the four fields worth mapping first.

A built list ranks Swiss companies worth selling to, from a definition you wrote in the app. This page takes one out as data, from the first page to the last, in the two formats the feed answers in.

Open the list and go to its Connect page. It carries a JSON URL, a CSV URL and the button that rotates the token in both.

Which companies you get

A built list sorts its companies into two buckets. Core is the list's answer to who to sell to, and it is what the feed serves by default. Explore is the wider set the list found and has not committed to, which is where you look when Core is smaller than your team can work through.

# Core, the default
curl -s 'https://prospex.ch/app/lists/feed/<token>.json?limit=200'

# The wider bucket
curl -s 'https://prospex.ch/app/lists/feed/<token>.json?tab=explore&limit=200'

Both buckets carry the same fields, and bucket on each row says which one it came from.

Walking every page

The envelope carries next_url, absolute and complete. Following it until it is empty is the entire pagination protocol:

#!/usr/bin/env bash
set -euo pipefail

url="https://prospex.ch/app/lists/feed/${PROSPEX_LIST_TOKEN}.json?limit=200"
: > list.jsonl

while [ -n "$url" ]; do
  body=$(curl -sS --fail-with-body "$url") || {
    echo "$body" | jq -r '"refused: \(.error) \(.message)"' >&2
    exit 1
  }
  echo "$body" | jq -c '.data[]' >> list.jsonl
  echo "$body" | jq -r '"\(.count) of \(.total), \(.usage.rows_today) rows today"' >&2
  url=$(echo "$body" | jq -r '.next_url')
done

wc -l list.jsonl

Three things to note:

  • It never builds a cursor. Cursors belong to the build that issued them; a hand-built one returns 400.
  • It reads .error from the body of a refusal. Every refusal answers JSON, including the 402 and the 429.
  • It watches usage.rows_today. A list larger than what is left of the day's or the month's row budget stops mid-walk with a 429, and $url still holds the page to resume from.

When the list refreshes underneath you

Built lists rebuild on a weekly schedule, and a rebuild can reorder the ranking. A cursor issued before it answers:

{
  "error": "invalid_parameter",
  "message": "This list has been refreshed since that cursor was issued, so its order has changed. Start again without a cursor."
}

Handle it by restarting the walk from the first page. market.refreshed_at in the envelope tells you which build you are reading.

The same list as a spreadsheet

The CSV needs no loop. It streams the whole bucket in one response, up to 10,000 rows, with human column headers instead of JSON keys:

curl -sS -o list.csv \
  'https://prospex.ch/app/lists/feed/<token>.csv?tab=core'

Arrays flatten rather than nest, because a CSV cell has nowhere to put an array: Reasons joins with a pipe, Missing with a comma, and the signal columns carry titles and ids for the same few recent events. When you need the structure, read the JSON. The list feed has every column and the key behind it.

The four fields to map first

A list row carries the registry facts you would expect. These are the ones that carry the list's own judgement:

FieldUse it for
fit Sorting and filtering. It is the stored word, so it survives a change of interface language; fit_label is the version to show a person.
reasons The first line of an opener, and the answer to "why is this company on my list". Same words the workspace shows.
missing Deciding what to enrich yourself. An empty field means Prospex does not hold the data.
in_crm Skipping companies your CRM already holds, if you connected one.