Hydra
Docs

Authenticate with your wallet to receive a Hydra API key.

Wallet Authentication

Hydra uses wallet-signed JWTs for authentication. You sign a message proving ownership of your wallet — no custody, no bridging, no passwords.

Authentication Flow

1. GET  /auth/challenge          → receive a one-time challenge string
2. Sign challenge with your wallet
3. POST /auth/token  { address, signature, streams[] }
   → receive JWT (24hr expiry)
4. Use JWT in Authorization header or ?token= query param

Step 1 — Get a Challenge

GET https://api.hydra.app/auth/challenge?address=0xYourWalletAddress
{
  "challenge": "Hydra-AUTH:0x1a2b3c...:1710000000:nonce-abc123",
  "expiresAt": 1710000060
}

The challenge expires in 60 seconds. Request a new one if it expires.

Step 2 — Sign the Challenge

Sign the exact challenge string using personal_sign (EIP-191):

// ethers.js
const signature = await signer.signMessage(challenge)

// viem
const signature = await walletClient.signMessage({ message: challenge })

// web3.js
const signature = await web3.eth.personal.sign(challenge, address, '')

Step 3 — Exchange for a JWT

POST https://api.hydra.app/auth/token
Content-Type: application/json

{
  "address": "0xYourWalletAddress",
  "signature": "0xSignatureHex...",
  "streams": ["aircraft", "cyber", "alerts"]
}

Response:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresAt": 1710086400,
  "address": "0xYourWalletAddress",
  "streams": ["aircraft", "cyber", "alerts"],
  "tier": "advanced"
}
📋
Note

The server checks your on-chain token balance at the time of signing. If your balance is below the threshold for any requested stream, that stream is excluded from the JWT scope — no error is thrown.

Step 4 — Use Your JWT

Pass the JWT as a Bearer token on every request:

# REST
curl https://api.hydra.app/v1/signals \
  -H "Authorization: Bearer eyJhbGci..."

# WebSocket
wss://api.hydra.app/stream?token=eyJhbGci...

Token Renewal

JWTs expire after 24 hours. Renew by repeating the sign flow — no new challenge needed if you use the same wallet. Your application should handle 401 Unauthorized responses by re-authenticating:

async function fetchWithAuth(url: string) {
  let res = await fetch(url, { headers: { Authorization: `Bearer ${jwt}` } })
  if (res.status === 401) {
    jwt = await authenticate()  // re-run sign flow
    res = await fetch(url, { headers: { Authorization: `Bearer ${jwt}` } })
  }
  return res
}

Error Responses

Code Reason
400 Bad Request Invalid signature or malformed request
401 Unauthorized JWT expired or invalid
402 Payment Required Insufficient token balance
403 Forbidden Stream not in your JWT scope
429 Too Many Requests Rate limit exceeded