Discovery — /.well-known/bsp
Every BSP-compliant endpoint exposes a standard discovery URL:
GET /.well-known/bsp
Content-Type: application/json
This returns a JSON manifest describing the available agents, services, capabilities, and transport bindings. No prior configuration is needed — a consumer hits the URL and learns everything it needs to interact.
Content-Type: The response must use
Content-Type: application/json. Consumers must not assume a.jsonfile extension on the URL. The path/.well-known/bspis canonical. Implementations may also serve/.well-known/bsp.jsonas an alias (for compatibility with static file hosts), but this is not required and consumers must not rely on it.
Discovery Flow
- Consumer hits
/.well-known/bsp - Reads the structured manifest
- Discovers available services, capabilities, transport bindings, and authentication requirements
- If
authentication.typeis notnone, obtains credentials before calling API endpoints - Starts interacting without any hard-coded integration
Manifest Structure
/.well-known/bsp → what can I do? (discovery)
/schemas/event.json → what does an event look like? (contract)
/specs/agents/event-delivery → how does event delivery work? (documentation)
Manifest Root
{
"BSP": {
"version": "{{BSP_VERSION}}",
"authentication": { ... },
"tenants": { ... },
"services": { ... },
"capabilities": [ ... ],
"services": [ ... ]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
version |
string | yes | BSP spec version (semver: "MAJOR.MINOR.PATCH") |
authentication |
object | no | Authentication requirements for this endpoint (omit for public endpoints) |
tenants |
object | no | Multi-tenant manifest discovery. When present, signals that this is a multi-tenant host and provides a URI template for consumers to obtain a tenant-scoped manifest. See Multi-Tenant Routing. |
services |
object | yes | Service definitions with transport bindings |
capabilities |
array | yes | Supported capabilities with schema URLs |
services |
array | no | Snapshot of known services (discovery hint only — see below) |
Authentication
If an endpoint requires authentication, it declares this in the authentication block. Consumers must read this block before making any API calls.
"authentication": {
"type": "apiKey",
"scheme": "X-Api-Key",
"in": "header",
"docs": "https://docs.example.com/authentication"
}
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | One of: "none", "bearer", "apiKey", "oauth2" |
scheme |
string | no | Authorization header value prefix (e.g. "Bearer") |
in |
string | no | Where the API key is passed: "header" or "query" (for apiKey type) |
scopes |
string[] | no | Required OAuth2 / token scopes |
tokenUrl |
string | no | Token endpoint URL for OAuth2 or token-based flows |
docs |
string | no | URL to human-readable authentication documentation |
The /.well-known/bsp endpoint itself is always publicly accessible without credentials so consumers can read the manifest. All other endpoints may require authentication as declared.
Services
Services are top-level domains. Each service has its own version, spec URL, and transport bindings.
| Service | Namespace | Description |
|---|---|---|
| Agents | io.bsp.agents |
Service registry, command ingestion, published events |
Capabilities
Capabilities are composable building blocks within a service.
| Capability | Description | Extends |
|---|---|---|
io.bsp.agents.registry |
Register, remove, list, get services | — |
io.bsp.agents.lifecycle |
Pause and resume services | agents.registry |
io.bsp.agents.events |
List and query domain events, event catalogue, event schema discovery | — |
io.bsp.agents.commands |
Discover available commands (catalogue), send commands (ingestion) | — |
Each capability object has these fields:
| Field | Description |
|---|---|
name |
Fully qualified capability identifier (e.g. io.bsp.agents.registry) |
version |
Semver version string |
description |
Human-readable summary |
spec |
URL to the capability specification page |
schema |
URL to the JSON Schema for this capability's data structures — e.g. registry.json, events.json. This is a JSON Schema file, not an OpenAPI spec. |
service |
Key of the implementing service in the manifest's services object (e.g. "io.bsp.agents", "io.dotquant.trading"). Required when the capability's name prefix does not match the service key — for example, a custom service implementing a standard BSP capability. Consumers use this to resolve which http.endpoint to call for the capability's endpoints. |
status |
active, partial, or planned (omitted means active) |
extends |
Parent capability name, if this extends another |
endpoints |
Machine-readable list of HTTP endpoints exposed by this capability. Paths are relative to http.endpoint. Each entry has a method (GET/POST/DELETE/etc.) and a path. The HTTP method signals whether the operation is a read (GET) or a write (POST/DELETE/etc.). Consumers use this to discover catalogue URLs and determine mutability without reading the spec page. |
push |
Optional object declaring which push channels this capability supports (see Push Channel Declaration). |
Push Channel Declaration
The io.bsp.agents.events capability may include a push object declaring which push channels are supported for delivering events to callers. All fields are optional — absence means the channel is not supported.
{
"name": "io.bsp.agents.events",
"version": "{{BSP_VERSION}}",
"endpoints": [
{ "method": "GET", "path": "/events" },
{ "method": "GET", "path": "/events/stream" },
{ "method": "GET", "path": "/events/{schema}/{version}" }
],
"push": {
"sse": true,
"mcp": true,
"a2a": true,
"webhook": true
}
}
| Field | Type | Description |
|---|---|---|
push.sse |
boolean | Server-Sent Events stream supported at GET /events/stream — recommended for browser apps, CLI tools, and locally-running agents that cannot expose a public webhook endpoint |
push.mcp |
boolean | Server-to-client MCP notifications are supported — see MCP transport |
push.a2a |
boolean | A2A message delivery to caller agent is supported — see A2A transport |
push.webhook |
boolean | Webhook callback delivery is supported — callers may register via POST /subscriptions |
Capability Status
Each capability in the manifest may declare a status field:
status |
Meaning | Consumer behaviour |
|---|---|---|
"active" (or omitted) |
Fully implemented | All required endpoints exist and are callable |
"partial" |
Subset implemented | Some required endpoints may be missing or stubbed — consumers must not assume full coverage |
"planned" |
Not yet implemented | No endpoints exist; declared for discovery purposes only |
Implementers should use "partial" when a backing service exists but does not yet cover all required endpoints for a capability, rather than declaring a capability active and returning 404 or 501 on some routes.
An BSP endpoint selectively exposes only the capabilities it supports. Consumers discover what's available by reading the manifest.
Custom and Domain-Specific Capabilities
Implementers can expose capabilities beyond the io.bsp.* set. Custom capabilities must use a reverse-domain prefix that the implementer controls — following the same convention as Java package names and Android intents:
| Convention | Example |
|---|---|
| BSP built-in | io.bsp.agents.registry |
| Organisation-scoped | io.dotquant.trading, com.acme.inventory |
| Team-scoped | com.acme.payments.refunds |
The capability name must be unique. Implementers are responsible for ensuring their prefix does not conflict with others. The io.bsp.* namespace is reserved for the BSP specification.
Services Array — Discovery Hint vs. Live Registry
The optional services array in the manifest is a snapshot at manifest-build time, not the authoritative live list.
services in manifest |
GET /services endpoint |
|
|---|---|---|
| Purpose | Quick discovery hint | Authoritative live list |
| Freshness | May be stale (built at deploy time) | Always current |
| Required | No | Yes, if agents.registry capability is declared |
| Dynamic services | May be absent or partial | Always complete |
For systems where services are created dynamically at runtime (e.g. per-account, per-tenant), the services array should be omitted or contain only representative examples. Consumers that need the real-time list must call GET /services on the HTTP endpoint.
Transport Bindings
Each service declares how it can be reached:
"http": {
"endpoint": "https://your.compliant.BSP.endpoint/"
},
"mcp": {
"transport": "stdio",
"server": "bsp-mcp"
},
"a2a": {
"agent_card_url": "https://your.compliant.BSP.endpoint/.well-known/agent.json"
}
| 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 |
| A2A | Other agents (Google Agent-to-Agent protocol) | HTTP/JSON |
| gRPC | Internal native runtime (optional) | Protocol Buffers |
Multi-Tenant Routing
For multi-tenant SaaS implementations that serve multiple tenants under one host, the standard pattern is to include a {tenantId} segment in every tenant-scoped path:
GET https://api.example.com/{tenantId}/agents
POST https://api.example.com/{tenantId}/events
http.endpoint remains the root consumer-facing URL (e.g. https://api.example.com/). The {tenantId} segment is declared as a path parameter on every tenant-scoped route. Authentication (typically a Bearer API key) identifies the caller; {tenantId} identifies which tenant's surface to target. Both are required on every request.
tenants.manifest — URI Template for Tenant Discovery
What is a tenant?
BSP does not define what a tenant is — that is the implementer's domain model. A tenant ID in BSP is simply an opaque string that scopes a manifest to a particular context. What that context represents depends entirely on the platform:
| Platform model | Tenant maps to |
|---|---|
| B2B SaaS serving multiple companies | The company / customer account |
| Developer platform with per-user isolation | The individual user account |
| Enterprise platform with sub-organisations | The organisation or workspace |
| API gateway serving multiple products | The product or project |
Why use multi-tenancy?
There are two distinct reasons to use tenants.manifest, and they are independent of each other:
| Reason | Description | Example |
|---|---|---|
| Different capability surfaces | Different tenants accept different commands or expose different schemas | Free tier vs. enterprise tier |
| Data scope isolation | All tenants share the same capabilities, but each tenant's data is separate | B2B SaaS where every org has the same commands but their own records |
The second reason is just as valid as the first — and more common in practice. You do not need different capabilities per tenant to benefit from tenants.manifest.
Same schemas
Separate endpoints
Benefits of per-tenant manifests even when capabilities are identical:
Pre-scoped base URL — the tenant manifest's
http.endpointalready contains the tenant context (e.g.https://api.example.com/api/BSP/tenants/acme). A consumer configured with this manifest never needs to inject a tenant ID into requests — it is structurally encoded into every path.Self-contained
dataschemaURIs — every command catalogue entry'sdataschemaURI is fully resolved against the tenant endpoint. No placeholders, no caller-side substitution required.Data isolation is explicit and auditable — tenant scope is visible in the URL on every request, not hidden inside an API key or a request header. This makes it easy to audit, log, and enforce at the infrastructure layer.
Shared team access — when a tenant represents an organisation, all members of that org share one manifest and one API key. No per-user configuration is needed for consumers (agents, MCP clients, bots).
(no tenantId needed)
tenants/acme/commands
When to use multi-tenancy: Use tenants.manifest when different callers operate in isolated data scopes, even if all tenants share the same capability surface. Also use it when different tenants genuinely have different capabilities or schemas.
When to skip it: A single-tenant deployment, a self-hosted service with one user, or any implementation where a single manifest describes the full capability surface and all callers share the same data scope. The tenants.manifest pattern adds a required onboarding step — only use it when per-caller scoping genuinely applies.
To make tenant manifest discovery machine-actionable, the root manifest may declare a tenants block with a manifest URI template (RFC 6570):
"tenants": {
"manifest": "https://api.example.com/.well-known/bsp/{tenantId}"
}
The {tenantId} segment trails the canonical /.well-known/bsp path. This keeps the well-known URL in its standard position and makes the tenant qualifier obvious to any consumer already familiar with the discovery convention.
| Field | Type | Required | Description |
|---|---|---|---|
tenants.manifest |
string (URI template) | yes (if tenants present) |
RFC 6570 URI template. {tenantId} is the only defined variable. Consumers expand this template with a known tenant ID to obtain a fully-resolved, self-contained manifest. |
Rules:
{tenantId}is the only permitted variable in the template. No other template variables are defined by BSP.- A consumer expands the template and fetches the resulting URL. That URL returns a fully self-contained manifest with no placeholders.
- The root manifest's
capabilitiesarray must contain only capabilities the root can fulfill directly. Tenant-scoped capabilities (e.g.io.bsp.agents.commands,io.bsp.agents.events) must be omitted from the root manifest — they appear only in the tenant manifest. - The tenant manifest does not include a
tenantsblock itself — it is already fully scoped. - The
tenants.manifesttemplate is distinct fromdataschema. URI templates are only valid intenants.manifest; everywhere else in the manifest URIs must be fully resolved.
For how {tenantId} maps to path parameters in the HTTP transport, see Multi-Tenant Routing in the HTTP transport spec.
Root manifest (multi-tenant host):
{
"BSP": {
"version": "{{BSP_VERSION}}",
"tenants": {
"manifest": "https://api.example.com/.well-known/bsp/{tenantId}"
},
"services": {
"io.bsp.agents": {
"http": { "endpoint": "https://api.example.com/" }
}
},
"capabilities": [
{
"name": "io.bsp.agents.registry",
"endpoints": [
{ "method": "GET", "path": "/services" },
{ "method": "POST", "path": "/services" },
{ "method": "GET", "path": "/services/{id}" },
{ "method": "DELETE", "path": "/services/{id}" }
]
}
]
}
}
Tenant manifest (returned at the expanded URI):
{
"BSP": {
"version": "{{BSP_VERSION}}",
"services": {
"io.dotquant.trading": {
"http": {
"endpoint": "https://api.example.com/api/BSP/tenants/be9e0176"
}
}
},
"capabilities": [
{
"name": "io.bsp.agents.commands",
"service": "io.dotquant.trading",
"endpoints": [
{ "method": "GET", "path": "/commands" },
{ "method": "POST", "path": "/commands" },
{ "method": "GET", "path": "/commands/{schema}/{version}" }
]
}
]
}
}
dataschemaURIs must be fully resolvable. Thedataschemafield in a command catalogue entry is a URI that a consumer dereferences directly. It must not contain placeholder segments (e.g.{tenantId}) that require caller-side substitution — BSP defines no URI templating convention. For multi-tenant implementations where command schemas are tenant-scoped, serve a distinct/.well-known/bspmanifest per tenant — via subdomain (https://{tenantId}.api.example.com/.well-known/bsp) or the canonical trailing-segment pattern (https://api.example.com/.well-known/bsp/{tenantId}) — so that every manifest contains fully-resolveddataschemaURIs. Tenant context is established at the manifest level, not inside nested URI values.
See HTTP transport for the full multi-tenant routing reference.
Agent Navigation Guide
This section describes the algorithm an AI agent or automated client should follow when interacting with an BSP endpoint for the first time. This is the canonical discovery flow — not just for web UIs.
Step 1 — Fetch the root manifest
GET /.well-known/bsp
This endpoint is always public — no credentials required. It returns the manifest JSON. An implementation that requires auth on /.well-known/bsp is non-conformant.
From the root manifest, extract two things before doing anything else:
BSP.authentication— what credentials are required for all other callsBSP.tenants.manifest— whether this is a multi-tenant host
Step 2 — Identify the manifest type and collect prerequisites
| What the root manifest shows | What it means | What to collect from the user |
|---|---|---|
capabilities contains io.bsp.agents.commands |
Direct service — commands discoverable here | Credentials only (if authentication is declared) |
tenants.manifest present, no io.bsp.agents.commands |
Multi-tenant router — must fetch tenant manifest first | Both credentials and tenant ID |
| Neither | Service has no command surface | Nothing — report to user |
Collect everything before making any authenticated request. When the root manifest declares authentication AND requires a tenant ID, ask the user for both in a single prompt. Do not make a round-trip to the tenant manifest endpoint without credentials — you already know from the root manifest that auth is required.
Step 3 — Resolve the tenant manifest (multi-tenant only)
Once you have both credentials and the tenant ID:
- Expand the URI template: replace
{tenantId}intenants.manifestwith the tenant ID the user provided. - Fetch the expanded URL, including any required credentials declared in
BSP.authentication. - The tenant manifest is a self-contained, fully-resolved manifest. Treat it exactly as you would a direct service manifest from this point on.
Template: https://api.example.com/.well-known/bsp/{tenantId}
Tenant ID: acme
Resolved: GET https://api.example.com/.well-known/bsp/acme
X-Api-Key: <user's key>
Tenant manifest auth: Only the API key credential is required to fetch a tenant manifest — the tenant ID is already in the URL. Implementations MUST NOT require a tenant ID header to access
/.well-known/bsp/{tenantId}; the path parameter already carries it.
Step 4 — Read the capability endpoints
Find the io.bsp.agents.commands capability in BSP.capabilities. Its endpoints array lists the available HTTP operations:
{
"name": "io.bsp.agents.commands",
"service": "io.example.myservice",
"endpoints": [
{ "method": "GET", "path": "/commands" },
{ "method": "POST", "path": "/commands" }
]
}
Resolve each path against the http.endpoint of the service named in service (or the default io.bsp.agents service if service is absent).
Step 5 — Fetch the command catalogue
GET {http.endpoint}/commands
X-Api-Key: <credentials>
This returns the list of command types the service accepts, each with a schema, version, dataschema URI, and optional description. This is the definitive answer to "what commands are available."
Quick reference: signals and actions
| What you see in the manifest | What to collect | What to do |
|---|---|---|
capabilities has io.bsp.agents.commands |
Credentials only | Fetch GET /commands |
tenants.manifest present, no commands capability |
Credentials + tenant ID together | Expand template, fetch tenant manifest, then proceed |
capabilities has io.bsp.agents.commands with status: "planned" |
— | Commands not yet implemented — report to user |
capabilities is empty and no tenants.manifest |
— | No discoverable capabilities — report to user |
Do not fall back to external OpenAPI/Swagger documents. The BSP manifest is the canonical discovery surface. Any Swagger or OpenAPI URL in an implementer's response describes their application API — it is not the BSP command catalogue and may expose internal, non-BSP endpoints.
http Transport Fields
| Field | Description |
|---|---|
http.endpoint |
Consumer-facing base URL. Must be publicly reachable by the consumer — never an internal backend address or private service-mesh URL. All HTTP API paths are appended to this value. |
The http.endpoint value is the consumer-facing base URL. All HTTP API paths are appended to it. For example, if http.endpoint is https://app.agenthost.example/, then the services registry is at https://app.agenthost.example/services.
http.endpointmust be a publicly reachable consumer address — never an internal backend or private service URL. If the implementation sits behind a proxy or API gateway,http.endpointis the outermost address consumers hit.
Multiple transports describe the same capability surface. When both
httpandmcp(ora2a) are declared, they each provide access to the same logical capabilities — they are alternative access methods, not separate operation sets. Consumers choose one transport; they do not infer separate capabilities from the presence of multiple transports.
Schema
See discovery.json for the full JSON Schema.
Full Example
See well-known-BSP.json for a complete manifest example.