Где предоставляют через api платный доступ к постоянно обновляемым данным по границам административных районов городов и регионов?
Нельзя легально выдернуть границы из гугла и яндекса.

На форуме же дали ссылку на наборы от nexgis, этого вполне достаточно для подобных задач.
Я такой скрипт написал, по кругу гонял, потому там бывало вытягивался список не тех границ,  вроде все данные 
 чистые вытянул. 

Суть в чем, сперва фильтруем по boundary=administrative, потом в получившимся мусоре ищем по type administrativ и в display_name в первом значении район, также слово район в запросе быть в результатах левой или с правой стороны от названия района, это тоже проверяем.

<? php
function curl($url = null, $user_agent = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 OPR/57.0.3098.116', $proxy = [], $header = [], $referer = null, $post = null)
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    //curl_setopt ( $ch, CURLOPT_VERBOSE, 2 );
    curl_setopt($ch, CURLOPT_ENCODING, 0);
    curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
    curl_setopt($ch, CURLOPT_FAILONERROR, 1);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, 120);
    curl_setopt($ch, CURLOPT_COOKIEJAR, __DIR__ . '/cookie.txt');
    curl_setopt($ch, CURLOPT_COOKIEFILE, __DIR__ . '/cookie.txt');
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    if (count($proxy)) {
        curl_setopt($ch, CURLOPT_PROXY, $proxy['ip']);
        curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxy['auth']);
    }
    if (count($header)) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
    }
    if ($referer) {
        curl_setopt($ch, CURLOPT_REFERER, $referer);
    }
    if ($post) {
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
    }
    curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 120);
    $content = preg_replace(['/\>[^\S ]+/s', '/[^\S ]+\</s', '/(\s)+/s', '/> </'], ['>', '<', '\\1', '><'], curl_exec($ch));
    curl_close($ch);
    return $content;
}
function get_lvl(array $array) {
    $max_lvl = 1;
    foreach ($array as $value) {
        if (is_array($value)) {
            $lvl= get_lvl($value) + 1;
            if ($lvl > $max_lvl) {
                $max_lvl = $lvl;
            }
        }
    }
    return $max_lvl;
}
error_reporting(E_ALL);
if (isset($_GET['page'])) {
    $_GET['page'] += 1;
}
else {
    $_GET['page'] = 0;
}
//Тут у меня дергаются из базы названия районов, у вас соответственно будет свой вариант
$sql = '
	SELECT
		*
	FROM
		`hi_district`
	WHERE
		`status` = "1" AND
		`region` != "0"';
//$sql .= ' AND `name` LIKE "Калининский" AND `city` LIKE "Челябинск"';
$sql .= '
	ORDER BY
		`id` ASC
	LIMIT
		' . $_GET['page'] * 1 . ', 1';
	$r = $db->query($sql);
if ($db->numrows($r) > 0) {
	while ($f = $db->fetcharray($r)) {
		$i = 0;
		$j = 0;
		$find = false;
		$f['name'] = 'район ' . $f['name'];
		name:
		$response = json_decode(curl('https://nominatim.openstreetmap.org/search?q=' . urlencode($f['name'] . ' ' . $f['city']) . '&format=json&polygon_geojson=1&boundary=administrative'), true);
		$count = count($response);
		while($i <= ($count - 1)) {
			$tmp = explode(', ', $response[$i]['display_name']);
			if ($response[$i]['type'] === 'administrative' && (stripos($tmp[0], 'район ') !== false || stripos($tmp[0], ' район') !== false)) {
				print_r($response[$i]);
				$result = [];
				if ($response[$i]['geojson']['type'] === 'MultiPolygon') {
					$coords = $response[$i]['geojson']['coordinates'];
					foreach ($coords as $coord) {
						$tmp2 = [];
						foreach ($coord[0] as $item) {
							$tmp2[] = array_reverse($item);
						}
						$result[] = $tmp2;
					}
				} elseif ($response[$i]['geojson']['type'] === 'Polygon') {
					$coords = $response[$i]['geojson']['coordinates'][0];
					foreach ($coords as $coord) {
						$result[] = array_reverse($coord);
					}
				}
				if ($result) {
					if(get_lvl($result) >= 3) {
						$f['boundary'] = $result[0];
					}
					else {
						$f['boundary'] = $result;	
					}
// Запись границ координат
					$sql = '
						UPDATE
							`hi_district`
						SET
							`boundary` = "' . serialize($f['boundary']) . '"
						WHERE
							`id`	= "' . $f['id'] . '"';
					$db->query($sql);
					$find = true;
					echo $find;
					break;
				}
			}
			$i++;
		}
		if ($find === false && $j == 0) {
			$f['name'] = str_replace('район ', '', $f['name']) . ' район';
			echo $find;
			sleep(2);
			$i = 0;
			$j++;
			goto name;
		}
	}
    echo '<meta http-equiv="refresh" content="2; url='ваш сайт/get_district.php?page=' . $_GET['page'] . '">';
    exit();
}
else {
    echo '<br />Done';
}