What an agent sees when it asks us who we are
Discovery is the first thing an agent does and the last thing anybody writes
about. It is one unauthenticated GET, it returns a small JSON document, and
everything the agent does afterwards is shaped by what that document said. This
post is that document, quoted whole, with a note on why it says what it says.
The platform address
curl https://api.cresva.ai/.well-known/osp.json
{ "osp_version": "3.0", "acp_version": "3.0", "type": "platform", "is_storefront": false, "platform": { "name": "Cresva", "host": "cresva.ai", "multi_tenant": true }, "catalogue": { "present": false, "reason": "This is the platform address. Products belong to a storefront, and a storefront is named by its brand id." }, "addressing": { "descriptor_by_query": "https://api.cresva.ai/.well-known/osp.json?brand=<brandId>", "storefront_root": "https://api.cresva.ai/api/storefront/<brandId>", "openapi_by_query": "https://api.cresva.ai/.well-known/openapi.yaml?brand=<brandId>" }, "brand_lookup": { "available": false, "endpoint": null, "reason": "There is no public endpoint that maps a domain to a brand id, and this says so rather than leaving the question open. The platform does not record which host belongs to which brand in a form it could answer from, so any mapping offered here would be a guess. Ask the merchant for their storefront URL: the brand id is in it." }, "documentation": "https://developers.cresva.ai/protocol/spec", "rate_limits": { "public": "60/minute", "authenticated": "300/minute", "anonymous": "10/minute" } }
Captured 16 September 2026. Three things in it are worth more than the rest.
is_storefront: false. An agent that arrives at a host and finds a
well-known file will assume it has found a shop. Here it has found a building
with many shops in it. Saying so in a boolean, at the top, means an agent does
not have to infer it from an empty catalogue.
catalogue.present: false, with a reason. The alternative was to return an
empty products array. An empty array is indistinguishable from a shop with
nothing in it, and an agent acting on that would tell a user this merchant sells
nothing. A refusal with a sentence attached cannot be misread that way.
brand_lookup.available: false, with a longer reason. This is the one an
agent most wants to be true. Give it a domain, get back a brand id. It is false,
and the field says why: the platform does not record which host belongs to which
brand in a form it could answer from, so any mapping offered would be a guess.
A guess here is worse than a refusal, because an agent cannot tell one from the
other and will send a shopper to the wrong merchant.
A storefront address
The same file, with a brand id on it:
curl "https://api.cresva.ai/.well-known/osp.json?brand=cmqmr1f6j0003la04nu93f4k4"
{ "osp_version": "3.0", "acp_version": "3.0", "type": "storefront", "is_storefront": true, "storefront_url": "https://cresva.ai/api/storefront/cmqmr1f6j0003la04nu93f4k4", "brand_id": "cmqmr1f6j0003la04nu93f4k4", "brand_name": "Cresva", "capabilities": ["search", "recommend", "compare", "negotiate"], "supported_currencies": ["USD"], "supported_languages": ["en"], "rate_limits": { "public": "60/minute", "authenticated": "300/minute", "anonymous": "10/minute" }, "documentation": "https://developers.cresva.ai/protocol/spec" }
capabilities is the load bearing field, and this storefront declares four.
The protocol defines five. transact is absent, which means this storefront
will not open a checkout session and an agent should not try. An agent that
reads the declaration first gets a clean refusal from its own code; an agent
that assumes and calls anyway gets a refusal from ours, naming the capability
that was missing. The first is faster and neither is a guess.
The two hosts are not interchangeable
api.cresva.ai serves the .well-known documents and the /v1/ platform API,
and answers every other path with a 404 before routing. Storefront routes are
on cresva.ai, under /api/storefront/{brandId}/. A storefront path sent to
the first host returns not_found whatever else is right about it, and that is
the single most common way a first integration fails.
The rate limit is in the document and in the header
Both manifests declare 10 requests a minute anonymous, 60 on a public key, 300
on a secret key. So does every response:
$ curl -sI "https://cresva.ai/api/storefront/$BRAND_ID/products" | grep -i ratelimit x-ratelimit-limit: 10 x-ratelimit-remaining: 8 x-ratelimit-reset: 1789565882
Publishing a ceiling in two places is a way of being wrong in two places, so
the header is the one to trust: it is enforced by the thing that answers you.
Read it rather than a page, including this one.
With one caveat that will confuse you if nobody says it. **A repeated identical
GET is served from CDN cache, and a cached response carries the rate limit
counter from whenever it was first computed.** So x-ratelimit-remaining can
sit still, or go up, across requests you know you made. The limiter is not
wrong and neither is the header; you are reading a copy. Vary the query string
if you want to watch the counter move.
Why any of this matters
A discovery document is a contract about what an agent may assume. Every field
that says false with a reason attached is a place this protocol chose to
refuse rather than to guess. That is not modesty. An agent cannot tell a guess
from a fact, and a wrong fact at discovery propagates into every call that
follows it.