Skip to content

Scopes and rate limits

Three tiers, in precedence order on /mcp:

  1. API keyAuthorization: Bearer pm_<key>. Carries scopes + a per-key rate limit from the key record.
  2. OAuth 2.1 tokenAuthorization: Bearer pmt_<token> (see auth). Grants read + underwrite. A successful OAuth token exchange also mints a pm_ API key (scoped read,underwrite), returned once in the token response as api_key.
  3. Anonymous — keyless beta, read-only, 30 calls/min/IP.

The private operator rail (/api/state, /api/deals, /api/run-listings, /api/keys) stays behind the shared write secret and is not issued externally.

Scope Grants
read All read tools + endpoints: search, tract reports, market pulse, comps, map-pins, viewport-context, property history.
underwrite deep_underwrite_asset / underwrite, get_comps / comps (the compute-heavy tools).
write-state The write tools — save_favorite_property, update_underwriting_preset, create_strategy_profile, add_note — and /api/me/* mutations.
webhooks Register signed-webhook delivery targets on strategy profiles (see webhooks).

A tool called without its scope returns forbidden_scope (HTTP 403 on REST, isError on MCP). UI-controller tools (update_map_filters, fly_to_location, highlight_properties) are available to any caller and append to the sync stream.

Principal Limit Mechanism
Anonymous 30 requests/min/IP Sliding per-minute window per IP.
API key rpm_limit (default 60) Per-key token bucket (KV sliding window keyed by the key prefix).

Exceeding a limit returns rate_limited (HTTP 429). Wait ~60s. Higher limits are a function of the key’s rpm_limit, not a different set of numbers.

Every authenticated tool call is metered per key, per tool, per day, with cost weights that reflect the work each triggers (a comps call fans out to live county fetches):

Tool Cost units
get_comps / comps 5
deep_underwrite_asset / underwrite 3
search_investment_inventory and all others 1

Metering protects the compute; billing remains deferred behind an explicit product gate (the beta is free).

Keys are issued by the operator today via POST /api/keys (write-secret gated). The plaintext pm_<key> is shown once — store it; only its SHA-256 hash is retained. Request body: { "scopes": "read,underwrite", "rpm_limit": 60, "email": "…" }. Self-serve issuance rides the existing OAuth consent flow (see above).