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

# Detect breakout posts

> Page through your tracked videos, compute engagement deltas, and flag the fastest-growing posts.

This tutorial finds breakout posts: you'll list your tracked videos, ask for batched
engagement deltas over a look-back window, then flag the fastest movers by view or
engagement change. The result is a short list of posts worth a closer look.

<Note>
  Read fields off the live delta response rather than assuming names. You'll need an API
  key first — see [API setup](/api-setup) or create one on the [API keys
  page](https://app.creatoraudit.com/app/api-keys) — and see the [API
  reference](/api-reference/introduction) for the shared conventions.
</Note>

<Steps>
  <Step title="Set up a client with error parsing">
    Wrap requests so any non-2xx response surfaces the [RFC 9457](/api-reference/errors)
    `code`, `detail`, and the `X-Request-ID` — you'll want both when a batch call fails.

    ```python theme={null}
    from __future__ import annotations

    import os
    from typing import Any

    import httpx

    BASE_URL = "https://api.creatoraudit.com/v2"


    class CreatorAuditError(Exception):
        def __init__(self, *, status: int, code: str, detail: str, request_id: str | None):
            super().__init__(f"[{status} {code}] {detail}")
            self.status = status
            self.code = code
            self.detail = detail
            self.request_id = request_id


    client = httpx.Client(
        base_url=BASE_URL,
        headers={"Authorization": f"Bearer {os.environ['CREATORAUDIT_API_KEY']}"},
        timeout=30.0,
    )


    def call(method: str, path: str, **kwargs: Any) -> dict[str, Any]:
        resp = client.request(method, path, **kwargs)
        if resp.is_error:
            try:
                problem = resp.json()
            except ValueError:
                problem = {}
            raise CreatorAuditError(
                status=problem.get("status", resp.status_code),
                code=problem.get("code", "INTERNAL_ERROR"),
                detail=problem.get("detail", resp.text),
                request_id=resp.headers.get("X-Request-ID"),
            )
        return resp.json()
    ```
  </Step>

  <Step title="Page through your tracked videos">
    `GET /v2/account-videos` lists the videos discovered for your tracked accounts (use
    `GET /v2/videos` if you track individual posts). Follow the cursor: read
    `pagination.has_next` and pass the previous `pagination.next_cursor` back as `cursor`,
    stopping when `has_next` is `false`. Collect each video `id`.

    ```python theme={null}
    def collect_video_ids(path: str) -> list[str]:
        ids: list[str] = []
        cursor: str | None = None
        while True:
            params: dict[str, Any] = {"limit": 200}
            if cursor is not None:
                params["cursor"] = cursor

            page = call("GET", path, params=params)
            ids.extend(item["id"] for item in page["data"])

            pagination = page["pagination"]
            if not pagination["has_next"]:
                break
            cursor = pagination["next_cursor"]
        return ids


    video_ids = collect_video_ids("/account-videos")
    print(f"{len(video_ids)} videos")
    ```
  </Step>

  <Step title="Request engagement deltas in batches">
    `POST /v2/videos/deltas` takes up to **200** `video_ids` and optional `windows` —
    look-back periods in days (1–365, default `[1, 7, 14, 30]`) to compute each delta over.
    Send your ids in batches of 200, with a tight window like `[7]` for week-over-week
    movement, and read the per-video deltas off the live response.

    ```python theme={null}
    def chunked(items: list[str], size: int = 200):
        for i in range(0, len(items), size):
            yield items[i : i + size]


    deltas: list[dict[str, Any]] = []
    for batch in chunked(video_ids, 200):
        result = call(
            "POST",
            "/videos/deltas",
            json={"video_ids": batch, "windows": [7]},
        )
        deltas.extend(result["data"])
    ```

    <Tip>
      `GET /v2/videos/deltas` accepts the same request as query parameters (`video_ids`
      repeated, optional `windows`) when a GET fits your tooling better.
    </Tip>
  </Step>

  <Step title="Flag the breakouts">
    Sort the deltas by the change metric you care about — view growth for raw reach, or the
    engagement change for quality — and flag the top movers. Read the delta fields from the
    live response; here we sort on a `views` change and treat the top slice as breakouts.

    ```python theme={null}
    def view_delta(row: dict[str, Any]) -> float:
        # Read whichever change field the delta response carries for the window.
        return float(row.get("views_delta", row.get("views", 0)) or 0)


    ranked = sorted(deltas, key=view_delta, reverse=True)
    breakouts = ranked[:10]

    for row in breakouts:
        print(row.get("id"), "Δviews(7d):", int(view_delta(row)))

    client.close()
    ```
  </Step>
</Steps>

<Warning>
  A newly tracked video has `last_scrape_time: null` and no baseline yet, so its delta
  is meaningless until at least two refreshes have landed. Skip videos without history,
  or re-run once the window has filled — see [Data freshness](/data-freshness).
</Warning>

<Columns cols={2}>
  <Card title="Error handling" href="/api-reference/errors">
    The RFC 9457 problem shape the client parses.
  </Card>

  <Card title="Rate limits" href="/api-reference/rate-limits">
    Back off on 429 when looping over large video libraries.
  </Card>

  <Card title="Python client" href="/examples/python">
    A fuller typed client to build these loops on.
  </Card>
</Columns>
