Regbase API

Consuming Changes

Poll the change feed and continue without skipping records.

Use /v1/entities/changes to read entity changes. The filter parameter is required.

curl --get "https://api.regbase.com/v1/entities/changes" \
  --header "Authorization: Bearer $REGBASE_API_KEY" \
  --data-urlencode "filter=jurisdiction:US.CA" \
  --data-urlencode "page[limit]=100"

Choose a feed strategy

Filters apply to the entity snapshot after the change. A narrow filter does not report an entity that stops matching it. Delete events also have an empty snapshot and usually do not match field filters.

Use a narrow filter only when missing departures and deletions is acceptable.

To maintain an accurate mirror, consume these two feeds with separate cursors:

-no:type
no:type

Together they cover snapshots where type is present or missing, including deletions. Apply your desired filter locally. Upsert matching entities and remove entities that no longer match. If you poll the feeds independently, store the latest recordedAt for each entity and ignore older changes.

Change metadata such as changeType, changedKeys, and recordedAt cannot be used in the server-side filter.

Continue from a response

Each successful response contains:

  • data: the matching change records
  • links.next: a continuation URL when another page may be ready
  • meta.cursor: the last safe position scanned by the server

Follow links.next exactly when it is present. This drains the current backlog without rebuilding query parameters.

When links.next is absent, you are at the current head. Save meta.cursor, put it in page[after], and poll again later. The cursor may advance even when data is empty. Always save it after you successfully process the response.

const pollIntervalMs = 1_000;

async function pollChanges(filter) {
  const initialUrl = new URL(
    "https://api.regbase.com/v1/entities/changes",
  );
  initialUrl.searchParams.set("filter", filter);
  initialUrl.searchParams.set("page[limit]", "100");

  let url = initialUrl;

  while (true) {
    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${process.env.REGBASE_API_KEY}`,
      },
    });

    if (response.status === 429) {
      await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
      continue;
    }

    if (!response.ok) {
      throw new Error(`Regbase returned ${response.status}`);
    }

    const document = await response.json();

    for (const change of document.data) {
      await applyChange(change);
    }

    if (document.links.next) {
      url = new URL(document.links.next);
      continue;
    }

    url = new URL(document.links.self);
    if (document.meta.cursor) {
      url.searchParams.set("page[after]", document.meta.cursor);
    }

    await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
  }
}

await Promise.all([
  pollChanges("-no:type"),
  pollChanges("no:type"),
]);

There is no required polling interval. Poll as often as your application needs.

Apply each change

The change resource is not an entity resource. Its id is the change ID, and its attributes include change metadata. The entity ID and type are in relationships.entity.data.

For create and update, either fetch relationships.entity.links.related or reconstruct the entity:

function entityFromChange(change) {
  const {
    changeType,
    changedKeys,
    mergedInto,
    recordedAt,
    ...attributes
  } = change.attributes;

  return {
    type: change.relationships.entity.data.type,
    id: change.relationships.entity.data.id,
    attributes,
  };
}

Use changeType to update your local store:

  • create or update: replace the local entity with the reconstructed or fetched entity.
  • delete: remove the ID in relationships.entity.data.
  • merge: remove that ID, then fetch the entity identified by mergedInto.

Change IDs and cursors are opaque. Compare or edit neither.

On this page