Skip to content

Page Lookout API version 1.2.0

Getting started

This guide takes you from nothing to the findings of a fresh scan: create an API key, find your organization and site ids, start a scan, wait for it, and read what it found. Every request is plain HTTPS with a bearer token, so curl is enough.

1. Create an API key

An organization admin creates keys on the Integrations screen of the dashboard: New credential, a name that says what will use it, the permissions (scopes) it needs, and when it expires (90 days by default; 1 to 365 days, or never).

ScopeLets the key
scans:readlist sites, list and read scan runs and their tasks, comparisons, consent inventories (and their CSV downloads), the approved third-party list and the dashboard summaries
scans:writestart a scan of a site
findings:readread findings, issues and the rules behind them
reports:readread report snapshots and PDF report states, and download the PDFs

For the calls below the key needs scans:read, scans:write and findings:read.

The key is shown once, in the dialog that follows - copy it into your secret store before you close the dialog. Page Lookout keeps only a one-way hash of it and cannot show it again. It looks like plk_live_<key id>_<secret>; the dashboard later shows only its first part and last four characters, so you can tell keys apart.

Send it on every request:

Authorization: Bearer plk_live_...

A key works only inside its own organization and only for the operations listed in the API index. An organization can have at most 10 active keys.

2. Find your ids

The Integrations screen shows the two values every call needs: the API base URL and the organization id (the dialog that shows a new key repeats the organization id). The examples below read them, and the key, from environment variables:

export PAGE_LOOKOUT_API_URL="https://api.example.invalid"   # the API base URL
export PAGE_LOOKOUT_ORG_ID="11111111-1111-4111-8111-111111111111"
export PAGE_LOOKOUT_API_KEY="plk_live_..."                 # from your secret store

A site id comes from the site list. The API calls a site a domain:

curl -sS "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/domains" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"
{
  "items": [
    {
      "id": "22222222-2222-4222-8222-222222222222",
      "hostname": "shop.example.com",
      "baseUrl": "https://shop.example.com/",
      "displayName": "Shop",
      "isActive": true,
      "tags": [{ "id": "44444444-4444-4444-8444-444444444444", "name": "shop", "color": "blue" }]
    }
  ]
}

(Shortened: every site carries more fields - its last scan and its next scheduled scan among them. tags are the labels your organization put on the site, by name, [] when there are none; a key reads them but cannot change them. The reference lists them all.) A site with "isActive": false is paused by Page Lookout: no scan of it starts until it is resumed.

3. Start a scan

export SITE_ID="22222222-2222-4222-8222-222222222222"

curl -sS -X POST \
  "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/domains/$SITE_ID/scans" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

The request takes no body: the scan uses the site's stored scan settings. The answer is 201 with the new scan run, "status": "queued"; its id is the scanRunId of every call below. A scan started with a key is subject to every limit a scan started in the dashboard is:

4. Wait for it to finish

Read the run until its status is one of completed, partially_completed or failed:

export SCAN_RUN_ID="33333333-3333-4333-8333-333333333333"

curl -sS "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/scans/$SCAN_RUN_ID" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

A scan takes minutes, not seconds - how long depends on the size of the site. Poll every 15 to 30 seconds rather than in a tight loop: every request counts against the key's per-minute limit (Errors and limits). Better still, let Page Lookout tell you: a scan.completed webhook arrives when the run ends, with the same scanRunId.

partially_completed means some of the work could not be done - a page that did not answer, a module that failed - and the run's moduleOutcomes say which. What the other modules observed is stored and readable as usual.

A module's rawResultsMissing above zero is not a failure: those checks completed and their findings are stored, but the raw result behind them could not be saved. The next scan saves a new one.

5. Read the findings

curl -sS \
  "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/scans/$SCAN_RUN_ID/findings?limit=50&offset=0" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

The answer is one page of findings, worst first, with the total that matches your filters:

{ "items": [], "total": 0, "offset": 0 }

Filters narrow it: severity (repeatable: ?severity=critical&severity=high), status, module, ruleId, url and more - see listScanRunFindings. The same filters work across a whole site (listDomainFindings) and across the organization (listOrganizationFindings).

Accessibility findings can be filtered by the WCAG 2.2 criterion their check relates to and by its level: ?wcag=1.4.3 (repeatable: a finding that cites any of the criteria matches) and ?wcagLevel=AA (repeatable, and the level is the one WCAG gives the criterion). With both, the level narrows the criteria: a finding matches when it cites a criterion that is one of the numbers and of one of the levels. The same two filters work on a site's issues (listDomainIssues), and every finding and issue carries the criteria it relates to as wcagCriteria. A number that is not a WCAG 2.2 criterion is a 400 that says what to enter.

The consent scan records which third parties a site's pages contact, which cookies they set and which local and session storage keys they write - before any choice, after Reject all and after Accept all. Page Lookout merges that over the pages of a scan into an inventory, and the API serves it with the scans:read scope. It records names only: never a cookie's or a stored value.

export TAG_ID="44444444-4444-4444-8444-444444444444"   # a tag of your organization

# One site: the third parties in its newest inventory.
curl -sS \
  "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/domains/$SITE_ID/consent-inventory/trackers?limit=50" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

# Every site at once: the cookies seen after Reject all, on the sites carrying one tag.
curl -sS \
  "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/consent-inventory/cookies?scenario=reject_all&tagId=$TAG_ID" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

As a CSV file

Every list also has a .csv form: add the suffix to the path (.../consent-inventory/trackers.csv, cookies.csv, and under a site storage-keys.csv). It answers the same filters and the same sort as the list, as a file:

curl -sS -OJ \
  "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/consent-inventory/trackers.csv?category=advertising" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

Opening the file in a spreadsheet. Excel in a language whose list separator is a semicolon (Polish, German, French and many others) does not split a comma file that is opened by double-clicking it: every row lands in one column. Open it instead through Data → From Text/CSV, choose UTF-8 as the file origin and Comma as the delimiter, then Load. LibreOffice Calc asks the same questions in its text import dialog when you open the file: choose Unicode (UTF-8) and tick Comma as the separator.

Read your organization's classifications

An organization admin can classify the organization's own cookies and storage keys in the dashboard (for all sites or for one). Scans use the answer from each site's next scan, ahead of Page Lookout's tables, and every answer is marked as the organization's. Three reads, with the scans:read scope:

# The entries, in pages (follow nextCursor as cursor until it is null).
curl -sS \
  "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/consent-classifications?kind=cookie&limit=50" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

# The set one site's next scan would use (block and version are null when no entry applies).
curl -sS \
  "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/domains/$SITE_ID/consent-classifications/effective" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

A consent scan can also read what each page declared about the visitor's choice: the IAB consent string (decoded) and the consent states Google tags sent under Google Consent Mode, in the three situations the scan checks - before a choice, after Reject all and after Accept all. Two reads, with the scans:read scope, take the id of a scan run:

curl -sS "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/scans/$SCAN_RUN_ID/consent-signals?limit=25" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

The same rows as a file, one line per page and scenario, with the header Page path, Scenario, TCF platform id, Applies to visitor, String, Purposes granted, Special features, Vendors with consent, Vendors with legitimate interest, the four Consent Mode states and Google requests:

curl -sS -OJ \
  "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/scans/$SCAN_RUN_ID/consent-signals.csv" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

The file follows the same rules as the other CSV files above (byte-order mark, CRLF, at most 10 000 rows with X-Rows-Truncated, the apostrophe guard) and is named <host>-consent-signals-<date>.csv.

Read the approved third-party list

An organization can keep a list of the third parties it expects on its sites; while the list is on, every consent scan reports a third party it sees that is not on the list as a finding. What the list is, and how an administrator turns it on, is explained in the dashboard's Settings → Approved third parties. A key reads it (the scans:read scope) and cannot change it.

# Is the list on, which version is it, how many entries.
curl -sS \
  "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/consent-approvals" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

# The entries, in pages.
curl -sS \
  "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/consent-approvals/trackers?limit=50" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

# The list one site is judged against.
curl -sS \
  "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/domains/$SITE_ID/consent-approvals/effective" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

# The findings for third parties that were not on the list (needs findings:read).
curl -sS \
  "$PAGE_LOOKOUT_API_URL/organizations/$PAGE_LOOKOUT_ORG_ID/scans/$SCAN_RUN_ID/findings?ruleId=consent.vendors.unapproved_tracker" \
  -H "Authorization: Bearer $PAGE_LOOKOUT_API_KEY"

Pagination

Lists are paged in one of four ways, and it is worth knowing which:

OperationPaginationlimit: range (default)
listScanRunFindingslimit + offset1-200 (50)
listDomainFindingslimit + offset1-200 (50)
listOrganizationFindingslimit + offset1-200 (50)
listDomainIssueslimit + offset1-200 (50)
listDomainIssuePageslimit + offset1-200 (50)
listDomainConsentInventoryTrackerslimit + offset1-200 (50)
listDomainConsentInventoryCookieslimit + offset1-200 (50)
listDomainConsentInventoryStorageKeyslimit + offset1-200 (50)
listOrganizationConsentInventoryTrackerslimit + offset1-200 (50)
listOrganizationConsentInventoryCookieslimit + offset1-200 (50)
listOrganizationConsentInventoryTrackerSiteslimit + cursor1-200 (50)
listOrganizationConsentInventoryCookieSiteslimit + cursor1-200 (50)
listApprovedTrackerslimit + cursor1-200 (50)
listOrganizationConsentClassificationslimit + cursor1-200 (50)
listScanRunConsentSignalslimit + cursor1-100 (25)
listReportDocumentslimit + offset1-100 (20)
listScanRunslimit only1-100 (20)
listDomainScanRunslimit only1-100 (20)
listScanTaskslimit only1-500 (100)
listScanRunClosedFindingslimit only1-200 (50)
listDomainsnone-
listOrganizationFindingRulesnone-

listDomainIssuePages also answers truncated: when it is true, total is a lower bound.

Next