curl --request GET \
--url https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId} \
--header 'Authorization: Bearer <token>'import requests
url = "https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"runId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"status": "queued",
"recordsScanned": 0,
"recordsAdhering": 0,
"recordsWithExtraKeys": 0,
"recordsWithAdvisories": 0,
"recordsBreaking": 0,
"error": null,
"createdAt": "2026-07-30T12:00:00.000Z",
"updatedAt": "2026-07-30T12:00:00.000Z",
"finishedAt": null
}Get Data Validation Run
Reports the progress and record counts of a data validation run started by
Start Data Validation Run (startFmv1DataValidationRun). It answers how
many records were scanned and how many would break, not which ones. Once the
run finishes, call List Data Validation Run Findings
(listFmv1DataValidationRunFindings) for the records and fields at fault.
Poll this until status is succeeded or failed. The counts climb as the scan
progresses, so a poll mid-scan is a real partial reading, not a placeholder.
Statuses
queued— the run exists and is waiting for a worker. Its counts are all0. This is a normal state that every run passes through, not a stall: the run is recorded before the background work is dispatched.running— the scan is in progress; the counts climb.succeeded— the whole book was scanned.finishedAtis set,errorisnull.failed— the scan stopped early.finishedAtis set anderrorcarries the reason. Start a new run to retry.
The counts
recordsScanned is how many records the scan has classified so far. The other
four are independent, overlapping buckets — a record with an undeclared key
and a value that breaks is counted in both, so they do not sum to
recordsScanned:
recordsAdhering— records that would persist cleanly under the scanned configuration: no undeclared keys and no violations.recordsWithExtraKeys— records holding keys the scanned configuration no longer declares. Suspect, not necessarily fatal.recordsWithAdvisories— records with advisory findings and no breaking violations. These remain writable and may also carry undeclared keys.recordsBreaking— records holding a value that would fail a write under the scanned configuration (a changed type or cardinality, an option-set key that no longer exists, a broken relational reference, a violated invariant).
There is deliberately no total and no percentage: producing a denominator
would mean counting every record of every entity type before the scan could
report anything. Treat recordsScanned as a monotonic progress signal, and the
terminal status as completion.
Per-record detail — which records and which fields — is not part of this response.
How long a run stays readable
A run’s status stays readable for as long as the run exists — nothing removes a run on a timer, and however long ago it completed this endpoint still reports it. A run’s age is never a failure: a run that exists is reported in full however long ago it finished, and no response reports a run as too old to read.
Required permission: configuration.validate
This endpoint requires an API key that holds the configuration permissions — the Admin role does. See Authentication for how to create API keys with specific roles.
curl --request GET \
--url https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId} \
--header 'Authorization: Bearer <token>'import requests
url = "https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://go.aiinsurance.io/api/v1/companies/{companyId}/configuration/data-validation-runs/{runId}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"runId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"status": "queued",
"recordsScanned": 0,
"recordsAdhering": 0,
"recordsWithExtraKeys": 0,
"recordsWithAdvisories": 0,
"recordsBreaking": 0,
"error": null,
"createdAt": "2026-07-30T12:00:00.000Z",
"updatedAt": "2026-07-30T12:00:00.000Z",
"finishedAt": null
}Authorizations
User-principal OAuth 2.0 Bearer authentication. Send a user-scoped Auth0 access token (audience = the app API audience) as Authorization: Bearer <jwt>. The request resolves to the user's identity and is authorized by their Role on the {companyId} in the path — the same role-based permissions the web app enforces. This is the path the MCP connector uses to act on a user's behalf; endpoints that accept it list both BearerAuth and ApiKeyAuth.
Path Parameters
Response
The run's current status and running counts
A data-validation run's current status and running counts. Fixed size: there is
no per-record findings list, and deliberately no total or percentage — the four
classification counts are independent, overlapping buckets and do not sum to
recordsScanned.
