logoPandorLabs
API reference

Every endpoint, straight from the API

All paths are relative to https://api.pandorlabs.com/v1. New here? Start with the quickstart.

Scrapers

The catalogue of scrapers. Each one declares the parameters it accepts as a JSON Schema, so a client can build a request without guessing. A scraper in live mode is run on demand with your own parameters; one in dataset mode is refreshed on a schedule (refreshEveryMinutes) and is bought as a dataset download instead of run. Each scraper's own parameters are documented in the scraper directory.

GET/v1/scrapers

List scrapers

Every scraper the key can run, with the parameters each one accepts.

Any valid key200 · application/json

Query parameters

  • categorystringoptional

    Keep one category only.

  • searchstringoptional

    Match against the id, name, description and tags.

Request
curl "https://api.pandorlabs.com/v1/scrapers" \  -H "Authorization: Bearer $PANDORLABS_KEY"
Response · 200
{  "data": [    {      "id": "reddit",      "name": "Reddit",      "description": "Posts from the subreddits and keywords you name.",      "category": "social_media",      "tags": [        "reddit",        "posts"      ],      "mode": "live",      "parameters": [        {          "name": "subreddits",          "label": "Subreddits",          "type": "stringList",          "description": "Names only, without the r/ prefix.",          "default": [            "forhire"          ]        }      ],      "inputSchema": {        "type": "object",        "additionalProperties": false,        "properties": {          "subreddits": {            "type": "array",            "items": {              "type": "string"            },            "title": "Subreddits",            "default": [              "forhire"            ]          }        }      },      "exampleInput": {        "subreddits": [          "forhire"        ]      },      "lastRunAt": "2026-09-30T08:00:00.000Z"    }  ]}
GET/v1/scrapers/{scraperId}

Describe a scraper

One scraper in full: its parameters, the JSON Schema of its input, and the fields its rows carry.

Any valid key200 · application/json

Path parameters

  • scraperIdstringrequired

    Scraper id, from GET /scrapers.

Request
curl "https://api.pandorlabs.com/v1/scrapers/{scraperId}" \  -H "Authorization: Bearer $PANDORLABS_KEY"
Response · 200
{  "id": "reddit",  "name": "Reddit",  "description": "Posts from the subreddits and keywords you name.",  "category": "social_media",  "tags": [    "reddit",    "posts"  ],  "mode": "live",  "parameters": [    {      "name": "subreddits",      "label": "Subreddits",      "type": "stringList",      "description": "Names only, without the r/ prefix.",      "default": [        "forhire"      ]    }  ],  "inputSchema": {    "type": "object",    "additionalProperties": false,    "properties": {      "subreddits": {        "type": "array",        "items": {          "type": "string"        },        "title": "Subreddits",        "default": [          "forhire"        ]      }    }  },  "exampleInput": {    "subreddits": [      "forhire"    ]  },  "lastRunAt": "2026-09-30T08:00:00.000Z",  "rowCount": 48210,  "fields": [    {      "name": "title",      "type": "string",      "coverage": 1    }  ],  "related": []}

Runs

One execution of a scraper with your own parameters. Runs are asynchronous: start one, poll its status, and read its rows once it has finished. A finished run is billed 2 credits per 100 rows it collected.

POST/v1/scrapers/{scraperId}/runs

Start a run

Queue a run and return immediately. Parameters you leave out keep the value the scraper is configured with; unknown or mistyped ones are rejected with a 400 that names each problem. If you already have a run of this scraper with the same input that is still in flight, or that finished within maxAgeMinutes, that run is returned instead of starting another. Only scrapers in live mode can be run; a dataset-mode scraper answers 409 and is downloaded instead. Needs a positive credit balance, otherwise 402.

scope: scrapers:run202 · application/json

Path parameters

  • scraperIdstringrequired

    Scraper id, from GET /scrapers.

Body

  • inputobjectoptional

    Parameters for this run, matching the inputSchema of the scraper.

  • maxAgeMinutesintegeroptional

    Reuse your own finished run with the same input if it completed within this many minutes (at most 1440). 0 only reuses a run still in flight.

    Default10

Request
curl -X POST "https://api.pandorlabs.com/v1/scrapers/{scraperId}/runs" \  -H "Authorization: Bearer $PANDORLABS_KEY" \  -H "Content-Type: application/json" \  -d '{    "input": {},    "maxAgeMinutes": 10  }'
Response · 202
{  "id": "66f2c1a9e4b0a7d3c9f01234",  "scraperId": "reddit",  "status": "queued",  "input": {    "subreddits": [      "webdev"    ],    "timeFilter": "week"  },  "rowCount": 0,  "createdAt": "2026-09-30T09:12:04.000Z",  "startedAt": "2026-09-30T09:12:05.000Z",  "billing": {    "status": "pending",    "credits": 4  },  "links": {    "self": "/v1/runs/66f2c1a9e4b0a7d3c9f01234",    "rows": "/v1/runs/66f2c1a9e4b0a7d3c9f01234/rows",    "download": "/v1/datasets/reddit/downloads"  }}
GET/v1/runs

List your runs

Runs started with any key of your account, newest first.

scope: scrapers:run200 · application/json

Query parameters

  • pageintegeroptional

    Page number, starting at 1.

    Default1

  • limitintegeroptional

    Rows per page, up to 100.

    Default20

  • scraperIdstringoptional

    Keep the runs of one scraper only.

  • statusstringoptional

    Keep runs in one status only.

    One ofqueuedrunningcompletedfailedcancelled

Request
curl "https://api.pandorlabs.com/v1/runs?page=1&limit=20" \  -H "Authorization: Bearer $PANDORLABS_KEY"
Response · 200
{  "data": [    {      "id": "66f2c1a9e4b0a7d3c9f01234",      "scraperId": "reddit",      "status": "running",      "input": {        "subreddits": [          "webdev"        ],        "timeFilter": "week"      },      "rowCount": 128,      "createdAt": "2026-09-30T09:12:04.000Z",      "startedAt": "2026-09-30T09:12:05.000Z",      "billing": {        "status": "pending",        "credits": 4      },      "links": {        "self": "/v1/runs/66f2c1a9e4b0a7d3c9f01234",        "rows": "/v1/runs/66f2c1a9e4b0a7d3c9f01234/rows",        "download": "/v1/datasets/reddit/downloads"      }    }  ],  "page": 1,  "limit": 20,  "total": 1,  "hasNext": false}
GET/v1/runs/{runId}

Get a run

Status, row count so far, timings and billing of one run. Poll this until the status is completed, failed or cancelled; the run is billed at that point.

scope: scrapers:run200 · application/json

Path parameters

  • runIdstringrequired

    Run id, returned when the run was started.

Request
curl "https://api.pandorlabs.com/v1/runs/{runId}" \  -H "Authorization: Bearer $PANDORLABS_KEY"
Response · 200
{  "id": "66f2c1a9e4b0a7d3c9f01234",  "scraperId": "reddit",  "status": "running",  "input": {    "subreddits": [      "webdev"    ],    "timeFilter": "week"  },  "rowCount": 128,  "createdAt": "2026-09-30T09:12:04.000Z",  "startedAt": "2026-09-30T09:12:05.000Z",  "billing": {    "status": "pending",    "credits": 4  },  "links": {    "self": "/v1/runs/66f2c1a9e4b0a7d3c9f01234",    "rows": "/v1/runs/66f2c1a9e4b0a7d3c9f01234/rows",    "download": "/v1/datasets/reddit/downloads"  }}
GET/v1/runs/{runId}/rows

Read the rows of a run

Only the rows this run collected. Available once the run has finished and is paid: 409 while it is queued or running, 402 while the balance cannot cover it.

scope: scrapers:run200 · application/json

Path parameters

  • runIdstringrequired

    Run id, returned when the run was started.

Query parameters

  • pageintegeroptional

    Page number, starting at 1.

    Default1

  • limitintegeroptional

    Rows per page, up to 1000.

    Default100

Request
curl "https://api.pandorlabs.com/v1/runs/{runId}/rows?page=1&limit=100" \  -H "Authorization: Bearer $PANDORLABS_KEY"
Response · 200
{  "run": "66f2c1a9e4b0a7d3c9f01234",  "status": "completed",  "data": [    {      "title": "Looking for a developer to rebuild our storefront",      "subreddit": "forhire",      "score": 42,      "created_at": "2026-09-30T07:41:10.000Z"    }  ],  "page": 1,  "limit": 100,  "total": 128,  "hasNext": true}
POST/v1/runs/{runId}/cancel

Cancel a run

Stop a queued or running run. The rows it already collected are billed and stay readable.

scope: scrapers:run200 · application/json

Path parameters

  • runIdstringrequired

    Run id, returned when the run was started.

Request
curl -X POST "https://api.pandorlabs.com/v1/runs/{runId}/cancel" \  -H "Authorization: Bearer $PANDORLABS_KEY"
Response · 200
{  "id": "66f2c1a9e4b0a7d3c9f01234",  "scraperId": "reddit",  "status": "cancelled",  "input": {    "subreddits": [      "webdev"    ],    "timeFilter": "week"  },  "rowCount": 128,  "createdAt": "2026-09-30T09:12:04.000Z",  "startedAt": "2026-09-30T09:12:05.000Z",  "billing": {    "status": "pending",    "credits": 4  },  "links": {    "self": "/v1/runs/66f2c1a9e4b0a7d3c9f01234",    "rows": "/v1/runs/66f2c1a9e4b0a7d3c9f01234/rows",    "download": "/v1/datasets/reddit/downloads"  }}

Dataset downloads

A whole dataset as one file, paid in credits: 1 per 100 rows when the stored data is recent enough, 2 per 100 rows when the scraper has to be re-run first.

POST/v1/datasets/{datasetId}/downloads

Download a dataset

Buy the whole dataset as a file. When the last successful run is within maxAgeHours the stored rows are charged at the cached rate and the download is ready at once. Otherwise the scraper is re-run with its configured settings, the download stays refreshing until that run completes, and the rows the dataset then holds are charged at the live rate, so the credits in the response are an estimate until the download is ready. A failed refresh charges nothing. Answers 402 when the balance cannot cover the quote.

scope: datasets:export202 · application/json

Path parameters

  • datasetIdstringrequired

    Dataset id. It is the id of the scraper that fills it.

Body

  • maxAgeHoursintegeroptional

    Oldest data you accept, in hours. 0 always re-runs the scraper.

    Default24

  • formatstringoptional

    File format.

    DefaultcsvOne ofcsvjsonl

Request
curl -X POST "https://api.pandorlabs.com/v1/datasets/{datasetId}/downloads" \  -H "Authorization: Bearer $PANDORLABS_KEY" \  -H "Content-Type: application/json" \  -d '{    "maxAgeHours": 24,    "format": "csv"  }'
Response · 202
{  "id": "66f2c3d1e4b0a7d3c9f05678",  "datasetId": "reddit",  "status": "ready",  "freshness": "cached",  "format": "csv",  "maxAgeHours": 24,  "rowCount": 48210,  "credits": 483,  "createdAt": "2026-09-30T09:12:04.000Z",  "readyAt": "2026-09-30T09:12:04.000Z",  "expiresAt": "2026-10-01T09:12:04.000Z",  "links": {    "self": "/v1/downloads/66f2c3d1e4b0a7d3c9f05678",    "file": "/v1/downloads/66f2c3d1e4b0a7d3c9f05678/file"  }}
GET/v1/downloads

List your downloads

Downloads of your account, newest first.

scope: datasets:export200 · application/json

Query parameters

  • pageintegeroptional

    Page number, starting at 1.

    Default1

  • limitintegeroptional

    Rows per page, up to 100.

    Default20

Request
curl "https://api.pandorlabs.com/v1/downloads?page=1&limit=20" \  -H "Authorization: Bearer $PANDORLABS_KEY"
Response · 200
{  "data": [    {      "id": "66f2c3d1e4b0a7d3c9f05678",      "datasetId": "reddit",      "status": "ready",      "freshness": "cached",      "format": "csv",      "maxAgeHours": 24,      "rowCount": 48210,      "credits": 483,      "createdAt": "2026-09-30T09:12:04.000Z",      "readyAt": "2026-09-30T09:12:04.000Z",      "expiresAt": "2026-10-01T09:12:04.000Z",      "links": {        "self": "/v1/downloads/66f2c3d1e4b0a7d3c9f05678",        "file": "/v1/downloads/66f2c3d1e4b0a7d3c9f05678/file"      }    }  ],  "page": 1,  "limit": 20,  "total": 1,  "hasNext": false}
GET/v1/downloads/{downloadId}

Get a download

Status and price of one download. Poll this until the status is ready; a download waiting on credits is charged again on each call.

scope: datasets:export200 · application/json

Path parameters

  • downloadIdstringrequired

    Download id, returned when the download was created.

Request
curl "https://api.pandorlabs.com/v1/downloads/{downloadId}" \  -H "Authorization: Bearer $PANDORLABS_KEY"
Response · 200
{  "id": "66f2c3d1e4b0a7d3c9f05678",  "datasetId": "reddit",  "status": "ready",  "freshness": "cached",  "format": "csv",  "maxAgeHours": 24,  "rowCount": 48210,  "credits": 483,  "createdAt": "2026-09-30T09:12:04.000Z",  "readyAt": "2026-09-30T09:12:04.000Z",  "expiresAt": "2026-10-01T09:12:04.000Z",  "links": {    "self": "/v1/downloads/66f2c3d1e4b0a7d3c9f05678",    "file": "/v1/downloads/66f2c3d1e4b0a7d3c9f05678/file"  }}
GET/v1/downloads/{downloadId}/file

Fetch the file

The dataset as CSV or JSONL. A paid download can be fetched any number of times for 24 hours. Answers 409 while it is refreshing or when it failed, 402 while it waits on credits, 410 once it expired.

scope: datasets:export200 · text/csv

Path parameters

  • downloadIdstringrequired

    Download id, returned when the download was created.

Request
curl "https://api.pandorlabs.com/v1/downloads/{downloadId}/file" \  -H "Authorization: Bearer $PANDORLABS_KEY"

Datasets

Everything a scraper has ever collected. A scraper and its dataset share one id. List and describe datasets here; the rows themselves are delivered as a paid download.

GET/v1/datasets

List datasets

Every dataset you can read, with its row count.

scope: datasets:read200 · application/json

Takes no parameters.

Request
curl "https://api.pandorlabs.com/v1/datasets" \  -H "Authorization: Bearer $PANDORLABS_KEY"
Response · 200
{  "data": [    {      "id": "reddit",      "name": "Reddit",      "description": "Posts from the subreddits and keywords you name.",      "category": "social_media",      "rowCount": 48210,      "tags": [        "reddit",        "posts"      ],      "updatedAt": "2026-09-30T08:00:00.000Z"    }  ]}
GET/v1/datasets/{datasetId}

Describe a dataset

One dataset and its columns. Rows have no declared schema, so the fields are discovered from a sample, with the share of rows that carry each one.

scope: datasets:read200 · application/json

Path parameters

  • datasetIdstringrequired

    Dataset id. It is the id of the scraper that fills it.

Request
curl "https://api.pandorlabs.com/v1/datasets/{datasetId}" \  -H "Authorization: Bearer $PANDORLABS_KEY"
Response · 200
{  "id": "reddit",  "name": "Reddit",  "description": "Posts from the subreddits and keywords you name.",  "category": "social_media",  "rowCount": 48210,  "tags": [    "reddit",    "posts"  ],  "updatedAt": "2026-09-30T08:00:00.000Z",  "fields": [    {      "name": "title",      "type": "string",      "coverage": 1    }  ]}

Lookups

Fetch individual profiles on demand, from cache or live, paid in credits.

GET/v1/lookups/pricing

Lookup pricing

Credits per result for each platform, cached and live.

Any valid key200 · application/json

Takes no parameters.

Request
curl "https://api.pandorlabs.com/v1/lookups/pricing" \  -H "Authorization: Bearer $PANDORLABS_KEY"
POST/v1/lookups/{platform}

Look up profiles

Fetch up to 100 profiles in one call. Spends credits from your balance.

scope: profiles:lookup200 · application/json

Path parameters

  • platformstringrequired

    Platform to look up on.

    One oflinkedin

Body

  • targetsstring[]required

    Profile URLs or handles, 1 to 100 of them.

  • freshnessstringoptional

    cached answers from stored profiles; live fetches each one now.

    One ofcachedlive

Request
curl -X POST "https://api.pandorlabs.com/v1/lookups/{platform}" \  -H "Authorization: Bearer $PANDORLABS_KEY" \  -H "Content-Type: application/json" \  -d '{    "targets": [],    "freshness": "cached"  }'
GET/v1/credits

Credit balance

Your balance and recent cached versus live usage.

scope: profiles:lookup200 · application/json

Takes no parameters.

Request
curl "https://api.pandorlabs.com/v1/credits" \  -H "Authorization: Bearer $PANDORLABS_KEY"

Vector sync

Push a dataset into Pinecone or Qdrant as embedded chunks and keep it in sync.

GET/v1/vector-destinations

List vector destinations

Your Pinecone and Qdrant destinations.

scope: datasets:vector200 · application/json

Takes no parameters.

Request
curl "https://api.pandorlabs.com/v1/vector-destinations" \  -H "Authorization: Bearer $PANDORLABS_KEY"
POST/v1/vector-destinations

Create a vector destination

Connect a dataset to a vector database.

scope: datasets:vector201 · application/json

Body

  • namestringrequired

    Label for the destination.

  • providerstringrequired

    Vector database.

    One ofpineconeqdrant

  • endpointstringrequired

    Index host or cluster URL.

  • apiKeystringrequired

    Credential for the vector database. Stored encrypted.

  • datasetIdstringrequired

    Dataset to push.

  • collectionstringoptional

    Qdrant collection name.

  • namespacestringoptional

    Pinecone namespace.

  • filtersstring[]optional

    field:operator:value clauses narrowing the rows to push.

  • textFieldsstring[]optional

    Fields to embed.

  • metadataFieldsstring[]optional

    Fields to store beside each vector.

  • autoSyncbooleanoptional

    Sync again whenever the dataset is refreshed.

Request
curl -X POST "https://api.pandorlabs.com/v1/vector-destinations" \  -H "Authorization: Bearer $PANDORLABS_KEY" \  -H "Content-Type: application/json" \  -d '{    "name": "name",    "provider": "pinecone",    "endpoint": "endpoint",    "apiKey": "apiKey",    "datasetId": "datasetId",    "collection": "collection",    "namespace": "namespace",    "filters": [],    "textFields": [],    "metadataFields": [],    "autoSync": false  }'
GET/v1/vector-destinations/{id}

Get a vector destination

One destination and its settings.

scope: datasets:vector200 · application/json

Path parameters

  • idstringrequired

    Vector destination id (UUID).

Request
curl "https://api.pandorlabs.com/v1/vector-destinations/{id}" \  -H "Authorization: Bearer $PANDORLABS_KEY"
PATCH/v1/vector-destinations/{id}

Update a vector destination

Change any of the fields accepted on creation. Fields left out keep their value.

scope: datasets:vector200 · application/json

Path parameters

  • idstringrequired

    Vector destination id (UUID).

Request
curl -X PATCH "https://api.pandorlabs.com/v1/vector-destinations/{id}" \  -H "Authorization: Bearer $PANDORLABS_KEY"
DELETE/v1/vector-destinations/{id}

Delete a vector destination

Remove the destination. Vectors already pushed are kept.

scope: datasets:vector204 · application/json

Path parameters

  • idstringrequired

    Vector destination id (UUID).

Request
curl -X DELETE "https://api.pandorlabs.com/v1/vector-destinations/{id}" \  -H "Authorization: Bearer $PANDORLABS_KEY"
GET/v1/vector-destinations/{id}/syncs

List syncs

Past syncs of one destination.

scope: datasets:vector200 · application/json

Path parameters

  • idstringrequired

    Vector destination id (UUID).

Request
curl "https://api.pandorlabs.com/v1/vector-destinations/{id}/syncs" \  -H "Authorization: Bearer $PANDORLABS_KEY"
POST/v1/vector-destinations/{id}/syncs

Start a sync

Chunk, embed and upsert the dataset now.

scope: datasets:vector202 · application/json

Path parameters

  • idstringrequired

    Vector destination id (UUID).

Request
curl -X POST "https://api.pandorlabs.com/v1/vector-destinations/{id}/syncs" \  -H "Authorization: Bearer $PANDORLABS_KEY"
GET/v1/vector-syncs/{id}

Get a sync

Progress of one sync.

scope: datasets:vector200 · application/json

Path parameters

  • idstringrequired

    Sync id (UUID).

Request
curl "https://api.pandorlabs.com/v1/vector-syncs/{id}" \  -H "Authorization: Bearer $PANDORLABS_KEY"

Previews

Render the site behind a URL as a PNG screenshot.

POST/v1/preview

Render a preview

A PNG screenshot of the site behind a URL.

scope: preview:render200 · image/png

Body

  • urlstringrequired

    http or https URL to render.

  • fullPagebooleanoptional

    Capture the whole page rather than the viewport.

  • widthintegeroptional

    Viewport width, 320 to 2560.

  • heightintegeroptional

    Viewport height, 240 to 4000.

  • maxAgeSecondsintegeroptional

    Accept a cached render no older than this.

  • filenamestringoptional

    Name of the downloaded file, without the extension.

Request
curl -X POST "https://api.pandorlabs.com/v1/preview" \  -H "Authorization: Bearer $PANDORLABS_KEY" \  -H "Content-Type: application/json" \  -d '{    "url": "url",    "fullPage": false,    "width": 0,    "height": 0,    "maxAgeSeconds": 0,    "filename": "filename"  }'

Account

What the presented key can do, and the machine-readable spec.

GET/v1/

Inspect the key

The scopes and rate limit of the presented key, and the endpoints it can reach.

Any valid key200 · application/json

Takes no parameters.

Request
curl "https://api.pandorlabs.com/v1/" \  -H "Authorization: Bearer $PANDORLABS_KEY"
GET/v1/openapi.json

OpenAPI document

This API as an OpenAPI 3.1 document, generated from the live scraper catalogue with one typed operation per scraper. No key needed.

No key needed200 · application/json

Takes no parameters.

Request
curl "https://api.pandorlabs.com/v1/openapi.json"

© 2026 PandorLabs LLC. All rights reserved.