Skip to main content
POST https://hubra.app/api/v1/stake
Build a Solana transaction that stakes amount of the strategy’s input asset. The returned transaction is base64-encoded:
  • Unsigned for sol-liquid-stake, sol-leveraged-stake, and usdc-earn.
  • Partially signed for sol-native-stake (the stake-account slot is pre-signed by the server; you only sign the wallet slot).
The agent signs locally and broadcasts via POST /api/v1/broadcast.

Request

curl -X POST https://hubra.app/api/v1/stake \
  -H 'Content-Type: application/json' \
  -d '{
    "strategy": "sol-liquid-stake",
    "wallet":   "<your-wallet>",
    "amount":   "1.5"
  }'
FieldTypeRequiredDescription
strategy"sol-native-stake" | "sol-liquid-stake" | "sol-leveraged-stake" | "usdc-earn"yesWhich strategy to stake into.
walletstringyesSolana wallet pubkey (base58) that will sign the transaction.
amountstringyesAmount to stake, decimal string. The input asset is the strategy’s assetSOL for the three sol-* keys (including sol-leveraged-stake), USDC for usdc-earn.
partner_api_keystringnoYour partner API key from partner.hubra.app. Credits the wallet you bring in to your partner account. Can also be sent as the X-Partner-Key header instead of in the body. See Partner attribution.

Partner attribution

If you’re a Hubra partner (protocol, dev, or creator), pass your partner API key on each stake call and the wallet you bring in is attributed to you — the resulting liquidity shows up on your partner dashboard. Get your key from partner.hubra.appDashboard → API key. Send it either as a body field or a header:
# As a body field
curl -X POST https://hubra.app/api/v1/stake \
  -H 'Content-Type: application/json' \
  -d '{
    "strategy":        "sol-liquid-stake",
    "wallet":          "<end-user-wallet>",
    "amount":          "1.5",
    "partner_api_key": "hbr_pk_<your-key>"
  }'

# …or as a header (keeps the key out of the JSON body)
curl -X POST https://hubra.app/api/v1/stake \
  -H 'Content-Type: application/json' \
  -H 'X-Partner-Key: hbr_pk_<your-key>' \
  -d '{"strategy":"sol-liquid-stake","wallet":"<end-user-wallet>","amount":"1.5"}'
Attribution is first-write-wins per wallet: the first partner to bring a wallet in keeps the credit. Passing the key is optional — omit it and the stake behaves exactly as before. If you pass a key that is unknown or belongs to an inactive partner, the call returns 403 forbidden so you notice rather than silently losing attribution.
Treat the partner API key like a secret — it identifies your partner account. Prefer the X-Partner-Key header in server-side integrations, and rotate the key from your dashboard if it leaks.

Response — sol-native-stake

The server generates a fresh stake-account keypair and pre-signs that signature slot. The agent only signs as the wallet. Save the returned stakeAccount pubkey — you need it for future deactivate / withdraw / instant-unstake calls against this position.
{
  "strategy":                  "sol-native-stake",
  "transaction":               "<base64 partially-signed tx>",
  "hubra_token":               "<token to pass to /broadcast>",
  "stakeAccount":              "<new pubkey to track>",
  "voteAccount":               "7K8DVxtNJGnMtUY1CQJT5jcs8sFGSZTDiG7kowvFpECh",
  "rentExemptReserveLamports": 2282880,
  "delegatedLamports":         1500000000,
  "signers":                   ["<your-wallet>"],
  "notes":                     "Stake account keypair was generated and pre-signed server-side."
}
Broadcast via route: "rpc" (no Sanctum involvement on the create + delegate path).

Response — sol-liquid-stake (Sanctum-routed)

{
  "strategy":      "sol-liquid-stake",
  "transaction":   "<base64 unsigned tx>",
  "hubra_token":   "<token to pass to /broadcast>",
  "route":         "sanctum",
  "sanctumKind":   "token",
  "sanctum_order": { "...": "..." },
  "signers":       ["<your-wallet>"]
}
When route: "sanctum", the response also carries sanctumKind and sanctum_order. All three (hubra_token, sanctumKind, sanctum_order) must be passed back to /broadcast if you want Sanctum’s MEV-protected broadcaster. Sanctum’s execute endpoint validates the signed transaction against the original order and rejects mismatches. Plain RPC (route: "rpc" at broadcast) works without the Sanctum-specific fields, but loses Sanctum’s broadcaster guarantees.

Response — usdc-earn (Voltr-routed)

{
  "strategy":    "usdc-earn",
  "transaction": "<base64 unsigned tx>",
  "hubra_token": "<token to pass to /broadcast>",
  "route":       "voltr",
  "signers":     ["<your-wallet>"]
}
Voltr-routed responses do not include sanctum_order. Broadcast via route: "rpc" — that is the only supported broadcast path for Voltr transactions.

Response — sol-leveraged-stake (raSOL Max)

amount is SOL. The server builds a single transaction that does the whole conversion: create the raSOL ATA (idempotent) → SPL stake-pool DepositSol (SOL → raSOL) → Voltr depositVault (raSOL → raSOL Max LP). One signature, atomic.
{
  "strategy":             "sol-leveraged-stake",
  "transaction":          "<base64 unsigned tx>",
  "hubra_token":          "<token to pass to /broadcast>",
  "route":                "rpc",
  "depositedSolLamports": "20000000",
  "stagedRasolLamports":  "16700000",
  "receiptMint":          "CJEYakpjmKBvvUzAn3HJSs9vtijnv472T8YJEV3WzToF",
  "receiptSymbol":        "raSOL Max",
  "signers":              ["<your-wallet>"]
}
Broadcast via route: "rpc" — no Sanctum or Voltr execute leg. After it confirms, read your raSOL Max LP balance on-chain; you pass that figure to POST /api/v1/unstake to burn the position later.
The API deposits SOL only — the SOL → raSOL leg is built in for you. There is no “deposit raSOL directly” variant on the agent surface. The Voltr leg is sized from the exact raSOL the stake-pool mint produces (the same floor-rounded lamports × supply / total_lamports the pool applies on-chain), so the staged raSOL always matches what the deposit instruction expects.

The hubra_token

Every /stake (and /unstake) response includes a hubra_token HMAC’d over the message bytes of the unsigned transaction. POST /api/v1/broadcast requires this token and rejects any transaction that was not built by Hubra. Save it alongside the transaction and pass it back when broadcasting. Tokens expire ~2 minutes after issue (matching Solana’s blockhash window). If your token expires, rebuild via /stake. For the full mechanics, see Hubra token.

Examples

# Native delegation to Hubra's validator
curl -X POST https://hubra.app/api/v1/stake \
  -H 'Content-Type: application/json' \
  -d '{"strategy":"sol-native-stake","wallet":"<your-wallet>","amount":"1.0"}'

# Liquid: SOL → raSOL via Sanctum
curl -X POST https://hubra.app/api/v1/stake \
  -H 'Content-Type: application/json' \
  -d '{"strategy":"sol-liquid-stake","wallet":"<your-wallet>","amount":"1.0"}'

# Leveraged: SOL → raSOL → raSOL Max in one tx
curl -X POST https://hubra.app/api/v1/stake \
  -H 'Content-Type: application/json' \
  -d '{"strategy":"sol-leveraged-stake","wallet":"<your-wallet>","amount":"1.0"}'

# USDC vault deposit
curl -X POST https://hubra.app/api/v1/stake \
  -H 'Content-Type: application/json' \
  -d '{"strategy":"usdc-earn","wallet":"<your-wallet>","amount":"100"}'

Errors

StatusSlugWhen
400invalid_requestMissing or malformed fields.
403forbiddenpartner_api_key (or X-Partner-Key) was supplied but is unknown or belongs to an inactive partner.
404not_foundUnknown strategy key.
502upstream_errorSanctum / Voltr could not build the transaction (wallet does not exist on-chain, no associated token account, insufficient liquidity for size).
503service_unavailableStrategy is announced but not live.

See also

POST /unstake

Reverse the position.

POST /broadcast

Submit the signed transaction.

Hubra token

The HMAC gate.

Strategies

All strategy keys.