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
Chargeback
Banking
Access Control
This guide is intended for customers and partners building an integration.
Data Ingestion Overview
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
Restaurant
Files Expected: 3 files per day; header, item, payment
Use Case: Event-level restaurant transactional data paired with video
Banking
Files Expected: 1 file per day
Use Case: Event-level ATM / Teller transactional data paired with video
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.
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, chargeback, accesscontrol |
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:mm:ss.sss±HH:MM (with timezone offset)
YYYY-MM-DDTHH:mm: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, Access Control result/state).
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.

