How to Export Trellus Calls Without Mistaking the Row Cap for the Total

Collect smaller intervals, replace retries and check coverage before calling a file complete.

How to Export Trellus Calls Without Mistaking the Row Cap for the Total
On this page

If a call-data request returns exactly 1,000 rows, don't put “all calls” on the file. Trellus returns the newest matching sessions up to the requested limit; it doesn't provide a cursor for the next page. You may have the whole interval, or only its newest part.

The practical way forward is to split the interval, keep a record of each request and combine only the successful pieces that didn't fill their limit. This guide walks through that process, including a fictional 1,070-session example and a worksheet you can copy into an approved working document.

Use this with an existing, authorized server-side Call Data API connection. It is a collection procedure, not a new export feature or a tested customer dataset. If you only need a total, use the API's aggregate endpoint instead of collecting every row. The Call Data API reference owns the endpoint, field and rate-limit definitions.

Write down the scope before the first request

“Yesterday's calls” isn't enough to reproduce an export. Name the reporting time zone, write an explicit start and end with their UTC offsets, and resolve both to UTC. A local calendar day is not always 24 hours when daylight saving time changes. Don't calculate every local day by adding 24 hours to midnight.

Trellus's top-level start is inclusive and end is exclusive. Write the interval as [start, end): include the start, exclude the end. Set both values explicitly. Leaving the end at its default of “now” changes the target while you work.

Keep the permitted team, readable reps, call filters and selected columns beside those bounds. This guide's example selects call sessions with conversation_type = 1. It does not define those rows as outbound attempts, answered calls, unique prospects or qualified conversations. Add only the documented filters that your actual question needs, and keep them unchanged across child requests.

Verify readable users through /visible in your existing connection. A team_id selects a permitted team; it doesn't grant access. An API key inherits its user's permissions and is not a separately restricted read-only credential. Keep it server-side and out of worksheets, screenshots, prompts and logs. The authentication reference explains the headers and scope.

Start with a small set of columns

For a collection check, session_id, started_at and resource_id are enough to identify rows, inspect their times and associate them with readable reps. Don't collect phone numbers, names or transcripts just to count rows. Add necessary fields only within your organization's approved access and retention rules.

The request below describes October 1, 2026 in UTC. It contains no credential and sends nothing from this page. Use the documented POST endpoint, https://rpt-2.trellus.ai/session-list-v6, through your existing server-side client.

{
  "start": 1790812800000000,
  "end": 1790899200000000,
  "cnf": [
    [{ "column_name": "conversation_type", "operator": "EQ", "value_safe": 1 }]
  ],
  "limit": 1000,
  "select": ["session_id", "started_at", "resource_id"]
}

Watch the units. Top-level request bounds use Unix microseconds. Returned started_at values, and values in a started_at filter, use seconds. In JavaScript, the reference uses Date.parse(ISO_WITH_TIMEZONE) * 1000 for request bounds. Don't apply that multiplication again to an already converted value.

The response is an array of positional rows, not objects named after your columns. With the select above, cell 0 is the session ID, cell 1 is the start time in seconds, and cell 2 is the rep/resource ID. Save that column order with the result. Changing the header order without changing the request would mislabel the data.

Split a full interval; don't append another copy

For each successful response, compare its row count with the limit you actually requested. Fewer rows means that request did not fill its cap. Exactly the limit means coverage is still uncertain—even when the interval happens to contain exactly that many sessions.

  1. Record the parent. Save the exact bounds, scope, limit, request time, status and returned row count. If it filled the cap, label it split; excluded from final rows.
  2. Choose a midpoint. Replace [A, B) with [A, M) and [M, B). Use the same exact value for the first child's end and the second child's start. Don't subtract a second or add a “safety” overlap.
  3. Check both children. Keep a successful child that returns fewer than its limit. Split a child that fills its limit again. A failed request stays pending; it isn't an empty interval.
  4. Combine only the final pieces. These are the successful, uncapped intervals with no children of their own—the leaves of your split. Keep parent responses for the audit trail, not in the final dataset.

Don't move the next request's end to the oldest timestamp in a capped response and assume that creates a safe next page. Several sessions can share that timestamp. Without a documented tie-breaking cursor, a timestamp-only shortcut can omit or repeat rows.

A fictional export: 1,000 rows become 1,070

All counts below are invented to explain the method, not Trellus benchmarks or a live API test. Assume the dataset stays unchanged during these requests, both children succeed, and their returned session IDs are distinct.

Worked interval ledger for October 1, 2026, UTC
RequestIntervalRows / limitTreatment
P00:00 inclusive → next 00:00 exclusive1,000 / 1,000Split. Keep for reference; exclude these rows from the final set.
P.100:00 inclusive → 12:00 exclusive640 / 1,000Successful uncapped leaf. Include once.
P.212:00 inclusive → next 00:00 exclusive430 / 1,000Successful uncapped leaf. Include once.

The two leaves cover the parent's entire interval with no gap or overlap. Their combined count is 640 + 430 = 1,070. It is not 2,070: the parent's 1,000 rows are superseded, not another batch to add. A session starting exactly at noon belongs to P.2, not P.1.

Now change one detail: P.2 fails with a server error. You have 640 collected rows and an unresolved half-day, not a 640-call day. Keep that state visible. If a later successful P.2 response contains 1,000 rows, split P.2 rather than declaring the retry complete.

Make retries replace attempts, not create new calls

Give each interval a stable label such as P.2, and give its attempts separate labels such as P.2/a1 and P.2/a2. Only one accepted response for that leaf should feed the combined dataset. Preserve earlier attempt status and timing in the ledger without appending both bodies.

Fictional retry history for the same P.2 interval
AttemptResultRows acceptedDecision
P.2/a1503 response; no successful listNoneKeep P.2 unresolved. Retain status and time; use the bounded retry policy.
P.2/a2200 response; 430 rows at limit 1,000430Accept this response for P.2. P.1 plus P.2 now contributes 640 + 430 distinct IDs under the example's unchanged-data assumption.

If you later re-collect P.2 successfully, replace its accepted response as a whole and record the change. Don't append the new 430 rows to the earlier 430. If the new response fills the cap, P.2 becomes unresolved again and needs splitting; an older accepted file cannot establish what the newer capped response omitted.

Deduplicate the combined rows by session_id. Do not use the phone number, contact or rep-plus-time as the unique key: separate calls can share those values. If the same session ID has conflicting values across responses, retain the discrepancy for investigation rather than silently choosing whichever arrived last. Deduplication removes repeated IDs; it does not recover an omitted interval.

A repeated ID across supposedly disjoint leaves also needs an explanation, even if its selected values match. Recheck the exact request bounds, scope and collection times before describing that union as fully covered. Removing the duplicate is not a substitute for that check.

The reference's default session-list budget is two requests per four seconds, shared by v5 and v6 for the same user. Pace calls at least 2.5 seconds apart and coordinate other workers using that user's key. Account overrides and upstream protection can differ.

For a 429 response, wait the Retry-After delay in seconds. Use a bounded retry policy for transient 5xx failures. Set your attempt and elapsed-time limits before starting; after those limits, leave the leaf unresolved. Errors may be plain text, so don't let a failed JSON parse hide the original status. Stop on authentication or permission failures and investigate the existing connection—don't rotate accounts or broaden access to finish the file.

Use this coverage worksheet before releasing the file

Copy these headings into an approved document. Add one interval row per leaf, with its attempt history linked or recorded alongside it. Keep credentials and unnecessary customer data out of the worksheet.

Blank interval ledger — repeat one row for every final leaf
Leaf / parentStart inclusive / end exclusive, UTCScope / column-order referenceAccepted attempt / saved responseHTTP / rows / limit / retrieval timesIncluded, replaced or unresolved — reason
Fill inFill in both exact boundsLink the unchanged scopeFill in, or “none”Fill in, including errorsFill in; don't leave an unknown state blank

Retain split parents separately with an excluded; replaced by these children entry. Sort the final leaves by their start. The first must start at the original start; each following start must equal the previous end; the last must end at the original end. Mark any failure of those checks on the affected leaf. Then apply the checks below to the whole file.

Copyable call-export coverage worksheet
CheckWhat to recordWhat blocks a full-coverage label
Original scopeLocal dates, named zone, resolved UTC bounds, permitted team/reps, filters and column order.Different scope between requests, or unreadable people treated as included.
Leaf identityLeaf label, parent label, exact inclusive start and exclusive end.A gap, overlap, or both a parent and its children included.
Accepted responseAttempt label, request/response times, HTTP status, requested limit, returned rows and saved result reference.A failed or unvalidated response, or rows equal to the requested cap.
ID checkCombined row count, distinct session IDs, duplicates and conflicting versions.Unexplained duplicates, missing IDs or conflicting values.
Coverage checkSorted leaf bounds join exactly from the original start to its end.Any unresolved or omitted leaf, including a dense interval you couldn't split usefully.
Count cross-checkSame-scope aggregate count, collection times and any difference from distinct exported IDs.An unexplained difference; matching totals alone don't establish identical records.
Release noteWhat was collected, access limits, collection period and remaining uncertainty.A claim of a point-in-time snapshot or company-wide access you haven't established.

A successful 200 response containing [] is a zero-row leaf for that readable scope and those filters. A timeout, 401, 429 or 5xx is not. Keep these cases separate even when your collection program produces no rows for both.

Know when smaller windows stop helping

If a very narrow interval still fills the cap, inspect the documented rep filters only when they can form a known, exhaustive and nonoverlapping partition of the original readable scope. Don't quietly drop unassigned, unknown or inaccessible records to make the pieces fit. A directory of visible reps by itself is not proof that every matching session falls into exactly one proposed filter.

Sessions sharing a timestamp can make repeated time splits unhelpful. Stop at your chosen request/time budget and record the exact unresolved interval. Ask for supported help with that collection problem rather than inventing an offset or calling the remaining rows a sample of known size.

There is another limit even after every leaf is uncapped: these requests are made at different times. The documented API contract does not promise a frozen snapshot across them. A fixed end time stops the requested period moving forward; it doesn't freeze later corrections or changes to access.

Use metric-details-v7 with the same bounds and top-level filters and a counting select, { "cnf": [] }, as a cross-check. Record when you queried it. A mismatch needs investigation; a match is useful but isn't proof that every field or record is identical. If you need an immutable snapshot for a formal audit, establish a supported process for that requirement before labeling this export as one.

The final file should say what it actually contains: for example, “Call sessions collected for this readable scope and UTC interval; all recorded leaves returned below their cap; collected between these times.” If a leaf remains unresolved, label the file partial and name that gap. That gives the next reviewer a specific place to resume.

If the next question is why the CRM reports a different number, use the separate dialer-to-CRM reconciliation guide. For other calling tasks, return to How-to Guides.

Method: original collection worksheet and fictional worked example from Trellus. Product limits and request shapes follow the public Call Data API documentation checked October 24, 2026. No customer-data request, account connection or production export test is presented.

Back to the Blog

About the author

Trellus Team

Guides from the team behind Trellus on power and parallel dialing, sales coaching, and working inside your CRM.