Migrating from /v1 to /v2
/v2 is the canonical API: one concept has one name, identical in
the request and in the response. /v1 does not have that property,
and the cost is concrete - the scan-run identifier comes back from scans-feed
as projectNumber and has to be sent to status as
publicProjectId, so a value read from one operation cannot be pasted into
the next.
/v1 is fully supported
Nothing about /v1 has changed. Every endpoint, field name and response
shape is exactly as it was, and it remains a supported version of the API - no
deprecation has been announced and no end date is set. Migrate when it suits you.
What to change
- Swap the prefix. Every
/v1 operation has exactly one
/v2 counterpart, listed below. Paths are lowercase kebab-case, so
/v1/getauth becomes /v2/get-auth.
- Move the tenant to the header. No
/v2 request body
carries userPoolId. Send it as the userpoolid header, which
is where /v1 needed it on most operations anyway. This now includes
get-auth, the one /v1 endpoint that took it in the body.
- Rename the fields in the tables below.
/v2 rejects an
unrecognised field by name rather than ignoring it, so a missed rename
produces a message naming the offending field instead of a puzzling empty result.
- Expect fewer fields in some responses.
/v2 returns
only what a caller can act on, so a handful of values that /v1 passed
straight through from its data store are no longer included. Nothing that any
operation accepts as input was removed.
- Read failures from the status code. Every failure returns a
non-2xx status and one error envelope:
{success: false, statusCode, error, message, details?, path, timestamp}.
/v1 has two incompatible error shapes and answers HTTP 200 for some
upstream failures.
Endpoint map
| /v1 | /v2 |
|---|
POST /v1/getauth | POST /v2/get-auth |
POST /v1/scan | POST /v2/scan |
POST /v1/stop-scan | POST /v2/stop-scan |
POST /v1/status | POST /v2/status |
POST /v1/scans-feed | POST /v2/scans-feed |
POST /v1/scans-stats | POST /v2/scans-stats |
POST /v1/report | POST /v2/report |
POST /v1/add-domain | POST /v2/add-domain |
POST /v1/remove-domain | POST /v2/remove-domain |
GET /v1/scan-settings/{domain} | GET /v2/scan-settings/{domain} |
PUT /v1/scan-settings/{domain} | PUT /v2/scan-settings/{domain} |
GET /v1/organizations | GET /v2/organizations |
POST /v1/organizations | POST /v2/organizations |
GET /v1/organizations/{orgId} | GET /v2/organizations/{organizationId} |
DELETE /v1/organizations/{orgId} | DELETE /v2/organizations/{organizationId} |
POST /v1/add-keyword | POST /v2/add-keyword |
POST /v1/remove-keyword | POST /v2/remove-keyword |
POST /v1/actions/insights | POST /v2/actions/insights |
POST /v1/actions/dns-twist | POST /v2/actions/dns-twist |
POST /v1/actions/dns-twist/initiate | POST /v2/actions/dns-twist/initiate |
GET /v1/actions/dns-twist/status/{taskId} | GET /v2/actions/dns-twist/status/{taskId} |
POST /v1/actions/infostealers | POST /v2/actions/infostealers |
POST /v1/actions/infostealers/status | POST /v2/actions/infostealers/status |
POST /v1/actions/social-profiler/detect | POST /v2/actions/social-profiler/detect |
POST /v1/actions/social-profiler/progress | POST /v2/actions/social-profiler/progress |
POST /v1/actions/social-profiler/results | POST /v2/actions/social-profiler/results |
POST /v1/issues/download-csv | POST /v2/issues/download-csv |
POST /v1/password-spray | POST /v2/password-spray |
Field renames
Only the fields that changed are listed. Anything not mentioned keeps its
/v1 name.
POST /v2/get-auth
| Where | /v1 | /v2 | Why |
|---|
| request | userPoolId | removed - send the `userpoolid` header | The tenant travels in the header on every /v2 operation, with no exceptions. Sending it in the body is rejected. |
| response | whole body | authorization | Only the token is returned; nothing else the auth service produces is exposed. |
POST /v2/scan
| Where | /v1 | /v2 | Why |
|---|
| request | tld | domain | The top-level domain the asset belongs to. |
| response | publicProjectId | publicProjectId | Unchanged in name, now typed as a number in both directions - /v1 declared it a string in requests while returning a number. |
POST /v2/status, POST /v2/stop-scan, POST /v2/report
| Where | /v1 | /v2 | Why |
|---|
| request | publicProjectId | publicProjectId | Unchanged in name, now a number. Paste the value from POST /v2/scan or POST /v2/scans-feed directly. |
| response | progress | progress | Never null. /v1 forwards the backend null for a queued scan. |
POST /v2/scans-feed
| Where | /v1 | /v2 | Why |
|---|
| request | projectType | scanType | The kind of scan to filter by - the same concept initiate-scan calls `scanType` in the same API. |
| response | projectNumber | publicProjectId | The rename that mattered most: the identifier this operation returns is now spelled the way every operation that takes one spells it. |
| response | testCard | scanCard | Scan configuration of the run. |
| response | testCard.url | scanCard.asset | What was scanned. |
| response | testCard.tld | scanCard.domain | The domain it belongs to. |
| response | testCard.type | scanCard.scanType | The kind of scan. |
POST /v2/scans-stats
| Where | /v1 | /v2 | Why |
|---|
| response | data | countsByOrganization | `data` named the wrapper rather than the content. |
POST /v2/report
| Where | /v1 | /v2 | Why |
|---|
| response | testType | scanType | The kind of scan, spelled as elsewhere. |
POST /v2/add-domain
| Where | /v1 | /v2 | Why |
|---|
| request | data | domains | `data` said nothing about what it held. remove-domain already called the same list `domains`. |
| request | organisationId | organizationId | One spelling of the organization identifier, matching what GET /v2/organizations returns. |
GET /v2/organizations, POST /v2/organizations
| Where | /v1 | /v2 | Why |
|---|
| request | logoURL | logoUrl | camelCase, consistent with every other field. |
| response | id | organizationId | Names what the identifier identifies, so it can be pasted into any operation that takes one. |
GET /v2/organizations/{organizationId}
| Where | /v1 | /v2 | Why |
|---|
| request | orgId (path) | organizationId (path) | The same concept as the body field, now the same name. |
POST /v2/actions/insights, POST /v2/actions/dns-twist
| Where | /v1 | /v2 | Why |
|---|
| request | tlds | domains | A list of top-level domains, named as everywhere else. |
| response | data.insights | insights | The meaningless `data` wrapper is gone. |
| response | data.dnsTwist.dnsTwistData | findings | Two levels of wrapper removed. |
| response | data.dnsTwist.activityId | taskId | The identifier the status operation takes, so it is now the identifier this one returns. |
POST /v2/actions/dns-twist/initiate
| Where | /v1 | /v2 | Why |
|---|
| request | tld | domain | The domain to scan. |
| request | uid | removed | Never validated and never read back. Nothing to supply. |
| response | activityId | taskId | One name for the scan identifier. |
| response | success: false with HTTP 200 | HTTP 502 | A start that produced nothing pollable is now reported as a failure, including the case where /v1 answered success with an empty identifier. |
GET /v2/actions/dns-twist/status/{taskId}
| Where | /v1 | /v2 | Why |
|---|
| request | tld (query) | domain (query) | Optional either way; echoed back. |
| response | id / metaData.uuid | taskId | Two spellings of one value collapsed into one. |
| response | type / metaData.type | scanType | Likewise, one name for the kind of scan. |
| response | metaData.tld | domain | Flattened and renamed. |
POST /v2/actions/infostealers
| Where | /v1 | /v2 | Why |
|---|
| request | tlds | domains | Consistent with every other operation. |
| request | password_days | passwordDays | camelCase. |
| request | cookie_days | cookieDays | camelCase. |
| request | filter | removed | Accepted but never used by any downstream service. |
| response | current_page | page | camelCase pagination. |
| response | total_pages | totalPages | camelCase. |
| response | total_items | totalItems | camelCase. |
| response | has_next | hasNext | camelCase. |
| response | has_previous | hasPrevious | camelCase. |
| response | usernamesList | facets.usernames | The seven filter facets are grouped under `facets` instead of sitting loose beside the pagination fields. |
| response | hostnamesList | facets.hostnames | Grouped under `facets`. |
| response | ipList | facets.ips | Grouped, and pluralised: the field holds a list. |
| response | countryList | facets.countries | Grouped, and pluralised. |
| response | osList | facets.operatingSystems | Grouped, and spelled out: nothing else in the API abbreviates to `os`. |
| response | infostealersList | facets.infostealerTypes | Grouped, and named for what the values are: malware families. |
| response | keywordsList | facets.keywords | Grouped under `facets`. |
| response | error with HTTP 200 | HTTP 502 | A rejected query is a failure, not an empty result set. |
POST /v2/actions/infostealers/status
| Where | /v1 | /v2 | Why |
|---|
| response | raw | removed | It re-exposed the snake_case spellings this version renames. Every value it held is reported alongside it. |
POST /v2/password-spray
| Where | /v1 | /v2 | Why |
|---|
| request | domain | domains | The field was singular in name and an array in type. |
| response | no flag | success | Reports that the run was accepted and started; the backend is fire-and-forget, so there is no identifier to poll. |
POST /v2/actions/social-profiler/*
| Where | /v1 | /v2 | Why |
|---|
| response | task_id / scan_id | taskId | One name for the detection identifier, and the one you asked about rather than whatever the upstream echoed. |
| response | error with HTTP 200 | HTTP 4xx/5xx | A failed lookup is reported as a failure instead of a 200 carrying `status: "error"`. |