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

# Bundles

> Group saved addresses into named bundles and manage their membership

Group addresses into named bundles — the same bundles you see in [Octav Pro](https://pro.octav.fi).

<Info>
  **Cost:** 1 credit per call
</Info>

<Warning>
  Every address in a bundle must already be in your [address book](/api/endpoints/addressbook).
</Warning>

<Note>
  **Interactive Playground:** Test these endpoints in the [API Playground](/api-reference/bundles). Get your API key at [data.octav.fi](https://data.octav.fi/)
</Note>

***

## The bundle object

<ResponseField name="id" type="string">
  Bundle identifier.
</ResponseField>

<ResponseField name="name" type="string">
  Bundle name.
</ResponseField>

<ResponseField name="addresses" type="array">
  Addresses in the bundle.
</ResponseField>

```json theme={null}
{
  "id": "7714a4a5-487b-4b36-99ba-537e9cca8e62",
  "name": "Client A",
  "addresses": [
    "0xe760ad8df0a54aafd95e3bc271b65d224abeaa57",
    "0x05d2e386525b0495e952f6945d0fa2480ed5b056"
  ]
}
```

<Note>
  Bundles created through the API are private. Sharing settings are managed in [Octav Pro](https://pro.octav.fi).
</Note>

***

## List Bundles

### Endpoint

```bash theme={null}
GET https://api.octav.fi/v1/bundles
```

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://api.octav.fi/v1/bundles" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.octav.fi/v1/bundles', {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  const { data } = await response.json();
  ```
</CodeGroup>

### Example Response

```json theme={null}
{
  "data": [
    {
      "id": "7714a4a5-487b-4b36-99ba-537e9cca8e62",
      "name": "Client A",
      "addresses": ["0xe760ad8df0a54aafd95e3bc271b65d224abeaa57"]
    }
  ]
}
```

***

## Create Bundle

### Endpoint

```bash theme={null}
POST https://api.octav.fi/v1/bundles
```

### Parameters

<ParamField body="name" type="string" required>
  1–255 characters. Must be unique.
</ParamField>

<ParamField body="addresses" type="array" required>
  1–100 addresses per request, each already in your address book. A bundle holds up to 10.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.octav.fi/v1/bundles" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Client A",
      "addresses": ["0xe760ad8df0a54aafd95e3bc271b65d224abeaa57"]
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch('https://api.octav.fi/v1/bundles', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      name: 'Client A',
      addresses: ['0xe760ad8df0a54aafd95e3bc271b65d224abeaa57']
    })
  });

  const { data } = await response.json();
  ```
</CodeGroup>

### Response

`201` with the new bundle.

***

## Get Bundle

### Endpoint

```bash theme={null}
GET https://api.octav.fi/v1/bundles/{bundleId}
```

### Parameters

<ParamField path="bundleId" type="string" required>
  Bundle identifier.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://api.octav.fi/v1/bundles/7714a4a5-487b-4b36-99ba-537e9cca8e62" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    `https://api.octav.fi/v1/bundles/${bundleId}`,
    { headers: { 'Authorization': `Bearer ${apiKey}` } }
  );

  const { data } = await response.json();
  ```
</CodeGroup>

***

## Rename Bundle

### Endpoint

```bash theme={null}
PATCH https://api.octav.fi/v1/bundles/{bundleId}
```

### Parameters

<ParamField path="bundleId" type="string" required>
  Bundle identifier.
</ParamField>

<ParamField body="name" type="string" required>
  1–255 characters. Must be unique.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH "https://api.octav.fi/v1/bundles/7714a4a5-487b-4b36-99ba-537e9cca8e62" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "name": "Client A — Q1" }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    `https://api.octav.fi/v1/bundles/${bundleId}`,
    {
      method: 'PATCH',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ name: 'Client A — Q1' })
    }
  );

  const { data } = await response.json();
  ```
</CodeGroup>

***

## Delete Bundle

### Endpoint

```bash theme={null}
DELETE https://api.octav.fi/v1/bundles/{bundleId}
```

### Parameters

<ParamField path="bundleId" type="string" required>
  Bundle identifier.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE "https://api.octav.fi/v1/bundles/7714a4a5-487b-4b36-99ba-537e9cca8e62" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  await fetch(`https://api.octav.fi/v1/bundles/${bundleId}`, {
    method: 'DELETE',
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });
  ```
</CodeGroup>

### Response

`204` with no body. The addresses stay in your address book.

***

## Add Address to Bundle

### Endpoint

```bash theme={null}
POST https://api.octav.fi/v1/bundles/{bundleId}/addresses
```

### Parameters

<ParamField path="bundleId" type="string" required>
  Bundle identifier.
</ParamField>

<ParamField body="address" type="string" required>
  Wallet address, already in your address book.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.octav.fi/v1/bundles/7714a4a5-487b-4b36-99ba-537e9cca8e62/addresses" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "address": "0x05d2e386525b0495e952f6945d0fa2480ed5b056" }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    `https://api.octav.fi/v1/bundles/${bundleId}/addresses`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ address: '0x05d2e386525b0495e952f6945d0fa2480ed5b056' })
    }
  );

  const { data } = await response.json();
  ```
</CodeGroup>

### Response

Returns the updated bundle.

***

## Remove Address from Bundle

### Endpoint

```bash theme={null}
DELETE https://api.octav.fi/v1/bundles/{bundleId}/addresses/{address}
```

### Parameters

<ParamField path="bundleId" type="string" required>
  Bundle identifier.
</ParamField>

<ParamField path="address" type="string" required>
  Wallet address.
</ParamField>

### Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE "https://api.octav.fi/v1/bundles/7714a4a5-487b-4b36-99ba-537e9cca8e62/addresses/0x05d2e386525b0495e952f6945d0fa2480ed5b056" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    `https://api.octav.fi/v1/bundles/${bundleId}/addresses/${address}`,
    { method: 'DELETE', headers: { 'Authorization': `Bearer ${apiKey}` } }
  );

  const { data } = await response.json();
  ```
</CodeGroup>

### Response

Returns the updated bundle with `200`. Removing the last member returns `422` — delete the bundle instead.

***

## Limits

| Limit                | Default |
| -------------------- | ------- |
| Bundles              | 5       |
| Addresses per bundle | 10      |

Contact us to raise these.

Bundle names must be unique and match case-sensitively. Two bundles cannot hold exactly the same addresses.

***

## Error Responses

| Code                                 | Status | Meaning                              |
| ------------------------------------ | ------ | ------------------------------------ |
| `VALIDATION_ERROR`                   | 400    | Invalid or duplicate address         |
| `BUNDLE_NOT_FOUND`                   | 404    | No such bundle                       |
| `BUNDLE_NAME_TAKEN`                  | 409    | Name already used                    |
| `BUNDLE_MEMBERS_ALREADY_BUNDLED`     | 409    | Another bundle holds these addresses |
| `BUNDLE_QUOTA_REACHED`               | 422    | Bundle limit reached                 |
| `BUNDLE_SIZE_LIMIT_EXCEEDED`         | 422    | Address limit reached                |
| `BUNDLE_EMPTY`                       | 422    | A bundle cannot be empty             |
| `BUNDLE_ADDRESS_NOT_IN_ADDRESS_BOOK` | 422    | Address is not in your address book  |
| `INTERNAL_ERROR`                     | 500    | Unexpected failure                   |

```json theme={null}
{
  "error": {
    "code": "BUNDLE_ADDRESS_NOT_IN_ADDRESS_BOOK",
    "message": "Not in the address book: 0x05d2e386...",
    "addresses": ["0x05d2e386525b0495e952f6945d0fa2480ed5b056"]
  }
}
```

<Note>
  **Rate limit:** 360 requests/min.
</Note>

***

## Related Endpoints

<CardGroup cols={2}>
  <Card title="Address Book" icon="address-book" href="/api/endpoints/addressbook">
    Manage saved addresses
  </Card>

  <Card title="Portfolio" icon="wallet" href="/api/endpoints/portfolio">
    Holdings for an address
  </Card>
</CardGroup>
