User Guide

This page summarizes how to use the main features of pyinaturalist.

Installation

Installation instructions:

Install the latest stable version with pip:

pip install pyinaturalist

Or install from conda-forge, if you prefer:

conda install -c conda-forge pyinaturalist

If you would like to use the latest development (pre-release) version:

pip install --pre pyinaturalist

See Contributing for details on setup for local development.

Python version compatibility

pyinaturalist currently requires python 3.6+. If you need to use an older version of python, here are the last compatible versions of pyinaturalist:

  • python 2.7: pyinaturalist 0.1

  • python 3.4: pyinaturalist 0.10

  • python 3.5: pyinaturalist 0.11

  • python 3.6: still supported, but expected to be dropped in a future release

Imports

You can import all public functions and classes from the top-level pyinaturalist package:

>>> from pyinaturalist import Taxon, get_observations, pprint  # etc.

Or you can just import everything:

>>> from pyinaturalist import *

Requests

Requests generally follow the same format as the API and search URLs.

For example, if you wanted to search observations by user, these three requests are equivalent:

https://www.inaturalist.org/observations?user_id=tiwane,jdmore
https://api.inaturalist.org/v1/observations?user_id=tiwane%2Cjdmore
>>> get_observations(user_id=['tiwane', 'jdmore'])

Compared to search URLs and raw API requests, pyinaturalist provides some conveniences for making requests easier:

  • Python lists instead of comma-separated strings

  • Python booleans instead of JS-style boolean strings or 1/0

  • Python file-like objects or file paths for photo and sound uploads

  • Python date and datetime objects instead of date/time strings

  • Simplified data formats for POST and PUT requests

  • Simplified pagination

  • Validation for multiple-choice parameters

And more, depending on the function. See the API Reference section for a complete list of functions available.

Responses

API responses are returned as JSON, with some python type conversions applied (similar to the request type conversions mentioned above). Example response data is shown in the documentation for each request function, for example get_observations().

API Data vs Web UI

Here is how some of those response fields correspond to observation details shown on iNaturalist.org:

_images/inat-observation-page-annotated.png

And here is what that same observation looks like in JSON:

Observation response JSON

{
  "quality_grade": "research",
  "time_observed_at": "2021-06-10T16:02:00-07:00",
  "taxon_geoprivacy": null,
  "annotations": [],
  "uuid": "cdbd7405-1901-409d-b784-7e79f26c1eb9",
  "id": 83103252,
  "cached_votes_total": 0,
  "identifications_most_agree": true,
  "species_guess": "Whip Cicadas",
  "identifications_most_disagree": false,
  "tags": [
    "Okanagana"
  ],
  "positional_accuracy": 33,
  "comments_count": 1,
  "site_id": 1,
  "license_code": "cc0",
  "quality_metrics": [],
  "public_positional_accuracy": 33,
  "reviewed_by": [
    1,
    443199
  ],
  "oauth_application_id": null,
  "flags": [],
  "created_at": "2021-06-14 23:53:01-07:00",
  "description": "",
  "project_ids_with_curator_id": [],
  "updated_at": "2021-06-15 04:50:40-07:00",
  "sounds": [],
  "captive": false,
  "taxon": {
    "is_active": true,
    "ancestry": "48460/1/47120/372739/47158/184884/47744/125816/372849/50190/50186/556902/605128/605130",
    "min_species_ancestry": "48460,1,47120,372739,47158,184884,47744,125816,372849,50190,50186,556902,605128,605130,176578",
    "endemic": false,
    "iconic_taxon_id": 47158,
    "min_species_taxon_id": 176578,
    "threatened": false,
    "rank_level": 20,
    "introduced": false,
    "native": false,
    "parent_id": 605130,
    "name": "Okanagana",
    "rank": "genus",
    "extinct": false,
    "id": 176578,
    "ancestor_ids": [
      48460,
      1,
      47120,
      372739,
      47158,
      184884,
      47744,
      125816,
      372849,
      50190,
      50186,
      556902,
      605128,
      605130,
      176578
    ],
    "photos_locked": false,
    "taxon_schemes_count": 1,
    "wikipedia_url": "http://en.wikipedia.org/wiki/Okanagana",
    "current_synonymous_taxon_ids": null,
    "created_at": "2012-10-24T02:20:10+00:00",
    "taxon_changes_count": 0,
    "complete_species_count": null,
    "universal_search_rank": 2634,
    "observations_count": 2634,
    "flag_counts": {
      "resolved": 0,
      "unresolved": 0
    },
    "place_ids": [
      1,
      14,
      2757,
      4512,
      9853,
      50422,
      53219,
      59613,
      62068,
      62332,
      65360,
      66741,
      67725,
      67759,
      67760,
      92151,
      92337,
      92665,
      96034,
      96057,
      96683,
      96687,
      97394,
      117476,
      121278,
      123427,
      129010,
      145270,
      145714,
      154011,
      155063,
      156220,
      165950
    ],
    "atlas_id": null,
    "default_photo": {
      "id": 398757,
      "license_code": "cc-by-nc",
      "attribution": "(c) faerthen, some rights reserved (CC BY-NC), uploaded by faerthen",
      "url": "https://inaturalist-open-data.s3.amazonaws.com/photos/398757/square.jpg?1372952785",
      "original_dimensions": {
        "height": 2048,
        "width": 1881
      },
      "flags": [],
      "square_url": "https://inaturalist-open-data.s3.amazonaws.com/photos/398757/square.jpg?1372952785",
      "medium_url": "https://inaturalist-open-data.s3.amazonaws.com/photos/398757/medium.jpg?1372952785"
    },
    "iconic_taxon_name": "Insecta",
    "preferred_common_name": "Whip Cicadas"
  },
  "ident_taxon_ids": [
    48460,
    1,
    47120,
    372739,
    47158,
    184884,
    47744,
    125816,
    372849,
    50190,
    50186,
    556902,
    605128,
    605130,
    176578
  ],
  "outlinks": [],
  "faves_count": 0,
  "ofvs": [],
  "num_identification_agreements": 1,
  "preferences": {
    "prefers_community_taxon": null
  },
  "comments": [
    {
      "id": 7171083,
      "uuid": "48b1188b-5811-42cb-8d21-782ae9cc81b1",
      "user": {
        "id": 443199,
        "login": "willc-t",
        "spam": false,
        "suspended": false,
        "created_at": "2017-04-06T15:02:36+00:00",
        "login_autocomplete": "willc-t",
        "login_exact": "willc-t",
        "name": "Will Chatfield-Taylor",
        "name_autocomplete": "Will Chatfield-Taylor",
        "orcid": "https://orcid.org/0000-0001-6509-4317",
        "icon": "https://static.inaturalist.org/attachments/users/icons/443199/thumb.jpg?1565472742",
        "observations_count": 813,
        "identifications_count": 10340,
        "journal_posts_count": 0,
        "activity_count": 11153,
        "species_count": 704,
        "universal_search_rank": 813,
        "roles": [
          "curator"
        ],
        "site_id": 1,
        "icon_url": "https://static.inaturalist.org/attachments/users/icons/443199/medium.jpg?1565472742"
      },
      "created_at": "2021-06-15T07:51:09-04:00",
      "created_at_details": {
        "date": "2021-06-15",
        "day": 15,
        "month": 6,
        "year": 2021,
        "hour": 7,
        "week": 24
      },
      "body": "With a female, this could be bella or occidentalis from this angle.",
      "flags": [],
      "moderator_actions": [],
      "hidden": false
    }
  ],
  "map_scale": 16,
  "uri": "https://www.inaturalist.org/observations/83103252",
  "project_ids": [],
  "community_taxon_id": 176578,
  "geojson": {
    "type": "Point",
    "coordinates": [
      -121.516314,
      41.72144
    ]
  },
  "owners_identification_from_vision": false,
  "identifications_count": 1,
  "obscured": false,
  "num_identification_disagreements": 0,
  "geoprivacy": null,
  "location": [
    41.72144,
    -121.516314
  ],
  "votes": [],
  "spam": false,
  "user": {
    "id": 1,
    "login": "kueda",
    "spam": false,
    "suspended": false,
    "created_at": "2008-03-20T21:15:42+00:00",
    "site_id": 1,
    "login_autocomplete": "kueda",
    "login_exact": "kueda",
    "name": "Ken-ichi Ueda",
    "name_autocomplete": "Ken-ichi Ueda",
    "orcid": "https://orcid.org/0000-0003-0145-6846",
    "icon": "https://static.inaturalist.org/attachments/users/icons/1/thumb.jpg?1475527316",
    "observations_count": 42642,
    "identifications_count": 100944,
    "journal_posts_count": 86,
    "activity_count": 143672,
    "species_count": 8986,
    "universal_search_rank": 42642,
    "roles": [
      "admin",
      "curator"
    ],
    "icon_url": "https://static.inaturalist.org/attachments/users/icons/1/medium.jpg?1475527316",
    "preferences": {
      "prefers_community_taxa": true,
      "prefers_observation_fields_by": "curators",
      "prefers_project_addition_by": "joined"
    }
  },
  "mappable": true,
  "identifications_some_agree": true,
  "project_ids_without_curator_id": [],
  "place_guess": "Bunchgrass Trail, Lava Beds National Monument, Siskiyou County, CA, USA",
  "identifications": [
    {
      "hidden": false,
      "disagreement": null,
      "flags": [],
      "created_at": "2021-06-15T02:53:01-04:00",
      "taxon_id": 176578,
      "body": null,
      "own_observation": true,
      "uuid": "9d96cc87-7728-4333-8593-932c7146a771",
      "taxon_change": null,
      "moderator_actions": [],
      "vision": false,
      "current": true,
      "id": 183324707,
      "created_at_details": {
        "date": "2021-06-15",
        "day": 15,
        "month": 6,
        "year": 2021,
        "hour": 2,
        "week": 24
      },
      "category": "improving",
      "spam": false,
      "user": {
        "id": 1,
        "login": "kueda",
        "spam": false,
        "suspended": false,
        "created_at": "2008-03-20T21:15:42+00:00",
        "login_autocomplete": "kueda",
        "login_exact": "kueda",
        "name": "Ken-ichi Ueda",
        "name_autocomplete": "Ken-ichi Ueda",
        "orcid": "https://orcid.org/0000-0003-0145-6846",
        "icon": "https://static.inaturalist.org/attachments/users/icons/1/thumb.jpg?1475527316",
        "observations_count": 42642,
        "identifications_count": 100944,
        "journal_posts_count": 86,
        "activity_count": 143672,
        "species_count": 8986,
        "universal_search_rank": 42642,
        "roles": [
          "admin",
          "curator"
        ],
        "site_id": 1,
        "icon_url": "https://static.inaturalist.org/attachments/users/icons/1/medium.jpg?1475527316"
      },
      "previous_observation_taxon_id": 176578,
      "taxon": {
        "id": 176578,
        "rank": "genus",
        "name": "Okanagana"
      },
      "previous_observation_taxon": {
        "id": 176578,
        "rank": "genus",
        "name": "Okanagana"
      }
    },
    {
      "hidden": false,
      "disagreement": false,
      "flags": [],
      "created_at": "2021-06-15T07:50:40-04:00",
      "taxon_id": 176578,
      "body": null,
      "own_observation": false,
      "uuid": "ae3a442a-9019-4621-8a37-1eba522ea735",
      "taxon_change": null,
      "moderator_actions": [],
      "vision": false,
      "current": true,
      "id": 183361878,
      "created_at_details": {
        "date": "2021-06-15",
        "day": 15,
        "month": 6,
        "year": 2021,
        "hour": 7,
        "week": 24
      },
      "category": "supporting",
      "spam": false,
      "user": {
        "id": 443199,
        "login": "willc-t",
        "spam": false,
        "suspended": false,
        "created_at": "2017-04-06T15:02:36+00:00",
        "login_autocomplete": "willc-t",
        "login_exact": "willc-t",
        "name": "Will Chatfield-Taylor",
        "name_autocomplete": "Will Chatfield-Taylor",
        "orcid": "https://orcid.org/0000-0001-6509-4317",
        "icon": "https://static.inaturalist.org/attachments/users/icons/443199/thumb.jpg?1565472742",
        "observations_count": 813,
        "identifications_count": 10340,
        "journal_posts_count": 0,
        "activity_count": 11153,
        "species_count": 704,
        "universal_search_rank": 813,
        "roles": [
          "curator"
        ],
        "site_id": 1,
        "icon_url": "https://static.inaturalist.org/attachments/users/icons/443199/medium.jpg?1565472742"
      },
      "previous_observation_taxon_id": 176578,
      "taxon": {
        "id": 176578,
        "rank": "genus",
        "name": "Okanagana"
      },
      "previous_observation_taxon": {
        "id": 176578,
        "rank": "genus",
        "name": "Okanagana"
      }
    }
  ],
  "project_observations": [],
  "photos": [
    {
      "id": 136458117,
      "license_code": "cc-by",
      "url": "https://inaturalist-open-data.s3.amazonaws.com/photos/136458117/square.jpg?1623739627",
      "attribution": "(c) Ken-ichi Ueda, some rights reserved (CC BY)",
      "original_dimensions": {
        "width": 1365,
        "height": 2048
      },
      "flags": []
    }
  ],
  "observation_photos": [
    {
      "id": 127470344,
      "position": 0,
      "uuid": "b9483003-c73c-40dd-aaa0-9bc70bdbed17",
      "photo": {
        "id": 136458117,
        "license_code": "cc-by",
        "url": "https://inaturalist-open-data.s3.amazonaws.com/photos/136458117/square.jpg?1623739627",
        "attribution": "(c) Ken-ichi Ueda, some rights reserved (CC BY)",
        "original_dimensions": {
          "width": 1365,
          "height": 2048
        },
        "flags": []
      }
    }
  ],
  "faves": [],
  "non_owner_ids": [
    {
      "hidden": false,
      "disagreement": false,
      "flags": [],
      "created_at": "2021-06-15T07:50:40-04:00",
      "taxon_id": 176578,
      "body": null,
      "own_observation": false,
      "uuid": "ae3a442a-9019-4621-8a37-1eba522ea735",
      "taxon_change": null,
      "moderator_actions": [],
      "vision": false,
      "current": true,
      "id": 183361878,
      "created_at_details": {
        "date": "2021-06-15",
        "day": 15,
        "month": 6,
        "year": 2021,
        "hour": 7,
        "week": 24
      },
      "category": "supporting",
      "spam": false,
      "user": {
        "id": 443199,
        "login": "willc-t",
        "spam": false,
        "suspended": false,
        "created_at": "2017-04-06T15:02:36+00:00",
        "login_autocomplete": "willc-t",
        "login_exact": "willc-t",
        "name": "Will Chatfield-Taylor",
        "name_autocomplete": "Will Chatfield-Taylor",
        "orcid": "https://orcid.org/0000-0001-6509-4317",
        "icon": "https://static.inaturalist.org/attachments/users/icons/443199/thumb.jpg?1565472742",
        "observations_count": 813,
        "identifications_count": 10340,
        "journal_posts_count": 0,
        "activity_count": 11153,
        "species_count": 704,
        "universal_search_rank": 813,
        "roles": [
          "curator"
        ],
        "site_id": 1,
        "icon_url": "https://static.inaturalist.org/attachments/users/icons/443199/medium.jpg?1565472742"
      },
      "previous_observation_taxon_id": 176578,
      "taxon": {
        "id": 176578,
        "rank": "genus",
        "name": "Okanagana"
      },
      "previous_observation_taxon": {
        "id": 176578,
        "rank": "genus",
        "name": "Okanagana"
      }
    }
  ],
  "observed_on": "2021-06-10 16:02:00-08:00"
}

Previewing Responses

These responses can contain large amounts of response attributes, making it somewhat cumbersome if you just want to quickly preview results (for example, in a Jupyter notebook). For that purpose, the pprint() function is included to format response data as a condensed, color-highlighted table.

Examples:

>>> from pyinaturalist import get_observations, pprint
>>> observations = get_observations(user_id='niconoe', per_page=5)
>>> pprint(observations)
ID         Taxon ID   Taxon                                                  Observed on    User      Location
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
82974075   61546      Species: Nemophora degeerella (Yellow-barred Longhorn) Jun 14, 2021   niconoe   1428 Braine-l'Alleud, Belgique
82827577   48201      Family: Scarabaeidae (Scarabs)                         Jun 13, 2021   niconoe   1428 Braine-l'Alleud, Belgique
82826778   48201      Family: Scarabaeidae (Scarabs)                         Jun 13, 2021   niconoe   1428 Braine-l'Alleud, Belgique
82696354   209660     Species: Chrysolina americana (Rosemary Beetle)        Jun 12, 2021   niconoe   1420 Braine-l'Alleud, Belgique
82696334   472617     Species: Tomocerus vulgaris                            Jun 07, 2021   niconoe   1428 Braine-l'Alleud, Belgique
>>> from pyinaturalist import get_places, pprint
>>> places = get_places_autocomplete('Vale')
>>> pprint(places)
 ID       Latitude    Longitude   Name                  Category   URL
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
96877      49.5189     -2.5190   Vale                             https://www.inaturalist.org/places/96877
21951     -16.8960    -40.8349   Fronteira dos Vales              https://www.inaturalist.org/places/21951
23663      -6.3677    -41.8001   Valença do Piauí                 https://www.inaturalist.org/places/23663
24222     -27.2220    -53.6338   Pinheirinho do Vale              https://www.inaturalist.org/places/24222
24374     -29.8309    -52.1121   Vale Verde                       https://www.inaturalist.org/places/24374
24442     -10.3841    -62.0939   Vale do Paraíso                  https://www.inaturalist.org/places/24442
103902     44.7355     27.5412   Valea Ciorii                     https://www.inaturalist.org/places/103902
103905     44.7529     26.8481   Valea Macrisului                 https://www.inaturalist.org/places/103905
105015     44.6805     24.0224   Valea Mare                       https://www.inaturalist.org/places/105015
104268     46.7917     27.0905   Valea Ursului                    https://www.inaturalist.org/places/104268
_images/pprint_table.png

Models

Data models (pyinaturalist.models) are included for all API response types. These allow working with typed python objects instead of raw JSON. These are not used by default in the API query functions, but you can easily use them as follows:

>>> from pyinaturalist import Observation, get_observations
>>> response = get_observations(user_id='my_username)
>>> observations = Observation.from_json_list(response)

In a future release, these models will be fully integrated with the API query functions.

Pagination

Most endpoints support pagination, using the parameters:

  • page: Page number to get

  • per_page: Number of results to get per page

  • count_only=True: This is just a shortcut for per_page=0, which will return only the total number of results, not the results themselves.

The default and maximum per_page values vary by endpoint, but it’s 200 for most endpoints.

To get all pages of results and combine them into a single response, use page='all'. Note that this replaces the get_all_*() functions from pyinaturalist<=0.12.

Authentication

For any endpoints that create, update, or delete data, you will need to authenticate using an OAuth2 access token. This requires both your iNaturalist username and password, and separate “application” credentials.

Note

Read-only requests generally don’t require authentication; however, if you want to access private data visible only to your user (for example, obscured or private coordinates), you will need to use an access token.

Summary:

  1. Create an iNaturalist application

  2. Use get_access_token() with your user + application credentials to get an access token

  3. Pass that access token to any API request function that uses it

Creating an Application

Why do I need to create an application?

iNaturalist uses OAuth2, which provides several different methods (or “flows”) to access the site. For example, on the login page, you have the option of logging in with a username/password, or with an external provider (Google, Facebook, etc.):

Login form

Outside of iNaturalist.org, anything else that uses the API to create or modify data is considered an “application,” even if you’re just running some scripts on your own computer.

See iNaturalist documentation for more details on authentication.

First, go to New Application and fill out the following pieces of information:

  • Name: Any name you want to come up with. For example, if this is associated with a GitHub repo, you can use your repo name.

  • Description: A brief description of what you’ll be using this for. For example, “Data access for my own observations”.

  • Confidential: ✔️ This should be checked.

  • URL and Redirect URI: Just enter the URL to your GitHub repo, if you have one; otherwise any placeholder like “https://www.inaturalist.org” will work.

New Application form

You should then see a screen like this, which will show your new application ID and secret. These will only be shown once, so save them somewhere secure, preferably in a password manager.

Completed application form

Basic Usage

There are a few different ways you can pass your credentials to iNaturalist. First, you can pass them as keyword arguments to get_access_token():

>>> from pyinaturalist import get_access_token
>>> access_token = get_access_token(
>>>     username='my_inaturalist_username',  # Username you use to login to iNaturalist.org
>>>     password='my_inaturalist_password',  # Password you use to login to iNaturalist.org
>>>     app_id='33f27dc63bdf27f4ca6cd95dd',  # OAuth2 application ID
>>>     app_secret='bbce628be722bfe2abde4',  # OAuth2 application secret
>>> )

Environment Variables

You can also provide credentials via environment variables instead of arguments. The environment variable names are the keyword arguments in uppercase, prefixed with INAT_:

  • INAT_USERNAME

  • INAT_PASSWORD

  • INAT_APP_ID

  • INAT_APP_SECRET

Examples:

>>> import os
>>> os.environ['INAT_USERNAME'] = 'my_inaturalist_username'
>>> os.environ['INAT_PASSWORD'] = 'my_inaturalist_password'
>>> os.environ['INAT_APP_ID'] = '33f27dc63bdf27f4ca6cd95df'
>>> os.environ['INAT_APP_SECRET'] = 'bbce628be722bfe283de4'
export INAT_USERNAME="my_inaturalist_username"
export INAT_PASSWORD="my_inaturalist_password"
export INAT_APP_ID="33f27dc63bdf27f4ca6cd95df"
export INAT_APP_SECRET="bbce628be722bfe283de4"
set INAT_USERNAME="my_inaturalist_username"
set INAT_PASSWORD="my_inaturalist_password"
set INAT_APP_ID="33f27dc63bdf27f4ca6cd95df"
set INAT_APP_SECRET="bbce628be722bfe283de4"
$Env:INAT_USERNAME="my_inaturalist_username"
$Env:INAT_PASSWORD="my_inaturalist_password"
$Env:INAT_APP_ID="33f27dc63bdf27f4ca6cd95df"
$Env:INAT_APP_SECRET="bbce628be722bfe283de4"

Note that in any shell, these environment variables will only be set for your current shell session. I.e., you can’t set them in one terminal and then access them in another.

Keyring Integration

To handle your credentials more securely, you can store them in your system keyring. You could manually store and retrieve them with a utility like secret-tool and place them in environment variables as described above, but there is a much simpler option.

Direct keyring integration is provided via python keyring. Most common keyring bakcends are supported, including:

To store your credentials in the keyring, run set_keyring_credentials():

>>> from pyinaturalist.auth import set_keyring_credentials
>>> set_keyring_credentials(
>>>     username='my_inaturalist_username',
>>>     password='my_inaturalist_password',
>>>     app_id='33f27dc63bdf27f4ca6cd95df',
>>>     app_secret='bbce628be722bfe283de4',
>>> )

Afterward, you can call get_access_token() without any arguments, and your credentials will be retrieved from the keyring. You do not need to run set_keyring_credentials() again unless you change your iNaturalist password.

Password Manager Integration

Keyring integration can be taken a step further by managing your keyring with a password manager. This has the advantage of keeping your credentials in one place that can be synced across multiple machines. KeePassXC offers this feature for macOS and Linux systems. See this guide for setup info: KeepassXC and secret service, a small walk-through.

_images/password_manager_keying.png

Credentials storage with keyring + KeePassXC

Sessions

If you want more control over how requests are sent, you can provide your own session object using the session argument for any API request function. It’s recommended to use ClientSession, but any requests.Session or compatible object will work.

See Caching and Rate-Limiting sections below for examples.

Caching

All API requests are cached by default. These expire in 30 minutes for most endpoints, and 1 day for some infrequently-changing data (like taxa and places). See requests-cache: Expiration for details on cache expiration behavior.

For example, to keep cached requests for 5 days:

>>> from datetime import timedelta
>>> from pyinaturalist import ClientSession, get_taxa
>>> session = ClientSession(expire_after=timedelta(days=5))
>>> get_taxa(q='warbler', locale=1, session=session)

To store the cache somewhere other than the default cache directory:

>>> session = ClientSession(cache_name='~/data/api_requests.db')

To Manually clear the cache:

>>> session.cache.clear()

Rate Limiting

Rate limiting is applied to all requests so they stay within the rates specified by iNaturalist’s API Recommended Practices.

If you want to customize these rate limits, you can make a Session to use for API requests. The easiest way to do this is with ClientSession. For example, to reduce the rate to 50 requests per minute:

>>> from pyinaturalist import ClientSession, get_taxa
>>> session = ClientSession(per_minute=50)
>>> get_taxa(q='warbler', locale=1, session=session)

Logging

You can configure logging for pyinaturalist using the standard Python logging module, for example with logging.basicConfig():

>>> import logging
>>> logging.basicConfig()
>>> logging.getLogger('pyinaturalist').setLevel('INFO')

For convenience, an enable_logging() function is included that will apply some recommended settings, including colorized output (if viewed in a terminal) and better traceback formatting, using the rich library.

>>> from pyinaturalist import enable_logging
>>> enable_logging()

Dry-run mode

While developing and testing, it can be useful to temporarily mock out HTTP requests, especially requests that add, modify, or delete real data. Pyinaturalist has some settings to make this easier.

Dry-run individual requests

All API request functions take an optional dry_run argument. When set to True, requests will not be sent but will be logged instead.

Note

You must enable at least INFO-level logging to see the logged request info

>>> from pyinaturalist import get_taxa
>>> get_taxa(q='warbler', locale=1, dry_run=True)
{'results': [], 'total_results': 0}
[07-26 18:55:50] INFO  Request: GET https://api.inaturalist.org/v1/taxa?q=warbler&locale=1
User-Agent: pyinaturalist/0.15.0
Accept: application/json

Dry-run all requests

To enable dry-run mode for all requests, set the DRY_RUN_ENABLED environment variable:

>>> import os
>>> os.environ['DRY_RUN_ENABLED'] = 'true'
export DRY_RUN_ENABLED=true
set DRY_RUN_ENABLED="true"
$Env:DRY_RUN_ENABLED="true"

Dry-run only write requests

If you would like to send real GET requests but mock out any requests that modify data (POST, PUT, and DELETE), you can use the DRY_RUN_WRITE_ONLY variable instead:

>>> import os
>>> os.environ['DRY_RUN_WRITE_ONLY'] = 'true'
export DRY_RUN_WRITE_ONLY=true
set DRY_RUN_WRITE_ONLY="true"
$Env:DRY_RUN_WRITE_ONLY="true"

User Agent

If you’re using the API as part of a project or application, it’s good practice to add that info to the user-agent. You can optionally set this on the session object used to make requests:

>>> from pyinaturalist import ClientSession
>>> session = ClientSession(user_agent='my_app/1.0.0')