Wallet Authentication

Authenticated endpoints require three HTTP headers. The signed message must match the format below and is valid for 60 seconds (single-use).

Headers
# Required headers for authenticated Vouch API endpoints
X-Wallet-Pubkey: <base58-public-key>
X-Wallet-Signature: <base58-ed25519-signature>
X-Wallet-Timestamp: <unix-epoch-seconds>
Signed Message Format
# Canonical message format: sign this with your Ed25519 key
vouch-auth:<pubkey>:<timestamp>:<method>:<path>

Note: Signatures are valid for 60 seconds and can only be used once to prevent replay attacks.

Example Error Responses

401 Unauthorized
// Possible 401 error messages:

{ "error": "Missing auth headers. Required: X-Wallet-Pubkey, X-Wallet-Signature, X-Wallet-Timestamp" }

{ "error": "Invalid timestamp" }

{ "error": "Signature expired. Timestamp must be within 60 seconds." }

{ "error": "Invalid wallet pubkey" }

{ "error": "Invalid signature encoding" }

{ "error": "Invalid signature" }

{ "error": "Signature already used. Sign a new message with a fresh timestamp." }

Code Example

Note: <method> is the uppercase HTTP method (GET, POST, etc.). <path> is the request path without query parameters (e.g., /agents).

TypeScript / JavaScript
import nacl from 'tweetnacl';
import bs58 from 'bs58';

const pubkey = keypair.publicKey.toBase58();
const timestamp = Math.floor(Date.now() / 1000).toString();
const method = 'GET';
const path = '/agents';
const message = `vouch-auth:${pubkey}:${timestamp}:${method}:${path}`;
const messageBytes = new TextEncoder().encode(message);
const signature = nacl.sign.detached(messageBytes, keypair.secretKey);
const signatureBase58 = bs58.encode(signature);

const response = await fetch('https://api.vouchprotocol.xyz/agents', {
  headers: {
    'X-Wallet-Pubkey': pubkey,
    'X-Wallet-Signature': signatureBase58,
    'X-Wallet-Timestamp': timestamp,
  },
});