{
  "openapi": "3.1.0",
  "info": {
    "title": "Setoku Gateway API",
    "version": "0.21.0",
    "summary": "The HTTP surface of a self-hosted Setoku MCP knowledge server.",
    "description": "Setoku is an open-source, self-hosted MCP server that gives an AI agent a read-only, audited view of a company's data plus the curated context needed to use it correctly.\n\nEvery Setoku box exposes this API. There is no multi-tenant setoku.com API: the product is single-tenant by design and runs on infrastructure you own, so point `host` at your own deployment (or at the public demo box).\n\nThe primary interface is MCP over Streamable HTTP at `/mcp`, which carries 19 tools (see https://setoku.com/api/tools.json). The REST endpoints below are the credential-free surface: box health, and published apps.",
    "license": {
      "name": "Apache-2.0",
      "identifier": "Apache-2.0"
    },
    "contact": {
      "name": "Setoku",
      "url": "https://setoku.com/docs",
      "email": "hello@setoku.com"
    }
  },
  "externalDocs": {
    "description": "Setoku API reference",
    "url": "https://setoku.com/docs"
  },
  "servers": [
    {
      "url": "https://{host}",
      "description": "Your Setoku box",
      "variables": {
        "host": {
          "default": "demo.setoku.com",
          "description": "Hostname of your Setoku deployment"
        }
      }
    },
    {
      "url": "https://demo.setoku.com",
      "description": "Public demo box (synthetic sports-club dataset)"
    }
  ],
  "tags": [
    {
      "name": "MCP",
      "description": "The Model Context Protocol endpoint — the main interface."
    },
    {
      "name": "Health",
      "description": "Liveness and dependency status."
    },
    {
      "name": "Apps",
      "description": "Published apps: live, read-only views built by an agent."
    },
    {
      "name": "Install",
      "description": "One-line installer."
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "A per-person connector token. Send it as `Authorization: Bearer <token>`, or as a path segment (`/mcp/<token>`) for MCP clients whose connector dialog has no header field. The token identifies one person and is revocable from the box's web console. Treat the whole URL as a credential, like a database connection string.\n\nOAuth 2.0 is scoped but deliberately not shipped; see https://setoku.com/docs#auth."
      }
    },
    "schemas": {
      "Health": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "version": {
            "type": "string",
            "examples": [
              "0.21.0"
            ]
          },
          "docs": {
            "type": "integer",
            "description": "Curated knowledge documents in the store."
          },
          "disk": {
            "type": "object",
            "properties": {
              "dir": {
                "type": "string"
              },
              "used_pct": {
                "type": "integer"
              }
            }
          },
          "deps": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "ok": {
                  "type": "boolean"
                },
                "ms": {
                  "type": "integer"
                },
                "error": {
                  "type": "string"
                }
              }
            }
          },
          "mirror": {
            "type": "object",
            "description": "Business-DB mirror freshness. Informational; never flips `ok`.",
            "properties": {
              "asOf": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "tables": {
                "type": "integer"
              }
            }
          }
        },
        "required": [
          "ok",
          "version"
        ]
      },
      "AppData": {
        "type": "object",
        "description": "Freshness metadata for a published app. Deliberately exposes no calculations: no SQL, no metric definitions, no schema.",
        "properties": {
          "title": {
            "type": "string"
          },
          "refreshSeconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "mirrorAsOf": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "AppState": {
        "type": "object",
        "description": "An app's own key/value store. Sandboxed to the app: it can never reach a business source.",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "entries": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "error": {
            "type": "string"
          }
        }
      }
    }
  },
  "paths": {
    "/mcp": {
      "post": {
        "tags": [
          "MCP"
        ],
        "operationId": "mcp",
        "summary": "Model Context Protocol endpoint (Streamable HTTP)",
        "description": "Speaks MCP over Streamable HTTP. Point any MCP client at this URL. The server is stateless per request: identity is bound from the bearer token, and the tools that get registered depend on that identity's role.\n\nAn analyst token may read the data lake but holds no tool that commits knowledge. A curator token may commit knowledge but cannot read the lake. The two never coexist on one session, so a prompt-injected session cannot weaponize the write path.",
        "security": [
          {
            "bearerToken": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "A JSON-RPC 2.0 message, per the MCP specification.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "JSON-RPC 2.0 request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response, or an SSE stream when the client accepts text/event-stream.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown, or revoked token."
          }
        }
      }
    },
    "/mcp/{token}": {
      "post": {
        "tags": [
          "MCP"
        ],
        "operationId": "mcpTokenInPath",
        "summary": "MCP endpoint with the token in the path",
        "description": "Identical to `POST /mcp`, but authenticated by a path segment. This exists because some MCP connector dialogs accept only a URL, with nowhere to put a header.",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Connector token."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response."
          },
          "401": {
            "description": "Unknown or revoked token."
          }
        }
      }
    },
    "/healthz": {
      "get": {
        "tags": [
          "Health"
        ],
        "operationId": "healthz",
        "summary": "Aggregate health",
        "description": "Knowledge-store size, data-disk usage, dependency pings, and mirror freshness. Credential-free, and cached about 5 seconds so polling it amplifies nothing. Returns 503 when a dependency fails or the data disk is at least 90% full.",
        "security": [],
        "responses": {
          "200": {
            "description": "Healthy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          },
          "503": {
            "description": "A dependency is down, or the data disk is nearly full.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Health"
        ],
        "operationId": "health",
        "summary": "Liveness probe",
        "description": "Cheap liveness check used by the reverse proxy during deploys.",
        "security": [],
        "responses": {
          "200": {
            "description": "The gateway is listening."
          }
        }
      }
    },
    "/p/{id}": {
      "get": {
        "tags": [
          "Apps"
        ],
        "operationId": "getApp",
        "summary": "A published app",
        "description": "The shell page for an app an agent built and published. Reachable without credentials only for apps an admin has explicitly made public; team-visibility and archived apps return 404 here.",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The app shell.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No such public app."
          }
        }
      }
    },
    "/p/{id}/data": {
      "get": {
        "tags": [
          "Apps"
        ],
        "operationId": "getAppData",
        "summary": "Freshness metadata for a published app",
        "description": "Returns when the app's panels were last computed. Reads cached values; it never re-runs a query, so this credential-free poll cannot drive load onto your data.",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Freshness metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppData"
                }
              }
            }
          },
          "404": {
            "description": "No such public app, or the app has no data panels."
          }
        }
      }
    },
    "/p/{id}/state": {
      "get": {
        "tags": [
          "Apps"
        ],
        "operationId": "getAppState",
        "summary": "Read an app's sandboxed state",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "app",
                "viewer"
              ],
              "default": "app"
            },
            "description": "`app` is shared by everyone with the link; `viewer` is keyed to an anonymous per-browser id."
          },
          {
            "name": "owner",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Anonymous viewer id. Required when scope=viewer."
          }
        ],
        "responses": {
          "200": {
            "description": "The app's entries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppState"
                }
              }
            }
          },
          "400": {
            "description": "Bad scope."
          },
          "404": {
            "description": "No such public app."
          }
        }
      },
      "post": {
        "tags": [
          "Apps"
        ],
        "operationId": "setAppState",
        "summary": "Write an app's sandboxed state",
        "description": "Sets or deletes one key. This store belongs to the app, not to your database: an app can only ever mess up its own notes, and it stays inside a size quota.",
        "security": [],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "app",
                "viewer"
              ],
              "default": "app"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "op": {
                    "type": "string",
                    "enum": [
                      "set",
                      "delete"
                    ]
                  },
                  "key": {
                    "type": "string"
                  },
                  "value": {},
                  "owner": {
                    "type": "string",
                    "description": "Anonymous viewer id. Required when scope=viewer."
                  }
                },
                "required": [
                  "op",
                  "key"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Applied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppState"
                }
              }
            }
          },
          "400": {
            "description": "Bad scope or operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such public app."
          },
          "413": {
            "description": "The app's storage quota is full.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/i/{token}": {
      "get": {
        "tags": [
          "Install"
        ],
        "operationId": "installer",
        "summary": "One-line installer script",
        "description": "Returns a shell script that configures an MCP client for this box, personalized to the token. Intended for `curl -fsSL https://<host>/i/<token> | sh`.",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A shell script.",
            "content": {
              "text/x-shellscript": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Unknown token."
          }
        }
      }
    }
  }
}
