> ## Documentation Index
> Fetch the complete documentation index at: https://docs.brudcast.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Response format

> The platform API response envelope, offset and cursor pagination, filters, and how the email send API's envelope differs.

Every platform API response is JSON wrapped in the same envelope. Lists add pagination details in
`meta`.

## The envelope

<ResponseField name="success" type="boolean" required>
  `true` for a successful request, `false` for an error.
</ResponseField>

<ResponseField name="message" type="string" required>
  A human-readable summary, such as `Contacts retrieved successfully`. Show it in logs, but don't
  branch on its wording.
</ResponseField>

<ResponseField name="data" type="object | array">
  The resource, or the page of resources. Absent on acknowledgements, such as a delete.
</ResponseField>

<ResponseField name="meta" type="object">
  Pagination details on list responses.
</ResponseField>

A single resource:

```json theme={"system"}
{
  "success": true,
  "message": "Sending domains fetched successfully",
  "data": {
    "id": "0b9d7c1e-5f7a-4a57-9a0e-3c2f5d1e8a41",
    "name": "mail.example.com",
    "status": "verified"
  }
}
```

An acknowledgement:

```json theme={"system"}
{
  "success": true,
  "message": "Operation completed successfully"
}
```

Timestamps are ISO 8601 strings. Errors use the same envelope with `success: false`. See
[Errors](/developers/errors).

## Offset pagination

Most list endpoints take a page number and a page size.

<ParamField query="page" type="integer" default="1">
  The page to return, starting at 1.
</ParamField>

<ParamField query="limit" type="integer">
  Items per page, from 1 to 100.
</ParamField>

The response carries these fields in `meta`:

```json theme={"system"}
{
  "success": true,
  "message": "Contacts retrieved successfully",
  "data": [],
  "meta": {
    "total": 137,
    "perPage": 20,
    "currentPage": 1,
    "lastPage": 7,
    "firstPage": 1,
    "firstPageUrl": "/?page=1",
    "lastPageUrl": "/?page=7",
    "nextPageUrl": "/?page=2",
    "previousPageUrl": null
  }
}
```

| Field                                                           | Meaning                                                                                |
| --------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `total`                                                         | Items across all pages                                                                 |
| `perPage`                                                       | Page size used for this response                                                       |
| `currentPage`, `firstPage`, `lastPage`                          | Page numbers                                                                           |
| `firstPageUrl`, `lastPageUrl`, `nextPageUrl`, `previousPageUrl` | Query strings for those pages, not full URLs. `nextPageUrl` is `null` on the last page |

Build the next request yourself by incrementing `page`, and stop when `currentPage` equals
`lastPage`.

<CodeGroup>
  ```bash cURL theme={"system"}
  # One page at a time: read meta.lastPage, then repeat with page=2, 3, and so on.
  curl -G https://core-service.prod.brudcast.com/api/v1/user/contacts \
    -H "X-API-Key: $BRUDCAST_API_KEY" \
    --data-urlencode "page=1" \
    --data-urlencode "limit=100"
  ```

  ```javascript Node.js theme={"system"}
  const base = "https://core-service.prod.brudcast.com/api/v1/user/contacts";
  const headers = { "X-API-Key": process.env.BRUDCAST_API_KEY };

  let page = 1;
  let lastPage = 1;

  do {
    const response = await fetch(`${base}?page=${page}&limit=100`, { headers });
    const body = await response.json();

    for (const contact of body.data) {
      console.log(contact.id);
    }

    lastPage = body.meta.lastPage;
    page += 1;
  } while (page <= lastPage);
  ```

  ```python Python theme={"system"}
  import os
  import requests

  base = "https://core-service.prod.brudcast.com/api/v1/user/contacts"
  headers = {"X-API-Key": os.environ["BRUDCAST_API_KEY"]}

  page = 1
  while True:
      body = requests.get(base, headers=headers, params={"page": page, "limit": 100}).json()

      for contact in body["data"]:
          print(contact["id"])

      if page >= body["meta"]["lastPage"]:
          break
      page += 1
  ```

  ```php PHP theme={"system"}
  <?php
  $base = "https://core-service.prod.brudcast.com/api/v1/user/contacts";
  $page = 1;

  do {
      $ch = curl_init($base . "?page=" . $page . "&limit=100");
      curl_setopt_array($ch, [
          CURLOPT_HTTPHEADER => ["X-API-Key: " . getenv("BRUDCAST_API_KEY")],
          CURLOPT_RETURNTRANSFER => true,
      ]);
      $body = json_decode(curl_exec($ch), true);

      foreach ($body["data"] as $contact) {
          echo $contact["id"], PHP_EOL;
      }

      $lastPage = $body["meta"]["lastPage"];
      $page++;
  } while ($page <= $lastPage);
  ```
</CodeGroup>

## Cursor pagination

Endpoints that read large, time-ordered logs use a cursor instead of page numbers: the
[message log](/developers/sending/message-status) (`/messages`), webhook deliveries
(`/webhook-deliveries`) and the campaign report endpoints (`/campaigns/reports/overview`,
`/campaigns/reports/performance` and `/campaigns/reports/trends`).

<ParamField query="cursor" type="string">
  The `nextCursor` value from the previous page. Leave it out for the first page.
</ParamField>

<ParamField query="limit" type="integer">
  Items per page, from 1 to 100.
</ParamField>

| `meta` field    | Meaning                                                                                                |
| --------------- | ------------------------------------------------------------------------------------------------------ |
| `nextCursor`    | Pass this as `cursor` to get the next page. `null` when there are no more pages                        |
| `hasMore`       | `false` on the last page. Some endpoints leave it out and signal the end with `nextCursor: null` alone |
| `limit`         | The page size, on endpoints that echo it                                                               |
| `retentionDays` | How far back the data goes, on endpoints that read data kept for a limited time                        |

Keep requesting until `nextCursor` is `null`.

## Filters

Filters are query parameters, and each endpoint documents its own in the
[API reference](/api-reference/introduction). Names you'll see often:

| Parameter    | Typical use                                               |
| ------------ | --------------------------------------------------------- |
| `search`     | Free-text match, for example on a contact's name or email |
| `status`     | One or more status values                                 |
| `from`, `to` | A date range, as ISO 8601                                 |

A filter that takes several values repeats the parameter: `?status=active&status=archived`. An
invalid filter value fails with `422` and a validation error.

## The email send API's envelope

The [email send API](/developers/sending/http-api) uses `status` instead of `success`, and has no
`meta` or `code`:

```json theme={"system"}
{
  "status": true,
  "message": "Email queued for delivery",
  "data": { "jobId": "3f1c2a9e-8b7d-4c6e-9a51-2d0e7f4b6c10" }
}
```

Its errors carry `status: false` and a `message`, plus an `errors` array on validation failures.

## Related

<Columns cols={2}>
  <Card title="Errors" icon="circle-alert" href="/developers/errors">
    Error codes, validation errors and status codes.
  </Card>

  <Card title="API reference" icon="square-terminal" href="/api-reference/introduction">
    Parameters and response schemas for every endpoint.
  </Card>

  <Card title="Message status" icon="list-checks" href="/developers/sending/message-status">
    Cursor pagination in practice, on the message log.
  </Card>

  <Card title="Rate limits" icon="gauge" href="/developers/rate-limits">
    What to do when a paging loop starts returning `429`.
  </Card>
</Columns>
