> ## 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.

# SDKs

> The official TypeScript package: install it, make one call, page a list, catch an error, read the credits.

The official TypeScript package is `@investorlift/godmode`. It is in beta, and its version starts at 0. It wraps every
endpoint of this reference as one method. Every guide on this site shows each request in five forms: TypeScript, curl,
Node.js, Python and the CLI. The TypeScript tab is the package and opens first. The CLI tab is the official
command-line tool, `@investorlift/cli` ([CLI](/guides/cli)).

<Note>
  Investorlift generates the package from the same OpenAPI document that generates this reference. A new field or
  endpoint reaches the package at the next release. A field the package does not know yet still comes back in the
  response.
</Note>

<Steps>
  <Step title="Install the package">
    ```bash theme={null}
    npm install @investorlift/godmode
    ```

    The package needs Node 18.17 or later. It has no runtime dependency. Use it on a server. Do not put the key in a
    browser page.
  </Step>

  <Step title="Put the key in your shell">
    ```bash theme={null}
    export GM_API_KEY="zpka_..."
    ```

    The client reads `GM_API_KEY`. You can also pass `apiKey` to the constructor. [Get an API key](/get-a-key) explains
    the console.
  </Step>

  <Step title="Make one call">
    ```typescript theme={null}
    import { GodMode } from "@investorlift/godmode";

    const gm = new GodMode();

    const summary = await gm.deals.summary({ lat: 33.476917, lng: -111.920385, radius_miles: 2 });

    console.log(summary.data.n_deals);
    console.log(summary.meta.coverage[0]?.data_end);
    console.log(summary.requestId);
    ```

    A method returns the response body and the response headers as fields beside it.

    | Field            | Header                                     | What it holds                                                                                |
    | ---------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------- |
    | `requestId`      | `X-Request-Id`                             | The id of the call. Quote it to [support@investorlift.com](mailto:support@investorlift.com). |
    | `credits`        | `X-Credits-Charged`, `X-Credits-Remaining` | The cost of the call and the balance after it. Null on a route that costs 0 credits.         |
    | `datasetVersion` | `X-Dataset-Version`                        | The cache key. See [Coverage and freshness](/guides/concepts/coverage).                      |
    | `dataEnd`        | `X-Data-End`                               | The last deed date per market.                                                               |
    | `etag`           | `ETag`                                     | Send it back as `etag` to get a 304 while the data is unchanged.                             |
    | `rows`           | `X-Rows`                                   | The number of rows in the body.                                                              |
  </Step>

  <Step title="Find the method">
    The client has one group per resource. The method name is on each endpoint page, under the request, in the
    TypeScript tab. A method with an id in the path takes the id first. The parcel search takes its body.

    ```typescript theme={null}
    await gm.investors.get("inv_0a20a550f33b", { market: "phx" });
    await gm.properties.search({ location: { zip: ["85251"] }, limit: 25 });
    ```

    | Group               | Methods                                                                                  |
    | ------------------- | ---------------------------------------------------------------------------------------- |
    | `deals`             | `list`, `cells`, `summary`, `get`                                                        |
    | `buyers`            | `match`                                                                                  |
    | `investors`         | `list`, `search`, `get`, `deals`, `wholesalePurchases`                                   |
    | `properties`        | `resolve`, `get`, `search`, `financing`, `permits`, `history`, `listingHistory`, `comps` |
    | `markets`           | `changes`                                                                                |
    | `agents`            | `search`, `get`, `listings`                                                              |
    | `wholesaleListings` | `list`, `get`                                                                            |
    | `wholesalers`       | `search`, `get`, `listings`                                                              |
    | `lenders`           | `search`, `list`, `get`, `loans`, `borrowers`, `rankings`, `cells`                       |
    | `strParcels`        | `list`                                                                                   |
    | `dataset`           | `get`                                                                                    |
    | `coverage`          | `get`                                                                                    |
  </Step>

  <Step title="Page a list">
    ```typescript theme={null}
    for await (const deal of gm.deals.list({ zip: ["85251"], limit: 100 }).items({ maxPages: 5 })) {
      console.log(deal.id);
    }
    ```

    `pages()` gives each page. `items()` gives each row. Both follow `page.next_cursor` until it is null. See
    [Pagination](/guides/concepts/pagination).

    A list that costs credits requires `maxPages`, because every page adds to the bill. A list that costs 0 credits takes
    no bound. A `400 invalid_cursor` stops the loop with an error. The client never starts again from page 1 on its own.
  </Step>

  <Step title="Catch an error">
    ```typescript theme={null}
    import { CoverageError, QuotaError, RateLimitedError } from "@investorlift/godmode";

    try {
      await gm.deals.summary({ lat: 40.7, lng: -74.0, radius_miles: 2 });
    } catch (error) {
      if (error instanceof CoverageError) console.log(error.recovery);
      if (error instanceof QuotaError) console.log(error.extras.used, error.extras.line);
      if (error instanceof RateLimitedError) console.log(error.retryAfterSeconds);
      throw error;
    }
    ```

    Every refusal the API sends is a `GodModeError`. It carries `status`, `code`, `title`, `detail`, `recovery`,
    `requestId`, `errors` and `extras`. The `recovery` sentence is the same one the [Errors](/guides/concepts/errors) page
    shows. A code the package does not know yet keeps its wire value in `code`. A request that gets no answer is a
    `ConnectionError`, a plain `Error`.

    | Class                 | Status and codes                                                               |
    | --------------------- | ------------------------------------------------------------------------------ |
    | `AuthenticationError` | 401                                                                            |
    | `RateLimitedError`    | 429                                                                            |
    | `QuotaError`          | 403 `quota_exceeded`, `subscription_required`, `payment_overdue`, `plan_limit` |
    | `RequestError`        | every 400 and 406, and every 422 that is not a coverage fact or an ambiguity   |
    | `NotFoundError`       | 404                                                                            |
    | `GoneError`           | 410, with `supersededBy`                                                       |
    | `CoverageError`       | 422 `outside_coverage` and every 422 `*_unavailable` code                      |
    | `AmbiguousError`      | 422 `ambiguous_apn` and `ambiguous_address`, with `candidates`                 |
    | `ServerError`         | 500, 503, 504                                                                  |
    | `ConnectionError`     | no answer: a network failure, a timeout, or your abort                         |
  </Step>
</Steps>

## Retries

The client follows the rule on [Rate limits](/guides/concepts/rate-limits) and the recovery sentence of
`internal_error` on [Errors](/guides/concepts/errors), and no other rule.

* On a `429` the client waits `Retry-After` seconds and sends the identical request again, at most `maxRetries` times.
* On a `503 pool_saturated` or `503 ledger_unavailable` the client does the same. The API charged nothing.
* On a `500 internal_error` the client sends the request again once.
* On a connection failure before any answer the client sends the request again once.
* The client never retries a `504`, any other `4xx`, or a timeout.

`maxRetries` bounds every one of these retries. Set `maxRetries: 0` to turn them all off. A `Retry-After` longer than
`maxRetryDelayMs` throws at once: `RateLimitedError` on a `429`, `ServerError` on a `503`.

## Check for a refresh

```typescript theme={null}
const first = await gm.dataset.get();
const again = await gm.dataset.get({ etag: first.etag! });
if (again.notModified) console.log("same data");
```

Every GET that is not a page loop accepts `etag`. The client does not cache. It gives you `datasetVersion` for your cache key. The
[cache window](/guides/terms) is the rule.

## Options

```typescript theme={null}
const gm = new GodMode({
  apiKey: process.env.GM_API_KEY,
  baseUrl: "https://api.investorlift.com",
  timeoutMs: 30_000,
  maxRetries: 2,
  maxRetryDelayMs: 30_000,
  appName: "my-app/2.1",
});
```

`appName` goes at the end of the `User-Agent` header. The client sends its own name, its version and the Node
version first. Each call also takes `{ etag, signal, timeoutMs, maxRetries, headers }` as its last argument.

## What the package does not do

* It does not run in a browser.
* It does not read the CSV export of [a lender's loans](/api-reference/endpoints/lenders-loans#csv). That export is a plain request from the Growth plan. The endpoint page shows it.
* It does not connect to [the MCP endpoint](/mcp/overview). An AI client uses its own MCP client.
* It does not cache answers and does not enforce the plan limits. The API reports both in the headers.
* It does not validate a response at run time. The types come from the contract.

## The command-line tool

The official command-line tool, `@investorlift/cli`, runs every method of this package as one command from a shell,
with no code: `investorlift deals summary --lat 33.476917 --lng -111.920385 --radius-miles 2`. It reads the same key,
prints a table, JSON, CSV or NDJSON, and states the cost of every call on stderr. [CLI](/guides/cli) explains it.

## Versions and other languages

`GodMode.version` is the package. `GodMode.apiVersion` is the API version that generated the package. A minor API
change is a minor package release. A removal follows the 30-day notice of the [Developer Agreement](/guides/terms).
Investorlift marks the method deprecated one minor release before it goes.

A Python package follows when the request log shows Python callers. Until then, the Python tab on every page shows
`requests`, and the OpenAPI document at `https://api.investorlift.com/openapi.json` generates a client with
openapi-python-client.


## Related topics

- [API reference](/api-reference/introduction.md)
- [Get an API key](/get-a-key.md)
- [Quickstart](/guides/quickstart.md)
- [Changelog](/changelog.md)
- [CLI](/guides/cli.md)
