> This page is for API.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.spreadspace.app/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.spreadspace.app/_mcp/server.

# List all paystubs

GET https://api.spreadspace.app/api/borrowers/{borrower_id}/extractions/paystubs

Lists every usable paystub on the borrower's file. Duplicates and failed extractions are left out, and you can pass `loan_id` to narrow the list to one loan.

Requires the `extractions:read` scope. Rate limit: 200 requests per minute per key (the `api` lane).

Reference: https://docs.spreadspace.app/api/api-reference/paystubs/get-paystubs

## Authentication

- `Authorization` header (bearer token, required) — Bearer token. Use `ss_live_…` for live data or `ss_test_…` for the sandbox (test mode). See [Authentication](https://docs.spreadspace.app/api/authentication).

## Request

### Path parameters

- `borrower_id` (string, required) — The borrower's id.

### Query parameters

- `loan_id` (string, optional) — Narrow the list to one loan.

### Headers

- `SpreadSpace-Version` (string, optional) — Pin the API version, e.g. `2026-10-07`. Omit to get the latest. See [Versioning](https://docs.spreadspace.app/api/versioning).

## Response

### 200

OK

- `borrower_id` (string, required) — The borrower the documents belong to.
- `paystubs` (list of PaystubReportDataListItem, required) — One row per document.

## Errors

### 400 Bad Request Error

Bad request: validation failed, the cursor is malformed, or the API version is unknown.

- `error` (ApiErrorBody, required) — Inner body of the canonical API error envelope.

### 401 Unauthorized Error

Unauthorized: the bearer token or API key is missing or invalid.

- `error` (ApiErrorBody, required) — Inner body of the canonical API error envelope.

### 402 Payment Required Error

Payment required: the standard error envelope with `type` `subscription_required` when the workspace has no active or trialing subscription, or `billing_required` where a plan entitlement applies.

- `error` (ApiErrorBody, required) — Inner body of the canonical API error envelope.

### 403 Forbidden Error

Forbidden: authentication succeeded but the caller is not permitted to perform this action, for example an API key missing the required scope, an embed session reaching a loan it was not minted for, or a feature outside the workspace's plan.

- `error` (ApiErrorBody, required) — Inner body of the canonical API error envelope.

### 404 Not Found Error

Not found: the resource does not exist or is not visible to the calling tenant.

- `error` (ApiErrorBody, required) — Inner body of the canonical API error envelope.

### 429 Too Many Requests Error

Too many requests: the rate limit was hit, or an idempotency-key replay is still in progress.

- `error` (ApiErrorBody, required) — Inner body of the canonical API error envelope.

### 500 Internal Server Error

Internal server error: an unexpected failure. Quote the `request_id` in support tickets. A mutation sent with an `Idempotency-Key` replays this fault under that key rather than re-running; confirm the resource state before resending with a new key.

- `error` (ApiErrorBody, required) — Inner body of the canonical API error envelope.

## Types

### PaystubReportDataListItem

One row of the Paystub list: the document's identity and its extracted object.

- `extracted_document_id` (string, required) — Identifier of the extracted document.
- `document_category` (string, required) — The document category the extraction is stored under.
- `report_data` (PaystubReportData, required) — The extracted object, in the family's shape.

### ApiErrorBody

Inner body of the canonical API error envelope.

- `type` (string, required) — Stable error type identifier. Pattern-match on this value, not the human-readable message.
- `message` (string, required) — Human-readable error message. Subject to wording changes; do not parse.
- `request_id` (string, optional, nullable) — Per-request correlation ID (matches `X-Request-ID` response header). Quote in support tickets.
- `details` (map from string to string, optional, nullable) — Structured detail. Some error types populate documented keys in every environment: a version conflict (409) carries `current_version`, the version the resource is actually at, so the client can reload and re-apply. Free-form diagnostic detail beyond those keys appears in development environments only. An `insufficient_credits` 402 carries `balance_usd`, `reserved_usd`, `in_flight_docs`, `incoming_docs` and `per_doc_reservation_usd` as decimal strings.

### PaystubReportData

A paystub: one employer's statement to one employee of one pay period's gross pay, deductions and net pay, this period and year to date, with the employee's Social Security number reduced to its last four digits. Money is a decimal string with two decimals.

- `employer_name` (string, required, nullable) — The employer's name.
- `employer_address` (string, required, nullable) — The employer's address on one line: street, city, state and postal code.
- `employee_name` (string, required, nullable) — The employee's name.
- `employee_address` (string, required, nullable) — The employee's address on one line: street, city, state and postal code.
- `employee_ssn_last4` (string, required, nullable) — The last four digits of the employee's social security number; the rest of the number is never served.
- `marital_status` (string, required, nullable) — The employee's marital status for tax withholding, lower case as stated.
- `pay_date` (string, required, nullable) — The date the pay was issued, as YYYY-MM-DD.
- `period_start` (string, required, nullable) — The first day of the pay period, as YYYY-MM-DD.
- `period_end` (string, required, nullable) — The last day of the pay period, as YYYY-MM-DD.
- `pay_frequency` (string, required, nullable) — How often the employee is paid: weekly, biweekly, semimonthly, monthly, quarterly, annual, or unknown when the frequency stated is none of these.
- `pay_basis` (string, required, nullable) — How the employee is paid, for example salary or hourly, lower case as stated.
- `rate_of_pay_amount` (string, required, nullable) — The employee's rate of pay, in the unit rate_of_pay_basis names.
- `rate_of_pay_basis` (string, required, nullable) — The unit of the rate of pay, for example annual or hourly, lower case as stated.
- `gross_current` (string, required, nullable) — Gross pay for the pay period.
- `gross_ytd` (string, required, nullable) — Gross pay for the year to date.
- `net_current` (string, required, nullable) — Net pay for the pay period.
- `net_ytd` (string, required, nullable) — Net pay for the year to date.
- `deductions_total_current` (string, required, nullable) — Total deductions for the pay period.
- `deductions_total_ytd` (string, required, nullable) — Total deductions for the year to date.
- `hours_current` (string, required, nullable) — Hours worked in the pay period, as a decimal string.
- `earnings` (list of PaystubReportData_PaystubLine, required) — The earnings rows the stub lists, one per kind of pay, in the order stated.
- `deductions` (list of PaystubReportData_PaystubLine, required) — The deductions rows the stub lists, one per tax or withholding, in the order stated.
- `distributions` (list of PaystubReportData_PaystubDistribution, required) — Where the net pay was deposited, one row per receiving account.
- `status` ("EXTRACTED", required) — Always `EXTRACTED` on a paystub the API returns.
- `signals` (list of PaystubReportData_PaystubSignal, required) — Notes about the paystub; empty when there are none.

### PaystubReportData_PaystubLine

One earnings or deductions row of a paystub.

- `description` (string, required, nullable) — The row's label as stated.
- `kind` (string, required, nullable) — The row's standard kind when the payroll provider states one, for example `regular_pay`, `overtime` or `bonus`; null otherwise.
- `hours` (string, required, nullable) — Hours for the row in the pay period, as a decimal string, when stated.
- `rate` (string, required, nullable) — The hourly rate for the row, when stated.
- `current_amount` (string, required, nullable) — The row's amount for the pay period.
- `ytd_amount` (string, required, nullable) — The row's amount for the year to date.

### PaystubReportData_PaystubDistribution

One deposit of the net pay.

- `bank_name` (string, required, nullable) — The receiving bank's name.
- `account_name` (string, required, nullable) — The receiving account's name.
- `account_type` (string, required, nullable) — The receiving account's type, for example checking or savings.
- `account_last4` (string, required, nullable) — The last four digits of the receiving account number; the rest of the number is never served.
- `amount` (string, required, nullable) — The amount deposited to the account.

### PaystubReportData_PaystubSignal

A note about the paystub.

- `severity` (enum, required) — How much weight the note carries: info or warn.
  - Allowed values: `info`, `warn`
- `code` (string, required) — The note's identifier.
- `detail` (string, required) — The note in words.

## Examples

**Response**

```json
{
  "borrower_id": "3f9c2b1e8d7a4c5b9e0f1a2b",
  "paystubs": [
    {
      "extracted_document_id": "7a1d4e9c2b3f4a5d8e6c1b0f",
      "document_category": "paystub",
      "report_data": {
        "employer_name": "Northgate Logistics Inc",
        "employer_address": "1500 Industrial Pkwy, Portland, ME 04103",
        "employee_name": "Dana Whitfield",
        "employee_address": "88 Spruce Hollow Rd, Gorham, ME 04038",
        "employee_ssn_last4": "4471",
        "marital_status": "single",
        "pay_date": "2026-09-11",
        "period_start": "2026-08-29",
        "period_end": "2026-09-11",
        "pay_frequency": "biweekly",
        "pay_basis": "salary",
        "rate_of_pay_amount": "84500.00",
        "rate_of_pay_basis": "annual",
        "gross_current": "3250.00",
        "gross_ytd": "61750.00",
        "net_current": "2431.18",
        "net_ytd": "46192.42",
        "deductions_total_current": "818.82",
        "deductions_total_ytd": "15557.58",
        "hours_current": "80.00",
        "earnings": [
          {
            "description": "Regular Salary",
            "kind": "regular_pay",
            "hours": "80.00",
            "rate": "40.62",
            "current_amount": "3250.00",
            "ytd_amount": "61750.00"
          }
        ],
        "deductions": [
          {
            "description": "Federal Income Tax",
            "kind": null,
            "hours": null,
            "rate": null,
            "current_amount": "412.60",
            "ytd_amount": "7839.40"
          },
          {
            "description": "Social Security",
            "kind": null,
            "hours": null,
            "rate": null,
            "current_amount": "201.50",
            "ytd_amount": "3828.50"
          },
          {
            "description": "Medicare",
            "kind": null,
            "hours": null,
            "rate": null,
            "current_amount": "47.13",
            "ytd_amount": "895.47"
          },
          {
            "description": "ME State Income Tax",
            "kind": null,
            "hours": null,
            "rate": null,
            "current_amount": "157.59",
            "ytd_amount": "2994.21"
          }
        ],
        "distributions": [
          {
            "bank_name": "Pinecrest Community Bank",
            "account_name": "Everyday Checking",
            "account_type": "checking",
            "account_last4": "3310",
            "amount": "2431.18"
          }
        ],
        "status": "EXTRACTED",
        "signals": []
      }
    }
  ]
}
```

**SDK Code**

```python
import requests

url = "https://api.spreadspace.app/api/borrowers/3f9c2b1e8d7a4c5b9e0f1a2b/extractions/paystubs"

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.spreadspace.app/api/borrowers/3f9c2b1e8d7a4c5b9e0f1a2b/extractions/paystubs';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.spreadspace.app/api/borrowers/3f9c2b1e8d7a4c5b9e0f1a2b/extractions/paystubs"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.spreadspace.app/api/borrowers/3f9c2b1e8d7a4c5b9e0f1a2b/extractions/paystubs")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.spreadspace.app/api/borrowers/3f9c2b1e8d7a4c5b9e0f1a2b/extractions/paystubs")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.spreadspace.app/api/borrowers/3f9c2b1e8d7a4c5b9e0f1a2b/extractions/paystubs', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.spreadspace.app/api/borrowers/3f9c2b1e8d7a4c5b9e0f1a2b/extractions/paystubs");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.spreadspace.app/api/borrowers/3f9c2b1e8d7a4c5b9e0f1a2b/extractions/paystubs")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```