Quick Start
Get up and running in under a minute.
Register
Send your email to the register endpoint. We'll send a 6-digit verification code.
Verify
Submit the code to receive your API key. Save it — it won't be shown again.
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
- 401 Unauthorized — Missing or invalid API key
- 403 Forbidden — Key has been deactivated
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.
Search and filter active rental listings across the Salt Lake Valley. Results are paginated.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| city | string[] | — | Filter by city slug(s). Repeat param or comma-separate. |
| min_price | int | — | Minimum monthly price |
| max_price | int | — | Maximum monthly price |
| min_beds | int | — | Minimum bedrooms |
| max_beds | int | — | Maximum bedrooms |
| source | string | — | Filter by source: rentler, ksl, craigslist |
| min_score | int | — | Minimum heuristic score |
| keyword | string | — | Search title and description (case-insensitive) |
| sort | string | score_desc | price_asc, price_desc, score_desc, newest |
| offset | int | 0 | Pagination offset |
| limit | int | 25 | Results 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 a single listing by ID, including description, amenities, photos, and price history.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | int | The 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.
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}
]
}
Percentile-based market rates broken down by city and bedroom count. Use this to understand what a fair price looks like.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| city | string | — | Filter by city slug |
| beds | int | — | Filter 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
}
]
}
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
}
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
| Field | Type | Required | Description |
|---|---|---|---|
| string | Yes | Your email address | |
| name | string | No | Name 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"
}
Submit the 6-digit code from your email to complete registration and receive your API key. No authentication required.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| string | Yes | The email you registered with | |
| code | string | Yes | 6-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.
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.
Rate Limit Headers
Every response includes these headers so you can track your usage:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests allowed in the current window |
| X-RateLimit-Remaining | Requests remaining in the current window |
| X-RateLimit-Reset | Unix 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
