Skip to content

Incident History#

Every incident carries a history: a time-ordered list of comments from your organisation, updates from PhishFort analysts, and automated status lines. Each entry tells you who wrote it through authorDetails, a structured object you can filter on directly, so you can tell client messages from PhishFort updates, and automated lines from human ones, without parsing any text.

If you are building a new integration, build on authorDetails. Integrations written against the older author string and type field keep working; see Legacy structure and Migrating an Existing Integration.

Enabled per account

authorDetails is switched on per account. Contact PhishFort support to enable it for yours before you go live. Until it is enabled, history entries carry only the legacy fields, so treat authorDetails as optional in your parser.

Where it appears#

authorDetails is returned on each history entry from the single-incident endpoints:

Method Endpoint
GET /v1/incident/{id}
GET /v1/incident/subject/{subject}

It needs no extra request parameters. When it's enabled for your account, it's present on every history entry those endpoints return.

Structure#

{
    "id": string,
    "timestamp": Date,
    "message": string,
    "authorDetails": {
        "type": "client" | "phishfort",
        "clientId": string | null,
        "email": string | null,
        "automated": boolean
    },
    "author": string, // display string, e.g. "PhishFort System" or "Client (jane@acme.com)"
    "type": "info" | "info/auto" | "info/comment" // legacy classification, see below
}
Field Type Meaning
id string Stable identifier for the entry. Use it to de-duplicate entries across polls.
timestamp Date When the entry was recorded, ISO 8601 in UTC.
message string The comment, update, or status line.
authorDetails.type "client" | "phishfort" client for messages and takedown approvals or declines from your organisation. phishfort for everything else, including analyst updates and automated status lines.
authorDetails.clientId string | null The id of the client that wrote the entry, when it can be matched to exactly one of the clients your API key covers. Always null for phishfort entries.
authorDetails.email string | null The responder's email address, when the entry was recorded with one (for example a comment posted with an author email, or a takedown approval). Always null for phishfort entries.
authorDetails.automated boolean true for automated, system-generated entries such as Awaiting client approval or Takedown initiated. Always false for client entries.
author string Human-readable author, suitable for display. Do not parse it; use authorDetails instead.
type string Legacy classification. See Legacy structure.

Some entries carry extra fields for specific events, such as the reopen object on a re-open or takedown review request. See Re-open and takedown review requests.

Response Example#

{
    "data": {
        "id": "054zKkjCnR1I3B3U812z",
        "history": [
            {
                "id": "5521874",
                "timestamp": "2026-09-10T08:12:04.000Z",
                "message": "Awaiting client approval",
                "authorDetails": {
                    "type": "phishfort",
                    "clientId": null,
                    "email": null,
                    "automated": true
                },
                "author": "PhishFort System",
                "type": "info/auto"
            },
            {
                "id": "5521908",
                "timestamp": "2026-09-10T09:30:47.000Z",
                "message": "Takedown approved by jane@acme.com",
                "authorDetails": {
                    "type": "client",
                    "clientId": null,
                    "email": "jane@acme.com",
                    "automated": false
                },
                "author": "Client (jane@acme.com)",
                "type": "info"
            },
            {
                "id": "5521911",
                "timestamp": "2026-09-10T09:31:02.000Z",
                "message": "Please prioritise this one, it is being shared with our customers.",
                "authorDetails": {
                    "type": "client",
                    "clientId": "a1B2c3D4e5F6g7H8i9J0",
                    "email": null,
                    "automated": false
                },
                "author": "Client (Acme Corp)",
                "type": "info/comment"
            },
            {
                "id": "5522046",
                "timestamp": "2026-09-10T11:05:19.000Z",
                "message": "We've contacted the hosting provider and are waiting on a response.",
                "authorDetails": {
                    "type": "phishfort",
                    "clientId": null,
                    "email": null,
                    "automated": false
                },
                "author": "PhishFort",
                "type": "info"
            }
        ]
    }
}

Other incident fields are left out above for brevity. See DetailedIncidentStructure for the full response.

Usage Examples#

import requests

incident_id = "054zKkjCnR1I3B3U812z"
response = requests.get(
    f"https://capi.phishfort.com/v1/incident/{incident_id}",
    headers={"accept": "application/json", "x-api-key": "YOUR_API_KEY"},
)
history = response.json()["data"].get("history", [])

# authorDetails is optional: skip entries without it
detailed = [e for e in history if "authorDetails" in e]

client_entries = [e for e in detailed if e["authorDetails"]["type"] == "client"]
analyst_updates = [
    e for e in detailed
    if e["authorDetails"]["type"] == "phishfort" and not e["authorDetails"]["automated"]
]
const incidentId = "054zKkjCnR1I3B3U812z";
const response = await fetch(
  `https://capi.phishfort.com/v1/incident/${incidentId}`,
  { headers: { accept: "application/json", "x-api-key": "YOUR_API_KEY" } }
);
const { data } = await response.json();

// authorDetails is optional: skip entries without it
const detailed = (data.history ?? []).filter((e) => e.authorDetails);

const clientEntries = detailed.filter((e) => e.authorDetails.type === "client");
const analystUpdates = detailed.filter(
  (e) => e.authorDetails.type === "phishfort" && !e.authorDetails.automated
);

Legacy structure#

Before authorDetails, the only way to classify a history entry was to match the author display string or the type field. Both are still returned on every entry, so integrations built this way keep working.

Supported, but plan to move

The legacy classification is still supported. It will be deprecated in the future, and we will give you plenty of notice before anything changes. We encourage you to switch to authorDetails whenever you can; the migration steps below show how to do it without a cutover.

author#

author Meaning
PhishFort System Automated status lines, e.g. Awaiting client approval, Takedown initiated, Approval received - takedown proceeding.
PhishFort Messages and updates from PhishFort analysts.
Client (<sender>) Messages from your organisation (via the dashboard or POST /incident/{id}/comment), and takedown approvals or declines. <sender> is the responder's email or display name; when none is stored the value is Client.

type#

type Meaning
info/auto Automated status line.
info Analyst update, or a takedown approval or decline from your organisation.
info/comment Comment from your organisation.

How the two line up#

type author authorDetails.type authorDetails.automated
info/auto PhishFort System phishfort true
info PhishFort phishfort false
info/comment Client (<sender>) or Client client false
info Client (<sender>) or Client (takedown approvals and declines) client false

Migrating an Existing Integration#

If your integration already classifies history entries by matching the author string or checking type, you can move it to authorDetails without a cutover date. author and type keep their current values, so nothing breaks while you switch.

1. Map your existing checks#

If you check today Use instead
author == "PhishFort System" or type == "info/auto" authorDetails.type == "phishfort" and authorDetails.automated == true
author == "PhishFort" authorDetails.type == "phishfort" and authorDetails.automated == false
author starts with Client authorDetails.type == "client"
The email inside Client (<sender>) authorDetails.email
Which of your clients wrote the entry authorDetails.clientId (new, no string equivalent)

Two checks don't have a direct authorDetails equivalent:

  • Comments vs. takedown responses. Both are client entries. If you need to tell them apart, keep using type (info/comment for comments) or the message text (Takedown approved by …, Takedown declined by …).
  • Display names. When Client (<sender>) holds a display name rather than an email, authorDetails.email is null. If you show the sender to your users, keep showing author.

2. Accept the field in your parser#

Add authorDetails as an optional field on your history entry model. If your client rejects unknown fields (strict schemas, generated SDKs with additionalProperties: false), update it before the field is enabled for your account.

3. Read authorDetails first, fall back to author#

Wrap the classification in one helper that prefers authorDetails and falls back to the old string checks when it's missing. The same code then works before and after the field is enabled for your account.

def classify(entry):
    """Returns (kind, automated): kind is "client" or "phishfort"."""
    details = entry.get("authorDetails")
    if details:
        return details["type"], details["automated"]

    # Fallback for entries without authorDetails
    author = entry.get("author", "")
    if author == "PhishFort System":
        return "phishfort", True
    if author.startswith("Client"):
        return "client", False
    return "phishfort", False
// Returns { kind: "client" | "phishfort", automated: boolean }
function classify(entry) {
  if (entry.authorDetails) {
    return {
      kind: entry.authorDetails.type,
      automated: entry.authorDetails.automated,
    };
  }

  // Fallback for entries without authorDetails
  const author = entry.author ?? "";
  if (author === "PhishFort System") return { kind: "phishfort", automated: true };
  if (author.startsWith("Client")) return { kind: "client", automated: false };
  return { kind: "phishfort", automated: false };
}

4. Ask PhishFort support to enable it#

Once your integration handles the field, contact PhishFort support to switch authorDetails on for your account. Then call GET /v1/incident/{id} for an incident with a known history and confirm every entry includes authorDetails and that your classification is unchanged.

5. Remove the fallback (optional)#

After it's enabled, every history entry from the single-incident endpoints carries authorDetails, so you can drop the string checks. Keeping the fallback is harmless, and it protects you if you use the same code against an account that doesn't have the field enabled.

Notes#

  • author and type are unchanged. authorDetails is added alongside them, so existing integrations keep working.
  • Don't rely on clientId or email being set on client entries. Each one is filled in only when the entry carries that information.
  • For how history entries fit into the incident response, see IncidentHistory.