# SaaS RFP: full agent manual

> SaaS RFP is a public marketplace for software procurement. A buyer posts an RFP that names a vendor to replace, the annual spend and the features in use. A seller bids with a product and a per-feature coverage claim.

## What the site is

- Everything is public. Anyone can read every RFP, product and bid.
- An RFP is a request to replace one software vendor. It is open, closed or awarded.
- A bid is a seller's offer on one RFP. It has a yearly price and a coverage claim for each RFP feature.
- Money is whole US dollars per year.
- Every record has a public page URL. Cite it.

## Data model in plain words

- Vendor: a software product that companies pay for, such as Notion. A vendor has a slug, a category and a feature list.
- Feature: a capability of a vendor. Features have a slug, a name and a group.
- RFP: one buyer, one vendor, one annual spend, and a list of RFP features. Each RFP feature has an id, an importance ("must" or "nice") and an optional usage note.
- Product: something a seller offers, such as a cheaper tool. A product has a slug, a website and a pricing model.
- Bid: one product on one RFP. It has an annual price, a status and coverage entries. A coverage entry names an RFP feature id and a level: full, partial, integration or none.
- Bid status: submitted, shortlisted, rejected or accepted. Accepting a bid awards the RFP.
- A record can be anonymous. An anonymous party shows no handle to other viewers.

## Connect

- MCP, sign-in required: https://saasrfp.com/mcp
- MCP, read-only, no sign-in: https://saasrfp.com/mcp/public
- OpenAPI 3.1: https://saasrfp.com/openapi.json
- REST base: https://saasrfp.com/api/v1
- Setup steps for each client: https://saasrfp.com/agents
- Agent Skill: https://saasrfp.com/skills/saas-rfp/SKILL.md
- MCP server card: https://saasrfp.com/.well-known/mcp/server-card.json

## Auth

- Read endpoints and the read-only MCP server need no token.
- Send "Authorization: Bearer <token>" for write calls and for /mcp.
- Personal token: make one at https://saasrfp.com/settings under API tokens. The prefix is srfp_pat_. It does not expire.
- OAuth 2.1: MCP clients sign the user in. Authorization URL: https://saasrfp.com/oauth/authorize. Token URL: https://saasrfp.com/oauth/token. PKCE S256 is required. Scopes: "read write".
- Dynamic client registration: https://saasrfp.com/oauth/register. Metadata: https://saasrfp.com/.well-known/oauth-authorization-server.
- Access tokens last 1 hour. Refresh tokens last 60 days and rotate on use.
- A token with the write scope may post RFPs, create products and submit bids.

## MCP tools

Transport: Streamable HTTP, stateless. Send a JSON-RPC POST.

### Read tools (on /mcp and /mcp/public)

- `search`
  - Arguments: query (string)
  - Search vendors, RFPs and products. Returns { results: [{ id, title, url }] }.
- `fetch`
  - Arguments: id (string): rfp:<slug>, vendor:<slug>, product:<slug> or bid:<id>
  - Read one record as text. Returns { id, title, text, url, metadata }.
- `list_vendors`
  - Arguments: query, category, limit, offset (all optional)
  - List vendors in the directory.
- `get_vendor`
  - Arguments: slug (string)
  - Read one vendor with its feature list and open RFPs.
- `list_rfps`
  - Arguments: query, vendor, status, sort, limit, offset (all optional)
  - List RFPs. Filter by vendor slug or status.
- `get_rfp`
  - Arguments: slug (string)
  - Read one RFP with its features, feature ids and bids.
- `list_products`
  - Arguments: query, limit, offset (all optional)
  - List products that sellers offer.
- `get_product`
  - Arguments: slug (string)
  - Read one product with its public bids.
- `get_bid`
  - Arguments: id (string)
  - Read one bid with its price and per-feature coverage.
- `whoami`
  - Arguments: none
  - Return the signed-in account. Needs a token.
- `list_my_rfps`
  - Arguments: none
  - Your own RFPs, including anonymous ones. Needs a token.
- `list_my_products`
  - Arguments: none
  - Your own products. Needs a token.
- `list_my_bids`
  - Arguments: none
  - Your own bids. Needs a token.

### Write tools (on /mcp only)

- `post_rfps`
  - Arguments: rfps (array of entries, at most 60)
  - Post one open RFP per paid tool. This is the main job.
- `create_product`
  - Arguments: name, website, pricingModel, tagline, description, demoUrl, docsUrl, pricingUrl, pricingNote
  - Seller side. Create a product before you bid.
- `submit_bid`
  - Arguments: rfpSlug, productSlug, annualPrice, coverage[], extras[], priceNote, demoUrl, notes, anonymous
  - Seller side. Bid on an open RFP.
- `set_bid_status`
  - Arguments: bidId, status (submitted, shortlisted, rejected, accepted)
  - Buyer side. Accepting a bid awards the RFP.
- `set_rfp_status`
  - Arguments: slug, status (open, closed, awarded)
  - Buyer side. Close or reopen an RFP.

## REST endpoints

Errors use `{ "error": { "code", "message" } }`. Lists return `{ items, total, limit, offset, nextOffset }`. The default limit is 20. The maximum is 100.

| Method | Path | Scope | Note |
|---|---|---|---|
| GET | `/api/v1/vendors?query&category&limit&offset` | none | List vendors. |
| GET | `/api/v1/vendors/{slug}` | none | One vendor with features and open RFPs. |
| GET | `/api/v1/rfps?query&vendor&status&sort&limit&offset` | none | List RFPs. |
| POST | `/api/v1/rfps` | write | Post RFPs. Body: { "rfps": [...] } or [...]. |
| GET | `/api/v1/rfps/{slug}` | none | One RFP with features and bids. |
| PATCH | `/api/v1/rfps/{slug}` | write | Body: { "status": "open" | "closed" | "awarded" }. Buyer only. |
| POST | `/api/v1/rfps/{slug}/bids` | write | Submit a bid. Body: SubmitBidInput without rfpSlug. |
| GET | `/api/v1/products?query&limit&offset` | none | List products. |
| POST | `/api/v1/products` | write | Create a product. |
| GET | `/api/v1/products/{slug}` | none | One product with its bids. |
| GET | `/api/v1/bids/{id}` | none | One bid. |
| PATCH | `/api/v1/bids/{id}` | write | Body: { "status": "shortlisted" | "rejected" | "accepted" }. Buyer only. |
| GET | `/api/v1/search?query&limit` | none | Search vendors, RFPs and products. |
| GET | `/api/v1/me` | read | The signed-in account and its records. |

### Examples

List open RFPs that replace Notion:

```bash
curl "https://saasrfp.com/api/v1/rfps?vendor=notion&status=open"
```

Search:

```bash
curl "https://saasrfp.com/api/v1/search?query=project+management"
```

Post RFPs:

```bash
curl -X POST https://saasrfp.com/api/v1/rfps \
  -H "Authorization: Bearer $SAAS_RFP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rfps":[{"vendor":"Notion","annualSpend":9600,"features":["Docs","Wiki"]}]}'
```

Create a product, then bid:

```bash
curl -X POST https://saasrfp.com/api/v1/products \
  -H "Authorization: Bearer $SAAS_RFP_TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"Acme Docs","website":"https://acme.example","pricingModel":"per-seat"}'

curl -X POST https://saasrfp.com/api/v1/rfps/<rfp-slug>/bids \
  -H "Authorization: Bearer $SAAS_RFP_TOKEN" -H "Content-Type: application/json" \
  -d '{"productSlug":"acme-docs","annualPrice":4800,"coverage":[{"rfpFeatureId":"<id>","coverage":"full"}]}'
```

To bid, first read the RFP with GET /api/v1/rfps/{slug}. Its features list holds the feature ids. A feature you leave out counts as coverage "none".

## RFP entry format

Post up to 60 entries at once. The post_rfps tool, POST /api/v1/rfps and the page https://saasrfp.com/rfps/bulk all take this format.

- vendor: required. The product name, such as "HubSpot".
- annualSpend: required. USD per year. A number, or a string such as "$4,800".
- features: required, at least one. A feature name, or an object with name, importance ("must" or "nice") and evidence. Use the feature names from the vendor's page where you can.
- seats: optional whole number.
- renewalDate: optional, YYYY-MM-DD.
- plan: optional note.
- title: optional. The default is "Replacing <vendor> for a <seats>-seat team".
- description: optional plain text.
- anonymous: optional. true hides the company name.

Full example:

```json
[
  {
    "vendor": "Notion",
    "annualSpend": 9600,
    "seats": 40,
    "renewalDate": "2027-03-01",
    "plan": "Business plan",
    "title": "Replacing Notion for a 40-seat team",
    "description": "We use Notion for docs and a small wiki.",
    "features": [
      "Docs",
      "Wiki",
      {
        "name": "Databases",
        "importance": "must",
        "evidence": "212 active databases"
      },
      {
        "name": "AI search",
        "importance": "nice"
      }
    ],
    "anonymous": false
  }
]
```

A vendor that already has an open RFP from the account is skipped with outcome "already_posted". Posting the same list twice is safe. Each result has an outcome: posted, already_posted or error.

## Rules

- Everything you post is public.
- Report figures and feature usage only. Do not copy contract text, invoices or other confidential terms.
- Post only what the person has the right to disclose.
- Ask the person before any write call. Show them the entries first.
- Post one open RFP per paid tool. Sellers bid only on an open RFP.
- Cite the url field of each record when you report to the person.

## Rate limits

Write requests: 20 per minute for each client. Search: 60 per minute. Other POST requests: 120 per minute. Login and signup: 5 per minute. A limited request returns HTTP 429 with a Retry-After header in seconds.

The limits apply to each server instance. Wait for the Retry-After time, then retry.
