python-3.xkraken.com

Kraken API 'EAPI:Invalid key' with Python3


I tried making a simple function which makes a HTTP request to Kraken exchange API. The method is private, I am trying to fetch my account balance.

According to Kraken documentation (https://www.kraken.com/features/api#general-usage):

HTTP header:

API-Key = API key

API-Sign = Message signature using HMAC-SHA512 of (URI path + SHA256(nonce + POST data)) and base64 decoded secret API key

POST data:

nonce = always increasing unsigned 64 bit integer

otp = two-factor password (if two-factor enabled, otherwise not required)

I tried to make my signature generation similar to the "veox" Python library (available at: https://github.com/veox/python3-krakenex/blob/master/krakenex/api.py).

I am using Python 3.6.7 on Ubuntu 18.04.

2FA (otp) is turned on for my account on Kraken exchange, though I am not sure if I need to include in the request.

I searched stack overflow for the solution, but I can't seem to get anything from the posts available. (Please keep in mind I'm fairly new to Python and Stack Overflow)

I get 200 response from the server so I am pretty sure the problem is in generating the signature.

Here is my code (xxx, yyy and zzz variables are purposefully written like that):

Kraken_secret_key = 'xxx' 

Kraken_headers ={

    'Kraken_API_key': 'yyy' 

}

def Kraken_account_balance(Kraken_headers):

    URI_path= '/0/private/Balance'

    URL_path = 'https://api.kraken.com/0/private/Balance'

    Kraken_nonce = str(int(time.time()*1000))

    otp = 'zzz'

    Kraken_POST_data = {

        'nonce': Kraken_nonce,
        'otp': str(otp)

    } 

    encoded = (str(Kraken_nonce)+str(otp)).encode()  

    message = URI_path.encode() + hashlib.sha256(encoded).digest() 

    Kraken_signature = hmac.new(base64.b64decode(Kraken_secret_key), message, digestmod=hashlib.sha512)

    Kraken_signature_digest = base64.b64encode(Kraken_signature.digest())

    Kraken_headers['Kraken_API_Signature'] = Kraken_signature_digest.decode()

    response = requests.post(URL_path,data= Kraken_POST_data, headers = Kraken_headers)

    result = response.json()

    print(result)

Solution

  • So I figured out why my code was not working.

    Short answer:

    1. POST data used in the Kraken API call has to be URL encoded. That means that "nonce" and "otp" have to be URL encoded for the API to work properly. I used "urllib.parse.urlencode" method from the "urllib" module to get API to work properly.
    2. Header values have to explicitly be called the same names as in the Kraken API manual.

    Long answer:

    1. This may be because I am a beginner in coding API's, but Kraken API manual does not explicitly state that POST data has to be URL encoded. OTP (2 factor authentication) did not affect my code in this case so I got rid of that part of the POST data in the call.

    In my case, the only POST data I used was the "nonce" value. So for example, if in the above code nonce was equal to

    'nonce': 666999
    

    the same value used in the call, but URL encoded with 'urllib.parse.urlencode' method would be equal to

    "nonce=666999"
    
    1. Again, probably not a problem for more experienced developers, but it was not obvious to me that header values have to explicitly be called the same names as in the Kraken API manual.

    So in the above code

    Kraken_headers ={
    
    'Kraken_API_key': 'yyy' 
    
    }
    

    and

    Kraken_headers['Kraken_API_Signature'] = Kraken_signature_digest.decode() 
    

    should be renamed to

    Kraken_headers ={
    
    'API-Key': 'yyy' 
    
    }
    

    and

     Kraken_headers['API-Sign'] = Kraken_signature_digest.decode()
    

    Here is the full working code, just replace the private and public key values with your values:

    import requests
    import time 
    import hmac
    import hashlib
    import json
    import base64
    import urllib
    
    Kraken_secret_key = 'xxx' 
    
    Kraken_headers ={
    
    'API-Key': 'yyy' 
    
    }
    
    def Kraken_account_balance(Kraken_headers):
    
        URI_path= '/0/private/Balance'
    
        URL_path = 'https://api.kraken.com/0/private/Balance'
    
        Kraken_nonce = str(int(time.time()*1000))
    
        Kraken_POST_data = {
    
            'nonce': Kraken_nonce
        } 
    
        url_encoded_post_data = urllib.parse.urlencode(Kraken_POST_data) 
    
        encoded = (str(Kraken_POST_data['nonce'])+url_encoded_post_data).encode()  
    
        message = URI_path.encode() + hashlib.sha256(encoded).digest() 
    
        Kraken_signature = hmac.new(base64.b64decode(Kraken_secret_key), message,  
        hashlib.sha512)
    
        Kraken_signature_digest = base64.b64encode(Kraken_signature.digest())
    
        Kraken_headers['API-Sign'] = Kraken_signature_digest.decode()
    
        response = requests.post(URL_path,data= Kraken_POST_data, headers = 
        Kraken_headers)
    
        result = response.json()
    
        print(result)
    
    Kraken_account_balance(Kraken_headers)