Spreadsheet Formats
The Content Import system supports two spreadsheet formats: Publica.la and VitalSource. Accepted file types are XLSX, XLS, and CSV (max 10 MB).
Format Detection
Format resolution depends on how the user uploads the spreadsheet:
- Drag & drop:
SpreadsheetParsernormalizes all headers to lowercase and compares them against the expected headers for each format. A format is selected when 60% or more of its expected headers are present. If no format meets the threshold, a modal prompts the user to select the format manually, and headers are re-validated against the chosen format. - Browse file: The user selects the format from a modal before the file picker opens. Auto-detection is skipped and headers are validated directly against the pre-selected format.
Publica.la Format
Required Headers
| Spreadsheet Header | API Field | Notes |
|---|---|---|
| ISBN | external_id | External identifier |
| Type | file_type | Normalized: epub, pdf, audio, physical |
| Name | name | Required — product title |
| File URL | file_url | External URL or filename matching an uploaded file |
| Lang | lang | Language code (default: es) |
| Prices | prices | Multi-currency: ARS:1000|USD:20|CLP:23000 |
| Authors | author | Pipe, semicolon, or comma-separated list |
| Publishers | publisher | Pipe, semicolon, or comma-separated list |
Optional Headers
| Spreadsheet Header | API Field | Notes |
|---|---|---|
| Description | description | |
| Publication Date | published_at | |
| Keywords | keywords | Parsed as list |
| Narrators | narrator | Parsed as list |
| Categories | category | Parsed as list |
| Audience | audience | |
| Free | free | Boolean (true/false, yes/no, 1/0) |
| Retail Enabled | show_in_marketplace | Boolean |
| Countries | country | Parsed as list |
| Collections | collection | Parsed as list |
| Editions | edition | Parsed as list |
taxonomy_* | custom_metadata | Dynamic — prefix stripped to create key (see below) |
Optional headers are not used for format detection. Null values are filtered out before sending to the API.
Taxonomy Columns
Any column with the taxonomy_ prefix is mapped into the custom_metadata object. The prefix is stripped to produce the key, and values are parsed as lists.
Example: a column taxonomy_genre with value Fiction|Drama produces:
{ "custom_metadata": { "genre": ["Fiction", "Drama"] } }
List Parsing
Values in list fields (Authors, Publishers, Keywords, Narrators, Categories, Countries, Collections, Editions, taxonomy_*) are split on |, ;, or , and each value is trimmed.
Price Format
Prices use a pipe-separated multi-currency format:
ARS:1000|USD:20|CLP:23000
Each pair is parsed into { currency_id, amount } for the API payload.
VitalSource Format
Required Headers
| Spreadsheet Header | API Field | Notes |
|---|---|---|
| VBK ID | external_id | Fallback if ISBN is empty |
| ISBN | external_id | Preferred over VBK ID |
| Title | name | Required — product title |
| Author | author | Parsed as list |
| Publisher | publisher | Parsed as list |
| Format | file_type | Normalized via FileTypeNormalizer |
| Price | prices | Single price value (paired with Currency column) |
| Availability Date | published_at | Publication date |
| DRM Type | (recognized) | Recognized header, not mapped to API |
| Duration | (recognized) | Recognized header, not mapped to API |
Default language for VitalSource is en.
API Payload Example
The ImportContentData DTO's toArray() method produces the payload sent to Farfalla. Null values are filtered out.
{
"name": "Introduction to Digital Publishing",
"file_type": "pdf",
"lang": "es",
"external_id": "978-3-16-148410-0",
"published_at": "2026-01-15",
"prices": [
{ "currency_id": "USD", "amount": "29.99" },
{ "currency_id": "ARS", "amount": "15000" }
],
"author": ["Jane Smith", "John Doe"],
"publisher": ["Acme Publishing"],
"file_url": "https://s3.amazonaws.com/...",
"description": "A comprehensive guide...",
"keywords": ["publishing", "digital"],
"category": ["Technology"],
"free": false,
"show_in_marketplace": true,
"country": ["US", "GB"],
"collection": ["Spring 2026"],
"edition": ["2nd Edition"],
"custom_metadata": {
"genre": ["Non-Fiction", "Technology"],
"level": ["Intermediate"]
}
}
Each record in the batch is sent as an element of the items array in the POST body:
{
"items": [
{ "name": "...", "file_type": "...", "...": "..." },
{ "name": "...", "file_type": "...", "...": "..." }
]
}