{
  "openapi": "3.1.0",
  "info": {
    "title": "VoltDeck Site API",
    "version": "1.1.0",
    "description": "Read-only information about VoltDeck, the iPad dashboard for DIY electric vehicles.\n\n**Authentication:** none. All endpoints are public and read-only, need no authentication and expose no user data.\n\n**Rate limits:** 60 requests per minute per IP on /api/* and /mcp. Every response there carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers. Over the limit the server returns 429 with Retry-After and a JSON Error body.\n\n**Safe retries:** every operation is a safe read, so retrying is always safe. An optional Idempotency-Key header is accepted and echoed back.\n\n**Errors:** JSON objects of the form {\"error\": \"<code>\", \"message\": \"<text>\"}.\n\n**MCP:** a read-only MCP server (Streamable HTTP) is at https://getvoltdeck.com/volt/mcp; manifest at /.well-known/mcp.json.",
    "contact": {
      "name": "VoltDeck Support",
      "email": "support@getvoltdeck.com",
      "url": "https://getvoltdeck.com/contact"
    },
    "termsOfService": "https://getvoltdeck.com/terms"
  },
  "servers": [
    {
      "url": "https://getvoltdeck.com",
      "description": "Production"
    }
  ],
  "security": [],
  "externalDocs": {
    "description": "llms.txt",
    "url": "https://getvoltdeck.com/llms.txt"
  },
  "paths": {
    "/api/app-info": {
      "get": {
        "operationId": "getAppInfo",
        "summary": "Get VoltDeck app information",
        "description": "Returns the app's name, platforms, requirements, features, pricing, privacy summary, support email and page URLs.",
        "security": [],
        "responses": {
          "200": {
            "description": "App information",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Idempotency-Key": {
                "$ref": "#/components/headers/Idempotency-Key"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppInfo"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Health check",
        "description": "Returns {\"ok\": true} when the site is up. Not rate limited.",
        "security": [],
        "responses": {
          "200": {
            "description": "The site is up",
            "headers": {},
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/.well-known/mcp.json": {
      "get": {
        "operationId": "getMcpManifest",
        "summary": "MCP server manifest",
        "description": "Describes the MCP server: endpoint, transport, authentication (none) and tools. Also served at /.well-known/mcp/server-card.json.",
        "security": [],
        "responses": {
          "200": {
            "description": "MCP manifest",
            "headers": {},
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/mcp": {
      "servers": [
        {
          "url": "https://getvoltdeck.com/volt",
          "description": "MCP endpoint (POST is not redirected)"
        }
      ],
      "post": {
        "operationId": "mcp",
        "summary": "MCP Streamable HTTP endpoint (JSON-RPC 2.0)",
        "description": "Stateless, read-only MCP server. Send one JSON-RPC 2.0 message per request. Supported methods: initialize, notifications/initialized, ping, tools/list, tools/call. Tools: get_app_info, get_pricing, get_support_faq. Requests get a JSON response; notifications get 202 with no body.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "name": "MCP-Protocol-Version",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "example": "2025-06-18"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              },
              "example": {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "tools/call",
                "params": {
                  "name": "get_pricing",
                  "arguments": {}
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response (result or error)",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "Idempotency-Key": {
                "$ref": "#/components/headers/Idempotency-Key"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "202": {
            "description": "Notification accepted (no body)",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            }
          },
          "400": {
            "description": "Invalid JSON or JSON-RPC message",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "get": {
        "operationId": "mcpGet",
        "summary": "Not supported",
        "security": [],
        "description": "The server does not offer an SSE stream. Returns 405 with an Allow header.",
        "responses": {
          "405": {
            "description": "Method not allowed",
            "headers": {
              "Allow": {
                "schema": {
                  "type": "string",
                  "example": "POST, OPTIONS"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Optional. 1-255 visible ASCII characters. Echoed back in the response. All operations are safe reads, so retries never change anything.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 255
        }
      }
    },
    "headers": {
      "RateLimit-Limit": {
        "description": "Requests allowed per window (60 per minute per IP).",
        "schema": {
          "type": "integer",
          "example": 60
        }
      },
      "RateLimit-Remaining": {
        "description": "Requests left in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the current window resets.",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      },
      "Idempotency-Key": {
        "description": "Echo of the Idempotency-Key request header, if one was sent.",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Bad request (for example an invalid Idempotency-Key)",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimit-Limit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimit-Remaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimit-Reset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "invalid_idempotency_key",
              "message": "Idempotency-Key must be 1-255 visible ASCII characters."
            }
          }
        }
      },
      "NotFound": {
        "description": "No such endpoint",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "not_found",
              "message": "No API endpoint at GET /api/nope."
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit exceeded",
        "headers": {
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimit-Limit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimit-Remaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimit-Reset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "rate_limited",
              "message": "Too many requests. Limit is 60 per minute per IP. Retry after 30 seconds."
            }
          }
        }
      },
      "ServerError": {
        "description": "Unexpected server error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "internal_error",
              "message": "Something went wrong. Try again later."
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable error code"
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation"
          }
        },
        "required": [
          "error",
          "message"
        ]
      },
      "Plan": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "price": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "billingPeriod": {
            "type": "string",
            "description": "ISO 8601 duration, e.g. P1M or P1Y"
          },
          "freeTrialDays": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "name",
          "price",
          "currency",
          "billingPeriod"
        ]
      },
      "AppInfo": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "tagline": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "platforms": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "requirements": {
            "type": "object",
            "properties": {
              "os": {
                "type": "string"
              },
              "hardware": {
                "type": "string"
              },
              "simulator": {
                "type": "string"
              }
            }
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "pricing": {
            "type": "object",
            "properties": {
              "product": {
                "type": "string"
              },
              "billing": {
                "type": "string"
              },
              "plans": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Plan"
                }
              }
            }
          },
          "privacy": {
            "type": "string"
          },
          "supportEmail": {
            "type": "string",
            "format": "email"
          },
          "areaServed": {
            "type": "string"
          },
          "pages": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            }
          },
          "machineReadable": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            }
          },
          "appStoreUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "null until the app is released"
          }
        },
        "required": [
          "name",
          "platforms",
          "pricing",
          "supportEmail",
          "pages"
        ]
      },
      "JsonRpcRequest": {
        "type": "object",
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer"
            ]
          },
          "method": {
            "type": "string",
            "enum": [
              "initialize",
              "notifications/initialized",
              "ping",
              "tools/list",
              "tools/call"
            ]
          },
          "params": {
            "type": "object"
          }
        },
        "required": [
          "jsonrpc",
          "method"
        ]
      },
      "JsonRpcResponse": {
        "type": "object",
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer",
              "null"
            ]
          },
          "result": {
            "type": "object"
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "integer"
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "jsonrpc",
          "id"
        ]
      }
    }
  }
}
