Follow the endpoint's continuation contract
Many list endpoints return data.items and data.cursor. Where supported, pass that cursor back as the cursor query parameter while retaining the original filters. Treat it as opaque: it may wrap a platform token, a page number, a timestamp or source identity. Do not decode it, substitute another provider's token, or assume every endpoint uses this pattern.
null means no usable next cursor was supplied, not proof that all history was retrieved. Check has_more and warnings where present:has_more: null means unknown; false describes the returned source's continuation state or bounded surface, not necessarily the entire platform history.
| Underlying scheme | Examples | What the cursor holds |
|---|---|---|
| Opaque platform token | tiktok/search, meta_ads/search, facebook/marketplace-search, instagram/search-hashtag | An opaque continuation value; keep the original query and filters. |
| Page number | tiktokshop/search, tiktokshop/product-reviews, instagram/search-reels, google/search | A next-page value where this endpoint supports paging; some routes instead document a page parameter. |
| Timestamp watermark | tiktok/user-followers, tiktok/user-following | The min_time watermark the platform pages by. |
| Continuation token | youtube/shorts, youtube/community-posts, youtube/lives | The returned cursor for this endpoint; do not exchange it with another YouTube surface. |
For an endpoint using items and cursor, the loop looks like this:
const key = process.env.MONOCRAWL_API_KEY;
if (!key) throw new Error('Set MONOCRAWL_API_KEY on your server.');
let cursor = null;
const seen = new Set();
// Bound collection: each successful uncached page can cost credits.
for (let page = 0; page < 10; page++) {
const url = new URL('https://www.monocrawl.com/v1/tiktok/search');
url.searchParams.set('query', 'coffee brewing');
if (cursor) url.searchParams.set('cursor', cursor);
const res = await fetch(url, { headers: { 'x-api-key': key } });
const body = await res.json();
if (!body.success) throw new Error(body.error.message);
for (const item of body.data.items ?? []) console.log(item);
cursor = body.data.cursor;
if (!cursor || seen.has(cursor)) break;
seen.add(cursor);
}
// Stopping (including at the page cap) is not proof of complete history.Each page is a separate request under the endpoint's billing rules. Some endpoints are deliberately single-page and say so in their description (instagram/search-top returns Instagram’s one ranked page; instagram/reels-trending returns a fresh small batch per call).
Bundles and unsupported continuation
Social full-profile bundles expose posts, posts_cursor and posts_pagination.next_params. Use all the supplied next parameters together; some continuations require more than one token. A null next_params means no usable next request is offered, not that every post has been collected. The requested posts count is a desired page size; the source may return fewer or more.complete: true describes successful components and one bounded source page, not full history. Check partial, legs and warnings.
YouTube's channel bundle uses recent_videos, cursor and recent_videos_pagination. Other composites can have named collections without continuation. A fallback may not support a primary source's cursor or sort; an unsupported request can be refused before collection rather than restarting the first page. The endpoint reference and returned metadata take precedence over the generic loop above.
The response cache: repeat reads are free
Eligible public responses may enter a shared response cache. A repeat of the same endpoint with the same parameters can be served from a reusable entry while it remains valid. A cache hit costs 0 credits net; the response says "cached": true, the x-cache header says HIT, and your request log marks the row as a hit. A 3-credit search re-checked inside the window costs nothing, with zero code on your side.
Identical concurrent requests can share one upstream fetch: successful waiters receive a free cache hit. A waiting request may show a temporary reservation and matching refund in its ledger. This is best-effort, not an exactly-once guarantee. Entries may be evicted or exceed the size limit; a shared-store outage, lock expiry or wait timeout can cause a separate billed fetch. Use the idempotency contract for retries of the same logical direct request.
The default maximum lifetimes are below. Endpoint overrides and eviction can shorten them:
| Data | Lifetime | Examples |
|---|---|---|
| Live | 2 minutes | trending, live, comments, quotes, deals |
| Volatile | 5 minutes | searches, post and video lists, followers, listings |
| Item | 10 minutes | a post, a video, a product, a hotel page, reviews |
| Profile | 30 minutes | profiles, channels, companies, sellers |
| Hourly | 1 hour | trends, best-seller charts, price history |
| Reference | 6 hours | categories, directories, filters, typeahead |
| Static | 24 hours | locations, languages, currencies |
What never enters the cache: errors, synthetic fallbacks, sandbox samples, and anything that depends on who is asking — your balance, your monitors, jobs and reports are always live and never shared. Cache keys are canonicalized: ?a=1&b=2 and ?b=2&a=1 hit the same entry, and the page cursor is part of the key, so different page cursors do not collide. A cached page can still be as old as its permitted lifetime.
Asking for a fresh answer
Add fresh=1 (or send Cache-Control: no-cache) to skip the cache and request a new retrieval. A successful uncached retrieval is billed under the endpoint’s rules, the response carries x-cache-fresh: applied, and an eligible answer can update the shared cache. So that this cannot become a way to burn upstream quota, each key may ask for a fresh answer up to 30 times a minute; beyond that the request is served normally and the header says ignored. Add max_age=<seconds> (minimum 30) to accept a cached answer only if it is at most that old: a hit is still free, anything older is fetched at the endpoint price.
Streaming long searches (SSE)
The multi-source search endpoints (search/all, search/creators) accept stream=1 and answer text/event-stream: a plan event with the leg list, source-started / source-completed per leg as they actually run (with latency, upstream cost and items found), a cost rollup, enrichment-completed events when comment enrichment is on — then a result event carrying the same envelope a plain call returns. Billing, receipts and your usage log are identical either way. The HTTP stream can start with 200 and end with an error envelope, so inspect the final event’s success field. A transport exception may emit an error event instead; a dropped stream is not proof of success or failure. Disconnecting is not a supported cancellation mechanism.
Upstream caching on metered routes
Some flagship-upstream endpoints additionally accept cache_max_age-style recency controls at the platform layer; where an endpoint supports one, its parameter table on the endpoint reference lists it. When you need a new retrieval, request fresh=1 and check x-cache-fresh andcached. This bypasses Monocrawl’s cache when honoured, not every upstream’s cache, and cannot guarantee the source has updated its data.