Atlorium

«Причесать адрес» и «найти адрес в реестре» — две разные задачи, и путают их постоянно

Курьеру нужен разобранный адрес, бухгалтерии — код из государственного реестра. Это не одно и то же, и выбор не того инструмента стоит либо лишних денег, либо непринятых документов.

11 минут чтения логистика · бэкофис · разработчик · аналитик

Задача звучала безобидно: привести в порядок 62 тысячи адресов в CRM перед переездом на новую службу доставки. Разработчик взял поиск по государственному адресному реестру, прогнал первые пять тысяч записей — и получил "count": 0 примерно у трети. Строки при этом были совершенно живые: «мск, тверская 7 стр 2, 3 подъезд, домофон 45». Курьер по такой найдёт дом за минуту. Реестр — не найдёт, потому что подъездов и домофонов в реестре нет.

Через неделю та же тема пришла с другой стороны. Бухгалтерии понадобился ОКТМО по адресам двенадцати филиалов — для отчёта, который сдаётся раз в квартал и который уже один раз вернули. Разработчик, наученный прошлым разом, взял стандартизацию адреса. Получил аккуратный разбор, "quality": 100, красивую нормализованную строку — и "gar": null. Ни ОКТМО, ни индекса, ни кода КЛАДР. Их там нет и по замыслу не должно быть.

Обе стороны совершили одну и ту же ошибку с разных концов. «Причесать адрес» и «найти адрес в реестре» — две разные операции, и совпадают они только тем, что обе про адреса.

Граница между ними проходит по одной линии. Стандартизация работает со строкой и ничего не знает о реальном мире: она разбирает текст, а существует ли такой дом — вопрос не к ней. ГАР работает с реестром и почти ничего не знает о том, как люди пишут адреса в свободном поле: этажи, ориентиры и «вход со двора» для него шум.

Где именно течёт

Ошибка выбора инструмента редко выглядит как ошибка. Код работает, ответы приходят, никаких исключений в логах. Просто результат оказывается не тем, за которым шли.

Прогнали базу через реестр и потеряли треть

Что происходит
В CRM адрес лежит одной строкой вместе с подъездом, этажом и комментарием для курьера. Поиск по реестру на такую строку возвращает пустой results и count = 0.
Во что обходится
Запрос выполнен, ответ получен, кредит списан. На 62 тысячах записей это двадцать тысяч оплаченных запросов, которые не могли сработать в принципе.
Чем закрывается
Сначала разбор строки: он выкидывает лишнее, раскладывает по уровням и собирает нормализованное представление. В реестр уходит уже адрес, а не заметка для курьера.

Разбор идеальный, кодов нет

Что происходит
Ответ стандартизации: quality 100, все уровни на месте, normalized выглядит как из учебника. Поля gar и geo — null.
Во что обходится
Квартальный отчёт возвращают повторно, бухгалтер ищет ОКТМО руками по сайту ФНС. Двенадцать филиалов — это полдня и три опечатки в кодах.
Чем закрывается
Коды живут в реестре, а не в парсере. Их отдаёт /api/gar/search в полях oktmo, okato, postalCode — либо /api/gar/object/{guid} в списке parameters с расшифровками типов.

quality 100 приняли за «адрес существует»

Что происходит
Строку «г Москва, ул Вымышленная, д.404» разбор принимает без единой претензии: структура правильная, ничего лишнего, коэффициент максимальный.
Во что обходится
Заказ уезжает на несуществующий дом, курьер возвращает его на склад, клиент получает возврат вместо покупки. Ошибка вскрывается на последней миле — самой дорогой.
Чем закрывается
Существование адреса подтверждает только реестр: /api/gar/search на такую строку вернёт count = 0. Разбор сюда добавить нечего, у него другая работа.

Сколько это стоит и в каком порядке звонить

Разница в деньгах возникает не из-за цены за запрос, а из-за числа запросов, которые вы сделаете зря.

Обозначим: N — записей в базе, d — доля строк, из которых разбор не смог выделить адрес (эти записи в реестр отправлять бессмысленно), q — доля тех, что разобрались, но с низким коэффициентом и уходят человеку на проверку. Наивный сценарий «всё в реестр» стоит N запросов к ГАР. Сценарий с предварительным разбором стоит N разборов плюс N × (1 − d − q) запросов к реестру. Разбор при этом дешевле: он выполняется локально, без обращений к внешним источникам, и у него есть бесплатный дневной лимит в веб-интерфейсе.

Параметр Пример (замерьте свой) Откуда взять
N — записей в базе 62 000 SELECT COUNT из таблицы адресов
d — не разобрались вовсе 0,06 Доля ответов 400 на выборке в тысячу записей
q — разобрались плохо 0,11 Доля ответов с quality ниже вашего порога
Запросов к реестру: наивно 62 000 Каждая строка идёт в поиск как есть
Запросов к реестру: с разбором 51 460 N × (1 − 0,06 − 0,11)

Десять с половиной тысяч запросов разницы — это на порядок больше, чем экономия на цене одного вызова. Но главное даже не в этом. Из 62 тысяч наивных запросов часть вернула бы мусорного кандидата вместо пустого ответа, и вот эти записи вы бы уже не отловили: в базе появился бы GUID чужого дома. Пустой ответ виден сразу, неправильный ответ — через месяц.

Какой сервис под какую задачу

Проще всего выбирать по одному вопросу: что должно оказаться в вашей базе после операции. Если строка — вам нужен разбор. Если идентификатор — вам нужен реестр.

Стандартизация адреса Адреса ГАР/ФИАС
Маршрут /api/addressstd /api/gar/search, /api/gar/suggest, /api/gar/object/{guid}
Что на входе Одна строка как её написал человек — с опечатками, сокращениями, нижним регистром Строка поиска: элементы адреса, можно через запятую
Что на выходе components по уровням, normalized, quality, countryCode results с fullAddress, objectGuid, objectId, regionCode, postalCode, oktmo, okato
Сколько ответов Ровно один разбор Список кандидатов, count штук, выбор за вами
Терпит мусор в строке Да, это его работа. Непонятное уходит в extra и message Плохо. Подъезд и домофон сбивают поиск
Подтверждает существование Нет Да — в пределах того, что есть в выгрузке ФНС
Типовое применение Форма, импорт CSV, чистка legacy-базы, единый формат для перевозчика Автодополнение в поле ввода, коды для отчётности, стабильный ключ для дедупликации

Сценарий первый: только разбор

Клиент оставил адрес доставки, вам нужно передать его курьерской службе в её формате: город отдельно, улица отдельно, дом с корпусом отдельно. Реестр здесь ничего не добавит — курьеру не нужен GUID объекта, ему нужны поля. Один вызов /api/addressstd, разложили components по колонкам, отдали.

Полезная деталь: параметр region — двузначный код региона. Если у вас интернет- магазин с доставкой по одному городу и люди пишут «ленинский проспект 4» без города, передавайте region=77, и верхний уровень восстановится сам.

Сценарий второй: только реестр

В форме заказа стоит поле с автодополнением. Человек начинает печатать, вы дёргаете /api/gar/suggest (минимум два символа, по умолчанию семь подсказок, максимум двадцать), он выбирает готовый вариант из списка. Разбирать нечего — строки в свободной форме не возникло вообще. В базу вы кладёте objectId и objectGuid, а не текст.

Это самый дешёвый способ решить проблему грязных адресов: не чистить их потом, а не дать им появиться. Но работает он только там, где есть живой пользователь и интерфейс.

Сценарий третий: оба, по очереди

Миграция legacy-базы, где адрес хранится строкой и нужен ОКТМО. Здесь два вызова, и порядок имеет значение.

  1. Разбор строки: /api/addressstd. Получаем components и quality.
  2. Отсев: всё ниже порога — в очередь на ручную проверку, дальше не идёт.
  3. Сборка поискового запроса из уровней от региона до дома. Квартиру, комнату и всё, что попало в extra, в запрос не кладём.
  4. Поиск: /api/gar/search. Смотрим count и решаем, что делать с кандидатами.
Что получили Что делать
400 от разбора Адрес из строки не выделен. В реестр не идём, запрос не оплачен. Запись — оператору
quality ниже порога Разбор неуверенный. Поиск по нему даст правдоподобный, но чужой дом. Оператору
count = 0 Реестр адрес не знает. Причины разные: снесённый дом, новостройка, ошибка в номере. Оператору
count = 1 Единственный кандидат. Забираем objectGuid, postalCode, oktmo, okato
count больше единицы Однофамильцы среди улиц — обычное дело. Если regionCode кандидата совпадает с ожидаемым и он один такой — берём его. Иначе оператору

Код: два вызова и склейка между ними

Всё ниже работает с публичным демо-ключом ak_sandbox_demo_mockdata_v1 — копируйте и запускайте, аккаунт не нужен. На демо-ключе оба сервиса отдают моки: правдоподобные, но сгенерированные данные. Они детерминированы, один и тот же запрос всегда даёт один и тот же ответ, поэтому на них удобно писать тесты. Настоящий разбор и настоящие записи реестра приходят только с личным ключом.

Разбор строки: что отдаёт стандартизация

# Строку кодируем через --data-urlencode: кириллица и пробелы в query иначе поедут не так
curl -G -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     --data-urlencode "address=мск ленинскй проспект д4 кв5" \
     --data-urlencode "region=77" \
     "https://atlorium.com/api/addressstd"
        

import requests

BASE = "https://atlorium.com"
HEADERS = {"Authorization": "Bearer ak_sandbox_demo_mockdata_v1"}


def parse_address(line: str, region: int | None = None) -> dict | None:
    """Разбор строки. None означает, что адрес из неё не выделен (400, без списания)."""
    resp = requests.get(
        f"{BASE}/api/addressstd",
        params={"address": line, "region": region},
        headers=HEADERS,
        timeout=10,
    )
    if resp.status_code == 400:
        return None
    resp.raise_for_status()
    return resp.json()


addr = parse_address("мск ленинскй проспект д4 кв5", region=77)

# components отсортированы сверху вниз: от страны к комнате.
# level — имя элемента перечисления, не число: Street, Building, Apartment...
levels = {c["level"]: c for c in addr["components"]}

street = levels.get("Street", {}).get("text")
house = levels.get("Building", {})
print(street, house.get("number"), house.get("buildNumber"))
        

using System.Net.Http.Json;
using System.Text.Json;

var http = new HttpClient { BaseAddress = new Uri("https://atlorium.com") };
http.DefaultRequestHeaders.Add("Authorization", "Bearer ak_sandbox_demo_mockdata_v1");

var line = Uri.EscapeDataString("мск ленинскй проспект д4 кв5");
var addr = await http.GetFromJsonAsync<JsonElement>($"/api/addressstd?address={line}&region=77");

var quality = addr.GetProperty("quality").GetInt32();
var normalized = addr.GetProperty("normalized").GetString();

// Порог свой. Ниже — не в реестр, а человеку.
if (quality < 80)
    Console.WriteLine($"На проверку: {normalized} (quality {quality})");
        

{
  "input": "мск ленинскй проспект д4 кв5",
  "normalized": "город Москва, проспект Ленинский, д.4, кв.5",
  "quality": 100,
  "countryCode": "RU",
  "message": null,
  "info": null,
  "components": [
    { "level": "City", "levelTitle": "Город", "text": "город Москва",
      "types": ["город"], "names": ["Москва"] },
    { "level": "Street", "levelTitle": "Улица", "text": "проспект Ленинский",
      "types": ["проспект"], "names": ["Ленинский"] },
    { "level": "Building", "levelTitle": "Дом/здание", "text": "д.4", "number": "4" },
    { "level": "Apartment", "levelTitle": "Помещение/квартира", "text": "кв.5", "number": "5" }
  ],
  "extra": null,
  "milliseconds": 7,
  "gar": null,
  "geo": null
}
        

Тот же вызов на шести языках (Python, TypeScript, Go, Java, C#, PHP) — в репозитории address-standardization-api-client. Примеры запускаются сразу: ключ в них уже стоит демонстрационный.

Два поля из этого ответа заслуживают отдельного взгляда. message — это жалоба разбора на конкретную неточность, и она же объясняет, почему коэффициент просел: непонятный фрагмент, лишний хвост. info — сообщение, которое на качество не влияет, чисто справочное. Логируйте оба: когда через месяц придёте разбираться, почему у трёх тысяч записей quality около шестидесяти, ответ будет лежать именно там.

Поля gar и geo в контракте уже есть, но заполняются они только в комбо-режимах, а те пока отвечают 501. Писать код в расчёте на то, что addr["gar"]["oktmo"] когда-нибудь появится сам, не надо — сегодня это гарантированный null.

Реестр: подсказки для формы и полный поиск для миграции

# Автодополнение: минимум 2 символа, по умолчанию 7 подсказок, максимум 20
curl -G -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     --data-urlencode "query=Москва Тверская" \
     --data-urlencode "limit=7" \
     "https://atlorium.com/api/gar/suggest"

# Полный поиск: по умолчанию 10 результатов, максимум 100
curl -G -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     --data-urlencode "query=Москва ул Тверская д 7" \
     --data-urlencode "limit=5" \
     "https://atlorium.com/api/gar/search"

# Карточка объекта по GUID: иерархия и параметры с расшифровками
curl -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     "https://atlorium.com/api/gar/object/0c5b2444-70a0-4932-980c-b4dc0d3f02b5"
        

const HEADERS = { Authorization: "Bearer ak_sandbox_demo_mockdata_v1" };
const BASE = "https://atlorium.com";

// Для поля ввода нужен именно suggest: он легче search и не собирает параметры объекта.
async function suggest(query) {
  if (query.trim().length < 2) return [];
  const url = new URL(`${BASE}/api/gar/suggest`);
  url.searchParams.set("query", query);
  url.searchParams.set("limit", "7");
  const r = await fetch(url, { headers: HEADERS });
  const data = await r.json();
  // items: text, objectId, objectGuid, objectType. Индекса и ОКТМО здесь НЕТ.
  return data.items;
}

// Пользователь выбрал подсказку — за кодами идём отдельным запросом.
async function details(objectGuid) {
  const r = await fetch(`${BASE}/api/gar/object/${objectGuid}`, { headers: HEADERS });
  const obj = await r.json();
  const byName = Object.fromEntries(
    obj.parameters.map((p) => [p.typeName, p.value])
  );
  return {
    fullAddress: obj.fullAddress,
    postalCode: byName["Почтовый индекс"],
    oktmo: byName["ОКТМО"],
    kladr: byName["Код КЛАДР"],
  };
}
        

// GET /api/gar/search?query=Москва ул Тверская д 7&limit=5
{
  "query": "Москва ул Тверская д 7",
  "results": [
    {
      "fullAddress": "г Москва, ул Тверская, д. 7",
      "objectType": "дом",
      "objectGuid": "0c5b2444-70a0-4932-980c-b4dc0d3f02b5",
      "objectId": 1405113,
      "regionCode": "77",
      "postalCode": "125009",
      "oktmo": "45382000",
      "okato": "45286585000"
    }
  ],
  "count": 1,
  "elapsedMs": 18
}

// GET /api/gar/object/0c5b2444-70a0-4932-980c-b4dc0d3f02b5
{
  "objectId": 1405113,
  "objectGuid": "0c5b2444-70a0-4932-980c-b4dc0d3f02b5",
  "fullAddress": "г Москва, ул Тверская, д. 7",
  "objectType": "дом",
  "regionCode": "77",
  "hierarchy": [
    { "objectId": 1405113, "displayName": "Москва" },
    { "objectId": 1408743, "displayName": "ул Тверская" },
    { "objectId": 1405113, "displayName": "д. 7" }
  ],
  "parameters": [
    { "typeId": 5, "typeName": "Почтовый индекс", "value": "125009" },
    { "typeId": 6, "typeName": "ОКТМО", "value": "45382000" },
    { "typeId": 7, "typeName": "ОКАТО", "value": "45286585000" },
    { "typeId": 8, "typeName": "Код ИФНС", "value": "7710" },
    { "typeId": 10, "typeName": "Код КЛАДР", "value": "77000000000072200" }
  ],
  "elapsedMs": 12
}
        

Тот же вызов на шести языках (Python, TypeScript, Go, Java, C#, PHP) — в репозитории gar-fias-address-api-client. Примеры запускаются сразу: ключ в них уже стоит демонстрационный.

Обратите внимание на разницу между двумя эндпоинтами. search уже отдаёт индекс, ОКТМО и ОКАТО прямо в каждом результате — для миграции базы этого достаточно, лишний запрос не нужен. А вот suggest нарочно облегчён: только text, objectId, objectGuid и objectType, потому что дёргается он на каждое нажатие клавиши. Коды по выбранной подсказке добираются отдельным вызовом object/{guid}, и в parameters их приходит больше: там же код ИФНС и КЛАДР, которых в результатах поиска нет.

Ещё в карточке объекта есть hierarchy — путь от региона до самого объекта. Если вам нужен не адрес целиком, а его верхние уровни (например, регион и город отдельными полями и обязательно в том написании, в каком они в реестре), берите оттуда, а не разбирайте fullAddress регулярками.

Связка: разбор, отсев, поиск

# Уровни, которые имеет смысл класть в поисковый запрос к реестру.
# Квартиры и комнаты в ГАР есть, но искать по ним строкой — лишний шум.
SEARCH_LEVELS = (
    "Country", "RegionArea", "RegionCity", "District", "Settlement",
    "City", "CityDistrict", "Locality", "Territory", "Street", "Building",
)

QUALITY_THRESHOLD = 80  # свой, подобранный на своей выборке


def to_search_query(addr: dict) -> str:
    """Из разобранных уровней собираем строку для поиска в реестре."""
    parts = [
        c["text"] for c in addr["components"]
        if c["level"] in SEARCH_LEVELS
    ]
    return ", ".join(parts)


def gar_search(query: str, limit: int = 5) -> dict:
    resp = requests.get(
        f"{BASE}/api/gar/search",
        params={"query": query, "limit": limit},
        headers=HEADERS,
        timeout=15,
    )
    resp.raise_for_status()
    return resp.json()


def enrich(line: str, region: int | None = None) -> dict:
    addr = parse_address(line, region)
    if addr is None:
        return {"state": "unparsed", "input": line}

    if addr["quality"] < QUALITY_THRESHOLD:
        return {"state": "low_quality", "normalized": addr["normalized"],
                "quality": addr["quality"], "message": addr["message"]}

    found = gar_search(to_search_query(addr))

    if found["count"] == 0:
        return {"state": "not_in_registry", "normalized": addr["normalized"]}

    if found["count"] > 1:
        return {"state": "ambiguous", "candidates": found["results"]}

    hit = found["results"][0]
    return {
        "state": "ok",
        "normalized": addr["normalized"],
        "objectGuid": hit["objectGuid"],
        "objectId": hit["objectId"],
        "postalCode": hit["postalCode"],
        "oktmo": hit["oktmo"],
        "okato": hit["okato"],
    }
        

// Пять исходов вместо булева «получилось / не получилось».
// Три из них — работа для человека, и это нормально: полностью
// автоматической миграции адресной базы не бывает.
public enum EnrichState
{
    Ok,             // разобрано и найдено в реестре ровно одно
    Unparsed,       // 400 от разбора: адреса в строке нет, списания не было
    LowQuality,     // quality ниже порога — в реестр не пускаем
    NotInRegistry,  // count = 0
    Ambiguous,      // count больше одного, выбор за человеком
}

public sealed record EnrichResult(
    EnrichState State,
    string? Normalized,
    int? Quality,
    string? ObjectGuid,
    long? ObjectId,
    string? PostalCode,
    string? Oktmo,
    string? Okato);
        

Тот же вызов на шести языках (Python, TypeScript, Go, Java, C#, PHP) — в репозитории address-standardization-api-client. Примеры запускаются сразу: ключ в них уже стоит демонстрационный.

Пять исходов вместо двух — не усложнение, а признание факта. Записи, которые нельзя обработать автоматически, есть всегда, и разница между хорошей миграцией и плохой в том, попадают ли они в отдельную очередь или молча уезжают в базу с чужим GUID.

Чего эти два сервиса не умеют

Раздел, из-за которого статью стоит дочитать до конца.

Разбор не подтверждает существование адреса. Он работает с текстом и оценивает качество распознавания текста. Дом с номером 404 на существующей улице разбирается на quality 100 и попадает в базу как безупречный. Единственное, что отвечает на вопрос «а есть ли такой дом», — поиск в реестре.

Комбо-режима пока нет. Параметры bind и geocode описаны в контракте, но возвращают 501. Ни привязки к реестру одним вызовом, ни координат сегодня получить нельзя, и поля gar и geo в ответе всегда пустые. Связку из двух запросов вы собираете сами — код выше и есть эта связка.

Мы не гарантируем полноту реестра и не даём его данным юридической силы. Справочник строится из официальной выгрузки ФНС и отдаёт ровно то, что в ней есть. Новостройка, которую в реестр ещё не внесли, вернёт count: 0 — и это будет правдой про реестр, а не про дом. Обратная ситуация тоже встречается: снесённое здание может оставаться в выгрузке.

Поиск в реестре не разбирает свободный текст. Он ищет по адресным объектам: регион, населённый пункт, улица, дом, помещение. Этажи, подъезды, коды домофона, ориентиры вроде «напротив школы», номера абонентских ящиков — всё это для поиска шум, и именно из-за него прямой прогон строк из CRM даёт пустые ответы. Разбор такие фрагменты как раз отделяет: часть уходит в extra, часть отражается в message.

Запрос карточки объекта по несуществующему GUID тарифицируется. Ответ будет 404, но кредит спишется: обращение к справочнику уже выполнено, ресурсы потрачены. Это осознанное решение, и лучше узнать о нём из статьи, чем из счёта. У поиска такой проблемы нет: пустой результат — это 200 с count: 0, обычный оплаченный запрос.

В песочнице ветвлений почти нет. Демо-ключ отдаёт разбор с quality 100 и gar: null, а поиск — пять сгенерированных домов на случайных улицах. Проверить на моках, как ваш код поведёт себя при count: 0 или при низком качестве разбора, не получится: эти ветки надо покрывать своими тестами с подставленным ответом. На моках удобно другое — убедиться, что вы правильно поняли форму ответа и разбираете нужные поля.

Частые вопросы

Частые вопросы

Чем нормализация адреса отличается от поиска в ГАР/ФИАС?

Нормализация работает со строкой: разбирает текст на уровни от региона до квартиры, приводит к единому виду и оценивает качество разбора числом от 0 до 100. Существование адреса она не проверяет. Поиск в ГАР ищет запись в государственном адресном реестре и отдаёт идентификаторы объекта: GUID, числовой ObjectId, почтовый индекс, ОКТМО и ОКАТО. Первое чинит формат, второе подтверждает реальность.

Можно ли одним запросом получить и разобранный адрес, и код ГАР?

Пока нет. У сервиса стандартизации есть параметр bind для привязки разобранного адреса к реестру, но он в разработке и возвращает код 501 — при этом деньги за него не списываются. Сегодня это два последовательных вызова: сначала /api/addressstd, потом /api/gar/search по собранной из уровней строке.

Что означает коэффициент качества и какой порог выбрать?

Коэффициент 0–100 показывает, насколько уверенно разобрана строка: 100 — весь текст распознан и лишних фрагментов не осталось, от 90 считается нормой, ниже 80 к результату стоит относиться осторожно. Порог для своего кода подбирают на выборке из собственной базы: у адресов интернет-магазина и у адресов юрлиц он обычно разный.

Как получить ОКТМО и почтовый индекс по адресу?

Через поиск в реестре: GET /api/gar/search возвращает в каждом результате поля postalCode, oktmo и okato. Если нужен ещё код КЛАДР или код ИФНС, запросите карточку объекта по GUID через /api/gar/object/{guid} — там они лежат в списке parameters вместе с расшифровкой типа. Разбор строки эти коды не выдаёт: их нет в самой строке.

Что использовать в поле ввода адреса на сайте — suggest или search?

Suggest. Он облегчённый и отдаёт только текст подсказки, ObjectId, ObjectGuid и тип объекта, поэтому его не жалко дёргать на каждое нажатие клавиши; минимум два символа, по умолчанию семь подсказок. Когда пользователь выбрал вариант, за индексом и кодами идите отдельным запросом по GUID.

Сколько запросов нужно на миграцию базы адресов?

По одному разбору на каждую запись плюс по одному поиску в реестре на те записи, которые разобрались с приемлемым качеством. Строки, из которых адрес выделить не удалось, возвращают 400 и не тарифицируются вовсе, так что они в счёт не попадут. Прогонять исходные строки сразу через реестр дороже и хуже: часть вернёт пустой ответ, а часть — правдоподобного, но чужого кандидата.

Сервисы из этой статьи

Стандартизация адреса

Разбор адресной строки в структуру: уровни (регион/город/улица/дом/квартира), нормализация, коэффициент качества

Адреса ГАР/ФИАС

Поиск и нормализация адресов по справочнику ГАР/ФИАС

Попробовать прямо сейчас — без регистрации

Демо-ключ ak_sandbox_demo_mockdata_v1 — публичный и общий для всех. С ним API отвечает моками: данные правдоподобные, но сгенерированные, и они не меняются от запроса к запросу — на них удобно писать тесты. Настоящие данные приходят с личным ключом.

curl -H "Authorization: Bearer ak_sandbox_demo_mockdata_v1" \
     https://atlorium.com/openapi/addressstd_ru.json
Полезно? Перешлите коллеге: Telegram VK

Восстанавливаем соединение…

Похоже, связь с сервером ненадолго прервалась. Переподключаемся автоматически — пожалуйста, подождите несколько секунд.

Не удалось переподключиться

Проверьте интернет-соединение. Можно повторить попытку или обновить страницу.

Сессия устарела

Соединение восстановлено, но сессию нужно перезагрузить. Обновите страницу, чтобы продолжить.