API Changelog
Stay up to date with API changes and improvements.
All notable changes to the Propstreet Public API are documented here. This project uses Semantic Versioning.
[1.8.0] - September 2026
Added
Interest and stage on prospects — the two axes beside the single classification:
intereston the prospect response — your judgement of the prospect's appetite:interested,maybe,not_interested;nullwhen none is recorded.maybeis new: it had no word in the old vocabulary.stageon the prospect response — how far the process has come, derived from what has actually happened:not_contacted,teaser,im,data_room,bid. Never lowered by later events.stageOverrideon the prospect response — the latest manual stage correction,{ stage, setBy, setAtUtc, reason }wheresetByis{ id, name }; omitted when the stage was never overridden.PATCH /api/v1/projects/{projectId}/prospects/{id}acceptsinterest(sendnullto clear),stage(a manual correction, returned asstageOverride; later events can still raise it) andstageReason(only together withstage).DELETE /api/v1/projects/{projectId}/prospects/{id}/stage-overridewithdraws the manual stage correction (204, also when there is none). Every correction on the prospect is withdrawn, sostageis derived from what has happened again andstageOverrideis omitted.GET /api/v1/projects/{projectId}/prospectsacceptsinterest=(interested,maybe,not_interested, ornonefor prospects without a judgement) andstage=filters. They combine with each other and withcapability/tags/classification.- New action-timeline events on
GET .../prospects/{id}/actionsand the project / offer timelines:prospect.interest_changed(an interest judgement recorded or cleared) andprospect.stage_overridden(a manual stage correction;details.messagecarries the reason when one was given). Both aretype=auditrows. - MCP:
set_prospect_interest(interestinterested/maybe/not_interested, ornoneto clear; optional note) andoverride_prospect_stage(stage + optional reason);list_prospectsgainsinterestandstagefilters;get_prospectreturns the three new fields.
Follow-up tasks on prospects — dated calls, meetings and emails you can complete, cancel and reopen:
nextTaskon the prospect response — the nearest-due open follow-up task,{ id, type, dueDateUtc, assignee, overdue }; omitted when the prospect has no open task.typeiscall,meetingoremail;overdueis true once the due date is before today (UTC).openTaskCounton the prospect response — how many open follow-up tasks the prospect carries.POST /api/v1/projects/{projectId}/prospects/{id}/tasksadds a task (type,dueDateastoday/tomorrow/7d/ an ISO date, optionalassigneeandnote) and returns it with201. AdueDatein the past is accepted and records a task that is already overdue. A prospect can carry any number of open tasks. Send anIdempotency-Keyheader to make a retry safe: the same key returns the task that was created instead of adding a second one.POST /api/v1/projects/{projectId}/prospects/{id}/tasks/{taskId}/completecompletes a task (204). Completing records who and when; a task can be completed once, and completing a canceled task is refused (400).POST /api/v1/projects/{projectId}/prospects/{id}/tasks/{taskId}/cancelcancels a follow-up task (204). The task no longer counts innextTask/openTaskCountand can no longer be completed; canceling it again is harmless. A completed task cannot be canceled (400); reopen it first.POST /api/v1/projects/{projectId}/prospects/{id}/tasks/{taskId}/reopenundoes a task's completion (204), so the task is open again and can be completed again. A task that is not completed, or that was canceled, returns400.- Task ids. Every task verb accepts either the task's
id— returned when it was added and served asnextTask.id— or the id of itsprospect.task_addedrow on the prospect's activity timeline. Either id is looked up on the prospect in the URL only; a task that is not on this prospect returns404. - Canceling a task, reopening one and withdrawing a stage correction change what the prospect answers, so they also advance the prospect's
updatedUtcandETag: a delta sync onupdated_sincereturns the prospect, and a staleIf-Matchis refused. - A canceled task, an undone completion and a withdrawn correction leave the default activity timeline;
include_archived=truestill returns them asauditrows carryingdetails.archivedAt— when the entry was archived, the same field archived notes carry. Active rows carry noarchivedAt. GET /api/v1/projects/{projectId}/prospectsacceptstask_due=(overdue,today,this_week) to find prospects with an open task due in that window.- New action-timeline events on
GET .../prospects/{id}/actions:prospect.task_added(the note, when given, indetails.message) andprospect.task_completed;type=taskslists just the task rows. - MCP:
add_prospect_task(type, due date, optional assignee and note, one or many prospects) andcomplete_prospect_task(by the task's id or its timeline id, looked up on that prospect only;not_foundotherwise);list_prospectsgains ataskDuefilter;get_prospectreturnsnextTaskandopenTaskCount.
Project-level tags on prospects — your labels on an investor in one deal, separate from the contact's own tags:
projectTagson the prospect response — your labels on this prospect within this project, for example["Single tenant", "Follow up Q4"]. They live on the prospect, not on the contact or company, so they never show on the same investor in another project, and they are never shown to the investor. The existingtagsfield keeps serving the contact's or company's network-wide tags.PATCH /api/v1/projects/{projectId}/prospects/{id}acceptsprojectTags— the whole list (send[]to clear). Values are trimmed and de-duplicated (case-sensitive) and a multi-word value stays one label.tagsis untouched.GET /api/v1/projects/{projectId}/prospectsacceptsproject_tags=(repeat the parameter; OR semantics, case-insensitive). It combines with every other filter, includingtags=.- Webhooks:
prospect.updatedfires when the project tags change, withproject_tagsinchanged_fields. - MCP:
update_prospectgainsaddProjectTags/removeProjectTags(the existingtagsparam still replaces the contact's or company's network tags);list_prospectsgains aprojectTagsfilter;get_prospectreturnsprojectTags.
Classification comments on the action timelines:
details.messageonprospect.classifiedrows — the comment the broker wrote with the classification. Present only when a comment was written; a classification without one keeps omittingdetailsentirely.
Changed
- A note sent with a classification is now its own timeline entry.
POST /api/v1/projects/{projectId}/prospects/classifyand MCPclassify_prospectsaccept the same optionalnoteas before, but the words are logged as a note on each prospect instead of being folded into the classification entry. A commented classification therefore produces two rows on the action timelines — anoterow carrying the text indetails.message, and theprospect.classifiedaudit row, which no longer repeats it. The note behaves like every other prospect note: it has its own id andETag, appears undertype=notes, and can be archived or superseded throughPATCH /api/v1/projects/{projectId}/prospects/{id}/notes/{noteId}. If you read the comment from the classification row, read it from the note row instead — entries written before this release keep the comment where it was. - A prospect whose note could not be logged is reported as failed. If the stage moves but the note cannot be written, the prospect is returned in
failedwith the note's reason instead of being counted inclassified, so a partial result is never reported as a complete one. The stage that was accepted stays applied — retry the request to add the note. Prospects you can classify at all can take a note, so this is a fault path rather than an everyday outcome. type=notesnow also returns commented classifications. OnGET /api/v1/projects/{id}/actions,GET /api/v1/projects/{projectId}/prospects/{id}/actions, andGET /api/v1/offers/{id}/actions, the note filter returns the same set the prospect's note count counts: note rows plus pipeline classifications carrying a broker comment. Such a row keeps the shape it always had —typestaysaudit,eventstaysprospect.classified— and is still returned bytype=audit, so the two filters overlap on it. It is the same row in both: dedupe byidif you union the filters, and read the comment fromdetails.message.- MCP:
update_activitynow names the record the row belongs to —networkReffor a contact or company,projectRefalone for a project note,projectRef+prospectReffor a prospect, orofferReffor an offer, the same scopelog_activitytakes. The activity id is looked up on that record only; an id from any other record is not found and nothing is changed.
Unchanged
classificationkeeps its meaning and its values. It is still the single-value classification you last wrote (not_contactedby default), read back exactly as written, andclassification=still filters on it; the two new axes are additive beside it. Writingclassificationnever lowers astageor clears aninterest— use thestage/interestfields for that. One addition: writingbidderalso recordsinterested, since a bid is interest. When a PATCH sendsclassificationtogether withinterest/stage, the two axes win.- The mapping runs one way only — the two axes never write back to
classification. Settinginterestor overridingstageleavesclassificationexactly as you last wrote it, so the two drift apart as soon as you use the axes. A prospect you classifiedinterestedin the spring and markednot_interestedlast week still answersclassification=interested, and a prospect whose recorded interest isinterestedcan still carry the classificationnot_contacted. Filter, report and route oninterestandstage; readclassificationas the last word that was written, not as a summary of where the prospect stands today. prospectClassificationon the associated-projects lists (GET /api/v1/network/contacts/{id}/projects,.../companies/{id}/projects) keeps reporting the single-value classification recorded on that project; read the prospect itself forinterestandstage.
[1.7.0] - June 2026
Added
Project restore:
POST /api/v1/projects/{id}/restore— restore a deleted project within 30 days of deletion. Restores the prospects and external references removed by the same delete. Returns409when the project is not deleted.- New webhook event
project.restored, emitted when a deleted project is restored. See Webhooks. - Deleting a project is now offered only for a true draft. A project that has been shared with investors (a teaser was communicated), or that has active Case Room members, is refused (
409) and must be closed instead — closing preserves the full history and winds the rooms down.
Prospect owner & room-access expiry on PATCH /api/v1/projects/{projectId}/prospects/{id}:
owner— assign a team member as the prospect's owner. Send the member's user id, or"me"for the caller; sendnullto unassign. Validated against the project's team (the user must belong to the project's billing group).accessExpiry— schedule when the prospect's room access expires. Accepts a relative ("7d","1w","1m"), named ("tomorrow","next_week","next_month"), or ISO date/datetime value, and must be in the future. Only valid on a prospect connected to a room (otherwise400); sendnullto clear a scheduled expiry.- The prospect response (
ProspectPublicDto) now includes a read-onlyownerobject ({ id, name }) when an owner is assigned.
Optional note on batch classification:
POST /api/v1/projects/{projectId}/prospects/classifyaccepts an optionalnote, attached to each prospect's classification action and surfaced in the prospect note column. Whitespace-only notes are rejected.
MCP Q&A Tools (broker):
list_questions— list a Case Room's Q&A threads (subject, status, category, priority, assignee, prospect), with optional status and prospect filters.get_question— read one thread in full: every post, including internal drafts not yet released to the prospect. Posts anchored to a room document carry afileobject (stable file id + download path), directly usable for download.draft_answer— write an internal draft answer on a thread. Drafts are never sent to the prospect — release stays a human decision in the app.update_question— triage a thread: setcategoryand/orpriority, or route it to a team member viaassigneeRef.
MCP Q&A rollups:
list_roomsitems now includeopenQuestionCount.get_roomnow includes aqablock with per-status thread counts and the room's Q&A window.
Offer inbox categories:
GET /api/v1/offersand MCPlist_offersnow include acategoryon each row:buying,invitation, orselling.filter=allreturns the complete visible offer inbox across those categories.filter=invitationsreturns pending Case Room / Seller Portal invitations withcategory=invitationandstatus=pending;filter=sellingreturns accepted Seller Portal offers where the caller is the seller withcategory=selling.
[1.6.0] - May 2026
Added
Note revisions (archive / unarchive / supersede):
PATCH /api/v1/network/contacts/{id}/notes/{noteId}— archive, unarchive, or supersede a note on a contact.PATCH /api/v1/network/companies/{id}/notes/{noteId}— same for a company.PATCH /api/v1/projects/{projectId}/prospects/{id}/notes/{noteId}— same for a prospect note inside a project.PATCH /api/v1/projects/{id}/notes/{noteId}— same for a project-level note.- Body is body-discriminated on
action:archive,unarchive, orsupersede.- archive soft-hides the note and stamps
archivedAt. Idempotent — re-archiving an already-archived note is a no-op204. - unarchive clears
archivedAt. Returns400when the note has already been superseded (unarchive the head of the chain instead). - supersede expires the predecessor and creates a new revision that points back at it. Send a
supersedeobject with the fields to change; unset fields inherit from the predecessor. Thesupersedeobject also acceptscontentFormat(plaintextormarkdown) on every note type — contact, company, prospect, and project notes.
- archive soft-hides the note and stamps
If-Matchis required on these four endpoints. Unlike every other V1PATCH/DELETE(whereIf-Matchis best-effort), a missing or blankIf-Matchreturns428 Precondition Required(/help/precondition-required). A staleIf-Matchstill returns412 Precondition Failed(/help/precondition-failed). Fetch the resource, copy theETag, then retry. A lost optimistic-concurrency race — a concurrent revision committed between your read and write — returns409 Conflict(/help/concurrency-conflict); re-fetch theETagand retry. These PATCH endpoints are not replay-cached byIdempotency-Key; use theETagprecondition for retry safety.
Project notes (create):
POST /api/v1/projects/{id}/notes— log a broker-authored note directly on a project, mirroring the existing prospect-note create endpoint.
Note response fields (read):
GETaction/note responses now exposesupersedesActivityId(the predecessor a revision replaced),supersededByActivityId(the successor that replaced this note),archivedAt(when the note was archived; null when active), andcontentFormat(markdownfor markdown notes; null for plain text).NetworkAction.detailsis now a typed OpenAPI union. Usedetails.kind(noteoraudit) to discriminate the payload; generated clients expose the note revision fields on thenotedetails type.- The contact/company action endpoints (
GET /api/v1/network/contacts/{id}/actions,GET /api/v1/network/companies/{id}/actions), the project action endpoint (GET /api/v1/projects/{id}/actions), and the prospect action endpoint (GET /api/v1/projects/{projectId}/prospects/{id}/actions) accept two new opt-in query params:include_archived(defaultfalse) surfaces archived notes alongside theirarchivedAt, andinclude_superseded(defaultfalse) surfaces the full revision chain with the entries linked viasupersedesActivityId/supersededByActivityId. Both default off, so existing integrations see no change.
Project & prospect activity timelines (new):
GET /api/v1/projects/{id}/actionsandGET /api/v1/projects/{projectId}/prospects/{id}/actionsare new full activity timelines. Each row carries atypeofnote,chat, orauditand a typeddetailsunion discriminated bydetails.kind(which mirrorstype).chatrows are broker↔counterparty conversation entries (a message, a shared file, or a logged call) — the message body itself is private and is never returned; only a shared file is exposed (ondetails.kind = chat).auditrows carry a stable dot-notationeventstring (e.g.project.created,prospect.accepted,room.file_uploaded) — the action event vocabulary is a superset of the webhook event vocabulary, so the same names you subscribe to also appear inline on the timeline. (chatrows carry aneventtoo, e.g.prospect.message_sent/prospect.file_shared/prospect.call_logged.)- New query params on both endpoints:
type(all|notes|chat|audit, defaultall) filters by row type, andinclude_rollups(defaulttrue) controls cross-entity interleaving. On the project timelineinclude_rollups=trueinterleaves the project's prospect and Case Room activity (each rolled-up row carries asubjectofprospectorroom); on the prospect timeline it interleaves the prospect's Case Room activity and its linked contact/company notes (subjectofroom,contact, orcompany). Setinclude_rollups=falseto scope the timeline to the entity's own actions (nosubject). - The row
id(and thesupersedesActivityId/supersededByActivityIdlink fields) on every action timeline — project, prospect, offer, and the contact/company (network) endpoints — is now a globally-unique, opaque action id, uniform across the whole API and MCP. Treat it as opaque and pass it back verbatim — it is the same id the note PATCH endpoints accept and the same id MCPlist_activities/update_activityuse.
New endpoint — offer activity timeline (investor/seller-facing):
GET /api/v1/offers/{id}/actions— the recipient's read of an offer's activity timeline (the counterparty view of the prospect timeline the broker sees in-app). The offer is the caller's own identity, so its Case Room activity folds in with nosubject, and conversation entries appear astype = chat. Only what the recipient is entitled to see appears — broker-private notes and broker-internal events never surface here. Read-only: rows carry noetag. Supports the sametype,include_rollups,include_archived, andinclude_supersededparams.
Changed
- V1
ETagheaders are now strong ("…") rather than weak (W/"…"). Strong ETags are usable withIf-Matchstrong comparison per RFC 9110. If you previously stripped a leadingW/before sendingIf-Match, that step is no longer needed.
[1.5.0] - May 2026
Added
Teasers:
GET /api/v1/projects/{id}/teaser— broker-authored teaser for a project. Returns the visibility matrix (options), the allowed display modes per field (template), and the investor-facingprojection.GET /api/v1/properties/{id}/teaser— pre-market teaser for a property. Returns a thinproperty_addedenvelope (withnudge.code = no_pre_market_yet) when no pre-market record exists yet.GET /api/v1/projects/{id}/rooms/{roomId}/teaser— per-room teaser inside a Case Room. Returns a virtual default room teaser plusnudge.code = room_teaser_not_designedwhen the room teaser hasn't been designed yet (the first PATCH pins a real row).PATCH /api/v1/projects/{id}/teaser,PATCH /api/v1/properties/{id}/teaser,PATCH /api/v1/projects/{id}/rooms/{roomId}/teaser— design-time write surface. Standard PATCH semantics: send only the fields you want to change; omitted fields are unchanged. Auto-creates the pre-market record or room teaser on first PATCH. Free-plan callers receive clamped results.- Snapshot
tagsfield — what investors see on the teaser. Send a list to write that snapshot verbatim (deduped and clamped totemplate.tags);[]writes an explicit empty snapshot;nullresets the snapshot to the property's current template pool. Snapshots are byte-stable: subsequent property-tag edits do not change what investors see on an existing teaser — the author writes a new teaser to pick them up. If-Match/ETagparity with the rest of V1 — every GET returns anETagheader andetagbody field; every PATCH supportsIf-Matchand returns412 Precondition Failedon stale; omitIf-Matchto skip the precondition check (V1 convention).- All three routes return the same teaser shape:
stage, timestamps (publishedUtc,verifiedUtc,communicatedUtc),options,template,projection, optionalnudge, and anetagfield. - Stage precedence:
communicatedUtc > verifiedUtc > publishedUtc > drafting > property_added. - Lifecycle transitions (publish, unpublish, verify, communicate) are not available via the API. Perform them in the Propstreet app.
[1.4.0] - April 2026
Added
Identity & Teams:
GET /api/v1/auth/me— fetch the authenticated caller's identity, capabilities, primary team, and any other teams the caller belongs to or has been invited to — all in a single call. ReturnsprimaryTeam: nullandsecondaryTeams: []for callers with no active team (e.g. pre-onboarding or service accounts).GET /api/v1/teams/{id}— fetch a team. Returns team details plus an inline roster (up to 50 active members) when the caller is an active member. Callers with a pending invite see the team's details without the member list.GET /api/v1/teams/{id}/members— paginated list of a team's active members, filterable byrole=admin|member. Stable keyset ordering for cursor pagination.
MCP Identity Tool:
get_me— same payload as/api/v1/auth/me, returned as structured content so AI agents can bootstrap with a single tool call.
Offers:
GET /api/v1/offers— list offers the investor has received. Filter byall,active,ongoing,inactive,removed,unread, orunanswered.filter=unansweredreturns the single teaser currently awaiting a response.GET /api/v1/offers/{id}— fetch a single offer with the anonymized teaser (asset categories, locality, country, price / yield / area / year ranges, zoning, tags, conversions). The broker contact card is populated after the investor accepts. Unknown or inaccessible ids return404 Not Found.teaser.title— a short human-readable label composed from asset categories and locality in the caller's UI language (e.g."Industrial, Tullinge (SE)"). Null when both are unknown.status:unanswered,accepted,rejected,expired,removed,closed.capabilities:"chat"when broker messaging is available.- Teaser range fields expose
min/max(andcurrency/scalewhere applicable). Exact values appear asmin == max. - Cursor pagination via the shared
data+page.{nextCursor, pageSize, hasMore}envelope.
MCP Offer Tools:
list_offers— list offers with the same filters as the REST endpoint.get_offer— fetch a single offer by Smart Ref (offer id orid=/ext=selector).
Mandates:
/api/v1/mandates— full CRUD for investor acquisition mandates, including cursor pagination, delta sync (updated_since,include_deleted), ETag-based concurrency, and idempotent create support.POST /api/v1/mandates/search— search mandates by geography or location.POST /api/v1/mandates/batch/get— fetch up to 100 mandates by id in a single call.- Mandate responses expose asset criteria (
categories,investmentSize,yearBuilt,parcelArea), geographic area,country, and two parallel tag arrays —positiveTags(desirable attributes) andnegativeTags(deal-breakers) — which are mutually exclusive. - Geographic input accepts
geography(GeoJSON Polygon / MultiPolygon),near+radius(point and radius), orlocation(free-text locality resolved server-side). Exactly one mode is allowed per request. Write responses echo aresolvedLocationhint;GETresponses do not. - Mandate responses include the standard audit/sync fields:
etag,changeType,changeOrigin, anddeletedUtc. - External reference support: attach third-party ids via
externalRefs(namespace + externalId pairs) for linking mandates to records in your CRM or other systems. PATCHis partial: omitted fields are unchanged; sent fields replace. Categories are full-replace.
MCP Mandate Tools:
list_mandates— list mandates with optional country, category, tag, name, andupdatedSincefilters.search_mandates— geography- or location-based search.get_mandate— fetch a single mandate by Smart Ref (mandate id, name,id=/name=/ext=selector).create_mandate— create a mandate with categories, ranges, tags, external refs, and geographic criteria. Supports idempotency.update_mandate— partial update with the same semantics as the REST endpoint.delete_mandate— delete a mandate.
Deprecated
Project field mandate → exclusivity:
project.mandateis now aliased by the newproject.exclusivityfield. Both names carry identical values (exclusive,non_exclusive,other) and are populated on every read response. Webhookproject.updatedevents list both names inchangeswhen the value changes.- Write requests (
POST /api/v1/projects,PATCH /api/v1/projects/{id}) accept either field. Sending bothmandateandexclusivitywith non-null values on the same request is rejected with422 validation_failed; sending neither leaves the value unchanged. - MCP tools
create_projectandupdate_projectaccept onlyexclusivityon write. Reads still return bothmandateandexclusivityon every project object for now. mandatewill be removed entirely in a future minor version. Migrate integrations toexclusivitynow.
[1.3.0] - March 2026
Added
Properties:
/api/v1/properties- Full CRUD for property assets, including cursor pagination, delta sync (updated_since,include_deleted), ETag-based concurrency, and idempotent create support.GET /api/v1/projects/{id}/properties- Returns properties linked to a project, using the same public property schema as the standalone property endpoints.- Property responses now expose audit/sync fields consistent with other versioned v1 resources:
etag,changeType,changeOrigin, anddeletedUtc.
Company Duplicate Detection:
POST /api/v1/companies/find-duplicates— Check for potential duplicate companies before creating. Acceptsname(required),domain,countryCode, andlinkedInUrl. Returns matching candidates ordered by relevance (same-country matches first whencountryCodeis provided). Each result includesid,name,domain, andcountryCode.
Company Fields:
linkedInUrlon companies — Company LinkedIn URL, available on read (GET), create (POST), and update (PATCH). Searchable and tracked in audit history.
MCP Property Tools:
list_properties- List broker properties with optional country/category filters.get_property- Fetch a property by smart reference (id,name,id=,name=).create_property- Create a property with idempotency support, description, location, and tenure fields.update_property- Update name, description, country, locality, coordinates, and tenure, including coordinate clearing.
MCP Server:
create_companynow performs duplicate detection before creation and returns a conflict with candidate details unlessforce=true. This prevents accidental duplicate company creation by AI agents.
Changed
Property Contract Clarifications:
- Area write payloads must include
unit: "sqm"onparcelArea,zoning.gfa,rentedArea, andvacantArea. Read responses remain normalized tosqm. - Property tags are documented enum-backed snake_case values; unknown tags are rejected with
400 Validation failedinstead of being silently dropped. - Property PATCH location updates preserve tri-state semantics for nested
countryandlocality, and late write collisions now return409 Conflict.
Prospect Tag Filters:
- Breaking change:
GET /api/v1/projects/{id}/prospectsnow accepts repeatabletagsquery parameters with OR semantics instead of a singletagstring. Example:?tags=vip&tags=nordic.
Property Tag Filters:
- Breaking change:
GET /api/v1/propertiesnow accepts repeatabletagsquery parameters with OR semantics instead of repeatabletag. Example:?tags=core&tags=value_add.
MCP Tool Tag Filters:
- Breaking change: MCP list tools now use array-based
tagsparameters consistently.list_companies,list_contacts, andlist_prospectsno longer accept string/CSV tag filters.
See the MCP Guide for tool-level details and the generated OpenAPI schema for the full property resource contract.
[1.2.1] - February 2026
Changed
Contact/Company Deletion — Automatic Prospect Removal:
DELETE /api/v1/network/contacts/{id}andDELETE /api/v1/network/companies/{id}no longer reject deletion when the entity is a prospect on active projects. Instead, the entity is automatically removed as a prospect from all active projects before deletion.- Each prospect removal fires a
prospect.updatedwebhook withaction.type: "removed". The contact/company deletion itself firescontact.deleted/company.deleted. - Timeline history for affected projects is preserved — prospects remain visible in project activity logs.
- MCP
delete_contact/delete_companypreview (preview=true) now includesaffected_projectsshowing which projects will be affected.
Fixed
Delete Webhook Payloads:
project.deletedandprospect.deletedwebhook payloads now includeexternal_refs— the list of active external references at deletion time. CRM integrators need these to identify and clean up the corresponding record in their system, sinceGETreturns 404 after deletion.- Prospects are now cascade-deleted when their parent project is deleted, ensuring
prospect.deletedwebhooks fire for each affected prospect. - Re-adding a previously removed prospect to a project now works correctly. Previously, the unique index blocked re-addition of deleted prospects.
[1.2.0] - February 2026
Added
Projects & Prospects:
/api/v1/projects- Full CRUD for real estate transaction projects/api/v1/projects/{id}/prospects- Manage investor prospects on project prospect lists- Unified prospects API - Returns both prospect-list and communicated prospects in one endpoint
- Capability flags -
communicated,accepted,chat,invitationindicate prospect status - Classification pipeline - Track prospects:
not_contacted→contacted→interested→bidder - Filtering - Query prospects by
capability,classification, ortag - Cursor-based pagination with delta sync (
updated_since,include_deleted) - Idempotency support via
Idempotency-Keyheader - ETag-based optimistic concurrency control
Associated Projects:
GET /api/v1/network/contacts/{contactId}/projects— List projects where a contact is a prospectGET /api/v1/network/companies/{companyId}/projects— List projects where a company is a prospect- Cursor-based pagination with
page_sizeandcursorparameters - Filter by
status(open/closed) andclassification(prospect pipeline stage)
Webhook Events:
project.created- Fired when a new project is createdproject.updated- Fired when project fields change (name, status, classification, etc.)project.deleted- Fired when a project is deletedprospect.created- Fired when a prospect is added to a projectprospect.updated- Fired when prospect classification or other fields changeprospect.deleted- Fired when a prospect is deleted from a project- Webhook payloads include
actionfield when changes were triggered by business actions (e.g.,{"action": {"type": "classified"}})
See the Projects & Prospects Guide for complete documentation. See the Webhooks Guide for webhook payload reference.
MCP Server:
- Structured content — All MCP tool responses now include
structuredContent(typed JSON element, MCP 2025-06-18+) alongside the existing textcontent. Agents can parse responses programmatically without re-deserializing from text. No client-side changes required.
Link Webhook Payloads:
- Link event webhooks (
link.created,link.updated,link.deleted) now includecontact_idandcompany_idfields in the payload. This is critical forlink.deletedevents where the link resource is no longer retrievable via the API. - Re-creating a previously deleted link (same contact-company pair) now correctly triggers a
link.createdwebhook event. Previously, reactivating a deleted link was silently treated as an update with no webhook fired.
Changed
OpenAPI Schema Cleanup:
- Removed
Dtosuffix from schema component names:WebhookDto→Webhook,WebhookCreatedDto→WebhookCreated,WebhookDeliveryDto→WebhookDelivery,EmailEntryRequestDto→EmailEntryRequest,PhoneEntryRequestDto→PhoneEntryRequest,CompanyLinkDto→CompanyLink,ContactLinkDto→ContactLink - Added property descriptions to all public API schema components
- Cleaned up internal terminology in API descriptions
Note: JSON property names and HTTP contracts are unchanged. If your code generator produces class names from OpenAPI schema IDs, update your generated code.
[1.1.0] - February 2026
Added
Company Fields:
homepageUrl- Company homepage URLdomain- Company domain, can be:- Set directly via API (e.g.,
"domain": "acme-corp.com") when only domain is known - Auto-extracted from
homepageUrlwhen URL is provided (e.g., "acme-corp.com" from "https://www.acme-corp.com/about") - When both are provided,
homepageUrltakes precedence for domain derivation
- Set directly via API (e.g.,
- Both fields now searchable and sortable via
/api/v1/network/companies:search - Domain changes tracked in audit history and webhook payloads
Profile Pictures:
profilePictureUrlon contacts and companies - Download URL when profile picture is set- Profile picture changes now tracked in webhook
changed_fieldsasprofile_picture
Webhooks:
- Real-time event notifications for contacts, companies, and links
- 9 event types:
contact.created/updated/deleted,company.created/updated/deleted,link.created/updated/deleted - HMAC-SHA256 signature verification via Standard Webhooks headers (
webhook-id,webhook-timestamp,webhook-signature)- Note: Initially shipped as
X-Propstreet-Signature; migrated to Standard Webhooks in v1.2.0
- Note: Initially shipped as
- Exponential backoff retry (9 attempts over ~35 hours)
- Auto-disable after 10 consecutive failures
- Delivery history tracking via
/api/v1/webhooks/{id}/deliveries - Secret regeneration endpoint
changed_fieldsreference documentation for all resource types
See the Webhooks Guide for complete documentation.
[1.0.0] - December 2025
Added
Core Resources:
- Contacts - Full CRUD, search, batch operations
- Companies - Full CRUD, search, batch operations
- Links - Contact-Company relationships
- Network Actions - Activity notes on contacts and companies
- Files - Document attachments (up to 100MB)
Authentication:
- OAuth 2.0 with Client Credentials flow for production integrations
- Personal Access Tokens for testing and scripts
- Bot Users for dedicated integration accounts
Sync Features:
- Delta sync with
updated_sinceandinclude_deleted - Cursor-based pagination (up to 500 per page)
- ETag-based concurrency for safe updates
- External references for CRM ID tracking
Rate Limits:
- OAuth Clients: 600 requests/minute
- Personal Access Tokens: 60 requests/minute
Documentation:
- Comprehensive guides at docs.propstreet.com
- Interactive API explorer
- Code samples for TypeScript, C#, Python
Roadmap
Upcoming Features
We're continuously improving the API. Contact feedback@propstreet.com to share your integration needs.
Deprecation Policy
We follow a deprecation policy to ensure stability:
- Deprecation notice - Announced in changelog and API responses
- Migration period - Minimum 6 months before removal
- Removal - After migration period ends
We'll always provide migration guidance and support.
Subscribe to Updates
For API announcements:
- Watch this changelog
- Contact feedback@propstreet.com to join our developer updates list