{"openapi":"3.1.0","info":{"title":"REFERRALS.md API","version":"1.1","description":"An open file format for professional referrals between agents, people and firms: who serves what, who needs what, who referred whom, what was disclosed and how it went. Because every agent keeps the same file, the files compose into a referral graph. Read-only tools match a need to candidates, score a referrer, map the graph, route a request and lint a file, from the records you send only. Read-only, no key, nothing stored. Use at your own risk. referrals.md is provided as is, without warranty. Information here may be inaccurate, incomplete or not current, and is not verified; figures are estimates from the numbers you send and the formulas stated. Nothing here is professional, legal, financial, employment or hiring advice, and no referral or score here is an endorsement or a verification of anyone. referrals.md never contacts anyone, sends a referral or pays a fee itself. Check everything yourself before you act on it.","termsOfService":"https://referrals.md/terms","license":{"name":"MIT","identifier":"MIT"}},"servers":[{"url":"https://referrals.md"}],"paths":{"/referrals.md":{"get":{"operationId":"getSkill","summary":"The REFERRALS.md skill as Markdown. Add a preset or any setting to write your settings in; notes on clamped or ignored values come back in the X-Settings-Notes header.","parameters":[{"name":"preset","in":"query","required":false,"schema":{"type":"string","enum":["core","recommended","strict"]}},{"name":"ask_before_passing","in":"query","required":false,"description":"Ask the person before passing a referral: always (Always), new_party (Only to a party not in the file yet), never (Never). Default always.","schema":{"type":"string","enum":["always","new_party","never"]}},{"name":"disclosure_required","in":"query","required":false,"description":"Disclosure line required on every referral: yes (Yes), no (No). Default yes.","schema":{"type":"string","enum":["yes","no"]}},{"name":"fees_allowed","in":"query","required":false,"description":"Referral fees: allowed (Allowed if disclosed), not_allowed (Not allowed). Default allowed.","schema":{"type":"string","enum":["allowed","not_allowed"]}},{"name":"max_hops","in":"query","required":false,"description":"Max hops a referral may travel (hops). Default 3. Out-of-range values are clamped.","schema":{"type":"integer","minimum":1,"maximum":6}},{"name":"list_people","in":"query","required":false,"description":"List a person as a party: with_consent (Only with their consent), never (Never, agents and firms only). Optional; unset clears it.","schema":{"type":"string","enum":["with_consent","never","unset"]}},{"name":"min_match_score","in":"query","required":false,"description":"Lowest match score worth passing (points). Optional; unset clears it. Out-of-range values are clamped.","schema":{"type":"integer","minimum":0,"maximum":100}},{"name":"budget_tolerance_pct","in":"query","required":false,"description":"Budget fit tolerance (%). Optional; unset clears it. Out-of-range values are clamped.","schema":{"type":"integer","minimum":0,"maximum":100}},{"name":"stale_after_days","in":"query","required":false,"description":"An open need is stale after (days). Optional; unset clears it. Out-of-range values are clamped.","schema":{"type":"integer","minimum":7,"maximum":365}},{"name":"half_life_days","in":"query","required":false,"description":"Half-life of a past referral's weight (days). Default 180. Out-of-range values are clamped.","schema":{"type":"integer","minimum":30,"maximum":3650}},{"name":"min_referrals","in":"query","required":false,"description":"Referrals before a score counts (referrals). Default 3. Out-of-range values are clamped.","schema":{"type":"integer","minimum":1,"maximum":50}},{"name":"weight_acceptance","in":"query","required":false,"description":"Weight of the acceptance rate (x). Optional; unset clears it. Out-of-range values are clamped.","schema":{"type":"integer","minimum":0,"maximum":10}},{"name":"weight_completion","in":"query","required":false,"description":"Weight of the completion rate (x). Optional; unset clears it. Out-of-range values are clamped.","schema":{"type":"integer","minimum":0,"maximum":10}},{"name":"weight_outcome","in":"query","required":false,"description":"Weight of the mean outcome rating (x). Optional; unset clears it. Out-of-range values are clamped.","schema":{"type":"integer","minimum":0,"maximum":10}},{"name":"rated_by","in":"query","required":false,"description":"Who rates an outcome: asker (The party that asked), receiver (The party that received it), both (Both, averaged). Optional; unset clears it.","schema":{"type":"string","enum":["asker","receiver","both","unset"]}}],"responses":{"200":{"description":"The skill","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/api/v1/settings":{"get":{"operationId":"listSettings","summary":"Every setting (group, kind, unit, range, options, default) and the presets.","responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}}}}},"/api/v1/match_need":{"get":{"operationId":"matchNeedGet","summary":"Match a need to candidate parties","description":"Ranks candidate parties for one need with an explained score out of 100: category served (40), requirements met (20), area (20), capacity (10) and budget fit (10), from the records you send only. Send the need and the parties as lists, or a REFERRALS.md file as text with need_id. The party that asked is left out. A match is a comparison of records, not an endorsement; the tool contacts nobody.","parameters":[{"name":"need","in":"query","required":false,"description":"The need: {category, budget (\"500-1500\"), where (an area or \"remote\"), by (a date), requirements (tags), from (the asking party's id)}. Or send text and need_id. URL-encoded JSON.","schema":{"type":"string"}},{"name":"need_id","in":"query","required":false,"description":"The id of a need in text, instead of need.","schema":{"type":"string"}},{"name":"parties","in":"query","required":false,"description":"Candidate parties: [{id, kind, categories, tags, areas, capacity, budget, url}]. Added to any parties in text. URL-encoded JSON.","schema":{"type":"string"}},{"name":"text","in":"query","required":false,"description":"A REFERRALS.md file: its Parties (and the need named by need_id) are used.","schema":{"type":"string"}},{"name":"files","in":"query","required":false,"description":"Several REFERRALS.md files, merged (the file with the later updated date wins an id). URL-encoded JSON.","schema":{"type":"string"}},{"name":"tolerance_pct","in":"query","required":false,"description":"Budget gap, as a percent of the need's nearest bound, that still earns half the budget points. Default 20.","schema":{"type":"number","minimum":0,"maximum":100}},{"name":"min_score","in":"query","required":false,"description":"The score a match needs to pass. Default 50.","schema":{"type":"number","minimum":0,"maximum":100}},{"name":"limit","in":"query","required":false,"description":"How many matches to return. Default 10.","schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"matchNeed","summary":"Match a need to candidate parties","description":"Ranks candidate parties for one need with an explained score out of 100: category served (40), requirements met (20), area (20), capacity (10) and budget fit (10), from the records you send only. Send the need and the parties as lists, or a REFERRALS.md file as text with need_id. The party that asked is left out. A match is a comparison of records, not an endorsement; the tool contacts nobody.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"need":{"type":"object","description":"The need: {category, budget (\"500-1500\"), where (an area or \"remote\"), by (a date), requirements (tags), from (the asking party's id)}. Or send text and need_id."},"need_id":{"type":"string","maxLength":64,"description":"The id of a need in text, instead of need."},"parties":{"type":"array","maxItems":200,"items":{"type":"object"},"description":"Candidate parties: [{id, kind, categories, tags, areas, capacity, budget, url}]. Added to any parties in text."},"text":{"type":"string","maxLength":60000,"description":"A REFERRALS.md file: its Parties (and the need named by need_id) are used."},"files":{"type":"array","maxItems":20,"items":{"type":"string"},"description":"Several REFERRALS.md files, merged (the file with the later updated date wins an id)."},"tolerance_pct":{"type":"number","minimum":0,"maximum":100,"default":20,"description":"Budget gap, as a percent of the need's nearest bound, that still earns half the budget points. Default 20."},"min_score":{"type":"number","minimum":0,"maximum":100,"default":50,"description":"The score a match needs to pass. Default 50."},"limit":{"type":"integer","minimum":1,"maximum":200,"default":10,"description":"How many matches to return. Default 10."}},"required":[]},"example":{"need":{"id":"need-2026-10-20-ops-helper-1","from":"agent:ops-helper","category":"tax-filing","budget":"500-1500","where":"remote","by":"2026-11-30","requirements":"multi-currency"},"parties":[{"id":"agent:ops-helper","kind":"agent","categories":"bookkeeping, payroll","areas":"remote","capacity":2},{"id":"agent:tax-scout","kind":"agent","categories":"tax-filing","areas":"remote","capacity":0},{"id":"firm:ledger-studio","kind":"firm","categories":"bookkeeping, tax-filing","tags":"multi-currency","areas":"example-city, remote","capacity":1,"budget":"400-3000"},{"id":"person:river-k","kind":"person","categories":"tax-filing","areas":"example-city","capacity":1,"budget":"2000-8000"}],"tolerance_pct":20,"min_score":70}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64 KB"}}}},"/api/v1/score_referrer":{"get":{"operationId":"scoreReferrerGet","summary":"Score a referrer from its referrals","description":"From the referrals a party passed (in a REFERRALS.md file or a list), works out its acceptance rate, completion rate and mean outcome rating, each weighted by a time decay with the half-life you set, and combines them with your weights into a score out of 100, with a confidence from the decayed count and your minimum sample. Without party, scores every referrer and ranks them. Optionally within one category. A score compares the records you send; it is not an endorsement or a verification of anyone.","parameters":[{"name":"text","in":"query","required":false,"description":"A REFERRALS.md file (its Referrals and Needs are used).","schema":{"type":"string"}},{"name":"files","in":"query","required":false,"description":"Several REFERRALS.md files, merged. URL-encoded JSON.","schema":{"type":"string"}},{"name":"referrals","in":"query","required":false,"description":"Referrals as a list: [{id, need, from, to, date, status, outcome, category}]. Added to any in text. URL-encoded JSON.","schema":{"type":"string"}},{"name":"needs","in":"query","required":false,"description":"Needs as a list, so referrals can be placed in a category. URL-encoded JSON.","schema":{"type":"string"}},{"name":"party","in":"query","required":false,"description":"The referrer to score. Without it, every referrer is scored and ranked.","schema":{"type":"string"}},{"name":"category","in":"query","required":false,"description":"Score only referrals for needs in this category.","schema":{"type":"string"}},{"name":"as_of","in":"query","required":false,"description":"The date ages are measured from. Default today.","schema":{"type":"string"}},{"name":"half_life_days","in":"query","required":false,"description":"Days after which a referral's weight halves. Default 180.","schema":{"type":"number","minimum":1,"maximum":3650}},{"name":"min_referrals","in":"query","required":false,"description":"Answered referrals before a score counts. Default 3.","schema":{"type":"integer","minimum":1,"maximum":50}},{"name":"weight_acceptance","in":"query","required":false,"description":"Weight of the acceptance rate. Default 1.","schema":{"type":"number","minimum":0,"maximum":10}},{"name":"weight_completion","in":"query","required":false,"description":"Weight of the completion rate. Default 1.","schema":{"type":"number","minimum":0,"maximum":10}},{"name":"weight_outcome","in":"query","required":false,"description":"Weight of the mean outcome rating. Default 2.","schema":{"type":"number","minimum":0,"maximum":10}},{"name":"limit","in":"query","required":false,"description":"How many referrers to return when party is not given. Default 20.","schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"scoreReferrer","summary":"Score a referrer from its referrals","description":"From the referrals a party passed (in a REFERRALS.md file or a list), works out its acceptance rate, completion rate and mean outcome rating, each weighted by a time decay with the half-life you set, and combines them with your weights into a score out of 100, with a confidence from the decayed count and your minimum sample. Without party, scores every referrer and ranks them. Optionally within one category. A score compares the records you send; it is not an endorsement or a verification of anyone.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":60000,"description":"A REFERRALS.md file (its Referrals and Needs are used)."},"files":{"type":"array","maxItems":20,"items":{"type":"string"},"description":"Several REFERRALS.md files, merged."},"referrals":{"type":"array","maxItems":200,"items":{"type":"object"},"description":"Referrals as a list: [{id, need, from, to, date, status, outcome, category}]. Added to any in text."},"needs":{"type":"array","maxItems":200,"items":{"type":"object"},"description":"Needs as a list, so referrals can be placed in a category."},"party":{"type":"string","maxLength":64,"description":"The referrer to score. Without it, every referrer is scored and ranked."},"category":{"type":"string","maxLength":40,"description":"Score only referrals for needs in this category."},"as_of":{"type":"string","maxLength":10,"description":"The date ages are measured from. Default today."},"half_life_days":{"type":"number","minimum":1,"maximum":3650,"default":180,"description":"Days after which a referral's weight halves. Default 180."},"min_referrals":{"type":"integer","minimum":1,"maximum":50,"default":3,"description":"Answered referrals before a score counts. Default 3."},"weight_acceptance":{"type":"number","minimum":0,"maximum":10,"default":1,"description":"Weight of the acceptance rate. Default 1."},"weight_completion":{"type":"number","minimum":0,"maximum":10,"default":1,"description":"Weight of the completion rate. Default 1."},"weight_outcome":{"type":"number","minimum":0,"maximum":10,"default":2,"description":"Weight of the mean outcome rating. Default 2."},"limit":{"type":"integer","minimum":1,"maximum":200,"default":20,"description":"How many referrers to return when party is not given. Default 20."}},"required":[]},"example":{"referrals":[{"id":"ref-2026-04-25-tax-scout-1","from":"agent:tax-scout","to":"firm:ledger-studio","category":"tax-filing","date":"2026-04-25","status":"completed","outcome":5},{"id":"ref-2025-10-27-tax-scout-1","from":"agent:tax-scout","to":"person:river-k","category":"tax-filing","date":"2025-10-27","status":"completed","outcome":3},{"id":"ref-2026-10-22-tax-scout-1","from":"agent:tax-scout","to":"firm:ledger-studio","category":"tax-filing","date":"2026-10-22","status":"accepted"},{"id":"ref-2026-04-25-tax-scout-2","from":"agent:tax-scout","to":"agent:ops-helper","category":"bookkeeping","date":"2026-04-25","status":"completed","outcome":4},{"id":"ref-2026-10-22-ledger-studio-1","from":"firm:ledger-studio","to":"person:river-k","category":"tax-filing","date":"2026-10-22","status":"declined"},{"id":"ref-2026-04-25-ops-helper-1","from":"agent:ops-helper","to":"agent:tax-scout","category":"tax-filing","date":"2026-04-25","status":"completed","outcome":4}],"party":"agent:tax-scout","as_of":"2026-10-22","half_life_days":180,"min_referrals":3}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64 KB"}}}},"/api/v1/referral_graph":{"get":{"operationId":"referralGraphGet","summary":"Map the referral graph","description":"From the referral records (a REFERRALS.md file, several files, or a list): the parties as nodes, who referred whom as edges with counts, in and out degree per party, reciprocity, the top connectors, the number of connected groups and the density. With from and to, the shortest referral path between two parties; with party and max_hops, every party reachable within that many hops. Paths are undirected (a referral either way links two parties) unless directed is true. A map of records, not of anyone's real relationships.","parameters":[{"name":"text","in":"query","required":false,"description":"A REFERRALS.md file.","schema":{"type":"string"}},{"name":"files","in":"query","required":false,"description":"Several REFERRALS.md files, merged into one graph. URL-encoded JSON.","schema":{"type":"string"}},{"name":"referrals","in":"query","required":false,"description":"Referrals as a list: [{from, to, need, date, status}]. URL-encoded JSON.","schema":{"type":"string"}},{"name":"parties","in":"query","required":false,"description":"Parties as a list, so nodes carry a kind and count as listed. URL-encoded JSON.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"With to: the start of the shortest path.","schema":{"type":"string"}},{"name":"to","in":"query","required":false,"description":"With from: the end of the shortest path.","schema":{"type":"string"}},{"name":"party","in":"query","required":false,"description":"List the parties within max_hops of this one.","schema":{"type":"string"}},{"name":"max_hops","in":"query","required":false,"description":"How far to walk from party. Default 3.","schema":{"type":"integer","minimum":1,"maximum":20}},{"name":"directed","in":"query","required":false,"description":"Follow referrals only in the direction they were passed. Default false.","schema":{"type":"boolean"}},{"name":"top","in":"query","required":false,"description":"How many top connectors to list. Default 5.","schema":{"type":"integer","minimum":1,"maximum":50}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"referralGraph","summary":"Map the referral graph","description":"From the referral records (a REFERRALS.md file, several files, or a list): the parties as nodes, who referred whom as edges with counts, in and out degree per party, reciprocity, the top connectors, the number of connected groups and the density. With from and to, the shortest referral path between two parties; with party and max_hops, every party reachable within that many hops. Paths are undirected (a referral either way links two parties) unless directed is true. A map of records, not of anyone's real relationships.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":60000,"description":"A REFERRALS.md file."},"files":{"type":"array","maxItems":20,"items":{"type":"string"},"description":"Several REFERRALS.md files, merged into one graph."},"referrals":{"type":"array","maxItems":200,"items":{"type":"object"},"description":"Referrals as a list: [{from, to, need, date, status}]."},"parties":{"type":"array","maxItems":200,"items":{"type":"object"},"description":"Parties as a list, so nodes carry a kind and count as listed."},"from":{"type":"string","maxLength":64,"description":"With to: the start of the shortest path."},"to":{"type":"string","maxLength":64,"description":"With from: the end of the shortest path."},"party":{"type":"string","maxLength":64,"description":"List the parties within max_hops of this one."},"max_hops":{"type":"integer","minimum":1,"maximum":20,"default":3,"description":"How far to walk from party. Default 3."},"directed":{"type":"boolean","default":false,"description":"Follow referrals only in the direction they were passed. Default false."},"top":{"type":"integer","minimum":1,"maximum":50,"default":5,"description":"How many top connectors to list. Default 5."}},"required":[]},"example":{"parties":[{"id":"agent:ops-helper","kind":"agent","categories":"bookkeeping, payroll","areas":"remote","capacity":2},{"id":"agent:tax-scout","kind":"agent","categories":"tax-filing","areas":"remote","capacity":0},{"id":"firm:ledger-studio","kind":"firm","categories":"bookkeeping, tax-filing","tags":"multi-currency","areas":"example-city, remote","capacity":1,"budget":"400-3000"},{"id":"person:river-k","kind":"person","categories":"tax-filing","areas":"example-city","capacity":1,"budget":"2000-8000"}],"referrals":[{"id":"ref-2026-04-25-tax-scout-1","from":"agent:tax-scout","to":"firm:ledger-studio","category":"tax-filing","date":"2026-04-25","status":"completed","outcome":5},{"id":"ref-2025-10-27-tax-scout-1","from":"agent:tax-scout","to":"person:river-k","category":"tax-filing","date":"2025-10-27","status":"completed","outcome":3},{"id":"ref-2026-10-22-tax-scout-1","from":"agent:tax-scout","to":"firm:ledger-studio","category":"tax-filing","date":"2026-10-22","status":"accepted"},{"id":"ref-2026-04-25-tax-scout-2","from":"agent:tax-scout","to":"agent:ops-helper","category":"bookkeeping","date":"2026-04-25","status":"completed","outcome":4},{"id":"ref-2026-10-22-ledger-studio-1","from":"firm:ledger-studio","to":"person:river-k","category":"tax-filing","date":"2026-10-22","status":"declined"},{"id":"ref-2026-04-25-ops-helper-1","from":"agent:ops-helper","to":"agent:tax-scout","category":"tax-filing","date":"2026-04-25","status":"completed","outcome":4}],"from":"agent:ops-helper","to":"person:river-k","party":"agent:ops-helper","max_hops":2}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64 KB"}}}},"/api/v1/route_need":{"get":{"operationId":"routeNeedGet","summary":"Who to ask for a referral","description":"For a need, says which parties to ask for a referral: the parties within max_hops of the asker in the referral graph, ranked by their referrer score in the need's category times its confidence, then by hops. Parties that serve the category themselves are flagged. Send a REFERRALS.md file (or lists) with the need's id, or the need as an object, and the asker. When the asker has no referrals yet, the listed parties that serve the category are offered instead. A ranking of records, not an introduction; the tool contacts nobody.","parameters":[{"name":"text","in":"query","required":false,"description":"A REFERRALS.md file.","schema":{"type":"string"}},{"name":"files","in":"query","required":false,"description":"Several REFERRALS.md files, merged. URL-encoded JSON.","schema":{"type":"string"}},{"name":"parties","in":"query","required":false,"description":"Parties as a list. URL-encoded JSON.","schema":{"type":"string"}},{"name":"needs","in":"query","required":false,"description":"Needs as a list. URL-encoded JSON.","schema":{"type":"string"}},{"name":"referrals","in":"query","required":false,"description":"Referrals as a list. URL-encoded JSON.","schema":{"type":"string"}},{"name":"need_id","in":"query","required":false,"description":"The id of the need, in text or needs.","schema":{"type":"string"}},{"name":"need","in":"query","required":false,"description":"Or the need itself: {category, from, budget, where, by, requirements}. URL-encoded JSON.","schema":{"type":"string"}},{"name":"asker","in":"query","required":false,"description":"The party asking. Default: the need's from.","schema":{"type":"string"}},{"name":"max_hops","in":"query","required":false,"description":"How far from the asker to look. Default 3.","schema":{"type":"integer","minimum":1,"maximum":20}},{"name":"directed","in":"query","required":false,"description":"Follow referrals only in the direction they were passed. Default false.","schema":{"type":"boolean"}},{"name":"category","in":"query","required":false,"description":"Override the category to score referrers in. Default: the need's.","schema":{"type":"string"}},{"name":"as_of","in":"query","required":false,"description":"The date ages are measured from. Default today.","schema":{"type":"string"}},{"name":"half_life_days","in":"query","required":false,"description":"Days after which a referral's weight halves. Default 180.","schema":{"type":"number","minimum":1,"maximum":3650}},{"name":"min_referrals","in":"query","required":false,"description":"Answered referrals before a score counts. Default 3.","schema":{"type":"integer","minimum":1,"maximum":50}},{"name":"weight_acceptance","in":"query","required":false,"description":"Weight of the acceptance rate. Default 1.","schema":{"type":"number","minimum":0,"maximum":10}},{"name":"weight_completion","in":"query","required":false,"description":"Weight of the completion rate. Default 1.","schema":{"type":"number","minimum":0,"maximum":10}},{"name":"weight_outcome","in":"query","required":false,"description":"Weight of the mean outcome rating. Default 2.","schema":{"type":"number","minimum":0,"maximum":10}},{"name":"limit","in":"query","required":false,"description":"How many candidates to return. Default 10.","schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"routeNeed","summary":"Who to ask for a referral","description":"For a need, says which parties to ask for a referral: the parties within max_hops of the asker in the referral graph, ranked by their referrer score in the need's category times its confidence, then by hops. Parties that serve the category themselves are flagged. Send a REFERRALS.md file (or lists) with the need's id, or the need as an object, and the asker. When the asker has no referrals yet, the listed parties that serve the category are offered instead. A ranking of records, not an introduction; the tool contacts nobody.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":60000,"description":"A REFERRALS.md file."},"files":{"type":"array","maxItems":20,"items":{"type":"string"},"description":"Several REFERRALS.md files, merged."},"parties":{"type":"array","maxItems":200,"items":{"type":"object"},"description":"Parties as a list."},"needs":{"type":"array","maxItems":200,"items":{"type":"object"},"description":"Needs as a list."},"referrals":{"type":"array","maxItems":200,"items":{"type":"object"},"description":"Referrals as a list."},"need_id":{"type":"string","maxLength":64,"description":"The id of the need, in text or needs."},"need":{"type":"object","description":"Or the need itself: {category, from, budget, where, by, requirements}."},"asker":{"type":"string","maxLength":64,"description":"The party asking. Default: the need's from."},"max_hops":{"type":"integer","minimum":1,"maximum":20,"default":3,"description":"How far from the asker to look. Default 3."},"directed":{"type":"boolean","default":false,"description":"Follow referrals only in the direction they were passed. Default false."},"category":{"type":"string","maxLength":40,"description":"Override the category to score referrers in. Default: the need's."},"as_of":{"type":"string","maxLength":10,"description":"The date ages are measured from. Default today."},"half_life_days":{"type":"number","minimum":1,"maximum":3650,"default":180,"description":"Days after which a referral's weight halves. Default 180."},"min_referrals":{"type":"integer","minimum":1,"maximum":50,"default":3,"description":"Answered referrals before a score counts. Default 3."},"weight_acceptance":{"type":"number","minimum":0,"maximum":10,"default":1,"description":"Weight of the acceptance rate. Default 1."},"weight_completion":{"type":"number","minimum":0,"maximum":10,"default":1,"description":"Weight of the completion rate. Default 1."},"weight_outcome":{"type":"number","minimum":0,"maximum":10,"default":2,"description":"Weight of the mean outcome rating. Default 2."},"limit":{"type":"integer","minimum":1,"maximum":200,"default":10,"description":"How many candidates to return. Default 10."}},"required":[]},"example":{"need":{"id":"need-2026-10-20-ops-helper-1","from":"agent:ops-helper","category":"tax-filing","budget":"500-1500","where":"remote","by":"2026-11-30","requirements":"multi-currency"},"parties":[{"id":"agent:ops-helper","kind":"agent","categories":"bookkeeping, payroll","areas":"remote","capacity":2},{"id":"agent:tax-scout","kind":"agent","categories":"tax-filing","areas":"remote","capacity":0},{"id":"firm:ledger-studio","kind":"firm","categories":"bookkeeping, tax-filing","tags":"multi-currency","areas":"example-city, remote","capacity":1,"budget":"400-3000"},{"id":"person:river-k","kind":"person","categories":"tax-filing","areas":"example-city","capacity":1,"budget":"2000-8000"}],"referrals":[{"id":"ref-2026-04-25-tax-scout-1","from":"agent:tax-scout","to":"firm:ledger-studio","category":"tax-filing","date":"2026-04-25","status":"completed","outcome":5},{"id":"ref-2025-10-27-tax-scout-1","from":"agent:tax-scout","to":"person:river-k","category":"tax-filing","date":"2025-10-27","status":"completed","outcome":3},{"id":"ref-2026-10-22-tax-scout-1","from":"agent:tax-scout","to":"firm:ledger-studio","category":"tax-filing","date":"2026-10-22","status":"accepted"},{"id":"ref-2026-04-25-tax-scout-2","from":"agent:tax-scout","to":"agent:ops-helper","category":"bookkeeping","date":"2026-04-25","status":"completed","outcome":4},{"id":"ref-2026-10-22-ledger-studio-1","from":"firm:ledger-studio","to":"person:river-k","category":"tax-filing","date":"2026-10-22","status":"declined"},{"id":"ref-2026-04-25-ops-helper-1","from":"agent:ops-helper","to":"agent:tax-scout","category":"tax-filing","date":"2026-04-25","status":"completed","outcome":4}],"max_hops":3,"as_of":"2026-10-22"}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64 KB"}}}},"/api/v1/check_referrals":{"get":{"operationId":"checkReferralsGet","summary":"Lint a REFERRALS.md file","description":"Reads one REFERRALS.md file (sent as text), a book or a profile, and returns findings with line numbers. For a book: format errors, referrals with no disclosed line when disclosure is required, fees disclosed where the rules forbid them, needs without parameters, referrals to or from a party that is not listed or for a need that is not listed, self-referrals and loops, chains over max_hops, stale open needs, by dates that passed, unanswered referrals, completed referrals with no rating, dates out of order or in the future, a party with no kind, a file with no updated date, reply-by dates that passed, packet ids that don't match the referral line, and text that reads like an instruction; the file's Rules section wins, arguments fill in, the defaults come last. For a profile (kind: profile, or a ## Profile section): the id, kind, categories, an https send endpoint or a contact pointer, the disclosure policy, fees against the disclosure, private details on a person, and sections a profile should not carry. It reports; it never changes the file, blocks anything or checks who anyone is.","parameters":[{"name":"text","in":"query","required":true,"description":"The REFERRALS.md file.","schema":{"type":"string"}},{"name":"as_of","in":"query","required":false,"description":"The date staleness is measured from. Default today.","schema":{"type":"string"}},{"name":"disclosure_required","in":"query","required":false,"description":"Require a disclosed line on every referral, when the file's Rules don't say. Default true.","schema":{"type":"boolean"}},{"name":"fees_allowed","in":"query","required":false,"description":"Allow fees in disclosed lines, when the file's Rules don't say. Default true.","schema":{"type":"boolean"}},{"name":"max_hops","in":"query","required":false,"description":"Longest allowed referral chain, when the file's Rules don't say. Default 3.","schema":{"type":"integer","minimum":1,"maximum":20}},{"name":"stale_after_days","in":"query","required":false,"description":"Days before an open need or an unanswered referral is stale, when the file's Rules don't say. Default 30.","schema":{"type":"integer","minimum":1,"maximum":3650}},{"name":"kind","in":"query","required":false,"description":"book, profile, or auto: the frontmatter's kind line, else a ## Profile heading, else book. Default auto.","schema":{"type":"string","enum":["auto","book","profile"]}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"checkReferrals","summary":"Lint a REFERRALS.md file","description":"Reads one REFERRALS.md file (sent as text), a book or a profile, and returns findings with line numbers. For a book: format errors, referrals with no disclosed line when disclosure is required, fees disclosed where the rules forbid them, needs without parameters, referrals to or from a party that is not listed or for a need that is not listed, self-referrals and loops, chains over max_hops, stale open needs, by dates that passed, unanswered referrals, completed referrals with no rating, dates out of order or in the future, a party with no kind, a file with no updated date, reply-by dates that passed, packet ids that don't match the referral line, and text that reads like an instruction; the file's Rules section wins, arguments fill in, the defaults come last. For a profile (kind: profile, or a ## Profile section): the id, kind, categories, an https send endpoint or a contact pointer, the disclosure policy, fees against the disclosure, private details on a person, and sections a profile should not carry. It reports; it never changes the file, blocks anything or checks who anyone is.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":60000,"description":"The REFERRALS.md file."},"as_of":{"type":"string","maxLength":10,"description":"The date staleness is measured from. Default today."},"disclosure_required":{"type":"boolean","description":"Require a disclosed line on every referral, when the file's Rules don't say. Default true."},"fees_allowed":{"type":"boolean","description":"Allow fees in disclosed lines, when the file's Rules don't say. Default true."},"max_hops":{"type":"integer","minimum":1,"maximum":20,"description":"Longest allowed referral chain, when the file's Rules don't say. Default 3."},"stale_after_days":{"type":"integer","minimum":1,"maximum":3650,"description":"Days before an open need or an unanswered referral is stale, when the file's Rules don't say. Default 30."},"kind":{"type":"string","enum":["auto","book","profile"],"description":"book, profile, or auto: the frontmatter's kind line, else a ## Profile heading, else book. Default auto."}},"required":["text"]},"example":{"text":"## Parties\n### agent:ops-helper\n- kind: agent\n- url: https://example.com/agents/ops-helper\n- categories: bookkeeping\n## Needs\n### need-2026-08-01-ops-helper-1\n- from: agent:ops-helper\n- category: tax-filing\n- opened: 2026-08-01\n","as_of":"2026-10-22"}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64 KB"}}}},"/api/v1/referrals_template":{"get":{"operationId":"referralsTemplateGet","summary":"Write a first REFERRALS.md","description":"Returns a ready-to-save REFERRALS.md file for one agent, person or firm from a few arguments: the owner as the first party (kind, url, categories, areas, capacity, budget range), an optional first open need, and the Rules section (disclosure, fees, max hops, who rates, stale after). The file is parsed and checked again before it is returned, and the counts come back as self_check. It writes text only; it lists nobody else and contacts nobody.","parameters":[{"name":"owner","in":"query","required":true,"description":"The owner's party id, like \"agent:ops-helper\" or \"firm:ledger-studio\". Without a prefix, the kind is added.","schema":{"type":"string"}},{"name":"kind","in":"query","required":false,"description":"The owner's kind. Default: the owner id's prefix (firm:..., person:...), else agent.","schema":{"type":"string","enum":["agent","person","firm"]}},{"name":"url","in":"query","required":false,"description":"A URL or contact pointer for the owner. Default a placeholder to replace.","schema":{"type":"string"}},{"name":"categories","in":"query","required":true,"description":"What the owner serves, comma-separated tags.","schema":{"type":"string"}},{"name":"tags","in":"query","required":false,"description":"Extra tags the owner carries, comma-separated. Optional.","schema":{"type":"string"}},{"name":"areas","in":"query","required":false,"description":"Areas served, comma-separated; \"remote\" means anywhere. Default remote.","schema":{"type":"string"}},{"name":"capacity","in":"query","required":false,"description":"Open slots now. Default 1.","schema":{"type":"integer","minimum":0,"maximum":10000}},{"name":"budget","in":"query","required":false,"description":"The range of job budgets the owner takes, like \"400-3000\". Optional.","schema":{"type":"string"}},{"name":"disclosure","in":"query","required":false,"description":"Rules: is a disclosed line required on every referral. Default required.","schema":{"type":"string","enum":["required","optional"]}},{"name":"fees","in":"query","required":false,"description":"Rules: are referral fees allowed if disclosed. Default allowed.","schema":{"type":"string","enum":["allowed","not_allowed"]}},{"name":"max_hops","in":"query","required":false,"description":"Rules: how far a referral may travel. Default 3.","schema":{"type":"integer","minimum":1,"maximum":20}},{"name":"rated_by","in":"query","required":false,"description":"Rules: who rates an outcome. Default asker.","schema":{"type":"string","enum":["asker","receiver","both"]}},{"name":"stale_after","in":"query","required":false,"description":"Rules: days before an open need is stale. Default 30.","schema":{"type":"integer","minimum":1,"maximum":3650}},{"name":"need_category","in":"query","required":false,"description":"A first open need: its category. Optional.","schema":{"type":"string"}},{"name":"need_budget","in":"query","required":false,"description":"The need's budget range, like \"500-1500\".","schema":{"type":"string"}},{"name":"need_where","in":"query","required":false,"description":"The need's area, or \"remote\".","schema":{"type":"string"}},{"name":"need_by","in":"query","required":false,"description":"The need's by date.","schema":{"type":"string"}},{"name":"need_requirements","in":"query","required":false,"description":"The need's requirements, comma-separated tags.","schema":{"type":"string"}},{"name":"as_of","in":"query","required":false,"description":"The date written as updated and opened. Default today.","schema":{"type":"string"}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"referralsTemplate","summary":"Write a first REFERRALS.md","description":"Returns a ready-to-save REFERRALS.md file for one agent, person or firm from a few arguments: the owner as the first party (kind, url, categories, areas, capacity, budget range), an optional first open need, and the Rules section (disclosure, fees, max hops, who rates, stale after). The file is parsed and checked again before it is returned, and the counts come back as self_check. It writes text only; it lists nobody else and contacts nobody.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"owner":{"type":"string","maxLength":64,"description":"The owner's party id, like \"agent:ops-helper\" or \"firm:ledger-studio\". Without a prefix, the kind is added."},"kind":{"type":"string","enum":["agent","person","firm"],"default":"agent","description":"The owner's kind. Default: the owner id's prefix (firm:..., person:...), else agent."},"url":{"type":"string","maxLength":200,"description":"A URL or contact pointer for the owner. Default a placeholder to replace."},"categories":{"type":"string","maxLength":400,"description":"What the owner serves, comma-separated tags."},"tags":{"type":"string","maxLength":400,"description":"Extra tags the owner carries, comma-separated. Optional."},"areas":{"type":"string","maxLength":200,"default":"remote","description":"Areas served, comma-separated; \"remote\" means anywhere. Default remote."},"capacity":{"type":"integer","minimum":0,"maximum":10000,"default":1,"description":"Open slots now. Default 1."},"budget":{"type":"string","maxLength":40,"description":"The range of job budgets the owner takes, like \"400-3000\". Optional."},"disclosure":{"type":"string","enum":["required","optional"],"default":"required","description":"Rules: is a disclosed line required on every referral. Default required."},"fees":{"type":"string","enum":["allowed","not_allowed"],"default":"allowed","description":"Rules: are referral fees allowed if disclosed. Default allowed."},"max_hops":{"type":"integer","minimum":1,"maximum":20,"default":3,"description":"Rules: how far a referral may travel. Default 3."},"rated_by":{"type":"string","enum":["asker","receiver","both"],"default":"asker","description":"Rules: who rates an outcome. Default asker."},"stale_after":{"type":"integer","minimum":1,"maximum":3650,"default":30,"description":"Rules: days before an open need is stale. Default 30."},"need_category":{"type":"string","maxLength":40,"description":"A first open need: its category. Optional."},"need_budget":{"type":"string","maxLength":40,"description":"The need's budget range, like \"500-1500\"."},"need_where":{"type":"string","maxLength":40,"description":"The need's area, or \"remote\"."},"need_by":{"type":"string","maxLength":10,"description":"The need's by date."},"need_requirements":{"type":"string","maxLength":400,"description":"The need's requirements, comma-separated tags."},"as_of":{"type":"string","maxLength":10,"description":"The date written as updated and opened. Default today."}},"required":["owner","categories"]},"example":{"owner":"agent:ops-helper","kind":"agent","url":"https://example.com/agents/ops-helper","categories":"bookkeeping, payroll","areas":"remote","capacity":2,"need_category":"tax-filing","need_budget":"500-1500","need_where":"remote","need_by":"2026-11-30","need_requirements":"multi-currency","as_of":"2026-10-22"}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64 KB"}}}},"/api/v1/referral_packet":{"get":{"operationId":"referralPacketGet","summary":"Build a referral packet","description":"Builds the handoff one agent sends another under REFERRALS.md version 1: the need (category, summary and parameters), from, to, date, why, the disclosure (required, \"none\" allowed), a reply-by date and a consent line saying what the asker agreed may be shared. Returns the packet as canonical JSON, its stable id (\"rp_\" plus the first 16 hex characters of sha256 over the canonical JSON of need, from, to and date), the referral line to add to the sender's book, and findings. Refuses a packet with no disclosure or no consent line. With the sender's book as text it also numbers the line, checks the parties, the rules, duplicates, loops and hops. It builds text; it never sends anything or checks who anyone is.","parameters":[{"name":"need","in":"query","required":true,"description":"The need: category (required), summary, budget (like 500-1500), where, by (a date), requirements (tags), and id (its id in your book, sent as need_ref). URL-encoded JSON.","schema":{"type":"string"}},{"name":"from","in":"query","required":true,"description":"The party passing the referral: you.","schema":{"type":"string"}},{"name":"to","in":"query","required":true,"description":"The party the need is referred to.","schema":{"type":"string"}},{"name":"date","in":"query","required":false,"description":"The date it is passed, like 2026-10-21. Default today.","schema":{"type":"string"}},{"name":"why","in":"query","required":false,"description":"The match reasons as comma-separated tags.","schema":{"type":"string"}},{"name":"disclosure","in":"query","required":true,"description":"The fee or relationship behind the referral, or \"none\". Required.","schema":{"type":"string"}},{"name":"consent","in":"query","required":true,"description":"What the asker agreed may be shared, in one line. Required.","schema":{"type":"string"}},{"name":"reply_by","in":"query","required":false,"description":"The date the receiver is asked to answer by. Default 7 days after date.","schema":{"type":"string"}},{"name":"asker","in":"query","required":false,"description":"The asker's party id, only when the consent line allows naming it.","schema":{"type":"string"}},{"name":"prev","in":"query","required":false,"description":"The id of the packet this one passes on, when a referred need is passed again.","schema":{"type":"string"}},{"name":"text","in":"query","required":false,"description":"Optional: your book (REFERRALS.md), to number the referral line and check it against your parties, needs and rules.","schema":{"type":"string"}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"referralPacket","summary":"Build a referral packet","description":"Builds the handoff one agent sends another under REFERRALS.md version 1: the need (category, summary and parameters), from, to, date, why, the disclosure (required, \"none\" allowed), a reply-by date and a consent line saying what the asker agreed may be shared. Returns the packet as canonical JSON, its stable id (\"rp_\" plus the first 16 hex characters of sha256 over the canonical JSON of need, from, to and date), the referral line to add to the sender's book, and findings. Refuses a packet with no disclosure or no consent line. With the sender's book as text it also numbers the line, checks the parties, the rules, duplicates, loops and hops. It builds text; it never sends anything or checks who anyone is.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"need":{"type":"object","description":"The need: category (required), summary, budget (like 500-1500), where, by (a date), requirements (tags), and id (its id in your book, sent as need_ref).","properties":{"category":{"type":"string"},"summary":{"type":"string"},"budget":{"type":"string"},"where":{"type":"string"},"by":{"type":"string"},"requirements":{"type":"string"},"id":{"type":"string"}}},"from":{"type":"string","maxLength":64,"description":"The party passing the referral: you."},"to":{"type":"string","maxLength":64,"description":"The party the need is referred to."},"date":{"type":"string","maxLength":10,"description":"The date it is passed, like 2026-10-21. Default today."},"why":{"type":"string","maxLength":300,"description":"The match reasons as comma-separated tags."},"disclosure":{"type":"string","maxLength":300,"description":"The fee or relationship behind the referral, or \"none\". Required."},"consent":{"type":"string","maxLength":300,"description":"What the asker agreed may be shared, in one line. Required."},"reply_by":{"type":"string","maxLength":10,"description":"The date the receiver is asked to answer by. Default 7 days after date."},"asker":{"type":"string","maxLength":64,"description":"The asker's party id, only when the consent line allows naming it."},"prev":{"type":"string","maxLength":19,"description":"The id of the packet this one passes on, when a referred need is passed again."},"text":{"type":"string","maxLength":60000,"description":"Optional: your book (REFERRALS.md), to number the referral line and check it against your parties, needs and rules."}},"required":["need","from","to","disclosure","consent"]},"example":{"need":{"id":"need-2026-10-20-ops-helper-1","category":"tax-filing","summary":"a yearly return with income in two currencies","budget":"500-1500","where":"remote","by":"2026-11-30","requirements":"multi-currency"},"from":"agent:tax-scout","to":"firm:ledger-studio","date":"2026-10-21","why":"category, multi-currency, budget","disclosure":"none","consent":"the asker agreed to share the summary and parameters, not its name"}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64 KB"}}}},"/api/v1/record_outcome":{"get":{"operationId":"recordOutcomeGet","summary":"Record a referral outcome in a book","description":"Applies an outcome report to a REFERRALS.md book: the packet id (or referral id), the new status (accepted, declined or completed), a rating from 1 to 5 with completed, a note, the date, and who sent it (role asker or receiver). Finds the referral line by its packet line, checks the move (passed to accepted, declined or completed; accepted to completed or declined; completed only takes a first rating; declined is final) and the rating against the book's rated_by rule, and returns the changed lines with a diff, the whole updated book to save, and the referrer's score in that category before and after. Findings come back when the packet id is not in the book or the move is not allowed. It edits the text you send; it never sends or stores anything.","parameters":[{"name":"text","in":"query","required":true,"description":"Your book (REFERRALS.md) with the referral line that carries the packet id.","schema":{"type":"string"}},{"name":"outcome","in":"query","required":true,"description":"The outcome report: packet (rp_... or the referral id), status (accepted, declined or completed), rating (1 to 5, with completed), note, date, by (party id) and role (asker or receiver). URL-encoded JSON.","schema":{"type":"string"}},{"name":"half_life_days","in":"query","required":false,"description":"Half-life for the referrer's score, in days. Default 180.","schema":{"type":"integer","minimum":1,"maximum":3650}},{"name":"min_referrals","in":"query","required":false,"description":"Answered referrals before the score counts. Default 3.","schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"recordOutcome","summary":"Record a referral outcome in a book","description":"Applies an outcome report to a REFERRALS.md book: the packet id (or referral id), the new status (accepted, declined or completed), a rating from 1 to 5 with completed, a note, the date, and who sent it (role asker or receiver). Finds the referral line by its packet line, checks the move (passed to accepted, declined or completed; accepted to completed or declined; completed only takes a first rating; declined is final) and the rating against the book's rated_by rule, and returns the changed lines with a diff, the whole updated book to save, and the referrer's score in that category before and after. Findings come back when the packet id is not in the book or the move is not allowed. It edits the text you send; it never sends or stores anything.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":60000,"description":"Your book (REFERRALS.md) with the referral line that carries the packet id."},"outcome":{"type":"object","description":"The outcome report: packet (rp_... or the referral id), status (accepted, declined or completed), rating (1 to 5, with completed), note, date, by (party id) and role (asker or receiver).","properties":{"packet":{"type":"string"},"status":{"type":"string"},"rating":{"type":"number"},"note":{"type":"string"},"date":{"type":"string"},"by":{"type":"string"},"role":{"type":"string"}}},"half_life_days":{"type":"integer","minimum":1,"maximum":3650,"description":"Half-life for the referrer's score, in days. Default 180."},"min_referrals":{"type":"integer","minimum":1,"maximum":100,"description":"Answered referrals before the score counts. Default 3."}},"required":["text","outcome"]},"example":{"text":"## Referrals\n### ref-2026-10-21-tax-scout-1\n- packet: rp_cd7a7b53f1c9db2f\n- need: need-2026-10-20-ops-helper-1\n- from: agent:tax-scout\n- to: firm:ledger-studio\n- date: 2026-10-21\n- category: tax-filing\n- disclosed: none\n- status: accepted\n","outcome":{"packet":"rp_cd7a7b53f1c9db2f","status":"completed","rating":5,"note":"filed a week early","date":"2026-11-20","role":"asker"}}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64 KB"}}}},"/api/v1/parse_referrals":{"get":{"operationId":"parseReferralsGet","summary":"Turn a REFERRALS.md file into JSON","description":"Reads a REFERRALS.md book or profile and returns the parser's canonical JSON: keys sorted, records sorted by id, tag lists sorted, absent values as null, with each referral's packet id computed from its need, from, to and date next to the one written in the file. For a book it also returns the graph view (every party a node, every referral line an edge, its outcome the edge's weight). The JSON matches /schema.json, so other programs can read the format without a parser of their own. It reads the text you send; it checks no one and stores nothing.","parameters":[{"name":"text","in":"query","required":true,"description":"A REFERRALS.md book or profile.","schema":{"type":"string"}},{"name":"kind","in":"query","required":false,"description":"book, profile, or auto: the frontmatter's kind line, else a ## Profile heading, else book. Default auto.","schema":{"type":"string","enum":["auto","book","profile"]}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"parseReferrals","summary":"Turn a REFERRALS.md file into JSON","description":"Reads a REFERRALS.md book or profile and returns the parser's canonical JSON: keys sorted, records sorted by id, tag lists sorted, absent values as null, with each referral's packet id computed from its need, from, to and date next to the one written in the file. For a book it also returns the graph view (every party a node, every referral line an edge, its outcome the edge's weight). The JSON matches /schema.json, so other programs can read the format without a parser of their own. It reads the text you send; it checks no one and stores nothing.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":60000,"description":"A REFERRALS.md book or profile."},"kind":{"type":"string","enum":["auto","book","profile"],"description":"book, profile, or auto: the frontmatter's kind line, else a ## Profile heading, else book. Default auto."}},"required":["text"]},"example":{"text":"---\nkind: profile\nupdated: 2026-10-23\n---\n## Profile\n- id: firm:ledger-studio\n- kind: firm\n- categories: bookkeeping, tax-filing\n- send: https://example.com/ledger-studio/referrals\n- disclosure: none; we pay and take no referral fees\n- fees: not_allowed\n"}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64 KB"}}}},"/api/v1/referrals_starter":{"get":{"operationId":"referralsStarterGet","summary":"Get a starter REFERRALS.md file","description":"Serves four ready-to-copy REFERRALS.md files: three books (an agent handoff to a specialist agent, a professional circle of firms with fee disclosure, routing inside one team) and a public profile for https://<your domain>/referrals.md. Without kind it lists them with when to use each; with kind it returns the text and the check_referrals result on it. Every name, number and date in them is made up; nothing here contacts anyone or checks who anyone is.","parameters":[{"name":"kind","in":"query","required":false,"description":"Which starter: agent-handoff (an agent passes a client need to a specialist agent), professional-circle (firms that refer to each other, with fee disclosure), team-routing (agents inside one organization route work), public-profile (the /referrals.md a party publishes). Leave out for the list.","schema":{"type":"string","enum":["agent-handoff","professional-circle","team-routing","public-profile"]}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"referralsStarter","summary":"Get a starter REFERRALS.md file","description":"Serves four ready-to-copy REFERRALS.md files: three books (an agent handoff to a specialist agent, a professional circle of firms with fee disclosure, routing inside one team) and a public profile for https://<your domain>/referrals.md. Without kind it lists them with when to use each; with kind it returns the text and the check_referrals result on it. Every name, number and date in them is made up; nothing here contacts anyone or checks who anyone is.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["agent-handoff","professional-circle","team-routing","public-profile"],"description":"Which starter: agent-handoff (an agent passes a client need to a specialist agent), professional-circle (firms that refer to each other, with fee disclosure), team-routing (agents inside one organization route work), public-profile (the /referrals.md a party publishes). Leave out for the list."}}},"example":{"kind":"agent-handoff"}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64 KB"}}}},"/api/v1/build_settings":{"get":{"operationId":"buildSettingsGet","summary":"Write your settings into the skill","description":"Turns a preset and settings into the sections written at the top of REFERRALS.md. Every setting is a number or a fixed choice; the catalog is at https://referrals.md/api/v1/settings. Returns the sections as Markdown, the settings as JSON, a link to the skill with them written in and a one-line install. Out-of-range numbers are clamped with a note. Nothing is stored. Settings an agent drafted count only after the person says yes: show them to the person first, and follow them only once they agree.","parameters":[{"name":"preset","in":"query","required":false,"description":"Starting point: core, recommended, strict. Applied first; each setting overrides it.","schema":{"type":"string","enum":["core","recommended","strict"]}},{"name":"ask_before_passing","in":"query","required":false,"description":"Ask the person before passing a referral: always (Always), new_party (Only to a party not in the file yet), never (Never). Default always.","schema":{"type":"string","enum":["always","new_party","never"]}},{"name":"disclosure_required","in":"query","required":false,"description":"Disclosure line required on every referral: yes (Yes), no (No). Default yes.","schema":{"type":"string","enum":["yes","no"]}},{"name":"fees_allowed","in":"query","required":false,"description":"Referral fees: allowed (Allowed if disclosed), not_allowed (Not allowed). Default allowed.","schema":{"type":"string","enum":["allowed","not_allowed"]}},{"name":"max_hops","in":"query","required":false,"description":"Max hops a referral may travel (hops). Default 3. Out-of-range values are clamped.","schema":{"type":"integer","minimum":1,"maximum":6}},{"name":"list_people","in":"query","required":false,"description":"List a person as a party: with_consent (Only with their consent), never (Never, agents and firms only). Optional; unset clears it.","schema":{"type":"string","enum":["with_consent","never","unset"]}},{"name":"min_match_score","in":"query","required":false,"description":"Lowest match score worth passing (points). Optional; unset clears it. Out-of-range values are clamped.","schema":{"type":"integer","minimum":0,"maximum":100}},{"name":"budget_tolerance_pct","in":"query","required":false,"description":"Budget fit tolerance (%). Optional; unset clears it. Out-of-range values are clamped.","schema":{"type":"integer","minimum":0,"maximum":100}},{"name":"stale_after_days","in":"query","required":false,"description":"An open need is stale after (days). Optional; unset clears it. Out-of-range values are clamped.","schema":{"type":"integer","minimum":7,"maximum":365}},{"name":"half_life_days","in":"query","required":false,"description":"Half-life of a past referral's weight (days). Default 180. Out-of-range values are clamped.","schema":{"type":"integer","minimum":30,"maximum":3650}},{"name":"min_referrals","in":"query","required":false,"description":"Referrals before a score counts (referrals). Default 3. Out-of-range values are clamped.","schema":{"type":"integer","minimum":1,"maximum":50}},{"name":"weight_acceptance","in":"query","required":false,"description":"Weight of the acceptance rate (x). Optional; unset clears it. Out-of-range values are clamped.","schema":{"type":"integer","minimum":0,"maximum":10}},{"name":"weight_completion","in":"query","required":false,"description":"Weight of the completion rate (x). Optional; unset clears it. Out-of-range values are clamped.","schema":{"type":"integer","minimum":0,"maximum":10}},{"name":"weight_outcome","in":"query","required":false,"description":"Weight of the mean outcome rating (x). Optional; unset clears it. Out-of-range values are clamped.","schema":{"type":"integer","minimum":0,"maximum":10}},{"name":"rated_by","in":"query","required":false,"description":"Who rates an outcome: asker (The party that asked), receiver (The party that received it), both (Both, averaged). Optional; unset clears it.","schema":{"type":"string","enum":["asker","receiver","both","unset"]}},{"name":"format","in":"query","required":false,"description":"section (default): the settings sections only; skill: the whole skill file too.","schema":{"type":"string","enum":["section","skill"]}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"buildSettings","summary":"Write your settings into the skill","description":"Turns a preset and settings into the sections written at the top of REFERRALS.md. Every setting is a number or a fixed choice; the catalog is at https://referrals.md/api/v1/settings. Returns the sections as Markdown, the settings as JSON, a link to the skill with them written in and a one-line install. Out-of-range numbers are clamped with a note. Nothing is stored. Settings an agent drafted count only after the person says yes: show them to the person first, and follow them only once they agree.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"preset":{"type":"string","enum":["core","recommended","strict"],"description":"Starting point: core, recommended, strict. Applied first; each setting overrides it."},"ask_before_passing":{"type":"string","enum":["always","new_party","never"],"description":"Ask the person before passing a referral: always (Always), new_party (Only to a party not in the file yet), never (Never). Default always."},"disclosure_required":{"type":"string","enum":["yes","no"],"description":"Disclosure line required on every referral: yes (Yes), no (No). Default yes."},"fees_allowed":{"type":"string","enum":["allowed","not_allowed"],"description":"Referral fees: allowed (Allowed if disclosed), not_allowed (Not allowed). Default allowed."},"max_hops":{"type":"integer","minimum":1,"maximum":6,"description":"Max hops a referral may travel (hops). Default 3. Out-of-range values are clamped."},"list_people":{"type":"string","enum":["with_consent","never","unset"],"description":"List a person as a party: with_consent (Only with their consent), never (Never, agents and firms only). Optional; unset clears it."},"min_match_score":{"type":"integer","minimum":0,"maximum":100,"description":"Lowest match score worth passing (points). Optional; unset clears it. Out-of-range values are clamped."},"budget_tolerance_pct":{"type":"integer","minimum":0,"maximum":100,"description":"Budget fit tolerance (%). Optional; unset clears it. Out-of-range values are clamped."},"stale_after_days":{"type":"integer","minimum":7,"maximum":365,"description":"An open need is stale after (days). Optional; unset clears it. Out-of-range values are clamped."},"half_life_days":{"type":"integer","minimum":30,"maximum":3650,"description":"Half-life of a past referral's weight (days). Default 180. Out-of-range values are clamped."},"min_referrals":{"type":"integer","minimum":1,"maximum":50,"description":"Referrals before a score counts (referrals). Default 3. Out-of-range values are clamped."},"weight_acceptance":{"type":"integer","minimum":0,"maximum":10,"description":"Weight of the acceptance rate (x). Optional; unset clears it. Out-of-range values are clamped."},"weight_completion":{"type":"integer","minimum":0,"maximum":10,"description":"Weight of the completion rate (x). Optional; unset clears it. Out-of-range values are clamped."},"weight_outcome":{"type":"integer","minimum":0,"maximum":10,"description":"Weight of the mean outcome rating (x). Optional; unset clears it. Out-of-range values are clamped."},"rated_by":{"type":"string","enum":["asker","receiver","both","unset"],"description":"Who rates an outcome: asker (The party that asked), receiver (The party that received it), both (Both, averaged). Optional; unset clears it."},"format":{"type":"string","enum":["section","skill"],"description":"section (default): the settings sections only; skill: the whole skill file too."}}},"example":{"preset":"recommended"}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64 KB"}}}},"/api/v1/whats_new":{"get":{"operationId":"whatsNewGet","summary":"What's new here, and what's next","description":"What changed on referrals.md and when, what is being built, when to check back, and the other .md sites. Pass since (a date) to get only newer entries, for example the updated date of the skill you have. Reads a list kept with the code; fetches nothing and stores nothing.","parameters":[{"name":"since","in":"query","required":false,"description":"Only entries after this date (YYYY-MM-DD).","schema":{"type":"string"}},{"name":"planned","in":"query","required":false,"description":"Include what is being built and the ideas being weighed. Default true.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}},"post":{"operationId":"whatsNew","summary":"What's new here, and what's next","description":"What changed on referrals.md and when, what is being built, when to check back, and the other .md sites. Pass since (a date) to get only newer entries, for example the updated date of the skill you have. Reads a list kept with the code; fetches nothing and stores nothing.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"since":{"type":"string","maxLength":10,"pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Only entries after this date (YYYY-MM-DD)."},"planned":{"type":"boolean","default":true,"description":"Include what is being built and the ideas being weighed. Default true."}}},"example":{"since":"2026-10-01"}}}},"responses":{"200":{"description":"Result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Result"}}}},"400":{"description":"Bad input, with a hint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"Body over 64 KB"}}}}},"components":{"schemas":{"Result":{"type":"object","additionalProperties":true,"properties":{"tool":{"type":"string"},"summary":{"type":"string"},"disclaimer":{"type":"string"},"terms":{"type":"string"}},"required":["disclaimer","terms"]},"Problem":{"type":"object","properties":{"error":{"type":"string"},"hint":{"type":"string"},"docs":{"type":"string"},"disclaimer":{"type":"string"},"terms":{"type":"string"}},"required":["error","disclaimer","terms"]}}}}