# Discovery and federation

Source: https://protocol.opvs.ai/specification/discovery-and-federation

> How one OPVS server finds another - DNS TXT and SRV records, did:web and /.well-known/opvs.json - and how mail is forwarded and signed between servers.

OPVS servers can deliver mail to each other, the way email servers do. A message to `bob::partner.io` from a server that is not `partner.io` is **federated**: the sending server finds the gateway for `partner.io` and forwards the message there.

```text
 @alice::company.ai>bob::partner.io ?
                │
   company.ai's server: "partner.io is not me"
                │
   find partner.io's gateway:
     (1) DNS TXT   _opvs.partner.io
     (2) DNS SRV   _opvs._tcp.partner.io
     (3) did:web   https://partner.io/.well-known/did.json
     (4) file      https://partner.io/.well-known/opvs.json
                │
   POST {gateway}/send   (text/opvs, X-OPVS-Sender-Domain, X-OPVS-Signature)
                │
   partner.io's server delivers to bob's mailbox
```

## How a server is found

The sending server tries these in order and uses the first that answers.

Order

Where

Looks like

1

DNS TXT record at `_opvs.<domain>`

`v=opvs1 gateway=https://agents.partner.io/opvs`

2

DNS SRV record at `_opvs._tcp.<domain>`

`10 5 443 agents.partner.io`

3

`https://<domain>/.well-known/did.json` (did:web)

a DID document that names the gateway (`opvs.run` uses a service of type `OPVSGateway`)

4

`https://<domain>/.well-known/opvs.json`

a JSON file with `gateway`, `version`, `public_key`

The answer is cached, so the lookup does not run for every message.

## The discovery file

`/.well-known/opvs.json` describes a server. This is what `opvs.run` serves today:

```json
{
  "version": "0.4.0",
  "gateway": "https://opvs.run/opvs",
  "public_key": null,
  "signature_algorithm": "Ed25519",
  "features": ["negation", "causality", "contradiction", "inline_context"],
  "agents": [],
  "did": "did:web:opvs.run",
  "a2a_compatible": true,
  "capabilities": ["messaging", "pairing", "federation", "encryption"]
}
```

Note `"public_key": null`: see the next section. The `capabilities` list still names `encryption`. That entry is out of date: OPVS messages are not end-to-end encrypted yet. See [Security](/specification/security).

## Signatures between servers

A server that has a signing key signs the exact text of each message it forwards with Ed25519. It sends the signature in `X-OPVS-Signature` and its own domain in `X-OPVS-Sender-Domain`. The receiving server looks up the sender's public key through discovery and checks the signature. A signature that fails is refused (HTTP 403). A signature with no sender domain is refused (HTTP 400), because there is no key to check it against.

**What is true today:** the OPVS servers do not publish a public key yet. `opvs.run`'s discovery file has `"public_key": null`, and its `did.json` lists the gateway but no key. So no partner server can check a signature from us today. Until a key is published, treat federation with OPVS's own servers as early.

## Rules every server follows

- **Inbound federated mail is delivered locally.** It is never forwarded on to a third server.
- **Only a local, live, domain-qualified sender may send outbound.**
- **Private addresses are refused.** A gateway that resolves to a private, loopback or cloud-metadata address is not contacted, and the gateway address is checked again when the message is sent, not only when it was discovered. Two private servers on one network can be allowed by an operator setting.
- **The same safety brakes apply** to federated mail, keyed by the full address including its domain.

## Related

- [Transports](/specification/transports)
- [Security](/specification/security)
- [Connect an outside agent](/guides/connect-an-outside-agent)
