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

Add new content items to your catalog. Supports all content types: PDF, EPUB, audiobooks, physical products, and external links.

---

## Endpoint

```
POST /api/v3/content
```

---

## Request Body

### Required Fields

| Field       | Type   | Description                                                                                     |
| ----------- | ------ | ----------------------------------------------------------------------------------------------- |
| `name`      | string | Content title (max 255 chars)                                                                   |
| `file_type` | string | Content type: `pdf`, `epub`, `audio`, `physical`                                                |
| `lang`      | string | ISO 639-1 language code. See [Supported Content Languages](./content-v3-supported-languages.md) |

### Optional Fields

| Field               | Type    | Description                             |
| ------------------- | ------- | --------------------------------------- |
| `description`       | string  | Full content description (HTML allowed) |
| `published_at`      | string  | Publication date (YYYY-MM-DD only)      |
| `audience`          | string  | Target audience                         |
| `subtitle`          | string  | Content subtitle (max 255 chars)        |
| `publication_place` | string  | Place of publication (max 255 chars)    |
| `edition_year`      | integer | Edition year (4 digits, e.g., `2025`)   |

### Identifiers

| Field                      | Type    | Description                                                                   |
| -------------------------- | ------- | ----------------------------------------------------------------------------- |
| `identifiers`              | array   | Array of identifier objects (max 20)                                          |
| `identifiers[].type`       | string  | Identifier type: `isbn_digital`, `isbn_printed`, `uuid`, `ddc`, `external_id` |
| `identifiers[].value`      | string  | Identifier value (max 255 chars). Validated per type (e.g., ISBN-13 checksum) |
| `identifiers[].is_primary` | boolean | Mark as primary identifier (optional, defaults to `false`)                    |

:::tip Primary Identifier
The primary identifier's value is automatically synced to the `external_id` field for backward compatibility. If no identifier is marked as primary, the first one in the array is auto-promoted. Only one identifier can be marked as primary.

Types `isbn_digital`, `isbn_printed`, `uuid`, and `external_id` require **tenant-level uniqueness** — two content items cannot share the same identifier of these types. The `ddc` type does not require uniqueness.
:::

### File Upload (digital content only)

| Field       | Type   | Description                                                                |
| ----------- | ------ | -------------------------------------------------------------------------- |
| `file`      | string | Storage path to uploaded file (mutually exclusive with `file_url`)         |
| `file_url`  | string | External URL to download file from (mutually exclusive with `file`)        |
| `cover`     | string | Storage path to uploaded cover image (mutually exclusive with `cover_url`) |
| `cover_url` | string | External URL for cover image (mutually exclusive with `cover`)             |

:::tip File Upload Options

- **`file_url` / `cover_url`**: Provide a publicly accessible URL. The platform will download the file automatically.
- **`file` / `cover`**: Provide a storage path from SFTP upload. Contact support to enable SFTP access for your account.
  :::

:::note File Processing
Files are processed asynchronously. The content is created immediately with `conversion_status: awaiting`. Use [Get Content](./content-v3-get.md) to poll for processing completion.
:::

### Multitrack Audio

For audiobooks split into multiple tracks, you can provide the full track list inline at creation time using `files`, as an alternative to creating tracks one by one with the [Audiobook Tracks API](./content-v3-tracks.md). Use `files` instead of `file`/`file_url`; if both are sent, `files` takes precedence and the single-file input is ignored.

| Field                 | Type    | Description                                          |
| --------------------- | ------- | ---------------------------------------------------- |
| `files`               | array   | Track list (only valid when `file_type` is `audio`)  |
| `files[].name`        | string  | Filename of the track (required)                     |
| `files[].url`         | string  | External URL to download the track from (required)   |
| `files[].duration`    | number  | Track duration in seconds (optional, min: 0)         |
| `files[].order`       | integer | Playback order (optional, min: 0)                    |
| `files[].track_title` | string  | Display title of the track (optional, max 255 chars) |

:::note Asynchronous Track Processing
Each track is downloaded from its `url` and persisted by a background batch. The response returns `conversion_status: awaiting`. Track duration is recalculated server-side from the downloaded audio (any value sent in `files[].duration` is overwritten). Poll [Get Content](./content-v3-get.md) until `conversion_status` becomes `done` to know when all tracks are ready.
:::

### Pricing

| Field    | Type  | Description                            |
| -------- | ----- | -------------------------------------- |
| `prices` | array | Price array: `[{currency_id, amount}]` |

:::tip Scheduled Pricing
Prices support `starts_at` and `ends_at` fields for scheduled pricing: `[{currency_id, amount, starts_at, ends_at}]`. Dates should be in `YYYY-MM-DD` format.
:::

### Access Settings

| Field                   | Type    | Description                         |
| ----------------------- | ------- | ----------------------------------- |
| `free`                  | boolean | Enable free access                  |
| `free_until`            | string  | Free access expiration (YYYY-MM-DD) |
| `require_login`         | boolean | Require login for free access       |
| `preview`               | boolean | Enable content preview              |
| `preview_require_login` | boolean | Require login for preview           |

### DRM Print Quota

Set a print quota to enable controlled printing from the reader. Printing stays disabled unless both fields are set to a positive value; a `0` or `null` value in either field keeps printing disabled.

| Field                           | Type          | Description                                                                                                                                                       |
| ------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `drm_print_limit`               | integer\|null | Maximum number of pages a user can print in a single print job (0 to 100)                                                                                         |
| `drm_print_lifetime_percentage` | integer\|null | Maximum percentage of the book's pages a user can print in total, across all print jobs (0 to 100). The page allowance derived from this percentage is rounded up |

:::note Supported Formats
The print quota applies to the paginated formats the print flow supports: `pdf` and `epub`. For any other content type the fields pass validation but no quota is stored. These fields are write-only: they are not included in content API responses.
:::

For an EPUB, the quota on its own is not enough. Printing targets the page markers derived while the file is processed, so a book without them reports no printable pages and printing stays disabled until it is reprocessed with pagination. A PDF is printable as soon as processing finishes.

### Subscribers-Only Access

| Field              | Type    | Description                                                      |
| ------------------ | ------- | ---------------------------------------------------------------- |
| `subscribers_only` | boolean | Restrict purchase to users with an active plan. Default: `false` |

Content flagged as `subscribers_only` cannot coexist with the `retail` marketplace license. If the content is already listed in the marketplace with a `retail` license, enabling `subscribers_only` automatically removes the `retail` license to keep the configuration consistent. Other licenses (e.g., `ppu`) are preserved.

### Marketplace

| Field                 | Type    | Description                                                                                                                                                                          |
| --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `show_in_marketplace` | boolean | Make content available in the Publica.la marketplace. When `true`, the content is listed for other tenants to discover and add to their stores. Defaults to `false` if not provided. |

### Metadata

| Field                    | Type    | Description                                                                                                                                                             |
| ------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `author`                 | array   | Author names: `[{first_name, last_name?, title?}]`. See [Author names](#author-names) below                                                                             |
| `publisher`              | array   | Publisher names (max 200 chars each)                                                                                                                                    |
| `keywords`               | array   | Keywords/tags (max 200 chars each)                                                                                                                                      |
| `bisac`                  | array   | BISAC codes: `[{code: "FIC000000"}]` (max 4 codes)                                                                                                                      |
| `thema`                  | array   | Thema subject codes (strings, max 200 chars each)                                                                                                                       |
| `category`               | array   | Category names (max 200 chars each)                                                                                                                                     |
| `collection`             | array   | Collection names (max 200 chars each)                                                                                                                                   |
| `series`                 | array   | Series names (max 200 chars each)                                                                                                                                       |
| `country`                | array   | Country names (max 200 chars each)                                                                                                                                      |
| `edition`                | array   | Edition names (max 200 chars each)                                                                                                                                      |
| `narrator`               | array   | Narrator names for audiobooks (max 200 chars each)                                                                                                                      |
| `publishing_group`       | array   | Publishing group names (max 200 chars each)                                                                                                                             |
| `accessibility_features` | array   | Accessibility features: `[{code: "04", description?: "..."}]` (code max 3 chars, description max 5000 chars)                                                            |
| `custom_metadata`        | object  | Custom taxonomy groups: `{"group_slug": ["value1", "value2"]}`                                                                                                          |
| `share_with_tenants_ids` | array   | Tenant IDs (strings) to share this content with. See [Content Sharing](./content-v3-sharing.md)                                                                         |
| `format_group_ids`       | array   | Format group IDs (strings) to associate this content with                                                                                                               |
| `pages`                  | integer | Number of pages (min: 0). Applies to any `file_type`. For PDF and audio content the value is derived from the file during processing; input is ignored for those types. |

#### Author names

`author` accepts structured names, so the first name, last name, and title are stored separately:

```json
"author": [
  { "first_name": "Jane", "last_name": "Smith" },
  { "title": "Dr.", "first_name": "Ana", "last_name": "Reyes" }
]
```

- `first_name` is required. `last_name` and `title` are optional.
- Each part is a string, max 200 characters.
- Other metadata arrays (`publisher`, `narrator`, `keywords`, and so on) stay plain strings.
- Responses return each author as the composed full name (for example, `"Dr. Ana Reyes"`).

### Geographic Restrictions

| Field                     | Type   | Description                          |
| ------------------------- | ------ | ------------------------------------ |
| `geographic_restrictions` | object | `{included: [...], excluded: [...]}` |

### Physical Product Fields

| Field              | Type    | Description                                                         |
| ------------------ | ------- | ------------------------------------------------------------------- |
| `binding_type`     | string  | Binding type (hardcover, paperback, etc.)                           |
| `height`           | float   | Height in cm (min: 0)                                               |
| `width`            | float   | Width in cm (min: 0)                                                |
| `weight`           | float   | Weight in grams (min: 0)                                            |
| `stock_quantity`   | integer | Units available for sale (min: 0). `0` creates the product sold out |
| `editing_location` | string  | Editing/printing location                                           |
| `thickness`        | float   | Thickness in cm (min: 0)                                            |

Omitting `stock_quantity` leaves the product untracked, which is not the same as sold out: it stays on sale and no quantity is enforced. Only a tracked product that reaches `0` is blocked from being bought.

---

## Request Examples

### Basic PDF Content

```bash
curl -X POST "https://yourstore.publica.la/api/v3/content" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Introduction to Machine Learning",
    "file_type": "pdf",
    "lang": "en",
    "file_url": "https://example.com/files/ml-intro.pdf",
    "identifiers": [
      { "type": "isbn_digital", "value": "978-1-234-56789-0", "is_primary": true }
    ]
  }'
```

### EPUB with Full Metadata

```bash
curl -X POST "https://yourstore.publica.la/api/v3/content" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "The Complete Guide to Digital Publishing",
    "file_type": "epub",
    "lang": "en",
    "description": "<p>A comprehensive guide to digital publishing...</p>",
    "published_at": "2025-01-15",
    "file_url": "https://example.com/files/publishing-guide.epub",
    "cover_url": "https://example.com/covers/publishing-guide.jpg",
    "prices": [
      { "currency_id": "USD", "amount": 19.99 },
      { "currency_id": "EUR", "amount": 17.99 }
    ],
    "author": [
      { "first_name": "Jane", "last_name": "Smith" },
      { "first_name": "John", "last_name": "Doe" }
    ],
    "publisher": ["Tech Publishing House"],
    "keywords": ["publishing", "digital", "ebooks"],
    "bisac": [
      { "code": "COM000000" }
    ],
    "identifiers": [
      { "type": "isbn_digital", "value": "978-0-987-65432-1", "is_primary": true }
    ]
  }'
```

### Content with Multiple Identifiers

```bash
curl -X POST "https://yourstore.publica.la/api/v3/content" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Advanced Data Structures",
    "file_type": "epub",
    "lang": "en",
    "file_url": "https://example.com/files/data-structures.epub",
    "identifiers": [
      { "type": "isbn_digital", "value": "978-0-306-40615-7", "is_primary": true },
      { "type": "ddc", "value": "005.73" }
    ]
  }'
```

### Free Content with Expiration

```bash
curl -X POST "https://yourstore.publica.la/api/v3/content" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sample Chapter - Preview Edition",
    "file_type": "pdf",
    "lang": "en",
    "file_url": "https://example.com/samples/chapter1.pdf",
    "free": true,
    "free_until": "2025-06-30",
    "require_login": true
  }'
```

### Content with a Print Quota

```bash
curl -X POST "https://yourstore.publica.la/api/v3/content" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Printable Course Handbook",
    "file_type": "pdf",
    "lang": "en",
    "file_url": "https://example.com/files/handbook.pdf",
    "drm_print_limit": 10,
    "drm_print_lifetime_percentage": 40
  }'
```

This allows each user to print up to 10 pages per print job, and up to 40% of the book's pages in total.

### Audiobook

```bash
curl -X POST "https://yourstore.publica.la/api/v3/content" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mystery Novel - Narrated Edition",
    "file_type": "audio",
    "lang": "en",
    "description": "Award-winning mystery novel, professionally narrated",
    "prices": [
      { "currency_id": "USD", "amount": 14.99 }
    ],
    "author": [{ "first_name": "Mystery", "last_name": "Author" }],
    "publisher": ["Audiobook Productions"]
  }'
```

:::tip Audiobook Tracks
After creating an audiobook, use the [Audiobook Tracks API](./content-v3-tracks.md) to add individual audio tracks.
:::

### Physical Product

```bash
curl -X POST "https://yourstore.publica.la/api/v3/content" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Collector Edition Hardcover",
    "file_type": "physical",
    "lang": "en",
    "cover_url": "https://example.com/covers/collector.jpg",
    "prices": [
      { "currency_id": "USD", "amount": 49.99 }
    ],
    "binding_type": "hardcover",
    "pages": 450,
    "height": 24.5,
    "width": 16.0,
    "weight": 650,
    "stock_quantity": 120
  }'
```

### Content with Geographic Restrictions

```bash
curl -X POST "https://yourstore.publica.la/api/v3/content" \
  -H "X-User-Token: your-api-token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Regional Edition",
    "file_type": "epub",
    "lang": "es",
    "file_url": "https://example.com/files/regional.epub",
    "prices": [
      { "currency_id": "USD", "amount": 9.99 }
    ],
    "geographic_restrictions": {
      "included": ["AR", "CL", "UY", "MX"],
      "excluded": []
    }
  }'
```

---

## Response

### Success Response (201 Created)

```json
{
  "data": {
    "id": "468170",
    "external_id": "978-1-234-56789-0",
    "name": "Introduction to Machine Learning",
    "slug": "introduction-to-machine-learning",
    "lang": "en",
    "file_type": "pdf",
    "audience": null,
    "subtitle": null,
    "publication_place": null,
    "edition_year": null,
    "cover_url": null,
    "reader_url": "https://yourstore.publica.la/reader/introduction-to-machine-learning",
    "product_url": "https://yourstore.publica.la/library/publication/introduction-to-machine-learning",
    "created_at": "2025-12-23T10:30:00.000000Z",
    "updated_at": "2025-12-23T10:30:00.000000Z",
    "published_at": "2025-12-23T00:00:00.000000Z",
    "license": "retail",
    "free": {
      "enabled": false,
      "until": null,
      "require_login": false
    },
    "preview": {
      "enabled": false,
      "require_login": false
    },
    "subscribers_only": false,
    "conversion_status": "awaiting",
    "prices": [],
    "description": null,
    "publisher": [],
    "author": [],
    "bisac": [],
    "keywords": [],
    "country": [],
    "edition": [],
    "narrator": [],
    "publishing_group": [],
    "thema": [],
    "series": [],
    "category": [],
    "collection": [],
    "metrics": {
      "total_pages": 0,
      "total_words": 0,
      "total_seconds": 0
    },
    "custom_metadata": {},
    "geographic_restrictions": null,
    "identifiers": [
      {
        "type": "isbn_digital",
        "value": "978-1-234-56789-0",
        "is_primary": true
      }
    ]
  }
}
```

:::info Always Included
The create response always includes `prices`, `description`, `metadata`, `geographic_restrictions`, and `identifiers` data regardless of the `include` parameter.
:::

---

## Conversion Status

Digital content undergoes asynchronous processing. Track the status via the `conversion_status` field returned in the content response.

| Status       | Description                                                     |
| ------------ | --------------------------------------------------------------- |
| `awaiting_*` | Content is queued or waiting for processing to begin            |
| `started_*`  | Processing in progress (varies by file type)                    |
| `done`       | Processing complete, content available                          |
| `deferred`   | Awaiting first user access to trigger processing (bulk imports) |
| `error_*`    | Processing failed (varies by file type)                         |

:::warning Intermediate statuses may change
Do not couple your logic to specific intermediate status values (`awaiting_*`, `started_*`, `error_*`) as they may change without notice. If you need to check whether content is ready, use `done` as the only stable status to rely on.
:::

### Polling for Completion

```bash
curl -X GET "https://yourstore.publica.la/api/v3/content/468170" \
  -H "X-User-Token: your-api-token"
```

---

## Error Handling

### Validation Errors (422)

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "name": ["The name field is required."],
    "file_type": ["The selected file type is invalid."]
  }
}
```

### Duplicate Identifier (422)

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "identifiers.0.value": [
      "This isbn_digital identifier is already in use as primary for another content."
    ]
  }
}
```

### File Processing Error (422)

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "file_url": ["Unable to download file from the provided URL."]
  }
}
```

### Authentication Error (401)

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

---

## Common Validation Rules

| Field                              | Rules                                                                                           |
| ---------------------------------- | ----------------------------------------------------------------------------------------------- |
| `name`                             | Required, max 255 chars                                                                         |
| `file_type`                        | Required, one of: `pdf`, `epub`, `audio`, `physical`                                            |
| `lang`                             | Required, valid language code                                                                   |
| `subtitle`                         | Optional, max 255 chars                                                                         |
| `publication_place`                | Optional, max 255 chars                                                                         |
| `edition_year`                     | Optional, 4 digits                                                                              |
| `drm_print_limit`                  | Optional, integer, between 0 and 100                                                            |
| `drm_print_lifetime_percentage`    | Optional, integer, between 0 and 100                                                            |
| `description`                      | Optional, max 20,000 chars, HTML allowed                                                        |
| `prices[].currency_id`             | Valid ISO 4217 currency code                                                                    |
| `prices[].amount`                  | Number, min: 0 (zero allowed for free pricing)                                                  |
| `file_url`                         | Valid URL, accessible for download                                                              |
| `published_at`                     | Valid date (YYYY-MM-DD only)                                                                    |
| `bisac[].code`                     | Valid BISAC code                                                                                |
| `geographic_restrictions.included` | Array of ISO 3166-1 alpha-2 country codes                                                       |
| `geographic_restrictions.excluded` | Array of ISO 3166-1 alpha-2 country codes                                                       |
| `identifiers`                      | Optional, array, max 20 items                                                                   |
| `identifiers[].type`               | Required, one of: `isbn_digital`, `isbn_printed`, `uuid`, `ddc`, `external_id`                  |
| `identifiers[].value`              | Required, max 255 chars. Validated per type (ISBN-10/ISBN-13 checksum, UUID format, DDC format) |
| `identifiers[].is_primary`         | Optional, boolean                                                                               |
| `subscribers_only`                 | Optional, boolean                                                                               |

---

## Best Practices

1. **Use identifiers** - Always set identifiers (ISBN, UUID, etc.) for cross-system synchronization
2. **Poll for completion** - Check `conversion_status` before assuming content is available
3. **Include complete metadata** - Better discoverability with author, publisher, and BISAC codes
4. **Set prices upfront** - Configure pricing at creation to avoid separate update calls

---

## See Also

- [Bulk Create](./content-v3-bulk.md) - Create multiple items at once
- [Update Content](./content-v3-update.md) - Modify existing content
- [Audiobook Tracks](./content-v3-tracks.md) - Add tracks to audiobooks
- [Overview](./content-v3-overview.md) - API overview

---
