Arthabase Data API
Bali villa market data for developers: what is for sale, how Airbnbs perform, and nearby sale and rental comparables. Returns Pack bundles comps and market stats into one deal analysis call.
Quick start
Building an agent? See MCP docs for copy-paste server config.
-
Sign in to Arthabase Studio
Open studio.arthabase.com.
-
Create an API key
Settings → API. Create a key, then copy the secret once.
-
Try it
export ARTHABASE_API_KEY="ab_live_…"
export API="https://data.arthabase.com"
curl -sS -H "Authorization: Bearer $ARTHABASE_API_KEY" "$API/health"
{ "ok": true, "service": "arthabase-data-api", "env": "production" }
Auth & errors
Studio credits: Authorization: Bearer ab_live_… debits your IDR wallet.
x402: omit Authorization, pay USDC on Base when you get 402. Discovery: GET /v1/x402. MCP setup: /api/mcp/.
Errors return JSON like { "error": "…" }.
| Status | Meaning |
|---|---|
| 401 | Bad or missing key |
| 403 | Key not allowed for this route |
| 400 | Bad request / filters |
| 402 | x402 payment required, or insufficient wallet credits |
| 404 | Not found |
| 429 | Too many calls — respect Retry-After (20/min per API key or x402 IP) |
| 500 / 502 / 503 | Temporary failure — retry |
Which call should I use?
| I want… | Call |
|---|---|
| Area name for a map pin | POST /v1/geo/resolve |
| Sale market trends over months | POST /v1/analytics/supply |
| Airbnb market trends over months | POST /v1/analytics/str |
| Compare a few markets | POST /v1/analytics/compare |
| Nearby sale peers only | POST /v1/comps/sale |
| Nearby Airbnb peers only | POST /v1/comps/str |
| Full deal analysis pocket | POST /v1/returns-pack/raw or /elaborated |
| Browse villas for sale | GET /v1/supply/listings |
| Browse many Airbnbs | GET /v1/airbnb/listings |
| Deep dive one Airbnb | GET /v1/airbnb/listings/:id |
| Airbnb financials for 1–10 ids | POST /v1/airbnb/listings/history |
Filters
Shared fields on many POST bodies. Each endpoint lists which it accepts.
| Field | Plain English | Supply | STR |
|---|---|---|---|
location |
Map pin (lat / lng). Required for geo, comps, Returns Pack. |
Yes | Yes |
bedrooms |
Bed count 1–12. Required for comps / Returns Pack. | Yes | Yes |
contract |
leasehold or freehold. |
Yes | No |
ready_offplan |
offplan or ready. |
Yes | No |
market_id |
Area slug like canggu. |
Yes | Yes |
months |
History depth for monthly series. Default 12, max 60. | Yes | Yes |
bedrooms_min / bedrooms_max |
Bedroom range. Analytics and GET listings only (not comps). | Yes | Yes |
sale_price_usd · land_m2 · building_m2 |
USD / m² ranges as { "min"?, "max"? }. Sale tools. |
Yes | No |
adr_usd · occupancy_pct · revpar_usd |
STR ranges as { "min"?, "max"? }. Also rating, revenue_30d_usd. |
No | Yes |
GET listings use flat query params, e.g. sale_price_usd_min=150000.
Markets
Valid market_id values:
canggu ·
uluwatu ·
mengwi_coast ·
seminyak ·
jimbaran ·
sanur ·
ubud ·
kedungu ·
other
1. Health
/health
Example response
{ "ok": true, "service": "arthabase-data-api", "env": "production" }
Paid uptime check. Same auth as other catalog calls: API key (Rp 100) or x402 ($0.001). Counts toward the global 20 req/min bucket.
2. Geo resolve
/v1/geo/resolve
Send a map pin (lat / lng). Arthabase returns market, village, district, and regency labels. Use this when you only have a click on the map and need a market_id for analytics or other calls.
Filters / params
| Field | Notes |
|---|---|
location.latRequired | Latitude of the map pin. |
location.lngRequired | Longitude of the map pin. |
Request
curl -sS -X POST "$API/v1/geo/resolve" \
-H "Authorization: Bearer $ARTHABASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "location": { "lat": -8.6472, "lng": 115.1386 } }'
Example response
{
"product": "Geo Resolve",
"location": { "lat": -8.6472, "lng": 115.1386 },
"area": {
"market": "Canggu",
"village": "Berawa",
"district": "North Kuta",
"regency": "Badung"
}
}
3. Supply analytics
/v1/analytics/supply
For-sale market stats for one area: today’s price and days-on-market bands, month-by-month history, plus a short pulse of newly listed similar villas across Bali. Optional filters: bedrooms, contract, off-plan.
Pass filters.market_id or a location pin.
Filters / params
| Field | Notes |
|---|---|
filters.market_id | Area slug (e.g. canggu). Required if no location. |
location.lat / lng | Map pin. Required if no market_id. |
filters.bedrooms | 1–12. |
filters.contract | leasehold or freehold. |
filters.ready_offplan | offplan or ready. |
filters.months | History depth. Default 12, max 60. |
Request
curl -sS -X POST "$API/v1/analytics/supply" \
-H "Authorization: Bearer $ARTHABASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"market_id": "canggu",
"bedrooms": 2,
"contract": "leasehold",
"ready_offplan": "offplan",
"months": 24
}
}
Example response
{
"product": "Supply Analytics",
"currency": "USD",
"snapshot": {
"active_count": 84,
"sale_price_usd": {
"p10": 120000, "p25": 145000, "median": 178000,
"p75": 210000, "p90": 265000, "avg": 182400, "count": 84
},
"days_on_market": {
"p10": 12, "p25": 28, "median": 45,
"p75": 72, "p90": 110, "avg": 51, "count": 84
}
},
"monthly": [
{
"month": "2025-09",
"active_count": 80,
"new_count": 6,
"removed_count": 3,
"sale_price_usd": { "p25": 140000, "median": 175000, "p75": 205000 },
"days_on_market": { "p25": 25, "median": 44, "p75": 70 }
}
],
"pulse": {
"window": "last_4_weeks",
"newly_listed_total": 42,
"by_market": [
{ "market_id": "canggu", "label": "Canggu · this area", "count": 8, "share": 0.19 }
]
}
}
4. STR analytics
/v1/analytics/str
Airbnb market stats for one area: snapshot and month-by-month bands for ADR, occupancy, RevPAR, and revenue. Optional bedroom filter. Contract and off-plan do not apply.
Filters / params
| Field | Notes |
|---|---|
filters.market_id | Area slug. Required if no location. |
location.lat / lng | Map pin. Required if no market_id. |
filters.bedrooms | 1–12. |
filters.months | History depth. Default 12, max 60. |
Request
curl -sS -X POST "$API/v1/analytics/str" \
-H "Authorization: Bearer $ARTHABASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": { "market_id": "canggu", "bedrooms": 2, "months": 24 }
}
Example response
{
"product": "STR Analytics",
"currency": "USD",
"snapshot": {
"listing_count": 210,
"adr_usd": { "p25": 140, "median": 175, "p75": 220 },
"occupancy_pct": { "p25": 48, "median": 61, "p75": 72 },
"revpar_usd": { "median": 107 },
"revenue_30d_usd": { "median": 3200 },
"revenue_90d_usd": { "median": 9100 }
},
"monthly": [
{
"month": "2025-09",
"listing_count": 198,
"adr_usd": { "p25": 135, "median": 168, "p75": 210 },
"occupancy_pct": { "p25": 45, "median": 58, "p75": 70 },
"revpar_usd": { "median": 97 },
"revenue_usd": { "median": 2900 }
}
]
}
5. Compare markets
/v1/analytics/compare
Runs supply and STR analytics for 2–5 markets with the same filters. Use it to compare areas side by side (for example Canggu vs Uluwatu vs Seminyak).
Filters / params
| Field | Notes |
|---|---|
market_idsRequired | Array of 2–5 market slugs. |
filters.bedrooms | 1–12. Applied to every market. |
filters.contract | leasehold or freehold (sale side only). |
filters.ready_offplan | offplan or ready (sale side only). |
filters.months | History depth. Default 12, max 60. |
Request
curl -sS -X POST "$API/v1/analytics/compare" \
-H "Authorization: Bearer $ARTHABASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"market_ids": ["canggu", "uluwatu", "seminyak"],
"filters": { "bedrooms": 2, "contract": "leasehold", "months": 24 }
}
Example response
{
"product": "Analytics Compare",
"filters_applied": { "bedrooms": 2, "markets": ["canggu", "uluwatu", "seminyak"] },
"areas": [
{
"market_id": "canggu",
"supply": {
"snapshot": { "active_count": 84, "sale_price_usd": { "median": 178000 } },
"monthly": []
},
"str": {
"snapshot": { "listing_count": 210, "adr_usd": { "median": 175 } },
"monthly": []
}
},
{
"market_id": "uluwatu",
"supply": { "snapshot": {}, "monthly": [] },
"str": { "snapshot": {}, "monthly": [] }
}
]
}
6. Sale comps
/v1/comps/sale
Active for-sale peers around a coordinate. Search starts at 1.5 km and expands to 3 km if fewer than 20 matches. Arthabase returns up to 20 closest listings.
Filters / params
| Field | Notes |
|---|---|
location.lat / lngRequired | Map pin for the search center. |
filters.bedroomsRequired | 1–12. |
filters.contract | leasehold or freehold. |
filters.ready_offplan | offplan or ready. |
Request
curl -sS -X POST "$API/v1/comps/sale" \
-H "Authorization: Bearer $ARTHABASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"location": { "lat": -8.6472, "lng": 115.1386 },
"filters": {
"bedrooms": 2,
"contract": "leasehold",
"ready_offplan": "offplan"
}
}
Example response
{
"product": "Sale Comps",
"currency": "USD",
"comp_search": {
"radius_m_used": 1500,
"expanded": false,
"count": 14,
"fixed_count": 20
},
"comps": [
{
"listing_id": "l9f2a1",
"listing_url": "https://…",
"image": null,
"distance_m": 180,
"bedrooms": 2,
"contract": "Leasehold",
"ready_offplan": "Off-Plan",
"sale_price_usd": 248000,
"days_on_market": 42,
"agency": "Example Agency"
}
]
}
7. STR comps
/v1/comps/str
Active Airbnb peers near the pin, with live financials and month history. Same radius rule as sale comps (1.5→3 km, up to 20). Weak listings are dropped (for example occupancy under ~40%, or ADR too low for the bedroom count).
Filters / params
| Field | Notes |
|---|---|
location.lat / lngRequired | Map pin for the search center. |
filters.bedroomsRequired | 1–12. |
Request
curl -sS -X POST "$API/v1/comps/str" \
-H "Authorization: Bearer $ARTHABASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"location": { "lat": -8.6472, "lng": 115.1386 },
"filters": { "bedrooms": 2 }
}
Example response
{
"product": "STR Comps",
"currency": "USD",
"comp_search": { "radius_m_used": 3000, "expanded": true, "count": 20 },
"comps": [
{
"airbnb_id": "676363015807687743",
"listing_url": "https://airbnb.com/rooms/…",
"title": "2BR villa near Berawa",
"image": "https://…",
"distance_m": 210,
"bedrooms": 2,
"financials": {
"adr_usd": 188,
"occupancy_pct": 61,
"revpar_usd": 115,
"revenue_30d_usd": 3420,
"revenue_90d_usd": 9800
},
"monthly_history": [
{
"month": "2025-09",
"occupancy_pct": 58,
"adr_usd": 175,
"revenue_usd": 3045,
"revpar_usd": 102
}
]
}
]
}
8. Returns Pack
/v1/returns-pack/raw
/v1/returns-pack/elaborated
One call for a deal pin: nearby sale comps, nearby STR comps (with history), and area supply + STR analytics (month bands). Use raw for the data ingredients, or elaborated when you also want yield scenarios.
| Mode | Price | What you get |
|---|---|---|
raw | Rp 25,000 | The ingredients only |
elaborated | Rp 50,000 | Same + Baseline / Optimal / Elite income, costs, yield (needs subject.sale_price_usd) |
Filters / params
| Field | Notes |
|---|---|
location.lat / lngRequired | Map pin for the deal. |
filters.bedroomsRequired | 1–12. |
filters.contract | leasehold or freehold. |
filters.ready_offplan | offplan or ready. |
filters.market_id | Area slug to scope area analytics. |
subject.sale_price_usd | Required for elaborated yield scenarios. |
Request
curl -sS -X POST "$API/v1/returns-pack/raw" \
-H "Authorization: Bearer $ARTHABASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"location": { "lat": -8.6472, "lng": 115.1386 },
"filters": {
"bedrooms": 2,
"contract": "leasehold",
"ready_offplan": "offplan",
"market_id": "canggu"
}
}
Example response
{
"product": "Returns Pack",
"mode": "raw",
"currency": "USD",
"area": { "market": "Canggu", "village": "Canggu", "district": "North Kuta", "regency": "Badung" },
"comp_search": {
"sale": { "radius_m_used": 1500, "count": 12 },
"str": { "radius_m_used": 3000, "count": 18 }
},
"sale_comps": [{ "listing_id": "l9f2a1", "sale_price_usd": 248000, "distance_m": 180 }],
"str_comps": [{
"airbnb_id": "6763…",
"financials": { "adr_usd": 188, "occupancy_pct": 61 },
"monthly_history": [{ "month": "2025-09", "adr_usd": 175 }]
}],
"area_analytics": {
"supply": { "snapshot": { "sale_price_usd": { "median": 178000 } }, "monthly": [] },
"str": { "snapshot": { "adr_usd": { "median": 175 } }, "monthly": [] }
}
}
9. Supply listing search
/v1/supply/listings
/v1/supply/listings/:id
Search or fetch one for-sale villa. Rp 2,500 / $0.10 per call. Paginate with limit / offset (max 50).
Filters / params
| Field | Notes |
|---|---|
market | Area slug, e.g. canggu. |
bedrooms | Integer bed count. |
contract | leasehold or freehold. |
ready_offplan | offplan or ready. |
sale_price_usd_min / _max | Ask USD range. |
land_m2_min / _max | Land m² range. |
building_m2_min / _max | Building m² range. |
limit | Page size (max 50). |
offset | Pagination offset. |
Request
curl -sS -H "Authorization: Bearer $ARTHABASE_API_KEY" \
"$API/v1/supply/listings?market=canggu&bedrooms=2&ready_offplan=offplan&limit=20"
Example search response
{
"count": 84,
"listings": [
{
"listing_id": "l7acb3e7c",
"title": "2BR off-plan villa",
"market_id": "canggu",
"bedrooms": 2,
"ready_offplan": "Off-Plan",
"contract": "Leasehold",
"latest_price_usd": 160849,
"lat": -8.65,
"lng": 115.14
}
]
}
Example by id
{
"listing": {
"listing_id": "l7acb3e7c",
"bedrooms": 3,
"ready_offplan": "Off-Plan",
"latest_price_usd": 160849
}
}
10. Airbnb listing search
/v1/airbnb/listings
Thin browse by area name and bedrooms (live estimates, no month history). Max 30 per call. Rp 5,000 / $0.25. Use by id or history for full financials.
Filters / params
| Field | Notes |
|---|---|
location | Area name, e.g. Canggu. |
bedrooms | Integer. |
adr_usd_min / _max | Nightly ADR USD range. |
occupancy_pct_min / _max | Occupancy percent 0–100. |
include_removed | 1 to include removed listings. |
limit | Page size (max 30). |
offset | Pagination offset. |
Request
curl -sS -H "Authorization: Bearer $ARTHABASE_API_KEY" \
"$API/v1/airbnb/listings?location=Canggu&bedrooms=2&limit=20"
Example response
{
"count": 210,
"listings": [
{
"airbnb_id": "676363015807687743",
"title": "2BR villa near Berawa",
"area": "Canggu",
"bedrooms": 2,
"adr_est": 188,
"occupancy_est": 0.61,
"thumbnail": "https://…"
}
]
}
11. Airbnb financial history
/v1/airbnb/listings/:id
/v1/airbnb/listings/history
Live financials + month history for one listing (GET) or up to 10 ids (POST). Flat price Rp 10,000 / $0.50 per request (not per listing).
Filters / params
| Field | Notes |
|---|---|
:id / airbnb_idRequired | Numeric Airbnb id (path or POST body). |
airbnb_ids | POST body: 1–10 ids (alternative to single id). |
months | History depth (default 12, max 60). |
month_from / month_to | Optional inclusive bounds YYYY-MM. |
Request
# Single listing
curl -sS -H "Authorization: Bearer $ARTHABASE_API_KEY" \
"$API/v1/airbnb/listings/1433898042832596830?months=12"
# Batch (same price for 1–10 ids)
curl -sS -X POST "$API/v1/airbnb/listings/history" \
-H "Authorization: Bearer $ARTHABASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "airbnb_ids": ["1433898042832596830", "1751587283212405948"], "months": 12 }'
Example response
{
"product": "Airbnb Financial History",
"currency": "USD",
"history_query": { "months": 12, "listings_max": 10 },
"count": 1,
"listings": [
{
"airbnb_id": "1433898042832596830",
"title": "Luxe 2BR Villa with Pool - Near Canggu Beach",
"bedrooms": 2,
"rating": 4.9,
"financials": {
"adr_usd": 117,
"occupancy_pct": 53.3,
"revpar_usd": 63,
"revenue_30d_usd": 1877
},
"monthly_history": [
{ "month": "2026-08", "occupancy_pct": 53.3, "adr_usd": 117, "revenue_usd": 1877 }
]
}
],
"not_found": []
}
GET by id returns the same fields under listing instead of listings[].
Pricing
API key: prepaid IDR wallet (same as Studio). Low balance → 402 { "error": "Insufficient funds" }.
x402: USDC per call on Base. No wallet. Live list: GET /v1/x402.
| Call | IDR (wallet) | USDC (x402) | In plain words |
|---|---|---|---|
| Geo resolve | 1,000 | $0.01 | Pin → area names |
| Analytics supply | 10,000 | $0.50 | Sale market stats over time |
| Analytics STR | 10,000 | $0.50 | Airbnb market stats over time |
| Analytics compare | 15,000 | $0.50 | Same stats for 2–5 markets |
| Sale comps | 20,000 | $0.20 | Nearby for-sale peers |
| STR comps | 25,000 | $0.25 | Nearby Airbnb peers + history |
| Airbnb search | 5,000 | $0.25 | Thin browse (max 30, no month history) |
| Airbnb by id / history | 10,000 | $0.50 | One listing or batch POST history (1–10 ids) |
| Returns Pack raw | 25,000 | $1.50 | Full investment ingredients |
| Returns Pack elaborated | 50,000 | $3.00 | Same + yield scenarios |
| Supply search | 2,500 | $0.10 | Browse for-sale villas (max 50) |
| Supply by id | 2,500 | $0.10 | One villa listing |
| x402 catalog | 1,000 | $0.01 | Machine-readable price list |
| Health | 100 | $0.001 | Uptime check (API key or x402) |
Paid responses do not echo a pricing block. Use this table or GET /v1/x402.
Rate limit: 20 requests / minute per API key (credits) or per IP (x402). On 429, wait for Retry-After.