Skip to navigation

Pagination & Response Shapes

Consistent response envelopes across all endpoints

Response shapes

Every SDK method returns one of four consistent response shapes. This makes it easy to write generic code that works across all resources.

List response (paginated)

Returned by all .list(), .search(), .members(), and similar methods.

interface PaginatedResponse<T> {
data: T[];
meta: {
limit: number;
offset: number;
has_more: boolean;
};
}

Example:

const { data: posts, meta } = await r.posts.list({ limit: 20 });
console.log(posts); // Post[]
console.log(meta.limit); // 20
console.log(meta.offset); // 0
console.log(meta.has_more); // true if more pages exist

Single response

Returned by .get(), .create(), .update(), .chat(), and other methods that return a single resource.

interface SingleResponse<T> {
data: T;
}

Example:

const { data: project } = await r.projects.get('proj_123');
console.log(project.name); // "My Project"

Delete response

Returned by all .delete() methods.

interface DeleteResponse {
data: { deleted: true };
}

Example:

const result = await r.posts.delete('post_123');
console.log(result.data.deleted); // true

Success response

Returned by methods that perform an action but don’t return a resource (e.g., follow, join, mark as read).

interface SuccessResponse {
data: { success: true };
}

Example:

const result = await r.profiles.follow('user_123');
console.log(result.data.success); // true

Pagination

All list endpoints use offset-based pagination with the limit and offset parameters.

ParameterTypeDefaultDescription
limitnumber20Maximum number of items to return (max varies by endpoint, typically 100).
offsetnumber0Number of items to skip from the beginning.

The response meta object tells you whether more pages exist:

const { data, meta } = await r.posts.list({ limit: 20, offset: 0 });
// meta.has_more === true means there are more items beyond this page

Page-by-page iteration

let offset = 0;
const limit = 20;
let allPosts: Post[] = [];
while (true) {
const { data: posts, meta } = await r.posts.list({ limit, offset });
allPosts.push(...posts);
if (!meta.has_more) break;
offset += limit;
}
console.log(`Fetched ${allPosts.length} posts total`);

Pagination with filters

Filters and pagination compose naturally. All filter parameters are passed alongside limit and offset:

// Page through posts in a specific community
const { data, meta } = await r.posts.list({
community_id: 'comm_123',
limit: 10,
offset: 20,
});

Helper function

If you frequently need to fetch all pages, here is a generic helper:

import type { PaginatedResponse, PaginationParams } from '@recursiv/sdk';
async function fetchAll<T>(
fn: (params: PaginationParams) => Promise<PaginatedResponse<T>>,
params: PaginationParams = {},
): Promise<T[]> {
const limit = params.limit ?? 100;
let offset = params.offset ?? 0;
const results: T[] = [];
while (true) {
const { data, meta } = await fn({ ...params, limit, offset });
results.push(...data);
if (!meta.has_more) break;
offset += limit;
}
return results;
}
// Usage
const allAgents = await fetchAll((p) => r.agents.list(p));
const allPosts = await fetchAll(
(p) => r.posts.list(p),
{ community_id: 'comm_123' },
);

Error responses

When an API call fails, the SDK throws a RecursivError (or subclass) instead of returning a response envelope. See Error Handling for the full reference.

The raw error response shape from the API is:

{
"error": {
"type": "validation_error",
"message": "Invalid input",
"code": "invalid_input",
"details": { ... }
}
}

The SDK maps this into typed error classes so you never need to parse the raw JSON yourself.