{
  "openapi": "3.1.0",
  "info": {
    "title": "Spekboom Agent API",
    "version": "1.0.0",
    "summary": "Machine access to the Spekboom regenerative travel platform",
    "description": "Spekboom's public machine interface is a Model Context Protocol (MCP) server: JSON-RPC 2.0 over Streamable HTTP. This document describes the MCP transport endpoints, the OAuth 2.1 authorization endpoints that gate the authenticated tools, and the machine-readable discovery documents. The individual MCP tools (search_stays, plan_trip, check_availability and twelve more) are self-describing: call tools/list on the guest endpoint for their JSON Schemas. Human documentation: https://www.spekboom.org/docs/mcp-connector\n\nVersioning policy: the MCP surface is versioned by protocol date via the MCP-Protocol-Version header (current protocol version 2025-06-18, returned by initialize). The OAuth and .well-known endpoints implement their RFCs (6749, 7591, 8414, 9728) and are stable. Deprecation policy: a breaking change to any endpoint or tool is announced at least 90 days in advance in this document and on https://www.spekboom.org/developers, and the affected endpoint signals it with Deprecation and Sunset response headers for the whole notice period. Tool additions and new optional fields are not breaking.\n\nRate limiting: every MCP response carries draft-RFC RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy headers (120 requests/minute, 1500/day per IP); 429 responses add Retry-After.",
    "contact": {
      "name": "Spekboom support",
      "email": "support@spekboom.org",
      "url": "https://www.spekboom.org/developers"
    },
    "termsOfService": "https://www.spekboom.org/terms-of-service"
  },
  "servers": [
    { "url": "https://mcp.spekboom.org", "description": "MCP endpoints (JSON-RPC 2.0)" },
    { "url": "https://www.spekboom.org", "description": "OAuth and discovery documents" }
  ],
  "paths": {
    "/mcp": {
      "post": {
        "operationId": "callGuestMcp",
        "summary": "Guest MCP endpoint (JSON-RPC 2.0)",
        "description": "The guest connector. Anonymous calls may use the discovery and planning tools (search_stays, plan_trip, find_experiences, find_bird_species, find_species, search_along_route, suggest_itinerary, compare_stays, get_property_details, check_availability, get_availability_alternatives, get_causes_supported, create_booking_link). A bearer token with guest:read unlocks list_my_bookings; guest:write unlocks create_booking_request. Send JSON-RPC methods initialize, tools/list and tools/call. Rate limits: 120 requests/minute and 1500/day per IP; 429 responses carry Retry-After.",
        "servers": [{ "url": "https://mcp.spekboom.org" }],
        "security": [{}, { "spekboomOAuth": ["guest:read", "guest:write"] }],
        "parameters": [{ "$ref": "#/components/parameters/McpProtocolVersion" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcRequest" } }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC envelope: result on success, error object (code, message) on tool or protocol errors.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcResponse" } }
            }
          },
          "400": { "$ref": "#/components/responses/JsonRpcError" },
          "401": { "$ref": "#/components/responses/JsonRpcError" },
          "403": { "$ref": "#/components/responses/JsonRpcError" },
          "413": { "$ref": "#/components/responses/JsonRpcError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/JsonRpcError" }
        }
      }
    },
    "/host": {
      "post": {
        "operationId": "callHostMcp",
        "summary": "Host MCP endpoint (JSON-RPC 2.0, OAuth required)",
        "description": "The host connector: listing, calendar, pricing and website management tools for the authenticated host's own properties. Every call requires a bearer token whose grant carries host:read (read tools) or host:write (write tools). An unauthenticated request receives 401 with a WWW-Authenticate header pointing at the RFC 9728 protected resource metadata.",
        "servers": [{ "url": "https://mcp.spekboom.org" }],
        "security": [{ "spekboomOAuth": ["host:read", "host:write"] }],
        "parameters": [{ "$ref": "#/components/parameters/McpProtocolVersion" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcRequest" } }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC envelope: result on success, error object on failure.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcResponse" } }
            }
          },
          "401": { "$ref": "#/components/responses/JsonRpcError" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/docs": {
      "post": {
        "operationId": "callDocsMcp",
        "summary": "Documentation MCP endpoint (JSON-RPC 2.0)",
        "description": "The 'learn' twin of the product MCP server: tools list_docs, get_doc and search_docs serve Spekboom's reference documentation (platform overview, pricing, FAQ, connector reference, developer resources) over the same JSON-RPC 2.0 Streamable HTTP transport agents use to act. No authentication required; the shared per-IP rate limits apply.",
        "servers": [{ "url": "https://mcp.spekboom.org" }],
        "security": [{}],
        "parameters": [{ "$ref": "#/components/parameters/McpProtocolVersion" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcRequest" } }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC envelope: result on success, error object on failure.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcResponse" } }
            }
          },
          "400": { "$ref": "#/components/responses/JsonRpcError" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/oauth/token": {
      "post": {
        "operationId": "exchangeOAuthToken",
        "summary": "OAuth 2.1 token endpoint",
        "description": "Exchanges an authorization code (with PKCE code_verifier) or a refresh token for an access token. Public clients only (token_endpoint_auth_method none, secured by PKCE S256). Access tokens live 1 hour; refresh tokens 90 days.",
        "servers": [{ "url": "https://www.spekboom.org" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": { "$ref": "#/components/schemas/TokenRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token response per RFC 6749 section 5.1, including the granted scope.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/TokenResponse" } }
            }
          },
          "400": {
            "description": "OAuth error response (invalid_grant, invalid_request, ...).",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/OAuthError" } }
            }
          }
        }
      }
    },
    "/api/oauth/register": {
      "post": {
        "operationId": "registerOAuthClient",
        "summary": "Dynamic client registration (RFC 7591)",
        "description": "Registers a public OAuth client and returns its client_id. No client secret is issued; the authorization code flow is secured with PKCE.",
        "servers": [{ "url": "https://www.spekboom.org" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ClientRegistrationRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Registered client metadata including client_id.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ClientRegistrationResponse" }
              }
            }
          },
          "400": {
            "description": "OAuth registration error.",
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/OAuthError" } }
            }
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "operationId": "getAuthorizationServerMetadata",
        "summary": "Authorization server metadata (RFC 8414)",
        "description": "Advertises the authorization, token and registration endpoints, PKCE support and scopes_supported (host:read, host:write, guest:read, guest:write).",
        "servers": [{ "url": "https://www.spekboom.org" }],
        "responses": {
          "200": {
            "description": "RFC 8414 metadata document.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AuthorizationServerMetadata" }
              }
            }
          }
        }
      }
    },
    "/.well-known/oauth-protected-resource": {
      "get": {
        "operationId": "getProtectedResourceMetadata",
        "summary": "Protected resource metadata (RFC 9728)",
        "description": "Names the resource (https://mcp.spekboom.org/host), its authorization server and the supported scopes.",
        "servers": [{ "url": "https://mcp.spekboom.org" }],
        "responses": {
          "200": {
            "description": "RFC 9728 metadata document.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProtectedResourceMetadata" }
              }
            }
          }
        }
      }
    },
    "/.well-known/mcp.json": {
      "get": {
        "operationId": "getMcpManifest",
        "summary": "MCP server manifest",
        "description": "Standard manifest naming the MCP endpoint, transport and capabilities.",
        "servers": [{ "url": "https://www.spekboom.org" }],
        "responses": {
          "200": {
            "description": "MCP manifest document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["name", "endpoint", "transport"],
                  "properties": {
                    "name": { "type": "string", "description": "Server display name" },
                    "description": { "type": "string" },
                    "version": { "type": "string" },
                    "endpoint": {
                      "type": "string",
                      "format": "uri",
                      "description": "The MCP JSON-RPC endpoint"
                    },
                    "transport": {
                      "type": "string",
                      "const": "streamable-http",
                      "description": "MCP transport"
                    },
                    "capabilities": { "type": "object" },
                    "auth": { "type": "object" }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "McpProtocolVersion": {
        "name": "MCP-Protocol-Version",
        "in": "header",
        "required": false,
        "description": "Date-based MCP protocol version negotiation (Model Context Protocol Streamable HTTP transport). The server's current protocol version is 2025-06-18, as returned by the initialize handshake. This header is the versioning mechanism for the MCP surface; see the info.description versioning and deprecation policy.",
        "schema": { "type": "string", "examples": ["2025-06-18"] }
      }
    },
    "securitySchemes": {
      "spekboomOAuth": {
        "type": "oauth2",
        "description": "OAuth 2.1 authorization code with PKCE (S256). Public clients, no secret. Request only the scopes needed: guest scopes act on the signed-in guest's own data, host scopes on the authenticated host's own properties.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://www.spekboom.org/oauth/authorize",
            "tokenUrl": "https://www.spekboom.org/api/oauth/token",
            "refreshUrl": "https://www.spekboom.org/api/oauth/token",
            "scopes": {
              "guest:read": "Read the signed-in guest's own bookings (list_my_bookings)",
              "guest:write": "Create booking requests on the guest's behalf (create_booking_request)",
              "host:read": "Read the host's listings, calendar and bookings (host connector)",
              "host:write": "Edit listings, block dates and manage the host's calendar (host connector)"
            }
          }
        }
      }
    },
    "responses": {
      "JsonRpcError": {
        "description": "Structured JSON error. Transport-level failures (bad JSON, auth, origin, body size) use the matching HTTP status; the body is always a JSON-RPC error envelope with a numeric code and human-readable message.",
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcResponse" } }
        }
      },
      "RateLimited": {
        "description": "Rate limit or daily quota exceeded. Honour the Retry-After header.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": { "type": "integer" }
          }
        },
        "content": {
          "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcResponse" } }
        }
      }
    },
    "schemas": {
      "JsonRpcRequest": {
        "type": "object",
        "description": "JSON-RPC 2.0 request. Methods: initialize, tools/list, tools/call.",
        "required": ["jsonrpc", "method"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": { "oneOf": [{ "type": "string" }, { "type": "integer" }] },
          "method": {
            "type": "string",
            "description": "initialize | tools/list | tools/call",
            "examples": ["tools/call"]
          },
          "params": {
            "type": "object",
            "description": "For tools/call: { name: string, arguments: object }. Tool argument schemas come from tools/list."
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "description": "JSON-RPC 2.0 response envelope: exactly one of result or error.",
        "required": ["jsonrpc"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": { "oneOf": [{ "type": "string" }, { "type": "integer" }, { "type": "null" }] },
          "result": { "type": "object" },
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "integer", "description": "JSON-RPC error code" },
              "message": { "type": "string", "description": "Human-readable error message" }
            }
          }
        }
      },
      "TokenRequest": {
        "type": "object",
        "required": ["grant_type", "client_id"],
        "properties": {
          "grant_type": { "type": "string", "enum": ["authorization_code", "refresh_token"] },
          "code": {
            "type": "string",
            "description": "Authorization code (authorization_code grant)"
          },
          "code_verifier": {
            "type": "string",
            "description": "PKCE verifier (authorization_code grant)"
          },
          "redirect_uri": { "type": "string", "format": "uri" },
          "refresh_token": {
            "type": "string",
            "description": "Refresh token (refresh_token grant)"
          },
          "client_id": { "type": "string" }
        }
      },
      "TokenResponse": {
        "type": "object",
        "required": ["access_token", "token_type", "expires_in"],
        "properties": {
          "access_token": { "type": "string" },
          "token_type": { "type": "string", "const": "Bearer" },
          "expires_in": { "type": "integer", "description": "Seconds until expiry (3600)" },
          "refresh_token": { "type": "string" },
          "scope": {
            "type": "string",
            "description": "Space-separated granted scopes from: host:read host:write guest:read guest:write"
          }
        }
      },
      "OAuthError": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "description": "RFC 6749 error code" },
          "error_description": { "type": "string" }
        }
      },
      "ClientRegistrationRequest": {
        "type": "object",
        "required": ["redirect_uris"],
        "properties": {
          "client_name": { "type": "string" },
          "redirect_uris": {
            "type": "array",
            "items": { "type": "string", "format": "uri" },
            "minItems": 1
          },
          "token_endpoint_auth_method": { "type": "string", "const": "none" }
        }
      },
      "ClientRegistrationResponse": {
        "type": "object",
        "required": ["client_id"],
        "properties": {
          "client_id": { "type": "string" },
          "client_name": { "type": "string" },
          "redirect_uris": { "type": "array", "items": { "type": "string", "format": "uri" } },
          "token_endpoint_auth_method": { "type": "string", "const": "none" }
        }
      },
      "AuthorizationServerMetadata": {
        "type": "object",
        "required": ["issuer", "authorization_endpoint", "token_endpoint", "scopes_supported"],
        "properties": {
          "issuer": { "type": "string", "format": "uri" },
          "authorization_endpoint": { "type": "string", "format": "uri" },
          "token_endpoint": { "type": "string", "format": "uri" },
          "registration_endpoint": { "type": "string", "format": "uri" },
          "response_types_supported": { "type": "array", "items": { "type": "string" } },
          "grant_types_supported": { "type": "array", "items": { "type": "string" } },
          "code_challenge_methods_supported": { "type": "array", "items": { "type": "string" } },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "items": { "type": "string" }
          },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["host:read", "host:write", "guest:read", "guest:write"]
            }
          }
        }
      },
      "ProtectedResourceMetadata": {
        "type": "object",
        "required": ["resource", "authorization_servers"],
        "properties": {
          "resource": { "type": "string", "format": "uri" },
          "authorization_servers": {
            "type": "array",
            "items": { "type": "string", "format": "uri" }
          },
          "bearer_methods_supported": { "type": "array", "items": { "type": "string" } },
          "scopes_supported": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["host:read", "host:write", "guest:read", "guest:write"]
            }
          },
          "resource_documentation": { "type": "string", "format": "uri" }
        }
      }
    }
  }
}
