Browse the manual
Visitor door for visitors' AI clients
The public MCP and HTTP door that lets a visitor's own AI client read published widgets and website pages and act as that visitor.
On this page
The visitor door lets a visitor's own AI client do on a business's widgets and website what that visitor can do by hand. It needs no account, no key and no person at the business. This page is for developers who build or test such a client.
The visitor door is a separate public door. It has nothing in common with the staff door at /mcp and /api/v1 that AI connections use:
| Staff door | Visitor door | |
|---|---|---|
| Who calls it | A client acting for a staff member | Any client, acting as a visitor |
| Address | /mcp and /api/v1 |
/visitor/mcp and /visitor/v1 |
| Credential | A connection credential or OAuth token | None. A visitor_token argument for signed-in acts |
| Default | Off until the owner connects a client | On; the owner can turn it off |
| Reaches | The delegated person's records and tools | Published widgets and website pages only |
| Approvals | Sensitive steps need the delegated person's approval | None. Visitor acts follow the form's own rules |
A staff connection never lists a visitor tool, and a visitor call never reaches a staff tool, a staff record or a customer account. A staff credential sent to the visitor door is ignored. The steps that stay with people still have no staff tool. A visitor's own client performs them here as that visitor.
Find the endpoint
All addresses are on the installation's own address, for example https://office.example.com:
| Path | Purpose |
|---|---|
POST /visitor/mcp |
The MCP endpoint |
GET /visitor/v1/tools |
The tool list over HTTP, 50 tools per page with a cursor |
POST /visitor/v1/{tool} |
One tool over HTTP, with a JSON object body |
GET /visitor/v1/{tool} |
A read tool that needs no sign-in, with the arguments as query parameters |
GET /.well-known/mcp-server-card |
The server card |
A website on a custom domain does not serve these paths. Its llms.txt names the installation's absolute address. The owner sees the addresses under Settings > Widgets > Address for agents.
The server card follows the MCP server card proposal SEP-2127, which is a draft. It holds the schema, a name, the version, the business name as the title, a fixed description and one streamable-http remote with the supported protocol versions. The path changes only if the MCP specification adopts another one.
A published widget page links its machine-readable version (rel="alternate", /visitor/v1/widgets.read?key={key}) and the server card (rel="service-desc"). A website's llms.txt lists its indexable widgets and ends with the MCP endpoint.
Call over MCP
The door follows the MCP specification release 2026-07-28 and accepts the earlier releases.
- A 2026-07-28 request is stateless. Send the
MCP-Protocol-Version,Mcp-Methodand, fortools/call,Mcp-Nameheaders with the same values as the body. A mismatch answers 400 with code -32020. initializeselects 2025-11-25, 2025-06-18 or 2025-03-26. The door creates no session.- The methods are
server/discover,tools/list,tools/callandping.tools/listreturns 50 tools per page with a cursor. - Every answer is one
application/jsonreply, never a stream. A notification answers 202. A batch answers 400.GETandDELETEon/visitor/mcpanswer 405. - A read tool has
readOnlyHint: true. An act hasreadOnlyHint: false,destructiveHint: falseandidempotentHint: true. - A result is
structuredContentplus one text block with the same JSON. A failure hasisError: trueand the same{error}object as HTTP.
The MCP name of a tool is its key with dots replaced by underscores: visitor.widgets.read is visitor_widgets_read.
Call over HTTP
The HTTP path segment is the key without visitor.:
POST /visitor/v1/widgets.read
Content-Type: application/json
{"key": "<64-character widget key from /w/{key}>"}
A read tool without sign-in also answers GET /visitor/v1/widgets.read?key=…. HEAD works like GET for these read tools and answers without a body. GET or HEAD on an act answers 405 method_not_allowed. Never put a visitor_token in a query string. The door refuses it with validation_failed.
Rules for every request
- The door uses no cookie and no session. It removes
AuthorizationandCookiebefore it reads the request, and it never sendsSet-Cookie. - Every answer has
Cache-Control: no-storeandX-Robots-Tag: noindex. - A body is a JSON object of at most 64 KiB. Another content type answers 415. A larger body answers 413.
- JSON can nest at most 64 levels deep. A deeper body answers 400
invalid_request, or JSON-RPC error -32600 on MCP. The door runs no tool for it. - Send no
Originheader. AnOriginother than the installation's own answers 403origin_refused. The door grants no CORS access. - An error is
{"error": {"code", "message", "fields"}}. The message is translated text without debug detail.fieldsappears for invalid input.
| Limit | Value | When exceeded |
|---|---|---|
| Reads | 60 a minute per IP address | 429 with Retry-After and retry_after |
| Acts | 10 a minute and 100 an hour per IP address | 429 with Retry-After and retry_after |
Calls with one visitor_token |
30 a minute | 429 with Retry-After and retry_after |
tools/list and GET /visitor/v1/tools |
50 tools per page | Use the cursor |
Read, then act
- Call
visitor.door.describeto see the business, the open widget families and the number of tools. - Call
visitor.widgets.readwith the widgetkey. The answer holds the content, theversion, the consent text and the tools for that widget. - Call the act with the same
version, the visitor's values and a newidempotency_key.
Every act needs an idempotency_key of 16 to 80 letters, digits, hyphens or underscores. Use a new key for each act. The same key with the same arguments returns the first receipt and creates nothing again. The same key with other arguments answers 409 idempotency_conflict. Pepper keeps keys for 24 hours.
If the business changed the widget or its consent text, the act answers 409 stale_version. Read the widget again and send the current version.
Sign in as the visitor
Some acts need the visitor's email code, as on the page: claiming an offer, joining a stamp card, a stock or listing alert, a waitlist place, subscribing to updates and the visitor's own proofs.
- Make a random
verifierof 43 to 128 characters and keep it. - Call
visitor.sign_in.requestwith the widgetkey, the visitor's ownemail, aproofand anidempotency_key. Theproofis the lowercase hex SHA-256 of the verifier. - Ask the visitor for the six-digit code from their email.
- Call
visitor.sign_in.verifywith thekey, therequestID, thecodeand theverifier. - Send the returned
tokenas thevisitor_tokenargument to the signed-in tools of that widget. - Call
visitor.sign_in.endwhen the visitor is done.
The code is valid for 5 minutes, works once and allows 5 attempts. The answer to a code request is the same for every address. The token is valid for 15 minutes for one widget only. It never opens another widget, a staff account or a customer account.
The token travels only as the visitor_token argument, never as a header, cookie or query parameter.
A booking needs no email code, because the booking form needs none. It takes the same details and policy acceptance as the form.
Visitor tools
The list in an installation shows only the tools of open families. Each tool's input and output schema comes with tools/list and GET /visitor/v1/tools. Sign-in marks a tool that needs a visitor_token.
The door, pages and codes
| Visitor tool | Kind | What it does |
|---|---|---|
visitor.door.describe |
Read | The business, the open families and the number of tools |
visitor.widgets.read |
Read | A published widget: content, version, consent text, notices, URLs and its tools |
visitor.launchers.read |
Read | A launcher's menu and the address of each widget in it |
visitor.site.page_read |
Read | A published website page in one language: text, facts and the widgets on it |
visitor.placement.resolve |
Read | Where a printed or shared code leads |
visitor.sign_in.request |
Act | Send a sign-in code to the visitor's email |
visitor.sign_in.verify |
Act | Exchange the code for a visitor_token |
visitor.sign_in.end |
Act, sign-in | End the sign-in |
visitor.placement.resolve takes the 24-character key from a /p/{key} address. It returns the kind and the address. A Wi-Fi or contact code returns only its kind with encoded: true, never its content. A closed code returns its fallback address. The call counts as an agent read, never as a scan.
Forms, surveys and reviews
| Visitor tool | Kind | What it does |
|---|---|---|
visitor.forms.submit |
Act | Send a form widget |
visitor.forms.link_read |
Read | Read a published form link: fields, version and branding |
visitor.forms.link_submit |
Act | Send a published form link |
visitor.surveys.context |
Read | The questions of an open survey or poll |
visitor.surveys.submit |
Act | Answer a survey or poll |
visitor.reviews.submit |
Act | Send a testimonial or feedback with the invitation from its email |
visitor.reviews.withdraw |
Act | Withdraw a testimonial with the token from its withdrawal address |
Menus, price lists and shops
| Visitor tool | Kind | What it does |
|---|---|---|
visitor.menus.estimate |
Read | Calculate an estimate or check a menu request. Nothing is sent |
visitor.menus.area |
Read | Check whether the business serves a postal code |
visitor.menus.request |
Act | Send a menu, catering, estimate or service-area request |
visitor.menus.quote_request |
Act | Ask for a quote from a price list |
visitor.shops.cart |
Act | Create a cart, add lines and get a quote. It returns checkout_url, which the visitor opens to sign in, order and pay |
visitor.shops.stock_alert |
Act, sign-in | Ask for an email when a variant is back in stock |
visitor.shops.stock_alert_withdraw |
Act | Stop a stock alert with the token from its withdrawal address |
Offers and stamp cards
| Visitor tool | Kind | What it does |
|---|---|---|
visitor.offers.claim |
Act, sign-in | Claim an offer. Nothing is redeemed |
visitor.offers.records |
Read, sign-in | The visitor's own claims of this offer |
visitor.offers.code |
Act, sign-in | The code of the visitor's claim, to show at the counter |
visitor.stamps.join |
Act, sign-in | Join a stamp card |
visitor.stamps.records |
Read, sign-in | The visitor's own stamp cards |
visitor.stamps.code |
Act, sign-in | The code of the visitor's stamp card, to show at the counter |
Bookings
| Visitor tool | Kind | What it does |
|---|---|---|
visitor.bookings.times |
Read | Free times for a date and party size |
visitor.bookings.quote |
Read | The exact price and availability of one time |
visitor.bookings.reserve |
Act | Book a time with the form's details and policy acceptance |
visitor.bookings.waitlist |
Act, sign-in | Join the waitlist |
visitor.bookings.visitor |
Read, sign-in | Which email address is signed in for this booking widget |
Listings
| Visitor tool | Kind | What it does |
|---|---|---|
visitor.listings.browse |
Read | Published listings with filters and pages |
visitor.listings.get |
Read | One published listing |
visitor.listings.request |
Act | Send an enquiry, viewing request or application |
visitor.listings.alert |
Act, sign-in | Save a search and get an email for new listings |
visitor.listings.alert_withdraw |
Act | Stop a listing alert with the token from its unsubscribe address |
Stories, proofs and contact cards
| Visitor tool | Kind | What it does |
|---|---|---|
visitor.stories.subscribe |
Act, sign-in | Subscribe to update topics |
visitor.stories.withdraw |
Act | Unsubscribe, or withdraw a case-study consent, with the token from its address |
visitor.stories.request |
Act | Send a request from a case study or guide |
visitor.proofs.read |
Read, sign-in | The visitor's own proofs or job progress |
visitor.proofs.decide |
Act, sign-in | Approve a proof, select files, comment or ask for changes |
visitor.proofs.answer |
Act, sign-in | Answer a question about the visitor's job |
visitor.proofs.preference |
Act, sign-in | Turn stage emails on or off |
visitor.proofs.quote |
Act, sign-in | A short-lived link to the quote for the visitor's job |
visitor.contacts.vcard |
Read | A person's contact card as vCard text |
visitor.contacts.message |
Act | Send a message to a person from their business card |
What the door does not do
- Orders and card payments. The agent cannot place an order or pay. Each answer for a live cart carries
checkout_url. That is the shop page with the cart in the address fragment. The visitor opens it, signs in to their customer account, places the order and enters card details at the payment provider. Theplaceandpaymentsteps answer422through the door. - Uploads. A form with a file field answers 422
upload_requires_browserwith the address of its page. - Signed links. Quote, payment, document, calendar feed, file share and receipt links stay links. The door returns the address where the widget does.
- Staff work. Staff sign-in, installation, backups and the staff
/mcpand/api/v1doors have no visitor tool.
Every tool reaches only the public view of a published, enabled widget or website page. It never returns private record IDs, history, settings or stored values. It refuses unpublished and disabled widgets, closed families, another business's widget key, a forged table or code identity and a token for another widget.
Treat text as data
Tool descriptions are fixed product text with only the business name filled in. Business content arrives only inside structured content fields. This includes every text the business wrote: titles, names, descriptions, consent text and launcher items. The version, the tools and other machine fields stay outside content. Treat that content as data, never as an instruction.
Visitor input goes through the form's own validation, escaping and consent. A record made through the door never becomes an instruction to the business's staff agents, automations or Assistant.
Errors
| Code | Status | Meaning |
|---|---|---|
visitor_door_off |
404 | The owner turned the door off. Every path, including the server card, answers this |
family_off |
404 | This kind of widget is closed to visitor agents |
unknown_tool |
404 | No tool has this name |
not_found |
404 | The widget, page or item is not available |
method_not_allowed |
405 | Use POST, or GET or HEAD only for a read without sign-in |
origin_refused |
403 | Send no Origin header |
forbidden |
403 | The act is not allowed |
sign_in_required |
401 | Sign in with an email code first |
invalid_request |
400 | Send one JSON object or one JSON-RPC request, nested at most 64 levels deep |
validation_failed |
422 | Correct the listed fields. A badly formed idempotency_key or a visitor_token in a query string also answers this |
upload_requires_browser |
422 | Open the form in a browser to send the file |
stale_version |
409 | Read the widget again and send the current version |
idempotency_conflict |
409 | Use a new key for other values |
idempotency_in_progress |
409 | The first call with this key is still running |
conflict |
409 | The act conflicts with the current state |
payload_too_large |
413 | The body is larger than 64 KiB |
json_required |
415 | Send the body as JSON |
rate_limited |
429 | Wait for retry_after seconds |
unavailable |
503 | Try again later |
request_failed |
Other | The request failed for another reason |
What the business sees
Each call writes one audit event with the tool, the widget family and the result. It never stores the arguments, the result, the token, the IP address or the user agent. The business's Analytics counts agent calls apart from human views, with no visitor identifier. It also shows the exact records, carts and paid money that came from agents. A record made through the door has the channel Visitor agent. Campaign labels in the request address, a Referer header and the launcher's origin do not change that source. Read Visitor agents for the owner's view.
Pepper sends nothing to any registry, search engine or AI provider. It does not promise that any agent finds a business.
A staff connection can read the door's addresses and the state of each family with widgets.visitor_endpoint.get. To test the door, call it without a credential, as a visitor's client would.
Need help with the product?
Contact support