WannaTapThat API

Programmatic access to Salt Lake Valley rental listings, market rates, and deal scores.

REST / JSON v1 Free tier

Quick Start

Get up and running in under a minute.

1

Register

Send your email to the register endpoint. We'll send a 6-digit verification code.

2

Verify

Submit the code to receive your API key. Save it — it won't be shown again.

3

Query

Pass your key in the X-API-Key header and start pulling data.

Your first request

# Register for an API key
curl -X POST https://wannatapthat.com/api/v1/register \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "name": "My App"}'

# Verify with the code from your email
curl -X POST https://wannatapthat.com/api/v1/verify-key \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "code": "123456"}'
# => {"api_key": "wtt_a1b2c3d4...", "message": "Save this key..."}

# Fetch the best deals in Sandy
curl https://wannatapthat.com/api/v1/listings?city=sandy&sort=score_desc \
  -H "X-API-Key: wtt_YOUR_KEY_HERE"

Authentication

All data endpoints require an API key. Registration and verification endpoints are open.

API Key Format

Keys are prefixed with wtt_ followed by 32 random hexadecimal characters:

wtt_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6

Passing Your Key

Include it as an X-API-Key header on every request:

curl https://wannatapthat.com/api/v1/listings \
  -H "X-API-Key: wtt_YOUR_KEY_HERE"

Error Responses

Key Rotation

If your key is compromised, rotate it immediately. The old key is invalidated and a new one is returned:

curl -X POST https://wannatapthat.com/api/v1/rotate-key \
  -H "X-API-Key: wtt_YOUR_OLD_KEY"
# => {"api_key": "wtt_new_key_here...", "message": "Old key invalidated."}

Endpoints

All endpoints are under https://wannatapthat.com/api/v1/. Click an endpoint to expand details.

GET /api/v1/listings Search active rental listings

Search and filter active rental listings across the Salt Lake Valley. Results are paginated.

Query Parameters

ParameterTypeDefaultDescription
citystring[]Filter by city slug(s). Repeat param or comma-separate.
min_priceintMinimum monthly price
max_priceintMaximum monthly price
min_bedsintMinimum bedrooms
max_bedsintMaximum bedrooms
sourcestringFilter by source: rentler, ksl, craigslist
min_scoreintMinimum heuristic score
keywordstringSearch title and description (case-insensitive)
sortstringscore_descprice_asc, price_desc, score_desc, newest
offsetint0Pagination offset
limitint25Results per page (max 100)

Example Request

curl "https://wannatapthat.com/api/v1/listings?city=sandy&max_price=1400&min_beds=2&sort=score_desc" \
  -H "X-API-Key: wtt_YOUR_KEY"

Example Response

{
  "listings": [
    {
      "id": 123,
      "title": "Updated 2BR Near TRAX Station",
      "address": "123 Main St",
      "city": "sandy",
      "state": "UT",
      "zip": "84070",
      "beds": 2,
      "baths": 1.0,
      "sqft": 852,
      "price": 1349,
      "deposit": null,
      "source": "ksl",
      "url": "https://homes.ksl.com/listing/123",
      "heuristic_score": 50,
      "first_seen": "2026-03-17",
      "last_seen": "2026-03-27"
    }
  ],
  "total": 247,
  "offset": 0,
  "limit": 25
}
GET /api/v1/listings/{id} Single listing with full details

Get a single listing by ID, including description, amenities, photos, and price history.

Path Parameters

ParameterTypeDescription
idintThe listing ID

Example Request

curl https://wannatapthat.com/api/v1/listings/123 \
  -H "X-API-Key: wtt_YOUR_KEY"

Example Response

{
  "id": 123,
  "title": "Updated 2BR Near TRAX Station",
  "address": "123 Main St",
  "city": "sandy",
  "state": "UT",
  "zip": "84070",
  "beds": 2,
  "baths": 1.0,
  "sqft": 852,
  "price": 1349,
  "deposit": null,
  "source": "ksl",
  "url": "https://homes.ksl.com/listing/123",
  "heuristic_score": 50,
  "first_seen": "2026-03-17",
  "last_seen": "2026-03-27",
  "description": "Newly updated unit with in-unit laundry...",
  "amenities": {
    "laundry": "in-unit",
    "parking": "covered",
    "pets": "cats only"
  },
  "photos": [
    "https://images.ksl.com/photo1.jpg",
    "https://images.ksl.com/photo2.jpg"
  ],
  "price_history": [
    {
      "old_price": 1450,
      "new_price": 1349,
      "changed_at": "2026-03-20T14:30:00Z"
    }
  ]
}

Returns 404 if the listing ID does not exist.

GET /api/v1/cities City stats and listing counts

List all cities with active listing counts and median prices.

Example Request

curl https://wannatapthat.com/api/v1/cities \
  -H "X-API-Key: wtt_YOUR_KEY"

Example Response

{
  "cities": [
    {"city": "sandy", "active_listings": 34, "median_price": 1395},
    {"city": "midvale", "active_listings": 22, "median_price": 1250},
    {"city": "murray", "active_listings": 19, "median_price": 1310},
    {"city": "west-jordan", "active_listings": 28, "median_price": 1425}
  ]
}
GET /api/v1/market-rates Market pricing by city and bedroom count

Percentile-based market rates broken down by city and bedroom count. Use this to understand what a fair price looks like.

Query Parameters

ParameterTypeDefaultDescription
citystringFilter by city slug
bedsintFilter by bedroom count

Example Request

curl "https://wannatapthat.com/api/v1/market-rates?city=sandy&beds=2" \
  -H "X-API-Key: wtt_YOUR_KEY"

Example Response

{
  "rates": [
    {
      "city": "sandy",
      "beds": 2,
      "p25_price": 1250,
      "median_price": 1395,
      "p75_price": 1500,
      "sample_size": 18
    }
  ]
}
GET /api/v1/stats Overall platform statistics

High-level statistics across all listings and sources.

Example Request

curl https://wannatapthat.com/api/v1/stats \
  -H "X-API-Key: wtt_YOUR_KEY"

Example Response

{
  "total_active": 487,
  "by_source": {
    "rentler": 210,
    "ksl": 145,
    "craigslist": 132
  },
  "avg_price": 1342,
  "last_scrape": "2026-03-27T06:00:00Z",
  "cities_covered": 12
}
POST /api/v1/register Register for an API key

Register for an API key. No authentication required. A 6-digit verification code will be sent to your email. Rate limited to 5 requests per hour per IP.

Request Body

FieldTypeRequiredDescription
emailstringYesYour email address
namestringNoName of your app or project

Example Request

curl -X POST https://wannatapthat.com/api/v1/register \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "name": "My Rental App"}'

Example Response

{
  "message": "Verification code sent to email"
}
POST /api/v1/verify-key Verify email and receive API key

Submit the 6-digit code from your email to complete registration and receive your API key. No authentication required.

Request Body

FieldTypeRequiredDescription
emailstringYesThe email you registered with
codestringYes6-digit verification code

Example Request

curl -X POST https://wannatapthat.com/api/v1/verify-key \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "code": "123456"}'

Example Response

{
  "api_key": "wtt_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "message": "Save this key — it won't be shown again."
}

The verification code expires after 10 minutes. If it expires, register again to receive a new code.

POST /api/v1/rotate-key Rotate a compromised key

Invalidate your current API key and receive a new one. Authenticate with your current key. Use this if a key has been leaked or compromised.

Example Request

curl -X POST https://wannatapthat.com/api/v1/rotate-key \
  -H "X-API-Key: wtt_YOUR_COMPROMISED_KEY"

Example Response

{
  "api_key": "wtt_f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4",
  "message": "Old key invalidated. Save this new key — it won't be shown again."
}

Code Examples

The same query — find 2-bedroom rentals under $1,400 in Sandy — in three languages.

curl -s "https://wannatapthat.com/api/v1/listings?city=sandy&max_price=1400&min_beds=2" \
  -H "X-API-Key: wtt_YOUR_KEY" | python3 -m json.tool
import requests

API_KEY = "wtt_YOUR_KEY"
BASE_URL = "https://wannatapthat.com/api/v1"

response = requests.get(
    f"{BASE_URL}/listings",
    headers={"X-API-Key": API_KEY},
    params={
        "city": "sandy",
        "max_price": 1400,
        "min_beds": 2,
    },
)
response.raise_for_status()
data = response.json()

for listing in data["listings"]:
    print(f"{listing['title']} — ${listing['price']}/mo (score: {listing['heuristic_score']})")

print(f"\n{data['total']} total results")
const API_KEY = "wtt_YOUR_KEY";
const BASE_URL = "https://wannatapthat.com/api/v1";

const params = new URLSearchParams({
  city: "sandy",
  max_price: "1400",
  min_beds: "2",
});

const response = await fetch(`${BASE_URL}/listings?${params}`, {
  headers: { "X-API-Key": API_KEY },
});

if (!response.ok) {
  throw new Error(`API error: ${response.status}`);
}

const data = await response.json();

for (const listing of data.listings) {
  console.log(`${listing.title} — $${listing.price}/mo (score: ${listing.heuristic_score})`);
}

console.log(`\n${data.total} total results`);

Rate Limits

The free tier is designed for personal projects and moderate usage.

100
Requests per hour
1,000
Requests per day

Rate Limit Headers

Every response includes these headers so you can track your usage:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets

429 Too Many Requests

When you exceed the limit, the API returns a 429 status with a Retry-After header:

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Try again in 45 seconds.",
    "retry_after": 45
  }
}

The Retry-After response header contains the number of seconds to wait before retrying.

Interactive Docs

Prefer to explore the API interactively? The auto-generated Swagger UI lets you try endpoints directly in your browser.

Open Swagger UI

Interactive API explorer with request builder and live responses at /docs