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.

1interface PaginatedResponse<T> {
2 data: T[];
3 meta: {
4 limit: number;
5 offset: number;
6 has_more: boolean;
7 };
8}

Example:

1const { data: posts, meta } = await r.posts.list({ limit: 20 });
2
3console.log(posts); // Post[]
4console.log(meta.limit); // 20
5console.log(meta.offset); // 0
6console.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.

1interface SingleResponse<T> {
2 data: T;
3}

Example:

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

Delete response

Returned by all .delete() methods.

1interface DeleteResponse {
2 data: { deleted: true };
3}

Example:

1const result = await r.posts.delete('post_123');
2console.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).

1interface SuccessResponse {
2 data: { success: true };
3}

Example:

1const result = await r.profiles.follow('user_123');
2console.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:

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

Page-by-page iteration

1let offset = 0;
2const limit = 20;
3let allPosts: Post[] = [];
4
5while (true) {
6 const { data: posts, meta } = await r.posts.list({ limit, offset });
7 allPosts.push(...posts);
8
9 if (!meta.has_more) break;
10 offset += limit;
11}
12
13console.log(`Fetched ${allPosts.length} posts total`);

Pagination with filters

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

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

Helper function

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

1import type { PaginatedResponse, PaginationParams } from '@recursiv/sdk';
2
3async function fetchAll<T>(
4 fn: (params: PaginationParams) => Promise<PaginatedResponse<T>>,
5 params: PaginationParams = {},
6): Promise<T[]> {
7 const limit = params.limit ?? 100;
8 let offset = params.offset ?? 0;
9 const results: T[] = [];
10
11 while (true) {
12 const { data, meta } = await fn({ ...params, limit, offset });
13 results.push(...data);
14 if (!meta.has_more) break;
15 offset += limit;
16 }
17
18 return results;
19}
20
21// Usage
22const allAgents = await fetchAll((p) => r.agents.list(p));
23const allPosts = await fetchAll(
24 (p) => r.posts.list(p),
25 { community_id: 'comm_123' },
26);

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:

1{
2 "error": {
3 "type": "validation_error",
4 "message": "Invalid input",
5 "code": "invalid_input",
6 "details": { ... }
7 }
8}

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