Build on explainable seller intelligence.
Search a territory, inspect the signals behind a property score, or export a clean CSV for your workflow. Every scoring request is protected by a brokerage-scoped API key.
01 · Quickstart
One authenticated request to find a territory signal.
Set your deployment origin and brokerage API key, then call the search route. Every successful item includes a score, human-readable explanations, and the full factor breakdown.
GET · territory search
export SELLERSIGNAL_URL="https://your-deployment.example"
export SELLERSIGNAL_API_KEY="ss_live_your-key"
curl --get "$SELLERSIGNAL_URL/api/territory-search" \
--header "Authorization: Bearer $SELLERSIGNAL_API_KEY" \
--data-urlencode "zip=94110" \
--data-urlencode "equityMin=40" \
--data-urlencode "listingStatus=expired" \
--data-urlencode "listingStatus=withdrawn" \
--data-urlencode "outcome=expired,withdrawn"Replace the example origin with your deployment URL. The `zip` parameter is required; all other filters are optional.
02 · Authentication
Brokerage-scoped access for every scoring route.
Create a SellerSignal API key from an authenticated brokerage owner session, then send it as an HTTP Bearer token on territory search, property detail, and CSV export. Keys are scoped to the brokerage that created them.
Include Authorization: Bearer <ss_live_token> on every scoring request. The server validates the hash, rejects revoked or unknown keys, and returns HTTP 401 with { error: "Unauthorized" } when credentials are missing or invalid.
The owner-scoped lifecycle is /api/brokerages/{id}/api-key; it requires a signed-in brokerage owner session. The raw token is returned only once from the create response.
03 · API errors
Know the failure envelopes before you ship.
Scoring routes use a small set of stable error responses. Match the HTTP status first, then use the response body and troubleshooting note to correct the request without changing the live API contract.
Every scoring response includes an x-request-id header. JSON error responses also include the same UUID in requestId; use it when sharing a failure with support. Successful JSON and CSV response bodies stay unchanged.
Endpoint/context: every SellerSignal scoring route when the Bearer credential is missing, malformed, unknown, or revoked.
Response
{ "error": "Unauthorized", "requestId": "550e8400-e29b-41d4-a716-446655440000" }Troubleshooting: Send the key as Authorization: Bearer <ss_live_token>. Check that the token is complete, uses the Bearer scheme, and has not been revoked.
Endpoint/context: GET /api/territory-search or GET /api/territory-search/export with a valid API key but invalid query values.
Example request · GET /api/territory-search?equityMin=101
{ "errors": { "zip": "Enter a 5-digit ZIP", "equityMin": "Number must be less than or equal to 100" }, "requestId": "550e8400-e29b-41d4-a716-446655440000" }Troubleshooting: Include a five-digit zip and keep equityMin and equityMax within the inclusive 0–100 range. The same field-error envelope is used by JSON search and CSV export.
Endpoint/context: a server-side brokerage-scope check where a valid key belongs to a different requested brokerage.
Response
{ "error": "Forbidden", "requestId": "550e8400-e29b-41d4-a716-446655440000" }Troubleshooting: Use the key issued for the brokerage the server-side integration is checking. Public scoring routes resolve brokerage scope from the key and do not expose a public brokerage selector or query parameter.
Endpoint/context: any scoring route when authentication, scoring, or a data lookup fails unexpectedly.
Response
{ "error": "Internal Server Error", "requestId": "550e8400-e29b-41d4-a716-446655440000" }Troubleshooting: Include the x-request-id value when reporting the failure so it can be correlated with server logs.
04 · Search
Filter properties, then rank the result set.
GET /api/territory-search accepts a required ZIP and optional filters. It returns { items: PropertyItem[] }, where the server computes each score after filtering.
/api/territory-search| Query parameter | Type | Behavior |
|---|---|---|
| zip | required string | Exactly five digits. |
| ownerName | optional string | Trimmed; 1–120 characters. |
| equityMin / equityMax | optional integer | Inclusive range from 0 through 100. |
| listingStatus | repeated value | listed, off_market, expired, withdrawn, coming_soon. |
| outcome | comma-separated | active, expired, withdrawn, canceled, none. |
Invalid queries return HTTP 400 with { errors: { field: "message" } } with one actionable message per invalid field. Missing, revoked, or invalid Bearer credentials return HTTP 401 with { error: "Unauthorized" }.
Response envelope
{
items: PropertyItem[]
}
A successful response is HTTP 200. Results are sorted by the live search pipeline; use `id` to request a property detail record.
05 · Property detail
Inspect one score without losing its context.
GET /api/properties/{id} returns the same PropertyItem shape as search, including the computed photoUrl field. The id must be a Prisma cuid.
GET · property detail
curl --header "Authorization: Bearer $SELLERSIGNAL_API_KEY" \
"$SELLERSIGNAL_URL/api/properties/$PROPERTY_ID"Responses
- 200
- { property: PropertyItem }
- 401
- Missing, revoked, or invalid brokerage API key.
- 400
- Malformed cuid.
- 404
- No property for that id.
06 · Export
Take the same search pipeline with you.
`GET /api/territory-search/export` accepts the same query contract as JSON search and returns a UTF-8 CSV attachment. It is useful for a CRM import or a quick offline review.
GET · CSV export
curl --fail --location --output territory-search.csv \
--get "$SELLERSIGNAL_URL/api/territory-search/export" \
--header "Authorization: Bearer $SELLERSIGNAL_API_KEY" \
--data-urlencode "zip=94110" \
--data-urlencode "listingStatus=expired"Invalid queries return the same HTTP 400 { errors: { field: "message" } } JSON envelope as the search route.
07 · Response
A property record is transparent by design.
Search and detail share one `PropertyItem` contract. Nullable fields are explicit; `scoreFactors` carries the raw value, weight, direction, and explanation for each row.
| Field | Type | Description |
|---|---|---|
| id | string | Prisma cuid used by the property detail endpoint. |
| zip | string | Five-digit ZIP code. |
| address | string | Property street address. |
| ownerName | string | Owner name from the property record. |
| estimatedValue | integer | Estimated property value in dollars. |
| mortgageBalance | integer | Estimated mortgage balance in dollars. |
| listingStatus | enum | listed | off_market | expired | withdrawn | coming_soon. |
| listingOutcome | enum | active | expired | withdrawn | canceled | none. |
| daysOnMarket | integer | Current or most recent days on market. |
| ownerOccupied | boolean | Whether the owner occupies the property. |
| absenteeYears | integer | Years the record has indicated absentee ownership. |
| lastSaleDate | ISO date | null | Most recent recorded sale date. |
| lastSalePrice | integer | null | Most recent recorded sale price in dollars. |
| yearBuilt | integer | Year the property was built. |
| sqft | integer | Recorded square footage. |
| beds / baths | integer | Recorded bedroom and bathroom counts. |
| neighborhoodTrend | enum | rising | stable | cooling. |
| medianDom | integer | Neighborhood median days on market. |
| neighborhoodScore | integer | Current neighborhood score. |
| equityPct / equityDollars | integer | Computed equity percentage and dollars. |
| score | integer | Clamped Seller Signal Score from 0 through 100. |
| explanation | string[] | Non-zero score explanations, with a fallback when none apply. |
| scoreFactors | object[] | Detailed factor rows used to reconcile the headline score. |
| outreachCopy | string | Generated outreach draft based on the property signals. |
| photoUrl | string | null | Property photo URL when available. |
| leadSignals | object | omitted | CRM flags when a linked lead exists. |
08 · Scoring
Every score has a legible reason behind it.
Callers control the candidate set with filters. The server then computes a deterministic 0–100 score, returns non-zero `explanation` strings, and includes the full `scoreFactors[]` array. Factor weights reconcile to the displayed score after clamping.
| Factor | Weight | Applied when |
|---|---|---|
| Long-tenure ownership | +15 | 10+ years owned or no prior sale on record |
| Absentee ownership | +15 / −5 | >5 absentee years and not occupied / owner-occupied |
| Equity position | +20 / +10 | >40% equity / 15–40% equity |
| Current listing status | +20 / +15 / +5 | expired or withdrawn / coming soon / listed |
| Prior listing outcome | +5 | expired, withdrawn, or canceled |
| Days on market | +15 / +10 | >180 days / >90 days while currently listed |
| Neighborhood trend | +5 / −5 | cooling / rising |
| Agent relationship | +15 / +8 | past client / referral, when supplied by the app |
| CRM flags | −100 / −25 / +10 | do-not-contact / unanswered outreach / owner note |
{ key, name, rawValue, weight, direction, explanation }`direction` is `up`, `down`, or `neutral`. Zero-weight factors stay in the breakdown so the caller can see the evaluated signal.
Scores are clamped to 0–100. When the raw total crosses either boundary, the server adds a `clamp_high` or `clamp_low` factor so the weights still reconcile to the headline score.
Ready to explore
Create a brokerage workspace to manage the API-key lifecycle and keep your team’s integration settings in one place.