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

# IoC feed (enterprise-only)

> Synchronize a local indicator dataset with paginated snapshots and daily incremental changes. Requires enterprise feed access.

<Warning>
  **Enterprise-only.** Kyberis must explicitly enable feed access for your account.
  A standard plan or an API key alone does not grant access. Contact
  [support@kyberis.ai](mailto:support@kyberis.ai) to arrange access.
</Warning>

Use the feed to maintain a local set of indicators of compromise for matching and
investigation. Start with a paginated snapshot, then retrieve additions, updates,
and removals using a saved cursor. The feed is published daily; it is not a live
stream or a record of every change between publications.

## Access and credits

| Requirement | Value |
| - | - |
| Availability | Enterprise accounts with explicitly enabled feed access |
| Required scope | `read:intel` |
| Authentication | API key or machine bearer token |
| Page size | 1–1,000 records; default 1,000 |
| Upsert charge | 10 credits per 1,000 upserts, rounded up to whole credits per response |
| Removals and empty responses | Free |

Use `Authorization: ApiKey <key_id>:<secret>` or
`Authorization: Bearer <access_token>`. See [authentication](/api-reference/introduction#authentication).
This endpoint does not require an `agent_context` body or agent-context headers.
Normal account and credential rate limits apply.

The exact charge is `ceil(upserts / 100)`: 5 or 100 upserts cost 1 credit, 101 cost
2, and 1,000 cost 10. Removals count toward page size but not the charge. A mixed
page of 100 upserts and 900 removals costs 1 credit. Smaller pages can increase
rounding costs. A 500,000-record snapshot retrieved in full pages costs 5,000 credits.

## Retrieve the first page

Set `KYBERIS_API_KEY` to your API key credential (`<key_id>:<secret>`):

```bash theme={null}
curl --fail-with-body \
  -H "Authorization: ApiKey $KYBERIS_API_KEY" \
  'https://api.kyberis.ai/v2/ioc-feed?limit=1000'
```

Omit `cursor` for the initial snapshot. There is no request that returns the
entire dataset without pagination. Dataset size changes as indicators become
eligible or are removed.

## Continue pages and daily polling

Use the previous response's **`next_cursor` as the `cursor` query parameter**:

```bash theme={null}
curl --fail-with-body --get \
  -H "Authorization: ApiKey $KYBERIS_API_KEY" \
  --data-urlencode "cursor=$NEXT_CURSOR" \
  --data-urlencode 'limit=1000' \
  'https://api.kyberis.ai/v2/ioc-feed'
```

1. Apply each record to your local dataset using `id` as the key. Replace or insert
   on `upsert`; delete on `remove`, even if the ID is already absent.
2. Save `next_cursor` atomically with those local changes.
3. While `has_more` is `true`, fetch the next page immediately.
4. When `has_more` is `false`, keep the returned cursor and resume with it at your
   next poll, typically 24 hours later. Save the returned cursor even on an empty page.

The snapshot remains consistent while you page through it. After its last page,
the cursor continues into incremental changes. A poll returns changes published
since your checkpoint, potentially across several pages. Do not restart a snapshot
for each daily poll or run parallel pagination chains against the same local dataset.
Treat cursors as opaque strings and URL-encode them.

## Select a confidence interval

The default feed includes all eligible indicators, including those with unknown
source confidence. You can select a confidence interval on the first request:

| Parameter | Meaning |
| - | - |
| `min_confidence` | Inclusive minimum, from 0 to 100 |
| `max_confidence` | Exclusive maximum, from 0 to 100; must exceed the minimum, or zero if no minimum is supplied |

For example, `?min_confidence=40&max_confidence=65` selects `[40,65)`.
`?min_confidence=65` includes scores from 65 through 100. An explicit
`max_confidence=100` excludes 100; omit the maximum to include it. Fractional
bounds are supported. Unknown confidence is excluded whenever either bound is
specified, including `min_confidence=0`.

Confidence describes source threat confidence, not freshness. The selected bounds
are fixed in the cursor. Subsequent requests should send only `cursor` and optional
`limit`, or repeat identical bounds. Changing a bound returns
`400 cursor_filter_mismatch`. Start a new snapshot to change selection.

An indicator entering your interval produces an upsert; one leaving it produces a
free removal. For example, a score falling from 70 to 50 produces a removal for a
consumer using `min_confidence=65`, but an update for one using `min_confidence=40`.
Each consumer should keep its own dataset and cursor. Cursors belong to the account,
not a particular API key. Overlapping downloads by separate consumers are each billable.

Unsupported or duplicate query parameters are rejected. Type, country, industry,
`not_older_than`, and `verbose` filters are not supported.

## Read the response

This illustrative incremental page contains one upsert and one removal. Cursor
strings in examples are placeholders.

```json theme={null}
{
  "records": [
    {
      "id": "indicator-1",
      "operation": "upsert",
      "type": "domain",
      "value": "example.test",
      "confidence": 80,
      "freshness_at": "2026-09-21T12:00:00Z",
      "freshness_basis": "last_feed_occurrence",
      "changed_at": "2026-09-22T04:00:00Z"
    },
    {
      "id": "indicator-2",
      "operation": "remove",
      "changed_at": "2026-09-22T04:00:00Z"
    }
  ],
  "next_cursor": "opaque-checkpoint",
  "has_more": false,
  "mode": "incremental",
  "as_of": "2026-09-22T04:00:00Z",
  "credits_charged": 1
}
```

| Field | Meaning |
| - | - |
| `records` | Upserts and removals in application order |
| `next_cursor` | Checkpoint for the next page or poll |
| `has_more` | More pages are available in the current snapshot or catch-up |
| `mode` | Mode of this response: `snapshot` or `incremental` |
| `as_of` | Publication's evaluation cutoff, not the request time |
| `credits_charged` | Whole credits charged for this response |

Upserts include `id`, `operation`, `type`, `value`, `changed_at`, `freshness_at`,
and `freshness_basis`. `confidence` is omitted when unknown. `last_seen` is actual
source observation time and is omitted when unavailable. `freshness_basis` is
`last_seen` or `last_feed_occurrence`; a feed occurrence describes source feed
recency, not a confirmed malicious sighting. `changed_at` indicates when the feed
detected the change, not when malicious activity occurred.

Removals contain only `id`, `operation`, and `changed_at`. They include withdrawals,
indicators aging out, and indicators leaving a selected confidence interval.
A quiet poll returns `records: []`, `has_more: false`, a new checkpoint, and zero credits.

The feed contains minimal indicators, not enriched investigation results or internal
lifecycle fields. Use [IoC assessment](/api-reference/endpoint/ioc-assessments)
for a separate, billable investigation of an indicator.

## Cursor expiry and recovery

Feed history is retained for seven days from each publication's evaluation cutoff.
A cursor expires with its retained base publication; each page does not reset that
clock. Catching up to a newer publication advances the checkpoint. Complete initial
snapshots promptly and poll regularly.

On `410 snapshot_required`, build a replacement snapshot without a cursor and
replace your local dataset after completing all its pages. Reapply your confidence
bounds when starting it. The replacement snapshot is billable. A cursor invalidated
by signing-key rotation also requires a fresh snapshot.

## Errors and retries

| HTTP status | Action |
| - | - |
| `400` | Check cursor and query parameters. `invalid_cursor` can require snapshot recovery; `cursor_filter_mismatch` means supplied bounds differ from the cursor; `invalid_confidence_interval` means the bounds do not form a valid interval. |
| `401` | Supply valid credentials. |
| `402` | Replenish credits or reduce page size. `insufficient_credits` rejects the entire page without a debit. |
| `403` | Confirm your account has enterprise feed access and your credential has `read:intel`. |
| `410` | `snapshot_required`: rebuild a snapshot. |
| `422` | Correct malformed or out-of-range parameter values. |
| `429` | Respect the retry delay. |
| `503` | Retry later with the same cursor. An unavailable or stale publication is not an empty feed. |

Replaying a cursor lets you apply records idempotently, but **every successful
response is charged again for its upserts**. Request IDs do not deduplicate charges.
A connection failure after a debit commits may leave you charged without receiving
the response; retrying is a new retrieval. Validation and access errors before a
debit do not charge. Keep your last successfully applied cursor on errors.


## OpenAPI

````yaml GET /v2/ioc-feed
openapi: 3.1.0
info:
  description: Deterministic, evidence-backed threat assessment API for machine consumers.
  title: Threat Investigator API v2
  version: 0.1.0
servers: []
security: []
paths:
  /v2/ioc-feed:
    get:
      tags:
        - Feeds
      summary: Read the IoC feed (enterprise-only)
      description: >-
        Enterprise-only: Kyberis must explicitly enable feed access for your
        account. Requires read:intel. Account-allowlisted feed. Maximum 1,000
        records per page. Omit cursor for an initial snapshot, optionally
        selecting a confidence interval (inclusive minimum, exclusive maximum).
        Omitted maximum includes confidence 100. Unknown confidence matches only
        unfiltered requests. Filters are bound to the cursor; omit them on
        continuation or repeat identical values. Follow next_cursor for all
        subsequent pages/polls. Seven-day cursor retention. Upserts cost
        ceil(count/100) credits per successful response, including replays.
        Removals and empty pages are free. Publication is daily; as_of is the
        feed evaluation cutoff.
      operationId: ioc_feed_v2_ioc_feed_get
      parameters:
        - in: query
          name: cursor
          required: false
          schema:
            anyOf:
              - maxLength: 4096
                minLength: 1
                type: string
              - type: 'null'
            title: Cursor
        - in: query
          name: limit
          required: false
          schema:
            default: 1000
            maximum: 1000
            minimum: 1
            title: Limit
            type: integer
        - description: Inclusive source-confidence minimum. Fixed for this cursor chain.
          in: query
          name: min_confidence
          required: false
          schema:
            anyOf:
              - maximum: 100
                minimum: 0
                type: number
              - type: 'null'
            description: Inclusive source-confidence minimum. Fixed for this cursor chain.
            title: Min Confidence
        - description: >-
            Exclusive source-confidence maximum. Omit to include 100. Must
            exceed the minimum (or zero).
          in: query
          name: max_confidence
          required: false
          schema:
            anyOf:
              - maximum: 100
                minimum: 0
                type: number
              - type: 'null'
            description: >-
              Exclusive source-confidence maximum. Omit to include 100. Must
              exceed the minimum (or zero).
            title: Max Confidence
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeedResponse'
          description: Successful Response
        '400':
          description: >-
            Invalid cursor, interval, changed cursor filters, or unsupported
            parameters
        '402':
          description: Insufficient credits; no debit or page delivered
        '403':
          description: Account not enabled
        '410':
          description: 'Cursor expired: snapshot_required'
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HTTPValidationError'
          description: Validation Error
        '503':
          description: Feed unavailable, stale, or not configured
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
components:
  schemas:
    FeedResponse:
      properties:
        as_of:
          format: date-time
          title: As Of
          type: string
        credits_charged:
          title: Credits Charged
          type: integer
        has_more:
          title: Has More
          type: boolean
        mode:
          enum:
            - snapshot
            - incremental
          title: Mode
          type: string
        next_cursor:
          title: Next Cursor
          type: string
        records:
          items:
            $ref: '#/components/schemas/FeedRecord'
          title: Records
          type: array
      required:
        - records
        - next_cursor
        - has_more
        - mode
        - as_of
        - credits_charged
      title: FeedResponse
      type: object
    HTTPValidationError:
      properties:
        detail:
          items:
            $ref: '#/components/schemas/ValidationError'
          title: Detail
          type: array
      title: HTTPValidationError
      type: object
    FeedRecord:
      properties:
        changed_at:
          format: date-time
          title: Changed At
          type: string
        confidence:
          anyOf:
            - maximum: 100
              minimum: 0
              type: number
            - type: 'null'
          description: >-
            Source threat confidence, not freshness; omitted when unknown and on
            removals.
          title: Confidence
        freshness_at:
          anyOf:
            - format: date-time
              type: string
            - type: 'null'
          description: >-
            Timestamp used for eligibility: observation time, or source feed
            occurrence when observation time is absent.
          title: Freshness At
        freshness_basis:
          anyOf:
            - enum:
                - last_seen
                - last_feed_occurrence
              type: string
            - type: 'null'
          description: Source field used for freshness_at; absent on removals.
          title: Freshness Basis
        id:
          title: Id
          type: string
        last_seen:
          anyOf:
            - format: date-time
              type: string
            - type: 'null'
          description: Actual source observation time; omitted when unavailable.
          title: Last Seen
        operation:
          enum:
            - upsert
            - remove
          title: Operation
          type: string
        type:
          anyOf:
            - type: string
            - type: 'null'
          title: Type
        value:
          anyOf:
            - type: string
            - type: 'null'
          title: Value
      required:
        - id
        - operation
        - changed_at
      title: FeedRecord
      type: object
    ValidationError:
      properties:
        ctx:
          title: Context
          type: object
        input:
          title: Input
        loc:
          items:
            anyOf:
              - type: string
              - type: integer
          title: Location
          type: array
        msg:
          title: Message
          type: string
        type:
          title: Error Type
          type: string
      required:
        - loc
        - msg
        - type
      title: ValidationError
      type: object
  securitySchemes:
    ApiKeyAuth:
      description: ApiKey <key_id>:<secret>
      in: header
      name: Authorization
      type: apiKey
    BearerAuth:
      description: Bearer <access_token>
      scheme: bearer
      type: http

````