Execute saved queries via the Specify API

:gear: This guide is for developers and technically-minded users who want to retrieve saved query results from Specify programmatically.

If you’ve built and saved a query in Specify’s Query Builder, you can run it directly via a simple GET request using the API.

The endpoint uses the same execution path as the Query Builder itself and supports pagination for working through large result sets.

Before you begin, it will help to familiarize yourself with how queries are built and saved in Specify:


Query API Endpoint

GET /stored_query/query/<QUERY_ID>/

The <QUERY_ID> is the numeric ID of the saved query record in Specify (you can look in the URL itself, e.g. https://sp7demofish.specifycloud.org/specify/query/56/ corresponds with query ID 56). The endpoint enforces the same Query Builder execute permission and honors your logged-in collection scoping so you’ll only see records your collection has access to.


Pagination Parameters

For most queries, you’ll want to page through results in batches rather than pulling everything at once.

Parameter Default What it does
limit 20 Maximum rows to return in a single response
offset 0 Number of rows to skip before returning results

[!tip]
For large result sets, keep limit fixed and step through the data by incrementing offset — for example offset=0, then offset=500, then offset=1000, and so on. You


Response Formats

What comes back depends on how the saved query was configured. There are three possible shapes.

Standard query

Each row is an array. The first element is always the base table record ID (e.g. the Collection Object ID), followed by the fields the query selects:

{
"results":
  [
    [1112, "32000", "02/12/2003", "Abudefduf sexfasciatus", "EtOH - 1", "Talai harbor, S side of Yadua Island", "Fiji",…],
    [5502, "31850", "02/12/2003", "Callogobius maculipinnis", "EtOH - 1", "Talai harbor, S side of Yadua Island", "Fiji",…],
    [6320, "31855", "02/12/2003", "Neopomacentrus metallicus", "EtOH - 1", "Talai harbor, S side of Yadua Island", "Fiji",…],
    [37139, "37088", "09/02/2005", "Hyporhamphus dussumieri", "EtOH - 1", "Talai harbor, S side of Yadua Island", "Fiji",…],
    [6216, "31996", "02/12/2003", "Neoniphon opercularis", "EtOH - 1", "Talai harbor, S side of Yadua Island", "Fiji",…],
    [39598, "31857", "02/12/2003", "Caesio caerulaurea", "EtOH - 1", "Talai harbor, S side of Yadua Island", "Fiji",…],
    [35016, "31984", "02/12/2003", "Oplopomus oplopomus", "EtOH - 1", "Talai harbor, S side of Yadua Island", "Fiji",…],
    [35319, "31860", "02/12/2003", "Ptereleotris microlepis", "EtOH - 1", "Talai harbor, S side of Yadua Island", "Fiji",…],
    [2535, "31955", "02/12/2003", "Chrysiptera taupou", "EtOH - 2", "Nananui-i-cake Island, reef to the East", "Fiji",…],
    [3773, "31919", "02/12/2003", "Pomacentrus spilotoceps", "EtOH - 1", "Nananui-i-cake Island, reef to the East", "Fiji",…],
    [5981, "31837", "02/12/2003", "Pomacentrus callainus", "EtOH - 2", "Nananui-i-cake Island, reef to the East", "Fiji",…],
    [37508, "32552", "06/19/2003", "Chaetodon baronessa", "EtOH - 2", "Nananui-i-cake Island, reef to the East", "Fiji",…],
    [2265, "31998", "02/12/2003", "Amblyglyphidodon curacao", "EtOH - 1", "Nananui-i-cake Island, reef to the East", "Fiji",…],
    [2588, "31986", "02/12/2003", "Apogon trimaculatus", "EtOH - 2", "Nananui-i-cake Island, reef to the East", "Fiji",…],
    [3371, "31965", "02/12/2003", "Pomacentrus nigromarginatus", "EtOH - 2", "Nananui-i-cake Island, reef to the East", "Fiji",…],
    [180, "38211", "05/08/2006", "Cercamia eremia", "EtOH - 1", "Nananui-i-cake Island, reef to the East", "Fiji",…],
    [763, "32568", "06/19/2003", "Cirrhilabrus punctatus", "EtOH - 1", "Kia Island - off rock cliffs on West side", "Fiji",…],
    [34066, "31899", "02/12/2003", "Stegastes nigricans", "EtOH - 1", "Kia Island - off rock cliffs on West side", "Fiji",…],
    [36156, "32023", "02/12/2003", "Epinephelus merra", "EtOH - 1", "Kia Island - off rock cliffs on West side", "Fiji",…],
    [2661, "37809", "12/02/2005", "Entomacrodus striatus", "EtOH - 1", "Kia Island - off rock cliffs on West side", "Fiji",…]
  ]
}

Distinct query

When the saved query is set to be distinct, the first element of each row is not a single record ID. Instead, it becomes a comma-separated list of all record IDs that share that distinct value. Make sure your code accounts for this before splitting or parsing the first field.

{
  "results": [
      [
          "166,296,412,591,652,683,817,887,1102,1110,1261,1322,1515,1525,1736,1860,1989,2003,2227,2231,2345,2533,2566,2604,2633,2714,2731,2813,2877,2884,2891,2900,2901,2923,2982,3002,3082,3263,3430,3598,3600,3616,3651,3697,3739,3814,3830,3839,4032,4063,4066,4082,4158,4164,4288,4321,4382,4438,4485,4582,4780,4840,4951,4989,5002,5040,5073,5151,5182,5194,5271,5308,5327,5365,5458,5521,5534,5695,6028,6077,6104,6131,6169,6208,6284,6304,6370,6429,6573,6692,6713,6807,34092,34162,34176,34219,34363,34382,34631,34635,34720,34897,35007,35023,35066,35338,35435,35495,35523,35548,35589,35687,35767,35778,35785,35787,35885,35955,36011,36045,36079,36162,36248,36350,36375,36392,36427,36431,36504,36559,36617,36721,36794,36849,36864,36981,37052,37114,37178,37266,37292,37385,37417,37536,37577,37602,37718,37767,37775,37845,37847,38003,38443,38683,38698,38739,38905,38909,38916,39015,39103,39139,39144,39376,39425,39476,39497,39516,39634,39918,39954,39998,40104,40106,40138,40167,40262,40291,40334,40347,40363,40378,40472,40492,40498,40616,40807,40820,40834,40917,40918,40951,41008,41125,41185",
          2012
      ]
  ]
}

Step 2: Export to CSV endpoint

If you need a downloadable CSV file instead of JSON, you can use the CSV export endpoint. It runs the export in the background and returns a plain-text OK while the file is generated.

POST /stored_query/exportcsv/
  • Request body: JSON for the query (the same used by the Query Builder).
  • Response: OK (text/plain) once the export job is started.
  • Permissions: requires Query Builder execute and CSV export permission.

Note that there are no query headers included by default while using this endpoint.

Download a CSV

You can use a bash script to download a CSV of any query.
This example requires jq to be installed. You can try this with the username and password of sp7demofish:

BASE_URL="https://sp7demofish.specifycloud.org"
COOKIE_JAR="./specify-cookies.txt"
LIMIT=500
OFFSET=0
OUT="query-results.csv"

COLLECTION_ID=$(curl -sS "$BASE_URL/context/login/" | jq -r '.collections | to_entries[0].value')
printf 'Using collection ID: %s\n' "$COLLECTION_ID"
printf 'Username: '
read -r USERNAME
printf 'Password: '
read -r -s PASSWORD
echo

curl -sS -c "$COOKIE_JAR" \
  -H 'Content-Type: application/json' \
  -X PUT \
  -d "{\"username\":\"$USERNAME\",\"password\":\"$PASSWORD\",\"collection\":$COLLECTION_ID}" \
  "$BASE_URL/context/login/" \
  -o /dev/null

: > "$OUT"
while :; do
  JSON=$(curl -sS -b "$COOKIE_JAR" "$BASE_URL/stored_query/query/56/?limit=$LIMIT&offset=$OFFSET")
  ROWS=$(printf '%s' "$JSON" | jq '.results | length')
  printf '%s' "$JSON" | jq -r '.results[] | @csv' >> "$OUT"
  [ "$ROWS" -lt "$LIMIT" ] && break
  OFFSET=$((OFFSET + LIMIT))
done

Steps to Use This Endpoint

  1. Build and save your query in Specify’s Query Builder.
  2. Find the query’s numeric ID in the saved query record.
  3. Call the endpoint with your chosen limit and offset values.
  4. Check the response shape (standard, distinct) and parse accordingly.