Overview
s402 is an HTTP 402-based protocol for machine-to-machine access to Bitcoin DeFi infrastructure. When an agent or automated client requests a paid resource, the server responds with HTTP 402 Payment Required along with headers describing the payment terms. The client settles the payment on Bitcoin, then retries the request with a proof-of-payment header to receive the data.
KeyBay Capital implements s402 as its primary agent access layer. The current production surface is centered on s402, with x402, L402, and OP_NET-native payment flows planned as future expansions. Agents, LLM tool-use integrations, and automated trading systems can consume vault data, oracle prices, and credit parameters without browser sessions or API keys — payment is the authentication.
Design Principles
- Payment is auth — No API keys, no OAuth tokens. A valid Bitcoin payment is the only credential required.
- Extensible — The current rollout uses s402, and the endpoint surface is designed to add more payment proofs over time.
- Satoshi-denominated — All prices are quoted in satoshis. Sub-cent micropayments are first-class.
- Stateless — Each request is independently verifiable. No session state, no cookies, no rate-limit counters.
Quick Start
Install the SDK
npm install @keybay/sdk
SDK Usage
import { KeyBay } from "@keybay/sdk";
const kb = new KeyBay({ apiBaseUrl: "https://api.keybaycapital.com" });
// Yield vault summary
const summary = await kb.vault.getSummary();
if (summary.ok) {
console.log("Total active:", summary.data.totalActiveSats, "sats");
console.log("Depositors:", summary.data.depositorCount);
}
// Credit risk parameters
const rates = kb.credit.getRiskParameters();
console.log("Max LTV:", rates.maxLtvBps, "bps"); // 4000n (40%)
console.log("Interest:", rates.interestRateBps, "bps"); // 1000n (10% APR)
Agent Access via cURL
Agents interact with paid endpoints directly over HTTP. The first request returns 402 with payment instructions. After paying, the agent retries with the proof-of-payment header.
# Step 1: Request the resource (receives 402)
curl -i https://api.keybaycapital.com/api/s402/oracle/price
# HTTP/1.1 402 Payment Required
# X-Payment-Required: true
# X-Payment-Amount: 10
# X-Payment-Currency: sats
# X-Payment-Address: bc1q...treasury
# X-Payment-Protocol: s402
# Step 2: Pay and retry with proof
curl -H "X-Payment: s402:<tx_id>:10:<signature>" \
https://api.keybaycapital.com/api/s402/oracle/price
# HTTP/1.1 200 OK
# { "price_usd_cents": 9402000, "block": 887421, "stale": false }
Pricing Tiers
KeyBay s402 uses a 3-tier pricing model. All prices are environment-variable driven and denominated in satoshis. s402 revenue is additive to primary vault/lending revenue.
Tier 1 — Free (Rate Limited)
Basic vault, oracle, and credit data. Rate limited to 10 requests per minute per IP. On 429, the response includes an s402 upsell with a link to the paid discovery endpoint.
| Endpoint | Price | Description |
|---|---|---|
| /api/public/vault/summary | Free | Basic vault TVL, deposit count, APY, and network status. |
| /api/public/oracle/price | Free | Live BTC/USD price snapshot. |
| /api/public/credit/params | Free | Credit engine parameters: max LTV, liquidation LTV, interest rate. |
Tier 2 — Paid (s402 Micropayment)
Full data access via s402 payment. Prices are set via environment variables (format: S402_PRICE_*) and auto-convert to satoshis using the live BTC/USD rate. All paid endpoints follow the same 402 handshake.
| Endpoint | Price (USD) | Env Var | Description |
|---|---|---|---|
| /api/s402/oracle/price | $0.01 | S402_PRICE_ORACLE_PRICE | Live BTC/USD oracle price with staleness indicator. |
| /api/s402/oracle/history | $0.01 | S402_PRICE_ORACLE_HISTORY | 24h historical BTC/USD oracle snapshots. |
| /api/s402/vault/stats | $0.02 | S402_PRICE_VAULT_STATS | Vault TVL, depositor count, APY, exchange rate. |
| /api/s402/vault/performance | $0.05 * | S402_PRICE_VAULT_PERFORMANCE | Historical performance: yield, Sharpe ratio, drawdowns. |
| /api/s402/vault/allocation | $0.02 | S402_PRICE_VAULT_ALLOCATION | Current vault allocation breakdown by strategy. |
| /api/s402/vault/reserves | $0.05 | S402_PRICE_VAULT_RESERVES | Reserve proof and solvency data. |
| /api/s402/credit/rates | $0.01 | S402_PRICE_CREDIT_RATES | Credit vault interest rates and risk parameters. |
| /api/s402/credit/positions | $0.02 | S402_PRICE_CREDIT_POSITIONS | Aggregate credit vault positions and system LTV. |
| /api/s402/credit/liquidations | $0.10 * | S402_PRICE_CREDIT_LIQUIDATIONS | Liquidation events: trigger prices, penalties, recovery. |
| /api/s402/credit/simulate | $0.02 | S402_PRICE_CREDIT_SIMULATE | Credit vault simulation: LTV, interest, liquidation scenarios. |
| /api/s402/yield/calculator | $0.01 | S402_PRICE_YIELD_CALCULATOR | Yield projection calculator by strategy and duration. |
* Endpoints marked with asterisk (*) are never free and cannot be bypassed via Tier 3 premium deposit status. Vault performance and liquidation data represent the highest-value intelligence feeds.
Tier 3 — Premium Deposit Bypass
Vault depositors with 0.01+ BTC deposited in KeyBay receive free access to all Tier 2 endpoints (except those marked as never-free). Include your depositor address in the X-Keybay-Address header. Deposit status is cached for 5 minutes.
# Premium bypass — no payment required if you have 0.01+ BTC deposited
curl -H "X-Keybay-Address: bc1q...your_depositor_address" \
https://api.keybaycapital.com/api/s402/vault/stats
# HTTP/1.1 200 OK
# { "totalValueLockedBtc": "12.34567890", "_tier": "premium", ... }
# Never-free endpoints still require payment even for depositors:
# /api/s402/vault/performance ($0.05)
# /api/s402/credit/liquidations ($0.10)
Payment Flow
The s402 payment flow is a two-step HTTP handshake. Every paid endpoint follows the same pattern:
- GET the endpoint — The server responds with HTTP 402 Payment Required. Response headers include
X-Payment-Amount(satoshis),X-Payment-Address(treasury Bitcoin address), andX-Payment-Protocol(s402in the current rollout). - Parse payment instructions — The response body contains a JSON object with the same payment parameters plus a human-readable description of the resource being purchased.
- Send payment — Transfer the required satoshi amount to the treasury address via any Bitcoin payment method (on-chain, Lightning, or OP_NET).
- Retry with proof — Re-send the original GET request with the
X-Paymentheader containing the payment proof. - Receive data — The server validates the payment proof and returns HTTP 200 with the requested data.
X-Payment Header Format
X-Payment: s402:<tx_id>:<amount_sats>:<signature>
# Fields:
# s402 Protocol identifier used by the current rollout
# tx_id Bitcoin transaction ID (hex, 64 chars)
# amount_sats Amount paid in satoshis (must match or exceed required amount)
# signature Signature proving ownership of the paying address
402 Response Example
HTTP/1.1 402 Payment Required
Content-Type: application/json
X-Payment-Required: true
X-Payment-Amount: 10
X-Payment-Currency: sats
X-Payment-Address: bc1q...treasury
X-Payment-Protocol: s402
{
"error": "payment_required",
"amount": 10,
"currency": "sats",
"address": "bc1q...treasury",
"protocol": "s402",
"description": "Live BTC/USD oracle price",
"endpoint": "/api/s402/oracle/price"
}
SDK Reference
The @keybay/sdk package provides typed access to vault and credit operations. All public methods return Result<T> — the SDK never throws.
Core Classes
- KeyBay — Main entry point. Instantiate with
{ apiBaseUrl }. Provides.vault(VaultClient) and.credit(CreditClient) accessors, plus staticKeyBay.utilsfor pure computation. - VaultClient — Yield vault operations:
getSummary(),getPosition(address),getOraclePrice(),getHealth(),getActivity(limit),getStablecoinRegistry(),validateDeposit(sats). - CreditClient — Credit vault operations:
getVaults(address),getVault(id, address),openVault(...),repay(...),getMaxBorrow(...),getLTV(...),getLiquidationPrice(...),getLiquidationInfo(...),getPendingInterest(...),getRiskParameters().
Utility Functions
Available via KeyBay.utils or as named imports. All functions are pure — no side effects, no network calls.
- Amount conversion —
btcToSats(str),satsToBtcString(sats),usdToCents(str),centsToUsdString(cents) - BPS conversion —
bpsToPercentString(bps),percentToBps(str) - Exchange rate —
calculateShares(btcSats, rate),sharesToBtc(shares, rate) - LTV math —
calculateLTV(collateral, debt, price),calculateLiquidationPrice(collateral, debt),calculateMaxBorrow(collateral, price, existingDebt) - Interest & liquidation —
calculateAccruedInterest(debt, blocks),calculateLiquidationPenalty(collateral),calculateSeizedCollateral(collateral, debt, price) - Validation —
isValidBitcoinAddress(addr),validateDepositAmount(sats),validateCollateralAmount(sats),validateRail(rail) - Formatting —
formatBtc(sats),formatUsd(cents),formatLtv(bps)
Amount Conventions
All amounts use bigint. There are no floating-point values in any public API surface.
- BTC — satoshis (1 BTC = 100,000,000n)
- USD — cents ($1.00 = 100n)
- BPS — basis points (1% = 100n, 100% = 10,000n)
- Exchange rate — 8 decimal places (1:1 = 100,000,000n)
Result<T> Pattern
The SDK never throws exceptions. All public methods that can fail return Result<T>, which is a discriminated union:
type Result<T> =
| { ok: true; data: T }
| { ok: false; error: KeyBayError };
// Usage:
const result = await kb.vault.getSummary();
if (result.ok) {
console.log(result.data.totalActiveSats);
} else {
console.error(result.error.code, result.error.message);
}
Contract Registry
The contract registry endpoint returns live contract addresses and status for all deployed OP_NET contracts. This is a free (unpaid) endpoint.
GET /api/contracts.json
{
"network": "mainnet",
"lastUpdated": "2025-06-15T00:00:00Z",
"contracts": [
{ "name": "KeyBayVault", "address": "bc1q...", "status": "active", "dataSource": "config" },
{ "name": "CreditVault", "address": "bc1q...", "status": "active", "dataSource": "config" },
{ "name": "Oracle", "address": "bc1q...", "status": "active", "dataSource": "config" },
{ "name": "kbBTC", "address": "bc1q...", "status": "active", "dataSource": "config" },
{ "name": "WithdrawalQueue", "address": "bc1q...", "status": "active", "dataSource": "config" },
{ "name": "ReserveProof", "address": "bc1q...", "status": "undeployed", "dataSource": "config" }
],
"stablecoinRails": [
{ "id": "op20s", "name": "OP_20S (L1)", "railType": 0, "active": false, "tokens": [] },
{ "id": "taproot-usdt", "name": "Taproot USDT (Lightning)", "railType": 1, "active": true, "tokens": ["USDT"] }
]
}
Use the SDK helper functions to consume this data programmatically:
import { fetchContractRegistry, extractAddresses } from "@keybay/sdk";
const config = { apiBaseUrl: "https://api.keybaycapital.com" };
const registry = await fetchContractRegistry(config);
if (registry.ok) {
const addrs = extractAddresses(registry.data);
console.log("Vault:", addrs.keyBayVault);
console.log("Credit:", addrs.creditVault);
console.log("Oracle:", addrs.oracle);
}
Each contract entry includes a status field: active (deployed and operational), paused (deployed but temporarily halted), or undeployed (address reserved, contract not yet on-chain). The dataSource field indicates whether the address comes from static configuration or was resolved from the chain.
Error Codes
All errors returned by the SDK use the KeyBayErrorCode enum. The error object includes a machine-readable code, a human-readable message, and an optional details field.
| Code | Category | Description |
|---|---|---|
| NETWORK_ERROR | Network | HTTP request failed (DNS, connection refused, etc.) |
| RPC_UNAVAILABLE | Network | OP_NET RPC node is unreachable. |
| RPC_TIMEOUT | Network | Request exceeded timeout (default 30s). |
| VAULT_PAUSED | Vault | Vault operations are temporarily paused by the operator. |
| BELOW_MINIMUM_DEPOSIT | Vault | Deposit amount is below the minimum (10,000 sats for vault, 50,000 sats for credit). |
| INSUFFICIENT_BALANCE | Vault | Insufficient BTC balance for the requested operation. |
| INSUFFICIENT_SHARES | Vault | Insufficient kbBTC shares for withdrawal. |
| VAULT_NOT_CONFIGURED | Vault | Vault contract has not been initialized by the operator. |
| LTV_EXCEEDED | Credit | Borrow would push LTV above the 40% maximum. |
| BELOW_LIQUIDATION_THRESHOLD | Credit | Position LTV has reached the 50% liquidation threshold. |
| NO_COLLATERAL | Credit | No collateral deposited for this address. |
| OUTSTANDING_DEBT | Credit | Cannot withdraw collateral while debt is outstanding. |
| POSITION_LIQUIDATED | Credit | Position has already been liquidated. |
| NO_ACTIVE_LOAN | Credit | No active loan exists for this address. |
| ORACLE_STALE | Oracle | Oracle price has not been updated within 6 blocks. |
| ORACLE_PRICE_ZERO | Oracle | Oracle returned a zero price (not initialized). |
| ORACLE_NOT_CONFIGURED | Oracle | Oracle contract address not set in configuration. |
| INVALID_ADDRESS | Validation | Bitcoin address format is invalid. |
| INVALID_AMOUNT | Validation | Amount string is malformed or negative. |
| INVALID_RAIL | Validation | Unknown stablecoin rail type (must be 0 or 1). |
| API_ERROR | API | Server returned a non-2xx status code. |
| NOT_FOUND | API | Requested resource does not exist (HTTP 404). |
| UNAUTHORIZED | API | Payment proof invalid or insufficient (HTTP 403). |
| CONTRACT_REVERT | Contract | OP_NET smart contract call reverted. |
| TRANSACTION_FAILED | Contract | Bitcoin transaction failed to broadcast or confirm. |
| SDK_NOT_CONFIGURED | Config | SDK was instantiated without a required config field (e.g., missing apiBaseUrl). |