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. |
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 |
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. Only milestones with a recorded date are returned.{
"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 downward)
{
"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": [
{ "label": "SSTC", "date": "2025-12-06" },
{ "label": "Searches Ordered", "date": "2026-05-03" },
{ "label": "Cash Buyer", "date": "2026-05-04" },
{ "label": "Search Delivered", "date": "2026-05-01" }
],
"participants": {
"sellingAgent": "Branch Test",
"sellerConveyancer": "Alun Thomas & John",
"buyerConveyancer": "Harrison & Co"
},
"isTopClosed": false, "isBottomClosed": false,
"connections": {
"upwardChain": [27516226],
"downwardChain": [27516224, 27516225]
}
},
// Property 2 - downward 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": [
{ "label": "SSTC", "date": "2026-04-15" },
{ "label": "Searches Ordered", "date": "2026-04-28" },
{ "label": "Search Delivered", "date": "2026-04-28" },
{ "label": "Mortgage Applied", "date": "2026-05-02" },
{ "label": "Exchange", "date": "2026-05-04" }
],
"participants": {
"sellingAgent": "Connells - Kenilworth",
"sellerConveyancer": "Roger Male & Co",
"buyerConveyancer": "Ison Harrison & Co"
},
"isTopClosed": false, "isBottomClosed": false,
"connections": {
"upwardChain": [27516223], "downwardChain": []
}
},
// Property 3 - downward 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": [
{ "label": "SSTC", "date": "2026-04-29" }
],
"participants": {
"sellingAgent": "Connells - Plymstock",
"sellerConveyancer": "Wholley Goodings",
"buyerConveyancer": "Alun Thomas & John"
},
"isTopClosed": true, "isBottomClosed": false,
"connections": {
"upwardChain": [27516223], "downwardChain": []
}
},
// Property 4 - upward (isBottomClosed: true = confirmed no further down)
{
"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": [
{ "label": "SSTC", "date": "2026-04-28" }
],
"participants": {
"sellingAgent": "Peter Facenna",
"sellerConveyancer": "Harrison & Co",
"buyerConveyancer": "Blazer, Mills, Winter, Taylor"
},
"isTopClosed": false, "isBottomClosed": true,
"connections": {
"upwardChain": [], "downwardChain": [27516223]
}
}
]
}
],
"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[].label | string | Canonical milestone identifier. See Section 05. Only milestones with a recorded date are included. |
| milestones[].date | string | Date when milestone was recorded. Format: YYYY-MM-DD. |
| 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.upwardChain | integer[] | VMC internal IDs of properties above this one. Cross-reference with properties[].id. |
| connections.downwardChain | integer[] | VMC internal IDs of properties below this one. Multiple entries = SPLIT chain. |
| 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.upwardChain and connections.downwardChain IDs against properties[].id within the same response.
Milestone Codes
label field is the canonical identifier. Only milestones with a recorded date are returned.label values below are the canonical identifiers returned in milestones[].label. Two of them — 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.| # | VMC Milestone (label) | Description |
|---|---|---|
| 01 | SSTC | Sold Subject to Contract |
| 02 | Searches Ordered | Conveyancer has ordered searches |
| 03 | Search Delivered | Search results received |
| 04 | Cash Buyer | Cash purchase confirmed |
| 05 | Mortgage Applied | Mortgage application submitted |
| 06 | Mortgage Offered | Mortgage offer received |
| 07 | Exchange | Contracts exchanged — legally binding |
| 08 | Completion Date Set | Completion date confirmed |
| 09 | Completion | Transaction completed |
| 10 | Fall Through | Transaction collapsed |
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 Huy Nguyen (huy.nguyen@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.