> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.recursiv.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.recursiv.io/_mcp/server.

# Pagination & Response Shapes

## 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.

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

Example:

```typescript
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.

```typescript
interface SingleResponse<T> {
  data: T;
}
```

Example:

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

### Delete response

Returned by all `.delete()` methods.

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

Example:

```typescript
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).

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

Example:

```typescript
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.

| Parameter | Type     | Default | Description                                                                |
| --------- | -------- | ------- | -------------------------------------------------------------------------- |
| `limit`   | `number` | `20`    | Maximum number of items to return (max varies by endpoint, typically 100). |
| `offset`  | `number` | `0`     | Number of items to skip from the beginning.                                |

The response `meta` object tells you whether more pages exist:

```typescript
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

```typescript
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`:

```typescript
// 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:

```typescript
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](/sdk/setup/error-handling) for the full reference.

The raw error response shape from the API is:

```json
{
  "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.