Automation Intelligence API for UCP: How to detect automated ecommerce checkout bots

An illustration of a magnifying glass revealing a bot behind a browser window indicating improved website performance and user experience.

Summarize this article with

Agentic commerce changes what a "checkout request" looks like.

In the Universal Commerce Protocol (UCP), the thing hitting your complete_checkout endpoint is often not a browser at all. It’s a platform's backend or an AI agent calling your API directly via REST or MCP. There’s no page load, no JavaScript execution, nothing for a browser-based bot detection script to run in.

This is the gap we built our Automation Intelligence API to fill: It detects automation from HTTP request metadata alone β€” headers, method, URL, IP β€” with no JavaScript agent required.

In this post, we’ll walk through how to wire it into a UCP merchant integration, and β€” because UCP happens to give you two separate places to look β€” how to run it on both the caller of your checkout API and, when the platform provides it, the buyer behind the transaction.

What a UCP checkout request actually looks like

UCP's spec and reference implementations live in the Universal-Commerce-Protocol GitHub org: The protocol itself in ucp, and runnable reference merchant servers (Python/FastAPI and Node.js/Hono) in samples.

The Checkout capability is what you'll integrate against.

Its REST binding defines five operations, all under /checkout-sessions:

Operation Method Endpoint
Create Checkout POST /checkout-sessions
Get Checkout GET /checkout-sessions/{id}
Update Checkout PUT /checkout-sessions/{id}
Complete Checkout POST /checkout-sessions/{id}/complete
Cancel Checkout POST /checkout-sessions/{id}/cancel

(Source: Checkout β€” REST Binding)

Every request, regardless of operation, must carry a UCP-Agent header identifying the calling platform's profile, plus required Idempotency-Key and Request-Id headers.

Platforms may additionally authenticate using RFC 9421 HTTP Message Signatures via Signature/Signature-Input headers.

MCP is a separate binding. The same five operations exist, but they arrive as tools/call requests to a single MCP endpoint, with the operation name in params.name and the payload in params.arguments.

The checkout session ID moves out of the URL path and into the arguments, Request-Id isn't part of the binding, and the platform profile travels in meta["ucp-agent"] inside the body.

The detection call is the same either way.

The Automation Intelligence API works from headers, method, URL, and IP, none of which change shape between a REST payload and a JSON-RPC envelope. In our documentation, the Bot Detection overview lists "AI agents or assistants calling your API directly, MCP servers, or any backend-to-backend traffic" as the traffic the Automation Intelligence API targets, because none of it requires a browser.

Two different things to point Automation Intelligence at

UCP integration gives you signal about two distinct actors, and they deserve two distinct checks.

The caller The buyer
What it is Whatever opened the HTTP connection to your checkout-sessions (or /mcp) endpoint The human the platform says is completing the purchase
Where it comes from The raw HTTP request itself β€” headers, method, URL, client IP signals["dev.ucp.buyer_ip"] / signals["dev.ucp.user_agent"] in the UCP request body, when the platform includes them
Availability Always present Optional

The signals object is documented in the protocol specification's Signals section.

One check tells you about the thing that's actually talking to your server. The other tells you what the platform says it observed about the buyer's connection, on a surface you never talked to. Both are useful, but not interchangeable.

Detecting the caller (REST or MCP)

The Automation Intelligence API has a single endpoint, POST /edge (also called the Collect Intelligence endpoint).

Its request body (EdgeRequest) requires:

  • headers β€” an ordered array of {name, value} pairs
  • method β€” the HTTP method
  • url β€” the absolute URL that was requested
  • at least one of ipv4_address / ipv6_address

REST example

Say a platform sends this to your complete_checkout endpoint:

POST /checkout-sessions/chk_9f2a/complete HTTP/1.1
Host: merchant.example.com
UCP-Agent: profile="https://platform.example/.well-known/ucp"
User-Agent: platform-client/1.0.0
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Request-Id: req_abc123
Authorization: Bearer sk-live-abc123
Content-Type: application/json

{ "payment": { "instruments": [ { "handler_id": "gpay_1234" } ] } }

MCP example

Over MCP, the same operation arrives as a tools/call to your MCP endpoint instead:

POST /mcp HTTP/1.1
Host: merchant.example.com
Content-Type: application/json
UCP-Agent: profile="https://platform.example/.well-known/ucp"
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Digest: sha-256=:RK/0qy18MlBSVnWgjwz6lZEWjP/lF5HF9bvEF8FabDg=:
Signature-Input: sig1=("@method" "@authority" "@path" "content-digest" "content-type" "ucp-agent" "idempotency-key");keyid="platform-2026"
Signature: sig1=:MEUCIQDXyK9N3p5Rt...:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "complete_checkout",
    "arguments": {
      "meta": {
        "ucp-agent": { "profile": "https://platform.example/.well-known/ucp" },
        "idempotency-key": "550e8400-e29b-41d4-a716-446655440000"
      },
      "id": "chk_9f2a",
      "checkout": {
        "payment": { "instruments": [ { "handler_id": "gpay_1234" } ] }
      }
    }
  }
}

Either way you forward that request β€” minus the secret values β€” to /edge:

POST https://api.fpjs.io/v4/edge
Authorization: Bearer FPJS_SECRET_API_KEY

{
  "headers": [
    { "name": "Host", "value": "merchant.example.com" },
    { "name": "UCP-Agent", "value": "profile=\"https://platform.example/.well-known/ucp\"" },
    { "name": "User-Agent", "value": "platform-client/1.0.0" },
    { "name": "Idempotency-Key", "value": "550e8400-e29b-41d4-a716-446655440000" },
    { "name": "Request-Id", "value": "req_abc123" },
    { "name": "Authorization", "value": "" },
    { "name": "Content-Type", "value": "application/json" }
  ],
  "method": "POST",
  "url": "https://merchant.example.com/checkout-sessions/chk_9f2a/complete",
  "ipv4_address": "34.162.244.71",
  // custom correlation value
  "linked_id": "chk_9f2a",
  // arbitrary metadata
  "tags": { "source": "ucp_transport", "user_agent": "platform-client/1.0.0" }
}

ipv4_address here is the actual peer connecting to your server, not anything from the UCP payload.

linked_id set to the checkout session ID lets you pull every Automation Intelligence event for a given checkout later via GET /v4/events?source=edge or a single event via GET /v4/events/{event_id}.

The endpoint shown is the global region. Use eu.api.fpjs.io or ap.api.fpjs.io if your workspace is in the EU or Asia.

Web Bot Auth is also supported

If the calling agent signs its requests per Web Bot Auth, the request carries Signature-Agent, Signature-Input (with tag="web-bot-auth"), and Signature headers, separate from β€” and possibly alongside β€” any UCP-level RFC 9421 signature.

Forward those three headers through in your headers[] array exactly like any other header (the values intact, unlike Authorization), and Fingerprint verifies the signature server-side against the published key directory, returning a response where bot_info.identity is "signed".

  • Visit ourΒ Web Bot Auth testing pageΒ for a free, public endpoint where you can send a signed request and get clear feedback on whether your signature validates correctly.

Reading the response

bot_info is only present in the response when a bot is detected β€” no bot_info means no automation signal, not a confirmed human. When present, per AI agent detection, bot_info.identity is the field to act on:

  • verified β€” identity confirmed by Fingerprint.
  • signed β€” the WBA signature checked out against the agent's published key directory.
  • unknown β€” recognized as automation, but it didn't present a verifiable identity.
  • spoofed β€” it claimed an identity that couldn’t be verified.

Detecting the buyer, when UCP has the signals

Now the second, complementary check. When a platform includes signals on a complete_checkout request:

{
  "payment": { "instruments": [ /* ... */ ] },
  "signals": {
    "dev.ucp.buyer_ip": "203.0.113.42",
    "dev.ucp.user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."
  }
}

That's a second, independent thing worth running through the Automation Intelligence API. This is the human the transaction is for, and their IP and browser are worth the same bot or IP intelligence (proxy, VPN, datacenter, geolocation) checks you'd apply to a caller you connected to directly. Signals aren't limited to completion, so you can run this earlier in the flow as well.

{
  "headers": [
    { "name": "User-Agent", "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..." }
  ],
  "method": "POST",
  "url": "https://merchant.example.com/checkout-sessions/chk_9f2a/complete",
  "ipv4_address": "203.0.113.42",
  "linked_id": "chk_9f2a",
  "tags": { "source": "ucp_signal", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..." }
}

A single User-Agent string, with no accompanying header set, is a weaker input than a real captured request β€” you're mainly getting IP intelligence on the buyer's IP, plus whatever automation signal Fingerprint can infer from the UA string. That's still useful β€” a buyer_ip that resolves to a datacenter or a known VPN exit node is worth knowing about.

Putting both checks together

  • Caller identity is signed/verified, no buyer signal present. Nothing further to review from this pair.
  • Caller identity is spoofed . Worth reviewing regardless of what the buyer signal says; a caller failing its own claimed identity check is a stronger signal than anything derived from a buyer IP you didn't observe yourself.
  • Caller looks clean, buyer IP resolves to a datacenter or known proxy/VPN. Doesn't necessarily mean fraud (plenty of legitimate users route through VPNs), but it's a reasonable input into a broader risk model alongside order value, shipping/billing mismatch, and history.
  • No buyer signal at all. UCP doesn't require platforms to send it, and many won't in agent-initiated purchases.

Store both events (or at least both event_ids) against the checkout, and let your fraud/risk logic decide what to do with the combination.

Where this stands today

Two things worth flagging before you build on this.

The first is that Automation Intelligence API is currently in Public Preview, so the response schema is subject to change.

The second is that UCP is an actively developed spec, too. The ucp repository has an open issue tracker and regular pull requests, and the protocol uses dated versioning (this post reflects the 2026-04-08 release) specifically because backward-incompatible changes are expected to keep shipping as the spec matures.


Further reading

GitHub:

UCP.dev:

Fingerprint docs:

Share this post