Fictional demonstration. No real properties, clients or agencies.

Open interfaces

# Public records for people and machines.

Only authorised public listings are available anonymously. Professional information requires a verified account and a scoped token.

Last updated: 30 September 2026

## Read a listing without connecting anything.

AI-readable pages do not guarantee search indexing or inclusion in an AI answer.

Every open listing has one stable address. People get a page; AI assistants and search engines get the same record as Markdown, JSON or JSON-LD, without a login.

- HTML [https://speakingbricks.eu/properties/L-DEMO-01](<https://speakingbricks.eu/properties/L-DEMO-01>)

- Markdown [https://speakingbricks.eu/properties/L-DEMO-01.md](<https://speakingbricks.eu/properties/L-DEMO-01.md>)

- JSON [https://speakingbricks.eu/properties/L-DEMO-01.json](<https://speakingbricks.eu/properties/L-DEMO-01.json>)

- JSON-LD [https://speakingbricks.eu/properties/L-DEMO-01.jsonld](<https://speakingbricks.eu/properties/L-DEMO-01.jsonld>)

- Accept: text/markdown https://speakingbricks.eu/properties/L-DEMO-01

- llms.txt [https://speakingbricks.eu/llms.txt](<https://speakingbricks.eu/llms.txt>)

- Sitemap [https://speakingbricks.eu/sitemap.xml](<https://speakingbricks.eu/sitemap.xml>)

- Feed [https://speakingbricks.eu/feeds/open-listings.json](<https://speakingbricks.eu/feeds/open-listings.json>)

## Connect an AI assistant.

Add the public MCP server as a custom connector in Claude, ChatGPT or any MCP client. No login is needed. It searches open listings and returns reasons, unknowns and the responsible agency.

- MCP https://speakingbricks.eu/mcp/public

- Discovery [https://speakingbricks.eu/.well-known/mcp.json](<https://speakingbricks.eu/.well-known/mcp.json>)

listing.searchdatabase.queryproperty.getproperty.compareproperty.verify_availabilityproperty.get_evidenceproperty.contact_representative

Verified professionals use a scoped token for permitted anonymous buyer requests and connection status. Semi-open matches require an eligible professional and a current represented buyer brief. Exclusive off-market listings and drafts are excluded from all AI tools.

- MCP https://speakingbricks.eu/mcp

## Build an integration.

The public REST API returns the same rights-checked answers as the MCP server. Its OpenAPI description works with GPT Actions and code generators.

- OpenAPI [https://speakingbricks.eu/v1/openapi.json](<https://speakingbricks.eu/v1/openapi.json>)

- POST https://speakingbricks.eu/v1/property-searches

- POST https://speakingbricks.eu/v1/database-queries

- GET https://speakingbricks.eu/v1/properties/{id}

- GET https://speakingbricks.eu/v1/properties/{id}/availability

- GET https://speakingbricks.eu/v1/properties/{id}/evidence

- GET https://speakingbricks.eu/v1/properties/{id}/contact-route

Signed JSONL pages and a cursor-based change feed support public-data synchronisation. Changes are observed updates, not a complete history of every edit. Receivers must re-check permission before reuse.

- JSONL [https://speakingbricks.eu/v1/public-sync/snapshot.jsonl](<https://speakingbricks.eu/v1/public-sync/snapshot.jsonl>)

- Changes [https://speakingbricks.eu/v1/public-sync/changes](<https://speakingbricks.eu/v1/public-sync/changes>)

- Signing key [https://speakingbricks.eu/v1/public-sync/key.json](<https://speakingbricks.eu/v1/public-sync/key.json>)

## Use it from the page.

Each open listing page also offers three read-only tools to browser agents that support WebMCP: read the record, check availability and find the contact route. They call the same API, so the answer is the same.

## What public clients never receive.

Public tools return only currently authorised open listings. Private client identities are never exposed through these tools. Each check names the reporting professional role and date; missing checks remain unknown.

## Try a read-only integration

These examples use the live fictional demonstration. No account or API key is needed. Results reflect current publication rights and availability, so the number of matches can change.

### Search by municipality, budget and bedrooms

```
curl --fail-with-body 'https://speakingbricks.eu/v1/property-searches' \
  -H 'Content-Type: application/json' \
  --data '{"municipalities":["Cascais"],"max_price_eur":900000,"min_bedrooms":3,"limit":5}'
```

### Read the record and its evidence

```
curl --fail-with-body 'https://speakingbricks.eu/properties/L-DEMO-01.md'
curl --fail-with-body 'https://speakingbricks.eu/v1/properties/L-DEMO-01/evidence'
curl --fail-with-body 'https://speakingbricks.eu/v1/properties/L-DEMO-01/availability'
```

Use a returned listing_id in place of L-DEMO-01. Check the reporting role, date, unknown checks and current availability before showing an answer to a user.

### Keep a partner copy current

```
curl --fail-with-body 'https://speakingbricks.eu/feeds/open-listings.json'
```

Follow the feed’s next link to retrieve further pages. For incremental synchronisation, use the signed snapshot and changes endpoints above. Apply removal hints, re-fetch current records and remove unavailable or expired copies. The change feed is an observed update stream, not a complete edit history or a proven five-minute delivery guarantee.

### Python (standard library)

```
import json
from urllib.request import Request, urlopen
from urllib.error import HTTPError

filters = json.loads("{\"municipalities\":[\"Cascais\"],\"max_price_eur\":900000,\"min_bedrooms\":3,\"limit\":5}")
request = Request("https://speakingbricks.eu/v1/property-searches",
    data=json.dumps(filters).encode(),
    headers={"Content-Type": "application/json"}, method="POST")
try:
    with urlopen(request, timeout=20) as response:
        result = json.load(response)
except HTTPError as error:
    raise SystemExit(f"Search failed: HTTP {error.code}")
for listing in result["results"]:
    print(listing["listing_id"], listing["price_eur"],
          listing["municipality"], listing["explanation"]["unknown"])
```

### JavaScript (Node.js 24 or a browser module)

```
const response = await fetch("https://speakingbricks.eu/v1/property-searches", {
  method: "POST",
  headers: {"Content-Type": "application/json"},
  body: JSON.stringify({"municipalities":["Cascais"],"max_price_eur":900000,"min_bedrooms":3,"limit":5}),
  signal: AbortSignal.timeout(20000)
});
if (!response.ok) throw new Error(`Search failed: HTTP ${response.status}`);
const result = await response.json();
for (const listing of result.results) {
  console.log(listing.listing_id, listing.price_eur,
    listing.municipality, listing.explanation.unknown);
}
```

### Read the response correctly

results contains only current, permitted matches. listing_id identifies the record; price_eur and beds are typed facts. explanation separates satisfied, unknown and conflicting information; readiness records the reporting role and dates. An empty results list does not reveal whether restricted records exist. Use the OpenAPI schema above for the complete contract.

### Authentication and scope

Public REST and public MCP need no credentials. Professional reads require a token issued for the professional MCP audience, an eligible role and the required stored buyer context. The demonstration uses synthetic tokens; production Supabase Auth and OAuth activation remain pending. Never put service keys in browser code. These examples cannot access restricted listings.

### Connect through an MCP client

Use an MCP SDK or a client that supports Streamable HTTP. It handles initialisation and protocol negotiation. Use the header Accept: application/json, text/event-stream; use Content-Type: application/json for JSON requests. An HTTP 202 may acknowledge a notification; it is not proof that a search completed.

### Handle access and errors

Unavailable or private records return no public details. A 404 can mean a record is absent or no longer public; do not infer which. Invalid filters return a client error. Respect 429 rate limits and Retry-After when present. Re-check availability at the time of use; never treat a cached result as a permanent permission.

### Connect your agency without moving its client files

The workbench supports manual entry, CSV import and agency-scoped exports. Documents and client relationships stay with the professional. A live CRM connector needs an agreed field mapping, rights rules and a tested removal flow. Automatic two-way CRM sync and association partnerships are not active in this demonstration.

### Will an AI find this without a link?

Public pages and links support discovery, while these APIs support explicit integrations. They do not guarantee that a search engine indexes a record or an assistant cites it. Measure those outcomes separately using URL inspection and saved no-link tests.

For AI and developers
- [AI directory (llms.txt)](<https://speakingbricks.eu/llms.txt>)
- [Open listings (JSON)](<https://speakingbricks.eu/feeds/open-listings.json>)
- [Developers](<https://speakingbricks.eu/developers>)

[HTML](<https://speakingbricks.eu/developers?lang=en>)
