# Read catalog evidence with A2A

Discover Open Agent Guide at `/.well-known/agent-card.json`. Send JSON-RPC
requests to `/a2a`. The endpoint accepts both A2A method spellings and routes
them to the same operations: the standard `message/send` method with
`role: "user"` (the default when you send no `A2A-Version` header), and the 1.0
`SendMessage` method with `role: "ROLE_USER"` and `A2A-Version: 1.0`. The server
uses the official Python `a2a-sdk` 1.2.2.

Reads work anonymously. Add `Authorization: Bearer YOUR_PERSONAL_API_TOKEN` for
your account's catalog permissions and higher API limits. MCP OAuth tokens and
MCP delegation credentials cannot authorize A2A calls.

## Send a structured request

```sh
curl https://www.openagentguide.com/a2a \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":"example","method":"message/send","params":{
    "message":{"messageId":"example","role":"user","parts":[{"kind":"data","data":{
      "operation":"search_products","params":{"search":"database","has_mcp":"yes","page_size":10}
    }}]}
  }}'
```

The 1.0 spelling is equivalent: set `A2A-Version: 1.0`, name the method
`SendMessage`, and use `role: "ROLE_USER"`. Both return the same artifact; the
0.3 response holds the completed task at `result`, and the 1.0 response holds it
at `result.task`.

Use exactly one data part. Operation inputs have a machine-readable schema at
`/a2a/schema.json`. Output schemas are at `/a2a/output-schema.json`; catalog
records follow the public [API schema](/api/v1/openapi.json).

Supported data objects:

```json
{"operation":"get_product","product":"product-slug","expand":"absence_attestations"}
{"operation":"get_product_coverage","product":"product-slug"}
{"operation":"list_categories","page_size":20}
{"operation":"list_jobs_to_be_done","page_size":20}
{"operation":"list_capabilities","page_size":20}
{"operation":"list_compliance_certifications","page_size":20}
{"operation":"compare_products","products":["11111111-1111-4111-8111-111111111111","22222222-2222-4222-8222-222222222222"],"fields":["name","open_source"]}
{"operation":"submit_feedback","title":"Search misses a known product","body":"A search for X returns nothing, but the product exists.","context":{"path":"/api/v1/catalog/products/","severity":"low"}}
```

## Report a problem with the service

Use `submit_feedback` to report a bug, request a feature, or flag a docs problem
about Open Agent Guide itself. Use it for the service, the API, or the docs. To
change catalog data, use the HTTP submissions API instead.

Add `Authorization: Bearer YOUR_PERSONAL_API_TOKEN`; the operation needs an
account and returns `401` without one. Send `title` and `body` as plain text. An
over-cap `title` or `body` is rejected, not truncated, so shorten it and send it
again. Do not tag a category; classification is applied later at triage. The
optional `context` object takes `path`, `method`, `expected`, `actual`,
`severity` (`low`, `medium`, or `high`), and `reference_url`. The operation is
submit-only: it returns a short receipt (`id`, `title`, `created_at`,
`submitted_by`) and there is no read-back.

Replace comparison UUIDs with IDs from search. Comparisons accept two to four
products and one to twenty top-level product detail fields. Each row contains
`values`, the full `product` evidence, and its `coverage` report. Unknown fields
retain `null`; confirmed absences retain their attestations. An invalid field
fails the comparison. Relations use MCP's default expansions plus absence
attestations; request extra expansions with `get_product` when you need more
detail.

Search `params` accepts the catalog API's query parameters, including arrays for
repeated filters. `query` also accepts the MCP spelling of `search`. The API
validates filter names and values and supplies ordering. Semantic similarity
helps find candidates; it does not establish that a requirement is satisfied.
Use the cursor from a response's `next` URL for the next request.

## Read completion evidence for a selected job

Use `get_product` to read the complete `documented_task_completions` relation.
Match each selected job to `job_to_be_done_slug` or `job_to_be_done_id`. Display
its `verdict`, `scope`, and `conditions` with its assessment. The verdict
includes delegation limits. Keep other jobs secondary. If you use `fields`,
include `documented_task_completions`.

No matching record means the job is unassessed. A matching record with null
`completion_support` means completion is unknown. Neither establishes that the
product cannot complete the job.

## Results and limits

A successful call returns a completed task with one JSON artifact. Save that
response locally: the server retains no tasks, context, or history. Task reads,
lists, continuation, cancellation, streaming, push notifications, and extended
Agent Cards are unsupported. New calls work independently across restarts and
server workers. The server does no background work.

Requests have a 1 MiB body limit and a ten-second body-read timeout by default.
Each operation has a sixty-second execution deadline. Comparisons perform at
most eight catalog reads, sequentially, and stop at the first failure. The
server applies ingress limits plus the existing catalog and embedding quotas to
the caller. Semantic search retains its cache and lexical fallback.

Protocol errors use A2A errors. Catalog failures include their public `status`,
`code`, `detail`, `retry_after`, and `rate_limit_scope` when available. Ingress
failures use HTTP 413, 408, 429, or 503; a 429 includes `Retry-After`. Wait for
that interval before retrying. Raw upstream errors and credentials stay private.

These operations retrieve catalog facts and accept service feedback.
Personalized advice, arbitrary use-case interpretation, generative inference,
live research, catalog submissions, and payments are unavailable.

## Python client

Install `a2a-sdk[http-server]==1.2.2`, then run:

```python
import asyncio
import httpx
from a2a.client.transports.jsonrpc import JsonRpcTransport
from a2a.types import a2a_pb2 as wire
from google.protobuf.json_format import MessageToDict, ParseDict


async def main():
    async with httpx.AsyncClient(headers={"A2A-Version": "1.0"}) as http:
        base = "https://www.openagentguide.com"
        response = await http.get(base + "/.well-known/agent-card.json")
        response.raise_for_status()
        card = ParseDict(response.json(), wire.AgentCard())
        client = JsonRpcTransport(http, card, card.supported_interfaces[0].url)
        request = ParseDict(
            {
                "message": {
                    "messageId": "example",
                    "role": "ROLE_USER",
                    "parts": [
                        {"data": {"operation": "list_categories", "page_size": 10}}
                    ],
                }
            },
            wire.SendMessageRequest(),
        )
        result = await client.send_message(request)
        print(MessageToDict(result.task.artifacts[0].parts[0].data))


asyncio.run(main())
```

For portability searches, pass `self_hostable`, `open_source`, and `data_export`
in `search_products.params`. The first two accept booleans; `data_export`
accepts `full`, `partial`, or `none`, or an array of these values for OR
matching. Unknown values are excluded when filtering. Follow the returned `next`
cursor with the same filters to continue through results.
