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

# Fetch New Sponsor Activity

> Fetch placements first detected since your last completed search without repeatedly loading the same history.

Use activity windows when you poll for new sponsorship discoveries. Each
window returns placements first detected after your starting time and through
the end time reported by the API.

The interval is:

```text theme={null}
activity_window.after < detected_at <= activity_window.through
```

You can use activity windows with [List sponsorships](/sponsorships/sponsorships)
for placement rows or [List sponsors](/sponsorships/sponsors) for results grouped
by sponsor name.

## Before you start

Choose all of the following before the first request:

* A scope filter such as `sponsor_id`, `sponsor_name`, `categories`, or
  `publication_url`, depending on the endpoint.
* One or more explicit platforms: `youtube`, `instagram`, `podcast`,
  `substack`, or `tiktok`.
* A fixed `since` date for the publication history you want to monitor.
* A durable place to save the final `activity_window.through` time.

<Note>
  `since` and `until` filter when content was published. `activity_after` filters
  when Upriver first detected a placement. They answer different questions and
  can be used together.
</Note>

For ongoing polling, always send the same explicit `since` date. If you omit
it, the default publication window moves forward over time, and content can
age out before a newly detected placement is delivered. Usually omit `until`
so each new completed poll can include newly published content. Set `until`
only when you intentionally want a permanent upper bound.

## Fetch a complete activity window

<Steps>
  <Step title="Start with activity_after">
    Send an ISO 8601 time with a timezone offset. This example finds new YouTube
    placements for a known sponsor name:

    ```bash theme={null}
    curl --get 'https://api.upriver.ai/v1/sponsorships' \
      --header 'X-API-Key: YOUR_API_KEY' \
      --data-urlencode 'sponsor_name=Notion' \
      --data-urlencode 'platforms=youtube' \
      --data-urlencode 'since=2025-01-01' \
      --data-urlencode 'activity_after=2026-08-23T10:00:00Z' \
      --data-urlencode 'limit=50'
    ```

    Every activity response includes its effective interval:

    ```json theme={null}
    {
      "results": [
        {
          "sponsor_id": "br_01hzy8k9m4qp7v2wxr3d5abcde",
          "sponsor_name": "Notion",
          "detected_at": "2026-08-23T10:18:42Z"
        }
      ],
      "activity_window": {
        "after": "2026-08-23T10:00:00Z",
        "through": "2026-08-23T10:42:00Z"
      },
      "next_cursor": "apg1...",
      "has_more": true
    }
    ```

    If your requested time predates activity tracking, the response's `after`
    shows the effective start time used by the API.
  </Step>

  <Step title="Follow every page cursor">
    When `has_more` is `true`, send `next_cursor` with the same filters. Omit
    `activity_after`; the cursor already identifies the window.

    ```bash theme={null}
    curl --get 'https://api.upriver.ai/v1/sponsorships' \
      --header 'X-API-Key: YOUR_API_KEY' \
      --data-urlencode 'sponsor_name=Notion' \
      --data-urlencode 'platforms=youtube' \
      --data-urlencode 'since=2025-01-01' \
      --data-urlencode 'cursor=apg1...' \
      --data-urlencode 'limit=50'
    ```

    Continue until `has_more` is `false`. Do not change filters, platforms,
    publication dates, or sort order between pages.
  </Step>

  <Step title="Save the final through time">
    The final page has no `next_cursor`:

    ```json theme={null}
    {
      "results": [],
      "activity_window": {
        "after": "2026-08-23T10:00:00Z",
        "through": "2026-08-23T10:42:00Z"
      },
      "has_more": false
    }
    ```

    After you successfully process the whole final page, save
    `activity_window.through`.
  </Step>

  <Step title="Use the through time for the next poll">
    Send the saved time as `activity_after`:

    ```bash theme={null}
    curl --get 'https://api.upriver.ai/v1/sponsorships' \
      --header 'X-API-Key: YOUR_API_KEY' \
      --data-urlencode 'sponsor_name=Notion' \
      --data-urlencode 'platforms=youtube' \
      --data-urlencode 'since=2025-01-01' \
      --data-urlencode 'activity_after=2026-08-23T10:42:00Z' \
      --data-urlencode 'limit=50'
    ```

    The next window starts after the previous window's `through` time.
  </Step>
</Steps>

## Switch from a sponsor name to sponsor\_id

Use `sponsor_name` as a convenience when you initially know only a brand's
external name. When a result includes `sponsor_id`, store it as the canonical
identifier for later sponsor-specific searches, saved filters, and
deduplication.

Finish all pages of the name-based window first. Then start a new ID-based
window from the prior response's `activity_window.through`:

```bash theme={null}
curl --get 'https://api.upriver.ai/v1/sponsorships' \
  --header 'X-API-Key: YOUR_API_KEY' \
  --data-urlencode 'sponsor_id=br_01hzy8k9m4qp7v2wxr3d5abcde' \
  --data-urlencode 'platforms=youtube' \
  --data-urlencode 'since=2025-01-01' \
  --data-urlencode 'activity_after=2026-08-23T10:42:00Z' \
  --data-urlencode 'limit=50'
```

Finish the name-based window before changing filters so the saved through time
does not introduce a gap. Page cursors remain bound to the filters that
produced them.

## Use sponsor summary activity

`GET /v1/sponsors` follows the same cursor and timestamp workflow. During an
activity search:

* `new_placements_in_window` counts newly detected placements for that sponsor
  name.
* `last_detected_at` reports the latest detection in the window.
* `sort_by=total_ads` ranks by `new_placements_in_window`.
* `sort_by=recent` ranks by `last_detected_at`.

The endpoint keeps its name-based grouping, so two results can share one
`sponsor_id`. Use `sponsor_id` to recognize that they represent the same brand.

## Delivery and billing behavior

Activity means first detection only. A later correction, deletion, or change
that makes an older placement match your filters does not create new activity.
The API does not provide a correction feed or maintain a permanent list of
which sponsors your account has seen.

Existing result-based prices apply to rows returned by each request. Retries
and overlapping workers can return and charge for the same rows again. Process
one page at a time for a given cursor. Persist `activity_window.through` only
after the final page has been processed.

## Troubleshooting

<AccordionGroup>
  <Accordion title="The API says the cursor does not match">
    Use the same endpoint, filters, platforms, publication dates, and sort order
    that produced it. Also omit `activity_after` while following a page cursor.
  </Accordion>

  <Accordion title="The API returns 409">
    Your requested start is later than the activity currently available, or a
    pinned page is temporarily unavailable. Keep your saved time or cursor and
    retry later.
  </Accordion>

  <Accordion title="The API returns 503 for a platform">
    Activity is not currently available through every required ingestion path
    for that platform. Keep your prior activity time and retry later. Contact
    support if the response persists.
  </Accordion>

  <Accordion title="An activity window returns no results">
    An empty window is valid. Save `activity_window.through` and use it as the
    next `activity_after` so you do not search the same interval again.
  </Accordion>
</AccordionGroup>
