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 results of a data validation run started by
POST /configuration/data-validation-runs.
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
Company identifier
The run identifier returned by POST /configuration/data-validation-runs. Runs are scoped to their company: a
run id belonging to another company returns 404, exactly as an id that
exists nowhere does.
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.
The run identifier
queued while the run waits for a worker (a normal state, not a stall), running mid-scan, then the terminal succeeded or failed
queued, running, succeeded, failed How many stored records the scan has classified so far
Records that would persist cleanly under the scanned configuration — no undeclared keys and no violations
Records holding keys the scanned configuration no longer declares (suspect, not necessarily fatal)
Records with one or more advisory findings and no breaking violations. This bucket may overlap recordsWithExtraKeys.
Records holding a value that would FAIL a write under the scanned configuration
Why the scan stopped; null unless status is failed
When the run was started
When the run's counts or status last changed
When the run reached a terminal status; null until then
