# Run a CMA

Source: https://help.ruuster.com/api/cma-create

```http
POST https://api.ruuster.com/v1/cma
```

A comparative market analysis for one home in a market your brokerage is on: subject profile, sold comps with dollar adjustments, active, pending and expired competition, market conditions, a value conclusion and a seller net sheet. Computed from MLS records; no AI text.

- Authentication: `x-api-key` and `x-email` headers of an owner or admin
- Rate limit: *lookup* call, 20 per minute per key, within the 120 per minute
- Counts toward your brokerage's daily CMA allowance (200 by default)

Runs a CMA and stores it; the response is the full analysis and [Get a CMA](https://help.ruuster.com/api/cma-get) returns it again for 90 days. The subject can be any home with an MLS record in one of your brokerage's MLSs, yours or not, or (in the US) a home that has never been listed, valued from its county public record. How the numbers are produced, and what they are not, is on [CMAs](https://help.ruuster.com/api/cma).

In Claude (claude.ai and Claude desktop) and other assistants that show MCP Apps, the `cma_create` and `cma_get` tools' answers come with a visual CMA: the subject's photo gallery, the value conclusion, each sold comp with its photo and adjustments, the active, pending and expired competition with thumbnails, the market and the net sheet, under your logo and in your colors and fonts from the [brand kit](https://help.ruuster.com/api/brand), with the credited agent's photo and contact details. Through MCP those answers also carry `brand` (the brand kit, for a seller-facing write-up or listing presentation), and the tools take `includePhotos` (0 to 8), which hands the assistant that many of the subject's photos to look at, for example to judge condition and updates before re-running with `overrides`. None of this is part of the HTTP API.

This is a *lookup* call (20 per minute) and counts toward your brokerage's daily CMA allowance (200 per day by default; `meta.cmaQuota` shows where you stand). One CMA runs at a time per brokerage. Typical response time is one to three seconds.

## Body

JSON, `Content-Type: application/json`, at most 64 KB. Everything is optional except one way of naming the subject.

| Name | Type | Required | Description |
|---|---|---|---|
| `subject.propertyId` | string | one of four | The listing's id, from any other call or event |
| `subject.mlsRecordId` | string | one of four | An MLS number in one of your MLSs; add `subject.mlsId` when the number matches more than one |
| `subject.address` | object | one of four | `line1` (street number first), optional `unit`, `city`, `state`, `postalCode`. Matched against MLS records at that number and postal code; a US home with no MLS record is found in the county public records instead |
| `subject.publicRecordId` | string | one of four | A `recordId` from [Look up a public record](https://help.ruuster.com/api/public-records-lookup): values a home from its county record (US only) |
| `agent` | string | no | The agent the CMA is for: their `agentId`, email or landing-page slug (see [List agents](https://help.ruuster.com/api/agents-list)). Every listing link in the CMA opens under that agent's page, so whoever signs up from it is their lead. Default: your brokerage's default agent (where unassigned leads go); plain links when there is none |
| `overrides` | object | no | Facts you know better than the MLS record: `bedrooms`, `bathrooms`, `livingArea`, `lotSizeAcres`, `yearBuilt`, `garageSpaces`, `stories`, `pool`, `basement` (`none`, `unfinished`, `finished`), `view`, `condition` (`poor`, `fair`, `average`, `good`, `excellent`), `updates` (any of `roof`, `hvac`, `kitchen`, `bathrooms`, `flooring`, `windows`), `propertyType` (`house`, `townhouse`, `condo`, `other`) |
| `options.radiusMiles` | number | no | Search radius for comps, 0.25 to 3. Default 0.75. Widened automatically to 1 then 2 miles when too few sales are found |
| `options.soldWithinMonths` | integer | no | 3 to 12. Default 6; widened to 12 when needed |
| `options.sqftTolerance` | number | no | 0.1 to 0.3. Default 0.2 (comps within ±20% of the subject's living area) |
| `options.maxComps` | integer | no | 3 to 8. Default 6 |
| `options.minComps` | integer | no | 1 to 6. Default 3; the search widens until this many distinct homes are found |
| `options.includeActives` | boolean | no | Default true |
| `options.includePendings` | boolean | no | Default true |
| `options.includeExpired` | boolean | no | Default true |
| `options.adjustmentRates` | object | no | Override any default rate from [CMAs](https://help.ruuster.com/api/cma), for example `{ "poolPct": 0.04, "updatePct": { "kitchen": 0.05 } }` |
| `options.netSheet` | object | no | `listPrice` (default: the recommended list price), `commissionPct` (default 5), `sellerClosingPct` (default 1.5), `mortgagePayoff` (default 0), `otherCosts` (default 0) |

## Response

`201 Created` with:

| Field | Description |
|---|---|
| `cmaId`, `createdAt`, `currency` | `currency` is `CAD` for Canadian provinces, else `USD` |
| `agent` | The agent every listing `url` in the CMA credits: `agentId`, `firstName`, `lastName`, `email`, `phone`, `photoUrl`, `landingPageUrl` and `source` (`request` when you named them, `default` for your brokerage's default agent). `null` when you named no one and there is no default agent; the links are then plain `/listings/` links |
| `subject` | The property object plus `photos`, `resolvedBy` (`propertyId`, `mlsRecordId` or `address`), `profile` (beds, baths, living area, lot, year built, garage, stories, pool, basement, view, condition, updates, HOA, annual tax, assessed value, parcel number, last status and sale, and `sources` saying whether each fact came from the MLS record or your override) and `publicRecord` with `publicRecordStatus`: for US homes, when public records are enabled for the deployment, the county record (parcel number, beds, baths, living area, lot, year built, stories, garage, pool, basement, condition, `tax` (year, annual amount, assessed value), `lastSale`, `occupancy` `owner` or `absentee`, `loans` (the open loans only, leaving out any a recorded release or a later refinance closed: total original, `estimatedBalance`, first and second loan amount, rate, type, date, lender type) and `avm` (automated value with range and confidence)). No owner, borrower, buyer or seller names. `publicRecordStatus` is `found`, `not_found`, `timeout`, `unavailable` (records off) or `unsupported` (Canada) |
| `search` | The MLS, property type, radius, months, size band actually used, what was widened, and how many listings were found |
| `soldComps[]` | Each sale: `property`, `distanceMiles`, `salePrice`, `concessions`, `netSalePrice`, `pricePerSqft`, `saleDate`, `originalListPrice`, `listPrice`, `listToSaleRatio`, `daysOnMarket`, `financing`, `facts`, `adjustments[]` (`factor`, `subject`, `comp`, `amount`, `basis`), `grossAdjustment`, `grossAdjustmentPct`, `netAdjustment`, `adjustedPrice`, `adjustedPricePerSqft`, `weight` (its share of the value conclusion) and `flags` (`high_gross_adjustment`, `older_sale`, `different_subtype`, `distance_expanded`) |
| `actives[]`, `pendings[]`, `expired[]` | Up to 10 each in the same area and size band: `property`, `listPrice`, `originalListPrice`, `pricePerSqft`, `daysOnMarket`, `distanceMiles`, and for expired, withdrawn and canceled listings `offMarketDate` and `lastStatus` |
| `market` | Within `radiusMiles` (at least 1 mile), 12 months: `months[]` (closed, median price, median price per sq ft, median days on market, median list-to-sale), `absorption` (closed per month, actives now, months of inventory), `trend` (price per sq ft movement per month and `rising`, `flat` or `declining`), `listToSaleRatio`, `medianPricePerSqft`, `season` (this month's share of the area's closings over three years), `rates` (the current 30- and 15-year fixed national averages with their dates, daily from Optimal Blue OBMMI and weekly from the Freddie Mac survey, as on [Mortgage rates](https://help.ruuster.com/api/mortgage-rates); `null` when the rate source is unreachable) |
| `valuation` | `indicatedValue`, `range.low` / `high`, `adjustedPricePerSqft` and `valueBySqft` (that rate × the subject's living area, a second indication), `avm` (the public-record automated value, range and confidence as a cross-check, never blended in; `null` without a record), `recommendedListPrice`, `confidence` (`high`, `medium`, `low`) with `reasons[]`, and `reconciliation[]` (each comp's adjusted price and weight). `null` when no comparable sale was found |
| `netSheet` | `listPrice`, `commission`, `sellerClosingCosts`, `mortgagePayoff`, `mortgagePayoffSource` (`request`, `publicRecord` when the estimated loan balance from the county record was used, or `none`), `otherCosts`, `estimatedProceeds` |
| `supporting` | `map` (subject and comp coordinates with distances), `photos` (subject gallery and each comp's first photo), `disclaimer` |

`meta.cmaQuota` has `limit`, `used`, `remaining` and `resetsAt`. Errors: `400 invalid_parameter` (names the field), `400 invalid_mls_number`, `400 mls_not_in_scope`, `404 not_found` (no MLS record for the subject in your MLSs), `429 daily_quota_exceeded`, `429 rate_limited` (another CMA for your brokerage is still running; retry after `Retry-After`).

## Example request

cURL:

```bash
curl -s -X POST "https://api.ruuster.com/v1/cma" \
  -H "x-api-key: $RUUSTER_API_KEY" \
  -H "x-email: $RUUSTER_EMAIL" \
  -H "Content-Type: application/json" \
  -d '{"subject":{"address":{"line1":"4182 E Saguaro Bloom Dr","city":"Tucson","state":"AZ","postalCode":"85718"}},"agent":"jane-doe","overrides":{"condition":"good","updates":["kitchen","flooring"]},"options":{"netSheet":{"commissionPct":5,"sellerClosingPct":1.5,"mortgagePayoff":312000}}}'
```

Node.js:

```javascript
const res = await fetch("https://api.ruuster.com/v1/cma", {
  method: "POST",
  headers: {
    "x-api-key": process.env.RUUSTER_API_KEY,
    "x-email": process.env.RUUSTER_EMAIL,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "subject": {
      "address": {
        "line1": "4182 E Saguaro Bloom Dr",
        "city": "Tucson",
        "state": "AZ",
        "postalCode": "85718"
      }
    },
    "agent": "jane-doe",
    "overrides": {
      "condition": "good",
      "updates": [
        "kitchen",
        "flooring"
      ]
    },
    "options": {
      "netSheet": {
        "commissionPct": 5,
        "sellerClosingPct": 1.5,
        "mortgagePayoff": 312000
      }
    }
  }),
});
const body = await res.json();
if (!body.ok) throw new Error(`${body.error.code}: ${body.error.message} (${body.requestId})`);
console.log(body.data);
```

Python:

```python
import os
import requests

res = requests.post(
    "https://api.ruuster.com/v1/cma",
    headers={"x-api-key": os.environ["RUUSTER_API_KEY"], "x-email": os.environ["RUUSTER_EMAIL"]},
    json={
        "subject": {
            "address": {
                "line1": "4182 E Saguaro Bloom Dr",
                "city": "Tucson",
                "state": "AZ",
                "postalCode": "85718",
            },
        },
        "agent": "jane-doe",
        "overrides": {
            "condition": "good",
            "updates": ["kitchen", "flooring"],
        },
        "options": {
            "netSheet": {
                "commissionPct": 5,
                "sellerClosingPct": 1.5,
                "mortgagePayoff": 312000,
            },
        },
    },
    timeout=30,
)
body = res.json()
if not body["ok"]:
    raise RuntimeError(f'{body["error"]["code"]}: {body["error"]["message"]} ({body.get("requestId")})')
print(body["data"])
```

## Example response

```json
{
  "ok": true,
  "data": {
    "cmaId": "cma_7Kp2mWq9Xz4R",
    "createdAt": "2026-10-13T15:04:11Z",
    "currency": "USD",
    "agent": {
      "agentId": "8d2f4a61-3c7e-4b95-a0d8-6e1f9b2c5a73", "firstName": "Jane", "lastName": "Doe", "email": "jane@saguarorealty.com", "phone": "+15205550134",
      "photoUrl": "https://cdn.example.com/agents/jane-doe.jpg", "landingPageUrl": "https://saguaro.ruuster.com/agent/jane-doe", "source": "request"
    },
    "subject": {
      "propertyId": "5b7e2d94-1c63-4a8f-b5e0-9d3c7a2f1e86",
      "mlsName": "MLS of Southern Arizona",
      "mlsRecordId": "22418765",
      "address": "4182 E Saguaro Bloom Dr", "city": "Tucson", "state": "AZ", "postalCode": "85718",
      "status": "closed", "mlsStatus": "Closed", "ownedByYou": true,
      "url": "https://saguaro.ruuster.com/agent/jane-doe/listings/5b7e2d94-1c63-4a8f-b5e0-9d3c7a2f1e86",
      "photos": ["https://cdn.example-mls.com/22418765/1.jpg", "https://cdn.example-mls.com/22418765/2.jpg"],
      "resolvedBy": "address",
      "profile": {
        "propertyType": "house", "bedrooms": 4, "bathrooms": 3, "livingArea": 2210, "lotSizeAcres": 0.21, "yearBuilt": 1998,
        "garageSpaces": 2, "stories": 1, "pool": true, "basement": null, "view": false, "condition": "good",
        "updates": ["kitchen", "flooring"], "hoa": { "hasHoa": true, "feeMonthly": 45 },
        "taxAnnual": 3812, "assessedValue": null, "taxYear": 2025, "parcelNumber": "109-12-3450",
        "lastStatus": "Closed", "lastListPrice": 489000, "lastSale": { "price": 482500, "date": "2024-06-14" },
        "sources": { "bedrooms": "mls", "bathrooms": "mls", "livingArea": "mls", "lotSizeAcres": "mls", "yearBuilt": "mls", "garageSpaces": "mls", "stories": "mls", "pool": "mls", "basement": null, "view": "mls", "condition": "override" }
      },
      "publicRecord": null
    },
    "search": {
      "mlsId": "3f4a7b12-6c85-4d93-a1e0-8b2f5c9d7e31", "mlsName": "MLS of Southern Arizona", "propertyType": "house",
      "radiusMiles": 0.75, "soldWithinMonths": 6, "sqftRange": { "min": 1768, "max": 2652 }, "widened": [],
      "found": { "closed": 19, "actives": 6, "pendings": 3, "expired": 1 }
    },
    "soldComps": [
      {
        "property": { "propertyId": "a03c8f61-7e2b-4d95-8a14-6f0b3e9c2d57", "mlsRecordId": "22419302", "address": "1920 W Mesquite Ridge Ln", "city": "Tucson", "state": "AZ", "postalCode": "85718", "status": "closed", "ownedByYou": false },
        "distanceMiles": 0.31, "salePrice": 515000, "concessions": 5000, "netSalePrice": 510000, "pricePerSqft": 221.26,
        "saleDate": "2026-09-02", "originalListPrice": 525000, "listPrice": 519000, "listToSaleRatio": 0.981, "daysOnMarket": 23, "financing": "Conventional",
        "facts": { "kind": "house", "bedrooms": 4, "bathrooms": 2.5, "livingArea": 2305, "lotSizeAcres": 0.19, "yearBuilt": 1996, "garageSpaces": 2, "stories": 1, "pool": false, "basement": null, "view": false, "condition": null, "updates": [] },
        "adjustments": [
          { "factor": "time", "subject": "2026-10-13", "comp": "2026-09-02", "amount": 2754, "basis": "0.40% per month × 1.3 months (clipped at ±1.5%)" },
          { "factor": "livingArea", "subject": 2210, "comp": 2305, "amount": -10497, "basis": "110 per sq ft (50% of the area's median 221/sq ft)" },
          { "factor": "halfBaths", "subject": 0, "comp": 1, "amount": -6250, "basis": "1.25% of the median comp price per half bath" },
          { "factor": "fullBaths", "subject": 3, "comp": 2, "amount": 12500, "basis": "2.5% of the median comp price per full bath" },
          { "factor": "pool", "subject": true, "comp": false, "amount": 15000, "basis": "3% of the median comp price" },
          { "factor": "yearBuilt", "subject": 1998, "comp": 1996, "amount": 2500, "basis": "0.25% per year, capped at ±5%" }
        ],
        "grossAdjustment": 49501, "grossAdjustmentPct": 0.0971, "netAdjustment": 16007, "adjustedPrice": 526007, "adjustedPricePerSqft": 228.2,
        "weight": 0.24, "flags": []
      }
    ],
    "actives": [
      { "property": { "propertyId": "c81f0d2e-5a47-4b93-9e6c-1d8a3f7b2e50", "mlsRecordId": "22431187", "address": "7710 N Camino de Oeste", "status": "active", "ownedByYou": false },
        "distanceMiles": 0.52, "listPrice": 549000, "originalListPrice": 559000, "pricePerSqft": 236.6, "daysOnMarket": 41, "bedrooms": 4, "bathrooms": 3, "livingArea": 2320, "yearBuilt": 2001, "offMarketDate": null, "lastStatus": "Active" }
    ],
    "pendings": [],
    "expired": [],
    "market": {
      "radiusMiles": 1,
      "months": [
        { "month": "2026-09", "closed": 11, "medianPrice": 512000, "medianPricePerSqft": 224.1, "medianDaysOnMarket": 27, "medianListToSale": 0.979 }
      ],
      "absorption": { "closedPerMonth": 9.5, "activesNow": 24, "monthsOfInventory": 2.5 },
      "trend": { "pctPerMonth": 0.0031, "label": "flat" },
      "listToSaleRatio": 0.981, "medianPricePerSqft": 222.4,
      "season": { "month": 10, "shareOfYearClosings": 0.081 },
      "rates": {
        "thirtyYearFixed": 6.21, "fifteenYearFixed": 5.58, "asOf": "2026-10-13", "source": "Optimal Blue OBMMI via FRED",
        "weeklySurvey": { "thirtyYearFixed": 6.27, "fifteenYearFixed": 5.6, "asOf": "2026-10-09", "source": "Freddie Mac PMMS via FRED" }
      }
    },
    "valuation": {
      "indicatedValue": 521400, "range": { "low": 509000, "high": 533000 }, "adjustedPricePerSqft": 235.0, "valueBySqft": 519350,
      "recommendedListPrice": 530000, "confidence": "high",
      "reasons": ["6 recent, similar sales with small adjustments"],
      "reconciliation": [ { "id": "a03c8f61-7e2b-4d95-8a14-6f0b3e9c2d57", "adjustedPrice": 526007, "weight": 0.71, "weightShare": 0.24 } ]
    },
    "netSheet": { "listPrice": 530000, "commissionPct": 5, "sellerClosingPct": 1.5, "mortgagePayoff": 312000, "otherCosts": 0, "commission": 26500, "sellerClosingCosts": 7950, "estimatedProceeds": 183550 },
    "supporting": {
      "map": { "subject": { "lat": 32.3301, "lng": -110.9284 }, "comps": [ { "propertyId": "a03c8f61-7e2b-4d95-8a14-6f0b3e9c2d57", "lat": 32.3338, "lng": -110.9311, "distanceMiles": 0.31, "label": "1920 W Mesquite Ridge Ln" } ] },
      "photos": { "subject": ["https://cdn.example-mls.com/22418765/1.jpg"], "comps": [ { "propertyId": "a03c8f61-7e2b-4d95-8a14-6f0b3e9c2d57", "photoUrl": "https://cdn.example-mls.com/22419302/1.jpg" } ] },
      "disclaimer": "This is a comparative market analysis computed from MLS records, not an appraisal. Adjustments are rule-based estimates (see help.ruuster.com/api/cma); verify facts and condition before relying on it. MLS data as of 2026-10-13."
    }
  },
  "meta": {
    "brokerageId": "4f6c2a9e-8d31-4b57-9a0e-2c7d5e1b8f43",
    "ms": 1840, "asOf": "2026-10-13",
    "cmaQuota": { "limit": 200, "used": 3, "remaining": 197, "resetsAt": "2026-10-14T05:00:00.000Z" }
  }
}
```
