Metadata-Version: 2.4
Name: submitshield
Version: 0.1.0
Summary: Submit Shield de-identifying client SDK — strips PHI locally, validates remotely.
Author: Submit Shield
License: Proprietary — Submit Shield customer license
Keywords: 837p,claims,de-identification,fhir,healthcare,hipaa
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Healthcare Industry
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.6.0; extra == 'dev'
Description-Content-Type: text/markdown

# Submit Shield SDK

**De-identify claims locally. Validate remotely. PHI never leaves your network.**

The Submit Shield SDK is the client-side half of Submit Shield. It runs inside
your clinic's network, strips every HIPAA identifier out of each claim
before the claim leaves your machine, and posts only the de-identified
payload to the Submit Shield validation API. Verdicts come back and the SDK
re-hydrates them locally with your real patient context so your billers
see real names in their UI while Submit Shield servers only ever see
pseudonyms.

This is not a promise — it's an architectural property. The SDK writes an
append-only SHA-256 attestation log on every call proving which bytes
went out, and those bytes contain no PHI by construction. Your HIPAA
officer can audit the log without ever sharing a single patient record.

## Why this exists

Traditional claim scrubbers (Waystar, Optum, FinThrive, Availity) all
operate under a Business Associate Agreement because they handle raw
PHI. That means every customer signs a BAA, every vendor passes PHI to
every downstream system, and every breach notification covers everyone.

Submit Shield instead chose to *never receive PHI in the first place*.
Under 45 CFR §164.514(b) (HIPAA Safe Harbor), health information with
the 18 enumerated identifiers removed is not PHI and can be disclosed
without authorization. The Submit Shield SDK performs that removal on the
customer's side of the wire, so Submit Shield's servers are not a Business
Associate for any customer using it.

## Install

Pick whichever delivery mode fits your environment:

```bash
# Python (Linux, macOS, any Python 3.10+ environment)
pip install submitshield

# Docker (any OS with Docker)
docker pull submitshield/sdk:latest

# Windows (standalone .exe, no Python required)
# Download from https://submitshield.health/downloads/submitshield-setup.exe
```

## Configure

The SDK needs two things:

1. **The Submit Shield API URL** — `https://api.submitshield.health`
2. **A customer secret** — a stable random string (≥32 chars) that you
   generate once and keep in your infrastructure. This is the HMAC key
   used to pseudonymize member IDs. It never leaves your network.

```bash
# Linux / macOS
export SUBMITSHIELD_API_URL=https://api.submitshield.health
export SUBMITSHIELD_CUSTOMER_SECRET="$(openssl rand -hex 32)"
echo "$SUBMITSHIELD_CUSTOMER_SECRET" > /secure/submitshield.secret

# Windows (PowerShell, as administrator)
[System.Environment]::SetEnvironmentVariable('SUBMITSHIELD_API_URL', 'https://api.submitshield.health', 'Machine')
# For the secret, generate with:
[System.Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 }))
```

## Validate a claim

```bash
# CSV
submitshield validate patient_claims.csv

# FHIR R4 Bundle
submitshield validate patient_claims.fhir.json

# Canonical Claim JSON
submitshield validate claim.json

# Multiple files at once
submitshield validate *.csv --output verdicts.json
```

## Watch a folder

Drop claim files into a folder and let the SDK process them automatically:

```bash
submitshield watch /var/spool/submitshield/inbox

# Or with the Docker image
docker run -d --name submitshield \
  -e SUBMITSHIELD_API_URL=https://api.submitshield.health \
  -e SUBMITSHIELD_CUSTOMER_SECRET="$(cat /secure/submitshield.secret)" \
  -v /var/spool/submitshield/inbox:/inbox \
  -v /var/spool/submitshield/verdicts:/outbox \
  -v /var/submitshield:/var/submitshield \
  submitshield/sdk:latest
```

## Inspect the attestation log

Every de-identification call is logged with two SHA-256 hashes — one
over the input, one over the de-identified payload. The log is
append-only JSONL so it's compatible with every log-rotation tool.

```bash
# Last 20 entries
submitshield attest --last 20

# Raw log (feed it to your SIEM)
cat $SUBMITSHIELD_ATTESTATION_LOG
```

A typical record:

```json
{
  "timestamp": "2026-04-11T19:04:32.118203+00:00",
  "sdk_version": "0.1.0",
  "hostname": "clinic-workstation-3",
  "pid": 4812,
  "format": "csv_row",
  "original_sha256": "a1b2c3d4e5f60708...",
  "deidentified_sha256": "9f8e7d6c5b4a3928...",
  "pseudonym_count": 1,
  "stripped_fields": [
    "patient_first_name",
    "patient_last_name",
    "patient_zip",
    "patient_dob"
  ],
  "server_url": "https://api.submitshield.health",
  "server_request_id": "req_abc123"
}
```

## What the SDK strips

Safe Harbor requires removing 18 identifiers. The SDK handles each one:

| # | Identifier | How we handle it |
|---|---|---|
| 1 | Names | Replaced with `REDACTED` |
| 2 | Geographic subdivisions smaller than state | Street/city/zip dropped; state retained for MAC routing |
| 3 | Dates (except year) related to the individual | DOB reduced to `{year}-01-01`; DoS optionally shifted |
| 4 | Phone numbers | Stripped |
| 5 | Fax numbers | Stripped |
| 6 | Email addresses | Stripped |
| 7 | SSN | Stripped |
| 8 | Medical record numbers | HMAC pseudonym |
| 9 | Health plan beneficiary / member numbers | HMAC pseudonym |
| 10 | Account numbers | HMAC pseudonym |
| 11 | Certificate / license numbers | Stripped |
| 12 | Vehicle identifiers | Stripped |
| 13 | Device identifiers / serial numbers | Stripped |
| 14 | URLs | Stripped |
| 15 | IP addresses | Stripped |
| 16 | Biometric identifiers | Not present in claim data |
| 17 | Full-face photographs | Not present in claim data |
| 18 | Any other unique identifying number | HMAC if it looks like an identifier column |

Provider NPIs are **not** stripped. NPIs are public data (NPPES is the
CMS public registry) and are not HIPAA identifiers for the *patient*.

## Remote updates

The SDK checks `GET {api_url}/sdk/version` on startup (cached 1 hour) and:

- **Same version**: silent.
- **Newer available**: prints an upgrade notice; the call proceeds.
- **Current version below minimum supported**: refuses to run. This is
  the kill-switch for critical privacy fixes.

To force an upgrade from the server side:

```bash
# Publish a new SDK release
hatch build && hatch publish
docker buildx build -t submitshield/sdk:0.1.1 -t submitshield/sdk:latest --push .

# Then update the API's environment
SUBMITSHIELD_SDK_LATEST_VERSION=0.1.1
SUBMITSHIELD_SDK_MIN_SUPPORTED_VERSION=0.1.1   # forces every client to upgrade
```

Any customer whose SDK hits `/sdk/version` after this will get the
kill-switch response on their next call.

## Questions

- **"What if my internet is down?"** The update check is best-effort;
  a network failure during the check does not block validation. Your
  SDK will keep running with the last successfully-fetched version
  metadata until connectivity returns.
- **"Can I use this under a BAA if I want to?"** Yes. Set
  `SUBMITSHIELD_DEIDENTIFY=false` and the SDK passes PHI through
  unmodified. Your Submit Shield account must be on the BAA-tier plan
  for this to be accepted on the server side.
- **"Can I self-host the API?"** Yes, at the Enterprise tier. See
  https://submitshield.health/enterprise.
- **"What happens if the Submit Shield API is down?"** The SDK fails fast
  with a clear error message. The raw claim file is untouched — you
  can re-run when the API is back.
- **"How do you prove my PHI never left my network?"** The attestation
  log carries the hash of the original input and the hash of the
  outgoing payload. An auditor can hash any PHI field that was present
  in the original and confirm that hash appears only in the
  `original_sha256` column and never in the `deidentified_sha256`
  column or the API request logs.

## License

Proprietary — Submit Shield customer license. See `docs/sdk_license.txt`.
