Authorization troubles on Advanced Trading API

Code:

import datetime
import requests

print(datetime.datetime.now().isoformat())
r=requests.get(url="https://api.exchange.coinbase.com/time")
print(r.text)

Result:

2022-11-29T22:28:12.420128
{"iso":"2022-11-30T06:28:11.157Z","epoch":1669789691.157}

I’m in Z -0800 so that jives perfectly, within 1.5 sec

1 Like

Ha yeah, I was responding to the other two and had to break away for a bit before I hit post. Didn’t see your comment come in until after.

Is that right? isn’t it just 'https://coinbase.com'?

It’s what working with the others. I could try changing it to see, but the other endpoints work with it.

Update: Made no difference

This is the code I use to build the request:

from requests.auth import AuthBase
import hmac, hashlib, time, base64

import KCSettings as Settings


api_key = Settings.api_key
api_secret = Settings.api_secret
api_url = Setttings.api_url

class CBAuth(AuthBase):

    def __init__(self, api_secret,api_key, api_url):
        # setup any auth-related data here
        self.api_secret = api_secret
        self.api_key = api_key
        self.api_url = api_url
        print(api_secret, api_key, api_url)


    def __call__(self, request):
        timestamp = str(int(time.time()))
        message = timestamp + request.method + self.api_url
        signature =  hmac.new(api_secret.encode('utf-8'), message.encode('utf-8'), digestmod=hashlib.sha256).digest()

        request.headers.update({
            'CB-ACCESS-SIGN': signature.hex(),
            'CB-ACCESS-TIMESTAMP': timestamp,
            'CB-ACCESS-KEY': api_key,
            'accept': "application/json"

        })

        return request

Then, to make the request from another module:

    import KCBAUTH as CBAuth

.....

    auth = CBAuth.CBAuth(api_key, api_secret, api_url)
    response = requests.get(url, auth=auth).json()

Ah I got it. You need to include the payload converted to json with the signature.

So, assuming payload looks something like this:

payload = {
    "side": "BUY",
    "order_configuration": {
        "limit_limit_gtc": {
            "base_size": "0.001",
            "limit_price": "1000"
        },
    },
    "product_id": "BTC-USD",
    "client_order_id": "whatever5"
}

Just change this one line and it should work.

message = timestamp + request.method + self.api_url + json.dumps(payload)
1 Like

So payload needs to be included? And if there is no payload? Looks like I might need to have a second routine if there is one. I guess I could check to see if there is a value for payload (there isn’t for GET requests) and add it if necessary, or include a blank payload.

Does that sound right?

UPDATE: Thanks for the tip. I created a second auth routine for requests that have a payload. It worked! I just have to pass that payload into the auth routine.

1 Like

So I think they are going with arbitrary “conventional” RESTful routing. Basically if you can put the data in a short string in the url, do that. So GET, and DELETE requests normally use a url string and no payload, since the route, url, and the string are effectively the payload object where ={delete: this_thing}. 3 things. While PUT and POST generally need at least 4. The thing to change/post, what to change, and if you are putting or posting if that makes sense. So you need an object with the data where POST = {this: value}. There’s no performance gain to doing it, it’s just convention. And that’s more nonsensical information than you asked for lol.

The one difference I see is that to cancel orders we use and array of IDs, but the endpoint is /batch_cancel, so potentially there will be a single delete route coming, although it’s easy enough to just send an array of one ID.

I just hope they get it all down more solidly soon. I had a whole app that handled five different strategies in Pro, each with its own portfolio. Now I’m working out how to do “portfolios” on my end by keeping track of things in my own database since it appears that portfolios are not coming to Advanced Trading anytime soon.

I know they want to sunset Pro, but if we can’t mostly duplicate the functions there, it’s gonna be a lot of coding.

1 Like

Indeed. Although I gotta say, if if you ignore what’s missing, what they do have so far (that works) is great and I love it.

Best example I can think of being canceled orders. In the Pro/Exchange API, if you cancelled an order it would just disappear. And sometimes, orders would get cancelled automatically for whatever reason. And sometimes when GETting an order, you would get a 404 if Coinbase was having problems or high volumes etc. Synchronization was a nightmare because if you got a 404, you never knew if it was canceled, or just needed to check back in a few seconds before reordering it. Checking multiple times used up you rate limit and wasted time. Now if you get a 404 you know to just check back later because canceled orders return a canceled status.

That alone allowed me to delete like 1/3 of my code because there was way less error handling. I just finished migrating my app (just one strat) to the new API today and the whole thing a whole lot faster and cleaner.

My next goal is to learn websockets. That way, I can just watch for the order completions there.

I got the same issue. but it did not work after passing the payload and including it in signature.


I am using javascript in google sheets script. 
The following code works for GET.
 I got internal error without including payload in signature and Unauthorized error after including it.

Can you pls point me to the right direction?


function GenerateSignature_Coinbase(message, secret)
{
  let byteSignature = Utilities.computeHmacSha256Signature(message, secret);
  let signature = byteSignature.reduce(function(str,chr) {
    chr = (chr < 0 ? chr + 256 : chr).toString(16);
    return str + (chr.length==1?'0':'') + chr;
  },'');

  return signature;
}

function GenerateTimeStamp_Coinbase()
{
  var timestamp = Math.floor(Date.now() / 1000).toString();
  return timestamp;
}

function SignAPICall_Coinbase(method, endpoint, params) 
{
  const baseUrl = 'https://coinbase.com';
  const api_key = Global_Exchange.coinbase_apikey;
  const api_secret = Global_Exchange.coinbase_secret;

  var timestamp = GenerateTimeStamp_Coinbase();

  var message = timestamp + method + endpoint;

  if(method=='POST' || method=='PUT')
  {
    message = message + JSON.stringify(params);
    Logger.log("message="+message);
  }


  var signature = GenerateSignature_Coinbase(message, api_secret);

  var query = "?" + ObjectToQueryParams(params);

  const options = 
  {
    method: method,
    muteHttpExceptions: true,    
    accept: 'application/json', 
    
    headers: 
    {
      'Content-Type': 'application/json',
      'CB-ACCESS-KEY': api_key,
      'CB-ACCESS-SIGN': signature,
      'CB-ACCESS-TIMESTAMP': timestamp,
      'CB-VERSION':          '2017-11-15'
    },
  }; 

  if(method=='POST' || method=='PUT')
  {
    query = "";

    options.payload = JSON.stringify(params);
    Logger.log("options.body="+options.payload);
  }

  var fullURL = baseUrl + endpoint + query;
  Logger.log(fullURL);

  var responseJson = UrlFetchApp.fetch(fullURL, options);
  return(responseJson);  

}


function MarketOrderFromCoinbase (orderSide,tradeSize) 
{
  if(orderSide.toString().toUpperCase().indexOf('BUY') > -1)
  {
    if(tradeSize<Global_Exchange.minBuy)
    {
      return null;
    }
  }
  else if(orderSide.toString().toUpperCase().indexOf('SELL') > -1)
  {
    if(tradeSize<Global_Exchange.minSell)
    {
      return null;
    }
  }

  const endpoint = '/api/v3/brokerage/orders';
  
  const method = 'POST';

  const orderGuid = GUID_Gen();

  const params = 
  {
    side: orderSide,
    order_configuration: 
    {
      market_market_ioc: {base_size: tradeSize},
    },
    client_order_id: orderGuid,
    product_id: 'BTC-USD'
  };
  
  
  var responseJson = SignAPICall_Coinbase(method, endpoint, params);
  Logger.log(responseJson);
  
}

I am not a Java guy, so I’m not sure I understand how you’re getting the timestamp. It seems like you’re doing some manipulation of it other than just converting it to a string. Perhaps that is the issue? The timestamps between your end and Coinbase need to be within 30 secs of each other.

Maybe check what you’re getting vs. Coinbase -

1 Like

Just figured it out. I changed the number to string in order_configuration and it worked as expected. Thanks anyway.

1 Like

Thanks for your help!
I figured out the authentication issue. I find out the IP from Texas is banned by Coinbase. After changing to a NewJesersy IP, the code worked well.

1 Like

My auth code is working for all of the endpoints I am trying to hit, but am getting a strange error when I try to hit /api/v3/brokerage/accounts.

The response that I get is a 404 with “error”: “unknown” and looks like this:

{'error': 'unknown', 'error_details': "Could not find user's accounts information", 'message': "Could not find user's accounts information"}

Any clue where i can look to get more information on what is wrong with my request? I know the user keys I am testing with have AVAX in the account.

The accounts endpoint I think is either buggy, or not what I would expect from it. I haven’t seen that response, but unless I have my api account permissions set to ‘All’, I don’t get anything back.

2 Likes

It seems to only return the wallets that Coinbase auto generates when you either deposit or buy something on there. Definitely not like the accounts endpoint on PRO. They should have named it /wallets instead.

Interesting, thanks for the replies. The keys I am testing with have all permissions and still no luck. Might be something to do with what @Rainner has suggested.

I have something similar where all my endpoints are working accept list_orders, yey for beta. In your example you appear to just be requesting all accounts, what is the relevance of AVAX ??

@marvin The relevance is solely that the api keys are for an account that is funded with a coinbase supported asset.