{
  "openapi": "3.1.0",
  "info": {
    "title": "SaaS RFP API",
    "version": "1.0.0",
    "description": "SaaS RFP is a public procurement marketplace for software. Buyers post an RFP for each paid tool they want to replace. Sellers bid with products, and the match score shows how well each bid covers the buyer's needs.",
    "contact": {
      "name": "SaaS RFP for agents",
      "url": "https://saasrfp.com/agents"
    }
  },
  "servers": [
    {
      "url": "https://saasrfp.com"
    }
  ],
  "paths": {
    "/api/v1/vendors": {
      "get": {
        "operationId": "listVendors",
        "summary": "List or search vendors",
        "description": "Use to find the catalog name of a tool before you post an RFP, or to browse a category. Returns a page of vendors with open RFP counts.",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Text to match in the vendor name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Category name filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, maximum 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Items to skip. Default 0.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "A page of vendors.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VendorPage"
                }
              }
            }
          },
          "401": {
            "description": "A token was sent but it is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/vendors/{slug}": {
      "get": {
        "operationId": "getVendor",
        "summary": "Get one vendor",
        "description": "Use to read a vendor's feature list, so the features you name in an RFP match the catalog. Also lists its newest open RFPs.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The vendor slug.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The vendor.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Vendor"
                }
              }
            }
          },
          "401": {
            "description": "A token was sent but it is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No record with that identifier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/rfps": {
      "get": {
        "operationId": "listRfps",
        "summary": "List or search RFPs",
        "description": "Use to find open RFPs a seller can bid on, or to check whether a tool already has an RFP. Filters by text, vendor slug and status.",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Text to match in the RFP title.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "vendor",
            "in": "query",
            "required": false,
            "description": "Vendor slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Default open.",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "awarded",
                "closed",
                "any"
              ]
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Default newest.",
            "schema": {
              "type": "string",
              "enum": [
                "newest",
                "spend"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, maximum 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Items to skip. Default 0.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "A page of RFPs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RfpPage"
                }
              }
            }
          },
          "401": {
            "description": "A token was sent but it is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postRfps",
        "summary": "Post RFPs in bulk",
        "description": "The main job. Post one open RFP per paid tool the company wants to replace. Max 60 per call. Safe to retry: an existing open RFP is reported as already_posted.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostRfpsBody"
              },
              "example": {
                "rfps": [
                  {
                    "vendor": "Figma",
                    "annualSpend": 4800,
                    "seats": 12,
                    "features": [
                      "Dev Mode",
                      {
                        "name": "Prototyping",
                        "importance": "nice",
                        "evidence": "Used for client demos"
                      }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Nothing new was posted. Check each result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostRfpsResult"
                }
              }
            }
          },
          "201": {
            "description": "At least one RFP was posted. Check each result.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostRfpsResult"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No valid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token is read-only, or the caller does not own the record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/rfps/{slug}": {
      "get": {
        "operationId": "getRfp",
        "summary": "Get one RFP",
        "description": "Use to read an RFP's features, feature ids and bids. A seller needs the feature ids to submit a bid.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The RFP slug.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The RFP.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Rfp"
                }
              }
            }
          },
          "401": {
            "description": "A token was sent but it is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No record with that identifier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "setRfpStatus",
        "summary": "Open, close or award an RFP",
        "description": "Buyer only. Use to close an RFP that is no longer needed, or to reopen it. Awarding happens when a bid is accepted.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The RFP slug.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RfpStatusInput"
              },
              "example": {
                "status": "closed"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated RFP.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Rfp"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No valid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token is read-only, or the caller does not own the record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No record with that identifier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/rfps/{slug}/bids": {
      "post": {
        "operationId": "submitBid",
        "summary": "Submit a bid on an RFP",
        "description": "Seller side. Bid with a product you own. Claim coverage for each RFP feature id and give a yearly price in dollars.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The RFP slug.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitBidInput"
              },
              "example": {
                "productSlug": "acme-design",
                "annualPrice": 3200,
                "coverage": [
                  {
                    "rfpFeatureId": "clx123",
                    "coverage": "full",
                    "note": "Built in"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The bid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bid"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No valid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token is read-only, or the caller does not own the record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No record with that identifier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The product already has a bid on this RFP.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/products": {
      "get": {
        "operationId": "listProducts",
        "summary": "List or search products",
        "description": "Use to find products sellers offer, or to check whether a product exists before you create one.",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Text to match in the product name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, maximum 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Items to skip. Default 0.",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "A page of products.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductPage"
                }
              }
            }
          },
          "401": {
            "description": "A token was sent but it is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createProduct",
        "summary": "Create a product",
        "description": "Seller side. Use once per product before you bid. Returns the product slug that submitBid needs.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProductInput"
              },
              "example": {
                "name": "Acme Design",
                "website": "https://acme.example",
                "pricingModel": "per-seat",
                "tagline": "Design for small teams"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The product.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No valid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token is read-only, or the caller does not own the record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/products/{slug}": {
      "get": {
        "operationId": "getProduct",
        "summary": "Get one product",
        "description": "Use to read a product's pricing links and its public bids.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The product slug.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The product.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Product"
                }
              }
            }
          },
          "401": {
            "description": "A token was sent but it is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No record with that identifier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/bids/{id}": {
      "get": {
        "operationId": "getBid",
        "summary": "Get one bid",
        "description": "Use to read a bid's price, per-feature coverage and match explanation.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The bid id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "The bid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bid"
                }
              }
            }
          },
          "401": {
            "description": "A token was sent but it is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No record with that identifier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "setBidStatus",
        "summary": "Shortlist, reject or accept a bid",
        "description": "Buyer only. Use to review bids on your RFP. Status accepted awards the RFP to the bid.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The bid id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BidStatusInput"
              },
              "example": {
                "status": "shortlisted"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated bid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bid"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No valid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token is read-only, or the caller does not own the record.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No record with that identifier.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/search": {
      "get": {
        "operationId": "searchAll",
        "summary": "Search vendors, RFPs and products",
        "description": "Use for a free-text lookup across the whole marketplace. Each hit has a type, a title, a snippet and a public URL.",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "description": "The search text.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size. Default 20, maximum 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Matching records.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResults"
                }
              }
            }
          },
          "400": {
            "description": "The query is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "A token was sent but it is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/me": {
      "get": {
        "operationId": "getMe",
        "summary": "Get the signed-in account",
        "description": "Use to learn who the token belongs to. Returns the profile, and the caller's own RFPs, products and bids.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "oauth2": [
              "read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The account and its records.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "401": {
            "description": "No valid token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The token has no read scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Party": {
        "type": "object",
        "properties": {
          "anonymous": {
            "type": "boolean"
          },
          "name": {
            "type": "string"
          },
          "handle": {
            "oneOf": [
              {
                "type": "string",
                "description": "Null when the party is anonymous to this caller."
              },
              {
                "type": "null"
              }
            ]
          },
          "company": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "url": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "anonymous",
          "name",
          "handle",
          "company",
          "url"
        ],
        "additionalProperties": false
      },
      "VendorSummary": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "website": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "description": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "openRfpCount": {
            "type": "integer"
          },
          "url": {
            "type": "string",
            "description": "Public page URL. Cite it."
          }
        },
        "required": [
          "slug",
          "name",
          "category",
          "website",
          "description",
          "openRfpCount",
          "url"
        ],
        "additionalProperties": false
      },
      "Feature": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "group": {
            "type": "string"
          }
        },
        "required": [
          "slug",
          "name",
          "description",
          "group"
        ],
        "additionalProperties": false
      },
      "Vendor": {
        "allOf": [
          {
            "$ref": "#/components/schemas/VendorSummary"
          },
          {
            "type": "object",
            "properties": {
              "features": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Feature"
                }
              },
              "openRfps": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RfpSummary"
                }
              }
            },
            "required": [
              "features",
              "openRfps"
            ],
            "additionalProperties": false
          }
        ],
        "description": "A vendor summary plus its feature list and up to 20 newest open RFPs."
      },
      "RfpSummary": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "awarded",
              "closed"
            ]
          },
          "vendor": {
            "type": "object",
            "properties": {
              "slug": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "slug",
              "name"
            ],
            "additionalProperties": false
          },
          "annualSpend": {
            "type": "number",
            "description": "Whole US dollars per year."
          },
          "seats": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          },
          "renewalDate": {
            "oneOf": [
              {
                "type": "string",
                "description": "ISO 8601 date."
              },
              {
                "type": "null"
              }
            ]
          },
          "featureCount": {
            "type": "integer"
          },
          "bidCount": {
            "type": "integer"
          },
          "bestMatchPercent": {
            "oneOf": [
              {
                "type": "number",
                "description": "The best match percent among visible bids."
              },
              {
                "type": "null"
              }
            ]
          },
          "lowestBid": {
            "oneOf": [
              {
                "type": "number",
                "description": "The lowest visible bid price in dollars."
              },
              {
                "type": "null"
              }
            ]
          },
          "buyer": {
            "$ref": "#/components/schemas/Party"
          },
          "createdAt": {
            "type": "string",
            "description": "ISO 8601 timestamp."
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "slug",
          "title",
          "status",
          "vendor",
          "annualSpend",
          "seats",
          "renewalDate",
          "featureCount",
          "bidCount",
          "bestMatchPercent",
          "lowestBid",
          "buyer",
          "createdAt",
          "url"
        ],
        "additionalProperties": false
      },
      "RfpFeature": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The RfpFeature id. A bid's coverage claims reference it."
          },
          "name": {
            "type": "string"
          },
          "importance": {
            "type": "string",
            "enum": [
              "must",
              "nice"
            ]
          },
          "usageNote": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "featureSlug": {
            "oneOf": [
              {
                "type": "string",
                "description": "The taxonomy feature slug, or null for a custom feature."
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "name",
          "importance",
          "usageNote",
          "featureSlug"
        ],
        "additionalProperties": false
      },
      "BidSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "submitted",
              "shortlisted",
              "rejected",
              "accepted"
            ]
          },
          "product": {
            "type": "object",
            "properties": {
              "slug": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "slug",
              "name"
            ],
            "additionalProperties": false
          },
          "annualPrice": {
            "type": "number",
            "description": "Whole US dollars per year."
          },
          "savingsPercent": {
            "type": "number",
            "description": "Percent saved against the RFP's annual spend. Negative when the bid costs more."
          },
          "matchPercent": {
            "type": "number"
          },
          "seller": {
            "$ref": "#/components/schemas/Party"
          },
          "createdAt": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "status",
          "product",
          "annualPrice",
          "savingsPercent",
          "matchPercent",
          "seller",
          "createdAt",
          "url"
        ],
        "additionalProperties": false
      },
      "Rfp": {
        "allOf": [
          {
            "$ref": "#/components/schemas/RfpSummary"
          },
          {
            "type": "object",
            "properties": {
              "description": {
                "oneOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "stats": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "label": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "label",
                    "value"
                  ],
                  "additionalProperties": false
                }
              },
              "features": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/RfpFeature"
                }
              },
              "bids": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BidSummary"
                }
              },
              "bidUrl": {
                "type": "string",
                "description": "The URL a seller opens to bid by hand."
              }
            },
            "required": [
              "description",
              "stats",
              "features",
              "bids",
              "bidUrl"
            ],
            "additionalProperties": false
          }
        ]
      },
      "ProductSummary": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "tagline": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "website": {
            "type": "string"
          },
          "pricingModel": {
            "type": "string",
            "enum": [
              "subscription",
              "per-seat",
              "usage",
              "one-time",
              "free",
              "custom"
            ]
          },
          "bidCount": {
            "type": "integer"
          },
          "seller": {
            "$ref": "#/components/schemas/Party"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "slug",
          "name",
          "tagline",
          "website",
          "pricingModel",
          "bidCount",
          "seller",
          "url"
        ],
        "additionalProperties": false
      },
      "Product": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProductSummary"
          },
          {
            "type": "object",
            "properties": {
              "description": {
                "oneOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "demoUrl": {
                "oneOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "docsUrl": {
                "oneOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "pricingUrl": {
                "oneOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "pricingNote": {
                "oneOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "bids": {
                "type": "array",
                "items": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/BidSummary"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "rfp": {
                          "type": "object",
                          "properties": {
                            "slug": {
                              "type": "string"
                            },
                            "title": {
                              "type": "string"
                            },
                            "vendorName": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "slug",
                            "title",
                            "vendorName"
                          ],
                          "additionalProperties": false
                        }
                      },
                      "required": [
                        "rfp"
                      ],
                      "additionalProperties": false
                    }
                  ]
                }
              }
            },
            "required": [
              "description",
              "demoUrl",
              "docsUrl",
              "pricingUrl",
              "pricingNote",
              "bids"
            ],
            "additionalProperties": false
          }
        ]
      },
      "Bid": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BidSummary"
          },
          {
            "type": "object",
            "properties": {
              "rfp": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string"
                  },
                  "annualSpend": {
                    "type": "number"
                  },
                  "vendorName": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string"
                  }
                },
                "required": [
                  "slug",
                  "title",
                  "annualSpend",
                  "vendorName",
                  "url"
                ],
                "additionalProperties": false
              },
              "priceNote": {
                "oneOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "demoUrl": {
                "oneOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "notes": {
                "oneOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "coverage": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "rfpFeatureId": {
                      "type": "string"
                    },
                    "feature": {
                      "type": "string"
                    },
                    "importance": {
                      "type": "string",
                      "enum": [
                        "must",
                        "nice"
                      ]
                    },
                    "coverage": {
                      "type": "string",
                      "enum": [
                        "full",
                        "partial",
                        "integration",
                        "none"
                      ]
                    },
                    "note": {
                      "oneOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "rfpFeatureId",
                    "feature",
                    "importance",
                    "coverage",
                    "note"
                  ],
                  "additionalProperties": false
                }
              },
              "extras": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "note": {
                      "oneOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "name",
                    "note"
                  ],
                  "additionalProperties": false
                }
              },
              "matchExplanation": {
                "type": "string"
              }
            },
            "required": [
              "rfp",
              "priceNote",
              "demoUrl",
              "notes",
              "coverage",
              "extras",
              "matchExplanation"
            ],
            "additionalProperties": false
          }
        ]
      },
      "SearchHit": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "\"vendor:<slug>\", \"rfp:<slug>\" or \"product:<slug>\"."
          },
          "type": {
            "type": "string",
            "enum": [
              "vendor",
              "rfp",
              "product"
            ]
          },
          "title": {
            "type": "string"
          },
          "snippet": {
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "type",
          "title",
          "snippet",
          "url"
        ],
        "additionalProperties": false
      },
      "VendorPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VendorSummary"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "nextOffset": {
            "oneOf": [
              {
                "type": "integer",
                "description": "Pass this as offset to get the next page. Null on the last page."
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "items",
          "total",
          "limit",
          "offset",
          "nextOffset"
        ]
      },
      "RfpPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RfpSummary"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "nextOffset": {
            "oneOf": [
              {
                "type": "integer",
                "description": "Pass this as offset to get the next page. Null on the last page."
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "items",
          "total",
          "limit",
          "offset",
          "nextOffset"
        ]
      },
      "ProductPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSummary"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "nextOffset": {
            "oneOf": [
              {
                "type": "integer",
                "description": "Pass this as offset to get the next page. Null on the last page."
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "items",
          "total",
          "limit",
          "offset",
          "nextOffset"
        ]
      },
      "SearchResults": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchHit"
            }
          }
        },
        "required": [
          "results"
        ],
        "additionalProperties": false
      },
      "PostRfpEntry": {
        "type": "object",
        "properties": {
          "vendor": {
            "type": "string",
            "description": "The tool to replace, e.g. Figma. The name is matched to the vendor catalog."
          },
          "annualSpend": {
            "oneOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ],
            "description": "Yearly cost in dollars, e.g. 4800 or \"$4,800\"."
          },
          "features": {
            "type": "array",
            "minItems": 1,
            "maxItems": 60,
            "description": "Features the company uses. A plain string means a must-have.",
            "items": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "importance": {
                      "type": "string",
                      "enum": [
                        "must",
                        "nice"
                      ]
                    },
                    "evidence": {
                      "type": "string",
                      "description": "Why the company needs it. Up to 200 characters."
                    }
                  },
                  "required": [
                    "name"
                  ]
                }
              ]
            }
          },
          "seats": {
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          },
          "renewalDate": {
            "oneOf": [
              {
                "type": "string",
                "description": "YYYY-MM-DD."
              },
              {
                "type": "null"
              }
            ]
          },
          "plan": {
            "oneOf": [
              {
                "type": "string",
                "description": "The current plan name."
              },
              {
                "type": "null"
              }
            ]
          },
          "title": {
            "oneOf": [
              {
                "type": "string",
                "description": "Defaults to Replacing <vendor>."
              },
              {
                "type": "null"
              }
            ]
          },
          "description": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "anonymous": {
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "description": "Hide the buyer's identity from sellers."
          }
        },
        "required": [
          "vendor",
          "annualSpend",
          "features"
        ]
      },
      "PostRfpsBody": {
        "oneOf": [
          {
            "type": "array",
            "minItems": 1,
            "maxItems": 60,
            "items": {
              "$ref": "#/components/schemas/PostRfpEntry"
            }
          },
          {
            "type": "object",
            "properties": {
              "rfps": {
                "type": "array",
                "minItems": 1,
                "maxItems": 60,
                "items": {
                  "$ref": "#/components/schemas/PostRfpEntry"
                }
              }
            },
            "required": [
              "rfps"
            ],
            "additionalProperties": false
          }
        ]
      },
      "PostRfpsResult": {
        "type": "object",
        "properties": {
          "posted": {
            "type": "integer"
          },
          "alreadyPosted": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "vendor": {
                  "type": "string"
                },
                "outcome": {
                  "type": "string",
                  "enum": [
                    "posted",
                    "already_posted",
                    "error"
                  ]
                },
                "rfpSlug": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "detail": {
                  "type": "string"
                }
              },
              "required": [
                "index",
                "vendor",
                "outcome"
              ]
            }
          }
        },
        "required": [
          "posted",
          "alreadyPosted",
          "failed",
          "results"
        ],
        "additionalProperties": false
      },
      "CreateProductInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "website": {
            "type": "string",
            "description": "A full https URL."
          },
          "pricingModel": {
            "type": "string",
            "enum": [
              "subscription",
              "per-seat",
              "usage",
              "one-time",
              "free",
              "custom"
            ]
          },
          "tagline": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "demoUrl": {
            "type": "string"
          },
          "docsUrl": {
            "type": "string"
          },
          "pricingUrl": {
            "type": "string"
          },
          "pricingNote": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "website",
          "pricingModel"
        ]
      },
      "SubmitBidInput": {
        "type": "object",
        "properties": {
          "productSlug": {
            "type": "string",
            "description": "A product the caller owns."
          },
          "annualPrice": {
            "type": "number",
            "description": "Whole US dollars per year."
          },
          "coverage": {
            "type": "array",
            "description": "One entry per RFP feature id. A feature left out counts as none.",
            "items": {
              "type": "object",
              "properties": {
                "rfpFeatureId": {
                  "type": "string",
                  "description": "From the RFP's features list."
                },
                "coverage": {
                  "type": "string",
                  "enum": [
                    "full",
                    "partial",
                    "integration",
                    "none"
                  ]
                },
                "note": {
                  "type": "string"
                }
              },
              "required": [
                "rfpFeatureId",
                "coverage"
              ]
            }
          },
          "extras": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              },
              "required": [
                "name"
              ]
            }
          },
          "priceNote": {
            "type": "string"
          },
          "demoUrl": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "anonymous": {
            "type": "boolean"
          }
        },
        "required": [
          "productSlug",
          "annualPrice",
          "coverage"
        ]
      },
      "RfpStatusInput": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "open",
              "awarded",
              "closed"
            ]
          }
        },
        "required": [
          "status"
        ],
        "additionalProperties": false
      },
      "BidStatusInput": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "submitted",
              "shortlisted",
              "rejected",
              "accepted"
            ]
          }
        },
        "required": [
          "status"
        ],
        "additionalProperties": false
      },
      "Me": {
        "type": "object",
        "properties": {
          "handle": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "company": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "url": {
            "type": "string"
          },
          "counts": {
            "type": "object",
            "properties": {
              "openRfps": {
                "type": "integer"
              },
              "products": {
                "type": "integer"
              },
              "bids": {
                "type": "integer"
              }
            },
            "required": [
              "openRfps",
              "products",
              "bids"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "handle",
          "displayName",
          "company",
          "url",
          "counts"
        ],
        "additionalProperties": false
      },
      "MeResponse": {
        "type": "object",
        "properties": {
          "me": {
            "$ref": "#/components/schemas/Me"
          },
          "rfps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RfpSummary"
            }
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductSummary"
            }
          },
          "bids": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BidSummary"
            }
          }
        },
        "required": [
          "me",
          "rfps",
          "products",
          "bids"
        ],
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "bad_request",
                  "unauthorized",
                  "forbidden",
                  "not_found",
                  "conflict",
                  "rate_limited",
                  "server_error"
                ]
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A personal token from /settings, or an OAuth access token."
      },
      "oauth2": {
        "type": "oauth2",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://saasrfp.com/oauth/authorize",
            "tokenUrl": "https://saasrfp.com/oauth/token",
            "refreshUrl": "https://saasrfp.com/oauth/token",
            "scopes": {
              "read": "Read the signed-in account.",
              "write": "Post RFPs and products, submit bids, and change statuses."
            }
          }
        }
      }
    }
  }
}