Complete reference for all REST API endpoints in Wulong.
https://localhost:3000
Note: In development, use -k flag with curl to accept self-signed certificates.
- Wulong API Reference
Get TEE attestation proving the service cannot access user data.
Authentication: None (publicly accessible)
Response:
{
platform: 'amd-sev-snp' | 'intel-tdx' | 'aws-nitro' | 'phala' | 'none';
report: string; // Base64-encoded attestation report/quote from TEE
measurement: string; // Measurement/hash of the code running in the TEE
timestamp: string; // ISO 8601 timestamp when attestation was generated
publicKey?: string; // Public key of the TEE (if applicable)
mlkemPublicKey?: string; // ML-KEM-1024 public key for quantum-resistant encryption (base64)
}Example:
# Request
curl -k https://localhost:3000/chest/attestation
# Response
{
"platform": "intel-tdx",
"report": "eyJhdHRlc3RhdGlvbiI6ICIuLi4ifQ==",
"measurement": "abc123def456...",
"timestamp": "2026-03-18T10:30:00.000Z",
"mlkemPublicKey": "k3VARNFcS4hWl6AfR0DMy..."
}Use Cases:
- Verify code integrity - Compare
measurementwith published source code hash - Get encryption key - Use
mlkemPublicKeyto encrypt secrets client-side (quantum-resistant) - Confirm TEE platform - Check that service is running in genuine TEE hardware
See also: Client-Side Encryption Guide
Store a secret with owner-based access control.
Authentication: None required for storing
Request Body:
{
secret: string; // The secret to store (recommend encrypting with ML-KEM first)
publicAddresses: string[]; // Array of Ethereum addresses that can access this secret
}Response:
{
slot: string; // Unique 64-character hex identifier for this secret
}Status Codes:
201 Created- Secret stored successfully400 Bad Request- Invalid request (empty secret, invalid addresses, etc.)
Example:
# Request
curl -k -X POST https://localhost:3000/chest/store \
-H "Content-Type: application/json" \
-d '{
"secret": "苟全性命於亂世,不求聞達於諸侯。",
"publicAddresses": ["0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"]
}'
# Response
{
"slot": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234567890"
}Security Recommendations:
-
Encrypt secrets client-side with ML-KEM before storing:
// Get ML-KEM public key from attestation const attestation = await fetch('/chest/attestation').then(r => r.json()); // Encrypt with ML-KEM (see Client-Side Encryption guide) const encrypted = await encryptWithMlKem(secret, attestation.mlkemPublicKey); // Store encrypted secret await fetch('/chest/store', { method: 'POST', body: JSON.stringify({ secret: JSON.stringify(encrypted), publicAddresses: [myAddress] }) });
-
Use checksummed Ethereum addresses - Both formats are accepted
-
Store slot ID securely - You'll need it to retrieve the secret later
See also: Client-Side Encryption Guide
Access a stored secret.
Authentication: Required (SIWE)
Path Parameters:
slot- The slot identifier returned from/chest/store
Headers:
x-siwe-message: <base64-encoded SIWE message>
x-siwe-signature: <hex signature>
Response:
{
secret: string; // The stored secret (decrypted if it was encrypted with ML-KEM)
}Status Codes:
200 OK- Secret retrieved successfully401 Unauthorized- Missing or invalid SIWE authentication403 Forbidden- Caller is not an owner of this secret404 Not Found- Slot does not exist
Example:
# Step 1: Get nonce
NONCE=$(curl -k -X POST https://localhost:3000/auth/nonce | jq -r '.nonce')
# Step 2: Create and sign SIWE message (using your wallet)
# Message format:
# localhost wants you to sign in with your Ethereum account:
# 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
#
# URI: https://localhost:3000
# Version: 1
# Chain ID: 1
# Nonce: <NONCE>
# Issued At: 2026-03-18T10:30:00.000Z
# Step 3: Access secret with SIWE authentication
curl -k https://localhost:3000/chest/access/a1b2c3d4... \
-H "x-siwe-message: $(echo -n "$MESSAGE" | base64)" \
-H "x-siwe-signature: $SIGNATURE"
# Response
{
"secret": "苟全性命於亂世,不求聞達於諸侯。"
}TypeScript Example:
import { SiweMessage } from 'siwe';
// Get nonce
const { nonce } = await fetch('https://localhost:3000/auth/nonce', {
method: 'POST'
}).then(r => r.json());
// Create SIWE message
const siweMessage = new SiweMessage({
domain: 'localhost',
address: walletAddress,
uri: 'https://localhost:3000',
version: '1',
chainId: 1,
nonce: nonce,
issuedAt: new Date().toISOString(),
});
const message = siweMessage.prepareMessage();
const signature = await wallet.signMessage(message);
// Access secret
const response = await fetch(`https://localhost:3000/chest/access/${slot}`, {
headers: {
'x-siwe-message': Buffer.from(message).toString('base64'),
'x-siwe-signature': signature,
},
});
const { secret } = await response.json();
console.log('Secret:', secret);Security Notes:
- SIWE nonces are single-use and expire after 5 minutes
- Signatures must be valid for the authenticated Ethereum address
- Only addresses in the
publicAddressesarray can access the secret - Case-insensitive address matching (checksummed or lowercase both work)
See also: SIWE Authentication Guide
Generate a SIWE (Sign-In with Ethereum) nonce for authentication.
Authentication: None
Response:
{
nonce: string; // Random nonce for SIWE message (single-use, expires in 5 minutes)
}Example:
# Request
curl -k -X POST https://localhost:3000/auth/nonce
# Response
{
"nonce": "1a2b3c4d5e6f7890"
}Usage Flow:
- Call
/auth/nonceto get a fresh nonce - Create SIWE message with the nonce
- Sign the message with your Ethereum wallet
- Use the signature in
x-siwe-signatureheader for protected endpoints
Example SIWE Message Format:
localhost wants you to sign in with your Ethereum account:
0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
URI: https://localhost:3000
Version: 1
Chain ID: 1
Nonce: 1a2b3c4d5e6f7890
Issued At: 2026-03-18T10:30:00.000Z
See also: SIWE Authentication Guide
General health check endpoint.
Authentication: None
Response:
{
status: 'ok';
timestamp: string; // ISO 8601 timestamp
}Example:
curl -k https://localhost:3000/health
# Response
{
"status": "ok",
"timestamp": "2026-03-18T10:30:00.000Z"
}Readiness probe for orchestration systems (Kubernetes, etc.).
Authentication: None
Response:
{
status: 'ready' | 'not ready';
checks: {
database?: boolean;
tee?: boolean;
encryption?: boolean;
};
}Status Codes:
200 OK- Service is ready to accept traffic503 Service Unavailable- Service is not ready
Example:
curl -k https://localhost:3000/health/ready
# Response
{
"status": "ready",
"checks": {
"tee": true,
"encryption": true
}
}Liveness probe for orchestration systems.
Authentication: None
Response:
{
status: 'alive';
}Status Codes:
200 OK- Service is alive503 Service Unavailable- Service should be restarted
Example:
curl -k https://localhost:3000/health/live
# Response
{
"status": "alive"
}All endpoints return consistent error responses:
{
statusCode: number;
message: string;
error?: string; // Error type (BadRequest, Unauthorized, etc.)
}Common Status Codes:
400 Bad Request- Invalid request parameters or body401 Unauthorized- Missing or invalid authentication403 Forbidden- Authenticated but not authorized404 Not Found- Resource does not exist500 Internal Server Error- Unexpected server error
Example Error:
{
"statusCode": 400,
"message": "Secret cannot be empty",
"error": "Bad Request"
}All endpoints are rate-limited to prevent abuse:
- Global limit: 100 requests per minute per IP
- Auth endpoints: 10 requests per minute per IP
- Store endpoint: 50 requests per minute per IP
Rate Limit Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1234567890
Rate Limit Exceeded Response:
{
"statusCode": 429,
"message": "Too Many Requests",
"error": "ThrottlerException"
}Interactive API documentation is available at:
https://localhost:3000
Features:
- Try out endpoints directly in the browser
- See request/response schemas
- View example requests and responses
- Download OpenAPI specification
TypeScript/JavaScript:
npm install mlkem siwe ethersRecommended Libraries:
mlkem- Quantum-resistant encryptionsiwe- Sign-In with Ethereumethers- Ethereum wallet interactionw3pk- Web3 passkey SDK (for wallet + encryption)
See also:
-
Always verify attestation before encrypting secrets
const attestation = await fetch('/chest/attestation').then(r => r.json()); if (attestation.measurement !== EXPECTED_MEASUREMENT) { throw new Error('Code measurement mismatch!'); }
-
Encrypt secrets client-side with ML-KEM before storing
-
Use HTTPS in production with real TLS certificates
- Never use
-kflag in production - Generate certificates inside TEE
- Never use
-
Store slot IDs securely
- Don't expose in URLs or logs
- Consider encrypting slot IDs client-side
-
Validate Ethereum addresses before storing
- Use checksummed addresses
- Verify addresses are owned by intended users
-
Handle SIWE nonces properly
- Request fresh nonce for each authentication
- Don't reuse nonces
- Check nonce expiration (5 minutes)
- Documentation: docs/
- Issues: File issues on GitHub repository
- Security: See SIDE_CHANNEL_ATTACKS.md
- Overview - Project overview and architecture
- Client-Side Encryption - Quantum-resistant encryption guide
- TEE Setup - Platform-specific deployment
- SIWE Authentication - Ethereum wallet authentication
- Side Channel Attacks - Security considerations