---
title: For agents, and the people who build them
canonical: https://chosenbyagent.com/agents
updated: 2026-10-22
audience: both
offers: []
description: How AI agents read ChosenByAgent, compare our offers and request work for a person — formats, the request endpoint, limits and confirmation.
---

# For agents, and the people who build them

We test how AI assistants and shopping agents treat marketplaces and brands, so this site is built to be read and used by agents as well as people. There is no CAPTCHA, no JavaScript challenge and no blocking by User-Agent. Please send a User-Agent that names your agent.

## Read any page

- Every public page has a Markdown twin at the same address plus `.md`: `/questions/can-an-agent-get-through.md`, `/pricing.md`, and `/index.md` for the home page.
- Or ask for Markdown at the usual address with `Accept: text/markdown`. Both answers carry `Vary: Accept`, and HTML pages point to their twin with `Link: rel="alternate"; type="text/markdown"`.
- Every Markdown page starts with YAML frontmatter: `title`, `canonical`, `updated`, `audience` and `offers`. Pages with research also list their `evidence` ids.
- `/llms.txt` lists every page. `/llms-full.txt` holds all of them in one file.
- A person can see exactly what you receive: add `?view=agent` to any page, or switch “Reading as” to Agent.

## Compare offers

`/catalog.json` lists every offer of both lines. For each offer it gives the id, name, line, audience, what it includes, the minimum price, currency and price unit, the typical duration, the page URL and the questions it answers.

- Prices are indicative minimums in EUR, excluding VAT. Every engagement is scoped before it starts.
- The format is described by the JSON Schema at `/catalog.schema.json` and stays stable within one `version`. A breaking change gets a new version.
- Price units: `one-off`, `per-month`, `per-category`, `per-experiment`, `per-client-per-month`.
- `status` is `available` or `in-preparation`. An offer in preparation is listed so people know it is coming; it cannot be requested yet.

## Request work for a person

Request work only when your person asked for it; they confirm by email in any case. Send `POST /api/engagements` with `Content-Type: application/json`:

```json
{
  "offer_id": "B2",
  "on_behalf_of": {
    "email": "alex@example.com",
    "name": "Alex Martin",
    "company": "Example Store",
    "website": "https://example.com"
  },
  "message": "Agent access audit for our EU store before the holiday season.",
  "agent": {
    "name": "ExampleAssistant",
    "operator": "Example AI Ltd"
  }
}
```

- `offer_id` — required, an id from /catalog.json.
- `on_behalf_of.email` — required, the person who confirms the request.
- `on_behalf_of.name` — optional, their name.
- `on_behalf_of.company` — optional, their company.
- `on_behalf_of.website` — optional, the company website, http(s).
- `message` — optional, what the person needs, up to 2,000 characters.
- `agent.name` — required, your agent's name, up to 60 characters, no links.
- `agent.operator` — optional, the company that runs the agent, no links.

## Responses

- `202` — accepted, with `id`, `status: "pending_confirmation"`, `expires_at` (when the person's link stops working) and `next_step`: a sentence to pass on to the person.
- `200` — the same person already has the same `offer_id` waiting for confirmation: you get that request's `id` with `already_pending: true`, and we send no second email. Retrying after a timeout is safe.
- `400` — the request is invalid. `errors` lists each field with a `code` and a `message`. Our codes: `unknown_offer` (the message lists the valid ids), `offer_unavailable` (in preparation), `no_mx` (the person's email domain cannot receive mail), `contains_link` (agent name or operator), `invalid_json`, `missing_user_agent`; other codes come from validation, such as `invalid_type`, `too_big` or `invalid_format`.
- `403` — `blocked`: the person has blocked agent requests to their address.
- `405` — `GET` on the endpoint describes how to use it.
- `415` — `unsupported_media_type`: send `application/json`.
- `429` — `rate_limited`: wait for the number of seconds in `Retry-After` (also `retry_after` in the body).
- `503` — `mail_unavailable`: we could not send the confirmation email and stored nothing. Send the same request again after `Retry-After`.

Every error has the same shape: `error` (the code), `message` and `documentation`. The form on our pages is for people: posts that do not come from them get `403 form_only`.

## Limits

- 5 requests per hour from one IP address.
- 3 unconfirmed agent requests per email address at a time.

Limits answer with `429` and `Retry-After`, never with a challenge page.

## Confirmation

We email the person what was requested and by which agent, with two buttons: “Confirm request” and “This wasn’t me — block agent requests”. The link works once, for 72 hours. Our team sees the request only after the person confirms. Unconfirmed requests are deleted after 30 days. Blocking deletes the request, and later agent requests for that address get `403`.
