6556479417
Features: - Full sync of NorthStar MLS properties via MLS Grid API v2 - Incremental sync using ModificationTimestamp - Local media download and storage - Rate limit compliance (2 req/sec, 7200/hr, 40000/day) - Sync state tracking with resume capability - WP-CLI commands: test, sync, status, stats, cache - Admin settings page with manual sync triggers - Public API functions: mls_get_properties, mls_get_property, etc. Database tables: - mls_properties: Listing data with full field mapping - mls_media: Downloaded images - mls_sync_state: Sync progress tracking - mls_rate_limits: API usage tracking - mls_sync_log: Debug logging Documentation: - docs/CLAUDE.md: AI development guide - docs/API.md: MLS Grid API reference - docs/USAGE.md: User documentation Tested: Connection, auth, sync 10 records, media download verified 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
299 lines
7.2 KiB
Markdown
299 lines
7.2 KiB
Markdown
# MLS Grid API Reference
|
|
|
|
Documentation for the MLS Grid API v2 used by this plugin.
|
|
|
|
## Base URL
|
|
|
|
```
|
|
https://api.mlsgrid.com/v2
|
|
```
|
|
|
|
## Authentication
|
|
|
|
Bearer token authentication via HTTP header:
|
|
|
|
```
|
|
Authorization: Bearer {access_token}
|
|
```
|
|
|
|
**Required Header:**
|
|
```
|
|
Accept-Encoding: gzip
|
|
```
|
|
|
|
The API requires gzip compression and will return error 400 without it.
|
|
|
|
## Rate Limits
|
|
|
|
| Limit | Value |
|
|
|-------|-------|
|
|
| Per Second | 2 requests |
|
|
| Per Hour | 7,200 requests |
|
|
| Per Day | 40,000 requests |
|
|
| Data Per Hour | 4GB |
|
|
|
|
Exceeding limits returns HTTP 429 and temporarily suspends access.
|
|
|
|
## Endpoints
|
|
|
|
### Property
|
|
|
|
```
|
|
GET /Property
|
|
```
|
|
|
|
Main endpoint for listing data.
|
|
|
|
**Query Parameters:**
|
|
- `$filter` - OData filter expression (required)
|
|
- `$expand` - Include related resources: Media, Rooms, UnitTypes
|
|
- `$top` - Records per page (max 5000, max 1000 with $expand)
|
|
- `$select` - Specific fields to return
|
|
- `$orderby` - Sort order
|
|
|
|
**Example:**
|
|
```
|
|
/Property?$filter=OriginatingSystemName eq 'northstar' and MlgCanView eq true&$expand=Media&$top=1000
|
|
```
|
|
|
|
### Member
|
|
|
|
```
|
|
GET /Member
|
|
```
|
|
|
|
Agent/member records.
|
|
|
|
### Office
|
|
|
|
```
|
|
GET /Office
|
|
```
|
|
|
|
Brokerage office records.
|
|
|
|
### OpenHouse
|
|
|
|
```
|
|
GET /OpenHouse
|
|
```
|
|
|
|
Open house event records.
|
|
|
|
### Lookup
|
|
|
|
```
|
|
GET /Lookup
|
|
```
|
|
|
|
Field value definitions. Query no more than once per day.
|
|
|
|
## OData Filter Syntax
|
|
|
|
### Operators
|
|
|
|
| Operator | Description | Example |
|
|
|----------|-------------|---------|
|
|
| eq | Equals | `City eq 'Austin'` |
|
|
| ne | Not equals | `Status ne 'Sold'` |
|
|
| gt | Greater than | `ListPrice gt 200000` |
|
|
| ge | Greater or equal | `BedroomsTotal ge 3` |
|
|
| lt | Less than | `ListPrice lt 500000` |
|
|
| le | Less or equal | `YearBuilt le 2020` |
|
|
| and | Logical AND | `City eq 'Austin' and BedroomsTotal ge 3` |
|
|
| or | Logical OR | Limited to 5 per query |
|
|
| in | In list | `City in ('Austin', 'Dallas')` |
|
|
|
|
### Required Filters
|
|
|
|
Every Property request MUST include:
|
|
|
|
```
|
|
OriginatingSystemName eq 'northstar'
|
|
```
|
|
|
|
For initial import, add:
|
|
```
|
|
MlgCanView eq true
|
|
```
|
|
|
|
### Timestamp Filters
|
|
|
|
For incremental sync:
|
|
```
|
|
ModificationTimestamp gt 2024-01-15T00:00:00.000Z
|
|
```
|
|
|
|
## Pagination
|
|
|
|
Responses include `@odata.nextLink` field containing URL for next page.
|
|
|
|
```json
|
|
{
|
|
"@odata.context": "...",
|
|
"value": [...],
|
|
"@odata.nextLink": "https://api.mlsgrid.com/v2/Property?$filter=...&$skip=1000"
|
|
}
|
|
```
|
|
|
|
Continue fetching until `@odata.nextLink` is absent.
|
|
|
|
## Property Fields
|
|
|
|
### Core Fields
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| ListingKey | string | Unique identifier |
|
|
| ListingId | string | MLS listing number |
|
|
| StandardStatus | string | Active, Pending, Closed, etc. |
|
|
| ListPrice | decimal | Listing price |
|
|
| ClosePrice | decimal | Sold price |
|
|
|
|
### Address Fields
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| StreetNumber | string | Street number |
|
|
| StreetName | string | Street name |
|
|
| StreetSuffix | string | St, Ave, Blvd, etc. |
|
|
| UnitNumber | string | Unit/apt number |
|
|
| City | string | City name |
|
|
| StateOrProvince | string | State abbreviation |
|
|
| PostalCode | string | ZIP code |
|
|
| CountyOrParish | string | County name |
|
|
| Latitude | decimal | GPS latitude |
|
|
| Longitude | decimal | GPS longitude |
|
|
|
|
### Property Details
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| PropertyType | string | Residential, Land, Commercial, etc. |
|
|
| PropertySubType | string | Single Family, Condo, etc. |
|
|
| BedroomsTotal | integer | Total bedrooms |
|
|
| BathroomsTotalInteger | integer | Total bathrooms |
|
|
| BathroomsFull | integer | Full bathrooms |
|
|
| BathroomsHalf | integer | Half bathrooms |
|
|
| LivingArea | integer | Square feet |
|
|
| LotSizeArea | decimal | Lot size |
|
|
| LotSizeUnits | string | Acres, SqFt |
|
|
| YearBuilt | integer | Year built |
|
|
| GarageSpaces | integer | Garage spaces |
|
|
|
|
### Description Fields
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| PublicRemarks | string | Property description |
|
|
| Directions | string | Driving directions |
|
|
|
|
### Agent/Office Fields
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| ListAgentKey | string | Listing agent ID |
|
|
| ListAgentMlsId | string | Agent MLS ID |
|
|
| ListOfficeKey | string | Listing office ID |
|
|
| ListOfficeName | string | Office name |
|
|
| ListOfficeMlsId | string | Office MLS ID |
|
|
|
|
### Timestamps
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| ModificationTimestamp | datetime | Last modified (use for sync) |
|
|
| PhotosChangeTimestamp | datetime | Media last changed |
|
|
| ListingContractDate | date | Listed date |
|
|
| CloseDate | date | Sold date |
|
|
| DaysOnMarket | integer | DOM count |
|
|
|
|
### MLS Grid Fields
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| MlgCanView | boolean | OK to display (false = delete) |
|
|
| MlgCanUse | array | Permitted use cases (IDX, VOW, etc.) |
|
|
| OriginatingSystemName | string | Source MLS identifier |
|
|
|
|
## Media (via $expand)
|
|
|
|
When using `$expand=Media`, each property includes Media array:
|
|
|
|
```json
|
|
{
|
|
"Media": [
|
|
{
|
|
"MediaKey": "abc123",
|
|
"MediaURL": "https://media.mlsgrid.com/...",
|
|
"Order": 1,
|
|
"ImageWidth": 1200,
|
|
"ImageHeight": 800,
|
|
"MediaModificationTimestamp": "2024-01-15T10:30:00Z"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Important:** MediaURL is for downloading only. Store images locally.
|
|
|
|
## Sync Strategy
|
|
|
|
### Initial Import
|
|
|
|
1. Query with `MlgCanView eq true` to get viewable records
|
|
2. Follow `@odata.nextLink` for pagination
|
|
3. Store `ModificationTimestamp` from last record
|
|
|
|
### Incremental Sync
|
|
|
|
1. Query with `ModificationTimestamp gt {last_timestamp}`
|
|
2. Do NOT filter by MlgCanView (need to see deletions)
|
|
3. If `MlgCanView = false`, delete local record
|
|
|
|
### Media Sync
|
|
|
|
1. Check `PhotosChangeTimestamp` on each property
|
|
2. If changed, replace all media for that listing
|
|
3. Match by `MediaKey`, download via `MediaURL`
|
|
4. Delete media where `MediaKey` no longer exists
|
|
|
|
### Error Recovery
|
|
|
|
Store `@odata.nextLink` after each page. On failure, resume from that URL.
|
|
|
|
## Best Practices
|
|
|
|
1. **Sequential requests only** - Do not parallelize API calls
|
|
2. **Respect rate limits** - 2 req/sec max, pause if approaching limits
|
|
3. **Use $expand wisely** - Reduces per-page limit from 5000 to 1000
|
|
4. **Store raw JSON** - Keep original response for debugging
|
|
5. **Query Lookup sparingly** - Once per day maximum
|
|
6. **Don't hotlink media** - Download and serve from local storage
|
|
|
|
## Error Responses
|
|
|
|
```json
|
|
{
|
|
"error": {
|
|
"code": 400,
|
|
"message": "Error description",
|
|
"target": "misc",
|
|
"details": []
|
|
}
|
|
}
|
|
```
|
|
|
|
| Code | Meaning |
|
|
|------|---------|
|
|
| 400 | Bad request (check filters, missing gzip) |
|
|
| 401 | Unauthorized (invalid token) |
|
|
| 429 | Rate limited (wait and retry) |
|
|
| 500+ | Server error (retry with backoff) |
|
|
|
|
## Resources
|
|
|
|
- [MLS Grid Documentation](https://docs.mlsgrid.com/)
|
|
- [API v2 Reference](https://docs.mlsgrid.com/api-documentation/api-version-2.0)
|
|
- [Best Practices Guide](https://www.mlsgrid.com/resources)
|