# CleanedWeb agent documentation > Authoritative, consolidated guide for AI agents and tool adapters using CleanedWeb maintained canonical property data. ## Product and data boundary CleanedWeb provides the latest maintained serving snapshot of canonical properties, source-advertisement provenance, and finalized observed changes. API requests do not trigger live scraping. Optional values may be absent. Never infer freshness, availability, entitlement, cost, legal rights, or missing facts that the runtime response does not state. API base URL: `https://app.cleanedweb.com`. ## Runtime discovery Always call `GET https://app.cleanedweb.com/v1/capabilities` before planning work. It returns contract and API versions, delivery-ready markets, supported filters, event types, freshness definitions, limits, and currently published Unit costs. `available_markets` is runtime readiness, not static coverage. A `503` means readiness is unknown and must not be converted to an empty list. Reject unknown major contract or schema versions. ## Authoritative contracts - OpenAPI: `https://cleanedweb.com/openapi.json` - Canonical property: `https://cleanedweb.com/schemas/property-profile-v2.json` - Finalized property event: `https://cleanedweb.com/schemas/property-event-v1.json` - Stable property API error: `https://cleanedweb.com/schemas/error-v1.json` - Property field semantics: `https://cleanedweb.com/schemas/property-profile-field-catalog-v2.json` - Search request example: `https://cleanedweb.com/examples/search-request.json` - Search response example: `https://cleanedweb.com/examples/search-response.json` - Property profile example: `https://cleanedweb.com/examples/property-profile-v2.json` - Webhook event example: `https://cleanedweb.com/examples/webhook-event.json` - Arazzo workflows: `https://cleanedweb.com/arazzo.json` - AsyncAPI webhooks: `https://cleanedweb.com/asyncapi.json` - Machine changelog: `https://cleanedweb.com/changelog.json` Static contracts on `cleanedweb.com` require no portal session. The OpenAPI server and all authenticated runtime operations remain on `https://app.cleanedweb.com`. ## Authentication Capabilities, OpenAPI, schemas, and examples are public. Browser property operations use the production workspace cookie `__Host-cw_session`. API-key authentication and MCP are release-gated; require `machine_access.enabled === true` at runtime. The trusted HTTP client or adapter owns the cookie. Never expose it to the model, tool arguments, tool results, logs, URLs, or generated code. Protected POST requests must include `Origin: https://app.cleanedweb.com`. On 401, stop and ask the user to sign in; do not automate mailbox access without separate authorization. ## Core property operations Authenticated operations published by OpenAPI: - `GET /v1/markets`: workspace-entitled and runtime-ready markets. - `POST /v1/properties/count`: exact projection-bound count without rich records. - `POST /v1/properties/search`: search one maintained snapshot. - `GET /v1/properties/{property_entity_id}`: current canonical property. - `GET /v1/properties/{property_entity_id}/changes?limit=50`: newest-first finalized changes; limit 1 through 200. Canonical location suggestions are available at `GET /v1/properties/locations?market=LU&q=Belair&limit=8`. Candidate limit defaults to 8 and cannot exceed 20. Send both location text and the selected `location_place_id`; ask the user when candidates are ambiguous. ## Search request Example: ```json { "market": "LU", "response_schema": "property-profile-v2", "deal_type": ["buy"], "location": "Belair", "location_place_id": "lux-neighbourhood-belair", "property_categories": ["apartment"], "max_price": 1000000, "min_bedrooms": 2, "sort_by": "observed_at", "sort_order": "desc", "limit": 100 } ``` The complete filter schema is in OpenAPI. Extra properties are rejected. Bounded searches return `has_more` and a signed `next_cursor`; send the cursor with the unchanged query and positive `limit` for the next page. Restart from page one after `snapshot_changed`. Omit `limit` to request every match. ## Search acceptance Before writing results downstream, require: 1. `returned === results.length`. 2. Each result has `schema_version === "property-profile-v2"`. 3. Each `property_entity_id` is present and unique within the response. 4. `snapshot.dataUpdatedAt` satisfies the workflow freshness policy. 5. `has_more === false` after aggregating cursor pages, or use one complete unbounded response, when completeness is required. Use `property_entity_id` as the canonical upsert key. Keep `sources` as provenance. A source listing ID is not the canonical property identity. ## MCP When runtime machine access is enabled, the authenticated Streamable HTTP endpoint is `https://app.cleanedweb.com/mcp` and publishes: - `cleanedweb_capabilities` to `GET /v1/capabilities`; - `cleanedweb_list_markets` to `GET /v1/markets`; - `cleanedweb_count_properties` to `POST /v1/properties/count`; - `cleanedweb_search_properties` to `POST /v1/properties/search`; - `cleanedweb_get_property` to `GET /v1/properties/{property_entity_id}`; - `cleanedweb_list_property_changes` to `GET /v1/properties/{property_entity_id}/changes`. Five tools are read-only. Search is non-destructive and exact-request idempotent, but it is not annotated read-only because it commits a durable Unit receipt. Search requires a bounded `limit`, `max_units`, and `idempotency_key`. The client must keep the Bearer key outside model context, allowlist the API origin, preserve structured output, and avoid generic HTTP, arbitrary URL, SQL, acquisition-control, webhook-management, or admin tools. Every tool publishes an `outputSchema` and returns the unchanged structured API payload in `structuredContent`. MCP resources publish runtime capabilities, property, field-catalog, event, and error contracts with assistant audience and priority annotations. Output schemas accept either the tool-specific success object or the stable structured tool-error object. ## Metering Read `unit_costs` from capabilities. An empty object means no operation cost is published by the runtime contract; do not estimate one. Webhook fields and HTTP statuses are not billing authority. Workspace receipts or the Unit ledger are the source of truth when available. ## Stable API errors OpenAPI property operations use: ```json { "schema_version": "error-v1", "error": { "code": "invalid_request", "message": "Request validation failed", "details": {} } } ``` Recovery: - `401 authentication_required`: ask the user to sign in; no unchanged retry. - `403 access_forbidden`: request entitlement or choose an entitled market. - `404 not_found`: treat the canonical ID as unavailable; do not fabricate. - `422 invalid_request`: correct using OpenAPI. - `422 ambiguous_location`: present candidates or ask the user. - `422 location_not_found`: change or remove location with user confirmation. - `503 service_unavailable`: bounded exponential backoff with jitter; never convert to empty data. Capabilities and location suggestions currently use a framework-style top-level `detail` error rather than the `error-v1` guarantee. Check HTTP status before parsing data. Deterministic acceptance must also reject duplicate canonical IDs, response-count mismatches, stale snapshots, unexpected truncation, and unknown schema versions. Return a true zero-result search as empty success. When those contracts are published, reuse a matching idempotent receipt and honor `Retry-After` on a rate limit. Never process a previously committed webhook event ID twice. Unit-budget behavior applies only when runtime metering data exists; an empty `unit_costs` object does not authorize an estimated charge. ## Signed webhooks Live saved-search events map to: - `new_match` -> `property.match.created` - `price_changed` -> `property.price.changed` - `status_changed` -> `property.status.changed` Live events use `property-event-v1`. Delivery is at least once and unordered across properties and retries. Deduplicate by stable event `id`, commit before returning `2xx`, and re-read the canonical property endpoint when current state matters. Verify the exact raw body with HMAC-SHA256. Headers are `webhook-id`, `webhook-timestamp`, and `webhook-signature`. Signed content is: ```text webhook-id + "." + webhook-timestamp + "." + raw_request_body ``` Decode the base64 bytes after `whsec_`, compare each space-separated `v1` signature in constant time, and enforce an explicit timestamp skew policy. Secret rotation can send old and new signatures for 24 hours. Failures retry after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, 14 hours, 20 hours, and 24 hours, with at most 10 attempts. Valid `Retry-After` is capped at 24 hours. HTTP 410 is terminal. Terminal failure moves the connection to `needs_attention`; failed deliveries from the previous 7 days can be replayed after the endpoint is restored and tested. ## Focused guides - `https://cleanedweb.com/docs/ai-agents.md` - `https://cleanedweb.com/docs/mcp.md` - `https://cleanedweb.com/docs/webhooks.md` - `https://cleanedweb.com/docs/errors.md` - `https://cleanedweb.com/docs/data-contract.md` - `https://cleanedweb.com/docs/security.md` - `https://cleanedweb.com/docs/operations.md` - `https://cleanedweb.com/docs/versioning.md` - `https://cleanedweb.com/docs/workflows.md` - `https://cleanedweb.com/docs/reference-clients.md`