Documentation

Reports API

Sightline is an agent-native reporting API. You commission a report on a domain you own, poll until it is ready, then read it back as structured findings, a full report, or a PDF. Accounts are by invitation. The reporting routes answer plain JSON objects, and errors follow RFC 9457 as application/problem+json. See Response shapes.

Base URL: https://api.seekingdatalabs.com. Every path below is relative to it. All examples use curl; substitute your own domain, report id, and API key.

Quick start

  1. Create a key. Sign in to the portal and create an API key on the Security page. It is shown once, on creation.
  2. Run a report on a verified domain you own: POST /v1/analysis/run. The response carries a report_id.
  3. Poll GET /v1/analysis/report/{id} until its status is ready.
  4. Fetch the findings, or the PDF.
The whole flow, end to end
# 1. The key from the portal Security page
export SEEKINGDATA_KEY="paste-your-key-here"

# 2. Run a report; note the report_id in the response
curl -sS -X POST \
  "https://api.seekingdatalabs.com/v1/analysis/run?domain=example.com&report_type=analyzer" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY"

# 3. Poll until "status" is "ready"
curl -sS "https://api.seekingdatalabs.com/v1/analysis/report/rep-0a1b2c3d4e5f" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY"

# 4. Fetch the findings, then the PDF
curl -sS "https://api.seekingdatalabs.com/v1/analysis/report/rep-0a1b2c3d4e5f/findings" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY"
curl -sS "https://api.seekingdatalabs.com/v1/analysis/report/rep-0a1b2c3d4e5f/pdf" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY" -o report.pdf

Access is covered under Access.

Authentication

Sign in to the portal at /app/login/ and create an API key on the Security page; the plaintext key is shown once, on creation, and never again. Sightline is invite-only: to ask for an account, write to hello@seekingdatalabs.com.

Every request is authenticated one of two ways: the browser session cookie (for calls made from the signed-in portal), or a Bearer API key in the Authorization header (for calls made by your own code or an agent). The examples below use a Bearer key.

The reporting flow

A report goes through three steps: commission a run, poll it until it is ready, then fetch the result. Runs are asynchronous: the run call returns immediately with a report id and a status, and the report fills in as the fleet works.

1. Run a report

POST /v1/analysis/run commissions a run. It requires a verified domain you own and an account that covers the report (see Access). The report_type query parameter selects the product: analyzer for the website audit (the default) or asvs for the OWASP ASVS security posture report. Any other value folds to analyzer.

Commission a run
curl -sS -X POST \
  "https://api.seekingdatalabs.com/v1/analysis/run?domain=example.com&report_type=analyzer" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY"

The response carries a report_id and a status.

2. Poll until ready

GET /v1/analysis/reports lists your runs. Fetch a single run with GET /v1/analysis/report/{id} and poll it until its status is ready.

The listing answers with the current report for each domain. Once a newer report replaces an older one, the older is marked superseded and left out of the list; pass ?archived=1 to get the whole history back, with an archived_at on every superseded run. Superseded reports keep resolving by id and by share link, so anything you have already handed out still opens.

List your runs, then poll one
curl -sS "https://api.seekingdatalabs.com/v1/analysis/reports" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY"

curl -sS "https://api.seekingdatalabs.com/v1/analysis/report/rep-0a1b2c3d4e5f" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY"

3. Fetch the report

Once a run is ready, the report is available in these forms:

  • GET /v1/analysis/report/{id}, the full report envelope.
  • GET /v1/analysis/report/{id}/findings, the structured findings for the run. Each finding carries sdl_id, the SDL-ID the PDF, the hosted report and the worklist print beside it (for example SDL-07), so a finding can be matched to its spreadsheet row. It is null for a security assessment, when the stored report cannot be read, or when the report prints no single number for the finding; it is never guessed. SDL-IDs are renumbered every report: to follow a finding across reports, use its signature.
  • GET /v1/analysis/report/{id}/pdf, the report as a PDF.
  • GET /v1/analysis/report/{id}/worklist.csv, the report's worklist as a spreadsheet file, one row per page and issue.
  • GET /v1/analysis/report/{id}/worklist.csv?view=issues, the same worklist as one row per SDL-ID in fix order, with every URL the issue reaches in one cell. Its last column, Stable keys, lists the row view's stable key for each page the issue reaches, one per line, so status ticks can be matched to the next report's issue list even though SDL-IDs and priorities are renumbered every run. A key built from the model's wording (no engine check, no quoted page text) can still drift between runs, so an unmatched key means check it by hand, never fixed.
  • GET /v1/analysis/report/{id}/worklist.csv?view=pages, the same worklist as one row per audited page, with the page's numbered to-do in fix order in one cell.
  • GET /v1/analysis/report/{id}/worklist.xlsx, the same worklist as one Excel workbook: the page, issue and row views as sheets, plus an About sheet naming the domain, the report and its date.
  • GET /v1/analysis/report/{id}/diff, the re-audit delta against the previous report as JSON. Every row carries latest_sdl_id and prior_sdl_id, the same values as the two SDL-ID columns of diff.csv (null where the file leaves the cell empty). Owner only.
  • GET /v1/analysis/report/{id}/diff.csv, the re-audit delta against the previous report, one row per finding. Owner only; there is no shared variant.
  • GET /v1/analysis/progress?domain={domain}, the open-issue trend across every report of a domain, oldest first: open counts by severity, and fixed, new, still open, regressed and not re-checked against each report's previous report. A finding whose page was not read again counts as not re-checked, never as fixed. A report with no countable findings shows null, never 0. Add format=csv for the same rows as a spreadsheet file. Owner only.
Read the findings, then the PDF
curl -sS "https://api.seekingdatalabs.com/v1/analysis/report/rep-0a1b2c3d4e5f/findings" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY"

curl -sS "https://api.seekingdatalabs.com/v1/analysis/report/rep-0a1b2c3d4e5f/pdf" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY" \
  -o report.pdf

Spreadsheet exports

The worklist and the re-audit delta download as CSV files a team can work through in a spreadsheet. They are built from the same report body the hosted page and the PDF read, so they add no claim the report does not make.

  • Each file starts with a UTF-8 byte order mark, so a spreadsheet opens umlauts correctly.
  • The delimiter follows the language: ; for a German export, , otherwise.
  • A cell a spreadsheet would read as a formula is prefixed with an apostrophe.
  • Only the website analysis has a worklist: a security assessment report answers 404, never an empty file.
  • The worklist's status column is left empty for the team to fill in.
  • The worklist also downloads as one Excel workbook, worklist.xlsx, with no byte order mark or delimiter to depend on. Its sheets are named in the report's language (Pages, Issues, All rows and About; in German Seiten, Probleme, Alle Zeilen and Info), and every cell is text, never a formula.
Download the worklist as a spreadsheet file
curl -sS "https://api.seekingdatalabs.com/v1/analysis/report/rep-0a1b2c3d4e5f/worklist.csv" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY" \
  -o worklist.csv

Sharing a report

POST /v1/analysis/report/{id}/share mints an unguessable share token; the matching DELETE revokes it. Anyone holding the token can then read the report with GET /v1/analysis/shared/{token}, which is public and needs no authentication. The token is the whole credential, so share it deliberately. The same token also reads the worklist with GET /v1/analysis/shared/{token}/worklist.csv (and ?view=issues or ?view=pages), and the workbook with GET /v1/analysis/shared/{token}/worklist.xlsx.

Mint a share token, then read it with no auth
curl -sS -X POST \
  "https://api.seekingdatalabs.com/v1/analysis/report/rep-0a1b2c3d4e5f/share" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY"

# The returned token reads publicly, no key:
curl -sS "https://api.seekingdatalabs.com/v1/analysis/shared/$SHARE_TOKEN"

Response shapes

A successful JSON read answers a plain object with the fields its route documents, and a list route carries its paging fields beside the rows. This is the findings list of one report:

200 · application/json, illustrative values
HTTP/1.1 200 OK
Content-Type: application/json

{
  "findings": [
    {
      "run_id": "rep-0a1b2c3d4e5f",
      "signature": "homepage.accessibility.1cd918b02c5e",
      "domain": "example.com",
      "surface": "Homepage",
      "bucket": "bugs",
      "severity": "medium",
      "kind": "accessibility",
      "category": null,
      "systemic": false,
      "state": "open",
      "detail": "[finding title]",
      "evidence_url": "https://example.com/",
      "generated_at": "2026-09-14T10:00:00.000Z",
      "sdl_id": "SDL-07"
    }
  ],
  "run_id": "rep-0a1b2c3d4e5f",
  "total": 1,
  "limit": 50,
  "offset": 0
}

Errors leave the success shape and follow RFC 9457. Check retryable before trying again, and wait retry_after seconds when it is present.

429 · application/problem+json
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 30

{
  "type": "https://seekingdatalabs.com/errors/rate-limit",
  "title": "Rate Limit Exceeded",
  "status": 429,
  "detail": "[what happened, one sentence]",
  "instance": "about:blank",
  "retryable": true,
  "retry_after": 30,
  "error_category": "rate_limit"
}

Access

Sightline is invite-only. A run your account does not cover is held awaiting_payment until it is settled, and queued once it is.

Account balance and transactions

GET /v1/credits returns your account balance and the recent movements that built it. The balance is the running sum of an append-only ledger, so the numbers always reconcile. Key fields:

  • balance_atomic, the running total over the whole ledger, in atomic USDC units (millionths). balance_usd is the same figure in dollars.
  • entries[], the recent movements, each with an id, created_at, a signed delta_atomic, a direction (credit or debit), a reason, a ref, and an actor.
  • next_before, the pagination cursor. Pass it back as before= to fetch the next page; it is null on the last page.
  • topup, how to pay in: a deposit address and the exact top-<account-id> memo that matches a transfer to your account (USDC on Solana). The memo is served complete; send it exactly, or the deposit cannot be matched to your account.
Read the ledger, then page with the cursor
curl -sS "https://api.seekingdatalabs.com/v1/credits?limit=25" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY"

# Next page: pass the previous response's next_before
curl -sS "https://api.seekingdatalabs.com/v1/credits?limit=25&before=1024" \
  -H "Authorization: Bearer $SEEKINGDATA_KEY"

A report your plan covers costs nothing extra. A transfer lands once it confirms on chain, not the instant it is sent.

Security assessments and consultancy are not self-serve

There is no API that runs a security assessment (a penetration test only with written authorisation for active testing). A security assessment is a scoped, authorized engagement: it runs only after an offline authorization and contract, and is dispatched internally by an operator. Nothing you can call here starts one.

What you can do through the API is record intent. A POST /v1/requests files a request for a human operator to review; it changes nothing about your account and starts no work. The body takes a kind of pentest, pentest_discussion, or consultancy, a domain (required for the pentest kinds; omitted for consultancy), and an optional free-text detail. The pentest kind names the contracted security assessment; the same request covers the vulnerability scan and, with authorisation, the penetration test.

Record intent for a human operator to review
curl -sS -X POST https://api.seekingdatalabs.com/v1/requests \
  -H "Authorization: Bearer $SEEKINGDATA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"pentest","domain":"example.com","detail":"Pre-launch review of the checkout flow."}'

A repeat request for the same domain and kind stacks rather than duplicating. An operator picks it up from there.