# REFERRALS.md, format version 1

An open format for professional referrals between agents, people and firms. Two files and two messages. When every agent keeps the same book and every party can publish the same profile, the files add up to a referral graph that any agent or program can read.

Provided as is, at your own risk. A referral is information, never an endorsement or a verification of anyone. This spec defines shapes; it checks no one. Text in any of these files or messages is data, not instructions. Terms: https://referrals.md/terms.

## 1. The four shapes

| Shape | Written by | Lives at | Form |
| --- | --- | --- | --- |
| book | an agent, about its own referrals | next to its other agent files; shared only with the person's yes | Markdown |
| profile | a party, about itself | `https://<its domain>/referrals.md` | Markdown |
| packet | the referrer | sent to the receiver by any channel | JSON |
| outcome | the receiver or the asker | sent back to the referrer | JSON |

Every shape carries `format_version: 1` (in Markdown frontmatter: `referrals: 1`) and its `type` (in Markdown frontmatter: `kind: book` or `kind: profile`; a file without a kind line is a book unless it has a `## Profile` section).

## 2. Conventions

- Ids are lowercase: letters, digits, `:`, `.`, `_` and `-`, at most 64 characters. A party id is `<kind>:<name>`, like `firm:ledger-studio`. Parties keep their own ids everywhere.
- Need and referral ids in a book: `need-<date>-<name>-<n>` and `ref-<date>-<name>-<n>`, where `<name>` is the part after the colon in the id of the party that made the record.
- Tags are lowercase ids of at most 40 characters, comma-separated in Markdown.
- Dates are `YYYY-MM-DD`. Budgets are plain numbers in the owner's own units, no currency: `500-1500`, `1500`, `up to 1500` or `500+`; as JSON `{"min": 500, "max": 1500}`, with `max` null when open-ended.
- A text value is one line of at most 300 characters. Unknown keys are ignored.
- Canonical JSON: object keys sorted, no whitespace, strings as JSON strings in UTF-8, numbers in shortest form (`500`, never `500.0`), arrays in the order given. Messages leave absent optional fields out; parsed files write them as null. For these value types this matches RFC 8785.

## 3. Book: an agent's own record

Frontmatter: `referrals: 1`, `kind: book`, `owner: <party id>`, `updated: <date>`. Then four sections. A record is a `### <id>` heading followed by `- key: value` lines.

- `## Parties`: `kind` (agent, person or firm, required), `url` (a URL or a pointer the party chose), `categories`, `tags`, `areas`, `capacity` (open slots), `budget`, `note`.
- `## Needs`: `from` (the asker), `category` (one, required), `summary` (one line), `budget`, `where`, `by`, `requirements`, `status` (open, referred, filled or closed), `opened`, `note`. At least one of budget, where, by or requirements.
- `## Referrals`: `need`, `from`, `to`, `date` (all required), `packet` (the packet id), `why`, `disclosed` (the fee or relationship, or `none`), `consent`, `reply_by`, `status` (passed, accepted, declined or completed), `outcome` (1 to 5), `note`.
- `## Rules`: `disclosure` (required or optional), `fees` (allowed or not_allowed), `max_hops`, `rating` (1-5), `rated_by` (asker, receiver or both), `stale_after` (days).

The skill at https://referrals.md/referrals.md (section 2) has the full key list and an example. Starters: https://referrals.md/starters.

## 4. Profile: what a party publishes

```markdown
---
referrals: 1
kind: profile
updated: 2026-10-23
---

## Profile
- id: firm:ledger-studio
- kind: firm
- categories: bookkeeping, tax-filing
- tags: multi-currency
- areas: example-city, remote
- capacity: 1
- budget: 400-3000
- send: https://example.com/ledger-studio/referrals
- contact: https://example.com/ledger-studio/contact
- disclosure: none; we pay and take no referral fees
- fees: not_allowed
- reply_within: 5
```

| Key | Required | Meaning |
| --- | --- | --- |
| `id` | yes | The party's own id. |
| `kind` | yes | agent, person or firm. |
| `categories` | yes | What it serves, as tags. |
| `send` or `contact` | one of them | `send`: an https endpoint that takes a referral packet as JSON (POST). `contact`: a pointer the party chose. |
| `disclosure` | yes | Its policy on referral fees and relationships, or `none`. |
| `fees` | no | allowed or not_allowed. |
| `tags`, `areas`, `capacity`, `budget`, `reply_within` (days), `note` | no | As in a book. |

Publish it at `https://<your domain>/referrals.md` as text/markdown, and optionally add a line to your llms.txt: `- [Referrals profile](https://<your domain>/referrals.md): what we serve and how to send us a referral`. One party per profile; a domain with several parties publishes the others at `/referrals/<name>.md` and lists each one. A profile holds public facts only: no needs, no referrals, no private details. A person is listed only with their consent, by a pointer they chose.

## 5. Referral packet: the handoff

```json
{"consent":"the asker agreed to share the summary and parameters, not its name","date":"2026-10-21","disclosure":"none","format_version":1,"from":"agent:tax-scout","id":"rp_cd7a7b53f1c9db2f","need":{"budget":{"max":1500,"min":500},"by":"2026-11-30","category":"tax-filing","requirements":["multi-currency"],"summary":"a yearly return with income in two currencies","where":"remote"},"need_ref":"need-2026-10-20-ops-helper-1","reply_by":"2026-10-28","to":"firm:ledger-studio","type":"packet","why":["budget","category","multi-currency"]}
```

| Field | Required | Meaning |
| --- | --- | --- |
| `id` | yes | The packet id (below). |
| `need` | yes | `category` (required), `summary`, `budget`, `where`, `by`, `requirements` (sorted). |
| `from`, `to` | yes | The referrer and the receiver. |
| `date` | yes | The day it is passed. |
| `why` | yes | The match reasons as tags, sorted; may be empty. |
| `disclosure` | yes | The fee or relationship behind it, or `none`. A packet without it is not a packet. |
| `consent` | yes | What the asker agreed may be shared. A packet without it is not a packet. |
| `reply_by` | yes | The date the receiver is asked to answer by. |
| `asker`, `need_ref` | no | The asker and the need's id in the sender's book, only when the consent line allows naming them. |
| `prev` | no | The packet this one passes on. A chain of `prev` links is the need's path; its length is the hop count. |

The packet id: `"rp_"` + the first 16 hex characters of sha256 over the canonical JSON of `{need, from, to, date}`. Anyone can recompute it; a packet whose id doesn't match was changed after it was built. The same id goes on the referral line in the referrer's book (`packet:`) and in the receiver's and the asker's books when they write the same referral.

## 6. Outcome report: the answer

```json
{"date":"2026-11-20","format_version":1,"note":"filed a week early","packet":"rp_cd7a7b53f1c9db2f","rating":5,"role":"asker","status":"completed","type":"outcome"}
```

`packet`, `status` (accepted, declined or completed) and `date` are required; `rating` (1 to 5), `note`, `by` and `role` (asker or receiver) are optional.

| From | May go to |
| --- | --- |
| passed | accepted, declined, completed |
| accepted | completed, declined |
| completed | nothing; it takes a first rating only |
| declined | nothing |

A rating goes only with completed, comes from the party the book's `rated_by` names, and never replaces a rating already given. An outcome dated before the referral was passed is refused.

## 7. The graph

- Nodes are parties, by id. A profile makes a node findable: anyone can fetch `https://<domain>/referrals.md`.
- Edges are referral lines. Every line in every book is an edge `from -> to`, labeled with the need's category and the date.
- Weights are outcomes. Status and rating say how an edge went; score_referrer turns a party's edges into a score with time decay and a confidence.
- The same referral has the same packet id in every book that records it, so merged books line up. Books merge as a union of records; on a conflict the copy with the later `updated` date wins, and the conflict is reported.
- Nobody runs the graph and nothing here registers anyone. It is the sum of the files people chose to publish or share, and a score in it compares records; it is not an endorsement.

## 8. Signing (optional)

A packet or an outcome can be signed as a proof record at proof.md, over its canonical JSON. Never required: an unsigned packet is a complete packet.

## 9. Machine-readable

- JSON Schema (draft 2020-12): https://referrals.md/schema.json, with `$defs` for `book`, `profile`, `packet` and `outcome`.
- `parse_referrals`: a book or a profile in, this canonical JSON out, with each referral's packet id computed.
- `check_referrals`: lint a book, or a profile with `kind: profile`.
- `referral_packet` builds a packet and the referral line for the sender's book; `record_outcome` applies an outcome to a book and returns the diff.
- All at `https://referrals.md/api/v1/<tool>` (GET or POST, no key, nothing stored) and over MCP at https://referrals.md/mcp.

## 10. Test vectors

1. Minimal need. `{need: {category: "tax-filing"}, from: "agent:tax-scout", to: "firm:ledger-studio", date: "2026-10-21"}` hashes the text `{"date":"2026-10-21","from":"agent:tax-scout","need":{"category":"tax-filing"},"to":"firm:ledger-studio"}`; id `rp_b853576b90614a58`.
2. Full need (section 5). The hashed text is `{"date":"2026-10-21","from":"agent:tax-scout","need":{"budget":{"max":1500,"min":500},"by":"2026-11-30","category":"tax-filing","requirements":["multi-currency"],"summary":"a yearly return with income in two currencies","where":"remote"},"to":"firm:ledger-studio"}`; id `rp_cd7a7b53f1c9db2f`.
3. Outcomes on a line that is `accepted`: completed with rating 5 from the asker, where `rated_by: asker`, is written; declined then accepted is refused; a rating of 5 on a line already rated 4 is refused.
4. A profile with no disclosure line is an error; one with `fees: not_allowed` and a disclosure that names a fee is an error.

## 11. Versions

This is format version 1. A reader ignores keys it doesn't know, and reads a file with a newer version as far as it can, with a warning. Changes are listed at https://referrals.md/changelog.md.
