Retry-After header is 0 when receiving 429 error.

Hi,

When calling the /sapi/v1/accountSnapshot endpoint more than 5 times in a minute I get a 429 response. I am using a library that will automatically back off for the number of seconds that the “Retry-After” header specifies. However, the header that arrives with the 429 response contains “0”, so the library waits zero seconds before trying again. I’ve worked around this by telling the library to ignore the header and back off by a hard coded number of seconds. I’ve also manually implemented rate limiting of 5 calls a minute for this endpoint.

The documentation at https://binance-docs.github.io/apidocs/spot/en/#limits says A Retry-After header is sent with a 418 or 429 responses and will give the number of seconds required to wait, in the case of a 429, to prevent a ban, or, in the case of a 418, until the ban is over.

Does anyone know if these 0s are intended behaviour? It makes the “Retry-After” header pretty useless.

Additionally the documentation for this endpoint at https://binance-docs.github.io/apidocs/spot/en/#daily-account-snapshot-user_data specifies the weight as 1, but only 5 calls per minute appear to be allowed. A header called “X-SAPI-USED-IP-WEIGHT-1M” appears in the response which increases by 2400 every time I make the call, but I cannot find any mention of this header in the documentation or what limits there might be on it. Can anyone point me towards documentation for this limit?

Thanks in advance

sh41

The requests limits should respect what’s specified in the exchangeInfo endpoint.
To know more about this, please visit: https://binance-docs.github.io/apidocs/spot/en/#limits

You should respect the limits, of course. But the specs define that the retry-after value of a 429 should tell you how long to wait to prevent a ban (418), as specified in the docs A Retry-After header is sent with a 418 or 429 responses and will give the number of seconds required to wait, in the case of a 429, to prevent a ban, or, in the case of a 418, until the ban is over. .
We ought to depend on that value to not get banned. Returning 0 for this value is simply a bug.

Hi @aisling, thanks for the pointer. Could you help me interpret this specifically in regards of the GET /sapi/v1/accountSnapshot endpoint? The documentation at https://binance-docs.github.io/apidocs/spot/en/#daily-account-snapshot-user_data specifies a weight of 1. When I call GET /api/v3/exchangeInfo the rateLimits property in the response contains the following:

[
  {
    "interval": "MINUTE",
    "intervalNum": 1,
    "limit": 1200,
    "rateLimitType": "REQUEST_WEIGHT"
  },
  {
    "interval": "SECOND",
    "intervalNum": 10,
    "limit": 100,
    "rateLimitType": "ORDERS"
  },
  {
    "interval": "DAY",
    "intervalNum": 1,
    "limit": 200000,
    "rateLimitType": "ORDERS"
  }
]

my interpretation of this is that I there is a limit of 1200 “weights” per “1” “MINUTE”. GET /sapi/v1/accountSnapshot has a “weight” of “1” so I should be able to make 1200 calls to the GET /sapi/v1/accountSnapshot endpoint every minute. However, the observed behaviour is that I can make 5 calls to the endpoint and then on the 6th call I will receive at 429 response with a Retry-After header value of 0. Rather than waiting 0 seconds I actually have to until a minute has passed since the first call before I can make calls to the endpoint again.

Could you help me to reconcile the observed behaviour with the documented behaviour?

Thanks and all the best

Its correct that the Retry-After header does not work. It always is 0 for me also.

The work around that I found for this was using the Date in the response header. I found that the Weight Used always resets to 0 when the server time changes to the next minute. By extracting the seconds value from the Date header and using the Weight Used, you can write some code to back off until the server rolls over to the next minute.

My Python code:

def _check_api_weight_limit(self, response):
    """Check if weight limit is exceeded"""
    print(response.headers)
    print(response.headers['x-mbx-used-weight'])
    print(response.headers['Date'][23:25])

    if response.status_code == 429:
        sys.exit("Weight Limit Exceeded")
    elif response.status_code == 200:
        if int(response.headers['x-mbx-used-weight']) > 1180:
            time.sleep(60-int(response.headers['Date'][23:25]))
        else:
            pass
    else:
        pass

Feedback has been passed internally.