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

# Troubleshooting

> Common issues when calling the Lofty MCP server, and how to resolve them.

## My client wants to "sign in with Lofty" and can't find anything

Discovery only fires on a request that carries **no** `Authorization` header at all — the `401` + `WWW-Authenticate` challenge is what points the client at `/.well-known/oauth-protected-resource`. If the client is configured with a key, however wrong, it never sees the challenge. Clear the header and let it retry.

## The client rejected the granted scope

Almost always casing. The issued scope is `openApi`, camelCase — a client comparing it against `openapi` concludes it was granted something unsupported and stops. Match the exact string.

## "redirect uri is not the same as the registered"

Redirect URIs are compared for exact equality. A trailing slash, an extra query parameter, or `http` instead of `https` all fail. Copy the value your client actually sends, character for character, into the vendor portal — don't retype it from documentation.

## Authorization is rejected for everyone except one account

A newly registered app sits in `DEVELOPMENT` status, where only the designated test account may complete the consent flow, and requests are capped at 100/min. Other users being refused is the status working as intended, not a misconfiguration.

## "40101 The client not found", or authorize returns 400

PKCE replaces the client *secret*, not the client *id*. `client_id` is still required on every authorize request; omitting it returns `400`, and sending an unregistered value returns `40101`. The id isn't a secret — Lofty's own CLI ships one in plain source.

## A call returned needsConfirmation and nothing happened

That's the write path working. If your client supports MCP **elicitation**, the prompt is raised in your own interface and you answer there. If it doesn't, the result carries a `confirm_token` — replay the *identical* call with that token added within five minutes.

The token is bound to a hash of the arguments. Changing even one argument invalidates it, deliberately: a token issued for two recipients cannot be reused for two hundred. See [Confirmation](/mcp/guides/confirmation-tiers) for the full protocol.

## I asked for notes and can't find a notes tool

There isn't one — reads and writes are split by design. Read notes with `search_lead_activities({ channel: "note" })`; create, edit or delete them with `manage_leads({ action: "add_note" | "update_note" | "delete_note" })`. Manual logs follow the same pattern with `channel: "manual_log"`.

## A manual log I just wrote isn't readable yet

Manual log writes are asynchronous upstream: the POST returns an id once the write is queued, and the entry becomes readable a moment later. Don't treat an empty read-back as a failed write, and don't retry the write — you'll create a duplicate.

## A batch write was refused, or I'm tempted to loop

Enumerated batches are capped at 10 records. **Do not split the work into repeated calls.** No single confirmation would cover the whole set, a failure partway leaves the data half-changed with no rollback, and the call volume can trip rate limits. Narrow the operation, or use the CRM's own bulk tools.

"Change every Zillow-sourced lead to Nurture" cannot be expressed here at all — no tool accepts a filter to decide what to modify. See [Batch writes](/mcp/guides/bulk-writes).

## A tool call returned a 401 or 403

| Status                | Likely cause                                                                                                                                                                                    |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `401`                 | Missing, malformed or expired credential. Confirm you're sending `Authorization: Bearer <token>` — an API key uses the `token` prefix on the direct REST API, but **always** `Bearer` over MCP. |
| `403` (code `200100`) | An OAuth app calling an operation it was never granted. Add the scope in the Developer Portal and re-authorize.                                                                                 |
| `403` (other)         | The authenticated user genuinely lacks permission for that record or action. The same restriction applies in the CRM itself — this isn't an MCP problem.                                        |

## 404 for a record I'm sure exists

Usually a correct answer rather than a bug. A lead lookup returns `404` when that id doesn't exist *for the authenticated caller's team* — which includes leads that exist but belong to another account. Check the id, and check which credential you're using.

## A field I expect isn't in the schema

It's probably a team-defined custom field, which can't appear in any static schema. Call `search_team_config({ kind: "settings.custom_fields" })` before concluding the field doesn't exist or guessing an id for it.

## An id changed value, or a write hit the wrong record

Lofty ids are 64-bit and exceed `2^53 − 1`. Every id is returned as a **string** for exactly this reason — if your client re-parses tool output with `JSON.parse()` and coerces it to a number, the value is silently rewritten into a different, valid-looking id. Keep ids as strings end to end.

## Should I use v1 or v2?

Neither — that choice no longer exists at this layer. Four resource families ship non-equivalent v1 and v2 surfaces, and the tool picks per field coverage. If a field you need seems unavailable, report it rather than reaching for a version.

## Still stuck?

Check [Error Codes](/authentication/error-codes) for the full list of API error codes, or see the [API Reference](/api-reference/leads/overview) for the same operations documented as plain REST calls.
