{"openapi":"3.1.0","info":{"title":"PutRoots API","version":"1.0.1","description":"The renter's side of a move, as an API. Every row carries its source and the date we last saw it; nothing here returns a verdict about a person. Get a key with POST /v1/partners (shown once). Register once with \"test\": true for a pr_test_ sandbox key that works immediately, no approval step, everything dry (leads come back redacted, nothing recorded); register your production code separately for a pr_live_ key, which starts working once we approve you by email within 1 business day. Keys are scoped per journey stage (core / rental_readiness / move_intake / life_setup). Movers, storage and cleaning providers also receive a signed JSON lead at their endpoint the moment a renter approves a quote request; POST /v1/leads/sample sends a fictitious one to test with. The same operations are available as MCP tools at /mcp for assistants; the human-readable guide is at /connect."},"servers":[{"url":"https://www.putroots.com"}],"tags":[{"name":"core","description":"Hand-offs, stage status, billable events — every partner key has it."},{"name":"rental_readiness","description":"Listings with provenance, per-listing stated requirements, newcomer options. Never a renter's own data."},{"name":"move_intake","description":"Leads pushed to a provider (movers / storage / cleaning), quotes back, booked."},{"name":"life_setup","description":"Furniture and first-week pantry lists for embedding; outbound links carry attribution."}],"paths":{"/v1/listings":{"get":{"operationId":"search_listings","summary":"Search the SF rental index; every row carries source and last-seen date. Partner keys get the free sample tier, never the full index.","description":"Search PutRoots' San Francisco rental index. Every row carries its source, the date we last saw it, and whether the landlord stated any requirements at all. Use this when someone asks what's available in SF at a price / bedroom count / neighborhood. Without a signed-in PutRoots account this searches the free sample only (3 listings per bedroom tier) and says so, with the true total.","tags":["rental_readiness"],"security":[{"partnerKey":[]},{"accountToken":[]}],"x-scope":"rental_readiness","x-mcp-tool":"search_listings","parameters":[{"name":"max_price","in":"query","required":false,"description":"Max monthly rent in USD. Listings with no stated price are excluded when you set this — we don't let unpriced posts through a price filter.","schema":{"type":"number"}},{"name":"min_price","in":"query","required":false,"description":"Min monthly rent in USD.","schema":{"type":"number"}},{"name":"beds","in":"query","required":false,"description":"Bedroom tier. \"room\" means a room in a shared place.","schema":{"type":"string","enum":["room","studio","1","2","3+"]}},{"name":"hood","in":"query","required":false,"description":"Neighborhood name, matched loosely (e.g. \"Mission\", \"Sunset\").","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"How many to return (default 10, max 25). Newest first.","schema":{"type":"integer","minimum":1,"maximum":25}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","properties":{"gated":{"type":"boolean","description":"true = free sample only (anonymous or partner key); total still tells the true index size."},"total":{"type":"integer"},"searched":{"type":"integer"},"matched":{"type":"integer"},"not_checked_vs_budget":{"type":"integer"},"listings_with_no_stated_requirements":{"type":"integer"},"listings":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"title":{"type":["string","null"]},"price":{"type":["number","null"]},"beds":{"type":["string","null"]},"hood":{"type":["string","null"]},"source":{"type":["string","null"]},"posted":{"type":["string","null"]},"last_seen":{"type":["string","null"]},"requirements_stated":{"type":"boolean"},"not_checked_vs_budget":{"type":"boolean"}},"required":["url","price","source","requirements_stated"]}}},"required":["gated","total","searched","matched","listings"]}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}},"/v1/newcomer/options":{"get":{"operationId":"newcomer_options","summary":"Bank / credit card / phone plan / SSN routes that work without an SSN, with branches, sources and dates.","description":"The doors a newcomer has to walk through besides the apartment: opening a bank account, a credit card, a phone plan, or getting an SSN — specifically the routes that work without an SSN or US credit history. Returns real branches and stores (address, hours, phone) plus the no-branch options, each with its source and the date it was checked, and our firsthand account where we did it ourselves.","tags":["rental_readiness"],"security":[{"partnerKey":[]},{"accountToken":[]}],"x-scope":"rental_readiness","x-mcp-tool":"newcomer_options","parameters":[{"name":"topic","in":"query","required":true,"description":"bank = checking account, card = credit card, sim = phone plan, ssn = the SSN itself.","schema":{"type":"string","enum":["bank","card","sim","ssn"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","properties":{"topic":{"type":"string"},"places_total":{"type":"integer"},"places":{"type":"array","items":{"type":"object","description":"A branch / store: name, addr, tel, hours, the no-SSN line with its source."}},"options":{"type":"array","items":{"type":"object","description":"A no-branch option: name, stance (official page) and firsthand stance, facts, url, source."}},"prefNote":{"type":["string","null"]},"prefScope":{"type":["string","null"]}},"required":["topic","places","places_total","options"]}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}},"/v1/listings/requirements":{"get":{"operationId":"listing_requirements_feed","summary":"Per-listing requirement rows: the landlord's stated rule, the original sentence, source and date. No renter data of any kind.","description":"For one listing url from GET /v1/listings: the requirement lines the landlord actually stated (income multiple, credit score, SSN, co-signer, deposit, fee, lease length, documents), each with the landlord's own sentence, where the rule came from and when we fetched it. Contains nothing about any renter. \"Not stated\" is an answer: a listing with zero rows means the landlord wrote none, not that requirements are unknown to us.","tags":["rental_readiness"],"security":[{"partnerKey":[]},{"accountToken":[]}],"x-scope":"rental_readiness","x-mcp-tool":null,"parameters":[{"name":"url","in":"query","required":true,"description":"The listing's url, exactly as returned by GET /v1/listings.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","properties":{"listing":{"type":"string"},"price":{"type":["number","null"]},"source":{"type":"object","properties":{"kind":{"type":"string","enum":["landlord_rules","listing_text"]},"name":{"type":["string","null"]},"from":{"type":"string"},"fetched_at":{"type":["number","string","null"],"description":"Epoch ms for landlord rules; ISO date for listing text."},"fetched_from":{"type":["string","null"]}},"required":["kind","from","fetched_at"]},"requirements_stated":{"type":"boolean"},"requirements":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"},"required":{"type":"string"},"quote":{"type":["string","null"],"description":"The landlord's own sentence when the rule was read from prose; null when it came from a structured field (a stated fee, a form checkbox) and there is no sentence to quote. Never fabricated."},"quote_available":{"type":"boolean"},"source":{"type":"object","description":"Where this row came from and when we fetched it (same as the top-level source)."}},"required":["key","label","required","quote","quote_available","source"]}}},"required":["listing","price","source","requirements_stated","requirements"]}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}},"/v1/handoffs":{"post":{"operationId":"start_move","summary":"Create a hand-off: returns an id and a sign-up link carrying the partner tag, move date and new address. Read-only — no account is created until the person signs up.","description":"When someone has just signed a lease, accepted an offer, or otherwise has a move coming: returns a PutRoots link that pre-fills their move date and new address and attributes them to you. Nothing is created on their behalf — they sign up themselves by opening the link. Use the returned id with GET /v1/handoffs/{id} to see which stage of the move they are in.","tags":["core"],"security":[{"partnerKey":[]},{"accountToken":[]}],"x-scope":"core","x-mcp-tool":"start_move","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"trigger":{"type":"string","maxLength":60,"description":"What set the move off: lease_signed, offer_accepted, relocation_approved, or your own short label."},"stage":{"type":"string","enum":["pre_arrival","housing","move_planning","move_logistics","activate","settle","stabilize"],"description":"Which stage of the journey they are in right now, if you know it."},"d0":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Move-in day, YYYY-MM-DD."},"to":{"type":"string","maxLength":200,"description":"The new address, as one line."},"profile_seed":{"type":"object","description":"Reserved. Accepted but not applied yet — the person fills their own profile after signing up."}},"required":["trigger"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"partner":{"type":"string"},"trigger":{"type":"string"},"stage":{"type":["string","null"]},"d0":{"type":["string","null"]},"to":{"type":["string","null"]},"dry_run":{"type":"boolean","description":"true when called with a pr_test_ key."}},"required":["id","url","partner","trigger","dry_run"]}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}},"/v1/handoffs/{id}":{"get":{"operationId":"handoff_status","summary":"Stage status (not_started / in_progress / done / exception) for the person behind a hand-off id. No profile data, only task progress.","description":"For a hand-off id from POST /v1/handoffs: whether the person has signed up, and for each of the seven journey stages whether it is not started, in progress, done, or has an exception. Nothing from their profile is returned.","tags":["core"],"security":[{"partnerKey":[]}],"x-scope":"core","x-mcp-tool":null,"parameters":[{"name":"id","in":"path","required":true,"description":"The hand-off id.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","properties":{"id":{"type":"string"},"partner":{"type":"string"},"joined":{"type":"boolean"},"users":{"type":"array","items":{"type":"object","properties":{"user":{"type":"string","description":"An opaque reference, stable per person. Never an email."},"move":{"anyOf":[{"type":"object","properties":{"kind":{"type":"string"},"d0":{"type":"string"}}},{"type":"null"}]},"stages":{"type":"object","properties":{"pre_arrival":{"type":"string","enum":["not_started","in_progress","done","exception"]},"housing":{"type":"string","enum":["not_started","in_progress","done","exception"]},"move_planning":{"type":"string","enum":["not_started","in_progress","done","exception"]},"move_logistics":{"type":"string","enum":["not_started","in_progress","done","exception"]},"activate":{"type":"string","enum":["not_started","in_progress","done","exception"]},"settle":{"type":"string","enum":["not_started","in_progress","done","exception"]},"stabilize":{"type":"string","enum":["not_started","in_progress","done","exception"]}},"required":["pre_arrival","housing","move_planning","move_logistics","activate","settle","stabilize"]}},"required":["user","stages"]}}},"required":["id","partner","joined","users"]}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}},"/v1/events":{"get":{"operationId":"partner_events","summary":"Your billable events, oldest first; poll with since=<epoch ms>. What you see depends on your registration: leads you were pushed (quote_received, booked), opens of your affiliate link (link_out), or both for handed-off users.","description":"Billable events after the given epoch-millisecond timestamp. Provider keys with an endpoint / intake_email see quote_received and booked for the leads pushed to them. Provider keys with a delivery.affiliate_code see link_out: one per time our agent opened your affiliate link as the entry to do a task for a user (detail.billable is true at most once per user per day). Partner keys see booked and link_out for the users they handed off. Nobody sees another company's events.","tags":["core"],"security":[{"partnerKey":[]}],"x-scope":"core","x-mcp-tool":null,"parameters":[{"name":"since","in":"query","required":false,"description":"Epoch milliseconds; only events after this are returned (default 0).","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","properties":{"partner":{"type":"string"},"since":{"type":"integer"},"events":{"type":"array","items":{"type":"object","properties":{"ts":{"type":"integer"},"type":{"type":"string","enum":["booked","link_out","quote_received"],"description":"Partner keys: booked and link_out for users they handed off. Provider keys: quote_received and booked for leads pushed to them (endpoint / intake_email delivery), link_out for opens of their own affiliate link (affiliate_code delivery); a provider registered with both sees both."},"user":{"type":"string"},"detail":{"type":"object","description":"Projected, never the raw event. booked / quote_received: lead_id, task, code (+ amount on quote_received). link_out: task, code, billable."}},"required":["ts","type","user","detail"]}}},"required":["partner","since","events"]}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}},"/v1/partners":{"post":{"operationId":"partner_register","summary":"One form for any company. Returns 201 with your API key once (we keep only a hash). Providers say what service they are so leads reach them.","description":"Register once. kind=partner if you send us users; kind=provider if you take leads / orders / tasks from us (then service and at least one delivery are required). service is one of movers | storage | cleaning | furniture | grocery | insurance | isp | mobile, and stages must include the stage that service belongs to (move_planning / settle / activate). Affiliate providers (insurers, ISPs, carriers, stores): delivery.affiliate_code is your full https:// tracking link; config.vendor (one of the vendor names our task graph uses) is required when it is your only delivery and lets you omit service (we infer it). Responds 201; status is pending until approved by email within 1 business day (unless auto-approval is on).","tags":["core"],"security":[],"x-scope":"core","x-mcp-tool":null,"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","required":["code","name","kind","stages","contact_email"],"additionalProperties":false,"properties":{"code":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$","description":"Your short code: 3–32 lowercase letters / digits / hyphens. Used in hand-off links and outbound links."},"name":{"type":"string","maxLength":120},"kind":{"type":"string","enum":["partner","provider"],"description":"partner = you send us users and watch their stage progress; provider = you take tasks from us (leads, orders)."},"stages":{"type":"array","minItems":1,"items":{"type":"string","enum":["pre_arrival","housing","move_planning","move_logistics","activate","settle","stabilize"]},"description":"Which journey stages you are in. Scopes are derived from them."},"contact_email":{"type":"string","format":"email"},"service":{"type":"string","enum":["movers","storage","cleaning","furniture","grocery","insurance","isp","mobile"],"description":"Required for kind=provider (partners omit it): what kind of provider you are. movers / storage / cleaning need stage move_planning; furniture / grocery need settle; insurance / isp / mobile need activate. Affiliate providers may omit it when config.vendor is set: we infer it from the vendor. Puts you in the directory so leads / tasks reach you."},"delivery":{"type":"object","additionalProperties":false,"description":"provider only: how we deliver to you. At least one.","properties":{"endpoint":{"type":"string","format":"uri","description":"We POST { event: \"lead.created\", lead } here, signed with X-PutRoots-Signature (HMAC-SHA256). Additive to intake_email for quote services."},"intake_email":{"type":"string","format":"email","description":"Required for movers / storage / cleaning: the renter's quote request is emailed here from their own mailbox; the JSON lead follows to endpoint if set."},"affiliate_code":{"type":"string","format":"uri","pattern":"^https://","maxLength":500,"description":"Your full https:// affiliate tracking link, as issued by your affiliate network (not a bare code). When our agent does a task on your site for a user it opens this link as the entry (the 302 lands on your page inside our session), and records a link_out event you can read from GET /v1/events. Requires config.vendor when it is your only delivery. Not for Instacart (attribution is on their API key)."}}},"config":{"type":"object","additionalProperties":false,"description":"Adapter-level settings for your company only; the toolkit itself is the same for everyone.","properties":{"vendor":{"type":"string","enum":["lemonade","mint-mobile","us-mobile","visible","sonic","att-fiber","xfinity","astound","ikea","weee","instacart"],"description":"The vendor name our task graph knows you by. Required when delivery.affiliate_code is your only delivery; it is how a link_out is attributed to you. If you set it, service may be omitted (inferred). Not listed? Tell us and we add it."}}},"test":{"type":"boolean","description":"Issue a pr_test_ sandbox key instead of a pr_live_ one. A sandbox key works immediately, with no approval step, and runs dry: leads come back redacted, real leads are never delivered to you, nothing is recorded, hand-offs are not billable. A code's key type is fixed at registration and a code can only be registered once, so register your sandbox under its own code (e.g. acme-sandbox) and your production code separately."}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","required":["ok","code","kind","stages","scopes","status","api_key"],"properties":{"ok":{"type":"boolean"},"code":{"type":"string"},"kind":{"type":"string"},"stages":{"type":"array","items":{"type":"string"}},"scopes":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["pending","active"]},"api_key":{"type":"string","description":"Shown once. Store it; we keep only a hash."},"webhook_secret":{"type":"string","description":"Providers with a delivery.endpoint only. Shown once. We sign every POST to your endpoint: X-PutRoots-Signature: sha256=<hex(HMAC-SHA256(webhook_secret, raw request body))>."},"note":{"type":"string","description":"status=pending: approval by email within 1 business day; the key works once approved. status=active: the key (and webhook_secret) are shown once."}}}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}},"/v1/leads/sample":{"post":{"operationId":"leads.sample","summary":"Delivers one fixed, fictitious lead to your registered endpoint or intake email, signed like a real one, so you can test your integration. Nothing real is involved.","description":"For integration testing with any key. Posts a fixed sample lead (fictitious renter, fictitious addresses) to your delivery.endpoint or intake_email exactly as a real lead would be delivered, including the HMAC signature header.","tags":["move_intake"],"security":[{"partnerKey":[]}],"x-scope":"move_intake","x-mcp-tool":null,"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","required":["ok","via"],"properties":{"ok":{"type":"boolean"},"via":{"type":"string","enum":["endpoint","email"]},"lead_id":{"type":"string"},"preview":{"type":"object"}}}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}},"/v1/leads":{"get":{"operationId":"leads.list","summary":"Leads pushed to you: inventory, date window, both buildings' rules (COI, elevator booking, window, loading zone), floors, distance. On a pr_test_ key or before go-live, leads are redacted (no contact, no street address).","description":"Leads pushed to you: inventory, date window, both buildings' rules (COI, elevator booking, window, loading zone), floors, distance. On a pr_test_ key or before go-live, leads are redacted (no contact, no street address).","tags":["move_intake"],"security":[{"partnerKey":[]}],"x-scope":"move_intake","x-mcp-tool":null,"parameters":[{"name":"since","in":"query","required":false,"description":"只要这个时刻之后推送的","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","required":["leads"],"properties":{"leads":{"type":"array","items":{"allOf":[{"type":"object","required":["id","kind","move_date_window","from","to","inventory","contact","source","created_at"],"properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["movers","storage","cleaning"]},"move_date_window":{"type":"object","required":["from","to"],"properties":{"from":{"type":"string","format":"date"},"to":{"type":"string","format":"date"}}},"from":{"type":"object","properties":{"address":{"type":["string","null"]},"floor":{"type":["string","null"]},"elevator":{"type":["boolean","null"]},"coi":{"type":["boolean","null"]},"elevator_booking":{"type":["boolean","null"]},"window":{"type":["string","null"]},"loading_zone":{"type":["string","null"]}}},"to":{"type":"object","properties":{"address":{"type":["string","null"]},"floor":{"type":["string","null"]},"elevator":{"type":["boolean","null"]},"coi":{"type":["boolean","null"]},"elevator_booking":{"type":["boolean","null"]},"window":{"type":["string","null"]},"loading_zone":{"type":["string","null"]}}},"inventory":{"type":"object","properties":{"summary":{"type":["string","null"]},"rooms":{"type":["string","null"],"enum":["studio","1b","2b","3b+",null]},"big_items":{"type":["integer","null"]}}},"distance_mi":{"type":"number"},"contact":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"}}},"source":{"type":"string","const":"putroots"},"created_at":{"type":"string","format":"date-time"}}},{"type":"object","properties":{"pushed_at":{"type":"string","format":"date-time"},"quotes":{"type":"integer"},"booked":{"type":"boolean"},"redacted":{"type":"boolean","description":"true on pr_test_ keys and before go-live: contact is {} and addresses are null"}}}]}}}}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}},"/v1/leads/{id}/quotes":{"post":{"operationId":"leads.quote","summary":"Send a quote for a lead: amount (USD), crew, arrival window, what is included. Idempotent on (lead, amount, window): a retry returns already:true. The renter sees quotes side by side in their calendar. On a sandbox key the only id you can exercise is the sample lead's, from POST /v1/leads/sample: it is validated and accepted, but never recorded.","description":"Send a quote for a lead: amount (USD), crew, arrival window, what is included. Idempotent on (lead, amount, window): a retry returns already:true. The renter sees quotes side by side in their calendar. On a sandbox key the only id you can exercise is the sample lead's, from POST /v1/leads/sample: it is validated and accepted, but never recorded.","tags":["move_intake"],"security":[{"partnerKey":[]}],"x-scope":"move_intake","x-mcp-tool":null,"parameters":[{"name":"id","in":"path","required":true,"description":"Lead id, as pushed to you.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","required":["amount"],"properties":{"amount":{"type":"number","minimum":0,"description":"美元"},"crew":{"type":"integer","minimum":1},"window":{"type":"string","description":"到场时段,如 8–10am"},"includes":{"type":"array","items":{"type":"string"},"description":"含什么:packing / insurance / ……"},"note":{"type":"string","maxLength":1000}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","required":["ok","lead_id"],"properties":{"ok":{"type":"boolean"},"lead_id":{"type":"string"},"already":{"type":"boolean","description":"true when this exact quote was already recorded"},"dry_run":{"type":"boolean"},"sample":{"type":"boolean","description":"Sandbox only: this was the sample lead from POST /v1/leads/sample. Your call was validated and accepted, but nothing was recorded — there is no renter behind it."}}}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}},"/v1/leads/{id}/booked":{"post":{"operationId":"leads.booked","summary":"Mark a lead booked (the renter ordered with you). The billable booked event is recorded once, from this call or from the renter's own confirmation, whichever comes first. Empty body is fine. On a sandbox key only the sample lead's id is accepted, and nothing is recorded.","description":"Mark a lead booked (the renter ordered with you). The billable booked event is recorded once, from this call or from the renter's own confirmation, whichever comes first. Empty body is fine. On a sandbox key only the sample lead's id is accepted, and nothing is recorded.","tags":["move_intake"],"security":[{"partnerKey":[]}],"x-scope":"move_intake","x-mcp-tool":null,"parameters":[{"name":"id","in":"path","required":true,"description":"Lead id, as pushed to you.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","minimum":0},"note":{"type":"string","maxLength":1000}},"required":["id"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","required":["ok","lead_id"],"properties":{"ok":{"type":"boolean"},"lead_id":{"type":"string"},"already":{"type":"boolean"},"dry_run":{"type":"boolean"},"sample":{"type":"boolean","description":"Sandbox only: this was the sample lead from POST /v1/leads/sample. Your call was validated and accepted, but nothing was recorded — there is no renter behind it."}}}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}},"/v1/lists/furniture":{"get":{"operationId":"lists.furniture","summary":"按房型 × 预算档出家具清单(不带价格);sqft 给了就把放不下的丢进 dropped。","description":"按房型 × 预算档出家具清单(不带价格);sqft 给了就把放不下的丢进 dropped。","tags":["life_setup"],"security":[{"partnerKey":[]}],"x-scope":"life_setup","x-mcp-tool":null,"parameters":[{"name":"rooms","in":"query","required":false,"description":"房型;缺省 studio","schema":{"type":"string","enum":["studio","1b","2b"]}},{"name":"budget","in":"query","required":false,"description":"预算档;缺省 mid","schema":{"type":"string","enum":["low","mid","high"]}},{"name":"sqft","in":"query","required":false,"description":"房源面积(平方英尺);给了就按 min_sqft 过滤","schema":{"type":"number","minimum":1}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","properties":{"room":{"type":"string","enum":["studio","1b","2b"]},"budget":{"type":"string","enum":["low","mid","high"]},"sqft":{"type":["number","null"]},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"zh":{"type":"string"},"en":{"type":"string"},"q":{"type":"string","description":"站内搜索词"},"cat":{"type":"string"},"qty":{"type":"number"},"footprint_in":{"type":"array","items":{"type":"number"},"minItems":3,"maxItems":3},"note_zh":{"type":"string"},"note_en":{"type":"string"}},"required":["id","zh","en","q","cat","qty"]}},"dropped":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"zh":{"type":"string"},"en":{"type":"string"},"min_sqft":{"type":"number"}},"required":["id","zh","en","min_sqft"]}},"list_source":{"type":"string","description":"清单来源标识"},"list_updated":{"type":"string","description":"清单的 updated(YYYY-MM-DD)"}},"required":["room","budget","sqft","items","dropped","list_source","list_updated"]}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}},"/v1/lists/pantry":{"get":{"operationId":"lists.pantry","summary":"首周食材清单(不带价格),量按同住人数档;有孩 / 有宠各加一组。","description":"首周食材清单(不带价格),量按同住人数档;有孩 / 有宠各加一组。","tags":["life_setup"],"security":[{"partnerKey":[]}],"x-scope":"life_setup","x-mcp-tool":null,"parameters":[{"name":"household","in":"query","required":false,"description":"同住几人;缺省 1","schema":{"type":"string","enum":["1","2","3+"]}},{"name":"kids","in":"query","required":false,"description":"有孩子就加一组","schema":{"type":"boolean"}},{"name":"pets","in":"query","required":false,"description":"有宠物就加一组","schema":{"type":"boolean"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"source":{"description":"Who answered: the string \"putroots\", except on operations whose payload carries its own structured source (then that object wins)."},"fetched_at":{"type":"string","format":"date-time"}},"required":["source","fetched_at"]},{"type":"object","properties":{"tier":{"type":"string","enum":["1","2","3+"]},"kids":{"type":"boolean"},"pets":{"type":"boolean"},"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"zh":{"type":"string"},"en":{"type":"string"},"q":{"type":"string","description":"站内搜索词"},"cat":{"type":"string"},"qty":{"type":"number"},"unit":{"type":"string"}},"required":["id","zh","en","q","cat","qty"]}},"list_source":{"type":"string","description":"清单来源标识"},"list_updated":{"type":"string","description":"清单的 updated(YYYY-MM-DD)"}},"required":["tier","kids","pets","items","list_source","list_updated"]}]}}}},"400":{"description":"Bad input — error says which field.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"401":{"description":"No key / bad token.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"403":{"description":"Key is fine but not for this: pending approval, revoked, scope missing, or an operation only the renter's own account may call.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"404":{"description":"No such lead / hand-off / listing under this key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"409":{"description":"Conflict: the code is already registered.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}},"429":{"description":"Rate limited; Retry-After in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"}},"required":["error"]}}}}}}}},"components":{"securitySchemes":{"partnerKey":{"type":"http","scheme":"bearer","description":"Partner API key: pr_live_… or pr_test_… (dry-run). Issued once by POST /v1/partners."},"accountToken":{"type":"http","scheme":"bearer","description":"A PutRoots account token — the renter's own. Only operations marked for it accept this."}}}}