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

# Give me a spreadsheet

> Page a list to the end and write the rows to CSV yourself, with the columns to keep.

Every list on `api.investorlift.com` is JSON, paged with a cursor. A spreadsheet is a loop over those pages. Ask for
the largest page your plan allows. Write the rows. Follow `page.next_cursor` until it is null. This request gets every
flip and wholesale deal bought since August 2024 within two miles of a house in Scottsdale, newest first:

<CodeGroup>
  ```bash curl theme={null}
  curl "https://api.investorlift.com/v1/deals?lat=33.476917&lng=-111.920385&radius_miles=2&kind=flip,wholesale&bought_after=2024-08-12&sort=date_desc&limit=100" \
    -H "Authorization: Bearer $GM_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const params = new URLSearchParams({
    lat: "33.476917",
    lng: "-111.920385",
    radius_miles: "2",
    kind: "flip,wholesale",
    bought_after: "2024-08-12",
    sort: "date_desc",
    limit: "100",
  });
  const res = await fetch(`https://api.investorlift.com/v1/deals?${params}`, {
    headers: { Authorization: `Bearer ${process.env.GM_API_KEY}` },
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  const body = await res.json();
  ```

  ```python Python theme={null}
  import os

  import requests

  r = requests.get(
      "https://api.investorlift.com/v1/deals",
      params={
          "lat": 33.476917,
          "lng": -111.920385,
          "radius_miles": 2,
          "kind": "flip,wholesale",
          "bought_after": "2024-08-12",
          "sort": "date_desc",
          "limit": 100,
      },
      headers={"Authorization": f"Bearer {os.environ['GM_API_KEY']}"},
      timeout=30,
  )
  r.raise_for_status()
  body = r.json()
  ```
</CodeGroup>

This is the same loop in Python. It flattens the nested blocks into the columns a spreadsheet wants and writes them as
it goes:

```python theme={null}
import csv
import os

import requests

URL = "https://api.investorlift.com/v1/deals"
HEADERS = {"Authorization": f"Bearer {os.environ['GM_API_KEY']}"}
params = {"lat": 33.476917, "lng": -111.920385, "radius_miles": 2, "kind": "flip,wholesale", "bought_after": "2024-08-12", "sort": "date_desc", "limit": 100}
columns = ["id", "kind", "address", "city", "zip", "bought_on", "bought_price", "sold_on", "sold_price", "gross_profit", "investor"]

with open("deals.csv", "w", newline="") as f:
    out = csv.DictWriter(f, fieldnames=columns)
    out.writeheader()
    while True:
        r = requests.get(URL, params=params, headers=HEADERS, timeout=30)
        r.raise_for_status()
        body = r.json()
        for d in body["data"]:
            out.writerow({
                "id": d["id"], "kind": d["kind"],
                "address": d["property"]["address_short"], "city": d["property"]["city"], "zip": d["property"]["zip"],
                "bought_on": d["bought_on"], "bought_price": d["bought_price"],
                "sold_on": d["sold_on"], "sold_price": d["sold_price"], "gross_profit": d["gross_profit"],
                "investor": d["investor"]["name"] if d["investor"] else None,
            })
        cursor = body["page"]["next_cursor"]
        if cursor is None:
            break
        params["cursor"] = cursor
```

## What to know

* **The page size is your plan's.** The API lowers a `limit` above the plan's largest to that largest. It does not
  refuse it. `page.limit` says what the API used. [Plans and limits](/guides/plans-and-limits) has the number per plan.
* **Every new deal is a credit.** Each deal the export gets for the first time costs one credit. A deal your account
  already holds costs nothing again while the plan has credit. `X-Rows` is the size of the body, not a charge, and
  `X-Credits-Charged` is what the page cost. Narrow the geometry and the filters before you page. Two years of flips
  and wholesale deals within two miles is hundreds of rows, and the whole market is hundreds of thousands
  ([Plans and limits](/guides/plans-and-limits)).
* **A data refresh invalidates the cursor.** A `400 invalid_cursor` in the middle of a long export means a refresh
  rebuilt the tables. Start again from page 1, and keep `meta.coverage[].dataset_version` beside the file so you know
  which build it came from ([Pagination](/guides/concepts/pagination)).
* **A null price is an answer.** `bought_price` and `sold_price` are `null` when the deed carries none. This is common
  on Texas off-market deeds. Write the null. Do not turn it into a zero.
* **Names are entities.** `investor.name` is the LLC or trust. A buyer with no investor id (usually a household) has a
  null name. The API does not serve the people behind an entity on this host ([Authentication](/guides/concepts/authentication)).

## Partners and staff

Staff and contracted partners reach the internal host on the company network with a `gm_` key. On that host, nine
endpoints also answer `Accept: text/csv`. They stream the whole filtered set as a file and ignore pagination. The nine
are `/v1/deals`, `/v1/investors`, `/v1/investors/{id}/deals`, `/v1/buyers/match`, `/v1/wholesale-listings`,
`/v1/investors/{id}/wholesale-purchases`, `/v1/wholesalers/{id}/listings`, `/v1/str-parcels` and
`/v1/lenders/{id}/loans`.

The cap is 50,000 rows. Above it, the API answers [`422 csv_cap_exceeded`](/guides/concepts/errors#csv_cap_exceeded)
with the cap in the body and the `X-Row-Cap` header. The API ignores `limit` and `cursor`, and the set streams in the
sort you asked for. CSV has its own budget of 12 exports a minute per key, one at a time
([Rate limits](/guides/concepts/rate-limits)). The contact columns appear on a `contact`-scope key only. On a
`deals`-only key the owner columns are absent, and the Investor cell is blank for a buyer with no investor id.

If you ask any endpoint outside the nine for CSV, the API answers [`406 not_acceptable`](/guides/concepts/errors#not_acceptable).
On `api.investorlift.com` one route has a CSV representation.
[`GET /v1/lenders/{id}/loans`](/api-reference/endpoints/lenders-loans#csv) answers `Accept: text/csv` from Growth. It
requires a `recorded_from` window, keeps the same 50,000-row cap, costs 0 credits and leaves the borrowers column out.
On every other `/v1` call the gateway asks the API for JSON.


## Related topics

- [List one company's Investorlift listings](/api-reference/endpoints/wholesalers-listings.md)
- [List one investor's Investorlift purchases](/api-reference/endpoints/investors-wholesale-purchases.md)
- [List one investor's deals](/api-reference/endpoints/investors-deals.md)
- [List Investorlift listings](/api-reference/endpoints/wholesale-listings.md)
- [List deals around a location](/api-reference/endpoints/deals-list.md)
