API reference
The Signalhouse JSON API. Authentication is wallet-signature based: request a nonce, sign it, and verify to receive a bearer token. Examples below use real field names and are copyable. Money never moves without your signature, and the account-setup flow is build, confirm, verify.
Base URL and headers
All endpoints are served under a single base URL. Send JSON, and on protected endpoints include the bearer token from /auth/verify.
Content-Type: application/json
Authorization: Bearer <accessToken>Authentication
There are no passwords or API keys. You prove ownership of a wallet by signing a one-time nonce, then exchange the signature for a short-lived bearer token (a JWT valid for 12 hours).
/auth/nonceRequest a nonce to sign. The returned message is what you sign; it expires after five minutes.
{
"wallet": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
}{
"nonce": "y0Q3w8r2k1m9b7v4t6n8x2c5z1a3s7d9",
"message": "Sign in to Signalhouse\nNonce: y0Q3w8r2k1m9b7v4t6n8x2c5z1a3s7d9",
"expiresAt": "2026-05-31T19:45:00.000Z"
}/auth/verifyVerify the signed message and receive a bearer token plus your user record.
{
"wallet": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"message": "Sign in to Signalhouse\nNonce: y0Q3w8r2k1m9b7v4t6n8x2c5z1a3s7d9",
"signature": "4f8a...c2e1"
}{
"accessToken": "eyJhbGciOiJI...",
"expiresAt": "2026-06-01T07:45:00.000Z",
"user": {
"id": "usr_a1b2c3",
"wallet": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"role": "user"
}
}/auth/logoutAuthRevoke the current session server-side.
{ "ok": true }Strategies and leaderboards
These endpoints are public. Lists are offset-paginated: pass limit (1 to 100) and a numeric cursor, and read nextCursor from the response (it is null on the last page).
/strategiesList strategies. Query: sort (proof_of_edge | realized_pnl | drawdown | copy_safety | updated), status, creatorType, market, riskLevel, limit, cursor.
{
"items": [
{
"id": "str_9f3a",
"name": "Vector momentum · slow",
"description": "Trend-following SOL-PERP",
"managerWallet": "7xKX…gAsU",
"venue": "drift",
"isCopyable": true,
"proofOfEdge": 87,
"proofOfEdgeVerificationStatus": "verified",
"proofOfEdgeGrades": {
"risk": "A-", "consistency": "B+", "drawdown": "A-",
"copySafety": "A-", "edge": "A", "confidence": "A-"
},
"realizedPnlUsd": "18470.22",
"maxDrawdownBps": 612,
"followersCount": 1284,
"maxLeverage": "5",
"maxPositionSizeUsd": "250000",
"allowedMarkets": ["SOL-PERP"],
"feeBps": 1000,
"copyMode": "proportional",
"riskLevel": "moderate",
"latestSnapshotAt": "2026-05-31T18:00:00.000Z",
"createdAt": "2026-04-01T12:00:00.000Z",
"updatedAt": "2026-05-31T18:00:00.000Z"
}
],
"nextCursor": "20"
}/strategies/:idStrategy detail with the latest snapshot, latest ProofOfEdge score, and recent fills. snapshot and proofOfEdge are null until they exist.
{
"strategy": { "id": "str_9f3a", "name": "Vector momentum · slow", "venue": "drift", "isCopyable": true },
"snapshot": { "snapshotAt": "2026-05-31T18:00:00.000Z", "equityUsd": "121430.10", "realizedPnlUsd": "18470.22", "maxDrawdownBps": 612 },
"proofOfEdge": { "score": 87, "verificationStatus": "verified", "reasons": [] },
"recentFills": [
{ "id": "fil_01", "market": "SOL-PERP", "side": "long", "sizeUsd": "5000", "at": "2026-05-31T17:58:11.000Z" }
]
}/strategies/:id/positionsOpen positions for a strategy.
{
"items": [
{ "id": "pos_01", "market": "SOL-PERP", "side": "long", "sizeUsd": "12500", "unrealizedPnlUsd": "430.18", "updatedAt": "2026-05-31T18:00:00.000Z" }
]
}/leaderboards/strategiesRanked strategies for a window. Query: window (24h | 7d | 30d | all), sort (+ stake), market, riskLevel, limit, cursor.
{
"items": [
{
"rank": 1,
"score": 87,
"verificationStatus": "verified",
"reasons": [],
"window": "30d",
"snapshotAt": "2026-05-31T18:00:00.000Z",
"strategy": { "id": "str_9f3a", "name": "Vector momentum · slow", "managerWallet": "7xKX…gAsU", "venue": "drift", "isCopyable": true, "riskLevel": "moderate" }
}
],
"nextCursor": "20"
}Following and risk settings
These require a bearer token. Following is where you set the risk profile the risk engineenforces on every copy. The profile is validated against the strategy's own limits, so one that exceeds them is rejected, not saved. All write endpoints accept an idempotency key in the body as idempotencyKey or as the Idempotency-Key header.
/strategies/:id/followAuthFollow a strategy with an allocation and a risk profile. Returns the serialized follow with its active risk settings.
{
"followerDriftAccountId": "dac_77a1",
"allocationUsd": "2500",
"copyMode": "proportional",
"copyMultiplier": "1",
"riskProfile": {
"maxCopySizeUsd": "1000",
"maxLeverage": "3",
"maxDailyLossBps": 500,
"maxDrawdownBps": 1500,
"allowedMarkets": ["SOL-PERP"],
"reduceOnlyAfterLoss": true,
"minHealthRatio": "1.25",
"maxFundingBpsPerDay": 100
},
"idempotencyKey": "follow-2026-05-31-001"
}{
"id": "flw_3c2b",
"strategyId": "str_9f3a",
"status": "active",
"allocationUsd": "2500",
"copyMultiplier": "1",
"riskProfileVersion": 1,
"createdAt": "2026-05-31T19:00:00.000Z",
"updatedAt": "2026-05-31T19:00:00.000Z",
"strategy": { "id": "str_9f3a", "name": "Vector momentum · slow", "status": "active" },
"riskSettings": {
"version": 1,
"status": "active",
"maxCopySizeUsd": "1000",
"maxLeverage": "3",
"maxDailyLossBps": 500,
"maxDrawdownBps": 1500,
"allowedMarkets": ["SOL-PERP"],
"minHealthRatio": "1.25",
"maxFundingBpsPerDay": 100
}
}/me/risk-settingsAuthUpdate the allocation or risk profile of a follow. Returns the serialized follow with a new riskProfileVersion.
{
"strategyFollowerId": "flw_3c2b",
"allocationUsd": "3000",
"riskProfile": {
"maxCopySizeUsd": "1500",
"maxLeverage": "3",
"maxDailyLossBps": 500,
"maxDrawdownBps": 1500,
"allowedMarkets": ["SOL-PERP"]
}
}/me/followsAuthYour follows. Query: status (active | paused | stopped), limit, cursor.
{
"items": [
{
"id": "flw_3c2b",
"strategyId": "str_9f3a",
"status": "active",
"allocationUsd": "2500",
"riskProfileVersion": 1,
"strategy": { "id": "str_9f3a", "name": "Vector momentum · slow", "status": "active" },
"riskSettings": { "version": 1, "status": "active", "maxLeverage": "3" }
}
],
"nextCursor": null
}/strategies/:id/followAuthStop following. Sets the follow to stopped and revokes its active risk settings.
/followers/:id/pauseAuthPause copying for a follow without stopping it.
/followers/:id/resumeAuthResume a paused follow. Requires an active, delegated Drift account and an active strategy.
/followers/:id/riskAuthLatest risk snapshot for a follow, or null if no copy has been evaluated yet.
{
"snapshot": {
"healthBps": 8200,
"leverage": 2.4,
"drawdownBps": 410,
"fundingBpsPerDay": 35,
"liquidationBufferBps": 7700,
"decision": "approved",
"at": "2026-05-31T18:59:00.000Z"
}
}Account setup: build, confirm, verify
Setup, delegate, and revoke share one shape. Each build endpoint returns an unsigned transaction plus an intentId. You sign and submit the transaction, then call /drift/confirm-transaction with the intentId and signature. The server runs the strict transaction verifier before it trusts the confirmation, so nothing is recorded as done until the landed transaction is proven identical to what was built.
/drift/accountsAuthDetect existing Drift accounts for the wallet and their delegation state.
{
"status": "active",
"expectedDelegate": "Dgate…9kPp",
"accounts": [
{
"id": "dac_77a1",
"ownerWallet": "7xKX…gAsU",
"driftAccountPublicKey": "Acct…1b2c",
"subaccountId": 0,
"status": "active",
"currentDelegate": "Dgate…9kPp",
"expectedDelegate": "Dgate…9kPp",
"delegation": { "delegatePublicKey": "Dgate…9kPp", "txSignature": "5a…", "active": true, "delegatedAt": "2026-05-30T10:00:00.000Z" }
}
]
}/drift/setup-transactionAuthBuild account setup. Returns an unsigned transaction and an intent. delegate-transaction and revoke-delegation-transaction take { driftAccountPublicKey, subaccountId } and return the same shape.
{
"subaccountId": 0,
"name": "Main"
}{
"intentId": "int_4d5e",
"transactionBase64": "AQABA...==",
"transactionFingerprint": "f1a2…",
"instructionFingerprint": "9c8b…",
"recentBlockhash": "8Xy…",
"lastValidBlockHeight": 298411022,
"feePayer": "7xKX…gAsU",
"expiresAt": 1780251900000
}/drift/confirm-transactionAuthVerify a submitted signature against its intent. Confirms only on a clean verification.
{
"intentId": "int_4d5e",
"txSignature": "5aPq…f3"
}{
"status": "confirmed",
"confirmedSlot": 298411040
}Errors
Every error has the same shape. The HTTP status follows the code (400 validation and risk rejections, 401 auth, 404 missing, 409 conflicts, 500 server, 503 when Drift is unavailable).
{
"error": {
"code": "RISK_REJECTED",
"message": "Risk profile exceeds strategy limits",
"details": { "maxLeverage": "3 > 5" }
}
}- VALIDATION_ERROR
- A request body or query parameter failed validation.
- UNAUTHORIZED
- Missing, invalid, or revoked bearer token.
- WALLET_NOT_VERIFIED
- The signature or nonce did not verify during auth.
- NOT_FOUND
- The strategy, follow, or account does not exist.
- STRATEGY_NOT_ACTIVE
- The strategy is not in an active state.
- VENUE_READ_ONLY
- The strategy's venue does not support copying.
- DRIFT_ACCOUNT_MISSING
- No matching Drift account for the wallet.
- DELEGATION_MISSING
- The Drift account is not delegated, or no delegate is configured.
- RISK_REJECTED
- The risk profile exceeds the strategy's limits; details list the offending fields.
- IDEMPOTENCY_CONFLICT
- The idempotency key was already used for a different request.
- DRIFT_VERIFICATION_FAILED
- The submitted transaction did not match its intent, or was already resolved.
- DRIFT_UNAVAILABLE
- Drift could not be reached to build or read state (503).
- INTERNAL_ERROR
- An unexpected server error.