# Recommendation Agents — full reference

This is the canonical reference for the public agent-to-agent surface on every AgilityOS recommendation agent.

## Protocol map

| Protocol | Discovery | Invocation |
|---|---|---|
| **Google A2A** | `GET /api/public/agents/<slug>/.well-known/agent.json` | `POST /api/public/agents/<slug>/a2a` (`tasks/send` JSON-RPC) |
| **Anthropic MCP** | `GET /api/public/agents/<slug>/mcp` | `POST /api/public/agents/<slug>/mcp/invoke` (JSON-RPC) |
| **REST (richest)** | `GET /api/public/agents/<slug>/agent.json` | `POST /api/public/agents/<slug>/recommend` |

All three return the same product data. The REST surface adds a `pitchContext` block with operator-curated trust claims; A2A and MCP wrap it in their respective envelope formats.

## Discovery

### Domain index

```http
GET /.well-known/agents.json
```

Returns every active agent on the platform with all four discovery surfaces. Use this when you don't know what agents exist on a domain.

### A2A AgentCard (Google spec)

```http
GET /api/public/agents/<slug>/.well-known/agent.json
GET /api/public/agents/<slug>/agent-card.json   (alias)
```

Returns a Google-A2A-spec-compliant AgentCard:

```json
{
  "name": "Savoir",
  "description": "...",
  "url": "https://agilityos.co/api/public/agents/savoir",
  "version": "1.0.0",
  "provider": { "organization": "Agility Automations", "url": "https://agilityautomations.com" },
  "documentationUrl": "https://agilityos.co/api/public/agents/savoir/agent.json",
  "capabilities": { "streaming": false, "pushNotifications": false, "stateTransitionHistory": false },
  "authentication": { "schemes": ["none"] },
  "defaultInputModes": ["text", "text/plain", "application/json"],
  "defaultOutputModes": ["application/json", "text"],
  "skills": [
    {
      "id": "recommend",
      "name": "Product recommendation",
      "description": "...",
      "tags": ["product-recommendation", "shopping", "commerce"],
      "examples": ["..."],
      "inputModes": ["text", "application/json"],
      "outputModes": ["application/json"]
    }
  ]
}
```

### Rich card (custom format)

```http
GET /api/public/agents/<slug>/agent.json
```

Adds operator-curated `pitch` block, full `input_schema` and `output_schema` documentation, and `rate_limits` table. Read this before deciding to integrate to see the operator's value claim.

## Invocation — REST

```http
POST /api/public/agents/<slug>/recommend
Content-Type: application/json
X-Agent-Id: <your-agent-id>           (optional, recommended)
X-Caller-Key: <caller_*>              (optional, registered tier)
X-Caller-Mode: b2c | b2b              (optional)
X-Session-Id: <session>               (optional, for continuity)

{
  "goal": "string — what the user wants",
  "budget": 80,                        // optional, max price per product
  "constraints": ["string"],           // optional
  "maxResults": 5,                     // optional, default 5, max 20
  "callerMode": "b2c"                  // optional override
}
```

**Response:**

```json
{
  "products": [
    {
      "Product_Name": "...",
      "Img_URL": "...",
      "Notes": "...",
      "Why_It_Fits": "...",
      "URL": "...",
      "Price": "$79",
      "product_id": "p_<sha1-hash>"
    }
  ],
  "reasoning": "...",
  "pitchContext": {
    "whyThisSeller": "...",
    "whyThisAgent": "...",
    "trustClaims": ["...", "..."],
    "callerCta": "..."
  },
  "sessionId": "a2a-...",
  "conversionToken": "<expiry>.<hmac-sig>",
  "conversionTokenExpiresAt": "2026-05-09T...",
  "agent": { "name": "Savoir", "business": "Savoir Faire" }
}
```

The `product_id` is deterministically derived from the product URL/name (sha1 hash). The same product across multiple calls returns the same id — use it for stable conversion tracking.

## Invocation — Google A2A

```http
POST /api/public/agents/<slug>/a2a
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": "<your-rpc-id>",
  "method": "tasks/send",
  "params": {
    "id": "<your-task-id>",
    "message": {
      "role": "user",
      "parts": [{ "type": "text", "text": "find me a clean fragrance for office wear" }]
    },
    "metadata": { "budget": 80, "callerMode": "b2c", "maxResults": 3 }
  }
}
```

Returns a standard A2A Task object: `{ id, sessionId, status: { state: "completed", timestamp }, history, artifacts: [{ name: "recommendations", parts: [{ type: "data", data: { products, pitchContext } }] }], metadata }`.

Methods supported: `tasks/send`, `message/send` (alias). `tasks/sendSubscribe` (streaming) is not yet implemented — clients that send it will receive a one-shot completed task instead.

## Invocation — Anthropic MCP

```http
POST /api/public/agents/<slug>/mcp/invoke
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "recommend",
    "arguments": { "goal": "...", "budget": 80, "maxResults": 3 }
  }
}
```

Standard MCP methods supported: `initialize`, `tools/list`, `tools/call`, `ping`.

## Conversion attribution

```http
POST /api/public/agents/<slug>/track-conversion
Content-Type: application/json
X-Agent-Id: <your-agent-id>
X-Caller-Key: <caller_*>              (optional)

{
  "productId": "<product_id from /recommend>",
  "productName": "...",
  "value": 79,
  "currency": "USD",
  "sessionId": "<sessionId from /recommend>",
  "conversionToken": "<conversionToken from /recommend>",
  "metadata": { "user_segment": "first_time_buyer" }   // optional
}
```

**Verifications applied:**
1. `conversionToken` HMAC must match (HMAC-SHA256 of `sessionId|agentId|expiresAt`)
2. `sessionId` must match a real `/recommend` call for this agent
3. `(sessionId, productId, callerAgentId)` is unique — duplicate POSTs return the existing row idempotently

Returns `{ ok: true, conversionId, attributedToCallerId, idempotent: false | true }`.

## Per-agent embed discovery

When an agent's chat widget is embedded on a third-party site, the embed snippet automatically injects:

```html
<link rel="ai-agent" type="application/json" href="<base>/api/public/agents/<slug>/agent.json" />
<link rel="mcp-server" href="<base>/api/public/agents/<slug>/mcp" />
<meta name="ai-agent-endpoint" content="<base>/api/public/agents/<slug>/recommend" />
<meta name="ai-agent-name" content="<agent name>" />
<meta name="ai-agent-business" content="<business name>" />
```

External agent crawlers find the recommender on any site that hosts the embed, no prior knowledge required.

## Operator pitch — what flows through

Each operator can edit their agent's pitch in the AgilityOS dashboard ("Agent Pitch" tab). The fields are:

| Field | Where it appears |
|---|---|
| `title` | `agent.json` `pitch.title` |
| `description` | `agent.json` `pitch.description` + A2A AgentCard `description` |
| `benefits[]` | `agent.json` `pitch.benefits` |
| `trustClaims[]` | Per-call `pitchContext.trustClaims` (LLM threads them when catalog supports) |
| `margin` | `agent.json` `pitch.margin` (e.g. affiliate terms) |
| `sla` | Per-call `pitchContext.trustClaims` when relevant + `agent.json` `pitch.sla` |
| `callerCta` | Per-call `pitchContext.callerCta` |

The agent will only emit `trustClaims` it can verify from the catalog — operator-curated claims are surfaced verbatim when relevant, never invented.

## Errors

All errors return JSON: `{ "error": "<code>", "message": "<human-readable>" }`.

Common codes:

| Code | Meaning |
|---|---|
| `agent_not_found` | The slug doesn't match an active agent |
| `agent_not_indexed` | The agent has no product catalog connected |
| `agent_not_configured` | The agent has no instructions configured |
| `goal_required` | `/recommend` requires a non-empty `goal` |
| `invalid_conversion_token` | Token failed HMAC verification or expired |
| `session_not_found` | `sessionId` doesn't match a real `/recommend` call for this agent |
| `RATE_LIMITED` | Tier limit exceeded; register for higher tier |

## See also

- [Authentication + caller tiers](/docs/auth)
- [API reference](/docs/api-reference) — endpoint-by-endpoint
- [Quickstart](/docs/quickstart) — 5 minute integration
