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

# List, search, and bulk-export aggregated cybersecurity news

> The workhorse endpoint for the News API. One URL gives you list, filter, full-text search, delta polling, and bulk export — composed via query parameters.

## What it returns

A flat `Article` object per row. Every field is top-level (`source_name` not `source.name`) so streaming and CSV parsing are zero-friction. See the [Article](#model/Article) schema for the complete shape.

## Common use cases

### 1. Today's critical-severity vulnerabilities
```
GET /api/v1/articles?category=vulnerabilities&severity=critical&since=2026-05-17T00:00:00Z&limit=20
```

### 2. Delta poll for SIEM/SOAR sync (recommended pattern)
```
GET /api/v1/articles?time_field=modified_at&modified_since=<last_sync>&sort=modified:desc&limit=500
```
Persist the last `modified_at` you saw on each run; pass it back as `modified_since` on the next call. Use `X-Next-Cursor` to walk multi-page deltas.

### 3. Bulk NDJSON export with IOC expansions (Pro)
```
GET /api/v1/articles?format=ndjson&expand=iocs,mitre&has_iocs=true&limit=5000
```
Returns one homogeneous JSON object per line. Stream-parse with `for line in r: json.loads(line)`.

### 4. Threat-actor research feed
```
GET /api/v1/articles?threat_actor=lazarus,apt28&expand=threat_actors,iocs&limit=50
```

### 5. Industry-targeted intel (finance + healthcare)
```
GET /api/v1/articles?industry_tags=finance,healthcare&min_severity=7&sort=severity:desc
```

### 6. Sparse fields for high-volume polling
```
GET /api/v1/articles?fields=id,title,url,published_at&limit=100
```
Cuts payload by ~80% and reduces DB I/O proportionally.

## Tier behavior

| Tier | Lookback | Per-request cap | Formats |
|------|----------|-----------------|---------|
| Free | 7 days   | 100             | JSON                                       |
| Pro  | 365 days | 1,000 (5,000 for export) | + NDJSON, CSV (requires `read:articles:export`) |

## Pagination

Use **cursor pagination** for any walk past 1,000 rows. The response contains `links.next` (full URL) and `X-Next-Cursor` (header) — pass either back as `?cursor=`. Offsets > 1000 are rejected with `deep_paging_unsupported`.

## Response signaling headers

| Header | When | Meaning |
|--------|------|---------|
| `X-Next-Cursor` | More pages exist | Opaque cursor for next page |
| `X-Tier-Lookback-Days` | Always | Effective lookback window for the caller's tier |
| `X-Tier-Lookback-Clamped` | `since` was clamped | Caller asked for older data than tier allows; clamped silently |
| `X-Limit-Capped` | `limit` exceeded tier cap | Caller's requested limit was reduced |
| `X-Format-Cost-Multiplier` | Always | Rate-limit cost multiplier for this format (NDJSON/CSV cost more) |
| `Deprecation` + `Sunset` | Legacy `?cve=` param used | Migrate to `cve_text_search` |



## OpenAPI

````yaml https://socdefenders.ai/api/openapi.json get /api/v1/articles
openapi: 3.0.3
info:
  title: SOC Defenders Threat Intelligence API
  description: >-
    # Overview


    The SOC Defenders Threat Intelligence API provides programmatic access to
    aggregated threat intelligence from 30+ cybersecurity sources. Export IOCs
    (Indicators of Compromise) in various formats including JSON, CSV, STIX 2.1,
    MISP, CEF, and OpenIOC.


    ## Authentication


    All API endpoints require authentication using an API key. Include your key
    in one of these ways:


    - **Authorization Header** (recommended): `Authorization: Bearer
    sk_live_xxx`

    - **X-API-Key Header**: `X-API-Key: sk_live_xxx`

    - **Query Parameter**: `?api_key=sk_live_xxx`


    ## Rate Limiting


    Rate limits vary by tier. When limits are exceeded, the API returns a 429
    status code.


    | Tier | Requests/min | Requests/day | Lookback | Formats |

    |------|-------------|--------------|----------|---------|

    | Free | 10 | 1,000 | 1 day | JSON / CSV |

    | Pro ($299/mo) | 1,000 | 1,000,000 | 365 days | All (STIX, TAXII, MISP,
    CEF, OpenIOC, Sigma) |


    Rate limit headers are included in all responses:

    - `X-RateLimit-Limit`: Maximum requests per minute

    - `X-RateLimit-Remaining`: Requests remaining in current window

    - `X-RateLimit-Reset`: Unix timestamp when the limit resets


    ## Error Handling


    All errors follow a consistent format:


    ```json

    {
      "error": {
        "code": "error_code",
        "message": "Human readable message",
        "details": {},
        "request_id": "req_abc123"
      }
    }

    ```


    ## STIX/TAXII Support


    For STIX 2.1 and TAXII 2.1 integration, see the TAXII endpoints section.
    TAXII endpoints are available at `/api/taxii2/`.
  version: 1.0.0
  contact:
    name: SOC Defenders Support
    url: https://socdefenders.ai/contact
    email: support@socdefenders.ai
  license:
    name: Proprietary
    url: https://socdefenders.ai/terms
servers:
  - url: https://socdefenders.ai
    description: Production server
security: []
tags:
  - name: IOCs
    description: Indicators of Compromise endpoints
  - name: Statistics
    description: Feed and IOC statistics
  - name: API Keys
    description: API key management (requires session auth)
  - name: TAXII
    description: TAXII 2.1 threat intelligence sharing endpoints
  - name: Export Formats
    description: Specialized export format endpoints (MISP, CEF, OpenIOC)
  - name: Articles
    description: >-
      Aggregated news articles from 30+ cybersecurity sources, with rich
      filtering and bulk-export support
  - name: Lookup
    description: >-
      Single-IOC enrichment lookup. Auto-detects type, aggregates reporting
      sources, returns AI risk + MITRE techniques + hunting queries. Free-tier
      friendly.
  - name: Detection Rules
    description: >-
      Generate ready-to-deploy SIEM detection rules from recent IOCs. Sigma
      rules ship as multi-document YAML covering network / DNS / proxy /
      process_creation logsources.
paths:
  /api/v1/articles:
    get:
      tags:
        - Articles
      summary: List, search, and bulk-export aggregated cybersecurity news
      description: >-
        The workhorse endpoint for the News API. One URL gives you list, filter,
        full-text search, delta polling, and bulk export — composed via query
        parameters.


        ## What it returns


        A flat `Article` object per row. Every field is top-level (`source_name`
        not `source.name`) so streaming and CSV parsing are zero-friction. See
        the [Article](#model/Article) schema for the complete shape.


        ## Common use cases


        ### 1. Today's critical-severity vulnerabilities

        ```

        GET
        /api/v1/articles?category=vulnerabilities&severity=critical&since=2026-05-17T00:00:00Z&limit=20

        ```


        ### 2. Delta poll for SIEM/SOAR sync (recommended pattern)

        ```

        GET
        /api/v1/articles?time_field=modified_at&modified_since=<last_sync>&sort=modified:desc&limit=500

        ```

        Persist the last `modified_at` you saw on each run; pass it back as
        `modified_since` on the next call. Use `X-Next-Cursor` to walk
        multi-page deltas.


        ### 3. Bulk NDJSON export with IOC expansions (Pro)

        ```

        GET
        /api/v1/articles?format=ndjson&expand=iocs,mitre&has_iocs=true&limit=5000

        ```

        Returns one homogeneous JSON object per line. Stream-parse with `for
        line in r: json.loads(line)`.


        ### 4. Threat-actor research feed

        ```

        GET
        /api/v1/articles?threat_actor=lazarus,apt28&expand=threat_actors,iocs&limit=50

        ```


        ### 5. Industry-targeted intel (finance + healthcare)

        ```

        GET
        /api/v1/articles?industry_tags=finance,healthcare&min_severity=7&sort=severity:desc

        ```


        ### 6. Sparse fields for high-volume polling

        ```

        GET /api/v1/articles?fields=id,title,url,published_at&limit=100

        ```

        Cuts payload by ~80% and reduces DB I/O proportionally.


        ## Tier behavior


        | Tier | Lookback | Per-request cap | Formats |

        |------|----------|-----------------|---------|

        | Free | 7 days   | 100             |
        JSON                                       |

        | Pro  | 365 days | 1,000 (5,000 for export) | + NDJSON, CSV (requires
        `read:articles:export`) |


        ## Pagination


        Use **cursor pagination** for any walk past 1,000 rows. The response
        contains `links.next` (full URL) and `X-Next-Cursor` (header) — pass
        either back as `?cursor=`. Offsets > 1000 are rejected with
        `deep_paging_unsupported`.


        ## Response signaling headers


        | Header | When | Meaning |

        |--------|------|---------|

        | `X-Next-Cursor` | More pages exist | Opaque cursor for next page |

        | `X-Tier-Lookback-Days` | Always | Effective lookback window for the
        caller's tier |

        | `X-Tier-Lookback-Clamped` | `since` was clamped | Caller asked for
        older data than tier allows; clamped silently |

        | `X-Limit-Capped` | `limit` exceeded tier cap | Caller's requested
        limit was reduced |

        | `X-Format-Cost-Multiplier` | Always | Rate-limit cost multiplier for
        this format (NDJSON/CSV cost more) |

        | `Deprecation` + `Sunset` | Legacy `?cve=` param used | Migrate to
        `cve_text_search` |
      operationId: listArticles
      parameters:
        - name: source
          in: query
          description: >-
            Comma-separated source names. Use exact names from `/api/v1/sources`
            (or the [supported sources
            docs](https://socdefenders.ai/docs/sources)).
          schema:
            type: string
            example: BleepingComputer,Krebs on Security
        - name: source_type
          in: query
          description: >-
            Filter by content origin: `rss` (automated feeds, 99% of corpus) or
            `community` (user-submitted).
          schema:
            type: string
            enum:
              - rss
              - community
            example: rss
        - name: source_tier
          in: query
          description: >-
            Comma-separated source curation tiers. 1 = best-in-class (Krebs,
            BleepingComputer); 2 = solid vendors (Talos, Mandiant); 3 =
            community/aggregator; 4 = experimental.
          schema:
            type: string
            example: 1,2
        - name: category
          in: query
          description: >-
            Comma-separated canonical category slugs. Available:
            `attacks-breaches`, `vulnerabilities`, `threat-intel`, `malware`,
            `cloud-security`, `endpoint-security`, `network-security`,
            `identity-access`, `application-security`, `data-security`,
            `security-operations`, `incident-response`, `compliance`,
            `ai-ml-security`, `iot-ot`.
          schema:
            type: string
            example: vulnerabilities,malware
        - name: content_type
          in: query
          description: >-
            Filter by AI-classified content type. Default queries return all;
            pass explicit values to narrow.
          schema:
            type: string
            example: security,educational
        - name: tech_tags
          in: query
          description: >-
            Any-match against the per-article `tech_tags` array (GIN-indexed).
            Common tags: `aws`, `azure`, `kubernetes`, `docker`, `windows`,
            `linux`, `ios`, `android`, `openssh`, `nginx`, `apache`.
          schema:
            type: string
            example: aws,kubernetes
        - name: industry_tags
          in: query
          description: >-
            Any-match against the `industry_tags` array. Available: `finance`,
            `healthcare`, `government`, `technology`, `manufacturing`, `retail`,
            `energy`, `education`, `telecom`, `transportation`, `defense`,
            `water`, `unclassified`.
          schema:
            type: string
            example: finance,healthcare
        - name: threat_actor
          in: query
          description: >-
            Any-match against the `threat_actors` array (lowercase canonical
            names). Examples: `lazarus`, `apt28`, `apt29`, `lockbit`,
            `blackcat`, `clop`.
          schema:
            type: string
            example: lazarus,apt28
        - name: min_severity
          in: query
          description: >-
            Minimum severity_score. Articles below this score are excluded. Use
            7+ for "actionable" and 9+ for "drop everything".
          schema:
            type: number
            minimum: 0
            maximum: 10
            example: 8
        - name: severity
          in: query
          description: >-
            Severity-bucket filter. Mutually exclusive with `min_severity` (use
            one). Mapping: critical=9-10, high=7-8.9, medium=4-6.9, low=0.1-3.9.
          schema:
            type: string
            example: critical,high
        - name: has_iocs
          in: query
          description: >-
            Restrict to articles with extracted IOCs (`true`) or without
            (`false`). Useful for SIEM-feed use cases that only care about
            indicator-bearing content.
          schema:
            type: boolean
            example: true
        - name: cve_text_search
          in: query
          description: >-
            CVE ID substring match against `title` + `ai_summary`. **Note**: a
            proper join via `cve_article_mentions` is not yet populated; matches
            today are full-text only and may include false positives (e.g.
            `CVE-2024-1234` matches `CVE-2024-12345`). The legacy `cve`
            parameter remains as a deprecated alias and triggers `Deprecation` /
            `Sunset` response headers.
          schema:
            type: string
            pattern: CVE-\d{4}-\d{4,7}
            example: CVE-2024-12345
        - name: mitre_technique
          in: query
          description: >-
            Single MITRE ATT&CK technique ID. Backed by a GIN index on the
            per-article `ai_mitre_techniques` jsonb column.
          schema:
            type: string
            pattern: T\d{4}(\.\d{3})?
            example: T1566
        - name: has_ai_summary
          in: query
          description: >-
            Restrict to articles with an AI-generated extended summary. Most
            articles >24h old have one; use this to skip articles awaiting
            processing.
          schema:
            type: boolean
            example: true
        - name: has_audio
          in: query
          description: >-
            Restrict to articles with audio narration (TTS). Useful for
            podcast/audio-content integrations.
          schema:
            type: boolean
            example: true
        - name: is_archived
          in: query
          description: >-
            Include archived articles. Archived articles are kept for history
            but hidden from normal listings.
          schema:
            type: boolean
            default: false
        - name: submission_type
          in: query
          description: >-
            Filter by how the article entered the platform. `rss_auto` =
            harvested from an RSS feed (~99% of corpus). `user_submitted` =
            posted by an authenticated community member.
          schema:
            type: string
            enum:
              - rss_auto
              - user_submitted
            example: rss_auto
        - name: q
          in: query
          description: >-
            Full-text search query. Min 2 chars after sanitization
            (alphanumerics, spaces, `.`, `,`, `-` only). Must contain ≥1
            alphabetic token of length 3+ — gibberish like `' OR 1=1` is
            rejected with `q_invalid`.
          schema:
            type: string
            example: ransomware
        - name: q_field
          in: query
          description: >-
            Which field to search. `default` searches title+summary using the
            GIN index. `ai_summary` is ILIKE-only (no index — slower).
          schema:
            type: string
            enum:
              - default
              - title
              - summary
              - ai_summary
            default: default
            example: default
        - name: time_field
          in: query
          description: >-
            Which time field `since`/`until` apply to. `published_at` (default)
            is the article's original publication time. `modified_at` is when
            SOC Defenders last updated the row (use for delta polling).
            `fetched_at` is when we first crawled it. `created_at` is the DB
            insert time.
          schema:
            type: string
            enum:
              - published_at
              - fetched_at
              - modified_at
              - created_at
            default: published_at
            example: modified_at
        - name: since
          in: query
          description: >-
            Lower time bound (ISO 8601). Silently clamped to tier earliest —
            check `X-Tier-Lookback-Clamped` header to detect.
          schema:
            type: string
            format: date-time
            example: '2026-05-10T00:00:00Z'
        - name: until
          in: query
          description: Upper time bound (ISO 8601). Cannot be in the future.
          schema:
            type: string
            format: date-time
            example: '2026-05-17T00:00:00Z'
        - name: modified_since
          in: query
          description: >-
            Convenience alias for `time_field=modified_at&since=<value>`. Use
            this for delta-polling SIEM/SOAR sync.
          schema:
            type: string
            format: date-time
            example: '2026-05-16T00:00:00Z'
        - name: limit
          in: query
          description: >-
            Results per page. Out-of-range values are silently clamped to tier
            max (and `X-Limit-Capped` header is emitted).
          schema:
            type: integer
            minimum: 1
            default: 50
            example: 100
        - name: offset
          in: query
          description: >-
            Offset pagination. Rejected above 1000 — use `cursor` instead.
            Cursor is stable across vote/score updates; offset is not.
          schema:
            type: integer
            minimum: 0
            default: 0
            example: 0
        - name: cursor
          in: query
          description: >-
            Opaque pagination cursor from `links.next` or the `X-Next-Cursor`
            header. Encodes `{sort_key, timestamp, id}`; if you change the
            `sort` between calls, the cursor is rejected with `invalid_cursor`.
          schema:
            type: string
            example: eyJrIjoicHVibGlzaGVkX2F0IiwidCI6IjIw...
        - name: format
          in: query
          description: >-
            Response format. `ndjson` and `csv` require the
            `read:articles:export` scope (Pro tier).
          schema:
            type: string
            enum:
              - json
              - ndjson
              - csv
            default: json
            example: ndjson
        - name: fields
          in: query
          description: >-
            Comma-separated field allowlist for sparse responses. Maps to a
            minimal SQL SELECT list — saves DB I/O *and* response bytes. Unknown
            fields are silently dropped.
          schema:
            type: string
            example: id,title,url,published_at,severity_score
        - name: expand
          in: query
          description: >-
            Comma-separated expansions to include. `mitre` is on by default
            (already-fetched, zero overhead). `iocs`, `cves`, `threat_actors`
            each add one batched query.
          schema:
            type: string
            example: iocs,cves,threat_actors
        - name: sort
          in: query
          description: >-
            Sort order. `published:desc` (default) is newest-first.
            `engagement:desc` ranks by `rank_score` (HN-style decay).
            `severity:desc` surfaces critical vulnerabilities first.
          schema:
            type: string
            enum:
              - published:desc
              - published:asc
              - modified:desc
              - severity:desc
              - engagement:desc
            default: published:desc
            example: severity:desc
        - name: count_strategy
          in: query
          description: >-
            How `meta.total` is computed. **Default is `none`** so consumers do
            not accidentally ship the ~6%-drift `planned` estimate as if it were
            exact. Opt in to `planned` for a fast estimate or `exact` for a slow
            full count.
          schema:
            type: string
            enum:
              - none
              - planned
              - exact
            default: none
            example: none
      responses:
        '200':
          description: >-
            Successful response. See [`Article`](#model/Article) for the
            complete per-row shape and headers section for pagination/clamping
            signals.
          headers:
            X-RateLimit-Limit:
              description: Tier rate limit (requests/minute)
              schema:
                type: integer
            X-RateLimit-Remaining:
              description: Requests left in current window
              schema:
                type: integer
            X-RateLimit-Reset:
              description: Unix timestamp when limit resets
              schema:
                type: integer
            X-Next-Cursor:
              description: Opaque cursor for next page. Absent on last page.
              schema:
                type: string
            X-Tier-Lookback-Days:
              description: Effective tier lookback window in days
              schema:
                type: integer
            X-Tier-Lookback-Clamped:
              description: true if requested `since` was clamped to tier window
              schema:
                type: boolean
            X-Limit-Capped:
              description: true if requested `limit` was reduced to tier max
              schema:
                type: boolean
            X-Format-Cost-Multiplier:
              description: Rate-limit cost multiplier for this format
              schema:
                type: number
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ArticleListResponse'
              example:
                meta:
                  limit: 3
                  offset: 0
                  total: null
                  total_estimated: false
                  generated_at: '2026-05-17T10:30:00Z'
                  feed_version: '1.0'
                  source: SOC Defenders
                  filters:
                    time_field: published_at
                    since: '2026-05-10T00:00:00Z'
                    sort: published:desc
                    category:
                      - vulnerabilities
                data:
                  - id: e90ea8b7-8500-4a14-875c-1d64755c80ca
                    title: PoC Code Published for Critical NGINX Vulnerability
                    url: >-
                      https://www.securityweek.com/poc-code-published-for-critical-nginx-vulnerability/
                    canonical_url: >-
                      https://socdefenders.ai/item/e90ea8b7-8500-4a14-875c-1d64755c80ca
                    summary: >-
                      Researchers have released proof-of-concept exploit code
                      for a critical vulnerability in NGINX that could allow
                      remote code execution...
                    ai_summary: >-
                      A critical RCE vulnerability in NGINX (CVE-2026-12345) has
                      working PoC code circulating. Affected versions:
                      1.20.0-1.24.0. CVSS 9.8.
                    image_url: >-
                      https://www.securityweek.com/wp-content/uploads/2026/05/nginx-vuln.jpg
                    published_at: '2026-05-16T10:02:00Z'
                    fetched_at: '2026-05-16T10:05:23Z'
                    modified_at: '2026-05-16T10:08:11Z'
                    source_name: SecurityWeek
                    source_type: rss
                    source_tier: 1
                    category: vulnerabilities
                    category_label: Vulnerabilities
                    category_legacy: Vulnerabilities
                    content_type: security
                    severity_score: 9.2
                    severity_label: critical
                    categorized_by: ai
                    categorized_at: '2026-05-16T10:07:55Z'
                    ai_classification: critical-rce-with-poc
                    ai_classification_confidence: 0.94
                    tech_tags:
                      - nginx
                      - rce
                    industry_tags:
                      - technology
                    threat_actors: []
                    has_iocs: false
                    ioc_count: 0
                    score: 0
                    upvotes: 0
                    downvotes: 0
                    comment_count: 0
                    vouch_count: 0
                    rank_score: 0
                    submission_type: rss_auto
                    submitted_by_username: null
                    audio_url: null
                    audio_duration_seconds: null
                    audio_voice: null
                    mitre_techniques:
                      - id: T1190
                        name: Exploit Public-Facing Application
                        tactic: initial-access
                        confidence: high
                links:
                  self: >-
                    https://socdefenders.ai/api/v1/articles?category=vulnerabilities&limit=3
                  next: >-
                    https://socdefenders.ai/api/v1/articles?category=vulnerabilities&limit=3&cursor=eyJrIjoicHVibGlzaGVkX2F0...
            application/x-ndjson:
              schema:
                type: string
              example: >
                {"id":"e90ea8b7-...","title":"PoC Code Published for Critical
                NGINX
                Vulnerability","source_name":"SecurityWeek","severity_score":9.2,"published_at":"2026-05-16T10:02:00Z"}

                {"id":"7860eb44-...","title":"Cybercriminal Twins Caught After
                They Forgot to Turn Off Teams Recording","source_name":"WIRED
                Security","severity_score":6,"published_at":"2026-05-16T10:30:00Z"}
            text/csv:
              schema:
                type: string
              example: >
                id,published_at,fetched_at,modified_at,title,url,canonical_url,summary,ai_summary,image_url,source_name,source_type,source_tier,category,category_legacy,content_type,severity_score,severity_label,tech_tags,industry_tags,threat_actors,has_iocs,ioc_count,mitre_techniques,cve_ids,score,upvotes,downvotes,comment_count,vouch_count,rank_score,submission_type

                e90ea8b7-8500-4a14-875c-1d64755c80ca,2026-05-16T10:02:00Z,...,SecurityWeek,rss,1,vulnerabilities,Vulnerabilities,security,9.2,critical,nginx|rce,technology,,false,0,T1190,,0,0,0,0,0,0,rss_auto
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      x-codeSamples:
        - lang: cURL
          label: List + filter
          source: |-
            curl -H "Authorization: Bearer sk_live_..." \
              "https://socdefenders.ai/api/v1/articles?category=vulnerabilities&severity=critical&limit=10"
        - lang: cURL
          label: Delta poll
          source: |-
            curl -H "Authorization: Bearer sk_live_..." \
              "https://socdefenders.ai/api/v1/articles?time_field=modified_at&modified_since=2026-05-16T00:00:00Z&sort=modified:desc&limit=100"
        - lang: cURL
          label: Bulk NDJSON (Pro)
          source: |-
            curl -H "Authorization: Bearer sk_live_..." \
              "https://socdefenders.ai/api/v1/articles?format=ndjson&expand=iocs,mitre&has_iocs=true&limit=5000" \
              -o news.ndjson
        - lang: Python
          label: Delta sync with cursor walk
          source: |-
            import requests
            from datetime import datetime, timezone

            KEY = "sk_live_..."
            BASE = "https://socdefenders.ai/api/v1"
            last_sync = "2026-05-16T00:00:00Z"

            cursor = None
            while True:
                params = {
                    "time_field": "modified_at",
                    "modified_since": last_sync,
                    "sort": "modified:desc",
                    "limit": 500,
                }
                if cursor:
                    params["cursor"] = cursor
                r = requests.get(f"{BASE}/articles", headers={"Authorization": f"Bearer {KEY}"}, params=params)
                r.raise_for_status()
                page = r.json()
                for article in page["data"]:
                    process(article)
                cursor = r.headers.get("X-Next-Cursor")
                if not cursor:
                    break
        - lang: JavaScript
          label: List with expansions
          source: >-
            const KEY = "sk_live_...";

            const url = new URL("https://socdefenders.ai/api/v1/articles");

            url.searchParams.set("category", "vulnerabilities,malware");

            url.searchParams.set("expand", "iocs,mitre");

            url.searchParams.set("min_severity", "8");

            url.searchParams.set("limit", "20");


            const r = await fetch(url, { headers: { Authorization: `Bearer
            ${KEY}` } });

            const { data } = await r.json();

            for (const article of data) {
              console.log(`[${article.severity_label}] ${article.title} — ${article.source_name}`);
              if (article.iocs) console.log(`  IOCs: ${article.iocs.length}`);
            }
components:
  schemas:
    ArticleListResponse:
      type: object
      properties:
        meta:
          type: object
          properties:
            limit:
              type: integer
            offset:
              type: integer
            total:
              type: integer
              nullable: true
              description: null when count_strategy=none
            total_estimated:
              type: boolean
              description: true when total is from count_strategy=planned
            generated_at:
              type: string
              format: date-time
            feed_version:
              type: string
            source:
              type: string
            filters:
              type: object
              description: Echo of effective filters after tier clamping
        data:
          type: array
          items:
            $ref: '#/components/schemas/Article'
        links:
          type: object
          properties:
            self:
              type: string
              format: uri
            next:
              type: string
              format: uri
            prev:
              type: string
              format: uri
    Article:
      type: object
      description: >-
        A single news article — flat shape. Every field is top-level (no nested
        source.name etc.).
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        url:
          type: string
          format: uri
          description: Original source URL
        canonical_url:
          type: string
          format: uri
        summary:
          type: string
          nullable: true
        ai_summary:
          type: string
          nullable: true
        image_url:
          type: string
          format: uri
          nullable: true
        published_at:
          type: string
          format: date-time
        fetched_at:
          type: string
          format: date-time
          nullable: true
        modified_at:
          type: string
          format: date-time
          nullable: true
        source_name:
          type: string
        source_type:
          type: string
          enum:
            - rss
            - community
        source_tier:
          type: integer
          nullable: true
          minimum: 1
          maximum: 4
        category:
          type: string
          nullable: true
          description: Canonical category slug
        category_label:
          type: string
          nullable: true
        category_legacy:
          type: string
          nullable: true
        content_type:
          type: string
          nullable: true
          enum:
            - security
            - educational
            - marketing
            - other
        severity_score:
          type: number
          nullable: true
          minimum: 0
          maximum: 10
        severity_label:
          type: string
          nullable: true
          enum:
            - critical
            - high
            - medium
            - low
        categorized_by:
          type: string
          nullable: true
          enum:
            - ai
            - manual
            - rule-based
        categorized_at:
          type: string
          format: date-time
          nullable: true
        ai_classification:
          type: string
          nullable: true
        ai_classification_confidence:
          type: number
          nullable: true
          minimum: 0
          maximum: 1
        tech_tags:
          type: array
          items:
            type: string
        industry_tags:
          type: array
          items:
            type: string
        threat_actors:
          type: array
          items:
            type: string
          description: String names; resolve via expand=threat_actors
        has_iocs:
          type: boolean
        ioc_count:
          type: integer
          minimum: 0
        score:
          type: integer
        upvotes:
          type: integer
        downvotes:
          type: integer
        comment_count:
          type: integer
        vouch_count:
          type: integer
        rank_score:
          type: number
        submission_type:
          type: string
          nullable: true
          enum:
            - rss_auto
            - user_submitted
        submitted_by_username:
          type: string
          nullable: true
        audio_url:
          type: string
          format: uri
          nullable: true
        audio_duration_seconds:
          type: integer
          nullable: true
        audio_voice:
          type: string
          nullable: true
        mitre_techniques:
          type: array
          description: Present when expand=mitre (default for /articles)
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
              tactic:
                type: string
              confidence:
                type: string
                enum:
                  - high
                  - medium
                  - low
        cve_ids:
          type: array
          items:
            type: string
          description: Present when expand=cves
        iocs:
          type: array
          description: Present when expand=iocs. Max 25 per article — check iocs_truncated.
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              type:
                type: string
              value:
                type: string
              confidence:
                type: string
                enum:
                  - high
                  - medium
                  - low
              first_seen:
                type: string
                format: date-time
        iocs_truncated:
          type: boolean
          description: True if iocs[] is a capped subset of ioc_count
        cves:
          type: array
          description: Present when expand=cves
          items:
            type: object
            properties:
              cve_id:
                type: string
              cvss_score:
                type: number
                nullable: true
              severity:
                type: string
                nullable: true
        threat_actors_detail:
          type: array
          description: Present when expand=threat_actors
          items:
            type: object
            properties:
              id:
                type: string
                format: uuid
              name:
                type: string
              aliases:
                type: array
                items:
                  type: string
              actor_type:
                type: string
                nullable: true
              confidence:
                type: number
                nullable: true
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - request_id
          properties:
            code:
              type: string
              description: Error code for programmatic handling
            message:
              type: string
              description: Human-readable error message
            details:
              type: object
              description: Additional error details
            request_id:
              type: string
              description: Unique request identifier for support
  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: invalid_parameter
              message: 'Invalid IOC type: invalid'
              details:
                valid_types:
                  - ipv4
                  - ipv6
                  - domain
                  - url
                  - md5
                  - sha1
                  - sha256
              request_id: req_abc123
    Unauthorized:
      description: Authentication required or invalid
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: missing_api_key
              message: >-
                API key is required. Include it in the Authorization header as
                "Bearer sk_live_..."
              request_id: req_abc123
    Forbidden:
      description: Insufficient permissions or scope
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: insufficient_scope
              message: This endpoint requires the "read:stix" scope
              details:
                required_scope: read:stix
                your_scopes:
                  - read:iocs
                upgrade_url: https://socdefenders.ai/export#pricing
              request_id: req_abc123
    RateLimitExceeded:
      description: Rate limit exceeded
      headers:
        Retry-After:
          description: Seconds to wait before retrying
          schema:
            type: integer
        X-RateLimit-Limit:
          description: Rate limit ceiling
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: Remaining requests (will be 0)
          schema:
            type: integer
        X-RateLimit-Reset:
          description: Unix timestamp when limit resets
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: rate_limit_exceeded
              message: Rate limit exceeded. Please wait before making more requests.
              details:
                limit: 10
                reset_at: '2024-12-09T10:31:00Z'
                tier: free
                upgrade_url: https://socdefenders.ai/export#pricing
              request_id: req_abc123
    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: internal_error
              message: An internal error occurred. Please try again later.
              request_id: req_abc123
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key in X-API-Key header
    BearerAuth:
      type: http
      scheme: bearer
      description: 'API key in Authorization header: `Authorization: Bearer sk_live_xxx`'

````