docs/07-http-api
Saturday 7 March 2026

HTTP API Reference

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.

Table of Contents

  1. Overview
  2. Transport Security
  3. P2P Endpoints
  4. Kademlia DHT Endpoints
  5. Error Responses
  6. Client Implementation Guide

Overview

Base Concepts

Every Mau peer runs an HTTP/TLS server that exposes:

  1. P2P Content API (/p2p/*) - Serve and sync files
  2. Kademlia DHT API (/kad/*) - Peer discovery and routing

URL Structure

All 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).


Transport Security

TLS 1.3 with Mutual Authentication

All Mau communication uses TLS 1.3 with client certificates for mutual authentication.

Server Configuration

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]

Client Configuration

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

Certificate Verification

Instead of traditional CA-based verification, Mau uses PGP key fingerprints:

  1. Both peers present X.509 certificates during TLS handshake
  2. Certificates are self-signed and derived from PGP keys
  3. Each peer extracts the fingerprint from the peer’s certificate
  4. The fingerprint is compared against the expected value

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}

Timeouts

Default server timeouts:

  • ReadTimeout: 30 seconds
  • WriteTimeout: 30 seconds
  • IdleTimeout: 120 seconds
  • ReadHeaderTimeout: 10 seconds

P2P Endpoints

1. List Files

Retrieve a list of files owned by a specific user.

Request

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 key

Headers:

  • If-Modified-Since (optional) - RFC 7231 HTTP date. Only return files modified after this timestamp

Response

Success (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 bytes
  • sum (string) - SHA-256 hash of the file content (64 hex characters)

Authorization:

  • Only files that the requesting peer is authorized to read are included
  • Authorization is based on PGP encryption recipients (see Authorization Logic)

Example cURL

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"

Use Cases

  • Initial sync: Omit If-Modified-Since to get all files
  • Incremental sync: Include If-Modified-Since with the last sync timestamp
  • Bandwidth optimization: Check sum field to avoid re-downloading unchanged files

2. Get File

Download a specific file.

Request

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 downloads

Response

Success (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-stream
  • Accept-Ranges: bytes (supports range requests)
  • Content-Length: File size in bytes

Authorization:

  • Returns 401 Unauthorized if the requesting peer is not a recipient of the file
  • Returns 404 Not Found if the file doesn’t exist

Example cURL

 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"

File Format

Downloaded files are PGP-encrypted (RFC 4880 OpenPGP Message Format):

  1. Decrypt with recipient’s private key
  2. Verify signature against author’s public key
  3. Extract content (JSON-LD document)

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

3. Get File Version

Download a specific historical version of a file.

Request

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)

Response

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.

Use Cases

  • Version history: Browse old versions of files
  • Conflict resolution: Compare different versions
  • Audit trail: For contracts, signed documents, etc.

Versioning Strategy

When a file is updated:

  1. Old version moves to <filename>.versions/<sha256-hash>.pgp
  2. New version overwrites <filename>.pgp
  3. Versions are stored by content hash (deduplication)

Example:

alice-FPR/
├── recipe-lasagna.json.pgp          # Current version
└── recipe-lasagna.json.pgp.versions/
    ├── abc123...xyz.pgp             # Version 1
    └── def456...uvw.pgp             # Version 2

Kademlia DHT Endpoints

The Kademlia Distributed Hash Table (DHT) enables peer discovery without central servers.

4. Ping

Health check to verify a peer is online and update routing tables.

Request

1GET /kad/ping HTTP/1.1
2Host: peer.example.com

No parameters required.

Response

Success (200 OK):

1HTTP/1.1 200 OK
2Content-Length: 0

Empty body. A 200 status indicates the peer is alive.

Side Effects:

  • The receiver adds/updates the sender’s fingerprint in its routing table
  • Extracted from the client certificate’s fingerprint

Rate Limiting

Clients implement ping backoff to avoid flooding:

  • Minimum 30 seconds between pings to the same peer
  • Tracked per fingerprint in lastPing map

Example cURL

1curl -k \
2  --cert client-cert.pem \
3  --key client-key.pem \
4  "https://peer.example.com:8080/kad/ping"

5. Find Peer

Locate a peer by fingerprint using the Kademlia routing algorithm.

Request

1GET /kad/find_peer/<fingerprint> HTTP/1.1
2Host: peer.example.com

Parameters:

  • <fingerprint> (path) - Target user’s PGP key fingerprint (40 hex characters)

Response

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 fingerprint
  • address (string) - Peer’s network address (hostname:port or IP:port)

Algorithm:

  1. If the target fingerprint is in the receiver’s routing table, return it
  2. Otherwise, return the K closest peers (K=20 by default) based on XOR distance
  3. Client recursively queries returned peers until target is found

Example cURL

1curl -k \
2  --cert client-cert.pem \
3  --key client-key.pem \
4  "https://peer.example.com:8080/kad/find_peer/5D000B2F2C040A1675B49D7F0C7CB7DC36999D56"

Kademlia Parameters

  • B (Buckets): 160 (20 bytes × 8 bits) - for v4 fingerprints
  • K (Replication): 20 - max peers per bucket
  • α (Alpha): 3 - parallel lookup queries
  • Stall Period: 1 hour - bucket refresh interval

Use Case Flow

Bob wants to sync with Alice:

  1. Bob queries bootstrap node: GET /kad/find_peer/alice-FPR
  2. Bootstrap returns 3 peers closer to Alice
  3. Bob queries those 3 peers in parallel
  4. One peer returns Alice’s address
  5. Bob connects to Alice: GET /p2p/alice-FPR

Error Responses

HTTP Status Codes

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

Error Response Format

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

Authorization Logic

File access is controlled by PGP encryption recipients:

  1. Server decrypts file metadata to extract recipient list
  2. If requesting peer’s fingerprint is in recipient list → 200 OK
  3. If not in recipient list → 401 Unauthorized

Special 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.


Client Implementation Guide

Basic Sync Flow

 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}

DHT Peer Discovery

 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)

Serving Files

 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()

Handling Range Requests

 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)

mDNS Local Discovery

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}

Best Practices

1. Always Verify Signatures

1// After downloading a file
2if !file.VerifySignature(account, expectedFingerprint) {
3    return ErrInvalidSignature
4}

2. Use If-Modified-Since

1// Efficient incremental sync
2lastSync := getLastSyncTime(peer)
3files := client.ListFiles(peer, lastSync)

3. Implement Backoff

1// Avoid hammering peers
2if time.Since(lastPing[peer]) < 30*time.Second {
3    return // Skip ping
4}

4. Handle Network Errors Gracefully

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}

5. Limit File Sizes

1// Check size before downloading
2const maxFileSize = 100 * 1024 * 1024  // 100 MB
3if file.Size > maxFileSize {
4    return ErrFileTooLarge
5}

6. Use Context for Timeouts

1ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
2defer cancel()
3files, err := client.ListFiles(ctx, peer)

Protocol Versioning

Current Version: v1

The Mau HTTP API is currently at v1. Future versions will:

  • Add version negotiation via HTTP headers
  • Maintain backward compatibility
  • Use content negotiation for format changes

Future Extensions

Potential additions (not yet implemented):

  • WebSocket subscriptions: Real-time file updates
  • GraphQL endpoint: Complex queries
  • Batch operations: Download multiple files in one request
  • Compression: Gzip/Brotli for file lists

Security Considerations

1. Mutual TLS Authentication

Both client and server MUST present valid certificates derived from PGP keys.

2. Fingerprint Verification

Never trust certificates without verifying the fingerprint matches expected value.

3. Signature Verification

Always verify file signatures after download before accepting content.

4. Rate Limiting

Implement rate limiting to prevent:

  • DHT lookup floods
  • Ping storms
  • File download abuse

5. File Size Limits

Enforce reasonable file size limits to prevent disk exhaustion.

6. Timeout Configuration

Set conservative timeouts for all network operations:

  • Connection: 10 seconds
  • Read/Write: 30 seconds
  • Idle: 120 seconds


Reference Implementation

The canonical implementation is in the mau-network/mau repository:

  • server.go - HTTP server implementation
  • client.go - HTTP client implementation
  • kademlia.go - DHT implementation

For questions or clarifications, please open an issue on GitHub.