Overview
| Service root | https://multilistado.mx/reso/odata |
| Metadata | [/reso/odata/$metadata](https://multilistado.mx/reso/odata/$metadata) (EDMX/XML) |
| OAuth2 token | POST https://multilistado.mx/reso/oauth/token (client credentials) |
| Standards | Web API Core 2.0.0 (2.1.0 also tested) + Data Dictionary 2.0 · OData 4.0, JSON odata.metadata=minimal |
| Resources | Property (+ $expand=Media), Media, Member, Office, Lookup |
| Rate limit | 600 requests per 10 minutes per credential (429 with Retry-After) |
| Status | Available, read-only. Not yet RESO-certified (the official test suites pass in our test environments) |
Who can use it
| Credential | How to get it | What it sees |
|---|---|---|
Agent key (read) | Yourself at Portal → API | Your listings (published, pending, closed or withdrawn; no drafts) + those of agents who consented to share with partner MLSs |
**Partner, broker scope** | Issued by Multilistado to an MLS, GDX node or vendor (apply) | Only listings of agents who consented: Active, Pending, Closed, Withdrawn |
**Partner, idx scope** | Same, for IDX display | Only Active listings of consenting agents |
Each agent consents at Portal → International portals ("US MLSs and GDX network (RESO)"). Licensed imports from other MLSs, test accounts and any compensation field are never included. Coordinates are approximate and the street address is omitted unless the agent shows it.
1. Get a token
client_id = the key prefix (the 8 characters after pbm_); client_secret = the full key. Tokens last 1 hour (expires_in: 3600). You can also send the key directly as Authorization: Bearer pbm_…. Credentials in the URL (?api_key=) are refused.
curl -s -X POST https://multilistado.mx/reso/oauth/token \
-d grant_type=client_credentials \
-d client_id="$RESO_CLIENT_ID" \
--data-urlencode client_secret="$RESO_CLIENT_SECRET"curl -s -X POST https://multilistado.mx/reso/oauth/token \
-u "$RESO_CLIENT_ID:$RESO_CLIENT_SECRET" -d grant_type=client_credentials{ "access_token": "Qm9…", "token_type": "Bearer", "expires_in": 3600, "scope": "read partner idx" }OAuth errors: 400 {"error":"unsupported_grant_type"} and 401 {"error":"invalid_client"}.
2. OData queries
TOKEN="…" # access_token from the previous step
R=https://multilistado.mx/reso/odata
# Active listings in Tijuana, 10 per page, with total count and photos
curl -s -G "$R/Property" -H "Authorization: Bearer $TOKEN" \
--data-urlencode "\$filter=StandardStatus eq 'Active' and City eq 'Tijuana'" \
--data-urlencode "\$select=ListingKey,ListPrice,PBM_Currency,City,PropertySubType,BedroomsTotal,ModificationTimestamp" \
--data-urlencode "\$expand=Media" --data-urlencode "\$top=10" --data-urlencode "\$count=true"
# One listing by key
curl -s -G "$R/Property('07e25392-752f-4ca5-a441-51b4ad0625ed')" -H "Authorization: Bearer $TOKEN" \
--data-urlencode "\$select=ListingKey,ListPrice,StandardStatus"
# Lookup values of a field
curl -s -G "$R/Lookup" -H "Authorization: Bearer $TOKEN" --data-urlencode "\$filter=LookupName eq 'PropertySubType'"Response (trimmed, illustrative):
{
"@odata.context": "https://multilistado.mx/reso/odata/$metadata#Property",
"@odata.count": 27,
"value": [{
"ListingKey": "07e25392-752f-4ca5-a441-51b4ad0625ed", "ListPrice": 4350000, "PBM_Currency": "MXN", "City": "Tijuana",
"PropertySubType": "Single Family Residence", "BedroomsTotal": 3, "ModificationTimestamp": "2026-09-30T14:07:28.671Z",
"Media": [{ "MediaKey": "05453dad-…", "ResourceRecordKey": "07e25392-…", "MediaURL": "https://…/fachada.jpg", "MediaCategory": "Photo", "Order": 0 }]
}],
"@odata.nextLink": "https://multilistado.mx/reso/odata/Property?%24filter=…&%24skiptoken=WyIy…"
}Supported options
| Option | Detail |
|---|---|
$filter | eq ne gt ge lt le, in (…), has, and/or/not, parentheses, contains(), startswith(), endswith(), now(); collections with any()/all() |
$select | Field list; an unknown field returns 400 |
$orderby | Any scalar field, asc/desc |
$top / $skip | Property: default 100, max 500. Media, Member and Office: default 200, max 1000. Lookup: 1000 |
$count=true | Total in @odata.count |
$expand=Media | Property only |
@odata.nextLink | Without $orderby or $skip, paging uses a $skiptoken ordered by ModificationTimestamp + key: replication-safe — no record is skipped; one that changes during the download may appear again at the end (upsert by ListingKey) |
$format | json only |
OData errors: {"error":{"code":"BadRequest","message":"Unknown field Bogus"}} (400), Unauthorized (401), NotFound (404), TooManyRequests (429).
Lookups
Values are Data Dictionary 2.0 names (e.g. 'Single Family Residence', 'Square Meters'). The Lookup resource gives each one's LegacyODataValue (SingleFamilyResidence), and filters accept either form. Multi-value fields View, WaterfrontFeatures, CommunityFeatures and PetsAllowed are collections: View/any(v: v eq 'Ocean'). Local fields use the PBM_ prefix: PBM_Currency is the currency of ListPrice/LeaseAmount (MXN or USD, not converted).
3. Replication
To keep a copy: do a full download following @odata.nextLink, store the newest ModificationTimestamp, then request only changes.
# Incremental replica (Python 3.8+, requests)
import os, requests
R = "https://multilistado.mx/reso/odata"
tok = requests.post("https://multilistado.mx/reso/oauth/token", data={
"grant_type": "client_credentials", "client_id": os.environ["RESO_CLIENT_ID"], "client_secret": os.environ["RESO_CLIENT_SECRET"]}, timeout=30).json()
h = {"Authorization": f"Bearer {tok['access_token']}"}
since = "2026-01-01T00:00:00Z" # the mark saved by your last run
url, params, n = f"{R}/Property", {"$filter": f"ModificationTimestamp gt {since}", "$expand": "Media", "$top": "200"}, 0
while url:
page = requests.get(url, headers=h, params=params, timeout=60).json()
for p in page["value"]:
n += 1
since = max(since, p["ModificationTimestamp"]) # store your record here (upsert by ListingKey)
url, params = page.get("@odata.nextLink"), None # nextLink already carries every parameter
print(n, "changes; next mark:", since)Listings leaving the market change to Closed or Withdrawn (broker scope) or stop appearing (idx scope): with idx scope, delete from your copy whatever is missing from a periodic full download.
Partner rules
- Credit the listing agent (
ListAgentFullName,ListOfficeName) and link toListingURL. - Refresh at least every 12 hours and remove what is no longer active.
- Do not re-syndicate to third parties without a written agreement.
- Partner program: /partners · contacto@multilistado.mx.