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

# SFTP Getting Started

> Send your first workbook to the SOLO Network over SFTP

This guide walks you through your first SFTP upload end-to-end — from getting credentials to confirming that the network ingested your data. Plan on about 15 minutes.

## What you'll need

<Steps>
  <Step title="A WorkOS API key for your organization">
    SFTP authentication is delegated to WorkOS. Your SOLO account manager will help you (or your WorkOS admin) mint a key under your organization. The key string itself is the SFTP password.

    <Warning>
      Treat the API key like any other credential. Store it in a secrets manager and never commit it to source control. The key — not the username — is what grants access: anyone holding it can upload as your organization.
    </Warning>
  </Step>

  <Step title="The email you use to sign in to SOLO">
    Your email is the SFTP username. It identifies *who* is connecting and selects the environment via an optional plus-tag. The organization your uploads are attributed to is determined by the **API key's owning organization**, not by the email — so a valid key is required no matter what username you present.
  </Step>

  <Step title="An SFTP client">
    Anything that speaks SFTP works — the `sftp` command from OpenSSH, [FileZilla](https://filezilla-project.org/), [Cyberduck](https://cyberduck.io/), or a library such as Python's [Paramiko](https://www.paramiko.org/).
  </Step>
</Steps>

## Step 1 — Pick an environment

We recommend doing your first upload against **sandbox**. Sandbox is a fully isolated copy of the network: nothing you upload there reaches production data, so it's the right place to learn the format and verify your pipeline.

You select an environment by appending a plus-tag to the local part of your email:

| Environment | Username Format            | Example                      |
| ----------- | -------------------------- | ---------------------------- |
| Sandbox     | `email+sandbox@domain.com` | `alice+sandbox@yourbank.com` |
| Production  | `email@domain.com`         | `alice@yourbank.com`         |

Each environment writes to its own storage bucket and database; there is no crossover. Usernames are case-insensitive — `Alice@YourBank.com` and `alice@yourbank.com` are the same user.

## Step 2 — Connect

The SFTP host is the same for every environment:

```
sftp.solo.one:22
```

<CodeGroup>
  ```bash sftp (CLI) theme={null}
  sftp alice+sandbox@yourbank.com@sftp.solo.one
  # When prompted, paste your WorkOS API key as the password.
  ```

  ```python Python (paramiko) theme={null}
  import paramiko

  transport = paramiko.Transport(("sftp.solo.one", 22))
  transport.connect(
      username="alice+sandbox@yourbank.com",
      password="sk_live_your_workos_api_key",
  )
  sftp = paramiko.SFTPClient.from_transport(transport)
  # ... upload here ...
  sftp.close()
  transport.close()
  ```

  ```text ~/.ssh/config theme={null}
  Host solo-sftp-sandbox
      HostName sftp.solo.one
      User alice+sandbox@yourbank.com
      Port 22
  ```
</CodeGroup>

On a successful connection you'll land at the root of your organization's upload area.

<Warning>
  Your SFTP session is **upload-only**: you can `put` files into the category directories and create directories, but `ls`, `get`, overwriting, and deleting are not permitted. Don't be surprised when a directory listing is denied — that's policy, not a fault. Verification happens in the dashboard (Step 6), not over SFTP.
</Warning>

<Tip>
  If the server rejects you immediately, the most common causes are (1) the API key has been revoked, (2) your organization isn't provisioned in SOLO yet, or (3) the plus-tag is misspelled (only `+sandbox`, `+prod`, or no tag are accepted). Re-check the username and key, then reach out to your account manager if it persists.
</Tip>

## Step 3 — Download a template

Every upload is an Excel workbook (`.xlsx`) that lands in one of five category directories. Workbook templates are available on the [Upload Categories](/api-overview/sftp/schemas) page — they include the required header row, an example row, and the right column names already in place.

For this walkthrough, start with the simplest path: defining a **KYC certificate policy** that a furnisher can later attach data to.

<Card title="Download Template" icon="download" href="https://raw.githubusercontent.com/solo-finance/docs/dev/api-overview/sftp/templates/kyc-cert-policy-template.xlsx">
  KYC Certificate Policy Template (.xlsx)
</Card>

<Note>
  Policy and program workbooks define network configuration, so they are accepted only from the network's [governor](/concepts/governance/network-governance). If your organization is a furnisher but not a governor, start with a `kyc_furnish_data` workbook instead — the steps below are the same, only the directory and columns differ.
</Note>

## Step 4 — Fill it in

Open the template in Excel (or any other spreadsheet app) and add one row of real data below the example row:

| `kyc_policy_name`     | `start_date` | `entity_type` | `document_capture` | `liveness_capture` | `address_verification` |
| --------------------- | ------------ | ------------- | ------------------ | ------------------ | ---------------------- |
| First KYC Test Policy | `2026-01-01` | `Consumer`    | `true`             | `true`             | `true`                 |

A few things to know about the workbook format ([full reference](/api-overview/sftp/csv-format)):

* Only the **first sheet** of the workbook is read.
* **Row 1** is a banner — anything you put there is ignored.
* **Row 2** must contain the column headers.
* **Row 3 and below** are data rows. Each row is processed independently, so a single bad row won't fail the rest of the file.
* Headers are normalized (lowercased, non-alphanumerics collapsed to `_`), so `KYC Policy Name`, `kyc_policy_name`, and `KYC-Policy-Name` are all equivalent.
* Rows whose identifier column starts with `Ex.` are treated as in-workbook examples and skipped.

Save the file as `first-kyc-test.xlsx`.

## Step 5 — Upload

Drop the workbook into the directory that matches its category:

<CodeGroup>
  ```bash sftp (CLI) theme={null}
  sftp> cd kyc_cert_policy
  sftp> put first-kyc-test.xlsx
  Uploading first-kyc-test.xlsx to /kyc_cert_policy/first-kyc-test.xlsx
  sftp> bye
  ```

  ```python Python (paramiko) theme={null}
  sftp.put("first-kyc-test.xlsx", "/kyc_cert_policy/first-kyc-test.xlsx")
  ```
</CodeGroup>

That's it — there are no intermediate folders to manage. The category directory **is** the schema selector: a file in `kyc_cert_policy/` is parsed as a KYC certificate policy workbook, a file in `programs/` as a programs workbook, and so on.

<Tip>
  Pick a fresh filename for every upload (a date or batch number works well). Sessions can't overwrite or delete existing files, so re-using a name from a previous drop will be rejected.
</Tip>

## Step 6 — Verify ingestion

Ingestion is event-driven and asynchronous — processing starts within seconds of the file landing, and a small workbook typically completes in well under a minute.

The cleanest way to check is the [SOLO dashboard](https://app.solo.one): the **Data → Uploads** page lists every workbook your organization has sent, along with row-level success, filtered, and error counts. From there you can also drill into the resulting records (policies, programs, furnish events) to confirm they showed up where you expected.

<Note>
  Re-sending data is safe at the row level. Data rows upsert on their natural keys (SSN for consumers; tax identifier + jurisdiction for businesses), so a corrected re-upload updates records rather than double-creating them. Re-uploaded **policy** or **program** rows that collide with existing names will surface per-row name-conflict errors instead — version the name (e.g. `First KYC Test Policy v2`) if you intend a new policy.
</Note>

## Common first-upload issues

<AccordionGroup>
  <Accordion title="Authentication failed / Permission denied at login">
    Confirm the username is your full email address (with `+sandbox` for sandbox), and the password is the literal WorkOS API key string — no `Bearer` prefix, no quotes. If you rotated the key recently, the old value is rejected; a freshly revoked key may take up to a minute to be refused everywhere, since auth results are briefly cached.
  </Accordion>

  <Accordion title="&#x22;Permission denied&#x22; on ls, get, or overwrite">
    Working as intended — SFTP access is upload-only. You can `put` new files and create directories; everything else is denied. Use the dashboard to inspect what you've uploaded, and use a new filename rather than overwriting an old one.
  </Accordion>

  <Accordion title="&#x22;Host key verification failed&#x22;">
    The first time you connect from a new machine your SSH client doesn't know the server's host key yet. Either accept the fingerprint at the prompt, or pre-populate your `known_hosts`:

    ```bash theme={null}
    ssh-keyscan sftp.solo.one >> ~/.ssh/known_hosts
    ```
  </Accordion>

  <Accordion title="File uploaded but no records appear">
    Open the workbook in Excel and confirm:

    1. Headers are in **row 2** (not row 1), and there are no merged cells above them.
    2. The identifier column on every real data row is **not** prefixed with `Ex.` (those are skipped as examples).
    3. The file was saved as `.xlsx`, not `.xls` or `.csv`.
    4. For data workbooks: every row's `program_name` matches a program the governor has configured, and the `application_date` falls inside an active policy window — rows outside every window are **filtered** (skipped), which is reported separately from errors.

    The Uploads page in the dashboard surfaces the row-level reason for any failures.
  </Accordion>

  <Accordion title="Policy or program rows fail with an access error">
    Configuration categories (`kyc_cert_policy`, `kyb_cert_policy`, `programs`) are governor-only. The file itself uploads fine, but every row is rejected at ingest if your organization doesn't govern the target network. Furnishers should upload to the data categories instead.
  </Accordion>

  <Accordion title="Row rejected with formula cells that have no cached value">
    Workbooks are read using cached formula results. If a workbook was generated programmatically and never opened in Excel, formula cells may have no cached value — the row is rejected and the error names the offending columns. Open and save the file in Excel (or set cached values when generating it) before uploading.
  </Accordion>
</AccordionGroup>

## Where to go next

<CardGroup cols={2}>
  <Card title="SFTP Overview" icon="server" href="/api-overview/sftp/overview">
    The full mental model — categories, environments, how routing works.
  </Card>

  <Card title="Upload Categories" icon="list" href="/api-overview/sftp/schemas">
    Required and optional columns for every category, plus downloadable templates.
  </Card>

  <Card title="Workbook Format" icon="file-excel" href="/api-overview/sftp/csv-format">
    Detailed rules for headers, data types, and the row 1 banner convention.
  </Card>

  <Card title="API Reference" icon="webhook" href="/api-reference/introduction">
    Prefer to integrate over HTTPS? The same operations are available as REST endpoints.
  </Card>
</CardGroup>
