> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://help.malanta.ai/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# Malanta IOPA STIX/TAXII Feed - Integration Guide

# Malanta IOPA STIX/TAXII Feed

**TAXII 2.1 · STIX 2.1 · Pre-Attack Indicators · Clusters · Relationships**

Version 1.0 | October 2026

---

## **Contents**

- [Overview](#2-overview)
    - [Base URL](#3-base-url)
    - [Collection](#3-collection)
- [Authentication](#2-authentication)
- [Endpoints](#2-endpoints)
    - [Discovery](#3-discovery)
    - [API root](#3-api-root)
    - [List collections](#3-list-collections)
    - [Collection metadata](#3-collection-metadata)
    - [Get objects](#3-get-objects)
    - [Manifest](#3-manifest)
- [Pagination and incremental sync](#2-pagination-and-incremental-sync)
- [Recommended sync use cases](#2-recommended-sync-use-cases)
    - [Use case A — Full dump, then API intervals](#3-use-case-a-full-dump-then-api-intervals)
    - [Use case B — API full active set, then daily intervals](#3-use-case-b-api-full-active-set-then-daily-intervals)
    - [What not to do](#3-what-not-to-do)
- [STIX object model](#2-stix-object-model)
    - [Indicator](#3-indicator)
    - [Labels](#3-labels)
        - [Classification](#4-classification-always-one-of-these)
        - [Always added by the feed](#4-always-added-by-the-feed)
        - [APT attribution](#4-apt-attribution-apt-when-available)
        - [Other pass-through labels](#4-other-pass-through-labels-when-available)
        - [Cluster labels](#4-cluster-labels-on-intrusion-set)
    - [Cluster (intrusion-set)](#3-cluster-intrusion-set)
    - [Relationship](#3-relationship)
- [Platform setup examples](#2-platform-setup-examples)
    - [OpenCTI](#3-opencti)
    - [ThreatQ](#3-threatq)
    - [Python](#3-python)
    - [cURL smoke test](#3-curl-smoke-test)
- [Errors](#2-errors)
- [Limitations](#2-limitations)
- [Quick reference](#2-quick-reference)

---

## **Overview**

Malanta delivers pre-attack threat intelligence over **TAXII 2.1** as **STIX 2.1** objects. Use this guide to connect your TIP, SOAR, SIEM, or custom collector.

The feed exposes Malanta’s attack-infrastructure graph so you can:

* Ingest malicious / pre-attack **indicators** (primarily domains)
* See **clusters** of related attack infrastructure (`intrusion-set`)
* Follow **relationships** between indicators and clusters

Data is marked **TLP:AMBER**. Treat accordingly.

Support: [support@malanta.ai](mailto:support@malanta.ai)

### **Base URL**

| Field | Value |
| --- | --- |
| Discovery URL | `https://app.malanta.ai/feeds/taxii2` |
| API root | `https://app.malanta.ai/feeds/taxii2/default` |
| Protocol | TAXII 2.1 |
| Content | STIX 2.1 |

Always start from the **discovery** URL. Discovery advertises the API root; do not hardcode alternate hosts unless Malanta tells you to.

### **Collection**

| Collection ID | Title | Purpose |
| --- | --- | --- |
| `pre-attack-indicators` | Malanta Pre-Attack Indicators | Indicators, clusters, and indicator↔cluster relationships |

The collection is **read-only** (`can_read: true`, `can_write: false`).

[↑ Back to top](#2-contents)

---

## **Authentication**

All requests require your Malanta API key. Keys are prefixed with `malanta_`. A missing or invalid key is rejected before the request reaches the service (`401`/`403`).

Supported transports (first match wins):

| Priority | Method | Example |
| --- | --- | --- |
| 1 | `x-api-key` header | `x-api-key: malanta_xxxxxxxxxxxxx` |
| 2 | Bearer | `Authorization: Bearer malanta_xxxxxxxxxxxxx` |
| 3 | HTTP Basic | Username: any string · Password: your API key |

**Recommended:** `x-api-key`.

Basic Auth is provided for platforms (for example ThreatQ) that only expose username/password fields. Put the API key in the **password** field; username can be anything non-empty.

**Required headers**

```
Accept: application/taxii+json;version=2.1
x-api-key: malanta_xxxxxxxxxxxxx
```

Responses use `Content-Type: application/taxii+json;version=2.1` and include `X-TAXII-Content-Type` with the same media type.

Keep your API key private. Never expose it in frontend code, mobile apps, or public repositories.

[↑ Back to top](#2-contents)

---

## **Endpoints**

Base: `https://app.malanta.ai/feeds/taxii2`

Trailing slashes are accepted. Prefer **not** relying on HTTP redirects; redirects can drop auth headers.

### **Discovery**

**Endpoint:** `GET /taxii2`

Returns TAXII server information and available API roots.

**Example request**

```bash
curl --request GET \
  --url 'https://app.malanta.ai/feeds/taxii2' \
  --header 'Accept: application/taxii+json;version=2.1' \
  --header 'x-api-key: malanta_xxxxxxxxxxxxx'
```

### **API root**

**Endpoint:** `GET /taxii2/default`

Returns API root metadata (supported versions, max content length).

### **List collections**

**Endpoint:** `GET /taxii2/default/collections`

Returns available collections.

**Example request**

```bash
curl --request GET \
  --url 'https://app.malanta.ai/feeds/taxii2/default/collections' \
  --header 'Accept: application/taxii+json;version=2.1' \
  --header 'x-api-key: malanta_xxxxxxxxxxxxx'
```

### **Collection metadata**

**Endpoint:** `GET /taxii2/default/collections/{collection_id}`

**Path parameters**

* `collection_id` — use `pre-attack-indicators`

**Example request**

```bash
curl --request GET \
  --url 'https://app.malanta.ai/feeds/taxii2/default/collections/pre-attack-indicators' \
  --header 'Accept: application/taxii+json;version=2.1' \
  --header 'x-api-key: malanta_xxxxxxxxxxxxx'
```

### **Get objects**

**Endpoint:** `GET /taxii2/default/collections/{collection_id}/objects`

Returns STIX 2.1 objects in a TAXII envelope (paginated).

**Path parameters**

* `collection_id` — use `pre-attack-indicators`

**Query parameters**

| Param | Type | Default | Meaning |
| --- | --- | --- | --- |
| `limit` | integer | `2000` | Page size. Max response object count is **2002** (page + Malanta identity + TLP:AMBER marking). |
| `next` | string | none | Opaque pagination token from the previous response |
| `added_after` | RFC 3339 | none | Only objects added after this time (incremental sync) |

**Example request**

```bash
curl --request GET \
  --url 'https://app.malanta.ai/feeds/taxii2/default/collections/pre-attack-indicators/objects?limit=2000' \
  --header 'Accept: application/taxii+json;version=2.1' \
  --header 'x-api-key: malanta_xxxxxxxxxxxxx'
```

**Example response**

```json
{
  "objects": [ /* STIX 2.1 SDOs / SROs */ ],
  "more": true,
  "next": "<opaque-token>"
}
```

**Response fields**

* `objects` — STIX objects for this page
* `more` — `true` → call again with `next`; `false` → drain complete
* `next` — pagination token (present when `more` is true)

**Response headers**

| Header | Meaning |
| --- | --- |
| `X-TAXII-Date-Added-First` | Earliest `date_added` / `created` on this page |
| `X-TAXII-Date-Added-Last` | Latest `date_added` / `created` on this page |

Use `X-TAXII-Date-Added-Last` as the next `added_after` value for incremental sync.

**Paginated request**

```bash
curl --request GET \
  --url 'https://app.malanta.ai/feeds/taxii2/default/collections/pre-attack-indicators/objects?next=<token>' \
  --header 'Accept: application/taxii+json;version=2.1' \
  --header 'x-api-key: malanta_xxxxxxxxxxxxx'
```

### **Manifest**

**Endpoint:** `GET /taxii2/default/collections/{collection_id}/manifest`

Returns object metadata without full STIX bodies. Same query parameters as Get objects (`limit`, `next`, `added_after`).

[↑ Back to top](#2-contents)

---

## **Pagination and incremental sync**

### **Full drain (initial load)**

```text
1. GET .../objects?limit=2000
2. While response.more == true:
       GET .../objects?next=<token>
3. When more == false → collection fully drained for this cycle
```

Do **not** keep polling the final token. When `more` is false, the cycle is finished.

### **Incremental sync (deltas)**

Start a **new** request with `added_after` set to the last watermark you observed (typically `X-TAXII-Date-Added-Last` from the previous successful sync):

```bash
curl --request GET \
  --url 'https://app.malanta.ai/feeds/taxii2/default/collections/pre-attack-indicators/objects?limit=2000&added_after=2026-04-01T12:00:00.000Z' \
  --header 'Accept: application/taxii+json;version=2.1' \
  --header 'x-api-key: malanta_xxxxxxxxxxxxx'
```

Then follow `next` until `more` is false, same as a full drain.

`added_after` must be valid RFC 3339. Invalid values return HTTP **400** with a TAXII error resource.

### **Object order**

Within one drain cycle, objects arrive in this order across pages:

1. **Indicators** (reputation / observables)
2. **Clusters** (`intrusion-set`)
3. **Relationships** (indicator → cluster, type `indicates`)

A page never mixes streams; handoff to the next stream happens on the next HTTP call via `next`.

[↑ Back to top](#2-contents)

---

## **Recommended sync use cases**

Pick **one** of these patterns for first-time onboarding, then stay on incremental API pulls. Do not invent a hybrid that starts mid-history with a date filter.

**Important — clustering completeness.** Your **first** load of the graph must be a complete baseline: either a Malanta **full dump**, or a TAXII drain with **no** `added_after` (empty date).

If the first TAXII call uses a specific `added_after` (for example “last 7 days”), you can **lose clustering data**. Older `intrusion-set` / relationship objects fall outside the window, while newer indicators may still appear — your TIP ends up with orphan indicators and a broken cluster graph. Only use `added_after` **after** a successful full baseline.

### **Use case A — Full dump, then API intervals**

**Best when:** you need historical / inactive indicators as well as the live active set, and the full cluster graph in one shot.

| Step | What | Notes |
| --- | --- | --- |
| 1 | Request a **full feed dump** from Malanta | Includes **active and inactive** indicators, clusters, and relationships (baseline graph) |
| 2 | Load the dump into your TIP / store | Treat this as the complete starting state |
| 3 | Record a watermark | Use dump generation time, or run one TAXII drain and store `X-TAXII-Date-Added-Last` |
| 4 | Switch to TAXII **incremental** sync | `GET .../objects?added_after=<watermark>` on a schedule (e.g. daily) |
| 5 | Follow `next` until `more=false` each cycle | Same pagination rules as above |

```text
[Full dump: active + inactive + clusters + relationships]
                ↓
        load once into TIP
                ↓
[TAXII] ?added_after=<watermark>  →  daily / hourly intervals
```

Contact your Malanta account team or [support@malanta.ai](mailto:support@malanta.ai) to obtain the dump and confirm the handoff watermark.

### **Use case B — API full active set, then daily intervals**

**Best when:** you only need the **currently active** feed via TAXII and do not require inactive history from a dump.

| Step | What | Notes |
| --- | --- | --- |
| 1 | TAXII full drain **without** `added_after` | `GET .../objects?limit=2000` then follow `next` until `more=false` |
| 2 | Ingest all returned objects | Active indicators + clusters + relationships (complete active graph) |
| 3 | Persist watermark | Save `X-TAXII-Date-Added-Last` from the drain |
| 4 | Daily (or hourly) incremental | New cycle: `GET .../objects?added_after=<watermark>` |

```text
[TAXII] no added_after  →  full active baseline (indicators + clusters + relationships)
                ↓
        persist X-TAXII-Date-Added-Last
                ↓
[TAXII] ?added_after=<watermark>  →  every day (or chosen interval)
```

### **What not to do**

| Anti-pattern | Why it fails |
| --- | --- |
| First sync with `added_after=<some past date>` and no prior dump/full drain | Misses older clusters/relationships → incomplete clustering |
| Skipping relationships / clusters and ingesting only indicators | Loses Malanta’s attack-infrastructure graph |
| Keeping the final `next` token forever instead of starting a new cycle with `added_after` | Incorrect vs TAXII finite-drain model; use a fresh request each interval |

[↑ Back to top](#2-contents)

---

## **STIX object model**

Every page begins with a short **preamble**:

1. Malanta `identity`
2. TLP:AMBER `marking-definition`

Then the page’s feed objects.

### **Indicator**

| Field | Notes |
| --- | --- |
| `type` | `indicator` |
| `id` | `indicator--<uuid>` — deterministic from Malanta observable id (stable across syncs) |
| `name` | Observable value (e.g. the domain or IP string) |
| `pattern` | STIX pattern, e.g. `[domain-name:value = 'evil.example']` |
| `pattern_type` | `stix` |
| `indicator_types` | Always `malicious-activity` |
| `confidence` | 0–100 from Malanta `malicious_score` when present; otherwise `100` |
| `labels` | Classification and provenance tags — see Labels |
| `created` | First observed time (`first_observed_at`) |
| `modified` | Last classified / updated time (`last_classified_at`) |
| `valid_from` | Same as `created` (validity starts when first observed) |
| `valid_until` | **Not set** on this TAXII feed today (no expiry emitted) |
| `object_marking_refs` | Points at TLP:AMBER |
| `created_by_ref` | Malanta identity |

Supported observable types in patterns: **domain-name**, **ipv4-addr**, **url**, **file** (SHA-256).

### **Labels**

Every indicator carries STIX `labels` so you can filter and prioritize in your TIP. Labels come from two places: **always added by the feed**, and **passed through** from Malanta’s reputation data when present.

#### **Classification (always one of these)**

| Label | Meaning |
| --- | --- |
| `IOPA` | **Indicator of Pre-Attack** — Malanta’s core signal. Infrastructure observed during adversary **setup** (resource development), often weeks before it appears in classic IoC feeds. Always paired with `source:malanta`. |
| `IOC` | **Indicator of Compromise** — known malicious infrastructure already reported by the broader threat-intel ecosystem. Malanta redistributes and correlates it into the same cluster graph. |

#### **Always added by the feed**

| Label | Meaning |
| --- | --- |
| `attack-infrastructure` | Marker that the object is attack infrastructure (not a post-compromise host artifact alone). |
| Observable type | Raw type from the feed, e.g. `domain`, `ip`, `url`, `hash` — aligns with the STIX pattern type. |
| `source:malanta` | **Always** on **IOPA**. On **IOC**, only if a provider name is `malanta` (unusual). |
| `source:<name>` | On **IOC** — one label per external provider (e.g. `source:proofpoint`, `source:github.com`). Values are lowercased. Comma-joined providers are split into separate labels. |
| `redistributed-by:malanta` | On **IOC** when at least one provider is not Malanta — Malanta is redistributing an external indicator. |

#### **APT attribution (`apt:…`) — when available**

| Label | Meaning |
| --- | --- |
| `apt:<name>` | Linked APT / threat-actor name when Malanta has attribution for that indicator (e.g. `apt:APT17`, `apt:APT5`). |

* APT is returned **only via labels** — there is no separate STIX APT field on indicators or clusters in this feed.
* Not every indicator has APT tags. If none are known, `apt:…` is simply omitted.
* Comma-joined values are split so TIP filters can match exactly: `apt:APT17,APT5` → `apt:APT17` + `apt:APT5`.
* Use `apt:…` for hunting, TIP tagging, and (with OpenCTI) connectors that map these labels to Intrusion Sets / Threat Actors.

#### **Other pass-through labels (when available)**

Upstream reputation `labels` are forwarded after normalization. Besides `IOC` / `IOPA` / `apt:…`, you may also see free-form or prefixed tags such as:

| Label (examples) | Meaning |
| --- | --- |
| `phishing` | Category / technique tag from Malanta classification (example; not always present). |
| `tag:<value>` or other `prefix:value` | Additional taxonomy tokens when present upstream. Comma-joined `prefix:a,b` forms are expanded to one label per value. |

Treat unknown labels as opaque tags — keep them for filtering; do not drop the indicator because a label is unfamiliar.

#### **Cluster labels (on `intrusion-set`)**

Clusters use a fixed label set (not the indicator taxonomy above):

| Label | Meaning |
| --- | --- |
| `attack infrastructure` | Cluster represents related attack infrastructure. |
| `source:malanta` | Cluster is authored by Malanta. |

**How to use this as a customer**

* Prefer **`IOPA`** when you want Malanta’s early / predictive coverage (pre-attack).
* Prefer **`IOC`** when you want confirmed malicious indicators with external provenance.
* Many SOCs ingest **both**: IOPA for proactive blocking / hunting, IOC for correlation with existing TI feeds.
* Use `source:…` to attribute origin; use `apt:…` when present for actor-focused triage.
* Do not drop either class solely because the word “IOC” or “IOPA” is unfamiliar — both are first-class objects in this feed.

### **Cluster (intrusion-set)**

A Malanta **cluster** is a group of related attack-infrastructure indicators that share ownership, certificates, or other linkage. In STIX it is published as an `intrusion-set`.

| Field | Notes |
| --- | --- |
| `type` | `intrusion-set` |
| `id` | `intrusion-set--<uuid>` — deterministic from Malanta `cluster_id` (stable across syncs) |
| `name` | Short stable display name derived from the cluster id (not a human campaign title) |
| `confidence` | Always `100` for clusters in this feed |
| `labels` | Fixed set: `attack infrastructure`, `source:malanta` |
| `created` | When Malanta first built the cluster (`first_built_at`) |
| `modified` | When the cluster was last updated (`last_updated_at`) |
| `object_marking_refs` | Points at TLP:AMBER |
| `created_by_ref` | Malanta identity |

**How to use this as a customer**

* Treat each `intrusion-set` as a **cluster of related infrastructure**, not necessarily a named APT group.
* Join indicators to clusters via **relationships** (below) — do not invent joins from indicator labels alone.
* Keep clusters even if you primarily alert on indicators; without them the graph (and `indicates` edges) is incomplete.

### **Relationship**

Edges that connect indicators to clusters. In this feed every relationship is type **`indicates`**: the indicator points at the intrusion-set it belongs to.

| Field | Notes |
| --- | --- |
| `type` | `relationship` |
| `id` | `relationship--<uuid>` — deterministic from indicator id + cluster id (stable across syncs) |
| `relationship_type` | Always `indicates` |
| `source_ref` | STIX id of the **indicator** (`indicator--…`) |
| `target_ref` | STIX id of the **cluster** (`intrusion-set--…`) |
| `created` | When Malanta first recorded the membership (`system_created_at`) |
| `modified` | When the membership was last updated (`system_updated_at`) |
| `object_marking_refs` | Points at TLP:AMBER |
| `created_by_ref` | Malanta identity |

**Direction**

```text
indicator  --indicates-->  intrusion-set (cluster)
```

**How to use this as a customer**

* Resolve `source_ref` / `target_ref` against objects already ingested (or pending in the same drain).
* Because object order is indicators → clusters → relationships, a full drain without `added_after` (or a dump baseline) keeps refs resolvable.
* STIX IDs for indicators, clusters, and relationships are **deterministic** (stable across days), so TIP snapshot diffs stay clean.

[↑ Back to top](#2-contents)

---

## **Platform setup examples**

### **OpenCTI**

1. **Data → Connectors** (or Ingestion) → add a **TAXII 2** connector / feed.
2. **Discovery URL:** `https://app.malanta.ai/feeds/taxii2`
3. **Auth:** API key via `x-api-key` or Bearer, depending on connector fields.
4. Select collection `pre-attack-indicators`.
5. Enable periodic sync. Prefer connectors that honor `added_after` / date-added headers for deltas.

OpenCTI expects a finite drain (`more: false`) then a new poll with a time filter — that matches Malanta’s behavior.

### **ThreatQ**

1. TAXII URL: `https://app.malanta.ai/feeds/taxii2`
2. Username: your org name or any placeholder
3. Password: your full `malanta_…` API key
4. Collection: `pre-attack-indicators`

### **Python**

```python
import httpx

BASE = "https://app.malanta.ai/feeds"
HEADERS = {
    "Accept": "application/taxii+json;version=2.1",
    "x-api-key": "malanta_xxxxxxxxxxxxx",
}

def drain_collection(collection_id: str, *, added_after: str | None = None):
    params: dict = {"limit": 2000}
    if added_after:
        params["added_after"] = added_after

    url = f"{BASE}/taxii2/default/collections/{collection_id}/objects"
    watermark = added_after

    with httpx.Client(headers=HEADERS, timeout=120.0) as client:
        while True:
            r = client.get(url, params=params)
            r.raise_for_status()
            body = r.json()
            yield from body.get("objects") or []

            last = r.headers.get("X-TAXII-Date-Added-Last")
            if last:
                watermark = last

            if not body.get("more"):
                break
            params = {"next": body["next"]}  # do not re-send limit/added_after with next

    return watermark
```

### **cURL smoke test**

```bash
curl --request GET \
  --url 'https://app.malanta.ai/feeds/taxii2' \
  --header 'Accept: application/taxii+json;version=2.1' \
  --header 'x-api-key: malanta_xxxxxxxxxxxxx'
```

Expected: JSON with `title`, `default`, and `api_roots`.

```bash
curl --request GET \
  --url 'https://app.malanta.ai/feeds/taxii2/default/collections/pre-attack-indicators/objects?limit=10' \
  --header 'Accept: application/taxii+json;version=2.1' \
  --header 'x-api-key: malanta_xxxxxxxxxxxxx'
```

[↑ Back to top](#2-contents)

---

## **Errors**

| Status | When | What to do |
| --- | --- | --- |
| `400` | Bad query (e.g. invalid `added_after` or `next`) | Fix parameter; do not reuse a corrupt token |
| `401` / `403` | Missing or invalid API key / plan | Check key; contact Malanta |
| `404` | Unknown API root or collection | Confirm path and collection ID |
| `429` | Rate limited | Back off and retry |
| `5xx` | Transient server error | Retry with backoff |

TAXII error bodies follow TAXII 2.1 error resources, for example:

```json
{
  "title": "Invalid Query Parameter",
  "description": "...",
  "http_status": "400"
}
```

[↑ Back to top](#2-contents)

---

## **Limitations**

* Collection object-by-id lookup resolves the Malanta **identity** and **TLP:AMBER** marking. Individual feed indicators / clusters / relationships are consumed via the paginated `/objects` stream.
* `match[…]` query filters may be accepted by the router; rely on **collection choice** + **`added_after`** for supported filtering unless Malanta confirms additional match support for your account.
* Feed is **pull-only** (no TAXII write / inbox).

[↑ Back to top](#2-contents)

---

## **Quick reference**

* Discovery: `https://app.malanta.ai/feeds/taxii2`
* API root: `https://app.malanta.ai/feeds/taxii2/default`
* Collection: `pre-attack-indicators`
* Auth: `x-api-key: malanta_xxxxxxxxxxxxx` (also Bearer / Basic)
* Accept: `application/taxii+json;version=2.1`
* Baseline (once): full dump (active+inactive) **or** TAXII with no `added_after` (active only)
* Then intervals: `GET objects?added_after=<watermark>` → while `more`: follow `next` → stop
* Always **baseline first** before any dated incremental — otherwise clustering can break
* Respect **TLP:AMBER** — do not redistribute outside your organization without agreement

Questions or onboarding help: [support@malanta.ai](mailto:support@malanta.ai)

[↑ Back to top](#2-contents)