Skip to content

Takedown Attempts#

An incident is not always taken down once. A phishing site that PhishFort takes down can come back and need another takedown. This endpoint lists your incidents and returns every takedown attempt for each one: when it started, when it finished, and how it ended.

Use it to answer questions such as:

  • How many times has PhishFort had to take this site down?
  • How long did each takedown take?
  • Which incidents came back after a successful takedown?

For where an incident stands right now, use the status field on the single incident endpoint.

Access

Takedown attempts are enabled per account. If your requests return 403 with "Incident resolution history is not enabled for this account", contact PhishFort support to request access.

Endpoint#

GET /v1/incidents/resolutions

Requires an API key with the incidents:read scope. /v1/incident/resolutions works as well.

Parameters#

All parameters are optional. The filters behave the same way as on the incident list.

Parameter Type Required Description
limit integer No Incidents per page, from 1 to 100. Defaults to 25.
cursor string No Use the paging.next value from the previous response to fetch the next page.
clientId string No Only return incidents for this sub-client. Only for clients with managed sub-clients.
fromDate string No Only incidents created after this date. ISO 8601 format.
toDate string No Only incidents created before this date. ISO 8601 format.
status string No Filter by current incident status. See Status Values.
incidentType string No Filter by observable type. See Incident Types.
reinitiatedAfter string No Only incidents where a new takedown started after an earlier one finished. success: the earlier takedown succeeded. resolved: the earlier takedown ended with any outcome. See Find incidents taken down more than once.
minTakedownAttempts integer No Only incidents with at least this many takedown starts, from 1 to 100000. Attempts with an unknown startedAt do not count.
includeTotal boolean No When true, paging.total contains the number of matching incidents across all pages.
search string No Find incidents by incident ID or by the value under attack (URL, domain, email address, phone number or IP). Can only be combined with clientId and limit.

Note

fromDate and toDate filter on when the incident was created, not on when its takedown attempts happened.

Find incidents taken down more than once#

Use reinitiatedAfter and minTakedownAttempts to return only incidents that needed more than one takedown. For example, incidents where a new takedown started after a successful one:

curl -X GET 'https://capi.phishfort.com/v1/incidents/resolutions?reinitiatedAfter=success&limit=25' \
  -H 'accept: application/json' \
  -H 'x-api-key: YOUR_API_KEY'
Question Query parameters
Which incidents needed a new takedown after a successful one? reinitiatedAfter=success
Which incidents needed a new takedown after any finished one? reinitiatedAfter=resolved
Which incidents came back after a successful takedown and have at least three takedown starts? reinitiatedAfter=success&minTakedownAttempts=3
Which of those are being taken down again right now? reinitiatedAfter=success&status=takedown_in_progress
Which incidents have at least two takedown starts, whatever the outcome? minTakedownAttempts=2

How the filters match:

  • reinitiatedAfter matches when an attempt with a known resolvedAt is followed by a later attempt with a known startedAt. The later attempt can still be in progress. An attempt whose resolvedAt is null never counts as the earlier, finished takedown.
  • minTakedownAttempts counts attempts with a known startedAt.
  • Both filters use recorded takedown attempts only. Takedowns from before attempts were recorded are not counted (see Reading Takedown Attempts).
  • A match shows that PhishFort started another takedown. On its own it does not confirm that the same content went back online.

The filters select incidents. Each matching incident is returned with all of its takedownAttempts, not only the attempts that matched. Filtering happens before pagination, so limit counts matching incidents and paging.next continues through matches only. Keep the same filters on every page.

You can combine these filters with clientId, incidentType, status, fromDate, toDate, cursor, limit and includeTotal. Combining them with search or statusVerbose returns 400. They are only available on this endpoint; sending them to GET /v1/incidents also returns 400.

With these filters, includeTotal=true checks every candidate incident and can make the request noticeably slower. Leave it out unless you need the overall count.

Request Example#

curl -X GET 'https://capi.phishfort.com/v1/incidents/resolutions?limit=25' \
  -H 'accept: application/json' \
  -H 'x-api-key: YOUR_API_KEY'
import requests

response = requests.get(
    "https://capi.phishfort.com/v1/incidents/resolutions",
    params={"limit": 25},
    headers={
        "accept": "application/json",
        "x-api-key": "YOUR_API_KEY",
    },
)
print(response.json())
const response = await fetch(
  "https://capi.phishfort.com/v1/incidents/resolutions?limit=25",
  {
    headers: {
      accept: "application/json",
      "x-api-key": "YOUR_API_KEY",
    },
  }
);
const data = await response.json();
console.log(data);

Response Example#

{
    "data": [
        {
            "id": "inc_7Kq2mZp9Lx",
            "subject": "https://example.test/login",
            "createdAt": "2026-09-15T08:00:00.000Z",
            "takedownAttempts": [
                {
                    "startedAt": "2026-09-15T08:10:00.000Z",
                    "resolvedAt": "2026-09-15T09:00:00.000Z",
                    "outcome": "success"
                },
                {
                    "startedAt": "2026-09-18T10:00:00.000Z",
                    "resolvedAt": null,
                    "outcome": null
                }
            ]
        },
        {
            "id": "inc_Qw81NcZr4T",
            "subject": "example-support.test",
            "createdAt": "2026-09-14T12:30:00.000Z",
            "takedownAttempts": []
        }
    ],
    "message": "success",
    "paging": {
        "next": "inc_Qw81NcZr4T",
        "limit": 25,
        "count": 2
    }
}

The first incident was taken down 50 minutes after the takedown started. The site came back, and a second takedown started on 18 September that has not finished yet. The second incident has no takedown attempts.

Response Fields#

data is an array of TakedownAttemptsStructure objects, one per incident, in the same order as the incident list.

Field Type Description
id string The incident ID, the same one the incident list returns.
subject string The URL, domain, email address, phone number or IP the incident is about.
createdAt string When the incident was created.
takedownAttempts array The incident's takedown attempts, oldest first. Empty when there are none.

Each takedown attempt contains:

Field Type Description
startedAt string | null When the takedown started.
resolvedAt string | null When the takedown finished. null while it is still in progress.
outcome string | null How the takedown ended (see below). null while it is still in progress.

All dates are ISO 8601 in UTC.

Outcome Values#

Value Meaning
success The content was taken down.
failed The takedown did not succeed.
not_actionable The incident could not be acted on.
closed The incident was closed without a takedown result.

Reading Takedown Attempts#

Attempts are recorded from the date this feature was enabled. Takedowns that happened earlier do not appear, so an older incident can return an empty takedownAttempts array even though it was taken down in the past.

Some attempts have missing values:

What you see What it means
startedAt set, resolvedAt and outcome are null The takedown has started but has not finished. If a later attempt follows it, the takedown was restarted before it finished.
startedAt is null The takedown started before attempts were recorded for this incident. resolvedAt and outcome show how it ended, when known.
resolvedAt is null but outcome is set The incident was already resolved before attempts were recorded. The outcome is known, the date is not.

A new takedown attempt can take a few minutes to appear here after it shows on the single incident endpoint.

Examples#

Get attempts for one incident#

Pass the incident ID as search:

curl -X GET 'https://capi.phishfort.com/v1/incidents/resolutions?search=inc_7Kq2mZp9Lx' \
  -H 'accept: application/json' \
  -H 'x-api-key: YOUR_API_KEY'

A search response has paging.limit and paging.count, and no cursor.

Count takedowns and find incidents that came back#

To list only incidents that came back, use reinitiatedAfter=success. To work out the same thing from a page you already have:

for (const incident of data) {
  const attempts = incident.takedownAttempts;
  const successes = attempts.filter((a) => a.outcome === "success");

  // A new attempt after a successful takedown means the site came back.
  const firstSuccess = attempts.findIndex((a) => a.outcome === "success");
  const cameBack = firstSuccess !== -1 && firstSuccess < attempts.length - 1;

  console.log(incident.id, {
    attempts: attempts.length,
    successes: successes.length,
    cameBack,
  });
}

Time to takedown#

from datetime import datetime

def parse(value):
    return datetime.fromisoformat(value.replace("Z", "+00:00"))

for incident in response.json()["data"]:
    for attempt in incident["takedownAttempts"]:
        if attempt["outcome"] == "success" and attempt["startedAt"] and attempt["resolvedAt"]:
            duration = parse(attempt["resolvedAt"]) - parse(attempt["startedAt"])
            print(incident["id"], duration)

Fetch every page#

import requests

url = "https://capi.phishfort.com/v1/incidents/resolutions"
headers = {"accept": "application/json", "x-api-key": "YOUR_API_KEY"}
params = {"limit": 100, "fromDate": "2026-09-01T00:00:00.000Z"}

incidents = []
while True:
    page = requests.get(url, params=params, headers=headers).json()
    incidents.extend(page["data"])
    if not page["data"] or not page["paging"].get("next"):
        break
    params["cursor"] = page["paging"]["next"]

Keep the same filters on every page and only change cursor.

Errors#

Status Cause What to do
400 Invalid parameter, such as a limit outside 1–100, an invalid date or status, or search combined with other filters. Fix the request.
400 reinitiatedAfter is not success or resolved, minTakedownAttempts is not a whole number from 1 to 100000, or either filter is combined with search or statusVerbose. Fix the request.
401 Missing or invalid API key, or a clientId your key does not cover. Check the key and clientId.
403 Incident resolution history is not enabled for this account. Contact PhishFort support to request access.
403 The API key does not have the incidents:read scope. Use a key with incidents:read.
404 An incident on the page could not be found. Retry the request.
502 / 503 Incident resolution history is temporarily unavailable. Retry later.

If any incident on a page fails, the whole request fails. A page never silently leaves incidents out, and an empty takedownAttempts array always means the incident has no recorded attempts.