> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pulsedive.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CSV Export

> Configure and automate bulk indicator exports from Pulsedive in CSV format.

export const FeedDownloadSample = () => {
  const FIELDS = [{
    name: "id",
    display: "Indicator ID"
  }, {
    name: "type",
    display: "Type"
  }, {
    name: "risk",
    display: "Risk"
  }, {
    name: "threats",
    display: "Threats"
  }, {
    name: "feeds",
    display: "Feeds"
  }, {
    name: "usersubmissions",
    display: "User Submission Count"
  }, {
    name: "riskfactors",
    display: "Risk Factors"
  }, {
    name: "reference",
    display: "Reference URL"
  }];
  const TYPES = [{
    name: "domain",
    display: "Domain"
  }, {
    name: "ip",
    display: "IP"
  }, {
    name: "ipv6",
    display: "IPv6"
  }, {
    name: "url",
    display: "URL"
  }];
  const RISKS = [{
    name: "unknown",
    display: "Unknown",
    color: "#9d9d9d",
    icon: "question"
  }, {
    name: "none",
    display: "Very Low",
    color: "#14c914",
    icon: "smile"
  }, {
    name: "low",
    display: "Low",
    color: "#e3d23d",
    icon: "circle"
  }, {
    name: "medium",
    display: "Medium",
    color: "#de8602",
    icon: "circle"
  }, {
    name: "high",
    display: "High",
    color: "#e83838",
    icon: "circle"
  }, {
    name: "critical",
    display: "Critical",
    color: "#7e39b3",
    icon: "dot"
  }];
  const RiskIcon = ({icon, color}) => {
    if (icon === "question") return <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{
      marginRight: 4,
      flexShrink: 0
    }}>
        <circle cx="12" cy="12" r="10" />
        <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
        <line x1="12" y1="17" x2="12.01" y2="17" />
      </svg>;
    if (icon === "smile") return <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{
      marginRight: 4,
      flexShrink: 0
    }}>
        <circle cx="12" cy="12" r="10" />
        <path d="M8 14s1.5 2 4 2 4-2 4-2" />
        <line x1="9" y1="9" x2="9.01" y2="9" />
        <line x1="15" y1="9" x2="15.01" y2="9" />
      </svg>;
    if (icon === "dot") return <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" style={{
      marginRight: 4,
      flexShrink: 0
    }}>
        <circle cx="12" cy="12" r="10" />
        <circle cx="12" cy="12" r="3" fill={color} />
      </svg>;
    return <svg width="16" height="16" viewBox="0 0 24 24" style={{
      marginRight: 4,
      flexShrink: 0
    }}>
        <circle cx="12" cy="12" r="10" fill={color} />
      </svg>;
  };
  const TIMESTAMPS = [{
    name: "seen",
    display: "Last seen"
  }, {
    name: "added",
    display: "First added"
  }];
  const PERIODS = [{
    name: "all",
    display: "All time"
  }, {
    name: "year",
    display: "Last Year"
  }, {
    name: "month",
    display: "Last Month"
  }, {
    name: "week",
    display: "Last Week"
  }, {
    name: "day",
    display: "Last Day"
  }];
  const RETIRED = [{
    name: "true",
    display: "Include"
  }, {
    name: "false",
    display: "Active only"
  }];
  const HEADERS = [{
    name: "true",
    display: "Print"
  }, {
    name: "false",
    display: "None"
  }];
  const BLUE = "#006699";
  const BLUE_LIGHT = "#3094c2";
  const GREY = "#3c3c3e";
  const WHITE = "#ffffff";
  const BORDER = "#e2e8f0";
  const [fields, setFields] = React.useState(FIELDS.map(f => f.name));
  const [header, setHeader] = React.useState(["true"]);
  const [types, setTypes] = React.useState(TYPES.map(t => t.name));
  const [risk, setRisk] = React.useState(RISKS.map(r => r.name));
  const [timestamp, setTimestamp] = React.useState(["seen"]);
  const [period, setPeriod] = React.useState(["all"]);
  const [retired, setRetired] = React.useState(["true"]);
  const [copied, setCopied] = React.useState(false);
  const params = new URLSearchParams();
  params.set("header", header[0]);
  params.set("fields", fields.join(","));
  params.set("types", types.join(","));
  params.set("risk", risk.join(","));
  params.set("retired", retired[0]);
  if (timestamp[0] === "added") {
    params.set("added", period[0]);
  } else {
    params.set("seen", period[0]);
  }
  const url = "https://pulsedive.com/export/sample?" + params.toString().replace(/%2C/gi, ",");
  const handleCopy = () => {
    navigator.clipboard.writeText(url).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const handleDownload = () => window.open(url, "_blank");
  const toggleMulti = (name, selected, options, setSelected) => {
    if (name === "__all__") {
      const allSelected = options.every(o => selected.includes(o.name));
      setSelected(allSelected ? [] : options.map(o => o.name));
    } else {
      setSelected(selected.includes(name) ? selected.filter(s => s !== name) : [...selected, name]);
    }
  };
  const toggleRadio = (name, setSelected) => setSelected([name]);
  const tagStyle = active => ({
    display: "inline-flex",
    alignItems: "center",
    padding: "0 1em",
    borderRadius: 15,
    border: "none",
    outline: active ? "2px solid #7caac1" : "none",
    background: "#123f58",
    color: "#edf0f2",
    fontSize: 13,
    cursor: "pointer",
    lineHeight: "30px",
    fontFamily: "inherit",
    verticalAlign: "top",
    wordBreak: "break-all",
    userSelect: "none"
  });
  const [hoveredTag, setHoveredTag] = React.useState(null);
  const rowStyle = {
    display: "grid",
    gridTemplateColumns: "140px 1fr",
    gap: "0.5rem 1rem",
    alignItems: "start",
    marginBottom: "1.25rem"
  };
  const labelStyle = {
    fontWeight: 500,
    color: WHITE,
    paddingTop: 5,
    fontSize: 13,
    margin: 0
  };
  const tagRowStyle = {
    display: "flex",
    flexWrap: "wrap",
    columnGap: 6,
    rowGap: 10
  };
  const CopyIcon = () => <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
      <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
    </svg>;
  const CheckIcon = () => <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <polyline points="20 6 9 17 4 12" />
    </svg>;
  return <div style={{
    backgroundColor: "#0F3143",
    borderRadius: "5px",
    padding: "1.25rem 1.5rem",
    fontFamily: "inherit",
    fontSize: 14
  }}>

      {}
      <Info>
        This form downloads a sample of our data as a preview and for testing purposes. <strong>Do not automate.</strong>
      </Info>
      
      {}
      <div style={rowStyle}>
        <label style={labelStyle}>CSV fields</label>
        <div style={tagRowStyle}>
          <button onMouseEnter={() => setHoveredTag("fields__all__")} onMouseLeave={() => setHoveredTag(null)} style={{
    ...tagStyle(FIELDS.every(f => fields.includes(f.name))),
    background: FIELDS.every(f => fields.includes(f.name)) || hoveredTag === "fields__all__" ? "#1d4f6b" : "#123f58"
  }} onClick={() => toggleMulti("__all__", fields, FIELDS, setFields)}>
            All
          </button>
          {FIELDS.map(f => {
    const active = fields.includes(f.name);
    return <button key={f.name} onMouseEnter={() => setHoveredTag("fields__" + f.name)} onMouseLeave={() => setHoveredTag(null)} style={{
      ...tagStyle(active),
      background: active || hoveredTag === "fields__" + f.name ? "#1d4f6b" : "#123f58"
    }} onClick={() => toggleMulti(f.name, fields, FIELDS, setFields)}>
                {f.display}
              </button>;
  })}
        </div>
      </div>

      {}
      <div style={rowStyle}>
        <label style={labelStyle}>Header row</label>
        <div style={tagRowStyle}>
          {HEADERS.map(h => {
    const active = header[0] === h.name;
    return <button key={h.name} onMouseEnter={() => setHoveredTag("header__" + h.name)} onMouseLeave={() => setHoveredTag(null)} style={{
      ...tagStyle(active),
      background: active || hoveredTag === "header__" + h.name ? "#1d4f6b" : "#123f58"
    }} onClick={() => toggleRadio(h.name, setHeader)}>
                {h.display}
              </button>;
  })}
        </div>
      </div>

      {}
      <div style={rowStyle}>
        <label style={labelStyle}>Indicator types</label>
        <div style={tagRowStyle}>
          <button onMouseEnter={() => setHoveredTag("types__all__")} onMouseLeave={() => setHoveredTag(null)} style={{
    ...tagStyle(TYPES.every(t => types.includes(t.name))),
    background: TYPES.every(t => types.includes(t.name)) || hoveredTag === "types__all__" ? "#1d4f6b" : "#123f58"
  }} onClick={() => toggleMulti("__all__", types, TYPES, setTypes)}>
            All
          </button>
          {TYPES.map(t => {
    const active = types.includes(t.name);
    return <button key={t.name} onMouseEnter={() => setHoveredTag("types__" + t.name)} onMouseLeave={() => setHoveredTag(null)} style={{
      ...tagStyle(active),
      background: active || hoveredTag === "types__" + t.name ? "#1d4f6b" : "#123f58"
    }} onClick={() => toggleMulti(t.name, types, TYPES, setTypes)}>
                {t.display}
              </button>;
  })}
        </div>
      </div>

      {}
      <div style={rowStyle}>
        <label style={labelStyle}>Indicator risk</label>
        <div style={tagRowStyle}>
          <button onMouseEnter={() => setHoveredTag("risk__all__")} onMouseLeave={() => setHoveredTag(null)} style={{
    ...tagStyle(RISKS.every(r => risk.includes(r.name))),
    background: RISKS.every(r => risk.includes(r.name)) || hoveredTag === "risk__all__" ? "#1d4f6b" : "#123f58"
  }} onClick={() => toggleMulti("__all__", risk, RISKS, setRisk)}>
            All
          </button>
          {RISKS.map(r => {
    const active = risk.includes(r.name);
    return <button key={r.name} onMouseEnter={() => setHoveredTag("risk__" + r.name)} onMouseLeave={() => setHoveredTag(null)} style={{
      ...tagStyle(active),
      background: active || hoveredTag === "risk__" + r.name ? "#1d4f6b" : "#123f58"
    }} onClick={() => toggleMulti(r.name, risk, RISKS, setRisk)}>
                <RiskIcon icon={r.icon} color={r.color} />
                {r.display}
              </button>;
  })}
        </div>
      </div>

      {}
      <div style={rowStyle}>
        <label style={labelStyle}>Timestamp</label>
        <div style={tagRowStyle}>
          {TIMESTAMPS.map(t => {
    const active = timestamp[0] === t.name;
    return <button key={t.name} onMouseEnter={() => setHoveredTag("timestamp__" + t.name)} onMouseLeave={() => setHoveredTag(null)} style={{
      ...tagStyle(active),
      background: active || hoveredTag === "timestamp__" + t.name ? "#1d4f6b" : "#123f58"
    }} onClick={() => toggleRadio(t.name, setTimestamp)}>
                {t.display}
              </button>;
  })}
        </div>
      </div>

      {}
      <div style={rowStyle}>
        <label style={labelStyle}>Time period</label>
        <div style={tagRowStyle}>
          {PERIODS.map(p => {
    const active = period[0] === p.name;
    return <button key={p.name} onMouseEnter={() => setHoveredTag("period__" + p.name)} onMouseLeave={() => setHoveredTag(null)} style={{
      ...tagStyle(active),
      background: active || hoveredTag === "period__" + p.name ? "#1d4f6b" : "#123f58"
    }} onClick={() => toggleRadio(p.name, setPeriod)}>
                {p.display}
              </button>;
  })}
        </div>
      </div>

      {}
      <div style={rowStyle}>
        <label style={labelStyle}>Retired indicators</label>
        <div style={tagRowStyle}>
          {RETIRED.map(r => {
    const active = retired[0] === r.name;
    return <button key={r.name} onMouseEnter={() => setHoveredTag("retired__" + r.name)} onMouseLeave={() => setHoveredTag(null)} style={{
      ...tagStyle(active),
      background: active || hoveredTag === "retired__" + r.name ? "#1d4f6b" : "#123f58"
    }} onClick={() => toggleRadio(r.name, setRetired)}>
                {r.display}
              </button>;
  })}
        </div>
      </div>

      {}
      <div style={rowStyle}>
        <label style={labelStyle}>Direct URL</label>
        <div style={{
    display: "flex",
    alignItems: "center",
    gap: 8,
    border: `1px solid ${BORDER}`,
    borderRadius: 6,
    padding: "0.4rem 0.6rem",
    background: "#b8cede",
    minWidth: 0
  }}>
          <span style={{
    flex: 1,
    wordBreak: "break-all",
    fontFamily: "monospace",
    fontSize: 11.5,
    color: GREY,
    lineHeight: 1.5
  }}>
            {url}
          </span>
          <button onClick={handleCopy} title="Copy URL" style={{
    flexShrink: 0,
    background: "none",
    border: "none",
    cursor: "pointer",
    color: copied ? "#16a34a" : BLUE_LIGHT,
    padding: 2,
    display: "flex",
    alignItems: "center"
  }}>
            {copied ? <CheckIcon /> : <CopyIcon />}
          </button>
        </div>
      </div>

      {}
      <div style={{
    textAlign: "center",
    marginTop: "1.25rem"
  }}>
        <button onClick={handleDownload} style={{
    background: BLUE,
    color: "#fff",
    border: "none",
    borderRadius: 6,
    padding: "0.5rem 1.5rem",
    fontSize: 14,
    fontWeight: 500,
    cursor: "pointer",
    fontFamily: "inherit"
  }}>
          Download Sample
        </button>
      </div>

    </div>;
};

<Callout icon="map-pin" color="#8b5cf6" iconType="regular">
  To use this feature, you must have a Pulsedive [Feed](https://pulsedive.com/about/feed) plan.
  Your export access and available options are determined by your plan.
</Callout>

Pulsedive's CSV export delivers a filtered subset of Pulsedive's indicator dataset as a flat file.
Configure your Direct URL with the filters you need, then point your tooling at it to automate recurring ingestion.

## The Direct URL

The Direct URL is a GET request to Pulsedive's CSV export endpoint with your API key and chosen filters as query parameters.
A Direct URL looks like this:

```
https://pulsedive.com/export/csv?key=<YOUR_API_KEY>&header=true&fields=id,type,risk,threats,feeds,usersubmissions,riskfactors,reference&types=domain,ip,ipv6,url&risk=unknown,none,low,medium,high,critical&retired=true&seen=all
```

<Warning>
  Your Direct URL contains your API key.
  Treat it as a secret.
  Do not commit it to version control or share it publicly.
</Warning>

To find your API key, visit your [Pulsedive account page](https://pulsedive.com/account/).

## Try It

<FeedDownloadSample />

## Fields

Use the `fields` parameter to control which columns appear in your CSV output.
The indicator value is always included as the first column, regardless of which fields you select.

| Field                 | Parameter value     | Description                                                                                                     |
| --------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------- |
| Indicator value       | *(always included)* | The indicator itself: an IP address, domain, URL, or IPv6 address.                                              |
| Indicator ID          | `id`                | Pulsedive's unique internal identifier for the indicator.                                                       |
| Type                  | `type`              | The indicator type: `ip`, `ipv6`, `domain`, or `url`.                                                           |
| Risk                  | `risk`              | The indicator's risk level, and retirement status if applicable. See [Reading the Output](#reading-the-output). |
| Threats               | `threats`           | Threat names associated with the indicator, comma-separated.                                                    |
| Feeds                 | `feeds`             | Source feeds that reference the indicator, in `Feed Name:Organization` format, comma-separated.                 |
| User Submission Count | `usersubmissions`   | The number of times Pulsedive users have submitted this indicator.                                              |
| Risk Factors          | `riskfactors`       | The risk factors contributing to the indicator's score, comma-separated.                                        |
| Reference URL         | `reference`         | A direct link to the indicator's page on pulsedive.com.                                                         |

## Filters

Use filters to limit your export to the indicators that matter for your use case. The options available to you depend on your Feed plan.

### Indicator Types

Filter by indicator type using the `types` parameter. Accepts a comma-separated list.

| Value    | Description    |
| -------- | -------------- |
| `ip`     | IPv4 addresses |
| `ipv6`   | IPv6 addresses |
| `domain` | Domain names   |
| `url`    | URLs           |

All Feed plans include all four indicator types.

### Indicator Risk

Filter by risk level using the `risk` parameter. Accepts a comma-separated list.

| Value      | Description                   |
| ---------- | ----------------------------- |
| `unknown`  | Risk could not be determined. |
| `none`     | Benign or very low risk.      |
| `low`      | Low risk.                     |
| `medium`   | Medium risk.                  |
| `high`     | High risk.                    |
| `critical` | Critical risk.                |

Risk level availability varies by plan.
To view which risk levels are available on your plan, visit [pulsedive.com/about/feed](https://pulsedive.com/about/feed).

### Timestamp

Use the `seen` or `added` parameter to filter by time period.
Use `seen` to filter by when an indicator was last seen.
Use `added` to filter by when it was first added to Pulsedive.

Both parameters accept the same values:

| Value   | Description |
| ------- | ----------- |
| `all`   | All time    |
| `year`  | Last year   |
| `month` | Last month  |
| `week`  | Last week   |
| `day`   | Last day    |

The time period available to you depends on your Feed plan. To view plan details, visit [pulsedive.com/about/feed](https://pulsedive.com/about/feed).

### Retired Indicators

Use the `retired` parameter to control whether retired indicators appear in your export.

| Value   | Description                                 |
| ------- | ------------------------------------------- |
| `true`  | Include both active and retired indicators. |
| `false` | Include active indicators only.             |

Access to retired indicators depends on your Feed plan.

### Header Row

Use the `header` parameter to include or exclude a column header row as the first line of the CSV.

| Value   | Description                             |
| ------- | --------------------------------------- |
| `true`  | Include a header row with column names. |
| `false` | Omit the header row.                    |

## Reading the Output

A few fields in the CSV output have formatting behaviors worth knowing before you build a parser.

### Risk and Retirement Status

The `risk` field combines an indicator's risk level and its retirement status in a single value.
When an indicator is retired, Pulsedive appends `:retired` to the risk level.

For example, an indicator with a high risk score that has since been retired appears as:

```
high:retired
```

Check for the `:retired` suffix if your pipeline needs to handle active and retired indicators differently.

### Multiple Values in a Single Field

The `threats`, `feeds`, and `riskfactors` fields may contain multiple values.
Pulsedive separates multiple values within a single field with a comma. Single fields that contain multiple values are encapsulated in quotes, which most CSV parsers will handle correctly.

When parsing these fields, treat the comma as an inner delimiter within the field value, not as a column separator.

### Feeds Format

Each entry in the `feeds` field follows `Feed Name:Organization` format. For example:

```
Zeus Bad Domains:abuse.ch,Tor IPs:dan.me.uk
```

### Empty Fields

Fields with no data output as empty.
Your parser should handle empty values for any optional field.

***

<Tip>
  Not sure whether CSV or STIX/TAXII 2.1 is the right format for your use case?
  Visit [Choose an Export Format](/export/formats).
</Tip>
