# SBA PPP Loan Data (FOIA) (`knotty_mistveil/sba-ppp-loan-data`) Actor

SBA Paycheck Protection Program (PPP) FOIA loan data (2020-2021) as flat JSON, filtered by state, NAICS code, borrower keyword and loan amount.

- **URL**: https://apify.com/knotty_mistveil/sba-ppp-loan-data.md
- **Developed by:** [Jason](https://apify.com/knotty_mistveil) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 1,000 result returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## SBA PPP Loan Data (FOIA)

Get U.S. Small Business Administration Paycheck Protection Program (PPP) loan records as clean, flat JSON: borrower name and address, lender, loan amount, jobs reported, NAICS industry code, approval and forgiveness dates. The data comes straight from the SBA's own FOIA release of the full PPP loan book (all loans made 2020-2021), published as 13 static CSV files on data.sba.gov. Filter by state, NAICS industry prefix, borrower-name keyword and loan amount, and pay only for the records you receive. No API key, no login, no browser automation.

**This is a static 2020-2021 snapshot, not a live feed**, and **there is no server-side filter**: SBA publishes the whole program as 13 fixed files (one for loans over $150,000, twelve for loans of $150,000 and under) with no query API at all. Every run streams the file(s) you pick in `tier` row by row and applies your filters locally — see "Limits and freshness" below for exactly what that means for coverage before you rely on this for a complete answer.

### What you get

One record per PPP loan. All 53 columns SBA publishes are kept, normalised to camelCase with dates as ISO strings and amounts/counts as numbers:

| Field | Description |
| --- | --- |
| `id` / `loanNumber` | SBA's own `LoanNumber` (unique within each published file; used as-is) |
| `source` | `sba-ppp-foia`, constant |
| `url` | always `null` — SBA does not publish a per-loan web page (see below) |
| `tier` | which of the 13 source files this record came from, e.g. `up_to_150k_12` |
| `dateApproved`, `loanStatusDate`, `forgivenessDate` | ISO `YYYY-MM-DD` (SBA publishes `MM/DD/YYYY`) |
| `borrowerName`, `borrowerAddress`, `borrowerCity`, `borrowerState`, `borrowerZip` | the borrowing business (see the personal-data note below) |
| `loanStatus`, `term`, `sbaGuarantyPercentage` | loan status, term in months, SBA guaranty % |
| `initialApprovalAmount`, `currentApprovalAmount`, `undisbursedAmount` | numbers, in USD |
| `franchiseName`, `businessType`, `businessAgeDescription`, `jobsReported`, `naicsCode`, `race`, `ethnicity`, `gender`, `veteran`, `nonProfit` | borrower characteristics as self-reported on the loan application |
| `utilitiesProceed`, `payrollProceed`, `mortgageInterestProceed`, `rentProceed`, `refinanceEidlProceed`, `healthCareProceed`, `debtInterestProceed` | planned use of proceeds by category, numbers or `null` |
| `servicingLenderName`, `servicingLenderAddress`, `servicingLenderCity`, `servicingLenderState`, `servicingLenderZip` | the lender currently servicing the loan |
| `originatingLender`, `originatingLenderCity`, `originatingLenderState` | the lender that originated the loan |
| `projectCity`, `projectCountyName`, `projectState`, `projectZip`, `congressionalDistrict` | where the funded business operates |
| `forgivenessAmount` | numbers or `null` |
| `fetchedAt` | ISO timestamp of the run |

The run also writes a `SUMMARY` record to the key-value store: which tier/file(s) were scanned, rows scanned vs. matched, and a `complete` flag (true only when every file named by `tier` was read all the way to its end and every matching record was delivered — see "Limits and freshness").

**No per-loan page exists.** SBA's FOIA release is CSV-only; there is no public SBA web page for an individual PPP loan, so `url` is always `null`. The dataset's own page is `https://data.sba.gov/dataset/ppp-foia`.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `tier` | string (select) | `up_to_150k_12` | Which of the 13 static files to scan, or `all` for every file in sequence. Default is the **smallest** file (measured 2026-10-05) so the Store "Try it" button runs fast and cheap. See the full 13-file manifest below. |
| `state` | string\[] | `[]` | USPS 2-letter codes (e.g. `CA`, `NY`). Matches if **either** `borrowerState` **or** `projectState` is in the list (they occasionally differ — a franchise borrower registered in one state can run a project in another). |
| `naicsPrefix` | string\[] | `[]` | Prefix match against `naicsCode`, e.g. `"72"` for Accommodation and Food Services, `"6221"` for General Medical and Surgical Hospitals. |
| `keywords` | string\[] | `[]` | Case-insensitive OR match against `borrowerName` only (not address or lender). |
| `minLoanAmount` / `maxLoanAmount` | integer (USD) | none | Range filter against `initialApprovalAmount` — the amount approved at origination, not `currentApprovalAmount`, which can later be reduced by a forgiveness adjustment or partial payoff. |
| `maxItems` | integer | `100` | Stop after this many matching loans (in file order, not sorted by anything). You are charged per result delivered. Kept small by default: this is a bulk lead list over a multi-hundred-MB source with no server-side filter, so a large `maxItems` with no other filter can mean scanning most or all of a 270-450 MB file (or all 13, with `tier: "all"`) for a correspondingly large bill. |

Example: food-service businesses in Texas with a loan of at least $50,000:

```json
{
  "tier": "all",
  "state": ["TX"],
  "naicsPrefix": ["72"],
  "minLoanAmount": 50000,
  "maxItems": 500
}
```

### Output example

A real record from a run on 2026-10-05, default input (`tier: "up_to_150k_12"`, no filters, `maxItems: 5`):

```json
{
  "id": "8094857009",
  "source": "sba-ppp-foia",
  "url": null,
  "tier": "up_to_150k_12",
  "loanNumber": "8094857009",
  "dateApproved": "2020-04-08",
  "sbaOfficeCode": "0304",
  "processingMethod": "PPP",
  "borrowerName": "P & R ASSOCIATES LLC",
  "borrowerAddress": "1160 S Providence Road",
  "borrowerCity": "RICHMOND",
  "borrowerState": "VA",
  "borrowerZip": "23236-2646",
  "loanStatusDate": "2021-01-20",
  "loanStatus": "Paid in Full",
  "term": 24,
  "sbaGuarantyPercentage": 100,
  "initialApprovalAmount": 84500,
  "currentApprovalAmount": 84500,
  "undisbursedAmount": 0,
  "franchiseName": null,
  "servicingLenderLocationId": "74135",
  "servicingLenderName": "Citizens and Farmers Bank",
  "servicingLenderAddress": "415 14th St",
  "servicingLenderCity": "WEST POINT",
  "servicingLenderState": "VA",
  "servicingLenderZip": "23181",
  "ruralUrbanIndicator": "U",
  "hubzoneIndicator": false,
  "lmiIndicator": false,
  "businessAgeDescription": "Unanswered",
  "projectCity": "RICHMOND",
  "projectCountyName": "CHESTERFIELD",
  "projectState": "VA",
  "projectZip": "23236-2646",
  "congressionalDistrict": "VA-04",
  "jobsReported": 40,
  "naicsCode": "713910",
  "race": "Unanswered",
  "ethnicity": "Unknown/NotStated",
  "utilitiesProceed": null,
  "payrollProceed": 84500,
  "mortgageInterestProceed": null,
  "rentProceed": null,
  "refinanceEidlProceed": null,
  "healthCareProceed": null,
  "debtInterestProceed": null,
  "businessType": "Limited  Liability Company(LLC)",
  "originatingLenderLocationId": "74135",
  "originatingLender": "Citizens and Farmers Bank",
  "originatingLenderCity": "WEST POINT",
  "originatingLenderState": "VA",
  "gender": "Unanswered",
  "veteran": "Unanswered",
  "nonProfit": false,
  "forgivenessAmount": 85048.67,
  "forgivenessDate": "2020-12-14",
  "fetchedAt": "2026-10-05T02:54:22.906Z"
}
```

### Pricing

Pay per result: **US$0.0015 per record, i.e. US$1.50 per 1,000 results.** You are charged only for records actually delivered to the dataset (nothing for empty runs), and the run stops automatically when your Apify spend limit is reached. Apify's free plan limits how much you can run per month; see the Apify pricing page for current limits.

### Data source and licence

- Dataset: [PPP FOIA — SBA Paycheck Protection Program loan-level data](https://data.sba.gov/dataset/ppp-foia), published by the U.S. Small Business Administration on data.sba.gov.
- Access: 13 static HTTPS CSV downloads, no API, no key. Full file list (sizes measured by HTTP `HEAD` request on **2026-10-05**; SBA republishes these files in place, so re-measure before relying on an exact byte count):

  | Tier id | File | Size (measured 2026-10-05) |
  | --- | --- | --- |
  | `150k_plus` | `public_150k_plus_240930.csv` | 452,077,279 bytes (452.1 MB) |
  | `up_to_150k_1` | `public_up_to_150k_1_240930.csv` | 413,938,705 bytes (413.9 MB) |
  | `up_to_150k_2` | `public_up_to_150k_2_240930.csv` | 412,495,612 bytes (412.5 MB) |
  | `up_to_150k_3` | `public_up_to_150k_3_240930.csv` | 412,228,470 bytes (412.2 MB) |
  | `up_to_150k_4` | `public_up_to_150k_4_240930.csv` | 403,417,859 bytes (403.4 MB) |
  | `up_to_150k_5` | `public_up_to_150k_5_240930.csv` | 406,175,547 bytes (406.2 MB) |
  | `up_to_150k_6` | `public_up_to_150k_6_240930.csv` | 411,120,417 bytes (411.1 MB) |
  | `up_to_150k_7` | `public_up_to_150k_7_240930.csv` | 405,119,643 bytes (405.1 MB) |
  | `up_to_150k_8` | `public_up_to_150k_8_240930.csv` | 410,044,760 bytes (410.0 MB) |
  | `up_to_150k_9` | `public_up_to_150k_9_240930.csv` | 406,277,638 bytes (406.3 MB) |
  | `up_to_150k_10` | `public_up_to_150k_10_240930.csv` | 410,285,658 bytes (410.3 MB) |
  | `up_to_150k_11` | `public_up_to_150k_11_240930.csv` | 405,702,746 bytes (405.7 MB) |
  | `up_to_150k_12` | `public_up_to_150k_12_240930.csv` | **271,182,004 bytes (271.2 MB) — smallest, used as the default `tier`** |

  All 13 files together: **5,220,066,338 bytes (≈5.22 GB)** — larger than the ≈4.5 GB a prior shift's note estimated; use the table above, not that estimate. Every file shares the same 53-column header, confirmed by range-requesting the first ~2 MB of two different files on 2026-10-05.
- Licence: the dataset page's own "License" field reads exactly **"U.S. Government Works"**, linked to <https://www.usa.gov/publicdomain/label/1.0/> (re-checked 2026-10-05; also confirmed by a prior shift on 2026-09-14). This is U.S. federal government work, which carries no copyright in the United States under 17 U.S.C. § 105; the linked label describes it as free of known copyright restrictions worldwide. Commercial use, redistribution and modification are permitted; no attribution is legally required, though citing "SBA PPP FOIA data, data.sba.gov" is good practice.
- **Personal data.** `BorrowerName` and `BorrowerAddress` are the loan's officially registered borrower — for the many PPP loans made to a sole proprietorship or single-member LLC, that is routinely an individual's own name and home address, not a company address. SBA published this data lawfully under a FOIA release, but that publication does not by itself make every downstream use lawful. If you use these fields — marketing in particular — the obligations under the CAN-SPAM Act, TCPA, state privacy laws and this platform's own rules are yours, not the source's. **This repository's no-bulk-email rule applies regardless of what any buyer does with the output**: do not use this Actor, or encourage its use, to build unsolicited bulk email or call lists from these records. No email address or phone number is published in this dataset to begin with.

### Limits and freshness

- **This is a frozen 2020-2021 snapshot, not a feed.** Every `dateApproved` falls in 2020 or 2021 (the PPP program's lifetime). SBA periodically republishes the same 13 files with correction updates (the `_240930` suffix is SBA's own as-of date for the current release), but no new loans are ever added — there is nothing "recent" to track here, unlike most Actors in this collection. Schedule this Actor only if you want to pick up SBA's occasional data corrections, not for new data; monthly or less is more than enough.
- **Per-run scope is exactly what `tier` says, nothing more.** There is no server-side query, so a single run only ever reads the file(s) named in `tier`. **A default run does *not* return "all PPP loans"** — it returns up to `maxItems` matches from one ≈271-452 MB file. For nationwide coverage across every borrower, every state and every industry, set `tier: "all"`, which streams all 13 files in sequence (stopping once `maxItems` is reached or every file is exhausted) — expect that to take several minutes and to need a correspondingly higher `maxItems` and spend limit.
- **Throughput, measured on this machine on 2026-10-05, running the Actor's own code end to end** (fetch + streaming CSV parse + filter, no matches so the full file had to be read): the smallest file (`up_to_150k_12`, 271.2 MB, 599,686 rows) took **8.7 s**; the largest (`150k_plus`, 452.1 MB, 968,524 rows) took **12.5 s** — about 31-36 MB/s net of parsing. Extrapolating that rate across all 13 files (≈5.22 GB), `tier: "all"` with a filter that doesn't shortcut it would take roughly **2.5-3 minutes** on this machine; actual Apify runner bandwidth may differ, so treat this as an estimate, not a guarantee, and budget accordingly. A plain network download (`curl`, no parsing) of the same two files took 2.9 s and 4.1 s respectively on this machine, so most of the Actor's time is JavaScript CSV parsing and filtering, not the network.
- **Completeness signal.** The `SUMMARY` record's `complete` field is `true` only when every file named by `tier` was read to its own end (not stopped early by `maxItems` or a spend limit) and every matching record found was actually delivered. `filesFullyScanned` lists which of the planned tier(s) were read to completion. If `complete` is `false`, raise `maxItems` (or your spend limit) and run again, or narrow your filters so fewer rows match before the cap is hit.
- **Record order.** Records are delivered in the order SBA lists them in the source file(s) — not sorted by date, amount or name.
- **Field quirks inherited from the source.** A small number of `FranchiseName`/`BusinessType` values in SBA's own export contain a stray non-UTF-8 byte (observed in sampled data on 2026-10-05); Node's UTF-8 decoder renders it as the Unicode replacement character (�) rather than crashing, exactly as SBA published it.
- **Rate limits.** data.sba.gov has no documented API rate limit (it is a static file server, not an API); the Actor retries on 429/5xx with backoff via this repository's shared `fetchWithRetry` regardless.

### Use cases

- **Lead generation by industry and geography:** pull every PPP borrower in a NAICS sector and state (e.g. `naicsPrefix: ["72"]`, `state: ["TX"]`) to build a prospect list of established small businesses.
- **Lender and competitor research:** filter by `keywords` on borrower name, or cross-reference `servicingLenderName`/`originatingLender` in the delivered JSON, to see which lenders were active in a market.
- **Academic and journalistic analysis:** export a clean, typed JSON extract (amounts as numbers, dates as ISO) of a slice of the program for a data pipeline, without hand-parsing 400+ MB CSV files.

### Support

Found a problem or need another field or filter? Open an issue on the Actor's Issues tab on Apify Store. Issues are answered within 14 days.

# Actor input Schema

## `tier` (type: `string`):

SBA publishes PPP loans as 13 separate static CSV files with no server-side filter: one for loans over $150,000, and twelve files of roughly equal size for loans of $150,000 and under. Each run scans only the file(s) you pick here. 'All tiers' streams all 13 in sequence until maxItems is reached or every file is exhausted - nationwide coverage, but it reads up to ~5.2 GB (measured 2026-10-05) so give it a generous maxItems and expect it to take several minutes.

## `state` (type: `array`):

USPS state codes (e.g. CA, NY, TX). Matches if EITHER the borrower's state OR the project's state is in this list (they occasionally differ). Empty = every state.

## `naicsPrefix` (type: `array`):

Keep only loans whose 6-digit NAICSCode starts with one of these prefixes, e.g. "72" for Accommodation and Food Services, "6221" for General Medical and Surgical Hospitals. Empty = every industry.

## `keywords` (type: `array`):

Case-insensitive OR match against BorrowerName, e.g. "restaurant", "salon". Empty = every borrower name.

## `minLoanAmount` (type: `integer`):

Keep only loans with InitialApprovalAmount at or above this value. Empty = no lower bound.

## `maxLoanAmount` (type: `integer`):

Keep only loans with InitialApprovalAmount at or below this value. Empty = no upper bound.

## `maxItems` (type: `integer`):

Stop after this many matching loans (in file order, not sorted). You are charged per result delivered, so this is also the run's cost cap. Kept small by default because this is a bulk lead list over a multi-hundred-MB source with no server-side filter: a large maxItems with no state/NAICS/keyword/amount filter can mean scanning most or all of a file (or all 13, with tier "all") for a correspondingly large bill.

## Actor input object example

```json
{
  "tier": "up_to_150k_12",
  "state": [],
  "naicsPrefix": [],
  "keywords": [],
  "maxItems": 100
}
```

# Actor output Schema

## `results` (type: `string`):

All loan records returned by the run, one JSON object each. Append ?format=csv or ?format=xlsx to download a spreadsheet.

## `summary` (type: `string`):

Counts, which tier/file(s) were scanned, rows scanned/matched, and the filesFullyScanned/complete completeness signal, written at the end of the run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "tier": "up_to_150k_12",
    "state": [],
    "naicsPrefix": [],
    "keywords": [],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("knotty_mistveil/sba-ppp-loan-data").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "tier": "up_to_150k_12",
    "state": [],
    "naicsPrefix": [],
    "keywords": [],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("knotty_mistveil/sba-ppp-loan-data").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "tier": "up_to_150k_12",
  "state": [],
  "naicsPrefix": [],
  "keywords": [],
  "maxItems": 100
}' |
apify call knotty_mistveil/sba-ppp-loan-data --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,knotty_mistveil/sba-ppp-loan-data"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Rrx7U3NBtD6H3QD2h/builds/Is3LkGlzvMflDdUQq/openapi.json
