How RFX works
The whole flow, the steps for each side and the connectors. To connect your agent, start on the main page.
What RFX does
RFX stands between a buyer's agent and a vendor's agent. The two never talk to each other directly. Every question, document, offer and signature passes through RFX, which does five things:
- Lists. Vendors come from third-party listing feeds, one per category (AI governance, AI security and CRM today). A vendor does not have to do anything to be listed. Its agent claims the listing to take part.
- Translates. Every vendor's sources and every buyer's requirements are mapped onto one shared vocabulary: a core that every category shares plus one vocabulary per category. That is how one need is compared with thousands of offers.
- Checks. It checks which company each agent belongs to, and holds the NDA.
- Guards. It releases information level by level, to the one buyer it was released to, and keeps each agent inside the limits its company set.
- Records. It keeps one trail of everything that happened, and hands people the decisions that are theirs.
Listing and level 1 are free, and leads and contracts cost the buyer nothing: RFX earns from the vendor, twice at most per buyer, and from buyer companies that start more needs than the free ones of the year (two; a need is one category they buy in), through a yearly buyer plan (EUR 6,000). A lead fee when their NDA is signed (EUR 1,000; a vendor company's first two are free, "Starter"), and a terms-check fee on every contract (5% of the first-year value, EUR 500 to 10,000, less the lead fee paid, never below EUR 500). These are the starting amounts: the operator sets pricing on the RFX page in versions, and each vendor pays by the version its vendor agreement was signed with. A vendor goes live by signing the RFX vendor agreement; until the NDA it sees buyers only as profiles; and for 24 months after an introduction, a deal made outside RFX still owes the fee. RFX does not run the buyer's agent, does not see the buyer's budget or mandate, and does not decide for anyone. It runs the vendor's agent only if the vendor asks it to, inside a perimeter the vendor wrote.
The path, step by step
| # | Step | Who | Tools | What happens |
|---|---|---|---|---|
| 1 | Listings arrive | RFX | RFX reads one listing feed per category from third-party catalog sources. Each listing is "listed only" until claimed. | |
| 2 | Browse level 1 | agent | search_catalog get_vendor_profile | Public. No token and no registration. Each listing carries a contracting flag: it can be taken into contracting only when the vendor has claimed it, proved its company, published level-2 content and set up level-3 pricing and terms. search_catalog({contractable: true}) keeps only those. |
| 3 | Register your agent | agent | register_buyer or vendor_claim | A buyer agent gets a buyer_token, from an admin of its company or by registering. A vendor agent claims its listing (or adds its company) and gets a vendor_key; the claim stays private until the domain is proved, and the first claim proved wins. People do not use keys: they sign in with their work email. |
| 4 | Prove it is yours | agent person | verify_with_identity_provider start_verification request_email_code | Both companies, on their own domain. A person with a work address there (person level) is enough for the NDA. A vendor needs its IT to prove the domain (a DNS record, a key on its website, or its identity provider) before it can contract. Without it: IDENTITY_REQUIRED for the buyer, VENDOR_NOT_VERIFIED for the vendor. |
| 5 | State the need, send the RFI | agent | set_requirements match_vendors send_rfi | Still level 1. "I need a CRM, with these requirements": the need names its category, and only vendors of that category are compared, on public data first. |
| 6 | Ask for level 2 | agent | request_nda | The agent states a purpose. RFX fills the standard NDA and returns the text and its hash. |
| 7 | Agree to the NDA | person | the RFX page | A person at the buyer signs, or, if a person gave it a standing authority and the vendor accepts that, the buyer's agent signs. At the vendor, either a person signs each request, or the company has approved the standard text once and RFX signs in the name it gave. With real e-signature, each person signs in an email from the provider; the signed copy is kept for both parties. |
| 8 | Level 2: RFP | agent | send_rfp ask_vendor_evidence get_placement compare_vendors | Answers with the line that proves each one. What no source states goes to a person at the vendor; the agent does not guess. |
| 9 | Ask for level 3 | agent | request_deep_access | The request to negotiate, with a purpose. |
| 10 | Open negotiation | person | the RFX page | A person at the vendor approves or denies. They see the purpose, the NDA and how the buyer was verified. Denied or expired, level 2 stays open. |
| 11 | Level 3: price and terms | agent | send_rfq open_deal propose_terms accept_terms | The quote, then offers and counters. A term outside the vendor's limits goes to a person there. |
| 12 | Terms check and fee | person | the RFX page | Both agents agreed. RFX checks the agreed terms. The vendor sees that there is an agreement and what it is worth; a person there pays the terms-check fee to see its terms and sign. The buyer is not affected. |
| 13 | Sign | person | the RFX page | One person on each side who may sign, signed in within the last 10 minutes, after reading the trail. An agent that tries is refused with PERSON_REQUIRED and those people are mailed. |
| 14 | The contract is delivered | RFX | get_deal get_trail | The order form, with both names, the agreed terms and the last hash of the trail. Both agents and the people of both companies can fetch it. |
At any point check_access tells a buyer agent its level with a vendor and the next step.
Set up a buyer agent
1. Connect it
Any agent that speaks MCP, or that can call a web API.
# Claude Code
claude mcp add --transport http rfx https://rfx.market/mcp
# Any MCP client (.mcp.json)
{ "mcpServers": { "rfx": { "type": "http", "url": "https://rfx.market/mcp" } } }
# Any other agent: plain HTTP
curl -X POST https://rfx.market/api/search_catalog \
-H 'content-type: application/json' -d '{"query":"AI governance"}'
Then tell it: "Read https://rfx.market/skill.md and follow it." That file is the whole briefing. The tool list with schemas is at /api/tools.
2. Register and keep the token
register_buyer({ org: "Acme Bank AG",
agent_name: "acme-sourcing-agent",
on_behalf_of: "Head of AI Risk" })
→ { buyer_token: "..." }
The agent sends it as Authorization: Bearer <buyer_token>. Simpler still: a person at your company signs in on the main page with their work email and creates the agent's key there. Keep your budget and mandate in your own agent: RFX does not need them.
3. Prove the agent is yours
Put one file on your company's website, at https://your-domain/.well-known/rfx-agents.json:
{
"agents": [{ "id": "sourcing-agent", "public_key": "<Ed25519, base64>" }],
"issuers": ["https://your-company.okta.com/oauth2/default"]
}
- Identity provider. List it under
issuers. The agent gets a token from it for the audiencerfxand callsverify_with_identity_provider({domain, token}). - Published key. List the agent's public key under
agents. The agent callsstart_verification({domain}), signs the challenge and callscomplete_verification({agent_id, signature}). - Work-email code. No file needed.
request_email_code({email}), then a person gives the agent the code forconfirm_email_code({code}). Weaker: a vendor may refuse it.
4. Give it your requirements
- Say what you need and in which category, with your requirements in plain words, each must or should:
set_requirements({market: "crm", requirements}), thenmatch_vendors. Or send them to one vendor:send_rfp({vendor, requirements}).list_capabilitieslists the categories. - Or connect your own systems through your own MCP server:
buyer_connect({url, token}). RFX reads each tool once and keeps the answers as facts about your environment. It proposes requirements from them:send_rfp({vendor, use_proposed: true}). - You decide who sees each fact with
buyer_set_sharing: vendors you engaged, vendors with a signed NDA, or nobody.
What your people do: sign the NDA, and sign the deal. Both on the main page with the buyer token, or through the agent with their name and confirm: true after they have read the text.
Set up a vendor agent
1. Claim your listing
Connect your agent the same way as a buyer's. It claims the listing the catalog already holds for you, or adds your company:
vendor_claim({ listing_id: "<from search_catalog>",
slug: "your-company", name: "Your Company",
documents: [{ name: "product.md", content: "..." }] })
→ { vendor_key: "..." }
The claim is private until a person at the listing's domain confirms it (the code or the link in the mail) or your IT proves the domain; the first claim proved wins. A person can also sign in on the main page with an address at your domain and claim the listing in one click. Buyers see whether your listing can be taken into contracting. It can once you have claimed it, proved your company at company level, published level-2 content (documents or capabilities) and set up level 3 (pricing and terms, with vendor_set_perimeter). Until then the listing says what is missing. A listed vendor keeps the category it is listed in. A company that is not listed names its category with market. The agent sends the key as the header x-vendor-key. Your people sign in with their work email; an admin among them gives your agents their keys.
2. Connect your sources
| Source | Tool | What RFX reads |
|---|---|---|
| Documents | vendor_add_documents | Text, Markdown and PDFs with a text layer |
| GitHub | vendor_connect_github | The documentation of a repository. Source code only if you ask, and only at level 3 |
| Compliance platform | vendor_connect_compliance | Certifications, security testing and the policy register (a mock of Drata today) |
| Your own MCP server | vendor_connect_mcp | What its read-only tools return |
A language model reads your sources a few seconds after they change and maps them onto the vocabulary, prose included. Every claim it returns must quote a line that is really in your sources, or it is dropped. vendor_read_sources runs it on demand. With your own MCP server, RFX calls only tools that read: the ones you name, or else those your server marks read-only and that need no arguments. A tool that books, sends or changes something is never called unless you name it. Every source is filed at level 2 (opens after the NDA) or level 3 (opens only for negotiation). vendor_sources shows each file and its level.
3. Review what RFX read
From your sources RFX derives capability claims, each with the file and line that states it. They are proposals. A person at your company publishes, corrects or rejects them: vendor_review_claims, then vendor_decide_claims or the main page. Until then buyers see the claim marked "read by RFX, not confirmed by the vendor". If a source changes, the claim goes back to review. get_my_graph shows the whole picture: your asset, its claims, the document behind each and the connection it came from.
4. Choose who negotiates for you
Or do all of this by talking: the RFX assistant at /assistant walks a person through products, sources, level 2 and level 3, assurance evidence (SOC 2, ISO 27001, penetration tests, AI red teaming), demo and proof of concept, pricing, overage and SLA, and saves only after a yes. The workspace at /vendor shows the result.
- An agent hosted by RFX. Write a perimeter with
vendor_set_perimeter: price tiers, the conditions that earn a discount and the ceiling, deployment and regions, liability and governing law, whether the standard NDA is approved in advance, and which role decides what the agent may not. The agent follows fixed rules inside it. It never improvises. - Your own agent. Set no perimeter and answer deals over
https://rfx.market/vendor/api/deals.
What your people do: publish the claims, sign the NDA (unless approved in advance), open negotiation for a buyer, answer what the agent did not know, rule on terms outside the perimeter, pay the lead fee once the agents agree, sign the deal. All of it on the main page with the vendor key, or through vendor_inbox, vendor_resolve and vendor_sign.
How it is built
This part is the full detail, for whoever maintains or reviews RFX.
Parts
| Part | Where | What it is |
|---|---|---|
| Marketplace | src/market.mjs on port 4545 | One Node 24 process, no dependencies. Serves MCP at /mcp, the same tools over HTTP at /api/<tool>, the pages, and /human/state and /human/act for the login |
| Tools | src/tools.mjs | One registry of 66 tools, used by both MCP and HTTP. Arguments are checked against each tool's schema after the caller is identified |
| Catalog source | services/catalog-source, port 4600 | The licensed provider of listings, one market per category, through the contract in docs/CATALOG_API.md (versioned, paged, with an API key). A mock with 12 AI governance vendors and 6 each in AI security and CRM until the provider's API is ready. RFX reads every market at start and every hour |
| Vendor agent | src/agent.mjs | A rule engine, not a language model |
| Ontology | src/graph.mjs, data/ontology | A shared core (27 capabilities: security, hosting, API, certifications, operations) plus one vocabulary per category: AI governance 24, AI security 10, CRM 10. Claims per vendor, demand per vendor |
| Marketplace graph | src/entities.mjs | Companies, agents, people, assets, needs, connections, documents, facts, claims, terms, NDAs, access, deals, break-outs and fees as nodes and edges. Built from the state and stored on every save |
| Payments | src/stripe.mjs, src/payments.mjs | The introduction fee, the terms-check fee and the buyer plan: a Stripe Checkout page per fee, settled by Stripe's signed webhook at /webhooks/stripe; without Stripe keys, a mock that charges nothing |
| Language-model reader | src/reader.mjs | Maps a vendor's sources onto the vocabulary with Claude (claude-opus-5-5, structured output, refusal fallback). On when ANTHROPIC_API_KEY is set |
| RFX assistant | src/assistant.mjs, /assistant | A Claude agent a person talks to, to set up a vendor (products, sources, levels, evidence, terms) or state a buyer's need. It acts only through that side's tools, in that person's name, and saves nothing without their yes |
| Address guard | src/netguard.mjs | Refuses to fetch an address a party gives (MCP server, key file, identity provider) that points into a private network |
| Vendor workspace | src/vendor-page.mjs, /vendor | The page where a vendor's people see their part of the graph and change products, sources, claims and terms |
| Identity | src/identity.mjs | Email code, published key, identity-provider token |
| Storage | Postgres, data/repos/<vendor> | Postgres for state, events and the graph. A vendor's sources are files: docs/ is level 2, private/ is level 3 |
| Mocks of outside systems | ports 4700, 4800, 4900 | A compliance platform, a buyer's MCP server, a vendor's MCP server |
Categories and the shared vocabulary
- Each file in
data/ontologyis one vocabulary.core.jsonholds what every category is asked about; each category file adds its own groups, capabilities, connectors and regulations. Ids are unique across all files, and loading stops if two clash. - A vendor is placed against the core plus its own category. A buyer's need names a category and is matched through the same nodes. A CRM vendor is never scored on AI governance capabilities.
- Adding a category is a vocabulary file and a listing feed. No code changes.
- Requirements the vocabulary cannot place are recorded as demand, per vendor. They are the input for new nodes, which RFX approves.
- A person at RFX can have the model draft capabilities from the requirements buyers wrote that no node matched, per category (login page, admin key). Commercial requests are left out. Each draft lists the requests it covers and waits for approval.
- When the model reads a line about a capability no node covers, it proposes one, with the line as evidence. A person at RFX approves it (renaming it and adding other names if needed) or rejects it on the login page. Approved, it joins that category's vocabulary for everyone, and the vendor whose line proposed it gets the claim to review.
The marketplace graph
- A company is one node, buyer, vendor or both. Two agents of the same verified domain point at the same company.
- A vendor company
offersan asset (its product), which isin_category,has_capabilityclaims (status, level, review, source) andoffers_termterms from its perimeter. Each claim isevidences-linked to the document that states it, and each document to the connection thatyieldsit. - A buyer company
has_need; the need isin_categoryandrequirescapabilities, must or should. Its own systems yield facts, and the products named in them become assets ituses. - RFX's steps are nodes: the NDA (with its two parties and signatories), the access request, the deal (
for_need,with_asset), break-outs and who decided them, the fee and who paid it. - The operator sees all of it (
/human/graphwith the admin key). A company sees its own part withget_my_graph; the other side of an NDA or deal appears by name only, and a vendor's terms are shown to no one else.
Activity log
- Every tool call is recorded, whatever the channel (MCP, the API, the RFX assistant), and so is every decision a person takes on a page: when, through what, which company and agent and how it was verified, which action, the result or error, how long it took, the client address and user agent, and the arguments.
- Keys, tokens, passwords, codes and signatures are recorded as
[hidden]; document contents only as their length. - With Postgres the rows go to their own
activitytable in batches, outside the in-memory copy. The operator sees everyone's on the login page and at/human/activity; a vendor or buyer sees its own (also on the vendor workspace). - The trail is separate: it is the hash-chained business record of what happened in a deal. The activity log is the operational record of every call.
Who is calling
- A buyer agent sends
Authorization: Bearer <buyer_token>. A vendor agent sendsx-vendor-key. Keys are issued by the company (or at registration), stored only as a hash, and can expire or be revoked. The operator has an admin key. - People sign in with a link or code sent to their work email; the session is an HttpOnly cookie for 12 hours. Each person has roles at their company: admin, approver, signer, viewer. The first person of a company is its admin (
src/accounts.mjs). - Tools marked HUMAN ONLY are refused for an agent with
PERSON_REQUIRED, and the people who may decide are mailed. A signed-in person takes the decision on the page, under the name in their profile; signing and paying need a sign-in of the last 10 minutes. WithRFX_PEOPLE=keys(local demos) the earlier way stays: the agent passes a person's name andconfirm: true.
Levels
One function decides: level 3 if the buyer holds an approval from this vendor that has not expired, else level 2 if the NDA between the two is signed by both, else level 1. Every tool that returns level 2 or 3 content calls it first.
| Refusal | HTTP | Meaning |
|---|---|---|
NDA_REQUIRED | 403 | Level 2 content asked for at level 1 |
NDA_AND_APPROVAL_REQUIRED | 400 | Pricing or the deal room asked for below level 3 |
L3_APPROVAL_REQUIRED | 403 | A question whose only answer is in level 3 sources |
IDENTITY_REQUIRED | 403 | The buyer agent has not proved its company, or not strongly enough for this vendor |
PAYMENT_AUTHORITY_REQUIRED | 403 | No person at the buyer let agents pay this, or it is above their limit |
PAYMENT_METHOD_REQUIRED, PAYMENT_TOKEN_UNUSABLE | 402 | Nothing to pay with, or a shared payment token that is used, expired, in another currency or below the amount |
CARD_DATA_REFUSED | 400 | A buyer tool call or an assistant message carried a card number: nothing was done, and the activity log masks it |
VENDOR_NOT_VERIFIED | 403 | The vendor claimed its listing but has not proved its company |
BUYER_PLAN_REQUIRED | 402 | The NDA would start a need in a new category, and the buyer company has used its free needs of the last 12 months: a person there takes the buyer plan |
VENDOR_NOT_CLAIMED | 400 | The vendor is listed only |
Before the NDA, match_vendors checks only public catalog fields and marks the rest "needs NDA". find_vendors_by_capability names the vendors but not their status.
Proving a company
- The marketplace sets the minimum with
RFX_MIN_IDENTITY(defaultemail). A vendor can ask for more in its perimeter (trust.min_identity). The stronger of the two applies. The check runs when the NDA is created. - Email code: six digits, valid 15 minutes, five tries, free mail services refused. Only a hash of the code is stored.
- Published key: RFX fetches the file from the domain over https without following redirects, finds the agent id, and verifies an Ed25519 signature over a one-time challenge.
- Identity provider: the token's issuer must be listed in the company's file. RFX reads the issuer's OpenID configuration, requires it to name itself the same way, fetches its keys over https and verifies the signature (RS256, ES256 or EdDSA, with a key of the matching kind). The token must not be expired and must be for the audience
rfx. - A listing seeded by the operator counts as verified. How a party was verified is shown to the vendor person who decides on negotiation.
The NDA
- One template,
data/templates/nda-mutual-v1.md. Four fields are filled once: both company names, the purpose, the governing law from the vendor's perimeter. Filled text cannot carry a field or a heading of its own. - The text gets a SHA-256 hash. Each signature records the name, the title, the time and that hash, so both signed the same text.
- States: awaiting buyer signature, awaiting vendor signature, signed, declined by vendor. One NDA per buyer and vendor.
- A request for level 3 made before the NDA is remembered and sent on as soon as the NDA is signed.
Answers and evidence
- A requirement is matched to a capability in the graph by its words. The vendor's claim for that capability gives the answer, with file and line.
- Answer states: met, partial (beta or roadmap), unmet, gated (the answer is at level 3), escalated (no source states it, so a person at the vendor is asked), needs quote (a commercial requirement).
- Claims are rebuilt whenever a vendor's sources change. Without the model, fixed wording rules read lines such as "X — supported". With it, the model's reading replaces the rules' reading for each capability it finds; a person's own statement, the standard answers and the offering profile are never replaced. A model claim is kept only while its line is in the sources, and withdrawn when a later reading no longer returns it. What no source states has no claim.
- A claim the vendor has not reviewed is shown to buyers marked "read by RFX, not confirmed by the vendor". A rejected claim is never shown.
- Every read of level 2 or 3 evidence is logged with the buyer and the question, and the vendor can see it.
Negotiation
- The quote: the tier for the number of units gives the base fee. Each condition the buyer meets earns its discount (in the sample perimeter: 15% for signing within 14 days, 10% for three years, 5% for paying a year upfront), up to the ceiling (30%).
- On each buyer offer the hosted agent rebuilds the whole term sheet from the perimeter. It never copies the buyer's sheet. For each term it accepts, counters with the rule behind the change, offers the stated alternative (private cloud for on-premises), or sends it to a person.
- Deal states: negotiating, awaiting human approval, agent agreed, pending human sign-off, agreed. A buyer agent cannot accept while a person still has to rule.
- When the agents agree, RFX checks the terms and the terms-check fee becomes due from the vendor (with accounts on; without, the flat
RFX_LEAD_FEE_EUR). Until a person at the vendor pays it, the vendor sees the buyer's name and the deal's value, not the terms or the rounds, andvendor_signanswersPAYMENT_REQUIRED(HTTP 402). Paying twice charges once. The admin view totals the fees paid.
| Break-out | When | Who decides |
|---|---|---|
| B3 | The vendor has not approved the standard NDA in advance | Legal at the vendor |
| B4 | A buyer asks to negotiate | The RFP owner at the vendor |
| B5 | An RFP requirement no source answers | Sales or product at the vendor |
| B6 | A term outside the perimeter (a cap, liability) | The role the perimeter names |
| B7 | A commitment the perimeter has not approved in advance | Product at the vendor |
Contract and trail
- When both agents agree, each side's person signs. RFX then writes the order form from the accepted terms: parties, scope, fees, commitments with their dates and remedies, both signatures, and the last hash of the trail.
- It is delivered through
get_deal(fieldagreement_md) and on both logins. - The trail is a list of events, each tagged with one of the five capabilities (think, discover, trust, negotiate, transact) and chained by hash, so a changed or removed event is detectable.
get_trailreturns it.
Protocols
RFX is the intermediary, but agents still have to reach it, and protocols are how. The five open agent protocols, and where RFX stands on each:
| Protocol | What it is for | RFX today | Plan |
|---|---|---|---|
| MCP Model Context Protocol | An agent calls tools and reads data on a server | Supported. RFX is an MCP server at /mcp. It is also an MCP client: it reads a buyer's or a vendor's own MCP server | Keep as the main way in |
| A2A Agent2Agent | One agent finds another through an agent card and sends it messages | Supported (version 1.0): an agent card at /.well-known/agent-card.json; SendMessage at /a2a talks to the RFX assistant | Tasks and streaming later |
| ACP Agent Communication Protocol | Messages between agents over plain HTTP. It is being merged into A2A | Not supported | Covered by A2A |
| ANP Agent Network Protocol | Agents find each other on the open internet without a central party, using decentralised identifiers | Not supported | Not planned. RFX is the place where agents find each other. Its identifiers could later be a fourth way to prove a company |
| AG-UI Agent-User Interaction | An agent streams its work to a user interface | Not supported | Not planned. RFX is headless; people only log in to decide |
Two more matter for a marketplace. The negotiation messages (offer, counter, accept) are RFX's own; they borrow vocabulary from the A2CN draft but are not compatible with it, and an adapter in front of the deal room is planned. Payment protocols (AP2, the Agentic Commerce Protocol) would matter only if RFX carried the payment after signing, which it does not. Any agent that speaks none of these can use plain HTTP: POST /api/<tool>.
Settings
| Variable | Default | Effect |
|---|---|---|
RFX_MIN_IDENTITY | email | Minimum proof before an NDA. declared switches the check off |
RFX_AUDIENCE | rfx | The audience an identity-provider token must carry |
RFX_IDENTITY_HOSTS | For demos: maps a domain to another address for its key file | |
RFX_ALLOW_INSECURE_IDP | For tests only: allows an identity provider over http | |
RFX_ADMIN_KEY, RFX_KEY_MODULOS | demo values | The operator's key and the seeded vendor's key |
RFX_ALLOW_PRIVATE_FETCH, RFX_FETCH_ALLOW | off | 1 lets RFX fetch private addresses (development, where the mocks run on localhost). The list names hosts always allowed |
ANTHROPIC_API_KEY | Turns on the language-model reader and the assistant (RFX_ASSISTANT=off turns the assistant off). RFX_MODEL chooses the model (default claude-opus-5-5); RFX_MODEL_READER=off turns it off | |
RESEND_KEY | Mails verification codes through Resend. Without it codes appear in the admin view. RFX_MAIL_FROM sets the sender, on a domain verified in Resend (default RFX <verify@rfx.market>); RFX_MAIL_REPLY_TO sets where replies go | |
RESEND_WEBHOOK_SECRET | The signing secret of the Resend webhook at /webhooks/resend. Delivery, bounce and complaint events then show in the admin view | |
RFX_INTRO_FEE_EUR | 1000 | The lead (introduction) fee, due when an NDA is signed (accounts on). With the two below, it seeds the first pricing version only: after that the operator sets pricing on the RFX page, in versions |
RFX_TERMS_FEE_PCT, _MIN_EUR, _MAX_EUR | 5, 500, 10000 | The terms-check (contract) fee: a share of the first-year value with a floor and a ceiling. Seeds the first pricing version |
RFX_STARTER_DISCOVERIES | 2 | Free L2 discoveries per vendor company (Starter). Seeds the first pricing version |
RFX_BUYER_FREE_NEEDS, RFX_BUYER_PLAN_EUR | 2, 6000 | Free needs per buyer company a year (a need: one category it buys in), and the yearly buyer plan beyond them; 0 for the plan means buyers pay nothing. Seed the first pricing version |
RFX_PRICING_MIN_LEAD_EUR, _MIN_CONTRACT_EUR, _MIN_TAKE_PCT, _MAX_STARTER, _MIN_BUYER_FREE_NEEDS | 250, 500, 3, 3, 1 | The bar a pricing version must clear before the operator can apply it: lead fee, the least any contract pays, the take on first-year value in the reference case (Starter included), free discoveries, the free needs a buyer company has before the buyer plan. Set here so a version cannot lower its own bar |
RFX_COSTS_EUR_YEAR, RFX_PRICING_VENDORS, RFX_PRICING_BUYER_PLANS | 0, 20, 0 | Yearly costs to cover, paying vendors and buyer plans expected, for the first version's reference case. A version is applied only if it covers the costs it states |
RFX_FEE_MODEL | terms_check | flat keeps the earlier flat fee (RFX_LEAD_FEE_EUR, 1000) |
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | RFX's fees through Stripe Checkout (a test key gives test mode). The webhook points to /webhooks/stripe with the events checkout.session.completed, .async_payment_succeeded, .async_payment_failed and .expired; node --env-file=.env scripts/stripe-webhook.mjs --write-env makes it and stores its secret. STRIPE_TAX=on adds Stripe Tax, once the account has a head office and an active registration. Without a key payments are mock | |
RFX_SALES_EMAIL, RFX_CONTACT_EMAIL | sales@rfx.market, contact@rfx.market | The addresses on the pricing section, /pricing and the contact page |
RFX_OPERATOR_NAME | The operator named in the NDA and the vendor agreement | |
RFX_ALERT_EMAIL | Mail unexpected errors and database or backup problems to this address (at most once an hour per problem) | |
SENTRY_DSN | Send unexpected errors to Sentry (stack only, nothing about the request) | |
SENTRY_CRON_BACKUP, SENTRY_CRON_OFFSITE | Sentry Crons check-in URLs for the nightly backup and the off-site copy | |
RFX_RELEASE | The version shown in the monitor and sent with errors | |
RFX_FOLLOW_UP_DAYS | 90 | How long an introduction is quiet before RFX asks both companies whether they contracted outside RFX |
LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_HOST (or LANGFUSE_BASE_URL) | Trace RFX's work with the language model to Langfuse (its JS SDK; one trace per message to the assistant or per reading). RFX_LANGFUSE_CONTENT: assistant (the default: conversations whole, other calls as metadata), all or off | |
RFX_DATA_KEY, RFX_DATA_KEY_OLD | Encryption at rest (AES-256-GCM) of the database, the activity log, signed documents and vendors' files: 32 random bytes, base64. Required on an https address. Old keys (comma-separated) only open: to change the key, the new one here, the old one there, restart | |
RFX_PUBLIC_URL | https://rfx.market in production | The address given to agents and people. With an https address, RFX refuses to start while RFX_ADMIN_KEY or RFX_KEY_MODULOS is a demo value or shorter than 24 characters |
DATABASE_URL | Postgres holds the state. RFX works on an in-memory copy loaded at start and writes back only the rows that changed. Without it the state is kept in memory only and lost when RFX stops (tests, short local runs) | |
RFX_SAVE_MIN_MS, RFX_GRAPH_SECONDS | 100, 60 | Saves after requests are shared: at most one every RFX_SAVE_MIN_MS, spaced further as the state grows. The marketplace graph is written to Postgres at most this many seconds behind |
POSTGRES_PASSWORD, RFX_DB_PASSWORD | In production: the superuser's password and the password of rfx_app, the role RFX connects as | |
PORT | 4545 | The port |
Running it on rfx.market
- RFX runs on a server behind a Cloudflare tunnel: no port is opened on the host, and Cloudflare provides HTTPS.
- Create the tunnel once with
cloudflared tunnel create rfx-marketandcloudflared tunnel route dns rfx-market rfx.market, and put its credentials file andconfig.yml(seedeploy/cloudflared-config.example.yml) in./cloudflaredon the server. - Put the secrets in
.env:RFX_ADMIN_KEYandRFX_KEY_MODULOS(long random values),ANTHROPIC_API_KEY, and when readyRESEND_KEY(with rfx.market verified in Resend) and the Yousign key. - Run
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build. The state is in Postgres, in its own container on a network with no route outside; RFX connects asrfx_app, which owns its tables and nothing else. Vendors' sources and uploads are on volumes. Vocabularies and templates come from the image, so a rebuild brings their changes. - Backups: Postgres archives its write-ahead log continuously (at most 60 seconds behind), and a base backup and a SQL dump are taken every night at 02:00 UTC; 14 days are kept.
deploy/pitr-restore.sh 2026-11-14T10:30:00Zreturns the database to that moment (UTC), keeping the current data beside it;latestreplays everything archived;listshows what can be restored. Backups are on the same machine: copying them elsewhere is the next step.
What is mock or missing
- Signatures. The NDA goes through Yousign when
RFX_ESIGN=yousign; production runs with a sandbox key, so NDAs are not binding until a production key is set. The order form goes the same way: when both sides approve, one PDF goes to both signers, and the signed copy and the provider's audit trail are kept and mailed to them. - Payments go through Stripe Checkout once
STRIPE_SECRET_KEYandSTRIPE_WEBHOOK_SECRETare set (an invoice per payment; VAT handling still to set up in Stripe); until then they are mock. The contract between buyer and vendor is paid between them, outside RFX. The vendor agreement and the NDA's non-circumvention clause are drafts for counsel. A real payment provider, or later a smart-contract escrow, is not connected. - People's logins. Done: sign-in by work email, roles, company-issued agent keys kept as a hash. Single sign-on through the company's identity provider (Google Workspace, Microsoft Entra ID, Okta, SAML or OIDC) is a placeholder: the buttons on the sign-in box explain what it will do, and a company admin can tell RFX which provider it uses. Not yet: single sign-on itself, SCIM, passkeys.
- Data at rest is encrypted with
RFX_DATA_KEY: the database (Postgres keeps only structure), the activity log, signed documents in R2 and vendors' files. The key is in the server's.env, beside the data, and the server's disk itself is not encrypted: the encryption protects the database, its backups and the storage bucket, not a server that is broken into. - Listings come from a mock of the licensed provider's API (
docs/CATALOG_API.md). The mock listings carry no website, so a vendor is verified on the website it gives when it claims. The compliance platform is a mock too. - Identity providers are tested against a local provider, not a live Okta or Entra tenant. Tokens are not bound to a key held by the agent.
- The model reader has been tried on one real vendor's documentation (Modulos), not yet on others. Its claims need a person at the vendor to confirm them before they show as confirmed.
- AI security and CRM have vocabularies and listings but no claimed vendor.
- The assistant keeps a conversation in memory for two hours, so a restart ends it and the person starts a new one. With Langfuse on, every conversation is kept there whole.
- A2A: the agent card and
SendMessageare built; tasks and streaming are not. People as assets are not built. - Running it. One RFX process on one server. Errors, failed backups and a database that cannot be written are mailed to the operator, and the tests run on every push; nothing outside the server notices when the whole server is down. The off-site backups restore onto a fresh database (
deploy/restore-drill.sh, last passed 14 November 2026, encrypted). - Legal and privacy. Terms of use, a privacy notice, a data processing agreement and a contact page are placeholders (
/terms,/privacy,/dpa,/contact, fromsrc/legal.mjs): the facts that follow from the code are written in, the rest is for counsel. A company's admin can download all its data and close the account (which keeps signed agreements and the record).
RFX in the middle, from first contact to a signed deal
The buyer and the vendor never deal with each other directly: every message, document and offer passes through RFX, which checks it, shares only what the other side may see, and records it. Each side connects its own systems with its own credentials. RFX never runs the buyer's agent and never sees its budget or mandate.
Three levels, each with its own lock
| Level | What opens | What unlocks it |
|---|---|---|
| 1 · Public | Catalog, vendor profile, RFI, a first shortlist | Nothing |
| 2 · RFP | RFP answers with their source, evidence, capability detail | Both companies verified, then the NDA signed by a person on each side |
| 3 · Deal | Pricing, negotiation, contracting | A person at the vendor opens it for this buyer |
An NDA covers one buyer and one vendor. If negotiation is denied or runs out, level 2 stays open. An agent asks where it stands with check_access.
If you are a vendor
- Connect your agent as shown on the main page. It claims your listing with
vendor_claim. - Prove which company you are. Until then no NDA can be signed with you.
- Connect your sources: documents, a GitHub repository, your compliance platform.
- A person at your company reviews what RFX read and publishes it.
- Set your perimeter: what your agent may offer, and who decides the rest.
- Your people log in here whenever a decision is theirs: the NDA, opening negotiation, a term, the deal.
If you are a buyer
- Connect your agent as shown on the main page. It registers with
register_buyerand can read level 1 at once. - Prove which company it acts for. Without that, no NDA.
- A person signs the NDA here. That opens the RFP with this vendor.
- Connect your own systems with
buyer_connect, or state your requirements, and send the RFP. - For pricing, your agent asks to negotiate and a person at the vendor decides.
- Your agent negotiates inside the limits you gave it. You sign.
Proving which company an agent acts for
| Way | How | Who can revoke |
|---|---|---|
| Identity provider | The company names its provider (Okta, Microsoft Entra, any OpenID Connect provider) on its website. The agent presents a token from it | The company, in its identity provider |
| Published key | The company lists the agent's public key on its website. The agent signs a challenge | The company, by removing the line |
| Work-email code | A person at the company's domain receives a code and gives it to the agent | Nobody. Weaker: a vendor can refuse it |
The file is /.well-known/rfx-agents.json on the company's own domain. It applies to buyers and to vendors.
Connectors
| Connector | For | You give | You get |
|---|---|---|---|
| Documents (PDF, Markdown, text) | Vendor | The files | Capabilities, each with the line that states it |
| GitHub | Vendor | A repository, and your own read-only token if it is private | Capabilities from the documentation. Source code only if you ask, and only behind the NDA |
| Compliance platform (mock of Drata) | Vendor | Its API key | Certifications, security testing and policies |
| Your own MCP server | Vendor | Its address and token | Capabilities from what its read-only tools return |
| Your own MCP server | Buyer | Its address and token | Requirements, and facts about your environment that you choose to share |
| Google Drive, SharePoint, Confluence | Vendor | An MCP server for it, connected when your company is set up | As for your own MCP server |
A credential you give stays with your connection and is never shown again. An agent gets this list from list_connectors.