Overview & URLs
VMC provides real-time property chain state data — transaction milestones, chain structure, and participant data — for properties in England and Wales. Each response includes an embedded JWS provenance block (see Section 06) for verification.
| Purpose | URL |
|---|---|
| VMC Chain State API | https://admin.demo3.viewmychain.com/api/v1/opda/chains |
| OPDA Participant Directory | https://data.directory.pdtf.raidiam.io/participants |
| Raidiam Directory (web) — create app + certs | https://web.sandbox.directory.openpropdata.org.uk |
| Raidiam token endpoint | https://matls-auth.directory.pdtf.raidiam.io/token |
| VMC JWKS — organisation-level signing key | https://keystore.directory.pdtf.raidiam.io/a61256b7-cd47-4103-937c-e9b90bbc0457/application.jwks |
Directory Registration
Authentication
transaction-status scope is being configured by Raidiam. Contact Alan Hughes (alan.hughes@raidiam.com) before attempting to connect — he will confirm when it is ready and assign it to your organisation.If you are not yet onboarded to the OPDA sandbox, follow the official Introduction to the Sandbox Technical Guide (OPDA) — the onboarding flow is identical for all participants.
OAuth Configuration
CLIENT_ID = "https://rp.directory.pdtf.raidiam.io/openid_relying_party/<your-app-uuid>" SCOPES = "transaction-status" TOKEN_URL = "https://matls-auth.directory.pdtf.raidiam.io/token"
Token Request
curl -X POST https://matls-auth.directory.pdtf.raidiam.io/token \ --cert transport.pem --key transport.key \ -d "grant_type=client_credentials" \ -d "scope=transaction-status" \ -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \ -d "client_assertion=<your_signed_jwt>"
API Reference
Returns chain state for a given address or UPRN. Includes all linked properties, milestones, participants, and JWS provenance.
Request Body — form-data
| Field | Type | Required | Description |
|---|---|---|---|
| inputType | string | required | address or uprn |
| value[] | string | required | One or more address strings or UPRN values. Repeat value[] for bulk queries. |
| rows | integer | optional | Chains per page, 1–100. Defaults to 15. Accepted in the form body or the query string. |
| page | integer | optional | Query string only — append ?page=2 to the URL. Sent as a form field it passes validation but is ignored, and page 1 is returned. |
rows travels in the form body like the other fields, but page must be in the query string: POST /api/v1/opda/chains?page=2. Requesting a page past the end of the result set returns 200 with an empty data array.Single address — fuzzy search supported
curl -X POST https://admin.demo3.viewmychain.com/api/v1/opda/chains \ --cert transport.pem --key transport.key \ -H "Authorization: Bearer $TOKEN" \ -F "inputType=address" \ -F "value[]=1 COBBLESTONE CORNER, LIVERPOOL, L19 9ES"
Single UPRN
curl -X POST https://admin.demo3.viewmychain.com/api/v1/opda/chains \ --cert transport.pem --key transport.key \ -H "Authorization: Bearer $TOKEN" \ -F "inputType=uprn" \ -F "value[]=38192980"
Multiple UPRNs — bulk query
curl -X POST https://admin.demo3.viewmychain.com/api/v1/opda/chains \ --cert transport.pem --key transport.key \ -H "Authorization: Bearer $TOKEN" \ -F "inputType=uprn" \ -F "value[]=38192980" \ -F "value[]=30016442" \ -F "value[]=25101892"
Multiple addresses — bulk query
curl -X POST https://admin.demo3.viewmychain.com/api/v1/opda/chains \ --cert transport.pem --key transport.key \ -H "Authorization: Bearer $TOKEN" \ -F "inputType=address" \ -F "value[]=1 COBBLESTONE CORNER, LIVERPOOL" \ -F "value[]=11 ADBER CLOSE, YEOVIL"
data[] corresponds to one queried value.Sample Test Data
Use these values to test against VMC's sandbox data. Click any value to copy.
By UPRN
By Address
| Address | UPRN | Chain |
|---|---|---|
| 1 COBBLESTONE CORNER, LIVERPOOL, L19 9ES | 38192980 | SPLIT chain· 4 properties |
| 11 ADBER CLOSE, YEOVIL, BA21 5XJ | 30016442 | Part of above chain |
| 54 MOORGATE, LEADENHALL, MILTON KEYNES, MK6 5NA | 25101892 | Part of above chain |
| 73 MANOR ROAD, TOTTENHAM, LONDON, N17 0JH | 100021200034 | Part of above chain |
Click any address to copy. Fuzzy search works — try "COBBLESTONE LIVERPOOL" or just the postcode.
Errors
| HTTP | Cause | Resolution |
|---|---|---|
| 401 | Missing or expired Bearer token | Request a new token from the Raidiam token endpoint |
| 401 | Certificate binding mismatch (RFC 8705) | Use the same transport certificate that was used when the token was issued |
| 403 | Missing transaction-status scope | Contact Alan Hughes at Raidiam to have the scope assigned |
| 404 | Address or UPRN not found in VMC's network | Try a different UPRN from the sample list in Section 03 |
| 422 | Invalid inputType or missing value[] | inputType must be address or uprn |
| 429 | Rate limit exceeded | The endpoint is rate limited. Space requests out and retry; use bulk queries (several values in one call) rather than one call per property |
429 and no chain data is returned. Prefer a single bulk query containing several value[] entries over many individual calls. Requesting a page past the end of the result set is not an error — it returns 200 with an empty data array.Response Format
data (an object wrapping the data[] array and pagination) and provenance (the signature block — see Section 06). The signature covers the entire top-level data object. milestones groups are always present with date fields null until recorded — except fallThrough, which is omitted entirely unless a Fall Through milestone has been recorded.{
"data": {
"data": [
{
"queriedAddress": "1 COBBLESTONE CORNER, LIVERPOOL, L19 9ES",
"chainId": "F9000B97-CEE0-49BC-B482-952EC0C6B7A3",
"chainSummary": {
"chainType": "SPLIT_CHAIN",
"chainLength": 4
},
"properties": [
// Property 1 - queried property (SPLIT node - 2 onward purchases)
{
"id": 27516223,
"uprn": 38192980,
"address": {
"line1": "1 COBBLESTONE CORNER", "line2": "",
"line3": "", "line4": "",
"postTown": "LIVERPOOL", "postcode": "L19 9ES",
"displayAddress": "1 COBBLESTONE CORNER, LIVERPOOL, L19 9ES"
},
"tenure": "commonhold",
"transactionType": ["probate", "freehold_enfranchisement"],
"milestones": {
"listed": { "completed": null },
"soldSubjectToContract": { "completed": "2025-12-06" },
"searches": {
"started": "2026-05-03",
"localAuthority": { "expected": null, "completed": "2026-05-01" },
"environmental": { "completed": null },
"drainageWater": { "completed": null }
},
"funding": { "isCashBuyer": true, "started": null, "completed": null },
"enquiries": { "started": null, "completed": null },
"exchangeOfContracts": { "expected": null, "completed": null },
"completion": { "expected": null, "completed": null }
},
"participants": {
"sellingAgent": "Branch Test",
"sellerConveyancer": "Alun Thomas & John",
"buyerConveyancer": "Harrison & Co"
},
"isTopClosed": false, "isBottomClosed": false,
"connections": {
"onwardPurchase": [30016442, 25101892],
"buyersSale": [100021200034]
}
},
// Property 2 - onward purchase branch A (reached Exchange)
{
"id": 27516224,
"uprn": 30016442,
"address": {
"line1": "11 ADBER CLOSE",
"postTown": "YEOVIL", "postcode": "BA21 5XJ",
"displayAddress": "11 ADBER CLOSE, YEOVIL, BA21 5XJ"
},
"tenure": "", "transactionType": [],
"milestones": {
"listed": { "completed": null },
"soldSubjectToContract": { "completed": "2026-04-15" },
"searches": {
"started": "2026-04-28",
"localAuthority": { "expected": null, "completed": "2026-04-28" },
"environmental": { "completed": null },
"drainageWater": { "completed": null }
},
"funding": { "isCashBuyer": false, "started": "2026-05-02", "completed": null },
"enquiries": { "started": null, "completed": null },
"exchangeOfContracts": { "expected": null, "completed": "2026-05-04" },
"completion": { "expected": null, "completed": null }
},
"participants": {
"sellingAgent": "Connells - Kenilworth",
"sellerConveyancer": "Roger Male & Co",
"buyerConveyancer": "Ison Harrison & Co"
},
"isTopClosed": false, "isBottomClosed": false,
"connections": {
"onwardPurchase": [], "buyersSale": [38192980]
}
},
// Property 3 - onward purchase branch B (isTopClosed: true = confirmed no further up)
{
"id": 27516225,
"uprn": 25101892,
"address": {
"line1": "54 MOORGATE", "line4": "LEADENHALL",
"postTown": "MILTON KEYNES", "postcode": "MK6 5NA",
"displayAddress": "54 MOORGATE, LEADENHALL, MILTON KEYNES, MK6 5NA"
},
"tenure": "", "transactionType": [],
"milestones": {
"listed": { "completed": null },
"soldSubjectToContract": { "completed": "2026-04-29" },
"searches": {
"started": null,
"localAuthority": { "expected": null, "completed": null },
"environmental": { "completed": null },
"drainageWater": { "completed": null }
},
"funding": { "isCashBuyer": false, "started": null, "completed": null },
"enquiries": { "started": null, "completed": null },
"exchangeOfContracts": { "expected": null, "completed": null },
"completion": { "expected": null, "completed": null }
},
"participants": {
"sellingAgent": "Connells - Plymstock",
"sellerConveyancer": "Wholley Goodings",
"buyerConveyancer": "Alun Thomas & John"
},
"isTopClosed": true, "isBottomClosed": false,
"connections": {
"onwardPurchase": [], "buyersSale": [38192980]
}
},
// Property 4 - buyer's sale (isBottomClosed: true = confirmed no further onward purchase)
{
"id": 27516226,
"uprn": 100021200034,
"address": {
"line1": "73 MANOR ROAD", "line4": "TOTTENHAM",
"postTown": "LONDON", "postcode": "N17 0JH",
"displayAddress": "73 MANOR ROAD, TOTTENHAM, LONDON, N17 0JH"
},
"tenure": "", "transactionType": [],
"milestones": {
"listed": { "completed": null },
"soldSubjectToContract": { "completed": "2026-04-28" },
"searches": {
"started": null,
"localAuthority": { "expected": null, "completed": null },
"environmental": { "completed": null },
"drainageWater": { "completed": null }
},
"funding": { "isCashBuyer": false, "started": null, "completed": null },
"enquiries": { "started": null, "completed": null },
"exchangeOfContracts": { "expected": null, "completed": null },
"completion": { "expected": null, "completed": null }
},
"participants": {
"sellingAgent": "Peter Facenna",
"sellerConveyancer": "Harrison & Co",
"buyerConveyancer": "Blazer, Mills, Winter, Taylor"
},
"isTopClosed": false, "isBottomClosed": true,
"connections": {
"onwardPurchase": [38192980], "buyersSale": []
}
}
]
}
],
"pagination": {
"total": 1, "perPage": 15,
"currentPage": 1, "lastPage": 1
}
},
"provenance": {
"alg": "RS256",
"kid": "D0L7Od06mNdegj8zGHZEV16QKyIJEotU9nvd8qMrP5I",
"signature": "BPGWzgURVfNpvCSh0pxqwk_Ay3lpg...<base64url RS256 over JCS(data)>",
"signedAt": "2026-06-02T03:18:26Z",
"payloadHash": {
"alg": "SHA-256",
"value": "CTPeW4KCpZEw6SbWi1Y0qFGpbsIzu32HL8gKkaIHf0o"
}
}
}
Key Fields
| Field | Type | Description |
|---|---|---|
| chainId | string | UUID identifying the chain group. Stable across requests. |
| chainType | string | IN_CHAIN — straight sequential chain, one buyer and one seller at each link (A→B→C→D). SPLIT_CHAIN — chain branches at one node; one buyer is simultaneously selling two or more properties (forms a Y shape, higher risk of delays). NOT_IN_CHAIN — standalone single property, not part of any chain. |
| chainLength | integer | Total number of properties in this chain. |
| milestones | object | Milestone groups — listed, soldSubjectToContract, searches (with nested localAuthority, environmental, drainageWater), funding, enquiries, exchangeOfContracts, completion are always present. fallThrough is omitted entirely unless a Fall Through milestone has been recorded. See Section 05. |
| milestones.*.expected / .started | string|null | Date the milestone was expected/started. Format: YYYY-MM-DD. null if not recorded. |
| milestones.*.completed | string|null | Date the milestone was completed. Format: YYYY-MM-DD. null if not recorded — except milestones.fallThrough, which does not appear at all until recorded. |
| milestones.funding.isCashBuyer | boolean | true if a Cash Buyer milestone has been recorded for this property. |
| isTopClosed | boolean | true = VMC has confirmed no further properties exist above this one in the chain. |
| isBottomClosed | boolean | true = VMC has confirmed no further properties exist below this one in the chain. |
| connections.onwardPurchase | integer[] | UPRNs of the property/properties this property's seller is buying next. Multiple entries = SPLIT chain. Cross-reference with properties[].uprn. |
| connections.buyersSale | integer[] | UPRNs of the property/properties currently being sold by this property's buyer(s). Cross-reference with properties[].uprn. |
| uprn | integer | Unique Property Reference Number for this property. Returned as an integer. Some pre-registration / new-build records may carry a placeholder value. |
| transactionType | string[] | auction, buy_to_let, freehold_enfranchisement, power_of_attorney, probate, repossession, shared_ownership. Empty array for a standard purchase. |
| tenure | string | freehold, leasehold, commonhold, shareoffreehold. Empty string if unknown. |
Chain Structure — This Example
connections.onwardPurchase and connections.buyersSale UPRNs against properties[].uprn within the same response.
Milestone Codes
properties[].milestones — see the "Response field" column.Completion Date Set and Completion — also map to Pexa/Smoove's completion hub events (completion_set / completion_actioned); that push flow is covered in VMC's separate webhook documentation, not this provider API. Completion Date Set is delivered only via webhook (Section 07) — it has no field in the /chains response.| # | VMC Milestone (label) | Description | Response field |
|---|---|---|---|
| 01 | Property Listed | Property listed for sale | milestones.listed.completed |
| 02 | SSTC | Sold Subject to Contract | milestones.soldSubjectToContract.completed |
| 03 | Searches Ordered | Conveyancer has ordered searches | milestones.searches.started |
| 04 | LA Search Expected | Local Authority search results expected | milestones.searches.localAuthority.expected |
| 05 | Search Delivered | Local Authority search results received | milestones.searches.localAuthority.completed |
| 06 | Environmental Search Delivered | Environmental search results received | milestones.searches.environmental.completed |
| 07 | Water Search Delivered | Drainage & water search results received | milestones.searches.drainageWater.completed |
| 08 | Cash Buyer | Cash purchase confirmed | milestones.funding.isCashBuyer (boolean) |
| 09 | Mortgage Applied | Mortgage application submitted | milestones.funding.started |
| 10 | Mortgage Offered | Mortgage offer received | milestones.funding.completed |
| 11 | Enquiries Opened | Conveyancer enquiries raised | milestones.enquiries.started |
| 12 | Enquiries Closed | Conveyancer enquiries resolved | milestones.enquiries.completed |
| 13 | Proposed Exchange | Target exchange date set | milestones.exchangeOfContracts.expected |
| 14 | Exchange | Contracts exchanged — legally binding | milestones.exchangeOfContracts.completed |
| 15 | Proposed Completion | Target completion date set | milestones.completion.expected |
| 16 | Completion | Transaction completed | milestones.completion.completed |
| 17 | Completion Date Set | Completion date confirmed | — (webhook only) |
| 18 | Fall Through | Transaction collapsed | milestones.fallThrough.completed — group omitted entirely if not recorded |
Provenance Verification
curl "https://keystore.directory.pdtf.raidiam.io/a61256b7-cd47-4103-937c-e9b90bbc0457/application.jwks"
kid matches provenance.kid.response.data using JCS (RFC 8785)response.data is the entire top-level data object — i.e. { data: [...], pagination: {...} }, the sibling of provenance. Sort all keys by Unicode code point at every nesting level. The resulting bytes are the canonical representation.provenance.payloadHash.value. Must match.provenance.signature over the raw JCS bytes using VMC's public key. RS256 hashes internally — pass raw bytes directly, do not pre-hash.Webhooks
X-Api-Key header — separate from the mTLS chain API auth.When a milestone event occurs on a VMC-tracked property, VMC POSTs a signed JWT to every registered callback URL subscribed to that event type. The JWT is signed with VMC's RS256 signing key — verifiable against the same JWKS as the chain API.
Event Types
| Event | Description |
|---|---|
| milestone.updated | A milestone has been recorded on a property (any label from Section 05 except Fall Through — which is delivered as the separate fallthrough event) |
| fallthrough | A property transaction has fallen through |
Subscribe
Register your callback URL to receive webhook events. Authenticate with your X-Api-Key header.
curl -X POST https://admin.demo3.viewmychain.com/api/v1/webhook/subscribe \ -H "X-Api-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "participantName": "Your Organisation Name", "callbackUrl": "https://your-endpoint.example.com/webhook", "events": ["milestone.updated", "fallthrough"] }'
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| participantName | string | required | Your organisation name — used for identification in VMC's subscriber registry |
| callbackUrl | string | required | HTTPS endpoint where VMC will POST webhook events |
| events | string[] | required | Event types to subscribe to: milestone.updated, fallthrough |
Simulate
Trigger a test webhook event to verify your subscription and callback endpoint are working.
Simulate — milestone.updated
curl -X POST https://admin.demo3.viewmychain.com/api/v1/webhook/simulate \ -H "X-Api-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "eventType": "milestone.updated", "propertyId": "p1", "uprn": "12345678987", "date": "2026-06-04", "label": "Searches Ordered" }'
Simulate — fallthrough
curl -X POST https://admin.demo3.viewmychain.com/api/v1/webhook/simulate \ -H "X-Api-Key: your-api-key" \ -H "Content-Type: application/json" \ -d '{ "eventType": "fallthrough", "propertyId": "p1", "uprn": "12345678987", "date": "2026-06-04" }'
Delivery Format
VMC delivers events as signed JWTs via HTTP POST. Content-Type is application/jwt. JWTs expire 300 seconds after issuance.
JWT Header
{
"typ": "JWT",
"alg": "RS256",
"kid": "D0L7Od06mNdegj8zGHZEV16QKyIJEotU9nvd8qMrP5I"
}
Decoded JWT — milestone.updated
{
"iss": "https://admin.demo3.viewmychain.com",
"iat": 1780647593,
"exp": 1780647893,
"eventId": "evt_01KTBDW1TGRBS6YVM057JZ0R2A",
"eventType": "milestone.updated",
"eventTimestamp": "2026-06-05T08:19:53Z",
"propertyId": "p1",
"uprn": "12345678987",
"data": {
"milestone": {
"label": "Searches Ordered",
"date": "2026-06-04"
}
}
}
Decoded JWT — fallthrough
{
"iss": "https://admin.demo3.viewmychain.com",
"iat": 1780647593,
"exp": 1780647893,
"eventId": "evt_01KTBDW1TGRBS6YVM057JZ0R2A",
"eventType": "fallthrough",
"eventTimestamp": "2026-06-05T08:19:53Z",
"propertyId": "p1",
"uprn": "12345678987",
"data": {
"fallThroughDate": "2026-06-04"
}
}
Common JWT Fields
| Field | Type | Description |
|---|---|---|
| iss | string | Issuer — always https://admin.demo3.viewmychain.com |
| iat | integer | Issued at — Unix timestamp |
| exp | integer | Expiry — Unix timestamp (300 seconds after iat) |
| eventId | string | Unique event identifier. Use for deduplication. |
| eventType | string | milestone.updated or fallthrough |
| eventTimestamp | string | ISO 8601 UTC timestamp of when the event occurred |
| propertyId | string | VMC internal property identifier |
| uprn | string | Unique Property Reference Number |
| data | object | Event-specific payload — see event types above |
X-Api-Key header for all webhook requests.For sandbox or production access, contact Dat Dang (dat.dang@viewmychain.com).
Verifying JWT Signatures
The JWKS for verifying webhook signatures is the same as the chain API (see Section 06). Use the kid in the JWT header to select the correct key.
curl "https://keystore.directory.pdtf.raidiam.io/a61256b7-cd47-4103-937c-e9b90bbc0457/application.jwks"
import * as jose from 'jose'; const JWKS_URL = 'https://keystore.directory.pdtf.raidiam.io/a61256b7-cd47-4103-937c-e9b90bbc0457/application.jwks'; const jwks = jose.createRemoteJWKSet(new URL(JWKS_URL)); // jwtString is the raw POST body received at your callback URL const { payload } = await jose.jwtVerify(jwtString, jwks); console.log(payload.eventType); // "milestone.updated" | "fallthrough" console.log(payload.uprn); // property UPRN console.log(payload.data); // event-specific data
using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; var jwksUrl = "https://keystore.directory.pdtf.raidiam.io/a61256b7-cd47-4103-937c-e9b90bbc0457/application.jwks"; var jwksJson = await new HttpClient().GetStringAsync(jwksUrl); var jwks = new JsonWebKeySet(jwksJson); var handler = new JwtSecurityTokenHandler(); var validationParams = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, IssuerSigningKeys = jwks.GetSigningKeys() }; var principal = handler.ValidateToken(jwtString, validationParams, out _); // Access claims via principal.Claims
- Use
eventIdto deduplicate events — future versions may retry delivery on failure. - The
kidin the JWT header identifies which key in the JWKS to use for verification. - JWTs expire 300 seconds after issuance — verify promptly.
- Your
callbackUrlmust use HTTPS.