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

# Import and export

> Create many supplies at once from a CSV file, follow the import, fix rejected rows, and export your catalog.

A CSV import creates new supplies in bulk. It never updates or deletes existing supplies. A CSV export downloads the whole catalog as a file, with costs only for people allowed to see them.

## Who can do it

| Action | Who |
| - | - |
| Import a CSV file | Members with the **Owner** or **Administrator** role. The server checks the role itself: a Member who was granted `catalog.manage` sees **Import CSV** but the upload is refused. Each row is also checked, while the import runs, against the uploader's `catalog.manage`. |
| Open an import's status page | Members of the same dental clinic who have its link. |
| Export the catalog | Members with the **Owner** or **Administrator** role. The **Export CSV** button is shown to members with `catalog.manage`; a Member with that permission is refused in the same way. |
| Get the cost columns in an export | Exporters who also hold `catalog.cost.read`. |

Import and export are marked as sensitive actions. MFA is optional on the current release and does not block them; if the console ever shows **Verify your identity to continue.**, confirm with your authenticator app and try again. See [Account security](/docs/en/account/security) and [Roles and permissions](/docs/en/account/roles-and-permissions).

## Where

| Screen | Path | How to get there |
| - | - | - |
| **Import CSV** | `/catalog/imports/new` | **Catalog**, then **Import CSV**. |
| **Import status** | `/catalog/imports/:importId` | Opens by itself after an upload. |
| Export | `/catalog` | **Catalog**, then **Export CSV**. |

<Note>
  There is no list of past imports. Keep the address of the **Import status** page if you want to check it later.
</Note>

## Prepare the file

The **Import CSV** screen (**Add supplies from a CSV file. Existing supplies are not updated.**) includes a guide, **Preparing your file**, with:

* **Required columns** and **Optional columns**.
* **Allowed units** and **Allowed criticalities**, with their codes.
* **Download column template**: a file named `muveya-catalog-template.csv` with the header row only.
* **Category IDs for your CSV**: each **Category name** next to its `categoryId`, with **Copy category ID**.

### File format

* Plain CSV with a comma as separator. Fields that contain commas, double quotes or line breaks go between double quotes, and a double quote inside them is written twice (`""`).
* Encoded as UTF-8, with or without a byte order mark. In a spreadsheet, save as "CSV UTF-8".
* The first record is the header. Column names are the English names below, written exactly (letter case counts), in any order.
* Columns muveya does not know are ignored. If a column appears twice, the first one is used, except `unitOfMeasure`, `cost`, `currency` and the cost metadata columns: a duplicate of any of them rejects the whole file.
* Empty lines are skipped. Spaces around each value are removed.

<Warning>
  Spreadsheets set to Spanish or Portuguese often save CSV files with semicolons. muveya reads such a file as a single column, and the import fails with **The file does not contain the required CSV columns.** Save with commas.
</Warning>

### Columns

| Column | Required | Format |
| - | - | - |
| `sku` | Yes | 1 to 64 characters: letters, digits, `.`, `_`, `/`, `-`. Must not exist yet in your dental clinic. |
| `name` | Yes | Up to 200 characters. |
| `categoryId` | Yes | The id of a category of your dental clinic, copied from the guide. |
| `unitOfMeasure` | Yes | `unit`, `box`, `pack`, `bottle`, `ampoule`, `milliliter`, `liter`, `gram`, `kilogram`, `pair` or `kit`. |
| `criticality` | Yes | `low`, `medium` or `high`. |
| `description` | No | Up to 2,000 characters. |
| `packaging` | No | Up to 200 characters. It is the free-text **Packaging** field. |
| `tracksLot`, `tracksSerial`, `tracksExpiry`, `highValue` | No | `true` or `false`. muveya also accepts `1`/`0` and `yes`/`no`, in any letter case. Empty means `false`. |
| `cost` | No | A whole number of minor units, for example `1200` for USD 12.00 or CLP 1200. Digits only. Requires `currency`. |
| `currency` | No | Three uppercase letters, for example `USD`. Requires `cost`. |
| `status` | Ignored | Every imported supply is created as `draft`. |
| `costStatus`, `costUnitOfMeasure`, `costMeasurementVersion`, `costQuantityProtocol` | No | Cost metadata written by an export. If any of them is present, all six cost columns (`cost`, `currency` and these four) must be present. See [Re-importing an export](#re-importing-an-export). |

Example:

```csv theme={null}
sku,name,categoryId,unitOfMeasure,criticality,description,packaging,tracksLot,tracksSerial,tracksExpiry,highValue,cost,currency
GLV-NIT-M,Nitrile gloves size M,665f1a2b3c4d5e6f7a8b9c01,unit,high,"Powder-free, blue",Box of 100,true,false,true,false,12,USD
ANS-LID-2,Lidocaine 2% cartridge,665f1a2b3c4d5e6f7a8b9c02,ampoule,high,,Box of 50,true,false,true,true,,
```

The second row has no cost: both `cost` and `currency` are empty.

### Limits

| Limit | Value | When exceeded |
| - | - | - |
| File size | 5 MB (5,242,880 bytes), not empty | The upload is refused: **The file must be 5 MB or smaller.** |
| Data rows | 10,000 per file, header excluded | The whole import fails: **Use no more than 10,000 data rows per file.** |
| Files per import | 1 | Split larger catalogs into several files. |

## Import supplies

<Steps>
  <Step title="Prepare the categories">
    Create any missing categories first and copy their ids from **Category IDs for your CSV**. See [Categories](/docs/en/catalog/categories).
  </Step>

  <Step title="Choose the file">
    In **Import CSV**, click **CSV file** and choose your `.csv` file.
  </Step>

  <Step title="Upload">
    Click **Import supplies**. When the file is accepted, the console opens **Import status**.
  </Step>

  <Step title="Follow the progress">
    The page refreshes by itself while the import is received or running. **Refresh status** checks again at any time.
  </Step>

  <Step title="Review the result">
    Read the counts and the rejected rows. Fix those rows in a new file and click **Import another file**.
  </Step>

  <Step title="Activate the new supplies">
    Imported supplies are drafts. Open each one and activate it when it is ready to be ordered. There is no bulk activation. See [Catalog items](/docs/en/catalog/items).
  </Step>
</Steps>

## Import statuses

| Status | Heading on the page | Meaning |
| - | - | - |
| `pending` | **Import received** | The file is stored and waiting to be processed. |
| `processing` | **Importing supplies** | Rows are being created, one by one, in file order. |
| `completed` | **Import completed** | Every row was attempted. Some may have been rejected. |
| `failed` | **Import could not be completed** | The file could not be processed as a whole. A message explains why. |

```mermaid theme={null}
stateDiagram-v2
  [*] --> pending: File accepted
  pending --> processing: Processing starts
  processing --> completed: All rows attempted
  pending --> failed: Could not start
  processing --> failed: File rejected or interrupted
  failed --> processing: Automatic retry after an interruption
```

Once finished, the page shows **Data rows**, **Created** and **Rejected**. If any row was rejected, it adds **Some rows were rejected. Review their reasons before uploading a corrected file.** and a table with **Data row** and **Reason**.

Each row is independent: a rejected row never stops the others, and rows created before a rejection stay created.

### Row numbers

**Rows count data records, excluding the header; a CSV record can span multiple text lines.** Data row 1 is the first record after the header. Empty lines are not counted, and a quoted value with line breaks counts as one row, so a data row number can differ from the line number your editor shows.

## File-level failures

| Message | Cause | What to do |
| - | - | - |
| **The file does not contain the required CSV columns.** | A required column is missing or misspelled, the separator is not a comma, a quote is left open, a cost or unit column is duplicated, or only some of the six cost columns are present. | Fix the header or the quoting and upload again. |
| **Use no more than 10,000 data rows per file.** | Too many rows. | Split the file. |
| **The uploaded file is no longer available. Upload it again.** | The stored file could not be read back. | Upload the file again. |
| **Processing was interrupted. Automatic recovery will retry the pending work.** | A temporary failure stopped the import. | Wait. The page keeps checking, and rows already created are not created twice. |
| **This interrupted import predates progress tracking. Review the existing catalog before uploading a corrected file.** | An old import was interrupted before muveya recorded progress per row. | Search the catalog for the file's SKUs and upload only the missing ones. |

## Rejected rows

| Reason shown | Code | Common causes | Fix |
| - | - | - | - |
| **A required field is missing.** | `catalog.import_row_incomplete` | `sku`, `name`, `categoryId`, `unitOfMeasure` or `criticality` is empty. | Fill in the cell. |
| **A required field is missing.** | `catalog.cost_incomplete` | `cost` without `currency`, or `currency` without `cost`. | Give both or neither. |
| **Review the fields in this row.** | `catalog.import_row_invalid` | Unknown unit or criticality, a tracking value that is not true or false, a `cost` that is not a number, inconsistent cost metadata. | Use the allowed values. |
| **Review the fields in this row.** | `common.invalid_request` | SKU with spaces or other characters, a text too long, a malformed `categoryId`, a `cost` with decimals or below zero, a `currency` that is not three uppercase letters. | Check the column formats above. |
| **This SKU already exists.** | `catalog.sku_taken` | A supply with that SKU exists, including one created by an earlier row of the same file. | Remove the row, or use another SKU. |
| **Category not found in this clinic.** | `catalog.category_not_found` | The `categoryId` is well formed but is not a category of this dental clinic. | Copy the id from **Category IDs for your CSV**. |
| **The recorded cost needs review. Confirm its unit and amount before importing this row.** | `catalog.cost_unverified` | The row comes from an export and its `costStatus` is `review_required` or `invalid`. | Set a confirmed cost, or clear the cost columns. |
| **The original uploader no longer has permission.** | `tenants.insufficient_role` | The person who uploaded the file lost `catalog.manage` while the import ran. | Ask someone with the right role to upload the remaining rows. |
| **This row could not be imported.** | Any other | An unexpected refusal. | Check the row; if it keeps failing, write to [team@muveya.com](mailto:team@muveya.com). |

To fix rejected rows, build a new file with *only* those rows, corrected. Leave out the rows that were created: they would now be rejected with **This SKU already exists.**

## Imports never update supplies

* An import only creates supplies. A row whose SKU already exists is rejected, whatever its other values. To change existing supplies, edit them in the console.
* Inside one file, the first row with a given SKU is created and later rows with the same SKU are rejected.
* Uploading the same file twice creates nothing the second time: every row is rejected as an existing SKU.
* If processing is interrupted and resumed, muveya remembers which rows it already created and does not create them again.
* A cost in a row becomes the supply's current cost, confirmed for the imported unit. See [Costs](/docs/en/catalog/costs).

## Export the catalog

<Steps>
  <Step title="Start the export">
    In **Catalog**, click **Export CSV**. The file is generated right away.
  </Step>

  <Step title="Download">
    Click **Download CSV**. Below the link, **Link expires:** shows the date and time.
  </Step>
</Steps>

The link is valid for 5 minutes. After that the console shows **This link expired. Generate a new export.**; click **Export CSV** again. If the console cannot verify the link, it shows **The download link could not be verified. Generate a new export.**

### Export contents

The file is named `catalog.csv` and contains every supply of the dental clinic, in every status, oldest first.

| Columns | Included |
| - | - |
| `sku`, `name`, `description`, `categoryId`, `unitOfMeasure`, `packaging`, `criticality`, `tracksLot`, `tracksSerial`, `tracksExpiry`, `highValue`, `status` | Always. |
| `cost`, `currency`, `costStatus`, `costUnitOfMeasure`, `costMeasurementVersion`, `costQuantityProtocol` | Only if you hold `catalog.cost.read`. Without it these columns do not exist in the file. |

* Empty optional values are empty cells; tracking columns are `true` or `false`; `cost` and `currency` are empty when the cost is `unset`.
* The file is UTF-8 with a byte order mark, comma separated, with Windows line endings, so spreadsheets open accented names correctly.
* A value that starts with `=`, `+`, `-` or `@` is written with a leading apostrophe (`'`), so a spreadsheet does not run it as a formula.
* Categories appear as `categoryId`, not by name. Presentations, package codes and stock are not exported.

### Re-importing an export

An export has the same column names as the import, so you can use it as a starting point, for example to load the same catalog into another dental clinic. Before uploading:

* Replace each `categoryId` with the ids of the destination dental clinic.
* Remove the apostrophe that the export added before values starting with `=`, `+`, `-` or `@`.
* The `status` column is ignored: every supply is created as a draft.
* If the file has the cost columns, each row's `costStatus` decides what happens:
  * `unset`: all the other cost cells must be empty.
  * `verified`: `costUnitOfMeasure` must equal `unitOfMeasure`, `costMeasurementVersion` must be a whole number from 1, `costQuantityProtocol` must be `base_number_v1`, `cost` a whole number and `currency` three uppercase letters. Otherwise the row is rejected with **Review the fields in this row.**
  * `review_required` or `invalid`: the row is rejected with **The recorded cost needs review. Confirm its unit and amount before importing this row.**
  * Empty or any other value: the row is rejected with **Review the fields in this row.**
* Re-importing into the same dental clinic creates nothing, because every SKU already exists.

## What the system records

| Event | Audit entry |
| - | - |
| An accepted upload | `catalog.import`, with the file name, its size and who uploaded it; the entry is settled when the import finishes or fails. |
| A refused upload | `catalog.import.denied`, with the file name. |
| An export | `catalog.export`, with the number of supplies and whether costs were included. Never the cost values. |
| A refused export | `catalog.export.denied`. |

The import also keeps, for each data row, whether it was created and the reason when it was not. It never stores cost values in that summary. The current release has no screen to browse the audit log.

## What can go wrong when uploading or exporting

| Message | Code | What to do |
| - | - | - |
| **Choose a CSV file.** | `catalog.import_file_missing`, `catalog.import_empty` | Choose a file that is not empty. |
| **The file must be 5 MB or smaller.** | `catalog.import_too_large` | Split the file. |
| **Your account does not have permission for this action.** | `tenants.insufficient_role` | Ask an Owner or Administrator to import or export. |
| **This record is not available in the active dental clinic account.** | `catalog.import_not_found` | The import belongs to another dental clinic, or the link is wrong. |
| **Verify your identity to continue.** | `auth.mfa_required` | Verify with your authenticator app and try again. |

The public API does not import or export the catalog. To read it from another system, use `GET /v1/catalog/items`. See the [API reference](/docs/en/api-reference/introduction).

## Related pages

<CardGroup cols={2}>
  <Card title="Catalog items" icon="box" href="/docs/en/catalog/items">
    Field rules and activation.
  </Card>

  <Card title="Categories" icon="tags" href="/docs/en/catalog/categories">
    Create categories and find their ids.
  </Card>

  <Card title="Costs" icon="coins" href="/docs/en/catalog/costs">
    Minor units and cost states.
  </Card>

  <Card title="Roles and permissions" icon="user-shield" href="/docs/en/account/roles-and-permissions">
    Roles that can import and export.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.