webull: get_options has stopped working today, returns a json error

Hello,

Looks like the endpoint for get_options has stopped working today. It now returns this json error when trying to unpack the response using json(): requests.exceptions.JSONDecodeError: [Errno Expecting value] : 0

The response code itself is 200, so it seems like it’s getting something back. Also, the other endpoints work just fine still(at least the ones I’ve tried). Has this one changed at all?

The specific endpoint that is causing trouble is this one: def options(self, stock): return f'{self.base_options_url}/quote/option/{stock}/list'

Can anyone else verify that the endpoint is not working as expected?

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 38 (9 by maintainers)

Most upvoted comments

I have got some workaround.

  1. Query option list. (POST)

curl -XPOST https://quotes-gw.webullfintech.com/api/quote/option/strategy/list -H “content-type:application/json” -d ‘{“tickerId”:913244796,“count”:-1,“direction”:“all”,“expireCycle”:[3,2,4],“type”:0,“quoteMultiplier”:100}’

The result is a HUGE json in which we can find the complete option expire date list and each option’s tickerID. We need the tickerID in the next HTTPS request.

{ “strikePrice”: “27”, “volume”: “0”, “latestPriceVol”: “0”, “expireDate”: “2022-06-03”, “tickerId”: 1032363507, “belongTickerId”: 913244796, “activeLevel”: 27, “cycle”: 2, “weekly”: 1, “executionType”: “A”, “direction”: “call”, “derivativeStatus”: 0, “currencyId”: 247, “regionId”: 6, “exchangeId”: 189, “symbol”: “SQQQ220603C00027000”, “unSymbol”: “SQQQ”, “quoteMultiplier”: 100, “quoteLotSize”: 100 }

  1. Get the option quotes. (GET)

GET this endpoint with a list of option ticker IDs.

curl “https://quotes-gw.webullfintech.com/api/quote/option/quotes/queryBatch?derivativeIds=1032283511,1032363508,1032363507

Thanks everyone for the work done here. I’m reopening this until the fix is integrated into this package and doesn’t require a custom workaround by users of the package.

It does appear that they POST them through https://quotes-gw.webullfintech.com/api/quote/option/strategy/list instead, with a payload of:

  • Ticker Id (obtained through the get_ticker call)
  • Count (Number of options to return in the call)
  • Direction
  • expireCycle (this one may need some deciphering, looks like it requires a list of integers)
  • Type (0, making an assumption that this will be defaulted to 0)
  • quoteMultiplier (100, how many shares are obtained when the contract is redeemed)

I can probably play with this a little later and see if I can make any headway.

Dear All.

Thanks for the discussion here.

After some time investigating, I realized that the webapp endpoint for option is updated. This is probably a good reason for intermittent success on the old endpoint. My guess is that they did not stop it completely, but some servers have it stopped working, so with some load balancing, if you are responded by servers that have deprecated the endpoint would result in the empty response or the json error.

It is likely that the old endpoint will be stopped completely, so I updated the endpoint. The response is not completely the same some some variables have been changed. I tried to respond it in the same format so the code might look a bit messy. I tried calling the new endpoint 30+ times and found it working 100%. The new endpoint is a bit fat though, so it’s taking webull’s server a bit longer for each request.

I am happy it’s solved. Sorry for the delayed response. I am a bit busy with job interviews and I try to shy away from my investing recently since the market is doing so bad. Thanks to Yashwant who has been encouraging me on LinkedIn and on the buymecoffee app. If you like I do, you can post some kind words to me too. 😂🙏🙏🙏

Sorry about the ignorance. The random reqid does fix the issue. It seems like Webull is demanding the headers as well. I think the package has been updated, and pushed to pypi as well. Thank you for the investigation!

Try this one:

self._headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:99.0) Gecko/20100101 Firefox/99.0',
            'Accept': '*/*',
            'Accept-Encoding': 'gzip, deflate',
            'Accept-Language': 'en-US,en;q=0.5',
            'Content-Type': 'application/json',
            'platform': 'web',
            'hl': 'en',
            'os': 'web',
            'osv': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:99.0) Gecko/20100101 Firefox/99.0',
            'app': 'global',
            'appid': 'webull-webapp',
            'ver': '3.39.18',
            'lzone': 'dc_core_r001',
            'ph': 'MacOS Firefox',
            'locale': 'eng',
            'reqid': 'feb46a9524574a59b8f121c8eab62023',
            'device-type': 'Web',
            'did': self._get_did()
        }

You may change reqid to any 32-byte random hash value.

More testing needed in today’s trading session, lets see.

This returns an unauthorized user error:

'msg': 'unauthorized user', 'code': '417'

EDIT: Nevermind, I got this to work by placing it in the headers area of the webull class. Prior to that I was doing a hacky job just for testing. Placing it in the correct spot seems to apply the trade token. Currently this is working for me. Thanks.

Per your suggestion I’m using a random value for the reqid. Perhaps this will avoid unwanted eyeballs if we don’t all use the same reqid for every request. Here’s my code - there’s probably a better way of doing it though.


import random
req_id = str(random.randint(10000000000000000000000000000000, 99999999999999999999999999999999))
self._headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:99.0) Gecko/20100101 Firefox/99.0',
            'Accept': '*/*',
            'Accept-Encoding': 'gzip, deflate',
            'Accept-Language': 'en-US,en;q=0.5',
            'Content-Type': 'application/json',
            'platform': 'web',
            'hl': 'en',
            'os': 'web',
            'osv': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:99.0) Gecko/20100101 Firefox/99.0',
            'app': 'global',
            'appid': 'webull-webapp',
            'ver': '3.39.18',
            'lzone': 'dc_core_r001',
            'ph': 'MacOS Firefox',
            'locale': 'eng',
            'reqid': req_id,
            'device-type': 'Web',
            'did': self._get_did()
            }

ok solved, you need almost all of the headers in the request device-type,os,ph,hl,platform,reqid,osv all of there are required now

That’s working for me too. I think I was missing the “json” field and that’s why it was failing. Thanks BobLiu20, jjqqkk, and grm083.

It is working well for me.

import requests
json_data = {
    'tickerId': 913244796,
    'count': 1,
    'direction': 'all',
    'expireCycle': [
        3,
        2,
        4,
    ],
    'type': 0,
    'quoteMultiplier': 100,
}

response = requests.post('https://quotes-gw.webullfintech.com/api/quote/option/strategy/list', json=json_data)
print(response.json())

The first request https://quotes-gw.webullfintech.com/api/quote/option/strategy/list must be called with http/2, otherwise webull server returns 500 error.

The python code in this library is not able to make http/2 connection coz requests is not ready for http/2 yet. We may import httpx for that POST request.

Curl makes http/1.1 POST and changes to http/2 seemlessly (curl -v to verify). I need a very quick fixup and let my bot work today. So I am using subprocess to fork curl and get the JSON data through stdout.