Skip to content
Reference

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.

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

POST/auth/nonce

Request a nonce to sign. The returned message is what you sign; it expires after five minutes.

Request body
{
  "wallet": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU"
}
Response · 200
{
  "nonce": "y0Q3w8r2k1m9b7v4t6n8x2c5z1a3s7d9",
  "message": "Sign in to Signalhouse\nNonce: y0Q3w8r2k1m9b7v4t6n8x2c5z1a3s7d9",
  "expiresAt": "2026-05-31T19:45:00.000Z"
}
POST/auth/verify

Verify the signed message and receive a bearer token plus your user record.

Request body
{
  "wallet": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "message": "Sign in to Signalhouse\nNonce: y0Q3w8r2k1m9b7v4t6n8x2c5z1a3s7d9",
  "signature": "4f8a...c2e1"
}
Response · 200
{
  "accessToken": "eyJhbGciOiJI...",
  "expiresAt": "2026-06-01T07:45:00.000Z",
  "user": {
    "id": "usr_a1b2c3",
    "wallet": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
    "role": "user"
  }
}
POST/auth/logoutAuth

Revoke the current session server-side.

Response · 200
{ "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).

GET/strategies

List strategies. Query: sort (proof_of_edge | realized_pnl | drawdown | copy_safety | updated), status, creatorType, market, riskLevel, limit, cursor.

Response · 200
{
  "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"
}
GET/strategies/:id

Strategy detail with the latest snapshot, latest ProofOfEdge score, and recent fills. snapshot and proofOfEdge are null until they exist.

Response · 200
{
  "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" }
  ]
}
GET/strategies/:id/positions

Open positions for a strategy.

Response · 200
{
  "items": [
    { "id": "pos_01", "market": "SOL-PERP", "side": "long", "sizeUsd": "12500", "unrealizedPnlUsd": "430.18", "updatedAt": "2026-05-31T18:00:00.000Z" }
  ]
}
GET/leaderboards/strategies

Ranked strategies for a window. Query: window (24h | 7d | 30d | all), sort (+ stake), market, riskLevel, limit, cursor.

Response · 200
{
  "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.

POST/strategies/:id/followAuth

Follow a strategy with an allocation and a risk profile. Returns the serialized follow with its active risk settings.

Request body
{
  "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"
}
Response · 200
{
  "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
  }
}
PATCH/me/risk-settingsAuth

Update the allocation or risk profile of a follow. Returns the serialized follow with a new riskProfileVersion.

Request body
{
  "strategyFollowerId": "flw_3c2b",
  "allocationUsd": "3000",
  "riskProfile": {
    "maxCopySizeUsd": "1500",
    "maxLeverage": "3",
    "maxDailyLossBps": 500,
    "maxDrawdownBps": 1500,
    "allowedMarkets": ["SOL-PERP"]
  }
}
GET/me/followsAuth

Your follows. Query: status (active | paused | stopped), limit, cursor.

Response · 200
{
  "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
}
DELETE/strategies/:id/followAuth

Stop following. Sets the follow to stopped and revokes its active risk settings.

POST/followers/:id/pauseAuth

Pause copying for a follow without stopping it.

POST/followers/:id/resumeAuth

Resume a paused follow. Requires an active, delegated Drift account and an active strategy.

GET/followers/:id/riskAuth

Latest risk snapshot for a follow, or null if no copy has been evaluated yet.

Response · 200
{
  "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.

GET/drift/accountsAuth

Detect existing Drift accounts for the wallet and their delegation state.

Response · 200
{
  "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" }
    }
  ]
}
POST/drift/setup-transactionAuth

Build account setup. Returns an unsigned transaction and an intent. delegate-transaction and revoke-delegation-transaction take { driftAccountPublicKey, subaccountId } and return the same shape.

Request body
{
  "subaccountId": 0,
  "name": "Main"
}
Response · 200
{
  "intentId": "int_4d5e",
  "transactionBase64": "AQABA...==",
  "transactionFingerprint": "f1a2…",
  "instructionFingerprint": "9c8b…",
  "recentBlockhash": "8Xy…",
  "lastValidBlockHeight": 298411022,
  "feePayer": "7xKX…gAsU",
  "expiresAt": 1780251900000
}
POST/drift/confirm-transactionAuth

Verify a submitted signature against its intent. Confirms only on a clean verification.

Request body
{
  "intentId": "int_4d5e",
  "txSignature": "5aPq…f3"
}
Response · 200
{
  "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 shape
{
  "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.