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

# Importing historical points

<Tip>
  This article covers the importing of historical points *before* point calculations (including calculations via API, automations, and purchase/return activity) have been started for your Engage environment. If point calculations are *already active* when you want to do a points import, [follow this guide](/docs/file-based-transfer/importing-points#case-2-when-tenant-is-already-active) instead.
</Tip>

To get your points data into Engage, you will need to migrate it. This migration is done by importing the points history, row by row, from a **CSV** or **XML** file.

If you want to keep track of the entire points history for each of your contacts, this import allows you to do that. You can also choose to just import the current balance for a contact as a single row, without the history. Both negative and positive points values can be migrated in this way.

For a successful import:

* Do not exceed 20,000 rows in your CSV file (or `pointtransaction` nodes in your XML) - split into several files if needed
* Be sure to include all the mandatory fields listed on the table below
* If using a CSV make sure it's UTF-8 encoded and with semicolon separated values
* As the `transactionType` value, use only "purchase", "return", "adjustment" or "rewardvoucher"
* As the `keyType` value, use only "memberNumber", "externalId" or "contactId"
* Only set a value in one of the fields “expireAfterMonthsInactive“ and “validTo“
* If the point transaction is already expired, set the date value of the expiration in the field `validTo`, but DO NOT set `expireAfterMonthsInactive`
* If the settings for expiration are correctly set up AND the value of `validTo` or `expireAfterMonthsInactive` matches that expiration setting, these fields can both be set to blank and values will be added based on what is set up in these expiration settings

<Warning>
  Do not mix different keyType values in the same file as the importer takes the first one it finds and then uses that keyType for all the subsequent rows in the import file.
</Warning>

<Warning>
  For a points import to work, contacts of type "Member" must have one of following keys: memberNumber, externalId or contactId. If the contact does not have any of these keys, the import will fail and give an error.
</Warning>

<Warning>
  When importing a CSV, single and double quote -- the characters " and ' -- are so-called reserved characters and will be interpreted as the start and end of a string field. They can't be used for anything else, or the import will fail. For example " is sometimes used as the symbol for "inch" in product names. Having this in just one field in your CSV could break the import.
</Warning>

## If a contact doesn't exist

When importing points transactions from a file, it can happen that the contact connected to the transaction might not exist in the database. If this happens, and `keyType` in your import has one of the values "memberNumber" or "externalId", then a new contact will be created.

However, if this happens when `keyType` has the value "contactId", then a new contact will *not* be created. Engage is not able to create a new contact from a specified `contactId`. So, in this case, the whole file will be skipped and not imported.

Any contact created in this way will be approved and their `firstname` and `lastname` will be “PointTransactionImport”.

## Structure of the import file

This is what each row in your CSV or each `pointtransaction` node in your XML should contain.

| Name                      | Description                                                                                                                                                                                 |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| \*key                     | The contact identification string (this can’t be empty).                                                                                                                                    |
| \*keyType                 | What `key` represents, which can be either "memberNumber", "externalId", or "contactId".                                                                                                    |
| \*transactionType         | This must be either "purchase", "return", "adjustment" or "rewardvoucher".                                                                                                                  |
| \*transactionDate         | The timestamp of the points transaction. Replaces the older "createdOn". Example: 2019-06-13T12:00:25+1:00                                                                                  |
| \*pointDefinitionId       | A positive value in the PointDefinition database table. The default (and currently only) value is 1. A value that doesn't exist in the PointDefinition table will cause a validation error. |
| \*amount                  | The number of points added (positive) or removed (negative).                                                                                                                                |
| description               | Optional string                                                                                                                                                                             |
| validTo                   | Optional string                                                                                                                                                                             |
| createdBy                 | Optional string                                                                                                                                                                             |
| source                    | We recommend leaving the source blank.                                                                                                                                                      |
| expireAfterMonthsInactive | Optional integer                                                                                                                                                                            |

Mandatory fields are marked with \*. The other fields are optional but recommended.

<Tip>
  The names in the column "Name" can start with a lower-case or upper-case letter.
</Tip>

<Warning>
  The fields `validto` and `expireAfterMonthsInactive` cannot be added in the same row.
</Warning>

## Example of values

| **key** | **keyType**  | **pointDefinitionId** | **transactionType** | **amount** | **transactionDate**       | **description**      |
| :------ | :----------- | :-------------------- | :------------------ | :--------- | :------------------------ | :------------------- |
| 1567    | memberNumber | 1                     | purchase            | 100        | 2021-08-21T08:16:59+02:00 | Trousers             |
| 1839    | memberNumber | 1                     | purchase            | 100        | 2022-10-17T20:36:26+02:00 | Hat                  |
| 1544    | memberNumber | 1                     | return              | -100       | 2021-12-23T14:05:25+01:00 | Gloves               |
| 1356    | memberNumber | 1                     | purchase            | 100        | 2022-03-02T00:47:20+01:00 | Pauldrons            |
| 1644    | memberNumber | 1                     | rewardvoucher       | -100       | 2021-05-21T11:17:54+02:00 | Converted to voucher |
| 1567    | memberNumber | 1                     | rewardvoucher       | -100       | 2021-11-24T01:04:38+01:00 | Converted to voucher |
| 2135    | memberNumber | 1                     | purchase            | 4210       | 2021-10-24T01:04:38+01:00 | Accumulated points   |

For the example contact with `key` of 2135, all their points are imported as one entry, without history.

<Warning>
  Currently, Engage supports only one point definition, with a value of 1. Support for using several point definitions will be added in the future.
</Warning>

You can download an example CSV and XML file on the example files page:

<Card title="Show me the example files page" href="/docs/file-based-transfer/example-files" icon="https://mintcdn.com/voyado/Ns4bBcK3LNctK_Un/icons/developer-link.png?fit=max&auto=format&n=Ns4bBcK3LNctK_Un&q=85&s=fbd08f956358ab12f664a7158e1a1399" horizontal width="128" height="128" data-path="icons/developer-link.png" />

<Warning>
  Keep your CSV to 20,000 rows or fewer, and your XML to 20,000 `pointtransaction` nodes or fewer.
</Warning>

## Importing the file

Your CSV or XML file / files must now be placed on your FTP inside a folder named **Integration/PointTransactionImport**

The actual processing of your files must be manually triggered by your Voyado Engage team.

The result of the integration, including the validation check, will be shown in the integration log as type **Data migration**.

## File validation

Each file is validated before it is processed. If any error is found during the validation of a file, it will be logged in the integration log and the whole file will be skipped. This is a safety feature since unique IDs are not used for the rows inside the files. Validation will then continue with the next file.

The following validations are applied to all mandatory fields in your files:

* All returns must have a negative value
* All purchases must have a positive value
* The `amount` value must be a number, either integer or decimal
* The `PointDefinitionId` value has to be a positive integer value and must exist in the "PointDefinitionId" database table
* The `transactionDate` value must be in the format`2019-06-13T12:00:25+1:00`
* The `transactionType` value must be either "purchase", "return", "adjustment" or "rewardvoucher"
* The `expireAfterMonthsInactive` must be a positive number higher than 0
* The fields `validto` and `expireAfterMonthsInactive` cannot be added in the same row

## Performing the import

After your files pass validation, the actual import is done like this:

1. They will be imported to blob storage and remain there until all files in the batch are imported
2. The system then reads the files from blob storage and start importing all rows
3. The system then updates `PointAmountLeft` for all contacts so it matches the imported data
