An exposure is a place Kanary found a profile’s PII.
| Resource | Path | Purpose |
|---|---|---|
| Member exposures (collection) | /api/v1/members/{member_id}/exposures/ |
GET paginated exposures for one profile; POST report a missed exposure |
| Exposure | /api/v1/exposures/{exposure_id}/ |
GET detail |
| Status actions | /api/v1/exposures/{exposure_id}/not_about_me/ /api/v1/exposures/{exposure_id}/not_a_risk/ |
POST mark as ignored |
GET /api/v1/members/{member_id}/exposures/
Returns a paginated page of exposures for the member in the path. The member must belong to an account under the authenticated partner. See Pagination.
| Param | Default | Max | Description |
|---|---|---|---|
limit |
100 |
250 |
Exposures per page (Pagination) |
offset |
0 |
— | Starting index into the member’s exposure list |
GET /api/v1/members/67890/exposures/?limit=100&offset=0
{
"member_id": 67890,
"count": 1,
"next": null,
"previous": null,
"results": [
{
"exposure_id": 123456,
"url": "https://example-broker.com/profile/12345",
"scrape_task_id": 9001,
"exposure_status": "ACTIVE",
"site_id": 42,
"site_domain": "example-broker.com",
"site_threat_score": 7.5,
"contents": "Bob Example\n123 Main St, NYC ...",
"screenshot_url": "https://cdn.example.com/screenshots/abc.png?X-Amz-Signature=...",
"last_submitted_at": null
}
]
}
Poll by following next until it is null (see README — v1 limitations).
| Field | Type | Description |
|---|---|---|
member_id |
integer | Profile queried |
count |
integer | Total exposures for this member |
next |
string | null | URL for the next page, or null |
previous |
string | null | URL for the previous page, or null |
results |
array | Current page of exposures |
Each item in results includes:
| Field | Type | Description |
|---|---|---|
exposure_id |
integer | Primary key |
url |
string | null | Exposure URL; on some sites this may be a URL with an anchor fragment, or null when no direct URL exists |
scrape_task_id |
integer | null | Scan id that produced this exposure (null if none) |
exposure_status |
string | Enum string (see status values) |
reason |
string | null | Set when exposure_status is BLOCKED. null otherwise. See BLOCKED metadata |
action_required |
string | null | Set when exposure_status is BLOCKED. What the partner or user must do before Kanary can proceed. null otherwise |
site_id |
integer | null | Site id |
site_domain |
string | null | Site.domain for display; null when no site is linked |
site_threat_score |
number | Site-level privacy threat score (higher = higher risk). Not yet available — returns null until threat score data is wired |
contents |
string | null | Raw text scraped from the page |
screenshot_url |
string | null | Presigned URL from AWS when screenshots are enabled; null otherwise. Expires after a limited time—refetch the exposure for a fresh URL |
last_submitted_at |
string (ISO 8601, UTC) | null | When a mitigation, deletion, or escalation was last submitted |
exposure_status values:
| Value | Definition |
|---|---|
IGNORED |
User or partner marked not relevant / not a risk |
ACTIVE |
Removal in progress |
REMOVED |
Exposure removed |
BLOCKED |
Kanary cannot proceed. reason and action_required are set. See BLOCKED metadata |
When exposure_status is BLOCKED, the exposure includes reason and action_required. Both are null on every other status.
reason is one of:
"uncertain this is about the user""uncertain this is a risk to the user""unable to remove due to legal protections on data""unable to remove due to verification or authentication requirements""user not on eligible subscription for Kanary to take action"action_required is one of:
"confirm about you""confirm risk""submit manual request""upgrade""no recommended action"{
"exposure_id": 123456,
"exposure_status": "BLOCKED",
"reason": "user not on eligible subscription for Kanary to take action",
"action_required": "upgrade"
}
| Status | Condition |
|---|---|
400 |
Invalid limit or offset |
401 |
Missing or invalid API key |
403 |
Partner inactive |
404 |
Member not found or terminated |
GET /api/v1/exposures/{exposure_id}/
Returns detail for one exposure. If the id is invalid, the exposure belongs to a terminated member, or the exposure is not accessible to your partner, the API returns 404 Not Found (see Resource access).
GET /api/v1/exposures/123456/
200 OK){
"exposure_id": 123456,
"member_id": 67890,
"url": "https://example-broker.com/profile/12345",
"scrape_task_id": 9001,
"exposure_status": "ACTIVE",
"site_id": 42,
"site_domain": "example-broker.com",
"site_threat_score": 7.5,
"contents": "Bob Example\n123 Main St, New York, New York ...",
"screenshot_url": "https://cdn.example.com/screenshots/abc.png?X-Amz-Signature=...",
"last_submitted_at": "2026-05-18T10:42:00Z"
}
| Status | Condition |
|---|---|
401 |
Missing or invalid bearer token |
403 |
Partner inactive |
404 |
Exposure not found |
POST /api/v1/exposures/{exposure_id}/not_about_me
POST /api/v1/exposures/{exposure_id}/not_a_risk
Marks an exposure as ignored when it is not about the profile or not a risk. The status becomes IGNORED.
Matches made on phone or email alone can be about someone else who shares that contact data. Use not_about_me to record that the result is not about this user. This works on BLOCKED exposures as well.
POST /api/v1/exposures/123456/not_about_me
POST /api/v1/exposures/123456/not_a_risk
200 OK){
"exposure_id": 123456,
"member_id": 67890,
"exposure_status": "IGNORED",
"status_update": "not_about_me"
}
| Status | Condition |
|---|---|
401 |
Missing or invalid bearer token |
403 |
Partner inactive |
404 |
Exposure not found or member terminated |
POST /api/v1/members/{member_id}/exposures
Submits a URL Kanary did not find (manual exposure). Creates a new exposure for the profile.
Not available to trial members; returns 403 (error: trial_not_supported).
| Field | Required | Description |
|---|---|---|
url |
Yes | Full exposure URL |
name_id |
No | Name attribute id on the profile that is exposed |
address_id |
No | Address attribute id on the profile that is exposed |
email_id |
No | Email attribute id on the profile that is exposed |
phone_number_id |
No | Phone attribute id on the profile that is exposed |
username_id |
No | Username attribute id on the profile that is exposed |
exposed_on_google |
No | Default false, true when the finding is a Google exposure |
{
"url": "https://example-broker.com/profile/12345",
"name_id": 111,
"exposed_on_google": false
}
201 Created){
"exposure_id": 123457,
"member_id": 67890,
"url": "https://example-broker.com/profile/12345",
"exposure_status": "ACTIVE",
"site_id": 99,
"site_domain": "example-broker.com",
"site_threat_score": 7.5,
"scrape_task_id": null,
"contents": null,
"screenshot_url": null,
"last_submitted_at": null
}
| Status | Condition |
|---|---|
400 |
Missing url or invalid URL format |
401 |
Missing or invalid bearer token |
403 |
Partner inactive, or member is on a trial account (error: trial_not_supported) |
404 |
Member not found or terminated |
409 |
An exposure for this URL already exists for this member (ACTIVE, BLOCKED, or IGNORED). Use not_about_me / not_a_risk to update an existing exposure’s status instead of re-reporting. |
422 |
URL domain is known but has no removal source configured |
A subscription change from Advanced to Core sets exposures to BLOCKED when they are no longer eligible for removal on Core. The count is exposures_blocked on the subscription’s changes.
Trial members cannot suggest exposures: POST .../exposures returns 403. Set a subscription to report missed results.