This document provides a complete reference for the Mau HTTP API that every peer implements. Understanding this API is essential for building Mau applications and interoperating with other Mau clients.
Every Mau peer runs an HTTP/TLS server that exposes:
/p2p/*) - Serve and sync files/kad/*) - Peer discovery and routingAll Mau endpoints follow this pattern:
mau://<host>:<port>/<namespace>/<resource>
Examples:
mau://192.168.1.100:8080/p2p/5D000B2F2C040A1675B49D7F0C7CB7DC36999D56
mau://alice.example.com:443/p2p/5D000B2F2C040A1675B49D7F0C7CB7DC36999D56/hello-world.json
mau://bob.local:8080/kad/ping
The mau:// protocol is HTTP/TLS with mutual authentication (see Transport Security).
All Mau communication uses TLS 1.3 with client certificates for mutual authentication.
1TLS Config:
2- MinVersion: TLS 1.3
3- Certificates: [Server's certificate derived from PGP key]
4- ClientAuth: RequestClientCert
5- InsecureSkipVerify: true // We verify manually via fingerprint
6- CurvePreferences: [X25519, P256, P384, P521]
1TLS Config:
2- MinVersion: TLS 1.3
3- Certificates: [Client's certificate derived from PGP key]
4- InsecureSkipVerify: true
5- VerifyPeerCertificate: Custom verification against expected fingerprint
Instead of traditional CA-based verification, Mau uses PGP key fingerprints:
Example fingerprint extraction:
1// From certificate's SubjectKeyId or by hashing the public key
2fingerprint := sha1(certificate.PublicKey)
3if fingerprint != expectedFingerprint {
4 return ErrIncorrectPeerCertificate
5}
Default server timeouts:
Retrieve a list of files owned by a specific user.
1GET /p2p/<fingerprint> HTTP/1.1
2Host: peer.example.com
3If-Modified-Since: Wed, 27 Feb 2026 00:00:00 GMT
Parameters:
<fingerprint> (path) - 40-character hex fingerprint of the user’s PGP keyHeaders:
If-Modified-Since (optional) - RFC 7231 HTTP date. Only return files modified after this timestampSuccess (200 OK):
1[
2 {
3 "path": "/p2p/5D000B2F2C040A1675B49D7F0C7CB7DC36999D56/hello-world.json",
4 "size": 2048,
5 "sum": "abc123def456789abcdef0123456789abcdef0123456789abcdef0123456789"
6 },
7 {
8 "path": "/p2p/5D000B2F2C040A1675B49D7F0C7CB7DC36999D56/recipe-lasagna.json",
9 "size": 4096,
10 "sum": "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
11 }
12]
Response Fields:
path (string) - Full path to the file (used for GET requests)size (int64) - File size in bytessum (string) - SHA-256 hash of the file content (64 hex characters)Authorization:
1curl -k \
2 --cert client-cert.pem \
3 --key client-key.pem \
4 -H "If-Modified-Since: Wed, 27 Feb 2026 00:00:00 GMT" \
5 "https://peer.example.com:8080/p2p/5D000B2F2C040A1675B49D7F0C7CB7DC36999D56"
If-Modified-Since to get all filesIf-Modified-Since with the last sync timestampsum field to avoid re-downloading unchanged filesDownload a specific file.
1GET /p2p/<fingerprint>/<filename> HTTP/1.1
2Host: peer.example.com
3Range: bytes=0-1023
Parameters:
<fingerprint> (path) - User’s PGP key fingerprint<filename> (path) - Name of the file (e.g., hello-world.json)Headers:
Range (optional) - RFC 7233 byte range for partial downloadsSuccess (200 OK or 206 Partial Content):
1HTTP/1.1 200 OK
2Content-Type: application/octet-stream
3Content-Length: 2048
4Accept-Ranges: bytes
5
6<file content (PGP-encrypted binary)>
Response Headers:
Content-Type: application/octet-streamAccept-Ranges: bytes (supports range requests)Content-Length: File size in bytesAuthorization:
401 Unauthorized if the requesting peer is not a recipient of the file404 Not Found if the file doesn’t exist 1# Download full file
2curl -k \
3 --cert client-cert.pem \
4 --key client-key.pem \
5 -o hello-world.json.pgp \
6 "https://peer.example.com:8080/p2p/5D000B2F.../hello-world.json"
7
8# Download partial file (resume)
9curl -k \
10 --cert client-cert.pem \
11 --key client-key.pem \
12 -H "Range: bytes=1024-" \
13 -o hello-world.json.pgp.part \
14 "https://peer.example.com:8080/p2p/5D000B2F.../hello-world.json"
Downloaded files are PGP-encrypted (RFC 4880 OpenPGP Message Format):
Example decryption:
1# Decrypt and verify
2gpg --decrypt hello-world.json.pgp > hello-world.json
3
4# Check signature
5gpg --verify hello-world.json.pgp
Download a specific historical version of a file.
1GET /p2p/<fingerprint>/<filename>.version/<hash> HTTP/1.1
2Host: peer.example.com
Parameters:
<fingerprint> (path) - User’s PGP key fingerprint<filename> (path) - Name of the file<hash> (path) - SHA-256 hash of the version (64 hex characters)Success (200 OK):
1HTTP/1.1 200 OK
2Content-Type: application/octet-stream
3Content-Length: 2048
4Accept-Ranges: bytes
5
6<file content (PGP-encrypted binary)>
Same response format as Get File.
When a file is updated:
<filename>.versions/<sha256-hash>.pgp<filename>.pgpExample:
alice-FPR/
├── recipe-lasagna.json.pgp # Current version
└── recipe-lasagna.json.pgp.versions/
├── abc123...xyz.pgp # Version 1
└── def456...uvw.pgp # Version 2
The Kademlia Distributed Hash Table (DHT) enables peer discovery without central servers.
Health check to verify a peer is online and update routing tables.
1GET /kad/ping HTTP/1.1
2Host: peer.example.com
No parameters required.
Success (200 OK):
1HTTP/1.1 200 OK
2Content-Length: 0
Empty body. A 200 status indicates the peer is alive.
Side Effects:
Clients implement ping backoff to avoid flooding:
lastPing map1curl -k \
2 --cert client-cert.pem \
3 --key client-key.pem \
4 "https://peer.example.com:8080/kad/ping"
Locate a peer by fingerprint using the Kademlia routing algorithm.
1GET /kad/find_peer/<fingerprint> HTTP/1.1
2Host: peer.example.com
Parameters:
<fingerprint> (path) - Target user’s PGP key fingerprint (40 hex characters)Success (200 OK):
1[
2 {
3 "fingerprint": "5D000B2F2C040A1675B49D7F0C7CB7DC36999D56",
4 "address": "alice.example.com:443"
5 },
6 {
7 "fingerprint": "A1B2C3D4E5F6789012345678901234567890ABCD",
8 "address": "192.168.1.100:8080"
9 }
10]
Response Fields:
fingerprint (string) - Peer’s PGP key fingerprintaddress (string) - Peer’s network address (hostname:port or IP:port)Algorithm:
1curl -k \
2 --cert client-cert.pem \
3 --key client-key.pem \
4 "https://peer.example.com:8080/kad/find_peer/5D000B2F2C040A1675B49D7F0C7CB7DC36999D56"
Bob wants to sync with Alice:
GET /kad/find_peer/alice-FPRGET /p2p/alice-FPR| Status | Meaning | Common Causes |
|---|---|---|
200 OK |
Success | Normal operation |
206 Partial Content |
Range request success | Resume download |
400 Bad Request |
Invalid request | Malformed fingerprint, invalid path |
401 Unauthorized |
Not authorized | Not a file recipient, TLS auth failed |
404 Not Found |
Resource not found | File doesn’t exist, unknown fingerprint |
405 Method Not Allowed |
Wrong HTTP method | Non-GET request to P2P/DHT endpoint |
500 Internal Server Error |
Server error | Filesystem error, PGP operation failed |
Errors return a plain text message in the response body:
1HTTP/1.1 404 Not Found
2Content-Type: text/plain; charset=utf-8
3Content-Length: 14
4
5File not found
File access is controlled by PGP encryption recipients:
200 OK401 UnauthorizedSpecial case: Public files are encrypted to the author’s own key, so only the author and explicit recipients can access them initially. To share publicly, encrypt for all known peers or use a shared public key.
1// 1. Discover peer
2peer, err := dht.FindPeer(ctx, aliceFingerprint)
3if err != nil {
4 return err
5}
6
7// 2. Create authenticated client
8client, err := account.Client(aliceFingerprint, []string{peer.Address})
9if err != nil {
10 return err
11}
12
13// 3. List files (incremental sync)
14lastSync := time.Date(2026, 2, 27, 0, 0, 0, 0, time.UTC)
15resp, err := client.Get(ctx, fmt.Sprintf(
16 "mau://%s/p2p/%s",
17 peer.Address,
18 aliceFingerprint,
19), map[string]string{
20 "If-Modified-Since": lastSync.Format(http.TimeFormat),
21})
22
23// 4. Parse file list
24var files []FileListItem
25json.Unmarshal(resp.Body, &files)
26
27// 5. Download each file
28for _, file := range files {
29 // Check if already downloaded
30 localHash := getLocalFileHash(file.Path)
31 if localHash == file.Sum {
32 continue // Skip, already up to date
33 }
34
35 // Download
36 content, err := client.Get(ctx, fmt.Sprintf(
37 "mau://%s%s",
38 peer.Address,
39 file.Path,
40 ), nil)
41
42 // 6. Verify signature
43 if !verifySignature(content, aliceFingerprint) {
44 return ErrInvalidSignature
45 }
46
47 // 7. Decrypt
48 decrypted, err := pgp.Decrypt(content, account.PrivateKey)
49
50 // 8. Save locally
51 saveFile(file.Path, decrypted)
52}
1// Bootstrap into network
2bootstrapPeers := []*Peer{
3 {Fingerprint: ..., Address: "bootstrap1.mau.network:443"},
4 {Fingerprint: ..., Address: "bootstrap2.mau.network:443"},
5}
6
7// Join DHT
8dht.Join(ctx, bootstrapPeers)
9
10// Find specific peer
11target := FingerprintFromString("5D000B2F...")
12nearest := dht.FindPeer(ctx, target)
13
14// Connect and sync
15client, _ := account.Client(target, []string{nearest.Address})
16client.Sync(ctx, target)
1// Create server
2server, err := account.Server(bootstrapPeers)
3if err != nil {
4 return err
5}
6
7// Listen (IPv4/IPv6 dual-stack)
8listener, err := ListenTCP(":8080")
9if err != nil {
10 return err
11}
12
13// Start serving
14externalAddress := "mau.example.com:443"
15go server.Serve(listener, externalAddress)
16
17// Graceful shutdown
18defer server.Close()
1// Client: Resume download
2resp, err := client.Get(ctx, fileURL, map[string]string{
3 "Range": "bytes=1024-", // Resume from byte 1024
4})
5
6// Server: Handled automatically by http.ServeContent
7// Supports:
8// - Range: bytes=0-1023 (first 1KB)
9// - Range: bytes=1024- (from 1KB to end)
10// - Range: bytes=-1024 (last 1KB)
11// - Range: bytes=0-1023,2048-3071 (multiple ranges)
In addition to DHT, peers announce themselves on the local network:
1// mDNS Service Type
2serviceType := "_mau._tcp"
3
4// Announce
5service, _ := mdns.NewMDNSService(
6 fingerprint.String(), // Instance name
7 serviceType,
8 "", // Domain (default .local)
9 "", // Hostname (auto-detect)
10 port,
11 nil, // IPs (auto-detect)
12 []string{}, // TXT records
13)
14server, _ := mdns.NewServer(&mdns.Config{Zone: service})
15
16// Discover
17entries := make(chan *mdns.ServiceEntry, 10)
18mdns.Lookup(serviceType, entries)
19
20for entry := range entries {
21 fingerprint := entry.Name
22 address := fmt.Sprintf("%s:%d", entry.AddrV4, entry.Port)
23 // Connect to peer...
24}
1// After downloading a file
2if !file.VerifySignature(account, expectedFingerprint) {
3 return ErrInvalidSignature
4}
1// Efficient incremental sync
2lastSync := getLastSyncTime(peer)
3files := client.ListFiles(peer, lastSync)
1// Avoid hammering peers
2if time.Since(lastPing[peer]) < 30*time.Second {
3 return // Skip ping
4}
1// Retry with exponential backoff
2for attempt := 0; attempt < 5; attempt++ {
3 if err := client.Sync(peer); err == nil {
4 break
5 }
6 time.Sleep(time.Second * (1 << attempt)) // 1s, 2s, 4s, 8s, 16s
7}
1// Check size before downloading
2const maxFileSize = 100 * 1024 * 1024 // 100 MB
3if file.Size > maxFileSize {
4 return ErrFileTooLarge
5}
1ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
2defer cancel()
3files, err := client.ListFiles(ctx, peer)
The Mau HTTP API is currently at v1. Future versions will:
Potential additions (not yet implemented):
Both client and server MUST present valid certificates derived from PGP keys.
Never trust certificates without verifying the fingerprint matches expected value.
Always verify file signatures after download before accepting content.
Implement rate limiting to prevent:
Enforce reasonable file size limits to prevent disk exhaustion.
Set conservative timeouts for all network operations:
The canonical implementation is in the mau-network/mau repository:
server.go - HTTP server implementationclient.go - HTTP client implementationkademlia.go - DHT implementationFor questions or clarifications, please open an issue on GitHub.