{
  "openapi": "3.1.0",
  "info": {
    "title": "RFP.ai REST API",
    "version": "2026-05-04",
    "summary": "Programmatic access to RFP.ai projects, answer generation, files, exports, and the MCP write surface.",
    "description": "The RFP.ai REST API lets paid-plan customers manage RFP projects, generate source-backed draft answers, list project files, and export response work. The same Bearer tokens drive the MCP JSON-RPC endpoint at `/mcp`, where token **scopes** gate write tools (`projects.write`, `qa.write`, `qa.approve`, `kb.write`). All endpoints use HTTPS. Tokens default to read-only; write scopes are granted per token in **Settings → API**."
  },
  "servers": [
    {
      "url": "https://rfp.ai/api",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Projects",
      "description": "Create, list, and retrieve RFP projects."
    },
    {
      "name": "Answers",
      "description": "Generate source-backed draft answers from project knowledge."
    },
    {
      "name": "Files",
      "description": "List files attached to a project."
    },
    {
      "name": "Exports",
      "description": "Export response content in common buyer formats."
    },
    {
      "name": "MCP",
      "description": "JSON-RPC endpoint for Model Context Protocol clients (Claude Desktop, Cursor, custom integrations). Exposes 18 tools (9 read + 9 write) discoverable via `tools/list`. Write tools require an explicit token scope and are recorded in the per-org MCP audit log. See https://rfp.ai/agents and https://rfp.ai/docs/claude-desktop-mcp-integration for the full tool catalogue."
    }
  ],
  "paths": {
    "/projects": {
      "get": {
        "tags": ["Projects"],
        "summary": "List projects",
        "description": "Returns projects visible to the authenticated user's organization.",
        "operationId": "listProjects",
        "responses": {
          "200": {
            "description": "Projects returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Project"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "tags": ["Projects"],
        "summary": "Create a project",
        "description": "Creates a new RFP project in the authenticated user's organization.",
        "operationId": "createProject",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProjectRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Project created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/projects/{projectId}": {
      "get": {
        "tags": ["Projects"],
        "summary": "Get a project",
        "description": "Returns project details for a project in the authenticated user's organization.",
        "operationId": "getProject",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProjectId"
          }
        ],
        "responses": {
          "200": {
            "description": "Project returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/projects/{projectId}/files": {
      "get": {
        "tags": ["Files"],
        "summary": "List project files",
        "description": "Returns file metadata for documents attached to a project.",
        "operationId": "listProjectFiles",
        "parameters": [
          {
            "$ref": "#/components/parameters/ProjectId"
          }
        ],
        "responses": {
          "200": {
            "description": "Files returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/File"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/ask": {
      "post": {
        "tags": ["Answers"],
        "summary": "Generate an answer",
        "description": "Generates a draft answer grounded in the project's knowledge base. Responses include citations and trust signals when evidence is available.",
        "operationId": "generateAnswer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AskRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Answer generated or safely abstained.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/ask-batch": {
      "post": {
        "tags": ["Answers"],
        "summary": "Generate answers for multiple questions",
        "description": "Processes several questions against the same project. Mixed success and failure results may return HTTP 207.",
        "operationId": "generateAnswersBatch",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AskBatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "All questions processed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskBatchResponse"
                }
              }
            }
          },
          "207": {
            "description": "Some questions failed while others completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AskBatchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/export/docx": {
      "post": {
        "tags": ["Exports"],
        "summary": "Export responses as DOCX",
        "operationId": "exportDocx",
        "requestBody": {
          "$ref": "#/components/requestBodies/ProjectExportRequest"
        },
        "responses": {
          "200": {
            "description": "DOCX export returned.",
            "content": {
              "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/export/pdf": {
      "post": {
        "tags": ["Exports"],
        "summary": "Export responses as PDF",
        "operationId": "exportPdf",
        "requestBody": {
          "$ref": "#/components/requestBodies/ProjectExportRequest"
        },
        "responses": {
          "200": {
            "description": "PDF export returned.",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/export/xlsx-filled": {
      "post": {
        "tags": ["Exports"],
        "summary": "Fill the source XLSX template with answers",
        "operationId": "exportFilledXlsx",
        "requestBody": {
          "$ref": "#/components/requestBodies/ProjectFileExportRequest"
        },
        "responses": {
          "200": {
            "description": "Filled XLSX export returned.",
            "content": {
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "tags": ["MCP"],
        "summary": "MCP JSON-RPC endpoint",
        "description": "Model Context Protocol endpoint for MCP-compatible clients. Uses JSON-RPC 2.0 envelopes. Tools split into read (no scope required beyond a valid token) and write (require explicit scopes: `projects.write`, `qa.write`, `qa.approve`, `kb.write`). Write tools also enforce per-scope rate sub-caps on top of the plan-wide MCP quota: `mcp-write-approve` is capped at 50/hour and `mcp-write-kb` at 30/hour. Every write call is recorded with a redacted argument summary in the per-org MCP audit log (visible to org owners in Settings → API). Sensitive payload fields (file content, comment bodies, base64 blobs) are never persisted. Forbidden-by-scope errors return JSON-RPC error code `-32001`.",
        "operationId": "callMcp",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JsonRpcRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC success response.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "const": "no-store"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "400": {
            "description": "JSON-RPC client error response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API token."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Internal JSON-RPC error with redacted public details."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API token created in Settings > API Tokens (paid plans). Tokens carry one or more scopes. New tokens default to `[\"read\"]` and can be granted any combination of `read`, `projects.write`, `qa.write`, `qa.approve`, `kb.write`. Scopes are enforced today by the MCP write tools at `/mcp`. REST endpoints in this spec accept any valid token regardless of scope."
      }
    },
    "parameters": {
      "ProjectId": {
        "name": "projectId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Project identifier."
      }
    },
    "requestBodies": {
      "ProjectExportRequest": {
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": ["projectId"],
              "properties": {
                "projectId": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ProjectFileExportRequest": {
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": ["projectId"],
              "properties": {
                "projectId": {
                  "type": "string"
                },
                "fileId": {
                  "type": "string",
                  "description": "Optional source file to fill."
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The request was malformed or failed validation.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Authentication failed or the API token is missing."
      },
      "NotFound": {
        "description": "The requested resource was not found or is not visible to the caller.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Conflict": {
        "description": "The requested action conflicts with the current review/export state.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded. Retry after the indicated interval.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "string"
            }
          }
        }
      }
    },
    "schemas": {
      "CreateProjectRequest": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "deadline": {
            "type": "string",
            "format": "date",
            "description": "Optional buyer deadline."
          }
        }
      },
      "Project": {
        "type": "object",
        "required": ["id", "name"],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "stats": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "File": {
        "type": "object",
        "required": ["id", "filename"],
        "properties": {
          "id": {
            "type": "string"
          },
          "filename": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "bytes": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AskRequest": {
        "type": "object",
        "required": ["projectId", "question"],
        "properties": {
          "projectId": {
            "type": "string"
          },
          "question": {
            "type": "string",
            "minLength": 1
          },
          "wordLimit": {
            "type": "integer",
            "minimum": 25,
            "maximum": 1000
          },
          "citationMode": {
            "type": "string",
            "enum": ["inline", "none"],
            "default": "inline"
          }
        }
      },
      "AskResponse": {
        "type": "object",
        "properties": {
          "answer": {
            "type": "string"
          },
          "sources": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Source"
            }
          },
          "confidence": {
            "type": "number"
          },
          "trustScore": {
            "type": "number"
          },
          "needs_sme": {
            "type": "boolean"
          },
          "error": {
            "type": ["string", "null"]
          }
        }
      },
      "AskBatchRequest": {
        "type": "object",
        "required": ["projectId", "questions"],
        "properties": {
          "projectId": {
            "type": "string"
          },
          "questions": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "string",
              "minLength": 1
            }
          },
          "wordLimit": {
            "type": "integer",
            "minimum": 25,
            "maximum": 1000
          },
          "autoSave": {
            "type": "boolean",
            "default": false
          },
          "citationMode": {
            "type": "string",
            "enum": ["inline", "none"],
            "default": "inline"
          }
        }
      },
      "AskBatchResponse": {
        "type": "object",
        "required": ["results"],
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AskResponse"
            }
          }
        }
      },
      "Source": {
        "type": "object",
        "properties": {
          "fileId": {
            "type": "string"
          },
          "filename": {
            "type": "string"
          },
          "excerpt": {
            "type": "string"
          },
          "citation_number": {
            "type": "string"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "string",
            "description": "Stable public error code or short message."
          },
          "message": {
            "type": "string",
            "description": "Human-readable public message. Internal exception details are not returned."
          }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "required": ["jsonrpc", "method"],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": ["string", "number", "null"]
          },
          "method": {
            "type": "string"
          },
          "params": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "required": ["jsonrpc", "id"],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": ["string", "number", "null"]
          },
          "result": {
            "type": "object",
            "additionalProperties": true
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "integer"
              },
              "message": {
                "type": "string"
              },
              "data": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        }
      }
    }
  }
}
