> For the complete documentation index, see [llms.txt](https://help.solink.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.solink.com/docs/install-connect-and-integrate/integrations/csv-formatting-guides/csv-format-for-sftp-data-integrations.md).

# CSV Format for SFTP Data Integrations

This guide defines the **file delivery and CSV formatting requirements** for sending operational data to Solink via SFTP and applies to all supported schemas:

* Retail
* Restaurant
* Banking

This guide is intended for customers and partners building an integration.

***

## Data Ingestion Overview

<img src="https://1562067156-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FlMN1EUQcXYfv7FYpGJWR%2Fuploads%2Fgit-blob-7b0f6e1669be38793e3aa9f4a26bb7dcfcd7b5c5%2F9ae0113364490ec8.png?alt=media" alt="" width="640">

*Figure 1: SFTP CSV data ingestion to Solink VD (high-level)*

***

## Scope and Supported Schemas

Solink ingests CSV files delivered over SFTP and processes them according to the schema indicated in the file content (typically via a dataType column).

Schema-specific field definitions are documented separately in the companion reference: “CSV Schemas for SFTP Data Integrations”.

### Retail

* **Files Expected**: 3 files per day; header, item, payment
* **Use Case**: Event-level retail transactional data paired with video
* [Retail CSV documentation](/docs/install-connect-and-integrate/integrations/csv-formatting-guides/retail-format-guidelines.md)

### Restaurant

* **Files Expected**: 3 files per day; header, item, payment
* **Use Case**: Event-level restaurant transactional data paired with video
* [Restaurant CSV documentation](/docs/install-connect-and-integrate/integrations/csv-formatting-guides/restaurant-format-guidelines.md)

### Banking

* **Files Expected**: 1 file per day
* **Use Case**: Event-level ATM / Teller transactional data paired with video
* [Banking CSV documentation](/docs/install-connect-and-integrate/integrations/csv-formatting-guides/banking-format-guidelines.md)

***

## Quick Start Checklist

* Generate CSV files in UTF-8 encoding with a header row.
* Include only one business day per file (or per file group, depending on schema).
* Prefix filenames with the date for automated discovery (YYYYMMDD or YYYYMMDDTHHmm).
* Split file groups by location/store (not by line count) and keep each daily group under 50 MB.
* Use ISO-8601/RFC 3339 timestamps with timezone (Z or ±HH:MM) for time synchronization.
* Ensure required identifiers are stable (storeId/locationId/terminalId) and unique keys do not collide.
* Validate your files using the [CSV validator](https://integrations.solinkcloud.com/csv-validator). More information on how to use the validator [is available here](/docs/install-connect-and-integrate/integrations/csv-formatting-guides/validate-files-standard-csv-validator.md).

***

## File Delivery Requirements

These requirements apply to all schema types unless the schema-specific documentation states otherwise.

* Automated delivery of files over SFTP using the credentials provided by Solink.
* Each file must contain data for a single day. Do not mix multiple business days in one file.
* Do not split a single day’s transactions across multiple, overlapping uploads. A transaction/event must appear in only one daily group.
* Split large deliveries into multiple files and add a numeric suffix (for example, 20240213-header-2.csv).
* Split by location/store when possible. Avoid splitting by line count unless required by file size limits.
* Keep each daily group (all files for a day, per schema and per location strategy) at or below 50 MB.

***

## CSV Formatting Requirements

Solink expects CSV files to follow standard conventions for comma-separated values, including quoting rules for fields containing commas, quotes, or line breaks.

* Encoding: UTF-8.
* Delimiter: comma (,).
* Header row: first row must contain column headers.
* Quoting: use double quotes (") around fields containing special characters (comma, CR/LF, or quotes). To include a quote inside a quoted field, escape it by doubling it (""), per common CSV conventions.
* Line endings: LF or CRLF are accepted; be consistent within a file.
* Empty values: represent missing optional values as empty fields; do not shift columns, and do not include NULL as a value.

***

## File Naming Conventions

File names are used for automated discovery and to group related files. Use the schema-specific fileType values where applicable.

| **Component** | **Required**    | **Format**                                             | **Examples**            |
| ------------- | --------------- | ------------------------------------------------------ | ----------------------- |
| \<datestamp>  | Yes             | YYYYMMDD (daily) or YYYYMMDDTHHmm (multiple drops/day) | 20240213, 20240213T1233 |
| \<storeId>    | No              | Alphanumeric identifier for a physical location/store  | A203, store535          |
| \<fileType>   | Schema-specific | One of the file types listed in the schema section     | header, item, payment   |

***

## Timestamp Requirements (ISO-8601 / RFC 3339)

Solink synchronizes events to video using absolute time. Use ISO-8601 timestamps with an explicit timezone designator (Z for UTC or an offset such as -05:00). Include milliseconds when possible to improve alignment.

* 2024-06-15T06:15:00.000-06:00 (local time with offset)
* 2024-06-15T12:15:00.000Z (UTC)

| **Tip:** Avoid ambiguous local timestamps (no timezone). If your source system cannot emit offsets, export in UTC and use the Z suffix. |
| --------------------------------------------------------------------------------------------------------------------------------------- |

### Format pattern:

YYYY-MM-DDTHH<span data-gb-custom-inline data-tag="emoji" data-code="1f1f2-1f1f2">:flag\_mm:</span>ss.sss±HH:MM (with timezone offset)

YYYY-MM-DDTHH<span data-gb-custom-inline data-tag="emoji" data-code="1f1f2-1f1f2">:flag\_mm:</span>ss.sssZ (UTC)

### Examples:

2024-08-02T17:09:04.818Z → Aug 2, 2024 5:09:04 PM UTC

2024-06-15T06:15:00.000-06:00 → Jun 15, 2024 6:15:00 AM CST

2024-12-30T00:06:31.070Z → Dec 30, 2024 12:06:31 AM UTC

***

## Validation and Common Rejection Reasons

During ingestion, Solink validates file structure and schema requirements. Rows (or entire files) may be rejected if they do not conform.

* Missing required columns or empty required fields.
* Invalid enum/picklist values (for example, Retail status field).
* Non-unique identifiers where uniqueness is required (for example, transactionID in Retail / Restaurant). Duplicate IDs overwrite earlier transactions. ID’s must be unique across all stores, registers, and days.
* Invalid timestamp format or timestamps without timezone.
* itemTime or paymentTime is outside the transaction window. The header startTime and endTime sets this window.
* Overlapping transactions/events across multiple daily groups.
* TransactionId / SequenceID cannot contain any of the following characters: ! ' ( ) ; : @ & = + $ , / ? % # \[ ]

***

## Data Privacy and Minimization

Send only the data required for the integration. When customer identifiers are needed, use redacted or tokenized identifiers where possible (for example, last four digits rather than full account or card numbers).

***

## Use Case Handling

This section explains how to send specific file types to Solink.

Examples below show only the relevant columns. Required columns such as dataType, storeId, and startTime still apply.

### The Balance Rule

Every transactionId must satisfy the following across the three files:

```
sum(line totals) - transactionDiscount or   
sum(itemdiscount) + transactionTax =   
sum(totalPaymentTendered) − changeDue
```

A line total is (unitPrice × quantity) − itemDiscount. Sale lines add, Return and Void lines subtract.

* Send positive amounts. Direction comes from status and itemStatus, never from a minus sign.
* Tax goes on the header only. Use transactionTax. Do not bake tax into unitPrice.

### Discounts

Send each discount in one place: itemDiscount for a line, or transactionDiscount for the whole receipt. Never both for the same dollars. Solink will subtract both.

Example: $100.00 jacket, $20.00 off, $8 tax (10%) on the discounted $80.00:

```
# item  
transactionId,itemStatus,quantity,unitPrice,itemDiscount  
1001,Sale,1,100.00,20.00
```

```
# header  
transactionId,status,transactionDiscount,transactionTax  
1001,sale,0.00,8.00
```

```
# payment  
transactionId,tenderType,totalPaymentTendered,changeDue  
1001,"Credit VISA",88.00,0.00
```

Check: (100.00 − 20.00) + 8.00 = 88.00

For a receipt-level discount, put the amount in transactionDiscount instead and leave itemDiscount at 0.00.

Free, comped, or 100% discounted lines must net to zero. Never discount against a 0.00 price:

* **Correct:** unitPrice 8.00, itemDiscount 8.00 → line total 0.00
* **Incorrect:** unitPrice 0.00, itemDiscount 8.00 → line total −8.00

### Refunds

Header status = refund, every line itemStatus = Return, all amounts positive. totalPaymentTendered is the amount returned to the customer and changeDue is 0.00. Link back with originalTransactionId.

**Example**: The $88.00 jacket sale returned in full:

```
# item  
transactionId,itemStatus,quantity,unitPrice,itemDiscount,originalTransactionId  
2001,Return,1,100.00,20.00,1001
```

```
# header  
transactionId,status,transactionDiscount,transactionTax  
2001,refund,0.00,8.00
```

```
# payment  
transactionId,tenderType,totalPaymentTendered,changeDue  
2001,"Credit VISA",88.00,0.00
```

Check: (100.00 − 20.00) + 8.00 = 88.00

### Partial Refund

Header status = sale, itemStatus = return+sale(mix).

Use status = sale here because the customer still pays. All amounts stay positive. changeDue = 0.00.

**Example**: Customer keeps shoes $50.00, returns socks $30.00. 10% tax on the net $20.00 = $2.00. The customer pays $22.00:

```
# item  
transactionId,itemStatus,quantity,unitPrice,itemDiscount,originalTransactionId  
2002,Sale,1,50.00,0.00,  
2002,Return,1,30.00,0.00,1002
```

```
# header  
transactionId,status,transactionDiscount,transactionTax  
2002,sale,0.00,2.00
```

```
# payment  
transactionId,tenderType,totalPaymentTendered,changeDue  
2002,"Credit VISA",22.00,0.00
```

Check: 50.00 − 30.00 + 2.00 − 0.00 = 22.00 − 0.00

### Full Voided Transactions

**Example**: A $100.00 jacket with a $20.00 discount is rung, then the whole ticket is cancelled before the customer pays. Header status is void, every line is Void, transactionTax is 0.00, and there is no payment row (no matching Sale is required):

```
# itemfile  
transactionId,itemStatus,quantity,unitPrice,itemDiscount  
1008,Void,1,100.00,20.00
```

```
# headerfile  
transactionId,status,transactionDiscount,transactionTax  
1008,void,0.00,0.00
```

```
# paymentfile  
transactionId,tenderType,totalPaymentTendered,changeDue  
(no rows)
```

### Partial Voided Transactions

itemStatus = Void +sales mix.

**Example**: A $25.00 item is rung up, voided, and the sale completes with a $10.00 item at 10% tax. Void lines subtract from the item total, so a matching $25.00 Sale is required. Without it the total would be $10.00 − $25.00 instead of $10.00, and it would not match the $11.00 payment:

```
# itemfile  
transactionId,itemStatus,quantity,unitPrice  
1003,Sale,1,25.00  
1003,Void,1,25.00  
1003,Sale,1,10.00
```

```
# headerfile  
transactionId,status,transactionTax  
1003,sale,1.00
```

```
# paymentfile  
transactionId,tenderType,totalPaymentTendered,changeDue  
1003,Cash,11.00,0.00
```

Check: (25.00 − 25.00 + 10.00) + 1.00 = 11.00

### Exchanges

**Example**: Exchange, header is sale, and on the receipt a Return and a Sale line that cancel, transactionTax of 0.00, and no payment row:

```
# item  
transactionId,itemStatus,quantity,unitPrice,originalTransactionId  
3001,Return,1,45.00,1004  
3001,Sale,1,45.00,
```

```
# header  
transactionId,status,transactionTax  
3001,sale,0.00
```

```
# payment  
(no rows)
```

Check: (45.00 − 45.00) + 0.00 = 0.00

### Split Tender

One payment row per tender. The sum of totalPaymentTendered minus changeDue must equal the transaction total. Keep paymentTime between the header's startTime and endTime.

Example: an $88.00 purchase paid with a $50.00 credit card and $60.00 cash:

```
# payment  
transactionId,tenderType,totalPaymentTendered,changeDue  
1006,"credit Card",50.00,0.00  
1006,Cash,60.00,22.00
```

Check: (50.00 + 60.00) − 22.00 = 88.00

changeDue goes on the cash row only.

### Change Due (Cash Payments)

changeDue is the cash handed back. totalPaymentTendered on that row is what the customer handed over, before change.

Example: $43.75 purchase paid with $50.00 cash:

```
# payment  
transactionId,tenderType,totalPaymentTendered,changeDue  
1007,Cash,50.00,6.25
```

Check: 50.00 − 6.25 = 43.75

* **Cash tendered above the total**: changeDue is the actual change, positive
* **Split tender with cash overpayment**: changeDue on the cash row only

Never send changeDue equal to the full cash tender, a negative changeDue.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://help.solink.com/docs/install-connect-and-integrate/integrations/csv-formatting-guides/csv-format-for-sftp-data-integrations.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
