
  "openapi": "3.1.0",
  "info": {
    "title": "BSP Agent Service",
    "description": "HTTP API for the io.bsp.agents service â€” service registry, command ingestion, command discovery, event log, and service memory.",
    "version": "0.5.9",
    "license": {
      "name": "Apache-2.0",
      "url": "https://www.apache.org/licenses/LICENSE-2.0"
    }
  },
  "servers": [
    {
      "url": "{endpoint}",
      "description": "BSP-compliant endpoint base URL (from the http.endpoint field in the /.well-known/bsp manifest). All API paths below are appended to this base URL. For example, if http.endpoint is https://app.example.com/BSP/, then GET /services resolves to https://app.example.com/BSP/services.",
      "variables": {
        "endpoint": {
          "default": "https://your.compliant.BSP.endpoint"
        }
      }
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/.well-known/bsp": {
      "get": {
        "operationId": "getDiscoveryManifest",
        "summary": "Discovery manifest",
        "description": "Returns the BSP discovery manifest describing available services, capabilities, and transport bindings.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "Discovery manifest",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/discovery.json"
                }
              }
            }
          }
        }
      }
    },
    "/services": {
      "get": {
        "operationId": "listServices",
        "summary": "List all registered services",
        "tags": [
          "Service Registry"
        ],
        "responses": {
          "200": {
            "description": "List of service descriptors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/agents/registry.json#/$defs/serviceList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "registerService",
        "summary": "Register or update a service (upsert)",
        "description": "Creates the service if it does not exist, or fully replaces its descriptor if it does. Re-registering an existing service does not remove its subscriptions — only DELETE /services/{id} does that.",
        "tags": [
          "Service Registry"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "../../schemas/agents/registry.json#/$defs/serviceRegistration"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Service registered or updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/agents/registry.json#/$defs/serviceDescriptor"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/error.json"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/services/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Service identifier"
        }
      ],
      "get": {
        "operationId": "getService",
        "summary": "Get service detail",
        "tags": [
          "Service Registry"
        ],
        "responses": {
          "200": {
            "description": "Service descriptor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/agents/registry.json#/$defs/serviceDescriptor"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Service not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/error.json"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeService",
        "summary": "Remove a service",
        "tags": [
          "Service Registry"
        ],
        "responses": {
          "204": {
            "description": "Service removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Service not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/error.json"
                }
              }
            }
          }
        }
      }
    },
    "/services/{id}/pause": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Service identifier"
        }
      ],
      "post": {
        "operationId": "pauseService",
        "summary": "Pause a running service",
        "tags": [
          "Service Lifecycle"
        ],
        "responses": {
          "204": {
            "description": "Service paused"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Service not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/error.json"
                }
              }
            }
          }
        }
      }
    },
    "/services/{id}/heartbeat": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Service identifier"
        }
      ],
      "post": {
        "operationId": "heartbeatService",
        "summary": "Signal that a service is still alive",
        "description": "Agents must call this endpoint periodically to confirm they are still running. Servers use the absence of heartbeats to detect stale registrations and transition the service status to 'error'. The required heartbeat interval is server-defined and should be documented by the implementation.",
        "tags": [
          "Service Lifecycle"
        ],
        "responses": {
          "204": {
            "description": "Heartbeat recorded"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Service not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/error.json"
                }
              }
            }
          }
        }
      }
    },
    "/services/{id}/resume": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Service identifier"
        }
      ],
      "post": {
        "operationId": "resumeService",
        "summary": "Resume a paused service",
        "tags": [
          "Service Lifecycle"
        ],
        "responses": {
          "204": {
            "description": "Service resumed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Service not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/error.json"
                }
              }
            }
          }
        }
      }
    },
    "/subscriptions": {
      "post": {
        "operationId": "createSubscription",
        "summary": "Register a webhook subscription for push event delivery",
        "description": "Registers a webhook callback URL to receive events as they are published. An optional serviceId links the subscription to a registered service — when that service is deleted via DELETE /services/{id}, this subscription is automatically removed.",
        "tags": [
          "Events"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "../../schemas/agents/events.json#/$defs/subscriptionRegistration"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subscription created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/agents/events.json#/$defs/subscriptionDescriptor"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/error.json"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/subscriptions/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Subscription identifier"
        }
      ],
      "delete": {
        "operationId": "deleteSubscription",
        "summary": "Remove a webhook subscription",
        "tags": [
          "Events"
        ],
        "responses": {
          "204": {
            "description": "Subscription removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Subscription not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/error.json"
                }
              }
            }
          }
        }
      }
    },
    "/commands": {
      "get": {
        "operationId": "listCommandCatalogue",
        "summary": "List available command types",
        "description": "Returns the catalogue of command types this service accepts, each with its dataschema URI. This is the discovery surface: callers (Process Managers, AI agents, UIs) call this to learn what commands they can POST and what schema each command's data must conform to.",
        "tags": [
          "Commands"
        ],
        "responses": {
          "200": {
            "description": "Command catalogue",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/agents/commands.json#/$defs/commandCatalogue"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "sendCommand",
        "summary": "Send a command to the service",
        "description": "Single CloudEvent ingestion entry point. The runtime validates the data property against the JSON Schema at the dataschema URI (hosted by this API), then queues the command for processing. Returns 201 on success.",
        "tags": [
          "Commands"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "../../schemas/agents/commands.json#/$defs/command"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Command accepted and queued for processing"
          },
          "400": {
            "description": "Invalid command â€” schema validation failed or missing required CloudEvent attributes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/error.json"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/events": {
      "get": {
        "operationId": "listEvents",
        "summary": "Query published domain events",
        "description": "Returns the queryable log of domain events published by this service. Supports filtering by event type, correlation ID, source, and time range, plus cursor-based pagination. All parameters are optional and combinable.",
        "tags": [
          "Events"
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Filter by CloudEvent type (PascalCase, e.g. ChatKitMessageRememberedV1). Returns all events of this type across all interactions."
          },
          {
            "name": "correlationId",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Filter by correlation identifier — the command id returned by POST /commands. Returns only events produced in response to that specific command submission."
          },
          {
            "name": "source",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Filter by event source — the string identifying the service that published the event (matches the CloudEvent source field)."
          },
          {
            "name": "from",
            "in": "query",
            "schema": { "type": "string", "format": "date-time" },
            "description": "Return only events published at or after this ISO 8601 timestamp (e.g. 2025-07-01T00:00:00Z)."
          },
          {
            "name": "to",
            "in": "query",
            "schema": { "type": "string", "format": "date-time" },
            "description": "Return only events published at or before this ISO 8601 timestamp."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "minimum": 1 },
            "description": "Maximum number of events to return. Server may apply a lower ceiling. Defaults to a server-defined value."
          },
          {
            "name": "after",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Pagination cursor. Pass the nextCursor value from a previous response to retrieve the next page. Opaque — do not construct manually."
          }
        ],
        "responses": {
          "200": {
            "description": "List of published domain events, with optional pagination cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/agents/events.json#/$defs/eventList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/commands/{schema}/{version}": {
      "parameters": [
        {
          "name": "schema",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Schema name in kebab-case, matching the 'schema' field of the catalogue entry (e.g. propose-counter, submit-order)"
        },
        {
          "name": "version",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "Schema version string (e.g. 1.0, 2.1)"
        }
      ],
      "get": {
        "operationId": "getSchema",
        "summary": "Get a versioned command schema",
        "description": "Returns the JSON Schema document for a specific command type and version. The full URL of this endpoint is the canonical value for the dataschema URI in a command catalogue entry.",
        "tags": [
          "Commands"
        ],
        "responses": {
          "200": {
            "description": "JSON Schema document",
            "content": {
              "application/schema+json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Schema or version not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "../../schemas/error.json"
                }
              }
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Discovery",
      "description": "Protocol discovery"
    },
    {
      "name": "Service Registry",
      "description": "io.bsp.agents.registry â€” Register, remove, list, get services"
    },
    {
      "name": "Service Lifecycle",
      "description": "io.bsp.agents.lifecycle â€” Pause and resume services"
    },
    {
      "name": "Commands",
      "description": "io.bsp.agents.commands — Discover command types (GET /commands), fetch versioned schemas (GET /commands/{schema}/{version}), and send commands (POST /commands)"
    },
    {
      "name": "Events",
      "description": "io.bsp.agents.events — List and query published domain events"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Bearer token authentication. Include the token in the Authorization header: Authorization: Bearer <token>. Required when the discovery manifest's authentication.type is 'bearer'. Omit if authentication.type is 'none'."
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key passed as a request header. Used when authentication.type is 'apiKey' and authentication.in is 'header'."
      },
      "apiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "api_key",
        "description": "API key passed as a query parameter. Used when authentication.type is 'apiKey' and authentication.in is 'query'."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Authentication required or credentials invalid. The endpoint requires authentication as declared in the discovery manifest's authentication block.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "../../schemas/error.json"
            },
            "example": {
              "error": {
                "code": "UNAUTHORIZED",
                "message": "Bearer token is missing or invalid",
                "details": {}
              }
            }
          }
        }
      }
    }
  }
}

