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

# Connect via SFTP

> Set up a secure SFTP connection to send customer, invoice, and payment data from your source system to Daylit.

Use this guide if your data reaches Daylit over **SFTP** instead of a live accounting integration. It walks through SSH key setup, connection testing, field mapping, sample validation, your first full snapshot, and ongoing delta uploads.

For what to send and how files should be shaped, see [SFTP data schema](/settings/sftp-data-schema).

## What is SFTP?

**SFTP** (SSH File Transfer Protocol) is a standard way to move files securely between your systems and Daylit over the internet. You export data from your source system as CSV files, then upload them to a Daylit-managed endpoint on a schedule you control. Authentication uses SSH keys — not passwords — so automated jobs can run unattended.

Once files land, Daylit validates them against your field mapping, ingests the records, and keeps your AR dashboard current through incremental delta uploads.

## Why SFTP?

Most Daylit customers connect their accounting system through [Merge](/settings/integrations) — an OAuth link to QuickBooks, NetSuite, Xero, Sage, or another Merge-supported platform. Data syncs automatically with no file exports on your side.

SFTP is the right path when a direct connection isn't possible or practical:

* **Your system isn't supported by Merge.** Legacy ERPs, industry-specific billing platforms, or custom-built tools often aren't available for OAuth linking.
* **Your IT or security team restricts API access.** Some organizations block third-party connections to accounting systems but allow scheduled file drops to an approved endpoint.
* **You already export data on a schedule.** If your ERP produces nightly CSV reports or you run SQL exports from a data warehouse, SFTP plugs into that existing workflow.
* **You need to combine data from multiple sources.** You can merge customers, invoices, and contacts from different systems into unified files before uploading.
* **You want explicit control over what Daylit receives.** You choose exactly which fields and records leave your environment, and when.

Both paths deliver the same outcome in Daylit — customers, invoices, contacts, and credit notes ready for collections and AR reporting. SFTP just means **you push files** instead of Daylit **pulling via API**.

<Note>
  If your accounting platform appears in the Merge provider list, a direct integration is usually faster to set up. Use this SFTP guide when your Daylit contact directs you here or when OAuth linking isn't an option.
</Note>

## Before you start

### What you'll need

* **An export from your source system.** Daylit accepts CSV files. Most ERPs, accounting systems, and billing platforms can produce scheduled exports or report-to-file output.
* **A machine that can run an SFTP client.** Use the command line (`sftp` on macOS, Linux, and modern Windows) or a GUI tool such as FileZilla, WinSCP, or Cyberduck.
* **A place to run a scheduled job.** Eventually you want uploads on a schedule — cron, Windows Task Scheduler, Airflow, or your ERP's built-in scheduler.
* **About 30 minutes** to reach your first validated sample upload. A full snapshot adds another \~20 minutes depending on volume.
* **One technical contact.** Daylit emails this address when something needs your attention.

### What you won't need

* A VPN or special network access. Standard internet over port 22 is enough.
* A password. Daylit uses SSH keys only — more secure and easier to automate.
* A waiting period on Daylit's side. Everything in this guide is self-serve in the portal, though [Daylit](/settings/sftp#getting-help) support is available if you need help.

## Quick start

If you've done SFTP integrations before, here's the speed run. Details follow in the steps below.

<Steps>
  <Step title="Generate an SSH key">
    `ssh-keygen -t ed25519 -f ~/.ssh/daylit_ed25519`
  </Step>

  <Step title="Register the public key">
    Sign in to Daylit, go to **Settings → SFTP Connection → Endpoints**, and paste the contents of `~/.ssh/daylit_ed25519.pub`. Give the connection a name (for example, `primary-source`).
  </Step>

  <Step title="Map your columns">
    On the **Mapping** tab, map your source columns to Daylit's canonical fields for each object.
  </Step>

  <Step title="Copy connection details">
    After saving, copy the hostname, port, and system-generated username from your connection card.
  </Step>

  <Step title="Upload a sample">
    `sftp -i ~/.ssh/daylit_ed25519 username@sftp.daylit.com` and `put` a sample file into `/sample/{object}/`.
  </Step>

  <Step title="Validate and go live">
    On **Sample Checks**, confirm validation passes for all four required objects. Upload your `/init/` snapshot, run a dry-run, then apply. Schedule delta uploads to `/delta/`.
  </Step>
</Steps>

## Step 1 — Generate your SSH key

An SSH key is a matched pair of files that proves your identity without a password. You generate them yourself and keep the **private** key secure. You'll register the **public** key with Daylit in Step 2.

<Frame>
  <iframe src="https://www.loom.com/embed/975d9689f6b24ac39230671f0cd5465e" title="Generate an SSH key for Daylit SFTP" allowFullScreen />
</Frame>

Generate the key **once** on the machine that will run your scheduled upload job. **On macOS, Linux, or Windows with WSL**, open a terminal and run:

```bash theme={null}
# -C "acmecorp-daylit-sftp" is a label to help you identify the key — use your company name instead of acmecorp if you prefer
ssh-keygen -t ed25519 -C "acmecorp-daylit-sftp" -f ~/.ssh/daylit_ed25519
```

When prompted for a passphrase: leave it empty if an unattended scheduled job will use this key. Use a passphrase only if a human connects interactively.

**Optional (macOS):** copy the public key to your clipboard:

```bash theme={null}
pbcopy < ~/.ssh/daylit_ed25519.pub
```

You'll end up with two files:

* `~/.ssh/daylit_ed25519` — the **private key**. Never share, email, or commit this file.
* `~/.ssh/daylit_ed25519.pub` — the **public key**. Safe to share with Daylit.

<Note>
  Daylit accepts ED25519, ECDSA, and RSA (3072 bits or larger). ED25519 is the modern default. Use `ssh-keygen -t rsa -b 4096` if your environment requires RSA.
</Note>

The public key is one long line starting with `ssh-ed25519` (or `ssh-rsa`), followed by base64, then your comment:

```
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINjB7Tx... acmecorp-daylit-sftp
```

If you see `-----BEGIN OPENSSH PRIVATE KEY-----`, you're looking at the private key. Don't paste that into the portal.

**Estimated time: 5 minutes.**

## Step 2 — Register the public key in Daylit

Sign in at [intelligence.daylit.com](https://intelligence.daylit.com/login). If you don't have access yet, request it from your [Daylit](/settings/sftp#getting-help) contact. Then:

1. Open your public key file in a text editor and copy the entire single line.
2. Go to **Settings → SFTP Connection → Endpoints**.
3. Paste the public key into the **SSH Public Key** field.
4. Enter a **Connection Name** (for example, `sage-export` or `primary-source`) so you can identify which system the data comes from. Use lowercase letters, digits, and hyphens.
5. Click **Save configuration**.

<Frame>
  <iframe src="https://www.loom.com/embed/e1a1ff4609c5423bae01ba4b78b5ac2e" title="Register your SSH public key in Daylit SFTP Connection settings" allowFullScreen />
</Frame>

The portal validates the format immediately. The most common error is pasting the private key by accident.

**Estimated time: 2 minutes.**

## Step 3 — Get your connection details

After you save your endpoint, Daylit creates your SFTP user, private storage area, and access scope. Your connection card shows:

* **SFTP Hostname / Endpoint** — `sftp.daylit.com`
* **Port** — `22` (standard SFTP)
* **Assigned Username** — system-generated for this connection
* **Key updated at** — timestamp of the latest key rotation for this connection

### Optional: SSH config entry

For command-line use, add an entry to `~/.ssh/config`:

```
Host daylit
  HostName sftp.daylit.com
  User your-assigned-username
  Port 22
  IdentityFile ~/.ssh/daylit_ed25519
  IdentitiesOnly yes
```

Run `chmod 600 ~/.ssh/config ~/.ssh/daylit_ed25519`. After that, you can connect with `sftp daylit` instead of typing the full command `sftp -i ~/.ssh/daylit_ed25519 your-username@sftp.daylit.com` each time. The SSH config entry supplies your hostname, username, port, and key automatically.

### Upload paths

Daylit creates these top-level directories for your workspace:

| Path       | Purpose                                               |
| ---------- | ----------------------------------------------------- |
| `/test/`   | Connectivity check only — no business validation      |
| `/sample/` | Small files (≤ 100 rows) to validate your mapping     |
| `/init/`   | Full snapshot of every record in an object            |
| `/delta/`  | Incremental updates since your last successful upload |

Within each phase folder, place files in the matching object subfolder (for example, `/sample/invoice/`). See [SFTP data schema](/settings/sftp-data-schema#where-to-put-files-sftp-paths) for the full path structure.

### Test your connection

Connect to your SFTP workspace with **one** of these commands:

```bash theme={null}
sftp -i ~/.ssh/daylit_ed25519 your-username@sftp.daylit.com
```

Or, if you completed the optional SSH config step above:

```bash theme={null}
sftp daylit
```

At the SFTP prompt, upload any small file to `/test/` and exit:

```bash theme={null}
sftp> put any_small_file.csv /test/
sftp> bye
```

<Frame>
  <iframe src="https://www.loom.com/embed/4083dfce56094ac8b220ce2a06465c75" title="Test your SFTP connection to Daylit" allowFullScreen />
</Frame>

On first connect, your client may ask to verify the host. Accept if the fingerprint matches what the portal shows.

<Warning>
  If you're prompted for a password, you have a key mismatch or wrong username/hostname. Re-check your private key path and the username on your connection card.
</Warning>

Back in Daylit, click **Test connection** on the connection card. Once active, you'll see a green status banner and a **Connection verified** status confirming Daylit received your file.

**Estimated time: 5 minutes.**

## Step 4 — Get your data prepared

Daylit requires five objects before you can go live:

| Object               | What it is                                                      |
| -------------------- | --------------------------------------------------------------- |
| **Customer**         | Companies or individuals you bill                               |
| **Customer Contact** | AP and billing contacts at each customer                        |
| **Invoice**          | Invoices with amounts, dates, status, and embedded payment info |
| **Credit Note**      | Refunds, returns, and applied or unapplied credits              |
| **Payment**          | Individual payment events, one row per invoice allocation       |

For file format, required fields, and upload paths, see [SFTP data schema](/settings/sftp-data-schema).

**Estimated time: 2 minutes.**

<h2 id="step-5-map-your-columns-to-daylit-fields">
  Step 5 — Map your columns to Daylit fields
</h2>

Daylit uses a **canonical schema** — a fixed set of field names per object (for example, every invoice has `remote_id`, `customer_remote_id`, `total_amount`, `issue_date`). Your source system likely uses different column names. The mapping tells Daylit how to translate.

For each object, the **Mapping** tab shows three columns: **Daylit field (fixed)**, **External parameter name**, and **Format**.

Enter the corresponding parameter name from your data for each Daylit field, then select the correct format. Be especially careful with date fields — the format must match how your export represents dates (for example, `MM/DD/YYYY` vs `YYYY-MM-DD`).

<Frame>
  <iframe src="https://www.loom.com/embed/ff57fa202fd34bd3901da9401544753f" title="Map SFTP columns to Daylit fields" allowFullScreen />
</Frame>

You can also download default YAML or JSON config files, edit them offline, and upload via **Upload config**.

Click **Save mapping** when done. Daylit keeps track of different mapping versions so you can always see exactly what was in effect when a given file was ingested.

**Estimated time: \~10 minutes per object the first time.**

## Step 6 — Upload and validate your sample

A **sample** proves your mapping is correct before you send data at scale. Daylit validates the sample but does **not** ingest it into production.

<Frame>
  <iframe src="https://www.loom.com/embed/a1ba41cccc24486992d050b3f11c4c01" title="Upload and validate an SFTP sample in Daylit" allowFullScreen />
</Frame>

### Produce the sample

Export 10–100 representative rows for one object from your source system. Include a mix of customers, statuses, and edge cases — not all rows from one customer.

### Upload

**Command line:**

```bash theme={null}
sftp -i ~/.ssh/daylit_ed25519 your-username@sftp.daylit.com
# (or just: sftp daylit, if you set up ~/.ssh/config above)
sftp> put invoices_sample_2026-06-02.csv /sample/invoice/
sftp> bye
```

You must upload to the correct object subfolder or Daylit won't detect the file.

### Validate in the portal

Go to **Settings → SFTP Connection → Sample Checks**, select your SFTP connection, and click **Validate** for each object (or **Validate all**).

**Pass** — all rows parsed with zero errors. Move on to the next object.

**Fail** — the report shows row-level errors in plain language:

**Common sample failures:**

* **Date format mismatch** — update the format in **Mapping**, save, and click **Validate** again. You don't need to re-upload unless you want to test a new file.
* **Currency symbol or thousands separator** in an amount column — add the `decimal` transform.
* **Empty required field** — map the field to the correct source column or fix the export.

**Estimated time: 5–15 minutes for the first iteration.**

## Step 7 — Upload your init (full snapshot)

Once every sample passes, send your first **init** — a complete snapshot of every record in the object. You usually do this once per object; future updates are deltas.

<Frame>
  <iframe src="https://www.loom.com/embed/00493fbf484642c789965a3cdfcf41c2" title="Upload your SFTP init snapshot in Daylit" allowFullScreen />
</Frame>

### Produce the init

Run the same export as your sample, but with no row limit:

* **Customer** — every active customer (inactive optional)
* **Customer Contact** — customer ID, contact ID, and email
* **Invoice** — every invoice: open, partially paid, paid, voided, and draft
* **Credit Note** — every credit note, applied or unapplied
* **Payment** — every payment event, one row per invoice allocation (optional)

### Upload order

Upload parents before children so foreign keys resolve immediately:

1. `/init/customer/` first
2. `/init/customer_contact/` and/or `/init/invoice/` next
3. `/init/credit_note/` and/or `/init/payment/`

```bash theme={null}
sftp daylit
sftp> put customers_init_2026-06-02.csv /init/customer/
sftp> put contacts_init_2026-06-02.csv /init/customer_contact/
sftp> put invoices_init_2026-06-02.csv /init/invoice/
sftp> put credit_notes_init_2026-06-02.csv /init/credit_note/
sftp> put payment_init_2026-06-02.csv /init/payment/
sftp> bye
```

For large files, enable progress in the SFTP client:

```bash theme={null}
sftp> progress
sftp> put invoices_init_2026-06-02.csv /init/invoice/
```

### Dry-run and apply

After upload, click **Dry-run** in the portal to generate a report and preview how your data will look. Start with the **Customer** object first, then proceed in dependency order. Fix any validation errors before moving on.

When the dry-run is clean, click **Apply** to ingest the data. Large inits may take time to complete.

<Warning>
  **Dependency order required**

  Dry-runs and apply must follow dependency order: `/init/customer/` → `/init/customer_contact/` → `/init/invoice/` → `/init/credit_note/`. Customer must be applied successfully before other objects can be dry-run or applied.
</Warning>

<Note>
  If validation fails, the init is rejected as a unit — Daylit doesn't write half of a snapshot. You see exactly what failed and re-upload after fixing. We again take the latest upload file for init check, or you can replace the original file.

  A single wrong row will invalidate the whole batch. This is initial batch hence we need to ensure the data is correct.
</Note>

**Estimated time: 30 minutes to few hours depending on data volume.**

## Step 8 — Set up your delta schedule

After your init is applied, keep Daylit current with **deltas** — incremental files containing every record created or modified since your last successful upload.

<Frame>
  <iframe src="https://www.loom.com/embed/72cb2d6a63ed4e46a21fccb53086254d" title="Set up SFTP delta uploads in Daylit" allowFullScreen />
</Frame>

### What a delta should contain

Filter exports by your source system's "last modified" timestamp:

```sql theme={null}
SELECT * FROM invoices
WHERE modified_on > '2026-06-02T14:43:51Z';
```

Whatever your tool is (saved search, scheduled report, SQL query, Power Automate flow), build it to filter by your "last modified" column and the timestamp of the last successful run.

### How often to run

Daylit picks up new files every **15 minutes**, so running more frequently than that doesn't speed ingestion. Common cadences:

* **Hourly** — active collections and AR aging workflows
* **Nightly** — when 24-hour freshness is sufficient
* **Every 15 minutes** — high-priority objects like invoices

You can use different cadences per object.

### Scheduling the upload

<Tabs>
  <Tab title="Linux / macOS cron">
    Add to your crontab (`crontab -e`):

    ```bash theme={null}
    # Daylit delta upload — invoices hourly
    0 * * * * /home/erp-export/daylit_delta.sh
    ```

    A minimal `daylit_delta.sh`:

    ```bash theme={null}
    #!/usr/bin/env bash
    set -euo pipefail

    # 1. Export from your source system
    LAST_RUN_FILE=/var/lib/daylit/last_run.txt
    SINCE=$(cat "$LAST_RUN_FILE" 2>/dev/null || echo "1970-01-01T00:00:00Z")
    NOW=$(date -u +%FT%H%M%SZ)

    # (Replace this with whatever produces your CSVs from your ERP.)
    /usr/local/bin/erp-export invoices --modified-after "$SINCE" \
      > "/exports/invoices_delta_${NOW}.csv"

    # 2. Upload to Daylit
    sftp -i ~/.ssh/daylit_ed25519 -o StrictHostKeyChecking=yes \
         your-username@sftp.daylit.com <<EOF
      put /exports/invoices_delta_${NOW}.csv /delta/invoice/
    EOF

    # 3. Record success
    echo "$NOW" > "$LAST_RUN_FILE"
    ```

    A few things this script does on purpose:

    * It uses a `last_run.txt` file to remember when the last successful run was, so the next delta picks up from there. Don't use "the last 24 hours" — if a run fails, you'd miss data permanently.
    * It only updates `last_run.txt` after the upload succeeds. If the SFTP step fails, the next run re-tries from the same starting point.
    * `set -euo pipefail` makes the script fail loudly on any error rather than silently producing an empty file.
    * `StrictHostKeyChecking=yes` ensures the host key is verified.
  </Tab>

  <Tab title="Airflow / Prefect / Dagster">
    If you already run a data orchestrator, build a DAG with two tasks: **export** and **upload**. Make the upload task idempotent (it should be safe to re-run if it failed). Use the orchestrator's secret store for the SSH key — never check it into your repo.
  </Tab>

  <Tab title="Built-in ERP scheduler">
    Many ERPs (NetSuite, Acumatica, Dynamics) have a built-in "scheduled SFTP upload" feature. We highly recommend using this feature if it's available, as it will be more reliable and require less work to set up. The configuration is the same: hostname, username, key, target folder.
  </Tab>
</Tabs>

**Estimated time: 30–60 minutes.**

***

## Reference

The numbered steps above walk you through initial SFTP onboarding. The sections below cover ongoing maintenance, troubleshooting, and quick lookup — they are not part of the setup flow.

### Rotate your SSH key

Rotate every 6–12 months, or when someone with access to your private key leaves the team:

1. Generate a new key: `ssh-keygen -t ed25519 -f ~/.ssh/daylit_ed25519_new`
2. In **Settings → SFTP Connection → Endpoints**, add the new public key.
3. Test: `sftp -i ~/.ssh/daylit_ed25519_new your-username@sftp.daylit.com`
4. Update your scheduled job to use the new key and run it once manually.
5. Remove the old public key from the portal and delete the old private key from disk.

### Troubleshooting

<AccordionGroup>
  <Accordion title="Permission denied (publickey)">
    Your SFTP client is presenting the wrong key, or the key isn't registered in the portal.

    * Confirm you're using `-i ~/.ssh/daylit_ed25519` (path to the **private** key).
    * Verify the matching public key appears under **Settings → SFTP Connection → Endpoints**.
    * Confirm the username matches your connection card exactly.
  </Accordion>

  <Accordion title="Host key verification failed">
    Compare the fingerprint your client shows against the value in the portal. If they match, accept the new key. If they don't, stop and contact [support@daylit.com](mailto:support@daylit.com).
  </Accordion>

  <Accordion title="Sample failed: column not found">
    Your CSV doesn't have a column your mapping references. Re-check the export query and your mapping on the **Mapping** tab.
  </Accordion>

  <Accordion title="Init failed: foreign key not resolved">
    A child record references a parent that isn't in the init. Upload the customer init first, and confirm customer ID columns match across exports (for example, `client_id` vs `account_id`).
  </Accordion>

  <Accordion title="Delta failed: remote_updated_at went backwards">
    A record's `remote_updated_at` is older than the version Daylit already has. Verify your export uses the same timestamp column as your mapping.
  </Accordion>

  <Accordion title="Scheduled job silently failing">
    Use `set -euo pipefail` in your script so SFTP failures exit non-zero. Log stderr and configure your scheduler to alert on failure.
  </Accordion>
</AccordionGroup>

### FAQ

<AccordionGroup>
  <Accordion title="Can I use a password instead of an SSH key?">
    No. Daylit accepts SSH keys only (ED25519, ECDSA, or RSA ≥ 3072 bits). Keys are more secure and easier to automate.
  </Accordion>

  <Accordion title="What if my ERP can't produce CSV?">
    Try formatting to CSV export. A small middleware script (SQL query → file) usually solves export limitations. More file format options are coming soon. Contact [Daylit](/settings/sftp#getting-help) to scope a solution.
  </Accordion>

  <Accordion title="Can I send the same data twice?">
    Yes. Daylit upserts on `remote_id` — duplicate sends update existing rows rather than creating duplicates.
  </Accordion>

  <Accordion title="Can I have separate test and production environments?">
    Yes. Create a second workspace and run your scheduled job against test first. Point at production once you're satisfied.
  </Accordion>

  <Accordion title="Where is my data stored?">
    Your files are encrypted at rest and in transit when you upload via SFTP. We never share your data with other clients. The ingestion pipeline is read-only into Daylit; we do not write back to your source system.
  </Accordion>
</AccordionGroup>

### Glossary

| Term                 | Definition                                                                                          |
| -------------------- | --------------------------------------------------------------------------------------------------- |
| **Canonical schema** | Daylit's fixed field names per object. Your mapping translates source column names to these fields. |
| **Sample**           | A small file that validates your mapping. Not ingested into production.                             |
| **Init**             | A full snapshot of every record in an object. Sent once as the starting state.                      |
| **Delta**            | An incremental update with records created or modified since the last successful upload.            |
| **remote\_id**       | The record's identifier in your source system. Required on every row. Daylit upserts on this value. |
| **Upsert**           | Insert if `remote_id` is new; update if it's already known.                                         |

<h3 id="getting-help">
  Getting help
</h3>

* **Portal in-app chat** — bottom-right on every page.
* **Email:** [support@daylit.com](mailto:support@daylit.com). Include your workspace name and the filename or timestamp you're asking about.
* **Status page:** [status.daylit.com](https://status.daylit.com) for any active incidents.
