CMAsnap Documentation (1.0.0)

Download OpenAPI specification:

Integrate CMAsnap into your product or workflow.

  • Authenticated Links & Embedding — deep-link or iframe CMAsnap with SSO.
  • Reports API — create CMA reports, poll their status, and read the data over a JSON API authorized with an OAuth 2.0 Bearer token.
  • MCP Server — connect AI assistants (Claude, ChatGPT, Gemini, and any other Model Context Protocol client) to a user's CMAsnap account.

Every API call acts as the user who authorized the token: it sees only that user's reports and that user's MLS coverage, and creating reports requires an active CMAsnap subscription.

Create new CMA report with SSO authentication

Main entry point for creating CMA reports with authenticated links.

Note: When using mlsId or address parameters, the system will first attempt to find an existing report for that property. If found, it will load the existing report; otherwise, it will create a new one.

Tangilla Auto-Login

Add ?autoLogin=tangilla to attempt automatic Tangilla authentication:

https://app.cmasnap.com/new?mlsId=PROP123&autoLogin=tangilla

This will redirect users to Auth0 with the Tangilla connection pre-selected, streamlining the login process for Tangilla users.

Realoms Auto-Login

Add ?autoLogin=realoms to attempt automatic Realoms SAML SSO:

https://app.cmasnap.com/new?mlsId=PROP123&autoLogin=realoms

This redirects users to Auth0, which initiates a SAML 2.0 authentication flow against Realoms (realoms.com) as the Identity Provider.

MARIS Auto-Login

Add ?autoLogin=maris to attempt automatic MARIS authentication:

https://app.cmasnap.com/new?mlsId=PROP123&autoLogin=maris
query Parameters
mlsId
string
Example: mlsId=PROP123456

MLS listing ID - searches for existing report or creates new one for this property

address
string
Example: address=123%20Main%20St%2C%20Austin%2C%20TX

URL-encoded property address - searches for existing report or creates new one at this address

embed
boolean
Default: false

Enable embed mode for iframe usage

autoLogin
string
Enum: "tangilla" "maris" "realoms"

Attempt auto-login with specified provider

Responses

Embedding

Iframe integration and embedding options

Embed CMA reports in your website

Embed CMAsnap functionality directly into your website using authenticated iframes.

Note: The actual URL is /new?embed=true but this documentation is for the embedding functionality.

Basic Embed

<iframe
  id="cmasnap-iframe"
  src="https://app.cmasnap.com/new?embed=true"
  scrolling="no"
  style="width: 1px; min-width: 100%; border: 0; overflow: hidden; height: 500px">
</iframe>

Embed with Property

<iframe
  id="cmasnap-iframe"
  src="https://app.cmasnap.com/new?embed=true&mlsId=PROP123"
  scrolling="no"
  style="width: 1px; min-width: 100%; border: 0; overflow: hidden; height: 500px">
</iframe>

Full Implementation with Auto-Resize

<iframe
  id="cmasnap-iframe"
  src="https://app.cmasnap.com/new?embed=true&address=123%20Main%20St"
  scrolling="no"
  style="width: 1px; min-width: 100%; border: 0; overflow: hidden; height: 500px">
</iframe>

<script src="https://app.cmasnap.com/static/js/embed.js"></script>

<script>
  const cmasnapInt = setInterval(() => {
    if (typeof CMAsnap !== 'undefined') {
      new CMAsnap('#cmasnap-iframe');
      clearInterval(cmasnapInt);
    }
  }, 1000);
</script>

Users will need to authenticate within the iframe on their first visit. Authentication persists across sessions.

query Parameters
embed
required
boolean
Value: true

Must be set to true for embed mode

mlsId
string

MLS listing ID to load

address
string

Property address (URL encoded)

autoLogin
string
Enum: "tangilla" "maris" "realoms"

Pre-select authentication provider

Responses

Reports

Create, list, and read CMA reports.

Report generation is asynchronous: POST /api/reports returns 202 Accepted immediately with the new report's id, and the comparable search, adjustments, and valuation run in the background. Poll GET /api/reports/{reportId}/status until status is ready (typically a few seconds to a minute) before reading the report.

Send Accept: application/json on every request so authentication failures come back as a 401 instead of a redirect to the login page.

List reports

Returns every report owned by the authorized user.

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Create a report

Queues a new CMA report and returns 202 Accepted with the report id. Poll /api/reports/{reportId}/status until it is ready.

Identify the subject property with one of:

  • address — a full street address, including city and state (ZIP recommended). CMAsnap geocodes it and matches it to a listing in the user's MLS coverage. When the property has several listings (e.g. a past sale and a current rental), the newest listing matching reportType is used.
  • listingId — the MLS number of a listing in one of the user's authorized MLS datasets. Use this when you already know the listing; it skips address matching.

If both are sent, listingId wins. Comparables are selected automatically.

curl -X POST "https://app.cmasnap.com/api/reports" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"address": "123 Main St, Austin, TX 78701"}'

Requires an active CMAsnap subscription.

Authorizations:
BearerAuth
query Parameters
listingId
string
Example: listingId=ABC123

MLS listing number of the subject property. Required unless address is sent.

address
string
Example: address=123%20Main%20St%2C%20Austin%2C%20TX%2078701

Full street address of the subject property (URL-encoded). May be sent in the body instead.

Request Body schema: application/json
optional
address
string

Full street address of the subject property. Required unless listingId is sent.

listingId
string

MLS listing number of the subject property. Required unless address is sent.

reportType
string
Enum: "Residential" "Residential Lease"

Defaults to Residential (Residential Lease for rental-only accounts)

compSelectionDistance
number

Optional search radius for comparables, in miles

Responses

Request samples

Content type
application/json
{
  • "address": "123 Main St, Austin, TX 78701",
  • "listingId": "string",
  • "reportType": "Residential",
  • "compSelectionDistance": 0
}

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "metadata": {
    },
  • "state": {
    }
}

Get a report

Returns the full report: the subject property (targetProperty), the selected comparables with adjustments (compProperties), the report settings, and the valuation. The nearby market listings are paginated separately via /api/reports/{reportId}/properties.

Property records use RESO-style field names (Address, List Price, Sold Price, Living Area, Bedrooms, Baths Total, MLS #, Status, Coordinates, …). The set of fields available depends on the MLS.

Authorizations:
BearerAuth
path Parameters
reportId
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "metadata": {
    },
  • "state": {
    }
}

Get report generation status

Authorizations:
BearerAuth
path Parameters
reportId
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
  • "status": "queued",
  • "progress": 0,
  • "error": "string",
  • "queuedAt": 0,
  • "readyAt": 0
}

List a report's market properties

Paginated list of the active, pending, and sold listings near the subject property.

Authorizations:
BearerAuth
path Parameters
reportId
required
string <uuid>
query Parameters
offset
integer
Default: 0
limit
integer
Default: 25

Responses

Response samples

Content type
application/json
{
  • "properties": [
    ],
  • "total": 0,
  • "offset": 0,
  • "limit": 0,
  • "hasMore": true
}

Get all photos for a property in a report

Report payloads carry only the first photo per property; fetch the full gallery here.

Authorizations:
BearerAuth
path Parameters
reportId
required
string <uuid>
listingKey
required
string

The property's ListingKey from the report

Responses

Response samples

Content type
application/json
{
  • "listingKey": "string",
  • "media": [
    ]
}

Webhooks

Webhooks send a signed HTTPS POST to your server when something happens in a user's CMAsnap account — a contact is added or changed, a contact opens a shared report, or a report finishes generating.

Setting up a webhook

In CMAsnap, go to Integrations → Webhooks and create a webhook with:

  • Event — one of contact.created, contact.updated, contact.activity, or report.generated. Create one webhook per event you want.
  • URL — an https:// endpoint on your server.
  • Filters (optional) — only send contact events for contacts that have an email and/or a phone number, or only send certain contact.activity types.
  • Retries (optional) — number of attempts (1–10, default 5) and initial backoff.

The signing secret (whsec_…) is shown once, when the webhook is created. Store it securely; you can rotate it from the webhook's page at any time.

Webhooks require an active CMAsnap subscription.

Request format

Every delivery is a JSON POST with this envelope:

{
  "event": "report.generated",
  "createdAt": "2026-09-30T16:40:25.000Z",
  "data": { }
}
Header Value
Content-Type application/json
X-CMAsnap-Event The event name, e.g. contact.created
X-CMAsnap-Webhook-Id The ID of the webhook that sent the delivery
X-CMAsnap-Signature sha256=<hex> — see below

Verifying signatures

X-CMAsnap-Signature is an HMAC-SHA256 of the raw request body using your signing secret, formatted as sha256=<hex>. Compute it over the exact bytes you received, before parsing the JSON.

For 24 hours after you rotate a secret, the header carries two comma-separated signatures (new secret first, previous secret second) so you can roll the secret on your side without dropping deliveries. Accept the request if any of them matches.

import crypto from 'node:crypto';
import express from 'express';

const app = express();

app.post('/webhooks/cmasnap', express.raw({ type: 'application/json' }), (req, res) => {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.CMASNAP_WEBHOOK_SECRET)
    .update(req.body) // raw Buffer
    .digest('hex');

  const signatures = (req.get('X-CMAsnap-Signature') || '').split(',');
  const valid = signatures.some((sig) =>
    sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)));
  if (!valid) return res.sendStatus(401);

  const { event, data } = JSON.parse(req.body);
  // ...handle the event
  res.sendStatus(200);
});

Responding and retries

Respond with any 2xx status within 10 seconds. Anything else — a non-2xx status, a timeout, or a connection error — is retried with exponential backoff (5 attempts starting at 2 seconds by default, configurable per webhook). Do slow work asynchronously after responding.

Deliveries can repeat (a retry after a timeout, a manual redelivery), so make your handler idempotent. Events are not debounced: a contact opening a report five times sends five contact.activity events.

Testing and delivery history

Each webhook's page in CMAsnap has:

  • Send test — sends a sample payload for any event, with data.test set to true (filters are skipped). Use it to build your receiver against every payload shape.
  • Delivery history — recent deliveries with their status, HTTP response code, and attempt count, with a Redeliver button that re-sends the original payload.
  • Pause / resume — stop deliveries without deleting the webhook.

contact.created Webhook

A contact was added to the user's account — from the contacts page, by sharing a report with a new recipient, or through a home-value lead form.

header Parameters
X-CMAsnap-Event
required
string

The event name

X-CMAsnap-Webhook-Id
required
string

ID of the webhook that sent this delivery

X-CMAsnap-Signature
required
string

sha256=<hex> HMAC of the raw body; two comma-separated values during secret rotation

Request Body schema: application/json
event
required
string
Value: "contact.created"
createdAt
required
string <date-time>

When the event happened

required
object

Responses

Request samples

Content type
application/json
{
  • "event": "contact.created",
  • "createdAt": "2026-09-30T16:40:25.000Z",
  • "data": {
    }
}

contact.updated Webhook

A contact's name, emails, or phone numbers changed. Other changes don't send this event.

header Parameters
X-CMAsnap-Event
required
string

The event name

X-CMAsnap-Webhook-Id
required
string

ID of the webhook that sent this delivery

X-CMAsnap-Signature
required
string

sha256=<hex> HMAC of the raw body; two comma-separated values during secret rotation

Request Body schema: application/json
event
required
string
Value: "contact.updated"
createdAt
required
string <date-time>

When the event happened

required
object

Responses

Request samples

Content type
application/json
{
  • "event": "contact.updated",
  • "createdAt": "2026-09-30T17:02:11.000Z",
  • "data": {
    }
}

contact.activity Webhook

A contact (or a not-yet-identified visitor) engaged with one of the user's reports.

activity When
report_shared The user sent a report to a recipient by email or text
report_opened A recipient opened a shared report link. openCount is the running total for that link
qr_scan Someone scanned a report's QR code. They aren't identified yet, so recipient and contactId are null
homevalue_opt_in A visitor opted in on a home-value page. level is first or second

Use trackingId + openCount to deduplicate repeated opens.

header Parameters
X-CMAsnap-Event
required
string

The event name

X-CMAsnap-Webhook-Id
required
string

ID of the webhook that sent this delivery

X-CMAsnap-Signature
required
string

sha256=<hex> HMAC of the raw body; two comma-separated values during secret rotation

Request Body schema: application/json
event
required
string
Value: "contact.activity"
createdAt
required
string <date-time>

When the event happened

required
object

Responses

Request samples

Content type
application/json
{
  • "event": "contact.activity",
  • "createdAt": "2026-09-30T18:15:00.000Z",
  • "data": {
    }
}

report.generated Webhook

A report finished generating and is ready to view — including reports created through the Reports API or an AI assistant.

header Parameters
X-CMAsnap-Event
required
string

The event name

X-CMAsnap-Webhook-Id
required
string

ID of the webhook that sent this delivery

X-CMAsnap-Signature
required
string

sha256=<hex> HMAC of the raw body; two comma-separated values during secret rotation

Request Body schema: application/json
event
required
string
Value: "report.generated"
createdAt
required
string <date-time>

When the event happened

required
object

Responses

Request samples

Content type
application/json
{}

MCP Server

Model Context Protocol server for AI assistants

Model Context Protocol endpoint

CMAsnap exposes a Model Context Protocol (MCP) server so AI assistants such as Claude, ChatGPT, Gemini, Perplexity, and other MCP clients can create and analyze CMA reports on a user's behalf.

Server URL: https://app.cmasnap.com/mcp (Streamable HTTP transport)

Connecting an AI assistant

Most users never need anything but the server URL — the assistant handles sign-in automatically. Step-by-step instructions for each assistant are also on the Integrations → AI Assistants page inside CMAsnap.

Claude (claude.ai / Claude Desktop): Settings → Connectors → Add custom connector → paste the server URL.

ChatGPT: Settings → Apps → Advanced settings → enable Developer mode, then Settings → Connectors → Create, name it "CMAsnap", paste the server URL, and choose OAuth.

Gemini: Settings & help → Connected apps → Add a custom app → paste the server URL.

Perplexity: Account settings → Connectors → name it "CMAsnap", paste the server URL, Authentication OAuth, Transport Streamable HTTP.

Claude Code:

claude mcp add --transport http cmasnap https://app.cmasnap.com/mcp

Any client using a JSON config:

{
  "mcpServers": {
    "cmasnap": {
      "type": "http",
      "url": "https://app.cmasnap.com/mcp"
    }
  }
}

Tools

Tool Description
list_reports List the user's reports (limit 1–100, offset)
create_report Create a report from a street address (optional reportType)
create_reports Create reports for up to 50 addresses at once; returns created and failed lists
create_report_by_listing Create a report from an MLS listing ID
get_report_status Poll a new report until status is ready (queued / ready / failed, with progress)
get_report Full report as markdown: subject, comparables, adjustments, market analysis, valuation
get_report_properties Nearby market properties, 50 per page
get_report_images Property photos as inline images the assistant can see (subject first, then comparables)
generate_report_summary AI-written professional summary of the report
ask_report_question Ask a question about a report (up to 2,000 characters)

Reports are generated in the background, so a typical flow is create_report → get_report_status (repeat until ready) → get_report.

Example prompts

  • "Create a CMA for 123 Main St, Austin, TX and summarize the value range."
  • "Build reports for these five addresses and compare price per square foot."
  • "Show me photos of the top three comparables in my latest report."
Authorizations:
BearerAuth
Request Body schema: application/json
required
jsonrpc
required
string
Value: "2.0"
method
required
string

MCP method (e.g. initialize, tools/list, tools/call)

params
object
required
string or integer

Responses

Request samples

Content type
application/json
{
  • "jsonrpc": "2.0",
  • "id": 1,
  • "method": "tools/call",
  • "params": {
    }
}

Response samples

Content type
application/json
{ }