Polling filing and transaction feeds

Keep your application up to date with newly published company reports, insider activity, and institutional filings.

The API provides four feeds for newly published company reports, insider filings and proposed sales, insider transactions, and institutional filings. Poll one when your application needs to send an alert, update a database, or start another workflow as soon as new data is ready.

These are polling feeds, not push notifications. Your application checks its chosen feed on a schedule. Each response contains a page of events and a cursor that marks your place. After you process the page, save the cursor and include it in the next request to receive what Chadwin published afterward.

Choose a feed

FeedContainsSDK resourceOptional SDK filter
Company reportsForms 10-K, 10-Q, 20-F, and 40-F, including amendmentsclient.feeds.companyReportsforms
Insider activityForms 3, 4, 5, and 144, including amendmentsclient.feeds.insiderActivityforms
Insider transactionsTransaction rows from Forms 4 and 5client.feeds.insiderTransactionstransactionCodes
Institutional filingsForms 13F-HR and 13F-NT, including amendmentsclient.feeds.institutionalFilingsforms

An unfiltered Company Reports feed includes both annual and quarterly reports. A form filter names a filing family, so 10-Q also includes 10-Q/A events. Existing consumers that need only annual reports should keep an explicit annual form filter.

Use the dataset guides to understand the filings and the API reference for each feed's exact response fields.

Choose where to start

Your first request determines which existing events, if any, the consumer receives:

  • Start with recent events: call listLatest(filters?). Process the returned events, then continue from its next_cursor
  • Start with new events only: call startFromNow(filters?). It returns an empty page and a cursor at the current end of the feed
  • Resume a consumer: call listAfter({ cursor, ...filters }) with its saved cursor

Use startFromNow only when you intend to skip all events already in the feed.

Run a durable consumer

This example starts at the present on its first run and resumes from saved state after that. Replace startFromNow with listLatest if the first run should include recent events.

import {
  Chadwin,
  type CompanyReportFeedFilters,
} from "@chadwin/sdk";

const client = new Chadwin({ apiKey: process.env.CHADWIN_API_KEY! });
const filters: CompanyReportFeedFilters = { forms: ["10-Q"] };
const wait = (milliseconds: number) =>
  new Promise((resolve) => setTimeout(resolve, milliseconds));

const storedCursor = await loadCursor();
let page = storedCursor
  ? await client.feeds.companyReports.listAfter({
      ...filters,
      cursor: storedCursor,
    })
  : await client.feeds.companyReports.startFromNow(filters);

while (true) {
  for (const update of page.updates) {
    await processOnce(update.feed_event_id, update);
  }

  await saveCursor(page.next_cursor);

  if (!page.has_more) {
    await wait(60_000);
  }

  page = await client.feeds.companyReports.listAfter({
    ...filters,
    cursor: page.next_cursor,
  });
}

loadCursor and saveCursor represent durable storage controlled by your application. processOnce should save feed_event_id with the work it creates so that processing the same event again does not repeat that work.

When has_more is true, request the next page immediately. When it is false, wait at least 60 seconds before polling again. Do not poll more than once per minute after the consumer catches up.

Store consumer state

Save the cursor only after every event in the page succeeds. If the process stops before that save, the next run starts from the prior cursor and safely attempts the page again.

A cursor records one position in a feed. It does not record the feed or filters used with it, so store these values together:

  • The feed name
  • The filter values
  • The latest successfully processed cursor

Treat the cursor as an opaque value and store it exactly as returned. Keep the same filters when continuing from it. If you need different filters, start a separate consumer with its own cursor.

Handle failures without losing data

After a network, rate-limit, server, or processing failure, retry from the last saved cursor. Use backoff for repeated failures and honor Retry-After when the API returns it.

Feed cursors do not have a routine expiration period. If the API rejects a stored cursor, first check that your application restored the full value and paired it with the original feed and filters.

If no valid saved cursor remains, choose a new starting point explicitly. listLatest starts with recent events; startFromNow skips to events published after the new request. Neither option restores every event that may have followed the lost cursor.