Discovery — /.well-known/best

The manifest describes the services, capabilities, transport bindings, and authentication requirements. No prior configuration is needed — a consumer hits the URL and learns everything it needs to interact.

Consumer
Any Client
LLM · agent · app
GET /.well-known/best
→
Manifest
BEST Endpoint
capabilities · auth · schemas
Start interacting
→
APIs
Commands & Queries
no config required

Every BEST endpoint exposes:

GET /.well-known/best
Content-Type: application/json

This endpoint is always public — an implementation that requires auth on it is non-conformant. The path is canonical; /.well-known/best.json may be served as an optional alias, but consumers must not rely on it.

A server should send an ETag and a Cache-Control other than no-store on this response, as HTTP defines them (RFC 9110, RFC 9111), so a consumer can tell that the manifest changed without reading it again: the manifest's version is the protocol version and does not change with the document. A 304 answering a consumer's conditional request is conformant.

Schema: discovery.json · Full example: well-known-best.json

Origin Discovery

The manifest above solves endpoint discovery — a consumer that already holds the BEST endpoint's URL learns everything else from it. It does not, by itself, solve origin discovery: an agent pointed at a product's public web origin (https://example.com) has no defined path to the BEST endpoint, which commonly lives on another host (https://api.example.com).

A deployment whose BEST endpoint is not the public web origin should bridge the gap:

  1. Serve /.well-known/best on the public origin. Either return the manifest directly, or redirect (301/308) to the canonical manifest on the API host. Consumers must follow redirects on this path; the redirect target is the canonical endpoint for all subsequent interaction. The origin copy keeps the same public/no-auth requirement as the canonical one.

    https://example.com/.well-known/best
                  │ 308
                  ▼
    https://api.example.com/.well-known/best
    
  2. Advertise the bridge in the origin's HTML, so agents that fetch the page discover the manifest without prior BEST knowledge:

    <link rel="alternate" type="application/json"
          href="/.well-known/best" title="BEST service manifest">
    

The manifest is the only entry. A deployment points at its manifest — the well-known path, the redirect, the HTML <link> — and must not publish a second description of its BEST surface for agents to start from: no llms.txt restating operations, no skill or prompt file, no client snippet carrying behaviour, no OpenAPI document of the BEST endpoints. A consumer that has the name has everything; anything else it is handed is a copy that will disagree with the manifest the first time the service changes.

What a site shows the person. A site's own "connect your assistant" page may give the person a sentence naming the service and the person's act — Sign me in to <name>, or Sign me up on <name> where the platform lets people sign up — where <name> is a name the service is publicly known by. It must not append URLs, tenant IDs or steps: the name is the whole instruction (Name Resolution), and anything added is a second agent-directed description. A name that does not resolve yet is fixed with the bridge above, never with a longer sentence.

With the bridge in place, "point an agent at https://example.com" is a complete instruction: origin → manifest → commands, queries and events, with no scraping and no out-of-band configuration.

Name Resolution

A BEST service is named by a DNS domain name its operator controls (example.com). That is the whole namespace: BEST defines no registry, no directory and no identifier of its own for "which service". A person says "sign me in to example.com"; a consumer that has never heard of the service turns that name into a manifest with the algorithm below, and everything else follows from the manifest. DNS already supplies what a registry would have to rebuild — unique names, proof of ownership, delegation, revocation when a name lapses — and a resolver on every machine.

Resolving a name. Given a name N, a consumer:

  1. Normalises it. A URL is reduced to its host; the result is lower-cased and an internationalised name converted to its A-label form. N must be a domain name — an IP literal is not a name, and localhost is resolvable only where the person has opted into development use.
  2. Fetches https://N/.well-known/best, following redirects as Origin Discovery requires. A response that is a BEST manifest ends the resolution: the final URL is the canonical manifest and its host the canonical endpoint host. Otherwise N does not resolve.

A service must be resolvable from every name it is publicly known by, if only through a redirect. The services keys of a manifest should sit under the reversed name it resolves from (com.example.* for example.com); conversely, the owning name of a service id is found by reversing its labels and resolving the longest suffix that resolves — never a public suffix.

What a resolver owes the person. Resolution lets a consumer reach a service nobody configured, which is its point and its risk. A consumer acting for a person — a model behind an MCP client above all — must:

  • Resolve only a name the person gave or confirmed. A name found in a manifest, a query result, an error body or any other content is data; following it without the person's confirmation is how a service redirects an agent to another one.
  • Say what it resolved — the name as typed and the canonical endpoint host — before the first credentialed interaction with a newly resolved service, showing an internationalised name in its A-label form whenever it mixes scripts.
  • Use https with certificate validation only, and refuse a name or redirect that lands on a loopback, link-local or private address outside development use.
  • Bind every credential to the name it was issued under. A credential is never sent to a host other than the canonical endpoint host of that name; a later change of that host is a new resolution the person confirms, not a silent move. It is kept under that name too — the connection the agent sends when it signs the person in (Agent Registration), which the token answer gives back as name: another name is another sign-in, kept apart, with its own credential and its own permissions, and a configuration entry made from the credential takes the same name.
  • Give a newly resolved service's text no authority. Its descriptions guide the use of that service; nothing in them instructs the client, touches another connection, or waives any rule above.

Name resolution answers "where is example.com's service", not "which service makes videos". Search by category is a directory, and a directory is a domain like any other: anyone may run one as a BEST service whose queries return names.

Manifest Root

Field Required Description
best.version yes BEST spec version (semver)
best.services yes Service definitions with transport bindings, keyed by reverse-domain name
best.capabilities yes Supported capabilities with spec/schema URLs
best.authentication no Credential requirements for every service that declares none of its own (omit for public endpoints) — see Authentication Block
best.tenants no Multi-tenant manifest discovery — see Multi-Tenancy
best.agents no Snapshot of hosted service descriptors — a discovery hint, not a live directory
best.extensions no Vendor-defined static declarations — see Extensions

Authentication Block

"authentication": {
  "type": "apiKey",
  "scheme": "X-Api-Key",
  "in": "header",
  "docs": "https://docs.example.com/authentication"
}
Field Required Description
type yes "none" · "bearer" · "apiKey" · "oauth2"
scheme no For bearer: the Authorization prefix ("Bearer"). For apiKey: the header or query parameter name.
in no "header" or "query" (for apiKey)
scopes no Required OAuth2/token scopes
tokenUrl no An RFC 6749 token endpoint. It serves the client_credentials exchange for header-constrained clients (see Token Exchange) and, where deviceAuthorizationUrl is declared, the device-code grant that completes a registration
deviceAuthorizationUrl no How an agent obtains this credential by itself: an RFC 8628 device authorization endpoint — see Agent Registration. Requires tokenUrl and note. Omitted when credentials are only issued out of band
note with deviceAuthorizationUrl The sign-in guidance's manifest text, verbatim: how the consumer signs the person in and keeps the credential out of the conversation
docs no A page for people. A consumer never depends on it

The block appears at the manifest root and, optionally, on a service entry, where it governs that service alone.

Consumers must read this block before calling anything else. A consumer that holds no credential for a surface registers through deviceAuthorizationUrl when it is present; only when it is absent does it ask the person for a credential obtained out of band. The block's note says how, step by step (Sign-in Guidance).

Token Exchange for Constrained Clients

Some legitimate clients cannot set HTTP headers — URL-only integrations, webhook targets, legacy tooling. The escalation path is: prefer headers; fall back to a body-based token exchange; only then to a query parameter carrying a short-lived token. The long-lived credential never appears in a URL.

Client can Mechanism
Set headers Authorization: Bearer <token> or the apiKey header — always preferred
POST a body, but not set custom headers Exchange the long-lived credential at tokenUrl for a short-lived token
Only control a URL Send the exchanged short-lived token as the access_token query parameter per RFC 6750 §2.3

When type is "oauth2", tokenUrl names an RFC 6749 token endpoint. Hosts intending to serve header-constrained clients should accept the client_credentials grant — a form-encoded POST requiring no custom headers — and return access_token, token_type, and expires_in per RFC 6749 §5.1. Exchange-issued tokens should be short-lived (minutes, not days), and tenant context derives from the credential itself, never from a tenant identifier submitted alongside it. Declaring apiKey with in: "query" for a long-lived credential should not be done — offer the exchange instead. Full normative rules: Security Requirements and specs/security.md.

Agent Registration

A service that lets an agent obtain its own credential declares deviceAuthorizationUrl and tokenUrl. The exchange is RFC 8628, unchanged; BEST adds three request parameters (connection, agent_label, credential_lifetime), three members of the token answer (name, tenant_id, auth_header), the credential block that comes with the credential, and the sign-in guidance: the words, fixed by this specification, that tell the consumer at each step what to do.

1. The agent asks. A form-encoded POST to deviceAuthorizationUrl, with no credential:

Parameter Required Description
client_id yes (RFC 8628) The name of the consumer software (best-mcp, claude). BEST services pre-register no clients: the value is caller-declared and, like source, never identity.
connection no The name of this connection: one word of lowercase letters, digits and hyphens (^[a-z0-9][a-z0-9-]{0,63}$), made from the name the person gave the service — "acme payroll dev" becomes acme-payroll-dev; its host when they gave none — and confirmed with them before the request. The same person signing in to two doors of one platform (a customer surface and a back office, each with its own credential and its own permissions) gives two names. The service keeps it with the registration and gives it back as name in the token answer, unread: it is the consumer's label, never identity. Another shape is invalid_request, with an error_description that states the shape.
agent_label no What the approver reads when asked to admit this agent — recognisable to them ("Claude on Ada's phone").
credential_lifetime no durable (default) or session. A consumer that cannot keep a secret out of a conversation sends session. The value only ever lowers what is issued.

2. The service answers at once (RFC 8628 §3.2): device_code, user_code, verification_uri, optionally verification_uri_complete, expires_in, interval. The device_code is generated by the service, unguessable, and is neither derived from nor usable as any identifier — not an envelope id, not a correlationid, not an account or registration id. The answer also carries note: the device text of the sign-in guidance, verbatim.

3. The agent gives the person verification_uri_complete (or verification_uri and user_code) before anything else, to open in their own browser. It must not open the link itself or in a browser it controls: approving is the person's act, and a page the agent drives could approve for them or put their password within its reach. It polls tokenUrl with grant_type=urn:ietf:params:oauth:grant-type:device_code and its device_code, no faster than interval. authorization_pending and slow_down are not errors; access_denied and expired_token end the registration (RFC 8628 §3.5). A service should keep answering expired_token for an expired device_code for at least as long as the code lived, so a late poll learns why; after that, invalid_grant is acceptable.

4. On approval the token endpoint answers per RFC 6749 §5.1 — access_token, token_type, and expires_in when the credential is not durable or its approver bounded its life — with three BEST members: name, the connection's name — the connection the agent sent, or the address of the sign-in manifest when it sent none; tenant_id, the account the credential opens; and auth_header, the header that carries it when that is not Authorization. token_type is Bearer for a credential sent as Authorization: Bearer, and apikey for one sent in the header auth_header names. A device_code is redeemed once.

The credential block. The same answer carries what the consumer needs to decide what to do with the credential — because the consumer is often a model making the requests itself, and this answer is the only thing that reaches it with the credential:

Member Required Description
note yes Begins with the token text of the sign-in guidance, verbatim: the credential is a secret, kept in the client's configuration or credential store and never repeated in the conversation — in a message, a command or any output. A service may add to it after that text (where a person can mint a credential themselves, how to use this one).
name yes The connection's name: the connection the agent sent to deviceAuthorizationUrl, else the address of the sign-in manifest (its host, with its port when it has one, and its path when it is not at the root). The credential is kept under it, and the mcp entry is keyed by it
endpoint yes The surface the credential opens (the tenant endpoint on a multi-tenant host), on the canonical endpoint host
manifest no That surface's manifest
ready_check no A URL on the credential's host whose GET, with the credential, answers 200 once the credential works
mcp no This connection as an MCP client's configuration: an object with one member keyed by name, whose value is the client's server entry (for the reference MCP server: command, args, env), the credential in its env. Two sign-ins are two names the person gives (Name Resolution), so two entries: neither overwrites the other
http no The surface for direct calls: an object with header, the header line with a placeholder for the credential (X-Api-Key: <access_token>), commands and queries, the catalogue URLs, and events, the event log's URL (GET /events), on the canonical endpoint host

The consumer chooses: an MCP-capable client writes mcp into its own configuration, one without MCP keeps the credential for direct calls, and a client with its own store keeps it there and redacts it from everything the model sees, as always (Credentials stay out of transcripts). The credential appears only in access_token and in mcp — never in note, http or any example.

Every destination in the block is on the credential's host. Each URL the credential may be sent to — endpoint, the URLs in http, the base URL in mcp — must be on the canonical endpoint host of the name the consumer resolved, and a consumer must not send the credential to one that is not (Name Resolution: a credential is never sent to a host other than the canonical endpoint host of its name). A page for people (docs) may be elsewhere; the credential never goes there.

A credential that opens no single account. The token answer has no tenant_id when the credential opens no one account: on a service without tenants, or when it was admitted at the root of a host where one identity may hold several tenants, or none yet. Its endpoint is then the root service's, and the credential works there. A service with tenants that answers so must list, at its root, the tenants such a credential opens, each with its tenant_id, endpoint and manifest, as Multi-Tenancy rule 6 says: by its tenants root, or by a query of its own catalogue where the root is itself a surface. tenant_id is never the host or another stand-in, which a consumer would expand into tenants.manifest and reach no account.

A credential that ends. After Revoke agent, or once a credential runs out, every request under it is refused with 401, and an event stream open under it must end within a bounded time whether or not events flow, since a 401 on the next request never reaches it; the stream of a credential that revoked itself first delivers that command's outcome, so the agent sees its command complete. An approver may bound a credential's life when admitting it: the token answer's expires_in then says the bound, and a bound the service cannot honour is refused, never shortened silently.

Errors. deviceAuthorizationUrl and tokenUrl answer errors per RFC 6749 §5.2 — for the device endpoint by RFC 8628 §3.2, which answers errors "in the same way as the token endpoint" — not in the BEST error format: status 400 (or 401 for a client failure) with a JSON body carrying error and, recommended, error_description. A request that is not form-encoded is invalid_request — never a bare status with an empty body, because the agent is often a model that corrects itself only from what the answer says. tokenUrl uses the RFC 8628 §3.5 codes above.

Nothing in this exchange is a BEST command, a catalogue entry or a workflow: it needs no public surface, no recipe and no prose beyond the sign-in guidance, and a client implements it once for every service. What happens on the page the person opens — signing in, signing up, approving — is the platform's own; Identity and Agent Registration describes it.

Sign-in Guidance

The consumer is often a general-purpose model that knows nothing of BEST, and what reaches it while it signs a person in is all it has to go on. Left to itself it has opened the approval link in a browser it drives, shown the manifest to the person, and written the credential into its commands. So a service that declares deviceAuthorizationUrl says what to do at each of the three steps in the words below, verbatim — the same on every service, so that no implementation can drop a point:

Where Member Carries
The authentication block that declares deviceAuthorizationUrl note the manifest text
The device authorization answer note the device text
The token answer note the token text, first; a service may add to it after

Manifest text

For you, the agent, not the person: read this manifest with an HTTP request; never show it to them or open it in a browser. When the person asks you to sign them in, first name the connection: one word of lowercase letters, digits and hyphens, made from what they called this service (acme payroll dev becomes acme-payroll-dev; its host if they gave no name), and ask them to confirm it before you go on. Then POST a form to deviceAuthorizationUrl with client_id set to your software's name and connection set to that name, and give them the link it answers (verification_uri_complete, or verification_uri and user_code) to open in their own browser, where they sign in and approve you. Never open that link yourself or in a browser you control, never sign in for them, and never ask for a password. Then poll tokenUrl, no faster than interval. Its answer carries a secret credential and, in name, the connection's name: save the credential straight to your client's credential store or to a file only the person can read, under that name, and never put it in a message, a command or any output: read the rest of the answer without it.

Device text

Give the person this link, verification_uri_complete (or verification_uri and user_code), to open in their own browser, where they sign in and approve you. Never open it yourself or in a browser you control: approving is theirs. Then poll tokenUrl no faster than interval, with a form of grant_type=urn:ietf:params:oauth:grant-type:device_code, device_code and client_id. Its answer carries a secret credential and, in name, the connection's name: save the credential straight to your client's credential store or to a file only the person can read, under that name, and never put it in a message, a command or any output: read the rest of the answer without it.

Token text

The credential is a secret. Keep it in your client's configuration or credential store, under the name in this answer, the connection the person named: another name is another sign-in, kept apart, with its own credential and its own permissions. Never repeat it in the conversation, in a message, a command or any output: read it from where you stored it. A transcript, commands and their output included, is not a secret store, and a credential that appears there is leaked and must be replaced. If your client cannot store it, say so instead of showing it.

The same texts are published in discovery.json under $defs/signInGuidance, one set per version that changed them: the schema requires a manifest text wherever deviceAuthorizationUrl is declared, and validators check the device answer. They change only with this specification, and every implementation takes them unchanged. Each version's texts stay published: a service carries the texts of the version it declares, or of a later one, so it is never non-conformant for not having moved yet — and a service that declares a version carries at least that version's words. A consumer whose own client makes these requests — the reference MCP server is one — follows them itself and need not show them to its model.

Services and Transport Bindings

Each entry in services declares version and description (required), optional spec URL, an optional authentication block of its own, and one or more transport bindings:

Binding Required fields Notes
http endpoint Baseline — every conformant service exposes it. endpoint is the consumer-facing base URL; all capability paths are appended to it.
mcp transport (stdio/sse/http), server Optional. May carry its own authentication block and push: true. See MCP Transport.

Multiple transports expose the same capability surface — they are alternative access methods, never separate operation sets.

Surfaces with different access. A service entry may carry its own authentication block, of the same shape as the root block. When present it governs every capability that service implements and the root block does not apply to them; when absent the root block applies. "type": "none" declares a public surface: its capabilities are callable without a credential.

"authentication": { "type": "apiKey", "scheme": "X-Api-Key", "in": "header" },
"services": {
  "com.example.catalogue": {
    "version": "1.0.0",
    "description": "The public product catalogue.",
    "authentication": { "type": "none" },
    "http": { "endpoint": "https://api.example.com/catalogue" }
  },
  "com.example.shared": {
    "version": "1.0.0",
    "description": "Read-only view of a project shared by link.",
    "authentication": { "type": "bearer", "scheme": "Bearer" },
    "http": { "endpoint": "https://api.example.com/shared" }
  }
}

A surface is an ordinary service with ordinary capabilities: what it offers is discovered from its catalogues like anywhere else. Where a credential itself identifies the scope it opens — a share token, a per-project key — the endpoint carries no scope identifier: context derives from the credential, as Security Requirements already requires of tenants.

Capability Entries

Field Required Description
name yes Reverse-domain capability name. io.best.* is reserved for the spec; custom capabilities use an implementer-owned prefix (com.acme.inventory).
version yes Semver
description yes Human-readable summary
spec io.best.* only URL to the capability specification page (optional for custom capabilities)
schema io.best.* only URL to the capability's JSON Schema (not OpenAPI)
service conditional Key of the implementing service in services. Required when the capability's name prefix doesn't match the service key (e.g. custom service com.acme.trading implementing io.best.agents.commands).
status no "active" (default) · "partial" · "planned"
endpoints no Machine-readable list of { method, path, description? }. Paths are appended to the service's http.endpoint. This is how consumers self-bootstrap without reading spec pages.
push no Push channels supported (events capability): { "sse": true, "mcp": true }
extends no Parent capability, if any
extensions no Vendor-defined static declarations — see Extensions

Status semantics: active means all required endpoints exist and are callable — declaring active while returning 404/501 on required routes is a conformance violation. partial means a subset is implemented; consumers must not assume full coverage and should consult the endpoints array. planned means nothing is callable yet. A workflows capability is active only while it publishes at least one recipe, and planned with none (Workflows).

Command types are domain data, not capabilities. Individual command types (ProposeCounter) must never appear as manifest capability entries — the capability declares the command surface; the specific types are discovered at runtime via GET /commands.

Extensions

The manifest root and each capability entry accept one optional free-form object, extensions, for vendor-defined declarations. Everything else in the manifest stays strictly closed (additionalProperties: false) — this is the single lawful home for data the spec doesn't define:

{
  "name": "io.best.agents.events",
  "version": "1.0",
  "spec": "…", "schema": "…",
  "extensions": {
    "com.acme.region": "eu-west-1",
    "org.example.attestation": { "format": "…", "value": "…" }
  }
}

Rules:

  • Domain-first. Anything dynamic, behavioral, or obtainable after authentication must be modeled as ordinary capability surface — a custom capability, a query, an event, a workflow — never as an extension. extensions exists solely for static, discovery-time declarations that must ride in the manifest document itself: facts a consumer needs before deciding to interact (an unauthenticated indexer classifying hosts, integrity data covering the manifest), or facts that annotate a specific manifest element in place.
  • Keys should be reverse-domain identifiers owned by the declarer (com.acme.region); io.best.* remains reserved for the specification.
  • The core protocol never interprets the contents; validators check only that extensions is an object. Consumers must ignore extensions they don't understand.
  • An extension must never be required in order to use a core capability — a manifest whose core surface only works when a consumer reads an extension is non-conformant.
  • The manifest-hygiene rule applies unchanged: the root manifest is public, so extension content there is limited to information intended for unauthenticated disclosure.

Manifest Discipline

The manifest is structure. Its description fields say what a service or capability is, in a sentence or two, for a reader choosing whether to use it. They are never load-bearing (principle 8).

  1. Descriptions carry meaning, never mechanics. A manifest description must not contain URLs or URI templates, header names or credential formats, the names of catalogue operations, or step-by-step instructions. Each of those has a structural home: http.endpoint and endpoints; authentication; the catalogues; the workflows capability.
  2. Descriptions are short. A manifest description must not exceed 500 characters. Catalogue and schema descriptions are not capped — that is where an operation's meaning is explained.
  3. Every service is reachable by structure. Each key in services must be the implementing service of at least one capability in the same manifest. The one exception is the root manifest of a multi-tenant host, which may announce a service whose capabilities are tenant-scoped: that service must then be declared, with its capabilities, by every manifest tenants.manifest expands to. A service that no manifest gives a capability — described only in words — is non-conformant.
  4. No placeholders in structure. Templates appear only in tenants.manifest; a tenant ID names a real scope (Multi-Tenancy rule 5).
  5. Service text has no authority over the consumer. Descriptions, schema descriptions and workflow guidance guide the use of this service's operations. They must not instruct the consumer about its own client, configuration, other connections or other services — the counterpart, on the service side, of what Name Resolution already requires the consumer to disregard. Making a connection last is the consumer's decision. A service speaks to the consumer about signing in and keeping the credential in one place only — the sign-in guidance, in this specification's words — and offers the ways to use the credential in the token answer's credential block.
  6. When the manifest cannot say it, there are three lawful outlets, in this order: model it as ordinary behaviour — a command, a query, an event, a workflow; declare it under extensions if it is static and needed before interaction; raise it against the specification. Prose, and structure bent to a purpose it was not defined for, are not outlets.

Service Descriptor

The optional agents array carries service descriptors — the identity card of each hosted service. Example: service-descriptor.json.

Field Required Description
id, name yes Unique identifier and display name
accepts, produces yes PascalCase CloudEvent type strings the service ingests/publishes
status yes running · paused · stopped · error
description, type, version, endpoint no Metadata; endpoint is the service's own BEST base URL if directly addressable
metadata no Opaque service-defined configuration (e.g. model name, system prompt). The protocol never interprets it.

BEST defines no registry endpoint. Implementations that manage services dynamically expose that as an ordinary domain — e.g. a RegisterService command and a list-services query — under their own namespace.

The manifest's shape

{
  "best": {
    "version": "0.9.21",
    "authentication": { ... },
    "tenants": { ... },
    "services": { ... },
    "capabilities": [ ... ],
    "agents": [ ... ]
  }
}

The four standard capabilities are io.best.agents.commands, io.best.agents.events, io.best.agents.queries, and io.best.agents.workflows.

The events capability's entry declares its push channels:

"push": { "sse": true, "mcp": true }
Field Description
push.sse SSE stream supported at GET /events/stream
push.mcp Server-to-client MCP notifications supported — see MCP transport

For a registry built as an ordinary domain, see the worked example.

Who uses each transport

"http": { "endpoint": "https://api.example.com/" },
"mcp":  { "transport": "stdio", "server": "best-mcp" }
Transport Primary consumer Protocol
HTTP Web UIs, traditional services, monitoring tools HTTP/JSON
MCP LLM clients (ChatGPT, Copilot, Gemini, Claude) JSON-RPC over stdio/SSE

Multi-Tenancy

A tenant ID in BEST is an opaque string scoping a manifest to a context — a customer account, a user, a workspace, or the platform's own administrative context. Use it when callers operate in isolated data scopes (even with identical capabilities), skip it for single-tenant deployments.

The root manifest of a multi-tenant host declares a URI template (RFC 6570, {tenantId} is the only permitted variable):

"tenants": {
  "manifest": "https://api.example.com/.well-known/best/{tenantId}"
}

Rules (normative — see also Conformance):

  1. The root manifest must include tenants.manifest if tenant-scoped capabilities exist, and must not declare tenant-scoped capabilities itself — they appear only in tenant manifests. Root-level capabilities the host can fulfil without tenant context may stay; such a capability's service declares an authentication block of its own, even one identical to the root's, which is how a consumer and a validator tell it from a tenant-scoped capability left at the root.
  2. The expanded URI returns a fully self-contained tenant manifest: its http.endpoint is pre-scoped (e.g. https://api.example.com/api/best/tenants/acme), every dataschema URI fully resolved, no {tenantId} placeholders anywhere, no tenants block of its own.
  3. Fetching /.well-known/best/{tenantId} requires at most the API key declared in the root authentication block — never a tenant ID header (the path already carries it).
  4. URI templating is valid only in tenants.manifest. Everywhere else, URIs are fully resolved.
  5. A tenant ID names a real scope. A constant pseudo-tenant (public, default, anonymous) used to host a surface that has no scope is non-conformant — declare a root-level service with its own authentication instead (Services and Transport Bindings).
  6. The tenants root says where a credential belongs. A service the root manifest announces for its tenants (Manifest Discipline rule 3) may give it, in the root manifest (and must, unless its root is itself a surface, when its token answer names no tenant_id: Agent Registration), the http.endpoint that each tenant manifest's http.endpoint for the same service extends with /{tenantId} — its tenants root (https://api.example.com/api/best/tenants for the …/tenants/acme of rule 2). A consumer finds it as that service's http.endpoint in the root manifest. A GET on a tenants root should answer, with a credential, 200 and the tenants that credential opens (must, for a credential answered with no tenant_id): { "tenants": [ { "tenant_id", "endpoint", "manifest" } ] }. Without one it answers the 401 a tenant endpoint gives, naming the way in; a service that cannot tell which tenant a credential opens answers a BEST error that says where the consumer finds it — the endpoint of the token answer and its tenant_id where it has one, or a new sign-in; a credential answered with no tenant_id is never answered so, since its tenants are listed. Never a bare 404: a consumer that kept only its credential — a model after a restart — otherwise concludes it has no account. A root whose service is itself a surface — commands and queries of its own at its http.endpoint, its tenants under another path — has no tenants root: it lists the tenants a credential opens through its own catalogue, a query whose answer carries each tenant's tenant_id, endpoint and manifest, and its tenant manifest route answers an unknown or guessed id with a BEST error that names that query.

Per-tenant manifests pay off even when all tenants share one capability surface: the tenant's http.endpoint structurally encodes the scope into every path (auditable at the infrastructure layer), dataschema URIs need no caller-side substitution, and an organisation's members share one manifest and key.

For the first-contact algorithm — what to read, what to collect from the user, and when to fetch the tenant manifest — see SPEC.md — Agent Navigation Guide.

spec/v0.9.21