-
Notifications
You must be signed in to change notification settings - Fork 490
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
KeyError on login #511
Comments
I am also having the same issue. I digged a little deeper. Seems like the return is some sort of verification issue. I have no idea how to fix this. |
have the same issue. Wondering if the API responses changed, but haven't had a chance to look into it at all yet. |
I have the same issue, unfortunately. It would be on a day where a stock is up 200%+. If anyone figures it out let me know! I'll take a look myself when I have the time. |
I am getting error message of unsupported_grant_type errors with a status code 403(had to modify my fork to get the error codes) #510 |
Getting the same error here, I used the otp code to see if it would work in robin and it seems to work fine. |
So you were able to verify the API or only that the OTP was working? |
@ravi-bharadwaj
I get error response of: |
@doormat-1010 I got the exact same error message. check out my comment. |
I was only able to veryify that the otp was working. |
anyone try it without an authenticator app 2FA enabled? Have a feeling that may be the issue.. but going to investigate tomorrow when I have time. |
I think the 2FA is mandatory in Robinhood now. |
@ahaggard2013 Yeah I'm thinking the structure has changed in the request somehow based on the error type. |
Is it possible the endpoint changed? |
If the structure of the request has changed, we should be able to use the DevTools network listener to view the new structure. I don't have time at the moment, but if anyone else does go for it! |
Alright, I've taken a look at it. Granted, I'm not the most experienced with this sort of thing but I've compared the payload sent by the browser to login to the one robin_stocks sends. It seems to me that the difference is that the payload used to include a header called mfa_code, but now the code may be received differently and is no longer a part of the same login request. When the browser sends the login payload even it receives the internal pending message which suggests to me that it's still waiting for the 2FA code to be entered. After that request, my machine sent a second payload consisting of my device_id, a value called flow and the workflow_id. Anyone have any ideas? |
Okay, yeah, there's a second payload sent titled "response" that contains the 2FA value once entered and submitted. Not sure if it can be included in the original login request, but it's being sent to a URL that I'm not sure how to use. It doesn't seem like it would be static. |
sounds like something changed. A few things, you could try the flow with device verification and sms verification instead of an authenticator. They may differ slightly and align with the current robin_stock flow. It's also likely the mobile api could use slightly different endpoints than the browser. You may have have to bypass certificate pinning to intercept robinhood mobile requests to compare them which can sometimes be tricky. The other option is modify the library to work with the new flow. A lot of work, but a few things I would like to try soon. not home so unable to debug atm |
Seems to me that we just have to whip up a new snippet that sends the mfa code to the new end-point under the "response" header. My lack of knowledge prevents me from quickly figuring out how to consistently have access to the correct end-point. |
There are two forks addressing it, has anyone checked them? |
Is it fixed ? not sure why, I reinstalled the robin_stocks and now it works |
I have not seen any commits to main and I tried on mine and it did not work |
When it fixed, were you using mfa? |
I am using mfa |
Could you check the version.. I updated to 3.2.1 and didnt work. |
it is 3.2.1 but I delete the original venv and create a new one to reinstall it. because I found pip uninstall will not remove old files in my machine |
I am fairly certain this is a Robinhood issue and not an issue with the python package since one of my accounts worked and one did not. Just my 2 cents On Dec 5, 2024, at 20:05, erchen7 ***@***.***> wrote:
it is 3.2.1 but I delete the original venv and create a new one to reinstall it. because I found pip uninstall will not remove old files in my machine
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you commented.Message ID: ***@***.***>
|
Could it be an account configuration issue? |
seems like the flow has changed, first it calls token then pathfinder/user_machine and then user_view then you can call the /respond endpoint. I have been able to successfully call the /user_machine but its not working for /user_view |
Thank to all as well. This situation is a stretch beyond my knowledge. I as other have been putting in years on building a platform using this tool and have appreciated it so much. So I look forward when a fix can be merged. |
def td_rh_login(account): Is my final fix to the below solution: To get the device code. Log into RobinHood, account, settings, security and privacy, Two-Factor Authentication, then Authentication app -> select Update. You can add an account to MS Authentication app (verify method) and/or select can't read barcode to copy then paste in your python code (my case is bah_device = 'copied device code'
|
Is there any solution for the robinhood two factor authentication type -- Device approval ? .. I tried the above solution with the device approval authentication type, but getting the error 'status'.. |
For Anyone still troubleshooting: try this |
For those who are still having a problem, I recommend following the steps above using Ravi's authentification.py file, But the issue that I was having was I was pasting Ravi's authentification file into the wrong location. I was using some location where I downloaded the robinstock-API from git but not where my computer was pulling the git files which was from an old location I have used in the past. This true location is given in the error message when I try to run my script. That is the location where you want to paste Ravi's change. Also as for the authentification app, I am just using a text file where I pull the MFA code from and run it through python otp module. See this old video for how I do that: |
Is this the safest way to do authentication or is there something else I should research ? |
@gianpierre2024 |
I've tried several of the tips mentioned above over the past few days, but I'm still encountering the error message below. Could anyone kindly provide a solution that has worked for successfully logging in? I'm currently using version 3.2.1 of robin-stocks and have attempted to reset two-factor authentication using both text messages and an authenticator app. It will be highly appreciated. ERROR: There was an issue loading pickle file. Authentication may be expired - logging in normally. File ~\anaconda3\lib\site-packages\robin_stocks\robinhood\authentication.py:197, in login(username, password, expiresIn, scope, by_sms, store_session, mfa_code, pickle_path, pickle_name) KeyError: 'detail' |
Update: Thank you ravi-bharadwaj for the fix and thank you Tan-TanDesign for this: "take ravi's auth file and paste it into robin-stocks/robinhood/authentication.py. make sure it's the one inside the robinhood folder and not the one inside the robin-stocks folder." I feel so dumb I did not realized the right path to authentication.py, hope my mistake helps another lost soul out there. And finally, thanks to all for coming together and best wishes. |
Seems to me you are missing your 2FA, passing to the log in function just the username and password is for logging via SMS code, which I believe is no longer supported when using the API. Notes from my code: ==> [ (2) Factor Authentication. No need for Tx. Displays Current OTP ] <==totp = pyotp.TOTP(log_2FA).now() Hope it helps. |
Is there any solution that does not require manual authentication? Aka if I have someone enter there username and password it should auto login without having to set up some version of MFA, or is this the best we have for now? also when will these changes be merged? |
I would suggest, once you obtain your 2FA form robin, to just hardcode the 2FA, user_name and password; that way there is no need for human input to log in. |
if we can get the PR merged soon, it will avoid so much confusion and unnecesary steps that so many have to take. |
@Adelantado |
@Gates8911 Regarding the sleep, I would recommend to anyone to pause their program for a few secs after cancelling or submitting orders and check it's status; before doing anything else; I always do. There is no room in my logic for unconfirmed, rejected or failed orders so Ive learned to wait, check, and loop if necessary, Robinhood has gotten better and faster over time, but I've seen orders for up to two minutes with a status of "unconfirmed". Just my $0.02 |
@Adelantado I like the method you described do you have discord or some sort, where I could as you questions about the code I seem to still be struggling with your method |
I don't but you are welcome to email me anytime, if I can help, I will
On Monday, December 9, 2024 at 06:50:08 PM EST, SumayKalra ***@***.***> wrote:
@Adelantado I like the method you described do you have discord or some sort, where I could as you questions about the code I seem to still be struggling with your method
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
@Adelantado Oh, when you said hardcoding I thought you meant your sensitive info is literally in the script itself, yeah as long as its masked some way it doesnt matter how you do it, i considered multiple strategies for this. Would certainly agree there is always different ways of accomplishing a task especially when it comes to coding, and yeah i also have my code check for the order status as well after a non interference sleep function, but i use a loop function with limited number of checks, and if the order isnt filled after all checks are ran it will simply cancel the order, I finally got a separate self update function to work as well, so if my script buys but then gets an error or for example I manually buy or sell one of the assets, the bot can update itself and store the necessary info to sell later or delete a position when sold. Not that this is relevant but i also found a python package recently that enables audio messages through voice assistance manipulation, so that your script can announce errors and other activities so that you dont need to be right there reading it to know what is happening, probably routine to a lot of people on this thread, but figured I would still throw it out there incase someone didnt know this exists. Also, what did you mean by you do not have room for unconfirmed etc.? And this may be a stupid question but again im self taught with full time job lol, so my knowledge is likely limited compared to most developers, the only reason i share my methods on here, is the off chance that someone else beginning their learning journey may stumble upon it an give em more ideas. |
@Gates8911 And once again....Thanks yall |
@Adelantado Assumed yall bounce ideas off eachother and what not as well, didnt mean to annoy anyone, have a good one. |
@Gates8911 .... All is cool. English is not my native language and I've been told more than once to come across as a bit hurst when expressing my thoughts. I am aware and I do apologize for it. |
anyone know how to make the code work for device approvals. i only get device approvals now from my iphone robinhood app. and then the code i run waits for the device approval to be approved and then checks to see if i can log in successfully. but it never sees that i approved the device. import random Generate a unique device tokendef generate_device_token():
Respond to challengesdef respond_to_challenge(challenge_id, sms_code): Save and load session datadef save_session(data, pickle_name="robinhood_session.pickle"): def load_session(pickle_name="robinhood_session.pickle"): def handle_verification_workflow(device_token, workflow_id):
def robinhood_login(username=None, password=None, expires_in=86400, scope="internal"):
Main flowif name == "main": Verification workflow triggered. Workflow ID: 00e60f42-de4c-4282-ad7d-e9c01a0206e1 |
First, I would like to thank the author of this wonderful project. You are a gentleman and a scholar! So, I started having the same problem last night after a month of everything working nicely. When I run my Python script I get a notification in the Robinhood App asking if I'm trying to access my account, I click yes, then I have to enter my PIN. The script fails before the notification appears. Subsequent runs produce the same result even though I've clicked OK in the App to approve. I have been using this for some time now:
But now instead of logging in cleanly I get: Traceback (most recent call last): I tried going into /lib/python3.11/site-packages/robin_stocks/robinhood/authentication.py and adding an input() before the line: challenge_response = request_post(url=challenge_url, payload=challenge_..... so I could get the notification in the App, approve, then hit Enter to have the challenge_response generated, but that produced the same results listed above. I tried doing a new pull from Git repo and it upgraded from 3.3.0 to 3.3.1 ( from 3 days ago ) but get same result. Then I added a line to print(challenge_response) before the error occurs and this is what I get:
... then the same error listed above. Also, i will mention that even though i Approve the login in the App, when i go onto Robinhood.com > Account > Security and privacy > Devices the device I have just approved w/ my Python script is NOT there in the list. Assuming this is because the login never successfully completes. |
Once again I'd like to start by thanking the author if this amazing package... I was able to finally get logged in again with robin-stocks 3.3.1 For some reason, even though it had been working for about a month, the Authenticator App 2FA selection in my Robinhood settings was ultimately what was preventing me from loging in. On Robinhood.com > Account > Settings > Security and Privacy > Two-Factor Authentication > Switch to Another Method: select "Text message (SMS)" And edited this:
To this:
Now I get a prompt for the MFA code when I run my python script... and when I enter the code sent to my text... I'M IN!!!! I may experiment with re-enabling the TOPT setting again and if i have any further revelations i will share them here. |
I think the problem is that you cannot automate the workflow, so it needs to be manually done each time which defeats the purpose of trading through an API imo. In response to this latest issue, I have decided to migrate over to Schwab. I wrote an R package - schwabr to help with trading on Schwab that is still under dev... but works. There are two downsides from what I can see with Schwab:
Alpaca is still the best API out there, but they don't (yet) offer IRAs. Bottom line - I am disappointed in Robinhood. they clearly have the capability to allow this to work and have actively made the decision to not allow easy trading through the API. |
#################################FIXED################################# Got the following code to work with mfa still on, should still work while off, this script should prompt you to type verification code sent to you via text or email and sign in seemlessly
|
I can't thank you enough for your help or all the time, dedication
and hard work I know you have "invested" in this project!
JohnC
On 1/28/2025 9:59 PM, Gates8911 wrote:
"""Contains all functions for the purpose of logging in and out to Robinhood."""
import getpass
import os
import pickle
import secrets
import time
from robin_stocks.robinhood.helper import *
from robin_stocks.robinhood.urls import *
def generate_device_token():
"""Generates a cryptographically secure device token."""
rands = [secrets.randbelow(256) for _ in range(16)]
hexa = [str(hex(i + 256)).lstrip("0x")[1:] for i in range(256)]
token = ""
for i, r in enumerate(rands):
token += hexa[r]
if i in [3, 5, 7, 9]:
token += "-"
return token
def respond_to_challenge(challenge_id, sms_code):
"""This function will post to the challenge url.
:param challenge_id: The challenge id.
:type challenge_id: str
:param sms_code: The sms code.
:type sms_code: str
:returns: The response from requests.
"""
url = challenge_url(challenge_id)
payload = {
'response': sms_code
}
return(request_post(url, payload))
def login(username=None, password=None, expiresIn=86400, scope='internal', by_sms=True, store_session=True, mfa_code=None, pickle_path="", pickle_name=""):
"""This function will effectively log the user into robinhood by getting an
authentication token and saving it to the session header. By default, it
will store the authentication token in a pickle file and load that value
on subsequent logins.
:param username: The username for your robinhood account, usually your email.
Not required if credentials are already cached and valid.
:type username: Optional[str]
:param password: The password for your robinhood account. Not required if
credentials are already cached and valid.
:type password: Optional[str]
:param expiresIn: The time until your login session expires. This is in seconds.
:type expiresIn: Optional[int]
:param scope: Specifies the scope of the authentication.
:type scope: Optional[str]
:param by_sms: Specifies whether to send an email(False) or an sms(True)
:type by_sms: Optional[boolean]
:param store_session: Specifies whether to save the log in authorization
for future log ins.
:type store_session: Optional[boolean]
:param mfa_code: MFA token if enabled.
:type mfa_code: Optional[str]
:param pickle_path: Allows users to specify the path of the pickle file.
Accepts both relative and absolute paths.
:param pickle_name: Allows users to name Pickle token file in order to switch
between different accounts without having to re-login every time.
:returns: A dictionary with log in information. The 'access_token' keyword contains the access token, and the 'detail' keyword \
contains information on whether the access token was generated or loaded from pickle file.
"""
device_token = generate_device_token()
home_dir = os.path.expanduser("~")
data_dir = os.path.join(home_dir, ".tokens")
if pickle_path:
if not os.path.isabs(pickle_path):
# normalize relative paths
pickle_path = os.path.normpath(os.path.join(os.getcwd(), pickle_path))
data_dir = pickle_path
if not os.path.exists(data_dir):
os.makedirs(data_dir)
creds_file = "robinhood" + pickle_name + ".pickle"
pickle_path = os.path.join(data_dir, creds_file)
# Challenge type is used if not logging in with two-factor authentication.
if by_sms:
challenge_type = "sms"
else:
challenge_type = "email"
url = login_url()
login_payload = {
'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS',
'expires_in': expiresIn,
'grant_type': 'password',
'password': password,
'scope': scope,
'username': username,
'challenge_type': challenge_type,
'device_token': device_token,
'try_passkeys': False,
'token_request_path':'/login',
'create_read_only_secondary_token':True,
'request_id': '848bd19e-02bc-45d9-99b5-01bce5a79ea7'
}
if mfa_code:
login_payload['mfa_code'] = mfa_code
# If authentication has been stored in pickle file then load it. Stops login server from being pinged so much.
if os.path.isfile(pickle_path):
# If store_session has been set to false then delete the pickle file, otherwise try to load it.
# Loading pickle file will fail if the acess_token has expired.
if store_session:
try:
with open(pickle_path, 'rb') as f:
pickle_data = pickle.load(f)
access_token = pickle_data['access_token']
token_type = pickle_data['token_type']
refresh_token = pickle_data['refresh_token']
# Set device_token to be the original device token when first logged in.
pickle_device_token = pickle_data['device_token']
login_payload['device_token'] = pickle_device_token
# Set login status to True in order to try and get account info.
set_login_state(True)
update_session(
'Authorization', '{0} {1}'.format(token_type, access_token))
# Try to load account profile to check that authorization token is still valid.
res = request_get(
positions_url(), 'pagination', {'nonzero': 'true'}, jsonify_data=False)
# Raises exception if response code is not 200.
res.raise_for_status()
return({'access_token': access_token, 'token_type': token_type,
'expires_in': expiresIn, 'scope': scope, 'detail': 'logged in using authentication in {0}'.format(creds_file),
'backup_code': None, 'refresh_token': refresh_token})
except:
print(
"ERROR: There was an issue loading pickle file. Authentication may be expired - logging in normally.", file=get_output())
set_login_state(False)
update_session('Authorization', None)
else:
os.remove(pickle_path)
# Try to log in normally.
if not username:
username = input("Robinhood username: ")
login_payload['username'] = username
if not password:
password = getpass.getpass("Robinhood password: ")
login_payload['password'] = password
data = request_post(url, login_payload)
# Handle case where mfa or challenge is required.
if data:
if 'mfa_required' in data:
mfa_token = input("Please type in the MFA code: ")
login_payload['mfa_code'] = mfa_token
res = request_post(url, login_payload, jsonify_data=False)
while (res.status_code != 200):
mfa_token = input(
"That MFA code was not correct. Please type in another MFA code: ")
login_payload['mfa_code'] = mfa_token
res = request_post(url, login_payload, jsonify_data=False)
data = res.json()
elif 'challenge' in data:
challenge_id = data['challenge']['id']
sms_code = input('Enter Robinhood code for validation: ')
res = respond_to_challenge(challenge_id, sms_code)
while 'challenge' in res and res['challenge']['remaining_attempts'] > 0:
sms_code = input('That code was not correct. {0} tries remaining. Please type in another code: '.format(
res['challenge']['remaining_attempts']))
res = respond_to_challenge(challenge_id, sms_code)
update_session(
'X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id)
data = request_post(url, login_payload)
elif 'verification_workflow' in data:
print("Verification workflow required. Please check your Robinhood app for instructions.")
workflow_id = data['verification_workflow']['id']
_validate_sherrif_id(device_token=device_token, workflow_id=workflow_id, mfa_code=mfa_code)
data = request_post(url, login_payload)
# Update Session data with authorization or raise exception with the information present in data.
if 'access_token' in data:
token = '{0} {1}'.format(data['token_type'], data['access_token'])
update_session('Authorization', token)
set_login_state(True)
data['detail'] = "logged in with brand new authentication code."
if store_session:
with open(pickle_path, 'wb') as f:
pickle.dump({'token_type': data['token_type'],
'access_token': data['access_token'],
'refresh_token': data['refresh_token'],
'device_token': login_payload['device_token']}, f)
else:
if 'detail' in data:
raise Exception(data['detail'])
raise Exception(f"Received an error response {data}")
else:
raise Exception('Error: Trouble connecting to robinhood API. Check internet connection.')
return(data)
def _validate_sherrif_id(device_token:str, workflow_id:str,mfa_code:str):
url = "https://api.robinhood.com/pathfinder/user_machine/"
machine_payload = {
'device_id': device_token,
'flow': 'suv',
'input': {'workflow_id': workflow_id}
}
data = request_post(url=url, payload=machine_payload,json=True)
machine_id = _get_sherrif_challenge(data)
inquiries_url = f"https://api.robinhood.com/pathfinder/inquiries/{machine_id}/user_view/"
response = request_get(inquiries_url)
challenge_id = response["context"]["sheriff_challenge"]["id"] # used to be type_context
challenge_url = f"https://api.robinhood.com/challenge/{challenge_id}/respond/"
challenge_payload = {'response': mfa_code}
challenge_response = request_post(url=challenge_url, payload=challenge_payload)
print(challenge_response)
start_time = time.time()
while time.time() - start_time < 120: # 2 minutes
time.sleep(5)
if challenge_response["challenge"]["status"] == "issued":
email_text_code = input("enter code sent to email or text: ")
challenge_payload['response'] = email_text_code
challenge_response = request_post(url=challenge_url, payload=challenge_payload)
inquiries_payload = {"sequence":0,"user_input":{"status":"continue"}}
inquiries_response = request_post(url=inquiries_url, payload=inquiries_payload,json=True )
if inquiries_response["type_context"]["result"] == "workflow_status_approved":
return
else:
raise Exception("workflow status not approved")
else:
challenge_response = request_get(url=challenge_url)
print("Waiting for challenge to be validated")
print(time.time() - start_time)
raise Exception("Login confirmation timed out. Please try again.")
def _get_sherrif_challenge(data):
if "id" in data:
return data["id"]
raise Exception("Id not returned in user-machine call")
@login_required
def logout():
"""Removes authorization from the session header.
:returns: None
"""
set_login_state(False)
update_session('Authorization', `None)```
—
Reply to this email directly, view
it on GitHub, or unsubscribe.
You are receiving this because you commented.Message ID: ***@***.***>
[
{
***@***.***": <a class="moz-txt-link-rfc2396E" href="http://schema.org">"http://schema.org"</a>,
***@***.***": "EmailMessage",
"potentialAction": {
***@***.***": "ViewAction",
"target": <a class="moz-txt-link-rfc2396E" href="#511 (comment)">"https://github.com/jmfernandes/robin_stocks/issues/511#issuecomment-2620518860"</a>,
"url": <a class="moz-txt-link-rfc2396E" href="#511 (comment)">"https://github.com/jmfernandes/robin_stocks/issues/511#issuecomment-2620518860"</a>,
"name": "View Issue"
},
"description": "View this Issue on GitHub",
"publisher": {
***@***.***": "Organization",
"name": "GitHub",
"url": <a class="moz-txt-link-rfc2396E" href="https://github.com">"https://github.com"</a>
}
}
]
|
You're welcome, im still new to coding so im suprised I was able to fix
this one but im glad it worked for you as well. I plan to edit the
authentication further to make it a bit more robust and hopefully more
reliable for different security settings. Will also note that the generate
device token function was completely changed using the "secret" python
package instead of the "random" python package. Using secret is an even
more secure method for this function.
…On Wed, Jan 29, 2025, 04:02 johnccraft2 ***@***.***> wrote:
I can't thank you enough for your help or all the time, dedication
and hard work I know you have "invested" in this project!
JohnC
On 1/28/2025 9:59 PM, Gates8911 wrote:
"""Contains all functions for the purpose of logging in and out to
Robinhood."""
import getpass
import os
import pickle
import secrets
import time
from robin_stocks.robinhood.helper import *
from robin_stocks.robinhood.urls import *
def generate_device_token():
"""Generates a cryptographically secure device token."""
rands = [secrets.randbelow(256) for _ in range(16)]
hexa = [str(hex(i + 256)).lstrip("0x")[1:] for i in range(256)]
token = ""
for i, r in enumerate(rands):
token += hexa[r]
if i in [3, 5, 7, 9]:
token += "-"
return token
def respond_to_challenge(challenge_id, sms_code):
"""This function will post to the challenge url.
:param challenge_id: The challenge id.
:type challenge_id: str
:param sms_code: The sms code.
:type sms_code: str
:returns: The response from requests.
"""
url = challenge_url(challenge_id)
payload = {
'response': sms_code
}
return(request_post(url, payload))
def login(username=None, password=None, expiresIn=86400, scope='internal',
by_sms=True, store_session=True, mfa_code=None, pickle_path="",
pickle_name=""):
"""This function will effectively log the user into robinhood by getting an
authentication token and saving it to the session header. By default, it
will store the authentication token in a pickle file and load that value
on subsequent logins.
:param username: The username for your robinhood account, usually your
email.
Not required if credentials are already cached and valid.
:type username: Optional[str]
:param password: The password for your robinhood account. Not required if
credentials are already cached and valid.
:type password: Optional[str]
:param expiresIn: The time until your login session expires. This is in
seconds.
:type expiresIn: Optional[int]
:param scope: Specifies the scope of the authentication.
:type scope: Optional[str]
:param by_sms: Specifies whether to send an email(False) or an sms(True)
:type by_sms: Optional[boolean]
:param store_session: Specifies whether to save the log in authorization
for future log ins.
:type store_session: Optional[boolean]
:param mfa_code: MFA token if enabled.
:type mfa_code: Optional[str]
:param pickle_path: Allows users to specify the path of the pickle file.
Accepts both relative and absolute paths.
:param pickle_name: Allows users to name Pickle token file in order to
switch
between different accounts without having to re-login every time.
:returns: A dictionary with log in information. The 'access_token' keyword
contains the access token, and the 'detail' keyword \
contains information on whether the access token was generated or loaded
from pickle file.
"""
device_token = generate_device_token()
home_dir = os.path.expanduser("~")
data_dir = os.path.join(home_dir, ".tokens")
if pickle_path:
if not os.path.isabs(pickle_path):
# normalize relative paths
pickle_path = os.path.normpath(os.path.join(os.getcwd(), pickle_path))
data_dir = pickle_path
if not os.path.exists(data_dir):
os.makedirs(data_dir)
creds_file = "robinhood" + pickle_name + ".pickle"
pickle_path = os.path.join(data_dir, creds_file)
# Challenge type is used if not logging in with two-factor authentication.
if by_sms:
challenge_type = "sms"
else:
challenge_type = "email"
url = login_url()
login_payload = {
'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS',
'expires_in': expiresIn,
'grant_type': 'password',
'password': password,
'scope': scope,
'username': username,
'challenge_type': challenge_type,
'device_token': device_token,
'try_passkeys': False,
'token_request_path':'/login',
'create_read_only_secondary_token':True,
'request_id': '848bd19e-02bc-45d9-99b5-01bce5a79ea7'
}
if mfa_code:
login_payload['mfa_code'] = mfa_code
# If authentication has been stored in pickle file then load it. Stops
login server from being pinged so much.
if os.path.isfile(pickle_path):
# If store_session has been set to false then delete the pickle file,
otherwise try to load it.
# Loading pickle file will fail if the acess_token has expired.
if store_session:
try:
with open(pickle_path, 'rb') as f:
pickle_data = pickle.load(f)
access_token = pickle_data['access_token']
token_type = pickle_data['token_type']
refresh_token = pickle_data['refresh_token']
# Set device_token to be the original device token when first logged in.
pickle_device_token = pickle_data['device_token']
login_payload['device_token'] = pickle_device_token
# Set login status to True in order to try and get account info.
set_login_state(True)
update_session(
'Authorization', '{0} {1}'.format(token_type, access_token))
# Try to load account profile to check that authorization token is still
valid.
res = request_get(
positions_url(), 'pagination', {'nonzero': 'true'}, jsonify_data=False)
# Raises exception if response code is not 200.
res.raise_for_status()
return({'access_token': access_token, 'token_type': token_type,
'expires_in': expiresIn, 'scope': scope, 'detail': 'logged in using
authentication in {0}'.format(creds_file),
'backup_code': None, 'refresh_token': refresh_token})
except:
print(
"ERROR: There was an issue loading pickle file. Authentication may be
expired - logging in normally.", file=get_output())
set_login_state(False)
update_session('Authorization', None)
else:
os.remove(pickle_path)
# Try to log in normally.
if not username:
username = input("Robinhood username: ")
login_payload['username'] = username
if not password:
password = getpass.getpass("Robinhood password: ")
login_payload['password'] = password
data = request_post(url, login_payload)
# Handle case where mfa or challenge is required.
if data:
if 'mfa_required' in data:
mfa_token = input("Please type in the MFA code: ")
login_payload['mfa_code'] = mfa_token
res = request_post(url, login_payload, jsonify_data=False)
while (res.status_code != 200):
mfa_token = input(
"That MFA code was not correct. Please type in another MFA code: ")
login_payload['mfa_code'] = mfa_token
res = request_post(url, login_payload, jsonify_data=False)
data = res.json()
elif 'challenge' in data:
challenge_id = data['challenge']['id']
sms_code = input('Enter Robinhood code for validation: ')
res = respond_to_challenge(challenge_id, sms_code)
while 'challenge' in res and res['challenge']['remaining_attempts'] > 0:
sms_code = input('That code was not correct. {0} tries remaining. Please
type in another code: '.format(
res['challenge']['remaining_attempts']))
res = respond_to_challenge(challenge_id, sms_code)
update_session(
'X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id)
data = request_post(url, login_payload)
elif 'verification_workflow' in data:
print("Verification workflow required. Please check your Robinhood app for
instructions.")
workflow_id = data['verification_workflow']['id']
_validate_sherrif_id(device_token=device_token, workflow_id=workflow_id,
mfa_code=mfa_code)
data = request_post(url, login_payload)
# Update Session data with authorization or raise exception with the
information present in data.
if 'access_token' in data:
token = '{0} {1}'.format(data['token_type'], data['access_token'])
update_session('Authorization', token)
set_login_state(True)
data['detail'] = "logged in with brand new authentication code."
if store_session:
with open(pickle_path, 'wb') as f:
pickle.dump({'token_type': data['token_type'],
'access_token': data['access_token'],
'refresh_token': data['refresh_token'],
'device_token': login_payload['device_token']}, f)
else:
if 'detail' in data:
raise Exception(data['detail'])
raise Exception(f"Received an error response {data}")
else:
raise Exception('Error: Trouble connecting to robinhood API. Check
internet connection.')
return(data)
def _validate_sherrif_id(device_token:str, workflow_id:str,mfa_code:str):
url = "https://api.robinhood.com/pathfinder/user_machine/"
machine_payload = {
'device_id': device_token,
'flow': 'suv',
'input': {'workflow_id': workflow_id}
}
data = request_post(url=url, payload=machine_payload,json=True)
machine_id = _get_sherrif_challenge(data)
inquiries_url = f"
https://api.robinhood.com/pathfinder/inquiries/{machine_id}/user_view/"
response = request_get(inquiries_url)
challenge_id = response["context"]["sheriff_challenge"]["id"] # used to be
type_context
challenge_url = f"
https://api.robinhood.com/challenge/{challenge_id}/respond/"
challenge_payload = {'response': mfa_code}
challenge_response = request_post(url=challenge_url,
payload=challenge_payload)
print(challenge_response)
start_time = time.time()
while time.time() - start_time < 120: # 2 minutes
time.sleep(5)
if challenge_response["challenge"]["status"] == "issued":
email_text_code = input("enter code sent to email or text: ")
challenge_payload['response'] = email_text_code
challenge_response = request_post(url=challenge_url,
payload=challenge_payload)
inquiries_payload = {"sequence":0,"user_input":{"status":"continue"}}
inquiries_response = request_post(url=inquiries_url,
payload=inquiries_payload,json=True )
if inquiries_response["type_context"]["result"] ==
"workflow_status_approved":
return
else:
raise Exception("workflow status not approved")
else:
challenge_response = request_get(url=challenge_url)
print("Waiting for challenge to be validated")
print(time.time() - start_time)
raise Exception("Login confirmation timed out. Please try again.")
def _get_sherrif_challenge(data):
if "id" in data:
return data["id"]
raise Exception("Id not returned in user-machine call")
@login_required
def logout():
"""Removes authorization from the session header.
:returns: None
"""
set_login_state(False)
update_session('Authorization', `None)```
—
Reply to this email directly, view
it on GitHub, or unsubscribe.
You are receiving this because you commented.Message ID: ***@***.***>
[
{
***@***.***": <a class="moz-txt-link-rfc2396E" href="http://schema.org">"
http://schema.org"</a>,
***@***.***": "EmailMessage",
"potentialAction": {
***@***.***": "ViewAction",
"target": <a class="moz-txt-link-rfc2396E" href="
#511 (comment)
">"
#511 (comment)
"</a>,
"url": <a class="moz-txt-link-rfc2396E" href="
#511 (comment)
">"
#511 (comment)
"</a>,
"name": "View Issue"
},
"description": "View this Issue on GitHub",
"publisher": {
***@***.***": "Organization",
"name": "GitHub",
"url": <a class="moz-txt-link-rfc2396E" href="https://github.com">"
https://github.com"</a>
}
}
]
—
Reply to this email directly, view it on GitHub
<#511 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/BG5BJIQCD7W2C265YEQ2SJ32NCKKPAVCNFSM6AAAAABTBXYXYSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMMRRGA2DINZRGM>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
When running the login protocol, I am getting a KeyError that shows this:
KeyError Traceback (most recent call last)
Cell In[6], line 3
1 import robin_stocks.robinhood as r
----> 3 login = r.login(username, password)
File ~/Library/jupyterlab-desktop/jlab_server/lib/python3.8/site-packages/robin_stocks/robinhood/authentication.py:190, in login(username, password, expiresIn, scope, by_sms, store_session, mfa_code, pickle_name)
185 pickle.dump({'token_type': data['token_type'],
186 'access_token': data['access_token'],
187 'refresh_token': data['refresh_token'],
188 'device_token': payload['device_token']}, f)
189 else:
--> 190 raise Exception(data['detail'])
191 else:
192 raise Exception('Error: Trouble connecting to robinhood API. Check internet connection.')
KeyError: 'detail'
The text was updated successfully, but these errors were encountered: