-
Notifications
You must be signed in to change notification settings - Fork 0
Authentication
Every SDK request requires an OAuth2 bearer token (the AWS API Gateway in front of the production API rejects every unauthenticated call as of today, including conceptually-"public" market-data routes).
- Sign in at cryptohopper.com and open the developer dashboard.
-
Create an OAuth application — you'll receive a
client_id(40 chars) and aclient_secret. -
Drive the OAuth consent flow to receive a 40-character bearer token scoped to the permissions you requested:
https://www.cryptohopper.com/oauth-consent?app_id=<client_id>&redirect_uri=<your_uri>&state=<csrf> -
Exchange the auth code at
POST https://api.cryptohopper.com/v1/oauth/token(the SDK does not handle this exchange — it's a one-time setup step).
Pass the resulting bearer token as apiKey to the SDK:
val ch = Client.create("your-40-char-oauth-token")For more options, use the builder:
val ch = Client.builder()
.apiKey("your-40-char-oauth-token")
.timeout(30, TimeUnit.SECONDS)
.maxRetries(3)
.build()The SDK sends the bearer in the access-token HTTP header (not Authorization: Bearer):
GET /v1/user/get HTTP/1.1
access-token: <your-40-char-oauth-token>
Accept: application/json
User-Agent: cryptohopper-sdk-kotlin/0.1.0-alpha.1
This is the contract the AWS API Gateway in front of the production API expects. Sending Authorization: Bearer instead returns 405 Missing Authentication Token from the gateway's SigV4 parser before the request ever reaches Cryptohopper. The legacy cryptohopper-android-sdk had this bug; this SDK gets it right from day one. See the live API docs for the authoritative reference.
If you have a client_id (the OAuth app ID), pass it as appKey. The SDK sends it as the x-api-app-key header so the server can attribute traffic to your app and apply per-app rate limits separately from per-user limits.
val ch = Client.builder()
.apiKey(System.getenv("CRYPTOHOPPER_TOKEN"))
.appKey(System.getenv("CRYPTOHOPPER_APP_KEY"))
.build()This is optional but recommended for production traffic — without it you share rate-limit budget with every other unattributed caller.
Standard CLI / server pattern:
val token = System.getenv("CRYPTOHOPPER_TOKEN")
if (token.isNullOrEmpty()) {
System.err.println("Set CRYPTOHOPPER_TOKEN to a 40-char OAuth bearer first.")
exitProcess(1)
}
val ch = Client.create(token)For Android apps, never hard-code the token — store it in EncryptedSharedPreferences or the Android Keystore and read it at app launch:
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val prefs = EncryptedSharedPreferences.create(
context,
"cryptohopper_secure",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val token = prefs.getString("oauth_bearer", null) ?: error("No token stored")
val ch = Client.create(token)Passing an empty apiKey raises IllegalArgumentException at construction time — it's a fail-fast guard so you don't silently issue unauthenticated requests:
try {
Client.create("")
} catch (e: IllegalArgumentException) {
// "apiKey must not be empty"
}The same check fires from the builder: Client.builder().apiKey("").build() throws before any HTTP call is made.
OAuth tokens don't auto-refresh in the SDK — there's no refresh-token plumbing on the public client. When a long-lived token expires you'll get a CryptohopperError with code == "UNAUTHORIZED". Your app is responsible for re-running the consent flow (or retrieving a freshly-minted token from your backend) and constructing a new Client.
class TokenStore(initialToken: String) {
@Volatile private var current: Client = Client.create(initialToken)
fun client(): Client = current
fun rotate(newToken: String) {
current = Client.create(newToken)
}
}The reference itself is a Client instance — old in-flight requests on the previous instance complete normally; new requests go through the new client.
If your OAuth app has IP whitelisting enabled, the server returns 403 FORBIDDEN from any other IP. The error includes the IP the server saw, which is the easiest way to debug:
try {
ch.user.get()
} catch (e: CryptohopperError) {
if (e.code == "FORBIDDEN") {
e.ipAddress?.let { ip ->
println("Server saw your IP as $ip. Add it to the OAuth app's whitelist on cryptohopper.com.")
}
}
}-
Error Handling —
UNAUTHORIZEDvsFORBIDDENvsDEVICE_UNAUTHORIZEDand how to recover -
Rate Limits — per-app vs per-user buckets and what
appKeybuys you - Recipes — copyable patterns including retry wrappers that respect auth errors