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

Як опублікувати /.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 мусить вказувати версію самого профілю.