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

# Create Aggregate

Provision a new sub-tenant (aggregate) under your aggregator store. The new tenant is created with its own domain, admin user, and store configuration.

---

## Endpoint

```
POST /api/v3/aggregates
```

---

## Request Body

### Required Fields

| Field           | Type   | Description                                                                  |
| --------------- | ------ | ---------------------------------------------------------------------------- |
| `name`          | string | Store name (max 255 characters)                                              |
| `lang`          | string | Store language. Must be one of the supported codes (e.g., `en`, `es`, `pt`). |
| `timezone`      | string | Store timezone (IANA format, e.g., `America/New_York`)                       |
| `support_email` | string | Support email address (max 255 characters)                                   |

### Optional Fields

| Field                       | Type    | Default                            | Description                                                                                                                                                                                                                          |
| --------------------------- | ------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `final_domain`              | string  | Auto-generated from `name` as slug | Store domain (must be unique, max 255 chars)                                                                                                                                                                                         |
| `external_id`               | string  | `null`                             | Your own opaque identifier for the sub-tenant (max 255 chars)                                                                                                                                                                        |
| `primary_color`             | string  | `null`                             | Primary brand color (hex, e.g., `#336699`)                                                                                                                                                                                           |
| `secondary_color`           | string  | `null`                             | Secondary brand color (hex, e.g., `#FF6600`)                                                                                                                                                                                         |
| `reader_accent_color`       | string  | `null`                             | Reader accent color used in the reader's light and dark themes (hex, e.g., `#0066CC`)                                                                                                                                                |
| `reader_flashcards_enabled` | boolean | `false`                            | When `true`, signed-in readers of the store can create flashcards from text selections in PDF and EPUB products. Never offered to anonymous readers or on audiobooks. Off by default; the aggregator's own setting is not inherited. |
| `logo_url`                  | string  | `null`                             | Public URL of the store logo, pointing to a JPG, PNG, WEBP, or SVG image (max 2048 chars). The platform downloads and hosts the image; see [Logo and Icon Import](#logo-and-icon-import).                                            |
| `icon_url`                  | string  | `null`                             | Public URL of the store icon/favicon, pointing to a JPG, PNG, WEBP, or SVG image (max 2048 chars). The platform downloads and hosts the image; see [Logo and Icon Import](#logo-and-icon-import).                                    |
| `disable_selling`           | boolean | `false`                            | When `true`, disables all e-commerce functionality on the store (checkout, paid plans, paid content). Selling is enabled by default.                                                                                                 |
| `landing_enabled`           | boolean | `false`                            | When `true`, the public landing page is shown. Disabled by default for new aggregates.                                                                                                                                               |

:::info External ID
`external_id` lets you reference sub-tenants using an identifier from your own system (e.g., a CRM ID, an ERP code). It must be unique per aggregator and can be used on the [get](./aggregates-v3-get.md) and [update](./aggregates-v3-update.md) endpoints by passing `?id_type=external` on the URL.
:::

:::info Domain Format
`final_domain` supports two formats:

- **Platform subdomain**: `mybookstore.publica.la` (accessible at `https://mybookstore.publica.la`)
- **Custom domain**: `www.mybookstore.com` ([requires DNS configuration](https://help.publica.la/en/article/personalize-your-domain-ix2ylz/) pointing to Publica.la)

If omitted, a domain is auto-generated from the store name (e.g., name `"My Bookstore"` generates `mybookstore.publica.la`).
:::

### Logo and Icon Import

When you send `logo_url` or `icon_url`, the platform checks the URL during validation, then downloads the image and stores it as the store's own logo or icon:

- The URL must be reachable when you make the request and must serve a JPG, PNG, WEBP, or SVG image, as reported by the `Content-Type` header the server returns.
- The image can be at most 20,000 KB.
- Responses return the URL of the hosted copy on the platform's asset domain, not the URL you sent. Treat it as an opaque value.
- The icon sizes derived from `icon_url` (favicon and app icons) are generated in the background, so they can take a moment to appear on the store.

### Auth Token (SSO) Fields

Configure single sign-on for the sub-tenant by sending an `external_auth` object. This is the same JWT (HS256) auth token integration that previously required a manual setup through support; you can now provision it programmatically for any of your sub-tenants. See the [Auth Token guide](../../auth-integrations/auth-token.md) for the token format and login flow.

| Field                        | Type   | Default | Description                                                                                                                                  |
| ---------------------------- | ------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `external_auth.key`          | string | `null`  | Write-only. The HMAC secret used to sign your JWTs (minimum 32 characters). Sending a valid key enables SSO. Never returned in any response. |
| `external_auth.issuer`       | string | `null`  | Your platform identifier. Must match the `iss` claim in your tokens. Required when `key` is sent.                                            |
| `external_auth.redirect_url` | string | `null`  | Where users are sent on auth errors. Required when `key` is sent.                                                                            |
| `external_auth.login_url`    | string | `null`  | Your platform's login URL.                                                                                                                   |
| `external_auth.logout_url`   | string | `null`  | Where users are sent after logging out.                                                                                                      |
| `external_auth.home_url`     | string | `null`  | Your platform's home URL.                                                                                                                    |
| `external_auth.account_url`  | string | `null`  | Your platform's account URL.                                                                                                                 |

:::info Key is write-only
The `key` is a shared secret and is never returned by any endpoint (create, get, list, or update). Responses include an `external_auth` object with every field except `key`, plus an `enabled` boolean that reflects whether a key is configured.
:::

### Storefront Redirect Fields

Send a `storefront_redirect` object to send visitors of the sub-tenant's storefront to an external site. Use it for stores that only need to serve reading and leave browsing to another site, such as a discovery portal you run.

| Field                         | Type    | Default | Description                                                                                                    |
| ----------------------------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------- |
| `storefront_redirect.enabled` | boolean | `false` | Turns the redirect on (`true`) or off (`false`). Required when `storefront_redirect` is sent.                  |
| `storefront_redirect.url`     | string  | `null`  | Where visitors are sent. Must be an `http` or `https` URL (max 2048 chars). Required when `enabled` is `true`. |

:::info What the redirect covers
While the redirect is on, every storefront page sends visitors to `url`, on every domain of the store and whether or not they are signed in. The reader and the store dashboard keep working. When a reader signs in through an integration that sends `reader_exit_url` (see the [Auth Token guide](../../auth-integrations/auth-token.md)), that URL takes precedence over `url` for that reader.
:::

### Inherited from Aggregator

These values are automatically set from the aggregator and cannot be overridden:

| Attribute      | Description                               |
| -------------- | ----------------------------------------- |
| **Fees**       | Transaction and sales fee structure       |
| **Currencies** | Main and secondary currency configuration |

---

## What Happens on Create

When you create an aggregate, the platform automatically:

1. **Creates the tenant** with the provided configuration
2. **Provisions store infrastructure** (library, default shelves, sales settings)
3. **Inherits fees and currencies** from the aggregator
4. **Creates an admin user** linked to the email of the authenticated user
5. **Returns an `api_key`** to authenticate requests on behalf of the new store

---

## Request Examples

### Minimal (Auto-Generated Domain)

```bash
curl -X POST "https://yourstore.publica.la/api/v3/aggregates" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Bookstore",
    "lang": "en",
    "timezone": "America/New_York",
    "support_email": "support@mybookstore.com"
  }'
```

The domain is auto-generated as `mybookstore.publica.la`.

### With Auth Token (SSO)

```bash
curl -X POST "https://yourstore.publica.la/api/v3/aggregates" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Bookstore",
    "lang": "en",
    "timezone": "America/New_York",
    "support_email": "support@mybookstore.com",
    "external_auth": {
      "key": "your-32-character-or-longer-secret-key",
      "issuer": "my-platform",
      "redirect_url": "https://my-platform.com/auth-error",
      "logout_url": "https://my-platform.com/logout"
    }
  }'
```

### With Flashcards and Storefront Redirect

```bash
curl -X POST "https://yourstore.publica.la/api/v3/aggregates" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "University Library",
    "lang": "en",
    "timezone": "America/New_York",
    "support_email": "library@example.com",
    "reader_flashcards_enabled": true,
    "storefront_redirect": {
      "enabled": true,
      "url": "https://discovery.example.com"
    }
  }'
```

### With Branding and Custom Domain

```bash
curl -X POST "https://yourstore.publica.la/api/v3/aggregates" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Bookstore",
    "final_domain": "www.mybookstore.com",
    "external_id": "crm-12345",
    "lang": "en",
    "timezone": "America/New_York",
    "support_email": "support@mybookstore.com",
    "primary_color": "#1A237E",
    "secondary_color": "#FF6F00",
    "reader_accent_color": "#0066CC",
    "logo_url": "https://example.com/assets/logo.png",
    "icon_url": "https://example.com/assets/icon.png"
  }'
```

---

## Response

### Success Response (201 Created)

```json
{
  "data": {
    "id": "42",
    "external_id": null,
    "slug": "my-bookstore",
    "name": "My Bookstore",
    "final_domain": "mybookstore.publica.la",
    "lang": "en",
    "timezone": "America/New_York",
    "api_key": "api-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "support_email": "support@mybookstore.com",
    "primary_color": null,
    "secondary_color": null,
    "reader_accent_color": null,
    "reader_flashcards_enabled": false,
    "logo_url": null,
    "icon_url": null,
    "disable_selling": false,
    "landing_enabled": false,
    "storefront_redirect": {
      "enabled": false,
      "url": null
    },
    "external_auth": {
      "enabled": false,
      "issuer": null,
      "redirect_url": null,
      "login_url": null,
      "logout_url": null,
      "home_url": null,
      "account_url": null
    },
    "created_at": "2026-03-24T15:30:00+00:00"
  }
}
```

The `api_key` is always included in the create response. To retrieve it later, use `?include=api_key` on the [get](./aggregates-v3-get.md) or [list](./aggregates-v3-list.md) endpoints.

:::tip API Key Usage
Use the `api_key` in the `X-User-Token` header to make API requests directly to the sub-tenant's endpoints (content, orders, etc.).
:::

---

## Error Handling

### Validation Errors (422)

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "name": ["The name field is required."],
    "final_domain": ["The final domain has already been taken."],
    "support_email": ["The support email must be a valid email address."]
  }
}
```

### Invalid Domain Format (422)

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "final_domain": ["The final_domain format is invalid."]
  }
}
```

### Invalid Color Format (422)

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "primary_color": ["The primary color format is invalid."]
  }
}
```

### Invalid Logo or Icon (422)

Returned when the image cannot be fetched, is not a supported format, or is larger than 20,000 KB:

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "logo_url": ["The logo url could not be downloaded."],
    "icon_url": ["The icon url must point to a JPG, PNG, WEBP or SVG image."]
  }
}
```

### Authorization Errors (403)

Returned when the authenticated tenant is not allowed to provision sub-tenants. Two messages are possible:

```json
{
  "message": "Tenant does not have aggregation capabilities."
}
```

```json
{
  "message": "Aggregated tenants cannot provision tenants."
}
```

### Authentication Errors (401)

```json
{
  "message": "Unauthenticated."
}
```

---

## Validation Rules

| Field                         | Rules                                                                                                                                           |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                        | Required, string, max 255 characters                                                                                                            |
| `final_domain`                | Optional, string, max 255 characters, unique, valid domain format                                                                               |
| `external_id`                 | Optional, nullable, string, max 255 characters, unique per aggregator                                                                           |
| `lang`                        | Required, string, one of the supported language codes                                                                                           |
| `timezone`                    | Required, valid IANA timezone                                                                                                                   |
| `support_email`               | Required, valid email, max 255 characters                                                                                                       |
| `primary_color`               | Optional, nullable, hex color format (`#RRGGBB`)                                                                                                |
| `secondary_color`             | Optional, nullable, hex color format (`#RRGGBB`)                                                                                                |
| `reader_accent_color`         | Optional, nullable, hex color format (`#RRGGBB`)                                                                                                |
| `reader_flashcards_enabled`   | Optional, boolean                                                                                                                               |
| `logo_url`                    | Optional, nullable, `http` or `https` URL, max 2048 characters. Must be reachable and serve a JPG, PNG, WEBP, or SVG image of at most 20,000 KB |
| `icon_url`                    | Optional, nullable, `http` or `https` URL, max 2048 characters. Must be reachable and serve a JPG, PNG, WEBP, or SVG image of at most 20,000 KB |
| `disable_selling`             | Optional, boolean                                                                                                                               |
| `landing_enabled`             | Optional, boolean                                                                                                                               |
| `storefront_redirect`         | Optional, object                                                                                                                                |
| `storefront_redirect.enabled` | Required when `storefront_redirect` is sent, boolean                                                                                            |
| `storefront_redirect.url`     | Required when `storefront_redirect.enabled` is `true`, nullable, `http` or `https` URL, max 2048 characters                                     |
| `external_auth.key`           | Required when `external_auth` is sent, string, minimum 32 characters                                                                            |
| `external_auth.issuer`        | Required when `external_auth` is sent, string, max 255 characters                                                                               |
| `external_auth.redirect_url`  | Required when `external_auth` is sent, valid URL, max 2048 characters                                                                           |
| `external_auth.login_url`     | Optional, nullable, valid URL, max 2048 characters                                                                                              |
| `external_auth.logout_url`    | Optional, nullable, valid URL, max 2048 characters                                                                                              |
| `external_auth.home_url`      | Optional, nullable, valid URL, max 2048 characters                                                                                              |
| `external_auth.account_url`   | Optional, nullable, valid URL, max 2048 characters                                                                                              |

### Validation Error (422)

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "external_auth.key": [
      "The external authentication key must be at least 256 bits (32 bytes)."
    ]
  }
}
```

---

## See Also

- [List Aggregates](./aggregates-v3-list.md) - Query your aggregates
- [Get Aggregate](./aggregates-v3-get.md) - Retrieve aggregate details
- [Update Aggregate](./aggregates-v3-update.md) - Modify configuration
- [Overview](./aggregates-v3-overview.md) - API overview
