Developer Documentation

Integration guide for displaying Source MLS badges on listing pages

Overview

Source MLS verifies that listing data displayed on real estate websites originates from an authorized MLS. Data Licensees, such as IDX Vendors, display a small badge on each listing detail page indicating this fact. The verification works through a simple SourceMLSURL that flows from the MLS to data Licensees via the RESO data feed.

How It Works

  1. MLS generates a SourceMLSURL for each listing in their RESO data feed. The URL contains a JWT token that encodes the listing and licensee information.
  2. Licensees receive the SourceMLSURL as a field in the RESO feed data for each listing.
  3. Licensees display a badge on their listing detail pages using the SourceMLSURL. The badge loads a small image and confirms the impression back to Source MLS.

SourceMLSURL Format

https://sourcemls.org/api/v1/{jwt_token}/badge

The {jwt_token} is a signed JWT containing RESO Data Dictionary claims that identify the listing, licensee, and MLS organization. MLSs generate this token server-side using their organization secret key.

For Licensees

As a data Licensee, you receive a simple and unique SourceMLSURL for each listing in the RESO data feed from your MLS. To display the Source MLS badge on your listing detail pages, add the following <img> tag.

Badge Implementation

Place this tag on each listing detail page, using the SourceMLSURL from that listing's feed data:

<img src="{SourceMLSURL}.png"
     width="132"
     height="60"
     alt="Source MLS Verified"
     onload="navigator.sendBeacon('{SourceMLSURL}')"
     onerror="this.style.display='none'" />
How Each Attribute Works
Attribute Purpose
src="{SourceMLSURL}.png" Loads the Source MLS badge image from the API. Appending .png to the SourceMLSURL requests the PNG format. This image is cached by browsers to improve performance.
width="66" height="30" Sets the badge display size in pixels.
onload="navigator.sendBeacon('{SourceMLSURL}')" After the badge image loads, sends a fire-and-forget POST to confirm the impression. The impression is confirmed whether the image loads the first time or from browser cache.
onerror="this.style.display='none'" If the badge fails to load (network error, invalid token), hides the broken image element so it doesn't affect your page layout.
Example: If the SourceMLSURL for a listing is https://sourcemls.org/api/v1/eyJhbGc.../badge, your img tag src would be https://sourcemls.org/api/v1/eyJhbGc.../badge.png and the sendBeacon URL would be https://sourcemls.org/api/v1/eyJhbGc.../badge.
Important: Use the SourceMLSURL exactly as provided in the feed data. Do not modify the JWT token or construct URLs manually.
Styling Tip: The badge image has a transparent background. If your site has a dark background, add style="background:white" to the <img> tag to ensure the badge remains readable.
Sample Badge Image

There is a sample badge image at https://sourcemls.org/sample-badge.png that you can use for testing image sizing and placement before your MLS starts providing real SourceMLSURLs in the feed.

For MLSs

As an MLS Organization, you generate a simple SourceMLSURL for each listing in your RESO data feed. The URL contains a JWT token signed with your organization's secret key. It is unique for each listing and licensee combination, allowing you to track the source and location of each badge impression.

You will want to work with your RESO Vendor or data feed provider to implement this. The Source MLS system will provide you with a secret key and your RESO Unique Organization Identifier once you sign up. Share those with your RESO Vendor or data feed provider and they can take it from there using the instructions below.

JWT Token Generation

Create a JWT token with the following RESO Data Dictionary claims, signed using your organization's secret key with the HS256 algorithm.

Required Claims
Claim Type Description
SourceSystemID String Your MLS organization code. We use RESO's Unique Organization Identifier for this - see RESO UOI.
LicenseeID String Unique identifier for the licensee in your MLS system
LicenseeName String Display name for the licensee (e.g., company name)
ListingID String Unique listing identifier in your system
StandardStatus String Current listing status (Active, Pending, Sold, etc.). Use RESO Data Dictionary definition.
ModificationTimestamp ISO 8601 When the listing was last modified. Use RESO Data Dictionary definition.
Auto-Creation: If a LicenseeID or ListingID doesn't exist in Source MLS, it will be automatically created on first badge request. This enables zero-friction integration.
No Expiration Required: Tokens do not require an exp claim.

Assembling the SourceMLSURL

After generating the JWT token, construct the SourceMLSURL:

https://sourcemls.org/api/v1/{jwt_token}/badge

Include this URL as a field in the RESO feed data for each listing.

Security Note: Never expose your MLS organization's secret key in client-side code. Generate tokens on your server when populating the RESO feed.

Sample Code

JavaScript (Node.js)
const jwt = require('jsonwebtoken');

// Generate JWT token with RESO claims
const payload = {
  SourceSystemID: 'your-org-code',
  LicenseeID: 'LIC-12345',
  LicenseeName: 'ABC Realty Group',
  ListingID: 'MLS-2026-12345',
  StandardStatus: 'Active',
  ModificationTimestamp: new Date().toISOString()
};

const token = jwt.sign(payload, process.env.MLS_SECRET_KEY, { algorithm: 'HS256' });

// Assemble the SourceMLSURL
const sourceMLSURL = `https://sourcemls.org/api/v1/${token}/badge`;
Ruby
require 'jwt'

# Generate JWT token with RESO claims
payload = {
  SourceSystemID: 'your-org-code',
  LicenseeID: 'LIC-12345',
  LicenseeName: 'ABC Realty Group',
  ListingID: 'MLS-2026-12345',
  StandardStatus: 'Active',
  ModificationTimestamp: Time.current.iso8601
}

token = JWT.encode(payload, ENV['MLS_SECRET_KEY'], 'HS256')

# Assemble the SourceMLSURL
source_mls_url = "https://sourcemls.org/api/v1/#{token}/badge"
Python
import jwt
import os
from datetime import datetime

# Generate JWT token with RESO claims
payload = {
    'SourceSystemID': 'your-org-code',
    'LicenseeID': 'LIC-12345',
    'LicenseeName': 'ABC Realty Group',
    'ListingID': 'MLS-2026-12345',
    'StandardStatus': 'Active',
    'ModificationTimestamp': datetime.utcnow().isoformat() + 'Z'
}

token = jwt.encode(payload, os.environ['MLS_SECRET_KEY'], algorithm='HS256')

# Assemble the SourceMLSURL
source_mls_url = f'https://sourcemls.org/api/v1/{token}/badge'

JWT Checker Tool

Use our online JWT checker to validate and decode your tokens during development.

Open JWT Checker Tool

Exporting Data

Vendors can programmatically export usage data from Source MLS using the Export API. This enables automated reporting workflows using the same MLS secret key used to generate SourceMLSURLs.

How It Works

  1. Request an export by sending a POST to /api/v1/export with your MLS secret key, export type, time frame, and optional file format.
  2. Receive an export ID immediately in the response. Since these files can be large and take time to generate, the export is processed asynchronously in the background.
  3. Poll for status by sending a GET to /api/v1/export/:id with your MLS secret key until the status is "completed". See details about a webhook alternative to polling below.
  4. Download the file using the download_url provided in the completed status response. The link expires after 72 hours.

POST /api/v1/export

Create a new export request. Returns 202 Accepted with an export ID for polling.

Parameters
Parameter Type Required Description
mls_secret_key String Yes Your MLS organization's secret key (the same key used to sign JWT tokens for SourceMLSURLs)
export_type String Yes Type of report to export (see valid types below)
time_frame String Yes Time range for the data (see valid time frames below)
file_format String No Output format: "csv" (default) or "json"
webhook_url String No HTTPS URL to receive a notification when the export is ready
Valid Export Types
Value Description
analytics_overview Analytics Overview — summary of badge impressions and activity
badge_requests_detail Badge Requests Detail — individual badge request records
licensee_summary Licensee Summary — aggregated data by licensee
Auto-discovery: New export types added to Source MLS are automatically available via the API. If you request an invalid type, the error response lists all currently valid types.
Valid Time Frames
Value Description
last_24_hoursLast 24 hours
last_7_daysLast 7 days (from beginning of day)
last_30_daysLast 30 days (from beginning of day)
current_monthCurrent month to now
last_monthFull previous month
Request Example
curl -X POST https://sourcemls.org/api/v1/export \
  -H "Content-Type: application/json" \
  -d '{
    "mls_secret_key": "your_secret_key_here",
    "export_type": "badge_requests_detail",
    "time_frame": "last_30_days",
    "file_format": "json"
  }'
Response (202 Accepted)
{
  "export_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "queued",
  "export_type": "badge_requests_detail",
  "file_format": "json",
  "message": "Export queued. Poll GET /api/v1/export/:id for status, or await webhook delivery."
}

GET /api/v1/export/:id

Check the status of an export request. Pass your mls_secret_key as a query parameter. The response fields vary based on the export status.

Request Example
curl "https://sourcemls.org/api/v1/export/EXPORT_ID?mls_secret_key=your_secret_key_here"
Response — Queued or In Progress
{
  "export_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "in_progress",
  "export_type": "badge_requests_detail",
  "file_format": "json"
}
Response — Completed
{
  "export_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "export_type": "badge_requests_detail",
  "file_format": "json",
  "download_url": "https://s3.amazonaws.com/...",
  "expires_at": "2026-03-14T12:00:00Z",
  "row_count": 1523,
  "file_size_bytes": 245678
}

When the status is "completed", use the download_url to download the file. The download link expires at the time shown in expires_at (72 hours after generation).

Response — Failed
{
  "export_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "failed",
  "export_type": "badge_requests_detail",
  "file_format": "json",
  "error_message": "An error occurred during export generation"
}

Polling Example

A simple polling loop to wait for an export to complete:

#!/bin/bash
# Request an export
RESPONSE=$(curl -s -X POST https://sourcemls.org/api/v1/export \
  -H "Content-Type: application/json" \
  -d '{
    "mls_secret_key": "your_secret_key_here",
    "export_type": "badge_requests_detail",
    "time_frame": "last_30_days"
  }')

EXPORT_ID=$(echo $RESPONSE | jq -r '.export_id')
echo "Export ID: $EXPORT_ID"

# Poll until complete
while true; do
  STATUS_RESPONSE=$(curl -s "https://sourcemls.org/api/v1/export/$EXPORT_ID?mls_secret_key=your_secret_key_here")
  STATUS=$(echo $STATUS_RESPONSE | jq -r '.status')

  if [ "$STATUS" = "completed" ]; then
    DOWNLOAD_URL=$(echo $STATUS_RESPONSE | jq -r '.download_url')
    echo "Download URL: $DOWNLOAD_URL"
    curl -o export.csv "$DOWNLOAD_URL"
    break
  elif [ "$STATUS" = "failed" ]; then
    echo "Export failed: $(echo $STATUS_RESPONSE | jq -r '.error_message')"
    break
  fi

  echo "Status: $STATUS - waiting..."
  sleep 5
done

Webhook Alternative

Instead of polling, you can provide a webhook_url in your export request. When the export completes, Source MLS will send a POST request to your webhook URL with the export details including the download_url.

curl -X POST https://sourcemls.org/api/v1/export \
  -H "Content-Type: application/json" \
  -d '{
    "mls_secret_key": "your_secret_key_here",
    "export_type": "analytics_overview",
    "time_frame": "current_month",
    "file_format": "csv",
    "webhook_url": "https://your-server.com/webhooks/export-ready"
  }'
Security: Keep your MLS secret key confidential. Never expose it in client-side code or public repositories. Make API calls from your server only.

Real-Time Stats

Get real-time badge request counts for your organization or individual listings. These lightweight endpoints return instantly and are designed for integration with vendor dashboards. They use the same MLS secret key as the Export API.

GET /api/v1/stats

Returns badge request counts for your entire MLS organization across four time windows.

Request Example
curl "https://sourcemls.org/api/v1/stats?mls_secret_key=your_secret_key_here"
Response (200 OK)
{
  "mls_code": "M00000389",
  "mls_name": "Doorify MLS",
  "counts": {
    "last_hour": 142,
    "last_24_hours": 3850,
    "last_7_days": 24300,
    "all_time": 185420
  }
}

GET /api/v1/stats/:listing_key

Returns badge request counts for a specific listing. The :listing_key is the listing's unique identifier (the same ListingID value used in your JWT tokens).

Request Example
curl "https://sourcemls.org/api/v1/stats/OC25012345?mls_secret_key=your_secret_key_here"
Response (200 OK)
{
  "mls_code": "M00000389",
  "mls_name": "Doorify MLS",
  "listing_key": "25012345",
  "counts": {
    "last_hour": 8,
    "last_24_hours": 45,
    "last_7_days": 312,
    "all_time": 2840
  }
}
Count Time Windows
Field Description
last_hourBadge requests in the trailing 60 minutes
last_24_hoursBadge requests in the trailing 24 hours
last_7_daysBadge requests in the trailing 7 days
all_timeTotal badge requests since the listing was first tracked

Error Handling

All API endpoints return standard HTTP status codes and a consistent JSON error format.

HTTP Status Codes

Status Code Meaning
200 Success
202 Accepted - Export queued for processing
204 No Content (sendBeacon POST success)
400 Bad Request - Required JWT claims missing
401 Unauthorized - Invalid credentials (JWT or MLS secret key)
403 Forbidden - Organization mismatch
404 Not Found - Resource doesn't exist
422 Unprocessable Entity - Invalid parameter value
500 Internal Server Error

Error Response Format

All errors return a JSON body with a consistent structure:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description",
    "details": "Additional context (may be null)"
  }
}

Error Codes

Code Status Applies To Description
MISSING_SECRET_KEY 401 Export, Stats No mls_secret_key parameter provided
INVALID_SECRET_KEY 401 Export, Stats Secret key is invalid or belongs to an inactive organization
INVALID_EXPORT_TYPE 422 Export Unrecognized export type (valid types listed in response)
INVALID_TIME_FRAME 422 Export Unrecognized time frame (valid frames listed in response)
INVALID_FILE_FORMAT 422 Export Unsupported file format (use csv or json)
EXPORT_NOT_FOUND 404 Export Export ID does not exist or belongs to another organization
LISTING_NOT_FOUND 404 Stats Listing key does not exist or does not belong to your organization

Need Help?

Can't find what you're looking for? Our support team is here to help.

Contact Support