Skip to main content

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

A tool call returned a 401 or 403

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 for the full list of API error codes, or see the API Reference for the same operations documented as plain REST calls.