---
title: "Pagination for List Endpoints"
slug: pagination-for-list-endpoints
description: "We've added optional pagination for list endpoints to optimize retrieving data."
created_at: "2025-10-16"
updated_at: "2025-10-16"
image: "/static/posts/pagination-for-list-endpoints.jpg"
humans: ["lucas-costa", "lucas-vieira"]
---

When retrieving data, it's important for it to be quick and easy. Our `list` endpoints have always returned _all_ existing data when called. While this default is convenient, it can **lead to performance issues** when dealing with large datasets.

Today, we've introduced **optional pagination** to our existing list endpoints to help you efficiently browse through large datasets when needed.

## How to use it

When calling an existing list endpoint, you can optionally add the `limit` query parameter to the request.

<CodeTabs codeHeight={250}>

```nodejs
const resend = new Resend('re_xxxxxxxxx');

const contacts = await resend.contacts.list({
  limit: 30,
});
```

```php
$resend = Resend::client('re_xxxxxxxxx');

$contacts = $resend->contacts->list([
  'limit' => 30,
]);
```

```python
import resend

resend.api_key = "re_xxxxxxxxx"

contacts = resend.contacts.list(limit=30)
```

```ruby
Resend.api_key = "re_xxxxxxxxx"

contacts = resend.contacts.list(limit: 30)
```

```go
import "github.com/resend/resend-go/v2"

client := resend.NewClient("re_xxxxxxxxx")

contacts, err := client.Contacts.List(&resend.ListContactsOptions{
  Limit: 30,
})
```

```rust
use resend_rs::{Resend, Result, types::ListContactsOptions};

#[tokio::main]
async fn main() -> Result<()> {
    let resend = Resend::new("re_xxxxxxxxx");

    let list_opts = ListContactsOptions::default().with_limit(30);
    let contacts = resend.contacts.list(list_opts).await?;

    Ok(())
}
```

```java
import com.resend.*;

public class Main {
    public static void main(String[] args) {
        Resend resend = new Resend("re_xxxxxxxxx");

        ListContactsResponse contacts = resend.contacts().list(30);
    }
}
```

```dotnet
using Resend;

IResend resend = ResendClient.Create("re_xxxxxxxxx");

var contacts = resend.Contacts.List(new ListContactsOptions { Limit = 30 });
```

```curl
curl -X GET https://api.resend.com/contacts?limit=30 \
-H "Authorization: Bearer re_xxxxxxxxx"
```
</CodeTabs>

This will return the first 30 contacts in the list.
```json
{
  "object": "list",
  "has_more": true,
  "data": [
    /* Array of 30 contacts */
  ]
}
```

To retrieve additional pages, use the `before` or `after` params to go through the list.

### Forward pagination

To paginate forward through results (newer to older items), use the `after` parameter with the ID of the **last item** from the current page:

<CodeTabs codeHeight={250}>
```nodejs
const resend = new Resend('re_xxxxxxxxx');

// First page
const { data: firstPage } = await resend.contacts.list({ limit: 10 });

// Second page (if has_more is true)
if (firstPage.has_more) {
  const lastId = firstPage.data[firstPage.data.length - 1].id;
  const { data: secondPage } = await resend.contacts.list({
    limit: 10,
    after: lastId
  });
}
````

```php
$resend = Resend::client('re_xxxxxxxxx');

// First page
$firstPage = $resend->contacts->list(['limit' => 10]);

// Second page (if has_more is true)
if ($firstPage['has_more']) {
    $lastId = end($firstPage['data'])['id'];
    $secondPage = $resend->contacts->list([
        'limit' => 10,
        'after' => $lastId
    ]);
}
````

```python
import resend

resend.api_key = "re_xxxxxxxxx"

# First page
first_page = resend.contacts.list(limit=10)

# Second page (if has_more is true)
if first_page['has_more']:
    last_id = first_page['data'][-1]['id']
    second_page = resend.contacts.list(limit=10, after=last_id)
```

```ruby
Resend.api_key = "re_xxxxxxxxx"

# First page
first_page = Resend::Contacts.list(limit: 10)

# Second page (if has_more is true)
if first_page['has_more']
  last_id = first_page['data'].last['id']
  second_page = Resend::Contacts.list(limit: 10, after: last_id)
end
```

```go
import "github.com/resend/resend-go/v2"

client := resend.NewClient("re_xxxxxxxxx")

// First page
firstPage, err := client.Contacts.List(&resend.ListContactsOptions{
    Limit: 10,
})

// Second page (if has_more is true)
if firstPage.HasMore {
    lastId := firstPage.Data[len(firstPage.Data)-1].ID
    secondPage, err := client.Contacts.List(&resend.ListContactsOptions{
        Limit: 10,
        After: lastId,
    })
}
```

```rust
use resend_rs::{Resend, Result, types::ListContactsOptions};

#[tokio::main]
async fn main() -> Result<()> {
    let resend = Resend::new("re_xxxxxxxxx");

    // First page
    let list_opts = ListContactsOptions::default().with_limit(10);
    let first_page = resend.contacts.list(list_opts).await?;

    // Second page (if has_more is true)
    if first_page.has_more {
        let last_id = &first_page.data.last().unwrap().id;
        let list_opts = ListContactsOptions::default()
            .with_limit(10)
            .list_after(last_id);
        let second_page = resend.contacts.list(list_opts).await?;
    }

    Ok(())
}
```

```java
import com.resend.*;

public class Main {
    public static void main(String[] args) {
        Resend resend = new Resend("re_xxxxxxxxx");

        // First page
        ListContactsResponse firstPage = resend.contacts().list(10);

        // Second page (if has_more is true)
        if (firstPage.getHasMore()) {
            String lastId = firstPage.getData().get(firstPage.getData().size() - 1).getId();
            ListContactsResponse secondPage = resend.contacts().list(10, lastId, null);
        }
    }
}
```

```dotnet
using Resend;

IResend resend = ResendClient.Create("re_xxxxxxxxx");

// First page
var firstPage = await resend.Contacts.ListAsync(limit: 10);

// Second page (if has_more is true)
if (firstPage.Content.HasMore)
{
    var lastId = firstPage.Content.Data.Last().Id;
    var secondPage = await resend.Contacts.ListAsync(limit: 10, after: lastId);
}
```

```curl
# First page
curl -X GET 'https://api.resend.com/contacts?limit=10' \
     -H 'Authorization: Bearer re_xxxxxxxxx'

# Second page
curl -X GET 'https://api.resend.com/contacts?limit=10&after=LAST_ID_FROM_PREVIOUS_PAGE' \
     -H 'Authorization: Bearer re_xxxxxxxxx'
```

</CodeTabs>

### Backwards pagination

To paginate backward through results (older to newer items), use the `before` parameter with the ID of the **first item** from the current page (or the most recent ID you have in your system):

<CodeTabs codeHeight={250}>
```nodejs
const resend = new Resend('re_xxxxxxxxx');

// Start from a specific point and go backward
const page = await resend.contacts.list({
  limit: 10,
  before: 'some-contact-id'
});

if (page.data.has_more) {
  const firstId = page.data.data[0].id;
  const previousPage = await resend.contacts.list({
    limit: 10,
    before: firstId
  });
}
```

```php
$resend = Resend::client('re_xxxxxxxxx');

// Start from a specific point and go backward
$page = $resend->contacts->list([
    'limit' => 10,
    'before' => 'some-contact-id'
]);

if ($page['has_more']) {
    $firstId = $page['data'][0]['id'];
    $previousPage = $resend->contacts->list([
        'limit' => 10,
        'before' => $firstId
    ]);
}
````

```python
import resend

resend.api_key = "re_xxxxxxxxx"

# Start from a specific point and go backward
page = resend.contacts.list(limit=10, before="some-contact-id")

if page["has_more"]:
    first_id = page["data"][0]["id"]
    previous_page = resend.contacts.list(limit=10, before=first_id)
```

```ruby
Resend.api_key = "re_xxxxxxxxx"

# Start from a specific point and go backward
page = Resend::Contacts.list(limit: 10, before: 'some-contact-id')

if page['has_more']
  first_id = page['data'].first['id']
  previous_page = Resend::Contacts.list(limit: 10, before: first_id)
end
```

```go
import "github.com/resend/resend-go/v2"

client := resend.NewClient("re_xxxxxxxxx")

// Start from a specific point and go backward
page, err := client.Contacts.List(&resend.ListContactsOptions{
    Limit:  resend.Int(10),
    Before: resend.String("some-contact-id"),
})

if page.HasMore {
    firstId := page.Data[0].ID
    previousPage, err := client.Contacts.List(&resend.ListContactsOptions{
        Limit:  resend.Int(10),
        Before: resend.String(firstId),
    })
}
```

```rust
use resend_rs::{Resend, Result, types::ListContactsOptions};

#[tokio::main]
async fn main() -> Result<()> {
    let resend = Resend::new("re_xxxxxxxxx");

    // Start from a specific point and go backward
    let list_opts = ListContactsOptions::default()
        .with_limit(10)
        .list_before("some-contact-id");
    let page = resend.contacts.list(list_opts).await?;

    if page.has_more {
        let first_id = &page.data.first().unwrap().id;
        let list_opts = ListContactsOptions::default()
            .with_limit(10)
            .list_before(first_id);
        let previous_page = resend.contacts.list(list_opts).await?;
    }

    Ok(())
}
```

```java
import com.resend.*;

public class Main {
    public static void main(String[] args) {
        Resend resend = new Resend("re_xxxxxxxxx");

        // Start from a specific point and go backward
        ListContactsResponse page = resend.contacts().list(10, null, "some-contact-id");

        if (page.getHasMore()) {
            String firstId = page.getData().get(0).getId();
            ListContactsResponse previousPage = resend.contacts().list(10, null, firstId);
        }
    }
}
```

```dotnet
using Resend;
using System.Linq;

IResend resend = ResendClient.Create("re_xxxxxxxxx");

// Start from a specific point and go backward
var page = await resend.Contacts.ListAsync( new PaginatedQuery() {
  Limit = 50,
  Before = "some-contact-id",
});

if (page.Content.HasMore)
{
    var firstId = page.Content.Data.First().Id;
    var prevPage = await resend.Contacts.ListAsync( new PaginatedQuery() {
      Limit = 50,
      Before = firstId.ToString(),
    });
}
```

```curl
curl -X GET 'https://api.resend.com/contacts?limit=10&before=some-contact-id' \
     -H 'Authorization: Bearer re_xxxxxxxxx'
```
</CodeTabs>

You can safely navigate lists with guaranteed stability, even if new objects are created or deleted while you’re still requesting pages.

## Supported endpoints

The following endpoints support optional pagination:
- [`GET /api-keys`](/docs/api-reference/api-keys/list-api-keys)
- [`GET /audiences`](/docs/api-reference/audiences/list-audiences)
- [`GET /contacts`](/docs/api-reference/contacts/list-contacts)
- [`GET /domains`](/docs/api-reference/domains/list-domains)
- [`GET /broadcasts`](/docs/api-reference/broadcasts/list-broadcasts)

## Query Parameters

All paginated endpoints support the following parameters:
- `limit`: The number of items to return per page. Maximum is `100`. Minimum is `1`.
- `after`: The cursor after which to start retrieving items. To get the next page, use the ID of the last item from the current page. This will return the page that **starts** after the object with this ID (excluding the passed ID itself).
- `before`: The cursor before which to start retrieving items. To get the previous page, use the ID of the first item from the current page. This will return the page that **ends before** the object with this ID (excluding the passed ID itself).

<Callout>
Note that for these endpoints the `limit` parameter is _optional_. If you do not provide a `limit`, all available resources will be returned in a single response.
</Callout>

## List Endpoints Response

When you request a paginated endpoint, the response includes:

```json
{
  "object": "list", // Always set to list.
  "has_more": true, // Indicates whether there are more elements available.
  "data": [
    /* Array of resources */
  ]
}
```

Pagination may return the following validation errors:

- `validation_error`: Invalid cursor format or limit out of range (1-100).
- `validation_error`: Both `before` and `after` parameters provided.

```json
{
  "name": "validation_error",
  "statusCode": 422,
  "message": "The pagination limit must be a number between 1 and 100. See https://resend.com/docs/api-reference/pagination for more information."
}
```

## Best Practices

Follow these best practices when using pagination:

1. **Choose a `limit` that balances performance and usability.** Smaller pages are good for real-time applications, while larger pages (hundreds of items) work better for bulk processing.
2. **Handle pagination gracefully.** Always check the `has_more` field before attempting to fetch additional pages. This prevents unnecessary API calls when you’ve reached the end of the dataset.
3. **Consider rate limits.** Implement appropriate delays or batching strategies if processing many pages.

## Conclusion

We're excited for the new control pagination gives you over your data and plan to include pagination by default in the future. For more information, check out our [pagination documentation](/docs/api-reference/pagination).

If you have any questions, please reach out to us and we'll be happy to help.