# SFTP for batch partners

Page: https://sandbox.myclaimhouse.com/docs/sftp

If your system already speaks X12, you can send claims as files instead of calling the API. You drop 837D files on a private SFTP account. Claim House reads each claim out of the file and makes it a claim, the same as one made with [`POST /claims`](https://sandbox.myclaimhouse.com/docs/api/createClaim.md): validated, queued, sent to the network, followed on its timeline, with events, webhooks and remittance matching. Claim House sends its own 837D to the network, never your file. What comes back, the acknowledgment, the statuses and the remittances, is written to a folder you read.

Every claim from a file is also a claim in the dashboard and the API. Read it with [`GET /claims`](https://sandbox.myclaimhouse.com/docs/api/listClaims.md). On its [timeline](https://sandbox.myclaimhouse.com/docs/api/getClaimTimeline.md), the entries your SFTP account caused name it as the actor: `actor.kind` is `sftp`.

Live mode is not open yet. Use the test folders with the sandbox network. A file dropped in `IN` is read and answered, but every claim in it is refused: Live claims are not available yet: nothing was sent. Drop test files in TEST/IN.

## Set up the account

An organization has one SFTP account, for test and live files. Set it up with [`POST /sftp-account`](https://sandbox.myclaimhouse.com/docs/api/setUpSftpAccount.md) (or the `set_up_sftp_account` tool of the [MCP server](https://sandbox.myclaimhouse.com/docs/build-with-ai.md#mcp-server)), with a key that has the `submit` permission:

```bash
curl -X POST "https://sandbox.myclaimhouse.com/api/v1/sftp-account" \
  -H "Authorization: Bearer $CLAIMHOUSE_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{}'
```

The reply has the account's `username`, its `host`, `port` and `host_key_fingerprint`, and a generated `password`. The password is shown once, in this reply and in the reply to [rotating it](https://sandbox.myclaimhouse.com/docs/api/rotateSftpPassword.md), and nowhere else: store it now. The copy kept for an `Idempotency-Key` replay has no password and says `"password_available": false`. Setting up a second time is refused with `409 CONFLICT`: rotate the password instead.

[`GET /sftp-account`](https://sandbox.myclaimhouse.com/docs/api/getSftpAccount.md) shows the account again without the password: its status, the connection facts, the folders, its keys, when it last logged in and when it last had a file taken.

## Connect

| | |
| --- | --- |
| Host | `sftp.sandbox.myclaimhouse.com` |
| Port | `22` |
| Host key fingerprint (SHA256) | `SHA256:Y28qHRaWLMyRfJfHvRuw9p2L/6EmZkF9tP179UmwPCo` |
| User name | The `username` of your account |

Check the fingerprint the first time you connect: your client shows the server's host key, and it must be the one above. Then log in with the password or with a key:

```bash
sftp -P 22 <username>@sftp.sandbox.myclaimhouse.com
```

Only SFTP is offered. There is no FTP, FTPS or web upload. An account can have 10 connections open at once.

**Password.** The generated password is a long random string. Rotate it with [`POST /sftp-account/rotate-password`](https://sandbox.myclaimhouse.com/docs/api/rotateSftpPassword.md): the new password is shown once and the old one stops working for every new login at once; rotating also ends a lock. 5 wrong passwords within 15 minutes lock the account for 15 minutes; while it is locked, the right password is refused too, but a key that is on the account still logs in. Disabling the account refuses every new login at once, with the password or a key ([`POST /sftp-account/disable`](https://sandbox.myclaimhouse.com/docs/api/disableSftpAccount.md)); [`enable`](https://sandbox.myclaimhouse.com/docs/api/enableSftpAccount.md) allows them again. While the account is disabled, its files stay where they are and nothing dropped in `IN` or `TEST/IN` is taken; it is taken once the account is enabled. A session already open stays open until the client disconnects or has been idle 15 minutes. That holds after a rotation, a disable or a key's removal too.

**Keys.** Add a public key with [`POST /sftp-account/keys`](https://sandbox.myclaimhouse.com/docs/api/addSftpKey.md), a `label` and the key as one OpenSSH line: `ssh-ed25519`, `ecdsa-sha2-nistp256`, `-nistp384`, `-nistp521`, or `ssh-rsa` of at least 2048 bits. An account takes at most 5 keys, and a key once; either refusal is `409 CONFLICT`. Remove one with [`DELETE /sftp-account/keys/{id}`](https://sandbox.myclaimhouse.com/docs/api/removeSftpKey.md). A key that is not on the account is refused, and that never counts toward the lock: a client that offers every key it holds cannot lock you out.

## Folders and modes

When you log in you see only your own account's folders, all four from the start. The folder is the mode. A name that starts with a dot is Claim House's (each folder holds an empty one, so the folder shows): you do not see it, and it is never taken as a file.

| Folder | Mode | What it is for |
| --- | --- | --- |
| `IN` | Live | Drop your 837D files here. |
| `OUT` | Live | Claim House writes what comes back here. |
| `TEST/IN` | Test | Drop your 837D files here. |
| `TEST/OUT` | Test | Claim House writes what comes back here. |

A file dropped in `TEST/IN` makes test claims, which go to the sandbox network: see [Sandbox and scenarios](https://sandbox.myclaimhouse.com/docs/sandbox.md). A file dropped in `IN` makes live claims. Responses are written to the `OUT` folder of the mode the file came from.

After a file is taken, it is moved to `processed/` under the folder it was dropped in (`TEST/IN/processed` for a test file), so you can see it was taken. Files in `OUT` and in `processed/` are removed after 60 days; Claim House keeps its own copy of each dropped file.

## File rules

- An 837D, implementation `005010X224A2` (the GS08 and ST03 of the file say so), in one interchange. A second interchange in the file, or any other kind of file, refuses the whole file.
- The usage indicator (`ISA15`) is the folder's: `T` in `TEST/IN`, `P` in `IN`. A file whose indicator is the other is refused whole.
- Up to 25,000 claims and 25 MB in a file. Split larger batches into several files.
- Give every file its own name. The responses are named after the file, so a file with the name of an earlier one writes over that file's responses. Dropping the very same file again changes nothing.
- A file is read after its upload has finished, never part-way, and is taken about every 60 seconds.
- The payer ID in each claim (`NM1*PR`) must be in the [payer directory](https://sandbox.myclaimhouse.com/docs/payers.md), and the payer must take claims. A claim for a payer that is not is refused.
- Your billing provider (`2010AA`) is matched to one of your [offices](https://sandbox.myclaimhouse.com/docs/api/listOffices.md) by NPI; by tax ID only when the file gives no NPI. An NPI that none of your offices has is a new location: an office is made from the file, never billed under another office's NPI. A rendering provider (`2310B`) is matched by NPI and made from the file when there is none; its license state is taken to be the billing office's (the file carries the number only). Offices and providers made from a file say `origin: "sftp"`.
- Primary claims only: a claim whose payer responsibility (`SBR01`) is not `P` is refused (`unsupported`). Coordination of benefits (loops `2320` and `2330`), `HI` diagnosis codes, `DN2` tooth status and line-level providers (`2420`) are not carried to the claim: they are left out, and the claim is made without them. A claim-level service date (`DTP*472` in `2300`) is used for every line that gives none.

A claim that cannot be made is refused on its own: the other claims in the file are still made. Its report line gives every reason found, not only the first. A file that is not a valid 837D, is too large, holds too many claims, or has the wrong usage indicator is refused whole, and nothing in it is made.

## What comes back and when

For every file it takes, Claim House writes three files to `OUT` (or `TEST/OUT`) once it has read the whole file, named after your file. A file of a few claims is answered within a minute or two; a large one is read at most about 500 claims a minute, so it is answered after several minutes, and every claim in it is answered then:

| File | What it is |
| --- | --- |
| `<name>.997` | A 997: your file's envelope was accepted, or rejected with the reasons. |
| `<name>.277` | A 277 with one status for each claim, in the order of the file. `TRN02` is your `CLM01` and `REF*D9` is the Claim House claim ID (`clm_...`). |
| `<name>.report.txt` | A plain-text report for people: the counts, then one line for each claim: `CLM01 \| result \| claim ID or - \| message`. |

A claim that was made is `A1 20` (accepted for processing). A claim that was made but needs attention, because its validation found a problem, is `A1 20` with the first finding in the status text; the claim waits in the dashboard until it is corrected (see [Claims](https://sandbox.myclaimhouse.com/docs/claims.md)). A refused claim is `A3`, with the reason in the status text and in the report:

| Reason | 277 status | What it means |
| --- | --- | --- |
| `payer_unknown` | `A3 33` | No payer in the [payer directory](https://sandbox.myclaimhouse.com/docs/payers.md) has the payer ID in the claim (NM1*PR). Use a payer ID from the directory. |
| `payer_no_claims` | `A3 21` | The payer is in the directory but does not take claims through Claim House. |
| `control_number` | `A3 21` | The claim's control number (CLM01) is not one a claim can have, or CLM05 says the claim replaces or voids one and no claim of that mode has this number. |
| `duplicate` | `A3 88` | A claim with this control number (CLM01) exists already. Send CLM05 7 to replace it or 8 to void it. |
| `attachment_unknown` | `A3 21` | A PWK names a Claim House attachment (att_...) that does not exist in this organization and mode. |
| `attachment_open` | `A3 21` | A PWK names a Claim House attachment that is not closed yet: close it, then send the claim again. |
| `claims_unavailable` | `A3 21` | Live claims are not available yet: nothing was sent. Drop test files in TEST/IN. |
| `unreadable` | `A3 21` | The claim could not be read from the file (a required segment is missing or a value cannot be used), or its billing provider or rendering provider could not be added from the file. The report says which. |
| `unsupported` | `A3 21` | A secondary or tertiary claim (SBR01 other than P): coordination of benefits (loops 2320 and 2330) is not carried yet, so only primary claims are sent. |
| `invalid` | `A3 21` | The claim was read, but sending it is refused: the API would refuse the same claim (a claim that cannot be replaced or voided in its state, for example). The report gives the reason. |

A file refused whole gets a 997 that rejects it and a report that says why, and no 277. The reason is one of:

| Reason | What it means |
| --- | --- |
| `too_large` | The file is larger than 25 MB: it was not read. Split it into smaller files. |
| `too_many_claims` | The file holds more than 25,000 claims: it was not read. Split it into smaller files. |
| `not_837d` | The file is not an 837D (005010X224A2) this service reads: its envelope is not valid. See the 997. |
| `wrong_usage` | The file's usage indicator (ISA15) is not its folder's: TEST/IN takes test files (T), IN takes production files (P). Nothing in it was read. |

Then, as the claims move, Claim House writes more files to the same `OUT` folder. In the sandbox these arrive on the compressed clock of [Claims](https://sandbox.myclaimhouse.com/docs/claims.md): the status about 60 seconds after the claims are sent and, for a claim of the `CH-PAID-ERA` scenario, an 835 about 2 minutes after that. The member ID in the claim chooses the scenario.

- `ClaimHouse-277.<YYYYMMDD>.<HHMMSS>.277`: a 277 with the statuses the network gave your claims, the category and status codes as it gave them, `TRN02` your control number, `REF*1K` the payer's claim number when it has given one. It holds only claims your files made. The network's own 997 of Claim House's file is not passed on: when it rejects the file your claims went out in, Claim House writes a `ClaimHouse-277.<YYYYMMDD>.<HHMMSS>.277` with `A3 21` for each of them, saying: The network rejected the file Claim House sent this claim in (its 997): the claim goes out again in the next batch.
- `ClaimHouse-835.<YYYYMMDD>.<HHMMSS>.835`: an 835 when a remittance pays or denies one of your claims. It is your organization's whole remittance as stored, with bank account numbers cut to their last four digits. If the same remittance also pays a claim you made with the API, that payment is in it too: it is your organization's own.

Claims made with the API or in the dashboard never produce files in `OUT`, so a partner that uses both never sees duplicates. If a file could not be written when the network's answer arrived, it is written on a later pass; it is never written twice. Each file taken, and each refusal of a whole file, also sends an event: see [File events](https://sandbox.myclaimhouse.com/docs/events-and-webhooks.md#file-events-from-sftp).

[`GET /sftp-account/files`](https://sandbox.myclaimhouse.com/docs/api/listSftpFiles.md) lists the files taken from your `IN` folders, newest first: how many claims each made and refused, and the names of the responses written. A key lists the files of its own mode: a test key lists `TEST/IN` files.

## Control numbers, replacements and voids

`CLM01` is the claim's patient control number: 1 to 20 letters, digits and hyphens. It is the one identifier that comes back on the 277 and on the 835 (`CLP01`), so use one your system can look up. A control number is unique in your organization, in test and live mode together.

`CLM05` carries the claim frequency in its third part, and Claim House reads it:

| `CLM05-3` | Meaning | What Claim House does |
| --- | --- | --- |
| `1` | An original claim | A claim is made. If a claim with this control number exists, the claim is refused as a duplicate (`A3 88`). |
| `7` | A replacement | The claim with this control number is corrected with this content and sent again as a replacement, by the same rules as [correcting a claim](https://sandbox.myclaimhouse.com/docs/claims.md). |
| `8` | A void | The claim with this control number is voided, by the same rules as [voiding a claim](https://sandbox.myclaimhouse.com/docs/api/voidClaim.md). |

A `7` or an `8` whose control number is not the number of a claim in that mode is refused. A replacement or a void that the claim's state does not allow, such as a claim that is queued and not yet sent, is refused with the reason in the report.

## Attachments

A `PWK` segment whose control number (`PWK06`) is the ID of one of your own [attachments](https://sandbox.myclaimhouse.com/docs/attachments.md) (`att_...`), in the same mode and closed, is swapped for the attachment's number on the claim Claim House sends, and linked to the claim. An `att_` ID that does not exist is refused, and so is one that is not closed yet: close it, then drop the claim again.

Any other `PWK` and its `NTE`, such as a control number from your own attachment account with the network, passes through to the claim unchanged.

## What the endpoints are

| Endpoint | Permission |
| --- | --- |
| [`GET /sftp-account`](https://sandbox.myclaimhouse.com/docs/api/getSftpAccount.md) | `read` |
| [`POST /sftp-account`](https://sandbox.myclaimhouse.com/docs/api/setUpSftpAccount.md) | `submit` |
| [`POST /sftp-account/rotate-password`](https://sandbox.myclaimhouse.com/docs/api/rotateSftpPassword.md) | `submit` |
| [`POST /sftp-account/keys`](https://sandbox.myclaimhouse.com/docs/api/addSftpKey.md) | `submit` |
| [`DELETE /sftp-account/keys/{id}`](https://sandbox.myclaimhouse.com/docs/api/removeSftpKey.md) | `submit` |
| [`POST /sftp-account/disable`](https://sandbox.myclaimhouse.com/docs/api/disableSftpAccount.md) | `submit` |
| [`POST /sftp-account/enable`](https://sandbox.myclaimhouse.com/docs/api/enableSftpAccount.md) | `submit` |
| [`GET /sftp-account/files`](https://sandbox.myclaimhouse.com/docs/api/listSftpFiles.md) | `read` |

A key sees its own organization's account, and the files of its own mode. Every `POST` needs an `Idempotency-Key`: see [Errors, retries and idempotency](https://sandbox.myclaimhouse.com/docs/errors-and-idempotency.md).
