Why does gong-get-extensive-data return the same page 6 times due to cursor being sent as a query string instead of in the POST body?

This topic was automatically generated from Slack. You can find the original thread here.

Bug: gong-get-extensive-data returns the same page 6× because the cursor is sent as a query string instead of in the POST body

Repo: PipedreamHQ/pipedream
Component: components/gong/
Action: gong-get-extensive-data (key gong-get-extensive-data, version 0.0.5)

TL;DR

The shared paginator in gong.app.mjs injects the Gong cursor into params (query string). That’s correct for GET /v2/calls (used by gong-list-calls) but wrong for POST /v2/calls/extensive (used by gong-get-extensive-data), where Gong’s API expects the cursor in the JSON request body. Gong therefore ignores the cursor on every iteration, returns page 1 each time, the action concatenates the duplicates, and terminates at the DEFAULT_MAX = 600 safety cap. The caller receives ~100 unique calls duplicated 6× with no signal of truncation.

Affected files

  1. components/gong/gong.app.mjsgetResourcesStream, lines 147-197.
  2. components/gong/actions/get-extensive-data/get-extensive-data.mjs — calls app.paginate(...) inside run, line 200ish.
  3. components/gong/common/constants.mjs — defines DEFAULT_MAX = 600.

Root cause

In gong.app.mjs:147-197, getResourcesStream loops like this:

js
async **getResourcesStream({ resourceFn, resourceFnArgs, resourceName, max }) {
  let cursor;
  let resourcesCount = 0;
  while (true) {
    response = await resourceFn({
      ...resourceFnArgs,
      params: { ...resourceFnArgs?.params, cursor },   // :warning: cursor goes in query string
    });
    const nextResources = resourceName && response[resourceName] || response;
    if (!nextResources?.length) return;
    for (const resource of nextResources) {
      yield resource;
      resourcesCount += 1;
      if (resourcesCount >= max) return;              // :warning: caps at DEFAULT_MAX (600)
    }
    if (!response.records?.cursor) return;
    cursor = response.records.cursor;
  }
}

The action that gets paginated is defined in get-extensive-data.mjs:

js
methods: {
  getExtensiveData(args = {}) {
    return this.app.post({ path: "/calls/extensive", ...args });
  },
},
async run({ $ }) {
  // ...
  const calls = await app.paginate({
    resourceFn: getExtensiveData,
    resourceFnArgs: {
      step: $,
      data: { filter, contentSelector },              // :warning: filter+selector go in body
    },
    resourceName: "calls",
    max: maxResults,
  });
}

The wrapper calls this.app.post({ path, ...args }), which delegates to makeRequest({ method: "post", ...args }), which spreads args into the axios config. Axios then sends:

  • request body: { filter, contentSelector } (from the data field)
  • URL: POST /v2/calls/extensive?cursor=value (from the params field)

Per Gong’s documented API for POST /v2/calls/extensive (the very link in the action’s own description field, Gong | Gong public API docs), the cursor is a property of the JSON request body, not a query parameter. Gong ignores the query-string cursor and treats every request as page 1. The response carries a fresh cursor each time (because Gong is just issuing the next-page cursor for page 1), so the loop’s if (!response.records?.cursor) return; exit is never taken. The loop ultimately terminates only when resourcesCount >= max fires at 600.

The sibling action works correctly because GET endpoints accept query-string cursors

components/gong/actions/list-calls/list-calls.mjs wraps GET /v2/calls. It exposes a top-level cursor prop and forwards it explicitly:

js
return app.listCalls({
  step,
  params: { ...params, cursor },
  // ...
});

For a GET, params is the query string and Gong does respect it there. So gong-list-calls paginates fine — verified empirically in the same Gong workspace with the same OAuth connection: page 0 returns 100 calls plus cursor eyJ...UEUQ; page 1 (called with that cursor) returns a different 100 calls plus a different cursor.

The bug is specific to combining the generic getResourcesStream paginator with a POST endpoint.

Empirical evidence

A single invocation in production with these inputs:

json
{
  "fromDateTime": "2026-04-15T00:00:00Z",
  "toDateTime":   "2026-04-30T23:59:59Z",
  "primaryUserIds": ["3233735285032703158", "6253634635529901593", "9158638369112583040"],
  "includeParties": true
}

Returned:

  • 600 entries in the response array
  • $summary: "Successfully retrieved data for 600 calls"
  • Wall time: ~10.7 seconds (consistent with 6 sequential HTTP calls to Gong)
  • Only 100 unique call IDs after deduping by metaData.id
  • Layout: positions 0–99, 100–199, 200–299, 300–399, 400–499, 500–599 are *byte-identical to each other
  • Verified: records[0] == records[100] as full JSON equality, including the nested parties array

The latest call’s scheduled field across all 100 unique calls is 2026-04-28T16:00:00-04:00 — within a query window that ran through 2026-04-30T23:59:59Z. The Gong workspace had additional calls on April 29 and April 30 that never made it into the response. There is nothing in the returned payload signaling the truncation: records, cursor, currentPageNumber, totalRecords, and requestId are all stripped because paginate(...) flattens to the bare item array.

The DEFAULT_MAX = 600 cap matches the observed termination exactly: 6 iterations × 100 calls per page.

Reproduction

  1. Connect a Gong workspace with substantially more than 100 calls in any 1–2 week window.
  2. Invoke gong-get-extensive-data with that window via fromDateTime/toDateTime (optionally narrowed by primaryUserIds).
  3. Dedupe the returned array by metaData.id. If unique count is exactly 100 and total count is exactly min(maxResults, 600), and unique count ≤ raw count divides evenly into the raw count, the bug is reproducing.