Перейти к основному содержимому
БЛОГ

Как опубликовать /.well-known/ucp: профиль продавца UCP

Опубликуйте /.well-known/ucp, чтобы ИИ-агенты видели, что умеет ваш магазин: 3 обязательных поля, правила хостинга, согласование и breaking changes v2026-08-25.

Автор: IMozzОбновлено 2026-10-05
Publish your UCP profile — aiSiteReady

Чтобы опубликовать профиль продавца Universal Commerce Protocol (UCP), отдайте по адресу /.well-known/ucp один JSON-документ: в нём версия протокола, сервисы с эндпоинтами, возможности (capabilities) и платёжные обработчики. ИИ-агент для покупок сначала забирает этот файл, сверяет его со своим профилем и использует только то, что поддерживают обе стороны (спецификация UCP).

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

Оговорка в самом начале: UCP молод. Shopify и Google опубликовали его 11 января 2026 года (Shopify Engineering, Google Developers Blog), и с тех пор вышло четыре датированных релиза, последний — 25 августа 2026 года (релизы). Поддержка версий неравномерна: 2 октября 2026 года руководство Google для продавцов всё ещё описывало версии профиля 2026-04-08 и 2026-01-23 (Google), тогда как витрины Shopify уже объявляют 2026-08-25 (changelog Shopify).

Ключевые выводы

  • Профиль продавца — это JSON-файл на /.well-known/ucp. Обязательны только ucp.version, ucp.services и ucp.payment_handlers, причём последние два должны присутствовать, даже если пусты (спецификация UCP).
  • Отдавайте его по HTTPS, без редиректов и с Cache-Control: public и max-age не меньше 60 секунд (спецификация UCP, Hosting). Правил про CORS в спецификации нет; проверка этого заголовка — собственное решение aiSiteReady.
  • Активные возможности выбирает бизнес: он пересекает свой профиль с профилем агента по точному имени и точной датированной версии, а затем отбрасывает «осиротевшие» расширения.
  • Релиз v2026-08-25 сломал профили четырьмя способами: keys[] заменил signing_keys[], переименованы флаги конфигурации fulfillment, перестроено согласие покупателя, а платёжные расширения переехали в dev.ucp.common.payment.* (заметки к релизу).
  • Shopify сам генерирует профиль для своих витрин, и, по словам сотрудников, разработчики не могут его править (форум Shopify); кастомные магазины публикуют свой.

Что такое профиль продавца /.well-known/ucp?

Это документ обнаружения бизнеса в UCP: публичный JSON-файл, который ещё до первого запроса checkout сообщает агенту, что поддерживает ваш магазин. Бизнес публикует его на /.well-known/ucp; платформы — то есть агенты и приложения — публикуют свой профиль по адресу, который передают с каждым запросом, поэтому любая платформа с доступным профилем может работать с любым бизнесом без предварительной регистрации (спецификация UCP).

Профиль — это карта, а не сам API. Все эндпоинты, к которым обратится агент, объявлены внутри него; описание shopping REST прямо требует брать базовый URL из блока services профиля (OpenAPI UCP). Каждый блок отвечает на один вопрос.

Анатомия бизнес-профиля UCP. Пять блоков JSON-документа отвечают каждый на один вопрос агента: ucp.version и supported_versions — какие версии протокола мы оба понимаем; ucp.services — куда обращаться и по какому транспорту; ucp.capabilities — что можно сделать в этом магазине; ucp.payment_handlers — как покупатель может заплатить; массив keys верхнего уровня — как проверить ваши подписи. Обязательны version, services и payment_handlers, а capabilities и keys необязательны.

Два факта стоит знать, прежде чем вкладываться. Во-первых, в реестре well-known URI у IANA ucp на момент его обновления 16 сентября 2026 года не значился, в отличие от api-catalog и agent-card.json (IANA); путь держится на спецификации, а не на регистрации. Во-вторых, участвовать в разработке может любой, но голоса распределены неравно: в Shopping Tech Council 16 мест, из них по четыре постоянных места у Google и Shopify — восемь голосов из шестнадцати (управление UCP).

UCP — не единственный коммерческий протокол. Он заявляет совместимость с AP2 и включает расширение ap2_mandate для подписанных платежей (UCP и AP2). У Agentic Commerce Protocol от OpenAI и Stripe есть собственный /.well-known/acp.json, но его RFC по обнаружению всё ещё помечен как предложение и не выпущен (ACP RFC). В остальной части гайда я его не касаюсь.

Что внутри профиля UCP: от минимального до полного?

Важны шесть полей объекта, и лишь три из них обязательны. Таблица следует определению business_schema в JSON Schema спецификации на релизе v2026-08-25 (profile.json).

ПолеЧто вы задаётеЧто важно знать
ucp.versionДата YYYY-MM-DDОбязательное. Только дата релиза, никаких строк вроде draft
ucp.supported_versionsСтарая дата → URL профиляНеобязательное. Каждый URL отдаёт полный профиль для этой даты
ucp.servicesКлюч dev.ucp.shopping, по записи на транспортОбязательное, может быть пустым. Запись: version, transport, endpoint, schema, spec
ucp.capabilitiesКлюч на возможность, массив версионных записейНеобязательное. Запись: version, schema (для бизнеса обязательно), spec, extends, config
ucp.payment_handlersКлюч на обработчик, массив записейОбязательное, может быть пустым. Запись: id, version, spec, schema, config
keysПубличные JWK: kid, kty, crv, x, y, algНеобязательное. Только публичные ключи

Транспорты — rest, mcp, a2a и embedded. Записям REST, MCP и A2A нужен endpoint; у A2A он указывает на agent card (сделайте сайт вызываемым агентами). Транспорт mcp в UCP — это коммерческий API, описанный файлом OpenRPC, и он не то же самое, что MCP Server Card (MCP Server Card: разбор).

Минимальный документ, проходящий схему, состоит из трёх полей, причём services и payment_handlers присутствуют, даже если пусты (спецификация UCP).

{
  "ucp": {
    "version": "2026-08-25",
    "services": {},
    "payment_handlers": {}
  }
}

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

{
  "ucp": {
    "version": "2026-08-25",
    "services": {
      "dev.ucp.shopping": [
        {
          "version": "2026-08-25",
          "spec": "https://ucp.dev/2026-08-25/specification/overview/",
          "transport": "rest",
          "endpoint": "https://shop.example.com/ucp/v1",
          "schema": "https://ucp.dev/2026-08-25/services/shopping/rest.openapi.json"
        }
      ]
    },
    "capabilities": {
      "dev.ucp.shopping.catalog.search": [
        {
          "version": "2026-08-25",
          "spec": "https://ucp.dev/2026-08-25/specification/shopping/catalog/search",
          "schema": "https://ucp.dev/2026-08-25/schemas/shopping/catalog_search.json"
        }
      ],
      "dev.ucp.shopping.catalog.lookup": [
        {
          "version": "2026-08-25",
          "spec": "https://ucp.dev/2026-08-25/specification/shopping/catalog/lookup",
          "schema": "https://ucp.dev/2026-08-25/schemas/shopping/catalog_lookup.json"
        }
      ]
    },
    "payment_handlers": {}
  }
}

С таким профилем агент превращает поиск товара в POST https://shop.example.com/ucp/v1/catalog/search, потому что пути из OpenAPI дописываются к объявленному эндпоинту. Магазин, который принимает заказы через агента, добавляет checkout, его расширения, жизненный цикл заказа, платёжный обработчик и ключ подписи.

{
  "ucp": {
    "version": "2026-08-25",
    "supported_versions": {
      "2026-04-08": "https://shop.example.com/.well-known/ucp/2026-04-08"
    },
    "services": {
      "dev.ucp.shopping": [
        {
          "version": "2026-08-25",
          "spec": "https://ucp.dev/2026-08-25/specification/overview/",
          "transport": "rest",
          "endpoint": "https://shop.example.com/ucp/v1",
          "schema": "https://ucp.dev/2026-08-25/services/shopping/rest.openapi.json"
        },
        {
          "version": "2026-08-25",
          "spec": "https://ucp.dev/2026-08-25/specification/overview/",
          "transport": "mcp",
          "endpoint": "https://shop.example.com/ucp/mcp",
          "schema": "https://ucp.dev/2026-08-25/services/shopping/mcp.openrpc.json"
        }
      ]
    },
    "capabilities": {
      "dev.ucp.shopping.checkout": [
        {
          "version": "2026-08-25",
          "spec": "https://ucp.dev/2026-08-25/specification/shopping/checkout",
          "schema": "https://ucp.dev/2026-08-25/schemas/shopping/checkout.json"
        }
      ],
      "dev.ucp.shopping.fulfillment": [
        {
          "version": "2026-08-25",
          "spec": "https://ucp.dev/2026-08-25/specification/shopping/extensions/fulfillment",
          "schema": "https://ucp.dev/2026-08-25/schemas/shopping/fulfillment.json",
          "extends": "dev.ucp.shopping.checkout",
          "config": {
            "multi_destination": [{ "method": "shipping" }],
            "method_combinations": [["shipping", "pickup"]]
          }
        }
      ],
      "dev.ucp.shopping.discount": [
        {
          "version": "2026-08-25",
          "spec": "https://ucp.dev/2026-08-25/specification/shopping/extensions/discount",
          "schema": "https://ucp.dev/2026-08-25/schemas/shopping/discount.json",
          "extends": "dev.ucp.shopping.checkout"
        }
      ],
      "dev.ucp.shopping.order": [
        {
          "version": "2026-08-25",
          "spec": "https://ucp.dev/2026-08-25/specification/shopping/order",
          "schema": "https://ucp.dev/2026-08-25/schemas/shopping/order.json"
        }
      ]
    },
    "payment_handlers": {
      "com.example.processor_tokenizer": [
        {
          "id": "processor_tokenizer",
          "version": "2026-08-25",
          "spec": "https://example.com/specs/payments/processor_tokenizer",
          "schema": "https://example.com/specs/payments/merchant_tokenizer.json",
          "available_instruments": [
            {
              "type": "card",
              "constraints": {
                "properties": { "brand": { "enum": ["visa", "mastercard"] } }
              }
            }
          ]
        }
      ]
    }
  },
  "keys": [
    {
      "kid": "business_2025",
      "kty": "EC",
      "crv": "P-256",
      "x": "qIVYZVLCrPZHGHjP17CTW0_-D9Lfw0EkjqF7xB4FivA",
      "y": "Mc4nN9LTDOBhfoUeg8Ye9WedFRhnZXZJA12Qp0zZ6F0",
      "use": "sig",
      "alg": "ES256"
    }
  ]
}

Все три профиля я проверил по JSON Schema из репозитория спецификации на теге v2026-08-25 — они проходят. Хосты, обработчик и ключ считайте заглушками: настоящие платёжные обработчики приходят от их провайдеров, а ключ — вашим собственным публичным ключом. Схема отвергает поля приватного ключа вроде d. Fulfillment и discount содержат extends, потому что это расширения, которые работают только поверх checkout.

Одно правило именования сбивает многих. Имена записываются в обратной доменной нотации: dev.ucp.* принадлежит ucp.dev, а com.example.* — example.com. Платформа обязана отвергнуть сущность, у которой origin URL из schema не совпадает с доменом в её имени, и не должна следовать редиректам при загрузке схемы (спецификация UCP, authority binding). Размещайте свои схемы на домене, который вы контролируете.

Кто хостит /.well-known/ucp: Shopify или вы?

На Shopify файл пишет платформа; в кастомном магазине вы пишете и хостите его сами. Документация Shopify для агентов помещает бизнес-профиль на витрину продавца, {shop}.myshopify.com/.well-known/ucp (документация Shopify для разработчиков). Сотрудник Shopify на форуме разработчиков выражается прямее: Shopify генерирует профиль сам, хостить свой не нужно, а поддерживаемого способа править или заменять его нет (форум, июль 2026). Второй ответ добавляет, что разработчики не могут ничего добавить в профиль или изменить в нём (форум, август 2026).

В Справочном центре Shopify сказано, что функция Agentic storefronts по умолчанию включена для подходящих магазинов (Справочный центр Shopify), но про UCP на этой странице ни слова, так что не гадайте, а запросите свой домен. Я так и сделал для одной витрины на Shopify на её собственном домене 2 октября 2026 года. Ответ — прямой 200 с application/json; charset=utf-8, Cache-Control: public, max-age=60, слабым ETag и Access-Control-Allow-Origin: *. Тело в 4,8 КБ прошло ту же проверку по схеме, объявляло версию 2026-08-25, перечисляло два прошлых релиза в supported_versions и содержало MCP-эндпоинт, десять возможностей и три платёжных обработчика, без keys[].

В кастомном магазине вы сами публикуете файл и отвечаете за каждый объявленный в нём эндпоинт, поэтому объявляйте только то, что реально работает. Тема на форуме от января 2026 года сообщает, что у headless-магазина на Shopify профиль нашёлся на домене checkout, а не на домене витрины (форум); документации Shopify о проксировании я не нашёл, так что смотрите, что отдаёт ваш домен.

Правила хостинга коротки и в основном нормативны (спецификация UCP, Hosting).

ТребованиеУровеньИсточник и пояснения
HTTPSMUSTСпецификация UCP
Без редиректов (3xx) на эндпоинте профиляMUST NOTСпецификация UCP; платформы за ними не идут
Cache-Control с public и max-age от 60MUSTСпецификация UCP; никаких private, no-store, no-cache
ETag или Last-ModifiedSHOULDСпецификация UCP: чтобы потребители могли перепроверять кэш
Доступность без аутентификацииОбязательно на практикеВ руководстве Google сказано, что файл должен быть публично доступен без аутентификации
Content-Type: application/jsonДля профиля не указанREST-транспорт спецификации требует его для API-трафика; Shopify отдаёт его
Access-Control-Allow-OriginНет в спецификацииПравила про CORS в тексте спецификации я не нашёл; Shopify отдаёт *

Про CORS: браузер даёт скрипту прочитать кросс-доменный ответ, только если сервер явно разрешил это заголовком Access-Control-Allow-Origin (MDN). Агенту в браузере он нужен, серверному — нет. Обычному GET preflight не нужен, так что обработчик OPTIONS не требуется. Заголовок ничего не стоит, и aiSiteReady его ищет, но это любезность, а не правило спецификации.

Статического файла за nginx достаточно: спецификация отмечает, что документированные профили занимают меньше 5 КиБ. add_header внутри location заменяет заголовки, унаследованные от блока server, так что свои заголовки безопасности повторите здесь.

location = /.well-known/ucp {
    alias /var/www/shop/ucp-profile.json;
    default_type application/json;
    add_header Cache-Control "public, max-age=300";
    add_header Access-Control-Allow-Origin "*";
    # для статических файлов nginx по умолчанию добавляет ETag и Last-Modified
}

В приложении на Nuxt то же делает маршрут Nitro. Собственные .well-known-документы aiSiteReady — это маршруты Nitro в server/routes/.well-known/, а имя файла ucp.get.ts превращается в маршрут /.well-known/ucp.

// server/routes/.well-known/ucp.get.ts
import profile from "../../data/ucp-profile.json";

export default defineEventHandler((event) => {
  setResponseHeader(event, "Content-Type", "application/json");
  setResponseHeader(event, "Cache-Control", "public, max-age=300");
  setResponseHeader(event, "Access-Control-Allow-Origin", "*");
  return profile;
});

Как согласование UCP превращает два профиля в одну сессию?

Решает бизнес. UCP работает по схеме server-selects: бизнес выбирает версию протокола и активные возможности из пересечения двух профилей (Google). Агент хостит собственный профиль по HTTPS-адресу и передаёт этот адрес с каждым запросом — в заголовке UCP-Agent для HTTP или в объекте meta для MCP (спецификация UCP).

POST /ucp/v1/checkout-sessions HTTP/1.1
Host: shop.example.com
UCP-Agent: profile="https://agent.example/profiles/shopping-agent.json"
Content-Type: application/json

Бизнес загружает и проверяет этот профиль, а затем запускает алгоритм пересечения (спецификация UCP).

  1. Оставьте возможность бизнеса, только если агент объявил возможность с тем же именем.
  2. Из версий, которые есть у обеих сторон, выберите самую позднюю. Если общей версии нет, возможность отбрасывается.
  3. Удалите расширения, у которых ни один из родителей из extends не попал в результат.
  4. Повторяйте удаление, пока удалять больше нечего.

Согласование двух профилей. Профиль бизнеса содержит checkout, fulfillment, discount и order; профиль агента содержит checkout, fulfillment, buyer_consent и cart. Совпадающие по имени и точной версии 2026-08-25 возможности остаются, расширения без родителя отбрасываются. Итог — активный набор этой сессии: checkout и fulfillment. Discount и order есть только у бизнеса, buyer_consent и cart только у агента, поэтому ни одна из них не активна.

Версии сравниваются по точному равенству, а не по порядку дат. Более ранняя версия не подразумевает совместимости с более поздними, и старые релизы dev.ucp.* отдаются только через профили из supported_versions, а не дополнительными записями в текущем профиле (спецификация UCP, versioning). Бизнес отвечает согласованным набором, так что агент всегда знает, что активно.

{
  "ucp": {
    "version": "2026-08-25",
    "capabilities": {
      "dev.ucp.shopping.checkout": [{ "version": "2026-08-25" }],
      "dev.ucp.shopping.fulfillment": [{ "version": "2026-08-25" }]
    }
  }
}

Если согласование не удалось, ошибка будет конкретной и может содержать continue_url, чтобы агент передал покупателя в ваш обычный веб-checkout (спецификация UCP, error handling).

КодЗначениеСтатус REST
invalid_profile_urlURL профиля искажён, отсутствует или не разрешается400
profile_unreachableАдрес разрешился, но загрузка не удалась или вернула не-2xx424
profile_malformedНевалидный JSON или нарушение схемы422
version_unsupportedВерсия протокола агента не поддерживается422
capabilities_incompatibleПересечение пустое200

Что изменилось в релизе UCP v2026-08-25?

Четыре датированных релиза — 2026-01-11, 2026-01-23, 2026-04-08 и 2026-08-25 (релизы). В заметках к последнему релизу перечислено несколько обратно несовместимых изменений (заметки к релизу); вот те, что касаются профиля.

ОбластьДо (2026-04-08)После (2026-08-25)
Ключи подписиsigning_keys[] верхнего уровняkeys[] верхнего уровня, JWK Set; signing_keys[] удалён
Конфигурация fulfillmentallows_multi_destination, allows_method_combinationsmulti_destination (массив объектов), method_combinations
Согласие покупателяФиксированные булевы поля вроде marketingСловарь с ключами-целями в обратной доменной нотации, например dev.ucp.consent.marketing
Платёжные расширенияИмена dev.ucp.shopping.*dev.ucp.common.payment.*, например ap2_mandate
Контракт версийДатированные версии, правила менее явныеУточнён и ужесточён: каждая запись dev.ucp.* объявляет версию профиля

Последняя строка названа уточнением, а не breaking change, но последствия у неё серьёзные: платформа обязана отвергать любую запись dev.ucp.*, версия которой отличается от ucp.version (спецификация UCP). Переименование в fulfillment — то, что видно прямо в профиле: сначала в том виде, как его показывает спецификация 2026-04-08, затем как 2026-08-25.

{
  "config": {
    "allows_multi_destination": { "shipping": true },
    "allows_method_combinations": [["shipping", "pickup"]]
  }
}
{
  "config": {
    "multi_destination": [{ "method": "shipping" }],
    "method_combinations": [["shipping", "pickup"]]
  }
}

Путь обновления короткий. Отдавайте новую версию на /.well-known/ucp. Прошлый релиз храните как полный профиль на собственном URL, например /.well-known/ucp/2026-04-08, и сошлитесь на него в supported_versions; у такого листового профиля собственного supported_versions быть не должно (спецификация UCP, protocol version). Поднимайте версии всех записей dev.ucp.* вместе с ucp.version. Чтобы заранее узнавать о breaking changes, следите за репозиторием: такие pull request анонсируют в GitHub Discussions за две недели до слияния (версионирование UCP).

Какие ошибки ломают профиль UCP?

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

ОшибкаЧто происходитИсправление
Копирование примера начала 2026 годаcapabilities как массив с полями name и payment.handlers верхнего уровня не проходят текущую схемуБерите актуальный пример из спецификации
Ключи остались в signing_keys[]Схема пропускает, но верификатор 2026-08-25 читает keys[]Перенесите ключи
Поднята только ucp.versionСхема пропускает; платформа отвергает каждую запись dev.ucp.* с другой версиейПоднимайте все записи вместе
Нет payment_handlers в магазине только с каталогомОшибка схемы: поле обязательноПубликуйте "payment_handlers": {}
Редирект с /.well-known/ucpСпецификация запрещает редиректы на эндпоинте профиляОтдавайте прямой 200
no-store, private, крошечный max-age или HTML со статусом 200 от fallback приложенияНарушены правила кэширования либо тело не JSONИсправьте заголовки; для отсутствующего файла верните 404
Origin schema не совпадает с именемСущность отвергается и никогда не активируетсяРазмещайте схемы на соответствующем домене
Защита от ботов или логин перед файломGoogle требует, чтобы файл был публичным; сканеры и агенты видят 403 или 429Исключите этот путь из проверок

Первую строку легко проверить. В примере из Google Developers Blog за январь 2026 года capabilities был массивом объектов с name, а payment.handlers лежал вне ucp (Google Developers Blog). Текущая схема выдаёт на ней ошибки: capabilities не проходит проверку на объект, services — на массив, а payment_handlers отсутствует. Примеры стареют; копируйте из спецификации своего релиза.

Как проверить профиль через curl?

Сначала заголовки, потом тело, и обе проверки запускайте на публичном edge-URL, а не только по localhost. Не добавляйте -L: редирект на этом пути — баг, а -L его скроет.

curl -sS -D - -o /dev/null -H 'Accept: application/json' https://shop.example.com/.well-known/ucp

Должны быть прямой 200, JSON-тип и заголовки кэширования.

HTTP/2 200
content-type: application/json; charset=utf-8
cache-control: public, max-age=300
etag: "7f3a1c"
access-control-allow-origin: *

Затем прочитайте то, что прочитал бы агент. Эта выборка выводит версию, поддерживаемые прошлые релизы и ключи реестров.

curl -sS https://shop.example.com/.well-known/ucp | jq '{
  version: .ucp.version,
  older: (.ucp.supported_versions // {} | keys),
  services: (.ucp.services | keys),
  capabilities: (.ucp.capabilities | keys),
  handlers: (.ucp.payment_handlers | keys),
  keys: [.keys[]?.kid]
}'

Для полного профиля выше вывод выглядит так.

{
  "version": "2026-08-25",
  "older": ["2026-04-08"],
  "services": ["dev.ucp.shopping"],
  "capabilities": [
    "dev.ucp.shopping.checkout",
    "dev.ucp.shopping.discount",
    "dev.ucp.shopping.fulfillment",
    "dev.ucp.shopping.order"
  ],
  "handlers": ["com.example.processor_tokenizer"],
  "keys": ["business_2025"]
}

Встройте эти правила в CI, чтобы сломанный профиль не попал в релиз. Скрипт проверяет статус, заголовки, обязательные поля, переименование в keys[] и расхождение версий. Я прогнал его на локальном сервере с корректным профилем и пятью сломанными вариантами — он отметил каждый.

#!/usr/bin/env bash
# использование: check-ucp.sh https://shop.example.com
set -uo pipefail
URL="${1:?usage: check-ucp.sh https://shop.example.com}"
URL="${URL%/}/.well-known/ucp"
HDR="$(mktemp)"; BODY="$(mktemp)"; fail=0
bad() { echo "FAIL: $1"; fail=1; }

# Без -L намеренно: спецификация запрещает редиректы на эндпоинте профиля.
CODE="$(curl -sS -m 15 -H 'Accept: application/json' -D "$HDR" -o "$BODY" -w '%{http_code}' "$URL")" \
  || { echo "FAIL: request error"; exit 1; }
[ "$CODE" = "200" ] || { echo "FAIL: status $CODE (expected a direct 200)"; exit 1; }

grep -qi '^content-type: *application/json' "$HDR" || bad "Content-Type is not application/json"
CC="$(grep -i '^cache-control:' "$HDR" | head -1)"
grep -qi 'public' <<<"$CC" || bad "Cache-Control lacks public"
grep -qiE 'private|no-store|no-cache' <<<"$CC" && bad "forbidden Cache-Control directive"
MAXAGE="$(grep -oi 'max-age=[0-9]*' <<<"$CC" | head -1 | cut -d= -f2)"
[ "${MAXAGE:-0}" -ge 60 ] || bad "max-age is missing or below 60"
grep -qiE '^(etag|last-modified):' "$HDR" || echo "WARN: no ETag or Last-Modified"
grep -qi '^access-control-allow-origin:' "$HDR" || echo "WARN: no CORS header (not in the spec)"

jq -e 'type == "object"' "$BODY" >/dev/null 2>&1 || { bad "body is not a JSON object"; exit 1; }
V="$(jq -r '.ucp.version // empty' "$BODY")"
[[ "$V" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || bad "ucp.version is missing or not YYYY-MM-DD"
jq -e '.ucp.services | type == "object"' "$BODY" >/dev/null 2>&1 || bad "ucp.services must be an object"
jq -e '.ucp.payment_handlers | type == "object"' "$BODY" >/dev/null 2>&1 \
  || bad "ucp.payment_handlers must exist, even when empty"
if [[ ! "$V" < "2026-08-25" ]]; then
  jq -e 'has("signing_keys") | not' "$BODY" >/dev/null || bad "signing_keys was removed; publish keys[]"
fi
# Каждая запись dev.ucp.* обязана нести версию самого профиля.
stale() { jq -r --arg v "$V" --arg f "$1" \
  '[(.ucp[$f] // {}) | to_entries[] | select(.key | startswith("dev.ucp.")) | .key as $k | .value[] | select(.version != $v) | $k] | unique | join(", ")' "$BODY"; }
CAPS="$(stale capabilities)"; SVCS="$(stale services)"
[ -z "$CAPS" ] || bad "capabilities not at ucp.version $V: $CAPS"
[ -z "$SVCS" ] || echo "WARN: services not at ucp.version $V: $SVCS"

[ "$fail" = 0 ] && echo "OK: $URL ($V)"
exit "$fail"

Для полной структурной проверки валидируйте тело по source/schemas/profile.json, определение business_schema, из репозитория спецификации на теге вашего релиза — любым валидатором JSON Schema 2020-12. Ни скрипт, ни схема не доказывают, что ваши эндпоинты работают. Прежде чем полагаться на профиль, вызовите каждый объявленный эндпоинт с тестовым профилем агента.

Как проверить весь магазин сразу?

С этим файлом работают две коммерческие проверки aiSiteReady. Обе запускаются в профилях сканирования Интернет-магазин и Все проверки и отключены в профилях Контентный сайт и API/приложение. Запросы только читают и защищены от SSRF. Точные проверки и веса описаны на странице методологии.

Проверка «Сигналы агентной коммерции» (agentic_commerce_signals) запрашивает вашу главную страницу и, только если она вернула 2xx, параллельно опрашивает /.well-known/ucp и /.well-known/acp.json с заголовком Accept, в котором приоритет у application/json, — всего не больше трёх запросов. Документ UCP считается валидным, если ответ имеет статус 2xx и JSON-тип, а тело разбирается в JSON-объект с ucp.version в виде строки YYYY-MM-DD. Валидный документ с заголовком Access-Control-Allow-Origin — это PASS; без этого заголовка проверка ставит PARTIAL. Отсутствующий документ никогда не превращает проверку в FAIL. Если главная отвечает 403 или 429, проверка сообщает UNKNOWN, а не засчитывает вам FAIL. Сайту без коммерческой поверхности ставится N/A.

Пробу я сознательно сделал узкой, поэтому знайте её границы. Она проверяет только ucp.version, а не сервисы, возможности, ключи и правила версий выше. Она терпима к версиям: сканер, зашивший в код сегодняшний релиз, после следующего начал бы ошибочно браковать валидные профили. Она не вызывает объявленные вами эндпоинты. Она следует до пяти редиректов, поэтому может засчитать профиль, который по спецификации не должен редиректить; продолжайте проверять curl без -L. Правило про CORS — наше, а не спецификации. Проверка «Подсказки товарного фида» (product_feed_hints) запрашивает тот же путь и считает любой JSON-объект подсказкой обнаружения каталога. Обе проверки имеют низкую важность, так что отсутствие профиля сдвигает оценку умеренно.

Сочетайте этот гайд с чек-листом и материалом о структурированных данных (ссылки на них — в начале статьи), а остальной сайт проверьте с помощью 30-минутного аудита готовности к агентам. Запустите бесплатное сканирование, чтобы увидеть, может ли агент найти и прочитать ваш /.well-known/ucp, какие коммерческие сигналы он видит и какие исправления сделать первыми — на английском, украинском или русском.

Если коротко: для UCP нужен один публичный JSON-файл, доступный по HTTPS, без редиректов и с настоящими заголовками кэширования. В нём должны быть только те эндпоинты и возможности, которые вы реально обслуживаете, а его даты — согласовываться друг с другом. Если вы на Shopify, проверьте, что уже отдаётся. Если нет — опубликуйте профиль сами и тестируйте его как любой другой API.

У IMozz 20 лет в разработке ПО, а последний год он строит продукты с помощью LLM. Он развивает aiSiteReady, сканер только для чтения, который проверяет, может ли ИИ-агент прочитать сайт. Сканер серверно рендерит собственный контент как рабочий пример.

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

/.well-known/ucp — это то же самое, что товарный фид?
Нет. Это профиль возможностей: версия протокола, сервисы с их эндпоинтами, capabilities, платёжные обработчики и необязательные ключи подписи. Товаров в нём нет. Агент читает его первым, а затем обращается к каталожным и checkout-эндпоинтам, которые вы объявили. Данные о товарах по-прежнему живут в структурированных данных и каталожных capabilities, так что профиль и разметка Product решают разные задачи.
Нужно ли магазинам на Shopify самим публиковать профиль UCP?
Как правило, нет. Документация Shopify помещает бизнес-профиль на /.well-known/ucp витрины, а сотрудник Shopify на форуме разработчиков пишет, что Shopify генерирует его сам и поддерживаемого способа править или заменять его нет. Кастомные магазины и магазины не на Shopify публикуют свой файл и сами обслуживают объявленные в нём эндпоинты. Всегда проверяйте, что реально отдаёт ваш домен.
Какие HTTP-заголовки нужны для /.well-known/ucp?
Спецификация требует HTTPS, отсутствие редиректов и Cache-Control с public и max-age не меньше 60 секунд, без private, no-store и no-cache. Рекомендуется также ETag или Last-Modified. JSON отдавайте как application/json. О CORS спецификация молчит, хотя профиль на Shopify отдаёт Access-Control-Allow-Origin со звёздочкой, а проверка aiSiteReady этот заголовок ищет.
Что изменилось в релизе UCP v2026-08-25 для продавцов?
Профиля касаются четыре изменения. Массив keys верхнего уровня заменил signing_keys. Флаги конфигурации fulfillment переименованы: allows_multi_destination стал multi_destination. Согласие покупателя из фиксированных булевых полей превратилось в словарь с ключами-целями в обратной доменной нотации. Платёжные расширения переехали в dev.ucp.common.payment. Каждая запись dev.ucp обязана указывать версию самого профиля.