# RE Data Refinery — Ohio property intelligence API for AI agents

> x402 pay-per-call API. Scored Ohio property intel in 18 cities: live listings with flip / wholesale / rental scores, per-property price + tax history and school ratings, plus county foreclosure, tax-sale and auction leads. Self-custody micropayments in USDC on Base, settled atomically per call. No accounts, no API keys. Every data endpoint returns JSON.

CONTENTS
1. What this API is
2. Endpoints and prices
3. Payment flow (x402, USDC on Base)
4. Worked example: challenge, sign, replay
5. Merchant and payment facts
6. Bazaar discovery
7. Errors and limits

## 1. What this API is

RE Data Refinery serves Ohio property intelligence built for autonomous agents. Two data lanes:

- Scored listings — live RealtyAPI (Zillow + Realtor.com) search and detail; every record ships flip_score, wholesale_score, rental_yield_pct, market_heat, days_on_market, tax flags.
- Distressed feeds — county foreclosure, tax-sale and auction leads (PropertyOnion-fed), scored, filterable by city / ZIP / status / type.

Cities covered: Columbus, Cincinnati, Cleveland, Toledo, Dayton, Akron, Canton, Youngstown, Hamilton, Lorain, Newark, Mansfield, Springfield, Findlay, Marion, Zanesville, Lancaster, Delaware (18 Ohio cities).

    Base URL: https://re-data-refinery.ares-hms.workers.dev

## 2. Endpoints and prices

All prices are per successful call and charged in USDC via x402 (scheme exact, permit2). Amounts are USD values; the wire amount in the challenge is in USDC atomic units (6 decimals).

    GET /search?q=Columbus+OH&limit=20                    $0.05 - scored property search; q = city/state or ZIP, limit = 1-40 (default 20)
    GET /properties?city=Columbus&min_price=&max_price=   $0.03 - city-filtered scored property list as JSON
    GET /properties/{zpid}                                $0.03 - full scored detail for one property
    GET /properties/{zpid}/price-history                  $0.02 - transaction price history for one property
    GET /properties/{zpid}/tax-history                    $0.02 - tax assessment history for one property
    GET /properties/{zpid}/schools                        $0.02 - school ratings near one property
    GET /foreclosures?city=Columbus                       $0.05 - scored active foreclosure leads by city
    GET /tax-sales?city=Columbus                          $0.05 - scored county tax-sale leads by city
    GET /auctions?city=Columbus&status=&zip=              $0.05 - combined foreclosure + tax-sale + auction leads (filters: city, status, zip, type)

Free, no payment, no auth:

    GET  /            - this landing page (text/markdown)
    GET  /llms.txt    - compact agent route index (text/plain)
    GET  /health      - cache size, supported cities, version
    GET  /credits     - data provider and billing info
    GET  /cache/stats - in-memory cache stats

Path params: {zpid} is the Zillow property id of the record (returned as "zpid" by /search and /properties).

## 3. Payment flow (x402, USDC on Base)

1. Call a paid endpoint WITHOUT a payment header. You get HTTP 402 and a response header PAYMENT-REQUIRED whose value is base64-encoded JSON (standard base64; also tolerate base64url). That JSON is the challenge: x402Version: 2, resource (url/description/mimeType), accepts[0] = payment requirements, and a top-level extensions.bazaar discovery block.
2. Sign a payment payload against accepts[0]. Scheme "exact", EVM: build an EIP-3005 Permit2 transferWithAuthorization for the exact amount, sign EIP-712 with domain name "USD Coin", version "2", chainId 8453, verifyingContract = USDC address below.
3. Encode the signed payment payload JSON as base64url. Send it on the replayed request in header x-payment or PAYMENT-SIGNATURE (both accepted, case-insensitive). Same URL, same query — the challenge is per-resource.
4. On success you get HTTP 200 with the JSON body and a PAYMENT-RESPONSE header (base64 JSON settle receipt: success: true, transaction: 0x...). Payment is verified and settled onchain via the CDP facilitator before the data is served.

## 4. Worked example: challenge, sign, replay

Step 1 — fetch the challenge (curl):

    curl -s -i 'https://re-data-refinery.ares-hms.workers.dev/properties?city=Columbus'

Expect: HTTP/2 402, and a header like:

    PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lv...<base64 JSON>

Step 2 — decode the challenge (shell):

    PR='<paste the PAYMENT-REQUIRED header value>'
    printf '%s' "$PR" | base64 -d | python3 -m json.tool

You will see accepts[0] — these are the facts you sign against (verbatim shapes):

    {
      "scheme": "exact",
      "network": "eip155:8453",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "amount": "<price in USDC atomic units, 6 decimals, e.g. 30000 = $0.03>",
      "payTo": "0xc30B886C0dba8781263794b58Da45E5ea39eAFE5",
      "maxTimeoutSeconds": 300,
      "extra": { "name": "USD Coin", "version": "2", "assetTransferMethod": "permit2" }
    }

Step 3 — sign and replay (node, single script; needs a USDC-funded EOA key in env BUYER_KEY):

    // payments.mjs — npm i @x402/core@^2.23 @x402/evm@^2.23 viem@^2
    import { privateKeyToAccount } from 'viem/accounts';
    import { x402Client } from '@x402/core/client';
    import { x402HTTPClient } from '@x402/core/http';
    import { registerExactEvmScheme } from '@x402/evm/exact/client';

    const TARGET = 'https://re-data-refinery.ares-hms.workers.dev/properties?city=Columbus';
    const acct = privateKeyToAccount(process.env.BUYER_KEY);

    const challenge = await fetch(TARGET, { headers: { Accept: 'application/json' } });
    if (challenge.status !== 402) throw new Error('expected 402, got ' + challenge.status);
    let pr = challenge.headers.get('payment-required') || challenge.headers.get('PAYMENT-REQUIRED');
    let s = String(pr).replace(/-/g, '+').replace(/_/g, '/');
    while (s.length % 4) s += '=';
    const paymentRequired = JSON.parse(Buffer.from(s, 'base64').toString('utf8'));

    const client = new x402Client();
    registerExactEvmScheme(client, { signer: acct });
    const http = new x402HTTPClient(client);
    const payload = await http.createPaymentPayload(paymentRequired);
    const headers = http.encodePaymentSignatureHeader(payload);   // => { PAYMENT-SIGNATURE: <base64url payload> }

    const paid = await fetch(TARGET, { headers: { ...headers, Accept: 'application/json' } });
    console.log('status:', paid.status);
    console.log('body head:', (await paid.text()).slice(0, 400));

Step 3 alternative — manual replay (curl) after signing with any x402 SDK:

    curl -s -H "PAYMENT-SIGNATURE: <base64url signed payload from your signer>" \
      'https://re-data-refinery.ares-hms.workers.dev/properties?city=Columbus'

Step 4 — verify: HTTP 200 + JSON data; the PAYMENT-RESPONSE header decodes to the onchain settle receipt (success: true, transaction: 0x...). Any x402-protocol client (x402 fetch wrapper, CDP agent toolkit, MCP proxy_tool_call) works with this same flow.

## 5. Merchant and payment facts

    payTo (merchant wallet):  0xc30B886C0dba8781263794b58Da45E5ea39eAFE5
    Network:                  Base mainnet (eip155:8453)
    Asset:                    USDC 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (6 decimals)
    Scheme:                   exact, assetTransferMethod: permit2 (EIP-3005)
    EIP-712 signing domain:   name "USD Coin", version "2", chainId 8453
    Facilitator:              Coinbase CDP x402 — https://api.cdp.coinbase.com/platform/v2/x402 (verify + settle)
    Protocol version:         x402Version 2
    Price band:               $0.02 - $0.05 per call (see section 2)
    Operator:                 HMS LLC

## 6. Bazaar discovery

Every 402 challenge advertises a top-level extensions.bazaar block (input/output schema per route), so agents can self-describe this API before paying. This API is registered with the Coinbase Bazaar catalog — discover it with the CDP x402 discovery MCP server:

    POST https://api.cdp.coinbase.com/platform/v2/x402/discovery/mcp
    (JSON-RPC: initialize, then tools/call name=search_resources arguments={query:"real estate columbus", limit:10}; Accept: application/json, text/event-stream)

Search queries that surface it: "real estate columbus", "ohio property data", "foreclosure leads ohio". Agents connected to CDP can also call it via the Bazaar proxy tool (toolName from search_resources) without managing keys themselves.

## 7. Errors and limits

    402 Payment Required - no or invalid payment header. Decode PAYMENT-REQUIRED, sign, replay.
    429                  - rate limit: 20 requests/minute per IP; retry after a pause.
    500                  - provider key misconfig on this server (check GET /health).
    502                  - upstream provider error; retry later.
    404                  - unknown route; see section 2.

All responses are JSON (this landing page and /llms.txt are the only text/markdown and text/plain routes). Challenge and settle metadata always live in headers, never in the body: PAYMENT-REQUIRED on 402, PAYMENT-RESPONSE on paid success.
