---
title: "Submit an async global CN/SAN sweep"
method: POST
path: "/v1/sweeps"
tags: ["sweeps"]
---

# Submit an async global CN/SAN sweep

`POST /v1/sweeps`

## Request

- Base URL: `https://api.ctindex.io`
- URL: `https://api.ctindex.io/v1/sweeps`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Submit a global (domain-less) attribution sweep as an async job.

Use this instead of `/v1/certificates/search` when a `cn` /
`san_contains` sweep needs to go deeper than the synchronous result
cap or would exceed the synchronous query budget. Consumes one
`global_sweep_per_hr` quota unit at submit; polling is free.

The attribution knobs (`strict_attribution`, `exclude_subject_o`,
`registrable_domain_denylist`) are **post-match filters** applied after
the candidate scan — they do not reduce scan cost and cannot prevent
`sweep_timeout`. To bound scan cost on a broad token, narrow `cn` /
`san_contains` or add `issuer` / `first_seen_*` / `not_after_*` windows.

**Continuation past the result cap:** when a finished sweep reports
`truncated: true`, its status response carries a `next_cursor` token.
Submit a NEW sweep with the SAME filters plus
`resume_token: <next_cursor>` to fetch the next chunk of up to
`result_cap` rows — keyset semantics, so no rows are skipped or
duplicated across the boundary. Repeat until `truncated` is false.
Example: sweep 1 (`{"cn": "acme"}`) → done, truncated, `next_cursor`
"eyJ2IjoxLC…" → sweep 2 (`{"cn": "acme", "resume_token": "eyJ2IjoxLC…"}`)
→ next 10 000 rows. Each continuation consumes one quota unit.

## Cookies

- `ctix_session` string, nullable

## Request body

- SweepSubmitRequest: Filter set for a global sweep. At least one of ``cn`` / ``san_contains`` is required (3+ chars, trigram-index-served); ``issuer`` alone is rejected. ``domain``-scoped queries belong on the synchronous ``/v1/certificates/search``.
  - `cn` string, nullable: Subject CN ILIKE substring (3+ chars)
  - `issuer` string, nullable: Issuer substring, matched against issuer CN and issuer organization. Only allowed composed with cn/san_contains.
  - `san_contains` string, nullable: SAN substring match across all names (3+ chars)
  - `is_wildcard` boolean, nullable
  - `is_precert` boolean, nullable
  - `expired` boolean, nullable: true = expired only; false = currently valid only
  - `first_seen_after` string, nullable: ISO-8601
  - `first_seen_before` string, nullable: ISO-8601
  - `not_after_after` string, nullable: ISO-8601
  - `not_after_before` string, nullable: ISO-8601
  - `strict_attribution` boolean, nullable: true = drop 'substring_collision', 'shared_hosting', and 'third_party_integration' rows from the persisted result set — these classes indicate legitimate third-party use, not brand abuse. 'first_party' and 'lookalike_candidate' rows are always retained. Default false. Post-match filter: applied after the candidate scan — does not reduce scan cost or prevent sweep_timeout.
  - `exclude_subject_o` string[], nullable: Exclude rows whose subject organization contains any of these substrings (case-insensitive), e.g. ["ADIDAS AG"]. Rows with no subject_o are kept. Max 20 entries. Post-match filter: applied after the candidate scan — does not reduce scan cost or prevent sweep_timeout.
  - `registrable_domain_denylist` string[], nullable: Exclude rows whose subject CN is, or is under, any of these registrable domains (eTLD+1 suffix match), e.g. ["silverstripe.com", "amazonaws.com"]. Max 20 entries. Post-match filter: applied after the candidate scan — does not reduce scan cost or prevent sweep_timeout.
  - `resume_token` string, nullable: Keyset continuation token (task #856). Pass the exact `next_cursor` value from a previous sweep result that had `truncated: true` (same filters!) to resume the sweep strictly after the last returned row. Continuation is deterministic — the result set is materialized under a stable total order (first_seen DESC, sha256 DESC), so pages never skip or duplicate rows across the boundary. Each continuation is a new sweep submission and consumes one global_sweep_per_hr quota unit. Do not construct this value yourself.
  - `first_party_domains` string[], nullable: The sweep target's OWN registrable domain(s), e.g. ["datadoghq.com", "datadoghq.eu"]. Any result name on one of these registrable domains is classed match_class = 'first_party' regardless of the certificate's subject organization — use this when the brand's domain does not contain the token as a whole label (datadog → datadoghq.com). Supplying this list makes it AUTHORITATIVE: a name on any OTHER registrable domain is no longer inferred first-party from the hostname alone, so a squat whose domain happens to carry the search token as a label (datadog.now, datadog.co.uk) stays visible as 'lookalike_candidate' instead of being filed as first-party. The brand's own certificates on domains you did not list still class 'first_party' when their subject organization proves it (match_reason 'subject_org_match;...' / 'first_party_org_match;...'); allowlist hits read 'first_party_allowlist;rd=...'. Classification-side only (does not filter the result set). Max 20 entries.
  - `match_class_filter` string, nullable: Optional comma-separated attribution classes to retain in polling results. Allowed values: first_party, lookalike_candidate, third_party_integration, substring_collision, shared_hosting. Values are normalized, de-duplicated, and persisted with the sweep.

## Response `202`

Successful Response

- SweepSubmitResponse
  - `job_id` string, uuid, required
  - `status` string, required
  - `result_cap` integer, required
  - `poll_url` string, required
  - `message` string, required

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/ctindex/apis/certindex-api.md) · [All operations](https://skmtc.dev/ctindex/apis/certindex-api/llms.txt) · [OpenAPI document](https://skmtc.dev/ctindex/apis/certindex-api/revisions/d5bc0ec85bc9?raw)
