@propertysimple/client 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +27 -73
- package/README.md +200 -57
- package/client/src/index.d.ts +257 -2422
- package/client/src/index.js +6 -69
- package/contracts/src/index.d.ts +0 -7
- package/contracts/src/index.js +0 -7
- package/contracts/src/openapi.js +1 -1
- package/contracts/src/operations.d.ts +950 -4678
- package/contracts/src/operations.js +6 -36
- package/contracts/src/opportunity-events.d.ts +2 -1
- package/contracts/src/opportunity-events.js +6 -1
- package/contracts/src/property-features.d.ts +2 -2
- package/contracts/src/property-features.js +62 -19
- package/contracts/src/routes.d.ts +28 -305
- package/contracts/src/routes.js +82 -354
- package/contracts/src/schemas.d.ts +1114 -1355
- package/contracts/src/schemas.js +45 -223
- package/contracts/src/search.d.ts +1366 -253
- package/contracts/src/search.js +117 -2
- package/openapi.json +19288 -38596
- package/package.json +1 -9
- package/client/src/directory-sync.d.ts +0 -123
- package/client/src/directory-sync.js +0 -404
- package/client/src/managed-sync.d.ts +0 -129
- package/client/src/managed-sync.js +0 -410
- package/contracts/src/directory-execution.d.ts +0 -320
- package/contracts/src/directory-execution.js +0 -122
- package/contracts/src/directory-identity.d.ts +0 -1255
- package/contracts/src/directory-identity.js +0 -129
- package/contracts/src/directory-sales.d.ts +0 -333
- package/contracts/src/directory-sales.js +0 -102
- package/contracts/src/directory.d.ts +0 -1088
- package/contracts/src/directory.js +0 -179
- package/contracts/src/intelligence.d.ts +0 -366
- package/contracts/src/intelligence.js +0 -157
- package/contracts/src/professional-intelligence.d.ts +0 -256
- package/contracts/src/professional-intelligence.js +0 -90
- package/contracts/src/publication.d.ts +0 -44
- package/contracts/src/publication.js +0 -42
- package/contracts/src/smart-feed.d.ts +0 -3731
- package/contracts/src/smart-feed.js +0 -109
package/API.md
CHANGED
|
@@ -2,78 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
Generated from the shared route registry. Run `npm run sdk:docs` in the platform repository to update it.
|
|
4
4
|
|
|
5
|
-
Every operation below is available through `ps.call(operation, input, options)`, with inferred input and response types. The [quickstart](README.md) documents convenient namespace helpers such as `ps.listings.get(id)` and `ps.search.listings(query)`. Request fields use the API's snake_case names.
|
|
5
|
+
Every operation below is available through `ps.call(operation, input, options)`, with inferred input and response types. The [quickstart](README.md) documents convenient namespace helpers such as `ps.listings.get(id)` and `ps.search.listings(query)`. Request fields use the API's snake_case names. Every POST in this registry is a read-only search request and needs no idempotency key; the SDK requires `{ idempotencyKey: 'stable-request-id' }` only for a mutating POST.
|
|
6
6
|
|
|
7
|
-
This reference describes the SDK contract, not a claim that every feature is enabled or accepted in production. Credential scopes, dataset grants, source freshness and index readiness still govern access. Legacy `/api/*`, admin pages, public health/OpenAPI routes and internal jobs are outside this versioned SDK registry. The package also includes `openapi.json` with full input/output schemas.
|
|
7
|
+
This reference describes the SDK contract, not a claim that every feature is enabled or accepted in production. Credential scopes, dataset grants, source freshness and index readiness still govern access. Legacy `/api/*`, admin pages, public health/OpenAPI routes and internal jobs are outside this versioned SDK registry. The package also includes `openapi.json` with full input/output schemas. Operations retired in October 2026 (directory, catalog snapshots and changes, publications, judgments, opportunity plans and actions) are no longer in the SDK; the API answers their routes with `410 RETIRED`.
|
|
8
8
|
|
|
9
|
-
| Operation for `ps.call`
|
|
10
|
-
|
|
|
11
|
-
| `searchFeatures`
|
|
12
|
-
| `searchListings`
|
|
13
|
-
| `searchFacets`
|
|
14
|
-
| `
|
|
15
|
-
| `
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `directoryResolveIdentity` | `GET /v1/directory/identities` | `directory:read` | Resolve a typed directory source identity. |
|
|
35
|
-
| `directoryListRelationships` | `GET /v1/directory/relationships` | `directory:read` | Read bounded evidenced relationships. |
|
|
36
|
-
| `directoryGetContacts` | `GET /v1/directory/entities/{id}/contacts` | `directory:contacts` | Read purpose-permitted directory contact endpoints. |
|
|
37
|
-
| `directoryGetMetrics` | `GET /v1/directory/entities/{id}/metrics` | `directory:read` | Read authorized observed inventory metrics. |
|
|
38
|
-
| `createDirectorySnapshot` | `POST /v1/directory/snapshots` | `directory:sync` | Capture a directory or combined feed boundary. |
|
|
39
|
-
| `getDirectorySnapshot` | `GET /v1/directory/snapshots/{id}` | `directory:sync` | Read snapshot scope, boundary and expiry. |
|
|
40
|
-
| `listDirectorySnapshotItems` | `GET /v1/directory/snapshots/{id}/items` | `directory:sync` | Page immutable typed snapshot resources. |
|
|
41
|
-
| `listDirectoryChanges` | `GET /v1/directory/changes` | `directory:sync` | Replay typed changes with complete commit-group boundaries. |
|
|
42
|
-
| `createDirectorySalesPlan` | `POST /v1/directory/sales-plans` | `directory:sales` | Prepare bounded professional or organization sales recommendations. |
|
|
43
|
-
| `getDirectorySalesPlan` | `GET /v1/directory/sales-plans/{id}` | `directory:sales` | Read a scoped recommendation-only directory sales plan. |
|
|
44
|
-
| `requestDirectoryAssessment` | `POST /v1/directory/assessments` | `intelligence:judge` | Queue a canonical-evidence directory assessment. |
|
|
45
|
-
| `latestDirectoryAssessment` | `GET /v1/directory/assessments/latest` | `intelligence:read` | Read prepared directory intelligence with current evidence checks. |
|
|
46
|
-
| `getDirectoryAssessment` | `GET /v1/directory/assessments/{id}` | `intelligence:read` | Read a purpose-scoped directory assessment. |
|
|
47
|
-
| `reportPublication` | `POST /v1/publications` | `publications:write` | Acknowledge this product’s current listing publication. |
|
|
48
|
-
| `resolveDestination` | `GET /v1/listings/{id}/destination` | `catalog:read` | Resolve a current product-reported destination. |
|
|
49
|
-
| `evaluateJudgment` | `POST /v1/judgments` | `intelligence:judge` | Evaluate a bounded versioned task against caller-supplied evidence. |
|
|
50
|
-
| `getJudgment` | `GET /v1/judgments/{id}` | `intelligence:read` | Read this credential subject’s durable judgment audit. |
|
|
51
|
-
| `intelligenceCapabilities` | `GET /v1/intelligence-capabilities` | `intelligence:read` | Read implemented judgment tasks and explicit unsupported capabilities. |
|
|
52
|
-
| `capabilities` | `GET /v1/capabilities` | Authenticated caller | Read this caller’s supported capabilities. |
|
|
53
|
-
| `getCatalogHealth` | `GET /v1/catalog-health` | `catalog:read` | Read authorized source freshness independently of catalog changes. |
|
|
54
|
-
| `listListings` | `GET /v1/listings` | `catalog:read` | Search the current authorized catalog. |
|
|
55
|
-
| `listListingMap` | `GET /v1/listings/map` | `catalog:read` | Read bounded map markers within a required geographic box. |
|
|
56
|
-
| `listSimilarListings` | `GET /v1/listings/{id}/similar` | `catalog:read` | Rank a bounded local shortlist using transparent fact similarity. |
|
|
57
|
-
| `getListing` | `GET /v1/listings/{id}` | `catalog:read` | Read a current listing. |
|
|
58
|
-
| `getListingContacts` | `GET /v1/listings/{id}/contacts` | `contacts:read` | Read protected source contact routing for a current active listing. |
|
|
59
|
-
| `resolveListingIdentity` | `GET /v1/listing-identities` | `catalog:read` | Resolve a namespaced source alias. |
|
|
60
|
-
| `resolveAgentIdentity` | `GET /v1/agent-identities` | `agents:read` | Resolve a namespaced agent source alias. |
|
|
61
|
-
| `getAgent` | `GET /v1/agents/{id}` | `agents:read` | Read permitted agent attribution. |
|
|
62
|
-
| `listAgentListings` | `GET /v1/agents/{id}/listings` | `catalog:read` | Read an agent’s authorized inventory. |
|
|
63
|
-
| `classificationSchema` | `GET /v1/classification-schema` | `catalog:read` | Read the current classification vocabulary. |
|
|
64
|
-
| `createSnapshot` | `POST /v1/catalog-snapshots` | `catalog:sync` | Create a consistent snapshot with its replay cursor. |
|
|
65
|
-
| `getSnapshot` | `GET /v1/catalog-snapshots/{id}` | `catalog:sync` | Read snapshot readiness and expiry. |
|
|
66
|
-
| `listSnapshotItems` | `GET /v1/catalog-snapshots/{id}/items` | `catalog:sync` | Page an immutable snapshot. |
|
|
67
|
-
| `listCatalogChanges` | `GET /v1/catalog-changes` | `catalog:sync` | Replay authorized projection changes and removals. |
|
|
68
|
-
| `listEvents` | `GET /v1/events` | `catalog:sync` | Replay committed domain occurrences. |
|
|
69
|
-
| `listActionProfiles` | `GET /v1/action-profiles` | `opportunities:read` | Read published action profiles. |
|
|
70
|
-
| `listOpportunities` | `GET /v1/opportunities` | `opportunities:read` | Read current candidates without reserving actions. |
|
|
71
|
-
| `createPlan` | `POST /v1/opportunity-plans` | `plans:create` | Create or reuse an actual persisted selection. |
|
|
72
|
-
| `getPlan` | `GET /v1/opportunity-plans/{id}` | `opportunities:read` | Read plan progress, coverage and execution counts. |
|
|
73
|
-
| `listPlanItems` | `GET /v1/opportunity-plans/{id}/items` | `opportunities:read` | Read stable selection pages. |
|
|
74
|
-
| `claimDispatchBatch` | `POST /v1/dispatch-batches` | `actions:dispatch` | Reserve executable actions after critical revalidation. |
|
|
75
|
-
| `startAttempt` | `POST /v1/actions/{id}/attempts` | `actions:dispatch` | Fence and record dispatch before an external effect. |
|
|
76
|
-
| `recordActionOutcomes` | `POST /v1/action-outcomes` | `actions:dispatch` | Record idempotent sender receipts with per-item results. |
|
|
77
|
-
| `recordProductContextEvents` | `POST /v1/product-context-events` | `product-events:write` | Ingest owned product context snapshots and deltas. |
|
|
78
|
-
| `createWebhookSubscription` | `POST /v1/webhook-subscriptions` | `webhooks:manage` | Register a subscription only where delivery support is configured. |
|
|
79
|
-
| `readiness` | `GET /v1/operations/readiness` | `operations:read` | Inspect configured operational dependencies. |
|
|
9
|
+
| Operation for `ps.call` | HTTP endpoint | Required scope | Purpose |
|
|
10
|
+
| ------------------------ | -------------------------------- | -------------------- | -------------------------------------------------------------------------------- |
|
|
11
|
+
| `searchFeatures` | `GET /v1/search/features` | `catalog:read` | Read typed feature definitions and supported search operators. |
|
|
12
|
+
| `searchListings` | `POST /v1/search/listings` | `catalog:read` | Search current inventory with typed hard constraints and evidence. |
|
|
13
|
+
| `searchFacets` | `POST /v1/search/facets` | `catalog:read` | Read authorized refinement counts for the same normalized search. |
|
|
14
|
+
| `searchStats` | `POST /v1/search/stats` | `catalog:read` | Read count, price, price per area and days-on-market statistics for a search. |
|
|
15
|
+
| `searchIndex` | `POST /v1/search/index` | `catalog:read` | Walk compact entries for every listing a search matches, for sitemaps. |
|
|
16
|
+
| `searchMap` | `POST /v1/search/map` | `catalog:read` | Read bounded map points and clusters with matching search semantics. |
|
|
17
|
+
| `searchSuggest` | `GET /v1/search/suggest` | `catalog:read` | Suggest registered features and permitted listing locations. |
|
|
18
|
+
| `searchInterpret` | `POST /v1/search/interpret` | `catalog:read` | Interpret a bounded search vocabulary into editable filters and unresolved text. |
|
|
19
|
+
| `capabilities` | `GET /v1/capabilities` | Authenticated caller | Read this caller’s supported capabilities. |
|
|
20
|
+
| `getCatalogHealth` | `GET /v1/catalog-health` | `catalog:read` | Read authorized source freshness independently of catalog changes. |
|
|
21
|
+
| `listListings` | `GET /v1/listings` | `catalog:read` | Search the current authorized catalog. |
|
|
22
|
+
| `listListingMap` | `GET /v1/listings/map` | `catalog:read` | Read bounded map markers within a required geographic box. |
|
|
23
|
+
| `listSimilarListings` | `GET /v1/listings/{id}/similar` | `catalog:read` | Rank a bounded local shortlist using transparent fact similarity. |
|
|
24
|
+
| `getListing` | `GET /v1/listings/{id}` | `catalog:read` | Read a current listing. |
|
|
25
|
+
| `getListingContacts` | `GET /v1/listings/{id}/contacts` | `contacts:read` | Read protected source contact routing for a current active listing. |
|
|
26
|
+
| `resolveListingIdentity` | `GET /v1/listing-identities` | `catalog:read` | Resolve a namespaced source alias. |
|
|
27
|
+
| `resolveAgentIdentity` | `GET /v1/agent-identities` | `agents:read` | Resolve a namespaced agent source alias. |
|
|
28
|
+
| `getAgentEmailSharing` | `GET /v1/agent-email-sharing` | `contacts:read` | Count known source-agent identities sharing an exact stored email. |
|
|
29
|
+
| `getAgent` | `GET /v1/agents/{id}` | `agents:read` | Read permitted agent attribution. |
|
|
30
|
+
| `listAgentListings` | `GET /v1/agents/{id}/listings` | `catalog:read` | Read an agent’s authorized inventory. |
|
|
31
|
+
| `listEvents` | `GET /v1/events` | `catalog:sync` | Replay committed domain occurrences. |
|
|
32
|
+
| `listListingChanges` | `GET /v1/listing-changes` | `catalog:sync` | Follow every listing change, including removals, from a durable cursor. |
|
|
33
|
+
| `readiness` | `GET /v1/operations/readiness` | `operations:read` | Inspect configured operational dependencies. |
|
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# @propertysimple/client
|
|
2
2
|
|
|
3
3
|
The typed **server-side PropertySimple SDK** for listing details, property search,
|
|
4
|
-
agents,
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
agents, source health, listing synchronization and the catalog event stream.
|
|
5
|
+
Configure one client in your backend, then use `ps.listings`, `ps.search`, `ps.agents` and `ps.catalog`
|
|
6
|
+
throughout your application.
|
|
7
7
|
|
|
8
8
|
This package is distributed publicly for **authenticated PropertySimple API
|
|
9
9
|
integrations**. An API key is required to access data; no credentials or listing
|
|
@@ -12,11 +12,21 @@ is separate and unchanged.
|
|
|
12
12
|
|
|
13
13
|
**Service readiness:** SDK availability and API feature readiness are separate.
|
|
14
14
|
Start direct catalog integrations with `ps.listings` and `ps.agents`. Managed
|
|
15
|
-
search uses `ps.search`
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
the SDK does not activate imports, indexing,
|
|
19
|
-
geocoding or outbound
|
|
15
|
+
search uses `ps.search` with an authorized index on the API server; responses
|
|
16
|
+
identify their backend and consistency (see [Search](#search)). Check the target
|
|
17
|
+
environment's capabilities and readiness before switching search traffic.
|
|
18
|
+
Installing the SDK does not activate imports, indexing, schedules,
|
|
19
|
+
geocoding or any outbound contact.
|
|
20
|
+
|
|
21
|
+
**Breaking changes from 0.1.0 to 0.2.0:** the directory, catalog snapshots and
|
|
22
|
+
changes, publications, judgments, opportunity plans and actions were retired from
|
|
23
|
+
the platform. Their SDK helpers (`ps.directory`, `ps.publications`, `ps.judgments`,
|
|
24
|
+
`ps.opportunityPlans`, `ps.actions`, `ps.directoryActions`, the catalog snapshot
|
|
25
|
+
and change helpers) and the `@propertysimple/client/managed-sync` and
|
|
26
|
+
`/directory-sync` exports are gone; the API answers their routes with
|
|
27
|
+
`410 RETIRED`. Use the new `ps.catalog.listingChanges` helper to keep a local copy
|
|
28
|
+
current, or follow events to react to listing occurrences (see
|
|
29
|
+
[Keeping a copy current](#keeping-a-copy-current)).
|
|
20
30
|
|
|
21
31
|
## Installation
|
|
22
32
|
|
|
@@ -26,7 +36,7 @@ included. TypeScript applications should use their normal `@types/node` setup.
|
|
|
26
36
|
CommonJS and browser clients are not supported by this release.
|
|
27
37
|
|
|
28
38
|
```sh
|
|
29
|
-
npm install @propertysimple/client
|
|
39
|
+
npm install @propertysimple/client@0.2.0
|
|
30
40
|
```
|
|
31
41
|
|
|
32
42
|
The archive contains compiled JavaScript, TypeScript declarations, the shared
|
|
@@ -65,7 +75,7 @@ instances can coexist without changing another tenant's configuration.
|
|
|
65
75
|
Request a service key and dataset ID from your PropertySimple platform
|
|
66
76
|
administrator. Keys grant specific datasets and operations: catalog reads use
|
|
67
77
|
`catalog:read`, agent profiles use `agents:read`, and protected contacts need their
|
|
68
|
-
own scope
|
|
78
|
+
own scope; listing synchronization and the event stream use `catalog:sync`. Choose the minimum permissions your integration needs. A dataset ID is
|
|
69
79
|
an authorization boundary, not an MLS number or a value to guess.
|
|
70
80
|
|
|
71
81
|
Keep the key in your backend's secret manager or an ignored local `.env` file.
|
|
@@ -144,29 +154,78 @@ const results = await ps.search.listings({
|
|
|
144
154
|
Search responses include compact listing cards with a cover photo, `photo_count`,
|
|
145
155
|
match reasons and index/freshness metadata. Use `ps.listings.get(id, options)` for
|
|
146
156
|
complete details. The SDK calls the shared API; it never exposes Typesense keys.
|
|
147
|
-
|
|
148
|
-
|
|
157
|
+
Missing feature evidence is not a negative match.
|
|
158
|
+
|
|
159
|
+
With a loaded, promoted index, managed search reports `meta.backend: 'typesense'`
|
|
160
|
+
and `meta.consistency: 'current_index'`. Listing results are rechecked against the
|
|
161
|
+
current catalog; totals, facets, statistics and index entries describe the search
|
|
162
|
+
index and can briefly lag catalog changes.
|
|
163
|
+
|
|
164
|
+
When the API uses the PostgreSQL fallback, `ps.search.listings` supports plain
|
|
165
|
+
filters (text, offer, price, beds/baths/area, city/region/ZIP, property type, bounds)
|
|
166
|
+
and the `price_asc`, `price_desc` and `newest` sorts, with
|
|
167
|
+
`meta.backend: 'postgres'` and `meta.consistency: 'degraded'`; `relevance` returns
|
|
168
|
+
newest first. Feature predicates, `facet_fields`, radius, polygon and the portal
|
|
169
|
+
filters below require the managed index and return HTTP 503 when it is unavailable.
|
|
170
|
+
`ps.search.facets`, `stats`, `index`, `map` and `suggest` also require the
|
|
171
|
+
index. `ps.search.features` and `interpret` remain available, but an interpreted
|
|
172
|
+
query can still require an unavailable index. Surface persistent search failures;
|
|
173
|
+
never drop filters and present a broader result. The catalog's bounded
|
|
174
|
+
`ps.listings.map` remains available independently of the search index.
|
|
175
|
+
|
|
176
|
+
The search map helper takes `{ query, viewport, zoom, limit }`.
|
|
177
|
+
Use the same applied query for listings, facets and maps, and honor
|
|
178
|
+
generation/checkpoint, coverage, truncation and expiry metadata.
|
|
179
|
+
|
|
180
|
+
`ps.search.stats({ query })` returns count, price, price-per-area and days-on-market
|
|
181
|
+
statistics for a query (place and feature pages); pass `currency` and
|
|
182
|
+
`transaction_type` (and `price_period` for rentals) to get prices; price-per-area
|
|
183
|
+
statistics also require `living_area_unit`. Missing context produces a null
|
|
184
|
+
statistic and a warning. Queries also take a
|
|
185
|
+
`polygon`, and `filters` take `county`, `days_on_market`, `year_built`, `lot_size`,
|
|
186
|
+
`has_photos`, `has_virtual_tour`, `property_types` and `exclude_property_types`
|
|
187
|
+
(`capabilities.features` lists `search_stats` and `search_portal_filters`).
|
|
188
|
+
`ps.search.features()` lists only the features that can match.
|
|
189
|
+
|
|
190
|
+
`ps.search.index({ query, limit, after })` pages compact entries for sitemaps and
|
|
191
|
+
link indexes, up to 5,000 entries per page (default 1,000). Pass the previous
|
|
192
|
+
page's `next` as `after` until `has_more` is false. Index entries can briefly be
|
|
193
|
+
stale; use `ps.listings.get` for current listing details. The index walk uses its
|
|
194
|
+
own stable order regardless of the query's `sort` and is not a snapshot.
|
|
195
|
+
|
|
196
|
+
<!-- sdk-example: search-index.ts -->
|
|
149
197
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
198
|
+
```ts
|
|
199
|
+
import ps from './ps.js';
|
|
200
|
+
|
|
201
|
+
const query = {
|
|
202
|
+
dataset_id: 'your-authorized-dataset',
|
|
203
|
+
filters: { region: 'TX', has_photos: true },
|
|
204
|
+
};
|
|
205
|
+
const page = await ps.search.index({ query, limit: 1000 });
|
|
206
|
+
|
|
207
|
+
if (page.has_more && page.next) {
|
|
208
|
+
const nextPage = await ps.search.index({ query, limit: 1000, after: page.next });
|
|
209
|
+
console.log(nextPage.data.map((entry) => entry.id));
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`ps.search.facets` accepts a `limit` of up to 1,000 values per field; honor each
|
|
214
|
+
field's `truncated` flag. `ps.search.suggest({ q, include_listings: true })` can
|
|
215
|
+
include street-address suggestions when the query starts with a house number and
|
|
216
|
+
the listing's address may be shown.
|
|
154
217
|
|
|
155
218
|
## Methods and complete API coverage
|
|
156
219
|
|
|
157
|
-
| Namespace
|
|
158
|
-
|
|
|
159
|
-
| `ps.listings`
|
|
160
|
-
| `ps.search`
|
|
161
|
-
| `ps.agents`
|
|
162
|
-
| `ps.catalog`
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
| `ps.opportunityPlans` | `create`, `get`, `items`, `claimDispatchBatch`, `waitUntilReady` |
|
|
167
|
-
| `ps.actions` / `ps.directoryActions` | Explicit action claims, attempts and outcomes |
|
|
168
|
-
|
|
169
|
-
All 69 registered versioned public operations are available through
|
|
220
|
+
| Namespace | Common methods |
|
|
221
|
+
| ------------- | --------------------------------------------------------------------------------- |
|
|
222
|
+
| `ps.listings` | `list`, `get`, `resolveIdentity`, `contacts`, `map`, `similar` |
|
|
223
|
+
| `ps.search` | `listings`, `suggest`, `facets`, `stats`, `index`, `map`, `features`, `interpret` |
|
|
224
|
+
| `ps.agents` | `get`, `resolveIdentity`, `listings`, `emailSharing` |
|
|
225
|
+
| `ps.catalog` | `health`, `listingChanges`, `events`, `opportunityObservations` |
|
|
226
|
+
|
|
227
|
+
`ps.iterateListings(query)` pages through live listing results. Every registered
|
|
228
|
+
versioned public operation is available through
|
|
170
229
|
`ps.call(operation, input, options)`, including operations without a shortcut.
|
|
171
230
|
Input and output types are inferred from the operation name. Input fields use the
|
|
172
231
|
API's snake_case names; helpers use camelCase. For example:
|
|
@@ -218,19 +277,102 @@ if (firstPage.has_more && firstPage.next_cursor) {
|
|
|
218
277
|
}
|
|
219
278
|
```
|
|
220
279
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
280
|
+
### Keeping a copy current
|
|
281
|
+
|
|
282
|
+
`ps.catalog.listingChanges` (`GET /v1/listing-changes`, scope `catalog:sync`)
|
|
283
|
+
returns each changed listing's current state, including identities for hidden
|
|
284
|
+
and removed records. Start with `start: 'all'` to walk the complete catalog and
|
|
285
|
+
then catch changes made during that walk. The default `start: 'now'` starts with
|
|
286
|
+
an empty page and a cursor for future changes; it does not load existing listings.
|
|
287
|
+
|
|
288
|
+
Apply `visible` items as upserts and take `hidden` or `removed` items down. Removal
|
|
289
|
+
is not evidence of a sale; a restored source listing returns under the same ID.
|
|
290
|
+
A listing can appear more than once during the initial walk; apply items in order
|
|
291
|
+
and keep the last state. This is a feed of current states, not every intermediate
|
|
292
|
+
revision. Store each page's changes and `next_cursor` in one transaction. Continue
|
|
293
|
+
with that cursor while `has_more` is true, then poll from the saved cursor.
|
|
294
|
+
|
|
295
|
+
<!-- sdk-example: sync.ts -->
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
import ps from './ps.js';
|
|
299
|
+
|
|
300
|
+
const datasetId = 'your-authorized-dataset';
|
|
301
|
+
let cursor: string | undefined = undefined; // load the cursor stored with your copy
|
|
302
|
+
|
|
303
|
+
const page = await ps.catalog.listingChanges({
|
|
304
|
+
dataset_id: datasetId,
|
|
305
|
+
...(cursor ? { cursor } : { start: 'all' }),
|
|
306
|
+
limit: 200,
|
|
307
|
+
});
|
|
308
|
+
for (const change of page.data) {
|
|
309
|
+
if (change.status === 'visible') {
|
|
310
|
+
if (!change.listing) throw new Error('Visible change is missing its listing.');
|
|
311
|
+
console.log('upsert', change.listing.id, change.listing.revision);
|
|
312
|
+
} else {
|
|
313
|
+
console.log('take down', change.listing_id, change.status);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (!page.next_cursor) throw new Error('Listing changes did not return a cursor.');
|
|
317
|
+
cursor = page.next_cursor;
|
|
318
|
+
// Replace logging with writes; commit those writes and this cursor in one transaction.
|
|
319
|
+
// Request the next page only after that commit; continue while page.has_more.
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Listing-change cursors expire seven days after issue. Poll at least daily; on
|
|
323
|
+
`410 CURSOR_EXPIRED`, rebuild the copy with `start: 'all'` and reconcile its
|
|
324
|
+
visible, hidden and removed states. Never skip or invent a cursor to repair lag.
|
|
325
|
+
`include_contacts: true` additionally requires `contacts:read`; contacts follow
|
|
326
|
+
the current listing's visibility and freshness rules. Respect location expiry
|
|
327
|
+
and address privacy in stored copies and caches.
|
|
328
|
+
|
|
329
|
+
### Reacting to events
|
|
330
|
+
|
|
331
|
+
`ps.catalog.events` (`GET /v1/events`, scope `catalog:sync`) replays committed
|
|
332
|
+
listing occurrences in order: additions, price and status changes, open houses,
|
|
333
|
+
removals and others. Each event names a `listing_id`; re-read that listing rather
|
|
334
|
+
than patching your copy from event evidence. To start, page events until
|
|
335
|
+
`has_more` is false and keep that cursor, then read the catalog with
|
|
336
|
+
`ps.listings.list` or `ps.iterateListings`, and store the rows and the cursor
|
|
337
|
+
together. Anything that changes during the read is replayed from the cursor;
|
|
338
|
+
re-reading a listing twice is harmless. Then poll from the stored cursor:
|
|
339
|
+
|
|
340
|
+
<!-- sdk-example: events.ts -->
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import { ApiError } from '@propertysimple/client';
|
|
344
|
+
import ps from './ps.js';
|
|
345
|
+
|
|
346
|
+
const datasetId = 'your-authorized-dataset';
|
|
347
|
+
let cursor: string | undefined = undefined; // load the cursor stored with your copy
|
|
348
|
+
|
|
349
|
+
const page = await ps.catalog.events({ dataset_id: datasetId, cursor, limit: 100 });
|
|
350
|
+
for (const listingId of new Set(page.data.map((event) => event.listing_id))) {
|
|
351
|
+
try {
|
|
352
|
+
const listing = await ps.listings.get(listingId, { dataset_id: datasetId });
|
|
353
|
+
console.log('upsert', listing.id, listing.revision);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
// No longer readable under this grant. That is not evidence of a sale.
|
|
356
|
+
if (error instanceof ApiError && error.status === 404) console.log('unavailable', listingId);
|
|
357
|
+
else throw error;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
cursor = page.next_cursor ?? cursor;
|
|
361
|
+
// Commit the changed rows and this cursor in one transaction; continue while page.has_more.
|
|
362
|
+
```
|
|
225
363
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
364
|
+
Events are served about a minute after they are written, so poll on a schedule.
|
|
365
|
+
Event cursors expire seven days after issue. Retention windows are eight days for
|
|
366
|
+
`listing_updated` and fourteen days for other events when retention is enabled.
|
|
367
|
+
A cursor behind deleted history gets `410 EVENT_HISTORY_PRUNED`; re-read the
|
|
368
|
+
listings you keep and restart from retained history instead of skipping the gap.
|
|
369
|
+
Use `listingChanges` for a catalog copy and events for occurrence history; event
|
|
370
|
+
evidence is not a full historical listing snapshot.
|
|
371
|
+
`ps.catalog.opportunityObservations`
|
|
372
|
+
returns the same page reduced to typed observations (new to market, price drop,
|
|
373
|
+
open house, market-age milestone, source removal).
|
|
374
|
+
The platform repository's `npm run demo:sync` runs the event loop against a disposable
|
|
375
|
+
fictional catalog.
|
|
234
376
|
|
|
235
377
|
## Errors, requests and retries
|
|
236
378
|
|
|
@@ -268,20 +410,21 @@ For an integration timeout spanning retries, pass
|
|
|
268
410
|
HTTP status, problem code and request ID; exclude API keys, contact details and
|
|
269
411
|
complete response bodies from ordinary logs. In particular:
|
|
270
412
|
|
|
271
|
-
| Result
|
|
272
|
-
|
|
|
273
|
-
| HTTP 401/403
|
|
274
|
-
| HTTP 404
|
|
275
|
-
| HTTP 429/503
|
|
276
|
-
| `
|
|
277
|
-
| `
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
`
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
The SDK
|
|
284
|
-
|
|
413
|
+
| Result | Integration behavior |
|
|
414
|
+
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
415
|
+
| HTTP 401/403 | Check the service key, dataset grant and operation scope; do not retry with broader credentials automatically. |
|
|
416
|
+
| HTTP 404 | Handle the unavailable resource explicitly; do not infer that a listing sold. |
|
|
417
|
+
| HTTP 429/503 | Honor the SDK's bounded retry behavior and surface persistent unavailability. |
|
|
418
|
+
| `SEARCH_UNAVAILABLE` (HTTP 503) | The query needs the managed index. Show that feature as unavailable; never retry it as a broader plain search. |
|
|
419
|
+
| `SEARCH_INDEX_NOT_READY` / `SEARCH_PROVIDER_UNAVAILABLE` (HTTP 503) | A required search index is not ready or its provider failed. Surface persistent unavailability without dropping filters. |
|
|
420
|
+
| `CURSOR_EXPIRED` (HTTP 410) | Restart a listing-change copy with `start: 'all'`; rebuild an event cursor from retained history after reconciling current listings. |
|
|
421
|
+
| `EVENT_HISTORY_PRUNED` (HTTP 410) | Event history has a gap. Reconcile current listings and restart from retained events. |
|
|
422
|
+
| `ContractError` | Check API/SDK compatibility and report the contract mismatch. |
|
|
423
|
+
|
|
424
|
+
Every POST operation is a read-only search request and needs no idempotency key.
|
|
425
|
+
The SDK requires a caller-supplied `{ idempotencyKey: 'stable-request-id' }` for any
|
|
426
|
+
mutating POST, keeping the same key and body across retries. The SDK does not call
|
|
427
|
+
email, advertising or payment providers.
|
|
285
428
|
|
|
286
429
|
## Types, validation and compatibility
|
|
287
430
|
|
|
@@ -331,11 +474,11 @@ npm run sdk:pack
|
|
|
331
474
|
```
|
|
332
475
|
|
|
333
476
|
For local integration testing, install the archive from
|
|
334
|
-
`packages/client/dist/propertysimple-client
|
|
477
|
+
`packages/client/dist/propertysimple-client-<version>.tgz` into a separate application.
|
|
335
478
|
Publish the exact inspected archive to avoid including workspace configuration:
|
|
336
479
|
|
|
337
480
|
```sh
|
|
338
|
-
npm publish ./packages/client/dist/propertysimple-client
|
|
481
|
+
npm publish ./packages/client/dist/propertysimple-client-<version>.tgz --access public --registry https://registry.npmjs.org/ --ignore-scripts
|
|
339
482
|
```
|
|
340
483
|
|
|
341
484
|
`npm run sdk:test` builds and packs the release, installs it in a temporary project
|