curl --request POST \
--url https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"dry_run": true,
"phone_region": "US",
"proximity_m": 100,
"rows": [
{
"address_line_1": "1250 Main St",
"city": "Springfield",
"country": "US",
"first_party": true,
"lat": 39.7817,
"lon": -89.6501,
"name": "Ulta Beauty",
"postal_code": "62704",
"region": "IL",
"source": "ulta_site",
"store_number": "595"
}
]
}
'import requests
url = "https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch"
payload = {
"dry_run": True,
"phone_region": "US",
"proximity_m": 100,
"rows": [
{
"address_line_1": "1250 Main St",
"city": "Springfield",
"country": "US",
"first_party": True,
"lat": 39.7817,
"lon": -89.6501,
"name": "Ulta Beauty",
"postal_code": "62704",
"region": "IL",
"source": "ulta_site",
"store_number": "595"
}
]
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
dry_run: true,
phone_region: 'US',
proximity_m: 100,
rows: [
{
address_line_1: '1250 Main St',
city: 'Springfield',
country: 'US',
first_party: true,
lat: 39.7817,
lon: -89.6501,
name: 'Ulta Beauty',
postal_code: '62704',
region: 'IL',
source: 'ulta_site',
store_number: '595'
}
]
})
};
fetch('https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch', 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://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'dry_run' => true,
'phone_region' => 'US',
'proximity_m' => 100,
'rows' => [
[
'address_line_1' => '1250 Main St',
'city' => 'Springfield',
'country' => 'US',
'first_party' => true,
'lat' => 39.7817,
'lon' => -89.6501,
'name' => 'Ulta Beauty',
'postal_code' => '62704',
'region' => 'IL',
'source' => 'ulta_site',
'store_number' => '595'
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch"
payload := strings.NewReader("{\n \"dry_run\": true,\n \"phone_region\": \"US\",\n \"proximity_m\": 100,\n \"rows\": [\n {\n \"address_line_1\": \"1250 Main St\",\n \"city\": \"Springfield\",\n \"country\": \"US\",\n \"first_party\": true,\n \"lat\": 39.7817,\n \"lon\": -89.6501,\n \"name\": \"Ulta Beauty\",\n \"postal_code\": \"62704\",\n \"region\": \"IL\",\n \"source\": \"ulta_site\",\n \"store_number\": \"595\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"dry_run\": true,\n \"phone_region\": \"US\",\n \"proximity_m\": 100,\n \"rows\": [\n {\n \"address_line_1\": \"1250 Main St\",\n \"city\": \"Springfield\",\n \"country\": \"US\",\n \"first_party\": true,\n \"lat\": 39.7817,\n \"lon\": -89.6501,\n \"name\": \"Ulta Beauty\",\n \"postal_code\": \"62704\",\n \"region\": \"IL\",\n \"source\": \"ulta_site\",\n \"store_number\": \"595\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"dry_run\": true,\n \"phone_region\": \"US\",\n \"proximity_m\": 100,\n \"rows\": [\n {\n \"address_line_1\": \"1250 Main St\",\n \"city\": \"Springfield\",\n \"country\": \"US\",\n \"first_party\": true,\n \"lat\": 39.7817,\n \"lon\": -89.6501,\n \"name\": \"Ulta Beauty\",\n \"postal_code\": \"62704\",\n \"region\": \"IL\",\n \"source\": \"ulta_site\",\n \"store_number\": \"595\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"already_claimed_stores": 0,
"committed": false,
"coverage_after": {
"stores": 1622,
"with_phone": 3,
"with_point": 1622
},
"created": 1,
"enriched": 2,
"errors": 0,
"existing_stores": 1621,
"fields_superseded": 4,
"legacy_keys_preserved": 2,
"matched_address": 0,
"matched_proximity": 2,
"matched_store_number": 0,
"near_misses": 1,
"not_created": 0,
"phones_written": 3,
"rekey_skipped_conflict": 0,
"row_errors": [],
"rows": [
{
"conflict": "",
"distance_m": 18.4,
"error": "",
"index": 0,
"match_method": "proximity",
"outcome": "enriched",
"store_id": "mhvXdrZT4jP5T8vBxuvm75",
"store_number": "595"
}
],
"submitted_rows": 3,
"unchanged": 0,
"unmatched_existing": 1619,
"unmatched_existing_store_ids": [
"kX4bR8WqkQx2Y7jJ9mZt3P"
],
"urls_written": 3
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}Upsert physical stores in bulk
Reconcile and apply up to 1,000 store rows for one retailer, returning a per-row result.
Rows are reconciled, not inserted. Each row is tied to an existing store by exact store number, then by proximity within proximity_m, then by normalised street plus postal code — one-to-one, highest-confidence tier first. A match enriches the existing store in place; only a row that matches nothing creates one. This is why the endpoint is safe to point at a full dataset that overlaps rows already in the database: an insert-only surface would plant a duplicate beside every store whose recorded key predates the retailer’s own numbering.
Nothing is ever merged, deactivated or deleted here. An existing store no row claims is counted in unmatched_existing and left exactly as it is; a pair the matcher finds contradictory is reported in the row’s conflict and left unrekeyed.
dry_run defaults to true: every path executes against real rows inside a transaction that is then rolled back, so the counts and per-row outcomes are the matcher’s real verdict rather than an estimate, and a first call writes nothing. Send dry_run: false to commit.
A dataset larger than one call. Batch it, and send each call the store_ids the other batches’ rows landed on as already_claimed_store_ids. Matching then stays one-to-one across the whole dataset — two rows in different batches can never both claim one existing store — and the last call’s unmatched_existing is the dataset figure. Without it every census field is per call and unmatched_existing does not sum.
retailer_id is a retailers.Retailer. An organization-private retailer requires OWNER or MANAGER on the organization that owns it; a canonical (Closient-curated) retailer requires a staff account, because those rows were previously writable only from a management command. The organization this credential acts for becomes the owning organization of any location record created.
Returns 200 with a per-row error report even on partial success — only request-level failures produce 4xx.
curl --request POST \
--url https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"dry_run": true,
"phone_region": "US",
"proximity_m": 100,
"rows": [
{
"address_line_1": "1250 Main St",
"city": "Springfield",
"country": "US",
"first_party": true,
"lat": 39.7817,
"lon": -89.6501,
"name": "Ulta Beauty",
"postal_code": "62704",
"region": "IL",
"source": "ulta_site",
"store_number": "595"
}
]
}
'import requests
url = "https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch"
payload = {
"dry_run": True,
"phone_region": "US",
"proximity_m": 100,
"rows": [
{
"address_line_1": "1250 Main St",
"city": "Springfield",
"country": "US",
"first_party": True,
"lat": 39.7817,
"lon": -89.6501,
"name": "Ulta Beauty",
"postal_code": "62704",
"region": "IL",
"source": "ulta_site",
"store_number": "595"
}
]
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
dry_run: true,
phone_region: 'US',
proximity_m: 100,
rows: [
{
address_line_1: '1250 Main St',
city: 'Springfield',
country: 'US',
first_party: true,
lat: 39.7817,
lon: -89.6501,
name: 'Ulta Beauty',
postal_code: '62704',
region: 'IL',
source: 'ulta_site',
store_number: '595'
}
]
})
};
fetch('https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch', 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://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'dry_run' => true,
'phone_region' => 'US',
'proximity_m' => 100,
'rows' => [
[
'address_line_1' => '1250 Main St',
'city' => 'Springfield',
'country' => 'US',
'first_party' => true,
'lat' => 39.7817,
'lon' => -89.6501,
'name' => 'Ulta Beauty',
'postal_code' => '62704',
'region' => 'IL',
'source' => 'ulta_site',
'store_number' => '595'
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch"
payload := strings.NewReader("{\n \"dry_run\": true,\n \"phone_region\": \"US\",\n \"proximity_m\": 100,\n \"rows\": [\n {\n \"address_line_1\": \"1250 Main St\",\n \"city\": \"Springfield\",\n \"country\": \"US\",\n \"first_party\": true,\n \"lat\": 39.7817,\n \"lon\": -89.6501,\n \"name\": \"Ulta Beauty\",\n \"postal_code\": \"62704\",\n \"region\": \"IL\",\n \"source\": \"ulta_site\",\n \"store_number\": \"595\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"dry_run\": true,\n \"phone_region\": \"US\",\n \"proximity_m\": 100,\n \"rows\": [\n {\n \"address_line_1\": \"1250 Main St\",\n \"city\": \"Springfield\",\n \"country\": \"US\",\n \"first_party\": true,\n \"lat\": 39.7817,\n \"lon\": -89.6501,\n \"name\": \"Ulta Beauty\",\n \"postal_code\": \"62704\",\n \"region\": \"IL\",\n \"source\": \"ulta_site\",\n \"store_number\": \"595\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://www.closient.com/retailers/api/v1/retailers/{retailer_id}/stores/batch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"dry_run\": true,\n \"phone_region\": \"US\",\n \"proximity_m\": 100,\n \"rows\": [\n {\n \"address_line_1\": \"1250 Main St\",\n \"city\": \"Springfield\",\n \"country\": \"US\",\n \"first_party\": true,\n \"lat\": 39.7817,\n \"lon\": -89.6501,\n \"name\": \"Ulta Beauty\",\n \"postal_code\": \"62704\",\n \"region\": \"IL\",\n \"source\": \"ulta_site\",\n \"store_number\": \"595\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"already_claimed_stores": 0,
"committed": false,
"coverage_after": {
"stores": 1622,
"with_phone": 3,
"with_point": 1622
},
"created": 1,
"enriched": 2,
"errors": 0,
"existing_stores": 1621,
"fields_superseded": 4,
"legacy_keys_preserved": 2,
"matched_address": 0,
"matched_proximity": 2,
"matched_store_number": 0,
"near_misses": 1,
"not_created": 0,
"phones_written": 3,
"rekey_skipped_conflict": 0,
"row_errors": [],
"rows": [
{
"conflict": "",
"distance_m": 18.4,
"error": "",
"index": 0,
"match_method": "proximity",
"outcome": "enriched",
"store_id": "mhvXdrZT4jP5T8vBxuvm75",
"store_number": "595"
}
],
"submitted_rows": 3,
"unchanged": 0,
"unmatched_existing": 1619,
"unmatched_existing_store_ids": [
"kX4bR8WqkQx2Y7jJ9mZt3P"
],
"urls_written": 3
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}{
"detail": "The requested resource was not found.",
"error_code": "not_found",
"retryable": false,
"status": 404,
"timestamp": "2026-03-31T12:00:00+00:00",
"title": "Not Found",
"type": "https://closient.com/docs/errors/not_found"
}Authorizations
Path Parameters
Unique identifier of the retailer these stores belong to.
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$Body
A batch of store rows plus the reconciliation knobs for the call.
Store rows to reconcile, at most 1,000 per call and at most 2.5 MiB (2,621,440 bytes) of request body — the server's upload ceiling, answered with 413 when exceeded. The byte limit usually binds first: 1,000 first-party rows carrying seven-day opening hours in extras serialise to about 2.7 MB, so size batches by bytes, not rows. Results come back in request order, each carrying its index. Matching is one-to-one within a call, so a store claimed by an earlier call is re-matched on the store-number tier in the next one — which is what makes repeating a batch idempotent rather than duplicative. To reconcile one dataset split across calls as if it were one pass, send already_claimed_store_ids.
1 - 1000 elementsShow child attributes
Show child attributes
For a dataset split across several calls: the store_id of every row result from the other batches of the same run (at most 50,000). No row in this call can match one of these stores, so matching stays one-to-one across the whole dataset rather than per call, and they are left out of unmatched_existing — which makes the last call's unmatched_existing and unmatched_existing_store_ids the dataset-wide figures. Leave out this batch's own earlier results when retrying it, or its rows cannot re-match their stores. Ids that are not this retailer's stores are ignored; already_claimed_stores reports how many were used.
50000URL-safe 22-character shortuuid encoding of the row's UUID primary key. Stable across the row's lifetime; suitable for sharing in URLs, log lines, and external SDK clients. Accepted on input as either the shortuuid form or the canonical UUID form (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$When true (the default) every path runs against real rows and the transaction is rolled back, so the results are the matcher's real verdict and nothing is written. Send false to commit.
Match radius in metres for the proximity tier (default 100). Capped at 500 m: pairs beyond the match radius but within it are reported as near_misses for a human to judge, and widening the radius to force a match is exactly what the tiered matcher exists to avoid.
x <= 500ISO 3166-1 alpha-2 region used to parse national phone numbers into E.164. An unassigned code parses nothing, so every phone in the batch would be counted invalid rather than written — silently, since a bad parse is a counter and not an error.
2^[A-Za-z]{2}$Response
OK
Batch census plus the per-row results.
created + enriched + unchanged + not_created + errors equals the
number of submitted rows for any 200 response. The endpoint returns 200
even when individual rows fail: a per-row failure is reported here rather
than aborting the batch, so a single malformed row cannot cost the other
999. Request-level failures (auth, an unwritable retailer, a batch over
the row cap) are 4xx instead.
False when this was a dry run — every path ran and the transaction was rolled back.
Number of rows in the request.
x >= 0Stores this retailer had when this call started, including any in already_claimed_store_ids. Measured per call: on a committed split run it grows by the stores earlier batches created.
x >= 0Rows that produced a new store.
x >= 0Rows that matched and changed something.
x >= 0Rows that matched and needed no change.
x >= 0Rows that matched nothing and were not allowed to create a store.
x >= 0Rows that raised. Equals the length of row_errors.
x >= 0Rows matched on the exact store-number tier.
x >= 0Rows matched on the proximity tier.
x >= 0Rows matched on the normalised-address tier.
x >= 0Pairs beyond proximity_m but within 500 m that were deliberately NOT matched. A judgement call for a human, never auto-matched by widening the radius. Per call: a split run's sum can include a pair whose store a later batch went on to claim.
x >= 0Existing stores no submitted row claimed and not in already_claimed_store_ids. For a full-dataset run these are stores the retailer's own list no longer carries, i.e. probably closed — reported, never deactivated or deleted. Per call, measured against the whole retailer: for a split run, read it from the last call, sent with every other batch's store_ids; summing per-call values double-counts.
x >= 0How many already_claimed_store_ids were this retailer's stores and so were held out of matching. Lower than the number sent means some ids were ignored — for example created ids from a dry run, whose stores never persisted.
x >= 0Phone numbers written or refreshed.
x >= 0Store URLs written or refreshed.
x >= 0Existing non-placeholder values a first-party row overwrote. The previous value is preserved under Place.source_extras['superseded'] rather than discarded.
x >= 0Stores whose pre-existing store number was replaced by this source's real one, with the old key kept in Place.source_extras['legacy_store_key'].
x >= 0Stores left on their existing key because the evidence for re-keying was contradictory.
x >= 0The ids of the stores counted in unmatched_existing, so a human can review exactly which stores the dataset no longer carries.
URL-safe 22-character shortuuid encoding of the row's UUID primary key. Stable across the row's lifetime; suitable for sharing in URLs, log lines, and external SDK clients. Accepted on input as either the shortuuid form or the canonical UUID form (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
22^[23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{22}$Per-column coverage for this retailer's stores after the call — stores, with_point, with_real_address, with_phone, with_url, with_source_extras. On a dry run these are the post-rollback figures the run would have produced, measured inside the transaction.
Show child attributes
Show child attributes
One result per submitted row, in request order.
Show child attributes
Show child attributes
Batch-level restatement of every row that raised, for callers that read only the census.