Skip to content

Instantly share code, notes, and snippets.

@abubelinha
Forked from kueda/inat-password.py
Last active July 31, 2022 08:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abubelinha/053bc3c649fd0f8e0e165ef1784c4fed to your computer and use it in GitHub Desktop.
Save abubelinha/053bc3c649fd0f8e0e165ef1784c4fed to your computer and use it in GitHub Desktop.
iNaturalist API Resource Owner Password Credentials Flow Example (Python)
def get_inat_access_token(username = None, password = None, app_id = None, app_secret = None, jwt=True):
""" Get iNaturalist access token to make authenticated api requests and access your private data.
Example posted in <https://groups.google.com/g/inaturalist/c/PNfHggqoIYs/m/Lk30rlzKBAAJ>
and forked from <https://gist.github.com/kueda/53ef93159c64de96ddc2>
Other Python-iNaturalist stuff:
- <https://github.com/pyinat/pyinaturalist/>
- <https://inaturalist.nz/posts/20991-20-oauth2-package-for-python-body-str-paramsxml>
- <https://www.inaturalist.org/journal/glmory/21331-python-upload-script>
- <https://www.inaturalist.org/journal/glmory/21539-updated-python-upload-script>
- <https://github.com/glmory/iNaturalist-Uploads>
- <https://medium.com/@johannes.t.klein/how-to-upload-many-observations-to-inaturalist-at-once-baf5b7eb113a>
"""
import requests
site = "https://www.inaturalist.org"
# Send a POST request to /oauth/token with the username and password
payload = {
'client_id': app_id,
'client_secret': app_secret,
'grant_type': "password",
'username': username,
'password': password
}
print ("POST %s/oauth/token, payload: %s" % (site, payload))
response = requests.post(("%s/oauth/token" % site), payload)
print ("RESPONSE")
print (response.content)
print
token = response.json()["access_token"]
if jwt: # GET JWT token (192 characters)
print("\n","="*30," OAuth token: ","="*30)
print("{} characters length OAuth token: '{}'".format(len(token),token))
print("\n","="*30," JWT token: ","="*30)
response = requests.get(
'https://www.inaturalist.org/users/api_token',
headers={'Authorization': 'Bearer %s' % token},
)
token = response.json()['api_token']
print("{} characters length JWT token: '{}'".format(len(token),token))
else: # stick to OAuth token (43 characcters)
print("\n","="*30," OAuth token: ","="*30)
print("{} characters length OAuth token: '{}'".format(len(token),token))
return (token)
if __name__ == "__main__":
USER = ''; PASSWORD=''; APP_ID=''; APP_SECRET=''; OBSERVATIONS = [111222333, 123123123]
# GET TOKENS:
import requests,json
# GET OAuth token (43 characters)
token = get_inat_access_token(username=USER, password=PASSWORD, app_id=APP_ID, app_secret=APP_SECRET, jwt=False)
# GET JWT token (192 characters)
token = get_inat_access_token(username=USER, password=PASSWORD, app_id=APP_ID, app_secret=APP_SECRET, jwt=True)
# USE TOKEN EXAMPLE 1 (taken from the original gist):
# ... although I doubt this request needs an authentication token
print("\n","="*30," EXAMPLE 1: ","="*30)
site = "https://www.inaturalist.org"
headers = {"Authorization": "Bearer %s" % token}
print ("GET %s/users/edit.json, headers: %s" % (site, headers))
print ("RESPONSE")
print (requests.get(("%s/users/edit.json" % site), headers=headers).content)
# USE TOKEN EXAMPLE 2: what I really want to do
# get a small list of obscured observations by their ids,
# and show the private geojson and positional accuracy:
print("\n","="*30," EXAMPLE 2: ","="*30)
site = "https://api.inaturalist.org"
idlist = OBSERVATIONS
url = site+"/v1/observations?id=" + "%2C".join([str(id) for id in idlist])
headers = {'Authorization': 'Bearer {}'.format(token)}
iresponse = requests.get(url=url,headers=headers)
idata = iresponse.json()["results"]
for n,d in enumerate(idata):
print( d["id"], d["taxon"]["name"], d["observed_on"], d["place_guess"] )
print("Geoprivacy:", d["geoprivacy"])
print("geojson: ", d["geojson"],"\nplace_guess: ", d["place_guess"])
print("private_geojson: ", d["private_geojson"],"\nprivate_place_guess: ", d["private_place_guess"])
print("positional_accuracy:", d["positional_accuracy"])
print("public_positional_accuracy:", d["public_positional_accuracy"])
print("-"*50)
@abubelinha
Copy link
Author

The solution was getting and using JWT tokens, as kindly suggested by @JWCook (pyinaturalist issue #403):

Do you know why my token length changed from 43 to 192 after upgrading pyinaturalist in my code above?

It's a different token format. In 0.17 I switched the default format to JWT, which according to iNat is now the preferred method: https://www.inaturalist.org/pages/api+recommended+practices

As far as I know, OAuth tokens (the 43-character ones) should still be working with the V1 API, so I'm not sure what's wrong in your example. If you want to try out JWT, just add another request to your example after line 22:

response = requests.get(
    'https://www.inaturalist.org/users/api_token',
    headers={'Authorization': f'Bearer {token}'},
)
token = response.json()['api_token']

That token then goes in your Authorization header, just like the OAuth tokens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment