The key idea
poppy.json is a small pointer file. It must sit at /.well-known/poppy.json over https, name a domain that matches the one agents asked, and point to an OAuth server whose metadata vouches for that domain.
Every personal agent starts with one request: GET https://yourdomain.com/.well-known/poppy.json. This file tells it who you are, how sessions and sign-in work, and which interfaces it may use. It is the first thing to publish, and the only thing you need to be discoverable.
Where it lives
- At
https://{domain}/.well-known/poppy.json, where the domain is yours or a subdomain you control. - Over https. The URL may redirect to a copy hosted elsewhere, but every redirect must be to an https URL.
- Served as JSON. Send caching headers (for example
Cache-Control: public, max-age=3600): agents cache the file by them.
The smallest file
If you only want agents to know your website is there:
{
"protocol_version": "0.1",
"organization": { "name": "Example Shop", "domain": "example.com" },
"web": {}
}
Agents then browse your site as ordinary signed-out visitors. No OAuth server is needed yet.
Every field
| Field | Required | What it holds |
|---|---|---|
protocol_version |
Yes | "0.1" for this draft, as major.minor. Agents refuse a major version they don’t support. |
organization |
Yes | name to show users, and domain, which must match the host agents requested (a leading www. is ignored). |
auth |
If you list agent, apis or a browser-session endpoint |
issuer, your OAuth issuer URL, plus one block per sign-in type you offer: direct, device, mediated. |
auth.direct / auth.device |
If offered | scopes: what that sign-in type can grant. |
auth.mediated |
If offered | endpoint that takes credentials, fields describing them (name, label, secret), and scopes. |
auth.custom_scopes |
No | A short description of each scope you define beyond poppy:read and poppy:write. |
agent |
One of agent, apis or web | protocols: each with a type (today poppy) and an https endpoint for conversations. |
apis |
One of agent, apis or web | A list, each with type (openapi or mcp), url and a short description. |
web |
One of agent, apis or web | Your website. The optional browser_session_endpoint is where an agent’s browser joins a session. |
extensions |
No | Extensions you support, keyed by name, each with a version (for example operations). |
Any entry under agent, apis or extensions may add a resource if you want tokens issued for that one service. Agents ignore fields they don’t recognise, so you can add more later without breaking anyone.
Three starter files
Website plus sign-in. Users connect their account on your own sign-in page, then the agent browses your site signed in:
{
"protocol_version": "0.1",
"organization": { "name": "Example Shop", "domain": "example.com" },
"auth": {
"issuer": "https://auth.example.com",
"direct": { "scopes": ["poppy:read", "poppy:write"] }
},
"web": { "browser_session_endpoint": "https://example.com/poppy/browser-session" }
}
An MCP server. The quickest API for agents. The MCP server must accept Poppy session tokens and point to your issuer in its protected resource metadata:
{
"protocol_version": "0.1",
"organization": { "name": "Example Shop", "domain": "example.com" },
"auth": {
"issuer": "https://auth.example.com",
"direct": { "scopes": ["poppy:read", "poppy:write"] },
"device": { "scopes": ["poppy:read"] }
},
"apis": [
{ "type": "mcp", "url": "https://mcp.example.com/mcp", "description": "Orders, returns and product search" }
]
}
Everything. APIs, your own agent, the website and a custom scope:
{
"protocol_version": "0.1",
"organization": { "name": "Example Shop", "domain": "example.com" },
"auth": {
"issuer": "https://auth.example.com",
"direct": { "scopes": ["poppy:read", "poppy:write", "addresses"] },
"custom_scopes": { "addresses": "Manage saved delivery addresses" }
},
"agent": {
"protocols": [{ "type": "poppy", "endpoint": "https://api.example.com/poppy/conversations" }]
},
"web": { "browser_session_endpoint": "https://example.com/poppy/browser-session" },
"apis": [
{ "type": "openapi", "url": "https://api.example.com/openapi.json", "description": "Orders, returns and exchanges" },
{ "type": "mcp", "url": "https://mcp.example.com/mcp", "description": "Product search and sizing" }
],
"extensions": {
"operations": { "version": "1", "endpoint": "https://api.example.com/poppy/operations" }
}
}
The OAuth metadata it points to
Before using your file, an agent fetches your issuer’s OAuth metadata (RFC 8414, at /.well-known/oauth-authorization-server) and checks two things: the metadata’s issuer is exactly your auth.issuer, and its poppy_domains list includes your organization.domain. Without that second check, any website could name your OAuth server and receive tokens meant for you.
{
"issuer": "https://auth.example.com",
"token_endpoint": "https://auth.example.com/oauth/token",
"revocation_endpoint": "https://auth.example.com/oauth/revoke",
"authorization_endpoint": "https://auth.example.com/oauth/authorize",
"poppy_domains": ["example.com", "example.co.uk"]
}
Several domains may name one issuer, such as a site per country. They then count as one company. More in Set up your OAuth server.
Mistakes agents reject
- The domain doesn’t match.
organization.domainsaysexample.combut the file is served fromshop.example.net. After a redirect, the host the file ends up on does not count; the host the agent asked does. poppy_domainsis missing from the OAuth metadata, or doesn’t list your domain.- The issuer differs by a character, such as a trailing slash in one document and not the other.
authis missing whileapisoragentis listed. Those need sessions, and sessions need auth.- A scope starts with
poppy:but isn’tpoppy:readorpoppy:write. That prefix is reserved. - A redirect to http, or a file served as HTML.
The checker tests each of these and shows the fix.
Free Poppy checker
See what your domain needs for the Personal Agent Protocol, one step at a time.
Check your domainWritten against Personal Agent Protocol (Poppy, PAP) draft 0.1, which may change. Flow is not affiliated with the protocol's authors.