RESO Web API Integration
RESO's own wiki defines ListingKey as a local key of the system that issued it, not the durable, cross-system identifier most integration plans assume. This guide works through the ten decisions a RESO Web API integration gets wrong on the second MLS: ModificationTimestamp polling against the EntityEvent alternative, a Lookup layer that resolves a local synonym back to one standard value and what a "RESO certified" server actually promises to expose.
- ListingKey is a local key, not a durable identity anchor RESO's own field definition scopes it to the system that issued it, so a merge across two MLS feeds that treats it as a global primary key can silently collapse two different listings into one.
- ModificationTimestamp replication drops updates without a trace Clock skew between producer and consumer silently loses a window of changes, which is the exact gap RESO's own EntityEvent proposal was written to close.
- LookupValue alone breaks on the second MLS A mapping built on the display string works for one provider and stops joining the moment a second provider sends a local synonym for the same concept.
- Certification asserts a narrow, specific baseline A certified server has to expose only a short fixed list of resources, so "RESO certified" is never a guarantee that every field a project needs is actually there.
- The ratified Web API Core 2.1.0 specification link is dead RESO's own ratified-standards table points a 2.1.0 reader at a branch that returns 404, which is why every quotation in this guide is drawn from the working 2.0.0 text instead.
RESO Web API integration is usually pitched as an OData learning curve: a metadata endpoint, a bearer token, a handful of query operators. The part that actually breaks a production feed sits one layer under that, in how a consumer decides what changed since the last pull. The RESO EntityEvent specification opens by naming the business case directly: "One of the most common business cases for real estate data is the replication of listing and other data between producers and consumers." It then states, in RESO's own words, why the mechanism almost every live RESO integration still runs on is fragile: "The current state of the art is to use modification timestamps, long polling, and state inference to synchronize data. This places an additional burden on the data consumer to know the state of the producer and coordinate timestamps across systems and vendors. It also results in the need to frequently resynchronize data, as it can become difficult or impossible to resolve timestamps or reconcile the current state of a given system." That is not a competitor's complaint about RESO. It is RESO describing the exact pattern a production integration has to get right.
In short: the decision table below holds the ten integration decisions that follow. Two are the ones a rushed integration gets wrong first. Identity is keyed on a record that is local to the system that served it, not on a value RESO documents as durable or globally unique. And change detection runs on timestamp polling in almost every feed still in production, exactly the pattern RESO's own specification just described as fragile.
What RESO Web API actually is, and what it replaced
RESO positions the Web API as the industry's default transport rather than one option among several. Its own program page states it as a design goal: "The RESO Web API is the modern way to transport data in the real estate industry. It is built on well-known, open technology standards so that any organization can use it to deliver or receive data quickly and efficiently." The document it replaces is named just as directly: "Companies are moving away from older, deprecated data transports like the Real Estate Transaction Standard (RETS) and transitioning to the RESO Web API for its superior technology benefits." RESO's own description of RETS is blunter still: "RETS is an older standard that is proprietary to the real estate industry. It has been deprecated and is no longer supported by RESO because the industry needs to move to a more well-known technology standard." A team maintaining a RETS integration today is maintaining a standard its own governing body has retired.
The specification is precise about what building on open standards means in practice: "The Web API Core Endorsement provides a subset of functionality from the OASIS OData specification relevant to those who need to perform live queries or replicate data using the RESO Web API." A conformant server carries two blanket obligations at once. It must follow OData conventions: "RESO Web API servers MUST conform to OData conventions with respect to metadata, query, and response formats as well as HTTP, TLS, and OAuth2 for application layer protocol, transport security, and authentication requirements." And it must run over HTTPS: "A compatible RESO Web API server MUST use HTTPS as the protocol declared by the server URL." The first request any integration makes is to metadata, and OData fixes where that lives: "In OData, metadata is always located at the path /$metadata relative to the provider's service root URL." Read /$metadata before writing a single line of mapping code. It is the advertised shape of everything the feed will send.
RESO itself does not hand out data. Its program page is explicit about the boundary: "RESO does not provide MLS real estate data. RESO creates data standards. Other organizations build technology based upon those standards. Data requests should be made to MLSs." A RESO Web API integration project starts with a licensing conversation, not a developer signup form.
Integration decisions and their failure modes
The options column below is sourced from the specifications named throughout. The choose-when and failure-mode columns are Pharos Production practice, not requirements of the standard itself. Every section after the table works through one row in full.
| Integration decision | Options | Choose when | Failure mode seen in practice |
|---|---|---|---|
| Access pattern | Live query against the resource endpoint; incremental replication on ModificationTimestamp; full refresh | Live query for low volume lookups against a license that allows it, replication for anything a search UI ranks over | The team ships live query, hits the provider's page size and complexity limits at production traffic and rewrites as replication after launch |
| Change detection | ModificationTimestamp polling; the EntityEvent append-only log; OData delta links | EntityEvent where the provider exposes it, timestamps otherwise with the cost priced in | Clock skew between producer and consumer silently drops a window of updates and nothing in the payload reveals the loss |
| Paging | Follow @odata.nextLink; drive $top and $skip directly | Always follow the next link; use $top and $skip only where the provider documents a skip depth | A client appends $select or $orderby to a next link, the service rejects or silently reorders it and the replication set comes back incomplete |
| Ordering guarantee | $orderby on an indexed timestamp; rely on the service's default order | Always impose an explicit $orderby before paging anything | No $orderby is set, the service's own stable ordering is not the one assumed and rows are fetched twice or never |
| Enumeration handling | Edm.EnumType members; Edm.String with the Lookup resource | Build for Edm.String plus Lookup, keep an Edm.EnumType path for older feeds | Values are stored as received, a second MLS sends a local synonym for the same concept and the two feeds stop joining |
| Lookup freshness | Re-pull all metadata on a schedule; synchronise the Lookup resource on its own ModificationTimestamp | Synchronise incrementally, keep a full metadata pull as a fallback rather than a schedule | The lookup table is loaded once at integration time, a new value appears months later and rows carrying it are dropped as invalid |
| Field contract | Standard Data Dictionary fields; local extensions | Standard fields for anything cross-MLS, local fields behind an explicit per-provider mapping | A local field is treated as standard, and the second MLS integration discovers the mapping was never portable |
| Identity | ListingKey as the local system key; ListingId as the human-facing value | Key joins on ListingKey scoped to its source system, never assume it is durable or unique elsewhere | A merge of two MLS feeds treats ListingKey as a global primary key and silently collapses two different listings |
| Authentication | OAuth2 Bearer token; Client Credentials | Client Credentials wherever offered, for unattended refresh | A static bearer token sits in configuration, expires outside business hours and the nightly replication job fails silently until someone checks |
| Certification expectation | Web API Core certified; Data Dictionary certified | Treat the two as separate assertions and ask which resources were actually certified | RESO certified is read as "every field will be there," and the first mapping pass finds a needed field missing from the advertised metadata |
The Data Dictionary is the field contract, not a suggestion
ddwiki gives the shortest correct definition: "The RESO Data Dictionary defines standard resources, fields and lookups for the exchange of real estate data." The specification itself is more specific about the three element classes: "The Data Dictionary endorsement defines models for use in the RESO domain. These include Resources, Fields, Lookups, and Relationships between Resources." Interoperability is the stated point of having them at all: "The primary goal of the RESO Data Dictionary is interoperability through the consistent use of standard data elements." The alternative, which is what the Dictionary is standing in for, is named plainly: "While the Web API Server specification ensures that servers can talk to each other in a uniform manner, if they are using different fields to represent the same data, it causes additional effort where mapping is concerned."
Three versions of the Dictionary are live at once, and they carry different obligations. DD 1.7 is Legacy, approved December 18, 2018, covering 26 Resources, 1,333 Fields and 2,920 Lookups. DD 2.0 is Active, approved October 23, 2023, covering 41 Resources, 1,745 Fields and 3,683 Lookups: "DD 2.0 is the current version for RESO certification." DD 2.1 is Draft, in development, already scoped to 43 Resources, 2,140 Fields and 4,129 Lookups. DD 2.0 also tightened what a provider is allowed to send without telling a consumer first: "In Data Dictionary 2.0, these two items MUST match. This means that providers will fail testing if resources, fields, or enumerations appear in the data set that weren't advertised on the server." That sentence is the whole justification for reading /$metadata before writing a mapping, rather than after a field turns up unmapped in production.
Replication: why ModificationTimestamp is fragile, and what EntityEvent proposes

RESO's proposed replacement for timestamp polling is an append-only log: "The RESO EntityEvent Resource provides an efficient way to represent event streams and replicate data by using the interface of an append-only log." Its ordering guarantee is what makes replay safe: "EntityEventSequence is a durable, immutable, monotonic identifier that preserves the order that events occur in a system. It can only increase in value." The obligation on the producer side supports exactly that. Providers, the specification says, should order events so that "When possible, providers should order events to simplify referential integrity for data consumers so they can replay events without underlying business knowledge of internal system relationships."
Two different consumer shapes fall out of the same log: "Most consumers will be synchronizing with the most current state of the system." And: "A much smaller number of consumers will be collecting state changes throughout history to create analytics." A withdrawn listing is not announced as a delete either way. It is inferred: "Based on business rules, EntityEvent records may change the availability and visibility for consumers based on the role of a consumer." The consuming side is then left to read a state change rather than an event type: "The consumer does not know what has happened to the record, only that the record has changed state." A reconciliation pass that treats a record's disappearance from the feed as the delete signal, run on a schedule rather than only on a missed event, is Pharos Production practice for exactly this reason. Nothing in the payload distinguishes a withdrawn listing from a listing the consumer's own poll simply missed.
Identity: ListingKey is not the anchor it looks like
Most integration plans for a merged-data platform treat ListingKey as a durable identity anchor: assign it as a primary key, join everything else on it, done. ddwiki's own field definition does not support that reading. Its definition of ListingKey is scoped, not global: "A unique identifier for this record from the immediate source. This is a string that can include a Uniform Resource Identifier (URI) or other forms. This is the local key of the system. When records are received from other systems, a local key is commonly applied. If conveying the original keys from the source or originating systems, see SourceSystemKey and OriginatingSystemKey." Nothing in that definition says the value is stable over time, and nothing in it says the value is unique outside the system that assigned it. It is the local key of the system, which is a narrower and more useful claim than simply the identifier for this listing.
The field a user actually sees on a listing sheet is the one RESO itself flags as the less reliable of the two. ListingId is defined as the human-facing value: "The well-known identifier for the listing. The value may be identical to that of the Listing Key, but the Listing ID is intended to be the value used by a human to retrieve the information about a specific listing. In a multiple originating system or a merged system, this value may not be unique and may require the use of the provider system to create a synthetic unique value." Read the last clause carefully. The identifier a person reads off a listing page is the one ddwiki explicitly says can collide once more than one originating system feeds the same store.
The practical consequence for a merged-data platform is a join key, not a primary key. A record's durable cross-system reference sits in SourceSystemKey and OriginatingSystemKey, the fields ListingKey's own definition points to for exactly this case. Pharos Production practice on a multi-MLS integration is to key storage on a composite of ListingKey and the source system, carry SourceSystemKey and OriginatingSystemKey wherever the feed provides them and treat a bare ListingKey collision across two feeds as an open question rather than an automatic merge. The failure mode the decision table above names under identity, two listings silently collapsed into one because ListingKey was read as a global key, raises no error and violates no constraint anywhere in the pipeline. It surfaces only if and when a downstream consumer notices two records describing the same property, which is what makes it dangerous: nothing in the data itself flags the merge.
The query surface: filter, select, expand and paging
$filter and $orderby carry the query itself. OASIS's OData 4.01 protocol specification states the first plainly: "The $filter system query option restricts the set of items returned." Web API Core narrows what a certified server has to support under $filter to operators for "This includes logical operators such as AND, OR, and NOT, as well as greater than, greater than or equal, less than, less than or equal, and not equals for applicable OData primitive types, and query support for enumerations." String search is the omission that matters most when scoping a search UI, and it is not in that list: "String query operators are not part of the RESO Web API Core specification at this time." A contains() search on a remarks field is a real request against a real listing description, and Core simply does not require a server to support it.
$select and $expand shape the payload rather than the result set. OASIS's $select does one job: "The $select system query option requests that the service return only the properties, dynamic properties, actions and functions explicitly requested by the client. The service returns the specified content, if available, along with any available expanded navigation or stream properties, and MAY return additional information." $expand does the neighboring one: "The $expand system query option indicates the related entities and stream values that MUST be represented inline. The service MUST return the specified content, and MAY choose to return additional information." This is the mechanism behind every joined-in Media or Office record a client asks for in one round trip.
Paging is server-driven end to end. OData requires a partial response to carry a link to the next page of results, and requires the final page to omit that link entirely. That link is not for a client to modify: "OData clients MUST treat the URL of the next link as opaque, and MUST NOT append system query options to the URL of a next link." The token inside it belongs to the service alone, never to the client: "OData clients MUST NOT use the system query option $skiptoken when constructing requests." RESO's own profile leaves the $skip depth limit to each provider to set. A client that appends $select to a next link, or rebuilds a skip token by hand after a code change, gets a next link the service can reject or reinterpret, and the replication set comes back incomplete with no error surfaced to the caller.
Lookups and enumerations: one value, several strings
Lookups exist because RESO's own reasoning about metadata size left no other option: "In systems that cover large geographic areas, the amount of metadata can grow quite large. This is due to the fact that there are lookups for cities, counties, subdivisions, etc. for each of the areas a given vendor covers, making it impractical to deliver this information through a static OData XML metadata document." The Core specification states plainly: "Enumerations define the allowed values in a given lookup field." And a field can carry one value or several: "They can either be single enumerations, where only one value is allowed within a given field, or multiple enumerations, in which case there is a list of values."
The Lookup resource's own field definitions are why a naive mapping breaks the first time a second MLS is added. LookupValue is defined as "The human-friendly display name the data consumer receives in the payload and uses in queries." That is the value most integrations map on. StandardLookupValue exists because that is not safe: "This field is required when a given enumeration is a standard lookup value, regardless of the value in LookupValue." It is RESO's own answer for a local synonym that has to resolve back to one standard concept. LegacyODataValue carries the migration history most integrations never planned for: "This value is optional, and has been included in order to provide a stable mechanism for translating OData lookup values to RESO standard lookup display names, as well as for historical data that might have included the OData value at some point, even after the vendor had converted to human friendly display names." A field mapped on LookupValue alone works for one provider and breaks the moment a second one sends a synonym for the same concept.
Authentication, certification and what RESO certified actually asserts
Authentication is narrower than the OAuth2 label suggests. The specification names exactly two accepted mechanisms: "At the time of writing, the RESO Web API uses the OAuth2 Bearer Token and Client Credentials standards for authorization." Client Credentials itself is defined as "A type of authorization grant that uses a client_id and client_secret (essentially username and password) as an additional layer of security in order to provide a Bearer Token upon request." Certification does not test anything wider: "As of Web API 1.0.2, RESO only supports Bearer tokens and Client Credentials during Certification."
A certified server has to expose at least one resource from a fixed, short list: "Web API Servers MUST expose at least one Property, Member, Office, Media, or InternetTracking Data Dictionary resource in order to be certified." Local extension is explicitly allowed on top of that baseline: "Servers MAY support local resources, fields, or lookups that don't follow the RESO Data Dictionary specifications, and may extend any of the existing standard resources or lookups with their own localized values, except where otherwise noted." Certification itself runs on tooling rather than manual review: RESO's own RESO Commander, a Java-based OData client and command-line tool, checks that a server supports the required query operations correctly, and RESO already plans to deprecate it in favor of the RESO SDK.
Anyone who has read a competing page about RESO certification has probably read about Gold or Platinum tiers. That ladder is gone. The specification states its own replacement plainly: "Removed metallic certification levels in favor of modular Endorsements to provide additional functionality." That change sits inside a Core specification meant to hold still: "The goal of the Web API 2.0.0 Core specification is to provide a common, stable set of authentication protocols and API functionality to meet the needs of the real estate industry, with the intent that the Core specification will rarely change going forward." New functionality now ships as separate, addable specifications instead: "Endorsements will be used to provide additional functionality to the Core specification in a modular manner and treated as separate specifications with their own dependencies, one of which may or may not be a dependency on Web API Core." A page still describing metallic tiers is describing a certification model RESO itself retired. One honesty note: Web API Core exists at 2.0.0 (ratified January 2021) and 2.1.0 (ratified December 2023). Every quotation here is 2.0.0, since the 2.1.0 text at https://raw.githubusercontent.com/RESOStandards/transport/22-web-api-core-210-specification/web-api-core.md returned HTTP 404 on a check run 2026-09-21.
How Pharos Production helps
A RESO Web API integration is rarely the finish line. It is the intake layer for a CRM, a search product or a portal that assumes clean, deduplicated listing data. Our real estate software development guide covers the platform layer this feed lands in, and where a synced listing needs to reach agent workflows and lead records, our CRM development guide covers that side of the pipeline.
Our real estate software development team builds the parts that actually decide whether a RESO integration survives contact with a second MLS: replication built on EntityEvent where it is available and on disciplined ModificationTimestamp polling where it is not, identity resolution that respects ListingKey's real scope instead of the scope a diagram implies and a lookup layer that resolves a local synonym back to one standard value before it ever reaches a search index.
Sources: RESO on reso.org (the Web API program page); the RESO Data Dictionary wiki on ddwiki.reso.org (the Data Dictionary overview and the ListingKey and ListingId field pages); the RESO transport specifications index, the Web API Core Specification, the Data Dictionary Endorsement specification and the EntityEvent Resource and Replication Model, all on raw.githubusercontent.com under RESOStandards/transport and RESOStandards/reso-transport-specifications; the RESO Commander repository on github.com; OASIS, OData Version 4.01 Part 1: Protocol. Read 21 September 2026. Engineering guidance, not legal or certification advice.
FAQ
Quick answers to common questions about custom software development, pricing, process and technology.
Type to filter questions and answers. Use Topic to narrow the list.
Showing all 6
No matches
Try a different keyword, change the topic or clear filters
-
Does "RESO certified" mean every field this guide describes will be there?
No, and reading it that way conflates what a server is certified for with what the Data Dictionary defines, a distinction worth checking explicitly in our experience at Pharos Production. Certification tests a fixed, short list of required resources and query operations, not the full Data Dictionary.
A server can be Web API Core certified while exposing only Property and Media, or Data Dictionary certified for a narrower resource set than a project assumed going in. Ask which resources and which Dictionary version were actually certified before a mapping pass starts, rather than after a needed field turns up missing.
-
What is the actual difference between ListingKey and ListingId?
ListingKey is the local key of the system that issued it, scoped to that system and not guaranteed durable or unique anywhere else. ListingId is the human-facing value a person reads off a listing sheet, and RESO's own field definition warns that it may not be unique once more than one originating system feeds a merged store.
Neither field is a safe global primary key on its own. A durable cross-system reference belongs in SourceSystemKey and OriginatingSystemKey, the fields both definitions point back to.
-
Is ModificationTimestamp polling still a valid way to replicate RESO data?
It is still the pattern almost every live integration runs on, and RESO's own EntityEvent specification names the reason it needs a successor: clock skew between producer and consumer can silently drop a window of updates, and nothing in a timestamp-polling payload reveals the loss. EntityEvent is RESO's proposed replacement, an append-only log with a durable, monotonic sequence a consumer can replay without reconstructing state from timestamps.
Where a provider does not yet expose EntityEvent, disciplined polling with an explicit reconciliation pass is still the working alternative, not a broken one.
-
Can a certified server add fields the Data Dictionary does not define?
Yes. Local extension is explicitly allowed on top of the certified baseline, and the specification says so directly: a server may support local resources, fields or lookups outside the Data Dictionary and may extend standard resources with its own localized values.
The risk sits on the consuming side, not the producing one. A local field treated as though it were standard is exactly the assumption that breaks the moment a second MLS integration is added, because the mapping was never portable to begin with.
-
Which Web API Core version should a new integration target, 2.0.0 or 2.1.0?
2.0.0, as a practical matter, in our experience at Pharos Production. It is the ratified, current Core specification, and DD 2.0 is the current version RESO certifies against.
A newer Core specification exists on paper and carries a later ratification date, but the branch RESO's own ratified-standards table links to for it returns a 404, which makes 2.0.0 the version an integration can actually read, cite and test against today.
-
What happens when two MLS feeds send different lookup values for the same real-world concept?
The join breaks, unless the mapping was built for it from the start. LookupValue is the human-friendly string most integrations map on first, and it is exactly the field two providers are free to populate with different local synonyms for one standard concept.
RESO's own Lookup resource carries StandardLookupValue for this reason, a field that has to resolve back to one standard value regardless of what LookupValue says. A mapping built on LookupValue alone works for one provider and breaks the day a second one is added.
I work with startup founders who need a dedicated software development team but don’t want to gamble on hiring, random outsourcing, or opaque delivery.
Most founders face the same problem sooner or later.
Early technical and team decisions lock the product into tech debt, slow delivery, missed milestones and constant re-hiring. By the time this becomes visible, fixing it is already expensive.As a CTO and software architect, I help founders design, build and run dedicated development teams that work as a true extension of the startup. Not as a black-box vendor.
My focus is on complex products where mistakes are costly:
- Web3 and blockchain platforms
- FinTech and regulated products
- High-load startup systems
- MVP → scale transitions
We don’t do body-shopping.
We don’t sell generic outsourcing.Instead, we help founders:
- build the right team structure from day one
- keep technical ownership and transparency
- scale delivery without losing control
- avoid vendor lock-in and hidden risks
Teams are aligned with the product roadmap, business goals and long-term architecture. Not just short-term velocity.