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"
}
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 |