Skip to content

Instantly share code, notes, and snippets.

Forked from kueda/
Last active July 31, 2022 08:24
What would you like to do?
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 <>
and forked from <>
Other Python-iNaturalist stuff:
- <>
- <>
- <>
- <>
- <>
- <>
import requests
site = ""
# 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 ="%s/oauth/token" % site), payload)
print ("RESPONSE")
print (response.content)
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(
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]
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 = ""
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 = ""
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"])
Copy link

This is not working.
The token length is 43 characters.
When I try to use it and get info from one of my observations with geoprivacy="obscured", I get this output:

Traceback (most recent call last):
  File "", line 63, in <module>
    print("private_geojson: ", d["private_geojson"],"\nprivate_place_guess: ", d["private_place_guess"])
KeyError: 'private_geojson'

Copy link

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:

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(
    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