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

# Keys and headers

> The same key as /v1, sent as a bearer header: what it gets over MCP, and how the gateway attributes a call to you.

The MCP endpoint takes the same `zpka_` key as `/v1`, as `Authorization: Bearer`, and nothing else: no second header,
no token exchange, no OAuth. The key comes from the developer console ([Get an API key](/get-a-key)) and belongs to a
plan. A `tools/call` costs what its route costs in credits ([Plans and limits](/guides/plans-and-limits)).

On a client that negotiates protocol 2026-07-28, the cost is one credit per deal, investor or parcel new to your
account, plus the tool's base. A record already seen costs nothing while the plan has credit. Every result reports the
cost in `meta.credits`. `charged` is the cost of the call. Since API 0.27.0, the block also carries three more fields:

* `used`, the period's exact count after the call.
* `line`, the figure it stops at.
* `period_end`, when it resets.

A legacy client gets a single-frame stream, which the API writes before the tool runs. So the API charges a legacy
client per call, refused or served, at the largest cost its page can have. `initialize`, `tools/list`,
`resources/read` and `prompts/get` cost nothing. If you lose a key, copy it again from [Your keys](/keys).

What the key gets over MCP is what it gets over `/v1`: [the twenty-six tools](/mcp/tools), every resource and
the seven prompts. The five listing-agent and Investorlift-listing tools and the five lender tools are in early access
for the team and trusted partners. The fields that identify a natural person are null and marked `contact_redacted: true`:

* the people behind an entity
* mailing addresses
* a parcel owner's identity
* listing agents' names, phones, emails and licence numbers
* a person borrower's name, and every borrower's party key on `godmode_lender_borrowers`

The API leaves these fields out of the object on four of the seven parcel tools (search, financing, history, listing
history). It does the same on the rows of `godmode_lender_loans`. Neither carries a flag
([Authentication](/guides/concepts/authentication)). `godmode_coverage` reports this, so a client can check before it
promises a name. Since API 0.27.0, it also reports where the account stands, in an `account` block beside those three
facts:

```json theme={null}
{ "key_scope": "deals", "contact_via_mcp": false, "on_behalf_of_present": true,
  "account": {
    "plan": "starter", "tier": "starter", "period_end": "2026-10-11T00:00:00.000Z", "counters": "read",
    "credits": { "grant": 500, "line": 500, "used": 3, "remaining": 497, "budget": null },
    "limits": { "largest_limit": 25, "geometry": { "max_radius_miles": 5, "max_bbox_diagonal_miles": 10, "max_zips": 1 }, "max_weeks": 52,
                "name_search": { "day_cap": 1000, "used_today": 12, "resets_in_seconds": 41220 } },
    "requests": { "per_minute": 30, "per_day": 5000 } } }
```

`plan` and `tier` are the plan the key belongs to and the tier the API reads its limits from. `period_end` is when the
billing period ends and `used` returns to 0.

`account.credits` is the period as the API's own ledger counts it. `used` is every credit charged this period, exact:
the figure the API enforces the line on.

`remaining` is `grant` minus `used`. It differs from `X-Credits-Remaining`, the gateway's balance, for two reasons.
First, the gateway reads subscription data cached for a minute. Second, a call charged for a client that left
mid-request is on the API's counter but never on the gateway's meter. That drift lasts until the period resets. Below
zero, the figure depends on the plan:

* On Growth and Scale, it goes negative into the overage, down to the ceiling (`grant` minus `line`).
* On Free, Starter and an Enterprise contract, it goes negative by at most the last page's cost. That is because the
  API checks a hard line before it prices the page.

On Free, `budget` is the lifetime stop (`line`, `spent`, `remaining`) that ends the plan before an allowance does. It is
null on every other plan.

`limits` are the plan's bounds in numbers. `geometry` and `max_weeks` are null from Growth up, where the API's own
maxima apply. `name_search.used_today` is the hits served today against `day_cap`. `requests` is the rate the gateway
admits. `counters` says if the API read the counts:

* `read`: exact.
* `off`: the API counts nothing for this request.
* `unavailable`: the ledger did not answer this time. `used`, `remaining` and `used_today` are then null, not guessed,
  and the next call reads again.

Every `meta.credits` also carries `used`, `line` and `period_end`. So a client reads its standing after each paid call
and does not need to call `godmode_coverage` again.

`on_behalf_of_present` is true on every call, because the gateway names you itself. The gateway attributes each request
to the Investorlift account the key belongs to, and a client never sends an identity header. With a missing, mistyped,
deleted or expired key, the API answers `401 unauthorized` with a problem body, before it reads any JSON-RPC message.
A key whose plan ended answers `403 subscription_required` ([Errors over MCP](/mcp/errors)).

## Partners and staff

On the internal host, staff and contracted partners use a `gm_...` key. Investorlift Data Services issues it per person
and per client, and it carries one of four scope forms. The two contact scopes are per channel:

| Scopes                      | On `/v1`                               | On `/mcp`                                               | Who gets it                                                                                                                             |
| --------------------------- | -------------------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `deals`                     | contact null, `contact_redacted: true` | same                                                    | every client but an interactive staff one: Cursor, VS Code, unattended jobs, the Inspector, hosted connectors. What a `zpka_` key gets. |
| `deals+contact`             | contact served, audited                | redacted: `mcp_contact` is what `/mcp` reads            | REST consumers                                                                                                                          |
| `deals+mcp_contact`         | redacted                               | served on the seven contact-capable single-record tools | an interactive staff client: Claude Code or the Agent SDK on a staff laptop                                                             |
| `deals+contact+mcp_contact` | served                                 | served                                                  | a consumer allowed contact on both channels                                                                                             |

**`X-On-Behalf-Of`.** A key that carries either contact scope must send the header on every request. A key without one
can send it. When the header is present, the API logs it, and the header keys the 240 per minute bucket.

On `/mcp` the value must carry one of the four prefixes (`user:`, `key:`, `svc:`, `org:`). The prefix is the contact
gate: `user:` and `key:` name a person and can get contact, `svc:` and `org:` never do. Both header refusals occur in
the auth hook, before the API reads any JSON-RPC message. So the API never logs a bad value and never charges it to a
bucket:

```json theme={null}
// no header at all, on a key carrying a contact scope
{"code":"on_behalf_of_required","status":400,"instance":"/mcp","request_id":"764305e2-...","detail":"This key carries a contact scope: send X-On-Behalf-Of: <opaque Investorlift user or org id> on every request."}
// X-On-Behalf-Of: user-42, the bare /v1 form
{"code":"validation_error","status":400,"detail":"On /mcp, X-On-Behalf-Of is <prefix>:<opaque id> with prefix user, key, svc or org; letters, digits, dot, underscore and hyphen only (never an email address or a name).","errors":[{"param":"X-On-Behalf-Of","message":"expected user:<id>, key:<id>, svc:<name> or org:<id>","code":"invalid_format"}]}
```

`godmode_coverage` on such a key reads, for example, `{ "key_scope": "deals+contact", "contact_via_mcp": false, "on_behalf_of_present": true }`.
The recorded [Worked examples](/mcp/examples) used that key: it carries `contact` but not `mcp_contact`, so every
example shows the redacted shape a `zpka_` key sees. The same call with `X-On-Behalf-Of: svc:nightly` returns those
three values unchanged, because a shared or unattended identity never opens the gate, whatever the key carries.


## Related topics

- [Errors over MCP](/mcp/errors.md)
- [The MCP endpoint](/mcp/overview.md)
- [Authentication](/guides/concepts/authentication.md)
- [Plans and limits](/guides/plans-and-limits.md)
- [Changelog](/changelog.md)
