v1 · JSON
API reference

Script your morning brief.

One read-only endpoint, GET /api/v1/findings, returns the signed-in tenant's findings fused across subdomains and exposed cloud buckets. Designed for cron jobs, Slack bots, and CI checks — not for replacing the human who reads it.

Endpoint

One read. Your tenant only.

MethodGET
Path/api/v1/findings
Authbetter-auth session — cookie or bearer token
Responseapplication/json

Every request is tenant-scoped to the signed-in user — the response only contains findings for domains you own. There is no global or admin scope; use the dashboard for cross-tenant work.

Query parameters

Keyset pagination + CSV filters.

?limitinteger 1–100
Defaults to 20, capped at 100.

Maximum number of items per page. Larger pages make pagination faster; 20 is a good default for cron jobs.

?limit=50

?cursoropaque token
Keyset pagination cursor.

Pass the nextCursor value from the previous response. Omit on the first page. Tokens are short-lived base64url; treat them as opaque.

?cursor=eyJsYXN0U2Vlbi…

?severitycsv enum
critical · high · medium · low

Single value or comma-separated. `?severity=critical` and `?severity=critical,high` both work. Pass nothing to see every severity.

?severity=critical,high

?statuscsv enum
open · fixed · suppressed

Single value or comma-separated. Useful for paging only what is currently open, or only what you have already suppressed.

?status=open

?domainstring
Narrow to one enrolled domain.

Filter findings to a single enrolled domain name. Cross-tenant values are rejected with 403.

?domain=acme.co

Response shape

What you get back.

Every row in items carries the brief essentials: identifier, severity, status, the asset it lives on, a one-line evidence snippet, and a suggested next-step action you can drop straight into a ticket.

{
  "items": [
    {
      "id": "ckxyz123…",
      "kind": "subdomain",
      "severity": "critical",
      "status": "open",
      "evidence": "Certificate Transparency log entry for staging.acme.co — current host status: LIVE.",
      "nextAction": "Investigate ownership and put a 404/redirect in front, or add to your allow list.",
      "lastSeenAt": "2026-04-12T10:14:22.000Z",
      "asset": {
        "name": "staging.acme.co",
        "domainId": "ckdom456…",
        "domainName": "acme.co"
      }
    },
    {
      "id": "ckbucket789…",
      "kind": "bucket",
      "severity": "critical",
      "status": "open",
      "evidence": "World-listable bucket acme-prod-backups (us-east-1), discovered via CNAME chain.",
      "nextAction": "Restrict the bucket policy/ACL and rotate any leaked credentials.",
      "lastSeenAt": "2026-04-11T18:02:09.000Z",
      "asset": {
        "name": "acme-prod-backups",
        "domainId": "ckdom456…",
        "domainName": "acme.co"
      }
    }
  ],
  "nextCursor": "eyJsYXN0U2VlbkF0IjoiMjAyNi0wNC0xMVQxODowMjowOS4wMDBaIiwiaWQiOiJrYnVjazEyMyIsImtpbmQiOiJidWNrZXQifQ"
}
id
Stable identifier for the finding — useful for cursor pagination and idempotent ticket creation.
kind
Either "subdomain" or "bucket" — the asset class the finding belongs to.
severity
Always lowercase: "critical" | "high" | "medium" | "low". Buckets are mapped to synthetic severities at the API edge.
status
Always lowercase: "open" | "fixed" | "suppressed". Reflects the suppression-aware derivation published with the dashboard.
evidence
One-line evidence snippet (e.g. "Certificate Transparency log entry for staging.acme.co — current host status: LIVE.").
nextAction
Suggested next-step remediation. Static one-liner you can drop into a Jira ticket body.
lastSeenAt
When the finding was last observed, ISO-8601 UTC. Drives keyset ordering.
asset
{ name, domainId, domainName } — the asset (subdomain host or bucket name) and its enrolled parent domain.
nextCursor
Opaque token to pass as ?cursor= on the next page; null when this is the last page.
curl examples

From your terminal.

Cookie auth (browser session)
# Export the cookie your browser keeps for app.forewatch.example
# (the better-auth .session_token cookie). Then page through results.
curl -s -G \
  --cookie "better-auth.session_token=$FOREWATCH_SESSION" \
  "https://app.forewatch.example/api/v1/findings?limit=20&severity=critical,high"
Bearer auth (CI / server jobs)
# In CI, export a bearer token issued for your service account.
export FOREWATCH_TOKEN="…"
curl -s -G \
  -H "Authorization: Bearer $FOREWATCH_TOKEN" \
  "https://app.forewatch.example/api/v1/findings?limit=20"
Unauthenticated → 401
# No cookie, no bearer — the route handler rejects you.
$ curl -i "https://app.forewatch.example/api/v1/findings"
HTTP/1.1 401 Unauthorized
content-type: application/json

{"error":"Unauthorized"}

Cookies and bearer tokens are interchangeable — the platform's better-auth session middleware accepts either. The 401 response above is what you get when neither is present.

Pagination

Re-supply nextCursor until it is null.

Responses include a nextCursor opaque token whenever there is at least one more page. Pass it back as ?cursor= on the next request; when the field is null, you have read the last page for this filter.

  1. 01Request page 1Hit the endpoint with no ?cursor= — get 20 items.
  2. 02Re-supply the cursorPass the response's nextCursor back as ?cursor=.
  3. 03Stop when nullA nextCursor: null means you hit the last page.
Errors

What you get when it does not work.