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

# Poll for viral days

> Keep a chart and a notification feed in step with Sonder, a few calls every few hours.

This is the loop an integration like Publisher Champ's runs for each connected author. It uses two endpoints:

* **`GET /events`** for anything that just happened (went viral, passed a milestone), to notify the author.
* **`GET /stats`** for day-by-day views, to draw or annotate a chart.

## 1. Notify on new events

Store one cursor per author. Each run, ask for events after it, handle them, and save the new cursor. You only ever see each event once.

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function pollEvents(author) {
    let after = author.sonderCursor; // null on the first run
    for (;;) {
      const url = new URL("https://api.yousonder.com/v1/events");
      if (after) url.searchParams.set("after", after);
      url.searchParams.set("limit", "100");
      const res = await fetch(url, { headers: { Authorization: `Bearer ${author.sonderKey}` } });
      if (!res.ok) return handleError(author, res);
      const page = await res.json();

      for (const event of page.data) {
        if (event.type === "video.went_viral") {
          await notify(author, `"${event.video.title}" is going viral: ${event.views.toLocaleString()} views`);
        } else if (event.type === "video.milestone_reached") {
          await notify(author, `"${event.video.title}" just passed ${event.milestone.toLocaleString()} views`);
        }
      }

      after = page.next_cursor ?? after;
      await saveCursor(author, after);
      if (!page.has_more) break;
    }
  }
  ```

  ```python Python theme={null}
  import requests

  def poll_events(author):
      after = author.sonder_cursor  # None on the first run
      while True:
          params = {"limit": 100}
          if after:
              params["after"] = after
          res = requests.get(
              "https://api.yousonder.com/v1/events",
              headers={"Authorization": f"Bearer {author.sonder_key}"},
              params=params,
          )
          if not res.ok:
              return handle_error(author, res)
          page = res.json()
          for event in page["data"]:
              if event["type"] == "video.went_viral":
                  notify(author, f'"{event["video"]["title"]}" is going viral: {event["views"]:,} views')
              elif event["type"] == "video.milestone_reached":
                  notify(author, f'"{event["video"]["title"]}" just passed {event["milestone"]:,} views')
          after = page["next_cursor"] or after
          save_cursor(author, after)
          if not page["has_more"]:
              break
  ```
</CodeGroup>

<Tip>
  The first call without `after` returns the author's whole history since 15 June 2026. If you only want to notify about new things, page through that first batch without notifying, save the cursor, and notify from the next run on. `occurred_at` tells you when each event happened.
</Tip>

## 2. Annotate the chart

Ask for the days you draw. The range is up to 90 days, and videos with no new views in the range are left out.

```bash theme={null}
curl "https://api.yousonder.com/v1/stats?start_date=2026-09-01&end_date=2026-09-16&group_by=book" \
  -H "Authorization: Bearer sonder_live_YOUR_KEY"
```

With `group_by=book`, each book gets its views per day plus `videos_went_viral` listing the videos that went viral that day, which lines up with sales charts drawn per book:

```json theme={null}
{
  "book": { "id": "5b1d7c2e-8f0a-4c1b-9d3e-2a6f4b8c9e01", "title": "The Quiet Hours" },
  "days": [
    { "date": "2026-09-14", "views": 2210, "videos_went_viral": [] },
    { "date": "2026-09-15", "views": 31480, "videos_went_viral": ["9f3c2a71-4b6e-4d8a-a1c5-7e2b9d0f3a64"] }
  ]
}
```

Sonder doesn't store ASINs. Match books to your catalogue by title; `GET /books` lists every title along with its subtitle and author name.

## 3. Schedule it

* Run every 1 to 4 hours per author. Views refresh hourly, so more often gains nothing.
* Each key allows 60 requests a minute, far more than this loop needs.
* On `401 api_key_revoked`, mark the connection as disconnected and ask the author to reconnect.
* On `403 plan_inactive`, keep the connection and try again on the next run.
* On `429` or `5xx`, wait (use `Retry-After` when present) and try again.
