# Inaturalist Observations Scraper (`zerobreak/inaturalist-observations-scraper`) Actor

iNaturalist Observations Scraper is a Python tool designed to extract and organize biodiversity data from iNaturalist. Easily fetch observation records, species information, locations, and media links into clean CSV or JSON formats for analysis and research.

- **URL**: https://apify.com/zerobreak/inaturalist-observations-scraper.md
- **Developed by:** [ZeroBreak](https://apify.com/zerobreak) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.99 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## iNaturalist Observations Scraper: Wildlife Observations and Taxa Export

<p align="center"><img src="https://imgur.com/1YQhOxm.png" alt="iNaturalist Observations Scraper" width="800" /></p>

iNaturalist Observations Scraper collects publicly available wildlife observations and taxonomic records through the official iNaturalist API. It requires no login or API key and produces structured JSON data that can be exported to CSV or Excel.

Search for a scientific or common taxon name, limit observation results to a specific place ID, and choose between observations and taxonomy lookup. Configure a record limit and schedule recurring runs to refresh your dataset.

### Features

- **Wildlife observation export:** Collect public sightings for a species or higher taxonomic group.
- **Taxa lookup:** Retrieve taxonomic rank, ancestry, common names, and related metadata.
- **Place filtering:** Restrict observation searches to a specific iNaturalist place ID.
- **License information:** Preserve observation license codes and photo licensing details.
- **Scheduled collection:** Refresh datasets periodically and maintain your own historical records.
- **Research metadata:** Collect quality grades, observation dates, and public observation links.
- **Pagination:** Retrieve multiple pages until the record limit or runtime limit is reached.
- **Duplicate detection:** Remove duplicate records using their IDs.
- **Automatic retries:** Retry HTTP 429 responses, server errors, and timeouts with backoff.
- **Error handling:** Save failed searches as error rows rather than stopping the entire run.

### Use Cases

- **Wildlife research:** Export recent observations for a species or taxonomic group.
- **Taxonomy research:** Resolve names to taxonomic ranks, ancestry, and common names.
- **Regional biodiversity analysis:** Filter observations by a known place ID.
- **License checks:** Review observation and photo license information before reuse.
- **Scheduled monitoring:** Collect new observations periodically and track new IDs.
- **Research datasets:** Export observation metadata for Python, Pandas, spreadsheets, and analytical workflows.

### Input Parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `taxonName` | string | `Python` | Scientific or common name to search. |
| `placeId` | integer | Empty | Optional iNaturalist place ID. Used only in observations mode. |
| `mode` | string | `observations` | Choose `observations` for sightings or `taxa` for matching taxonomic records. |
| `maxItems` | integer | `100` | Maximum number of records to save. Clamped to 1–1000. |
| `timeoutSecs` | integer | `300` | Maximum runtime in seconds. |
| `requestTimeoutSecs` | integer | `30` | HTTP timeout for each request, in seconds. |

### Example Input

```json id="a4v9mc"
{
  "taxonName": "Python",
  "mode": "observations",
  "maxItems": 100,
  "timeoutSecs": 300,
  "requestTimeoutSecs": 30
}
```

To restrict observations to a particular region, add a known place ID:

```json id="k7c3ny"
{
  "taxonName": "Python",
  "placeId": 1,
  "mode": "observations",
  "maxItems": 100,
  "timeoutSecs": 300,
  "requestTimeoutSecs": 30
}
```

Replace `1` with the intended iNaturalist place ID. Taxa mode ignores `placeId`.

### What Data Does This Actor Extract?

The actor supports two modes.

- **Observations mode:** Each row represents an individual wildlife sighting. Multiple sightings of the same species remain separate records.
- **Taxa mode:** Each row represents a matching taxonomic record. The `id` field identifies the taxon rather than an observation.

Taxonomic information comes from the nested taxon data. Text fields are cleaned where appropriate, and photo information is reduced to selected metadata.

#### Example Output

```json id="t8p2fr"
{
  "id": 407658633,
  "rank": "species",
  "rank_level": 10,
  "iconic_taxon_id": 26036,
  "ancestor_ids": [48460, 1, 2],
  "is_active": true,
  "name": "Morelia spilota",
  "parent_id": 67532,
  "ancestry": "48460/1/2/355675/26036",
  "extinct": false,
  "provisional": false,
  "default_photo": {
    "id": 107708381,
    "license_code": null,
    "attribution": "(c) honeyroll_ahmad_sah, all rights reserved",
    "url": "PHOTO_URL",
    "square_url": "PHOTO_SQUARE_URL",
    "medium_url": "PHOTO_MEDIUM_URL"
  },
  "taxon_changes_count": 0,
  "taxon_schemes_count": 1,
  "observations_count": 36196,
  "flag_counts": {
    "resolved": 0,
    "unresolved": 0
  },
  "current_synonymous_taxon_ids": null,
  "atlas_id": null,
  "complete_species_count": 38,
  "wikipedia_url": "WIKIPEDIA_URL",
  "complete_rank": "subspecies",
  "matched_term": "Pythons",
  "iconic_taxon_name": "Reptilia",
  "preferred_common_name": "Coastal Carpet Python",
  "qualityGrade": "research",
  "speciesGuess": "Coastal Carpet Python",
  "timeObservedAt": "2026-10-12T06:00:00+10:00",
  "observationUuid": "748b9834-0812-45f2-9c84-5e067fdd32e4",
  "observedOn": "2026-10-12",
  "licenseCode": "cc-by",
  "recordUri": "OBSERVATION_PAGE_URL",
  "sourceUrl": "API_REQUEST_URL",
  "scrapedAt": "2026-10-12T00:00:00+00:00"
}
```

The example illustrates the available fields. Observation-specific fields may be empty in taxa mode.

### Output Fields

| Field | Type | Description |
|---|---|---|
| `id` | integer | Observation ID or taxon ID, depending on the selected mode. |
| `rank` | string | Taxonomic rank. |
| `rank_level` | integer | Numeric taxonomic rank level. |
| `iconic_taxon_id` | integer | Iconic taxon ID. |
| `ancestor_ids` | array | IDs of ancestor taxa. |
| `is_active` | boolean | Whether the taxon is active. |
| `name` | string | Scientific name, with HTML removed. |
| `parent_id` | integer | Parent taxon ID. |
| `ancestry` | string | Taxonomic ancestry path. |
| `extinct` | boolean | Extinction flag. |
| `provisional` | boolean | Provisional taxon flag. |
| `default_photo` | object | Selected photo metadata, including license and image URLs. |
| `taxon_changes_count` | integer | Number of taxon changes. |
| `taxon_schemes_count` | integer | Number of taxon schemes. |
| `observations_count` | integer | Number of observations reported for the taxon. |
| `flag_counts` | object | Resolved and unresolved flag counts. |
| `current_synonymous_taxon_ids` | string or null | IDs of current synonymous taxa, when available. |
| `atlas_id` | string or null | Atlas ID, when available. |
| `complete_species_count` | integer | Number of complete species associated with the taxon. |
| `wikipedia_url` | string | Related Wikipedia URL. |
| `complete_rank` | string | Finest taxonomic rank covered by the taxon. |
| `matched_term` | string | Search term that matched the taxon. |
| `iconic_taxon_name` | string | Iconic taxon name. |
| `preferred_common_name` | string | Preferred common name or species guess when available. |
| `qualityGrade` | string | Observation quality grade. Empty in taxa mode. |
| `speciesGuess` | string | Observer's species guess. Empty in taxa mode. |
| `timeObservedAt` | string | Time the organism was observed. Empty in taxa mode. |
| `observationUuid` | string | Observation UUID. Empty in taxa mode. |
| `observedOn` | string | Calendar date of the observation. Empty in taxa mode. |
| `licenseCode` | string | License code associated with the observation. Photo licensing remains in `default_photo`. |
| `recordUri` | string | Public observation page URL. Empty in taxa mode. |
| `sourceUrl` | string | API request URL associated with the returned record. |
| `scrapedAt` | string | UTC timestamp when the record was saved. |
| `error` | string | Error description, present in failure rows. |
| `input` | object | Snapshot of the original input for a failure row. Proxy URLs are not copied. |

A bad mode, blank taxon name, HTTP error, or search with no results can produce an error row. Duplicate IDs are dropped, and `maxItems` stops the run even when additional pages exist.

### How It Works

1. Validates the input and checks the mode, taxon name, and optional place ID.
2. Calls the appropriate public iNaturalist endpoint for observations or taxa, using a contact User-Agent.
3. Retrieves results through pagination, requesting up to 200 records per page.
4. Continues until the record limit, final page, or runtime limit is reached.
5. Waits at least one second between requests.
6. Retries HTTP 429 responses, HTTP 5xx errors, and timeouts up to three additional times, using delays of 2, 4, and 8 seconds or longer when required by `Retry-After`.
7. Keeps the selected fields, removes HTML from applicable text, and writes records to the dataset.

Requests are sent directly unless proxy use is enabled in the actor configuration.

### Troubleshooting

#### The Dataset Contains Only an Error Row

A bad mode, blank name, failed API request, or search with no matching records can produce an error row. Read the `error` field for details.

#### The Name Matches No Records

Check the spelling of `taxonName` and try a recognized scientific or common name. If no matching record exists, the actor records an explanatory error row.

#### Fewer Records Were Collected Than Expected

Check `maxItems`, which defaults to 100 and cannot exceed 1000. Also check `timeoutSecs`, as the runtime limit can prevent additional pages from being requested.

#### The Place Filter Does Not Work

The `placeId` parameter is used only in observations mode. Make sure the value is a positive integer corresponding to the intended iNaturalist place.

#### Can I Filter by Observation Date?

Date-range inputs are not supported. After exporting, filter records using `observedOn` or `timeObservedAt`.

#### Why Are There Multiple Records for the Same Species?

Observations mode returns individual sightings, not one record per species. Multiple observations of the same species are expected. Deduplication removes repeated IDs rather than separate sightings.

#### Should I Enable a Proxy?

A proxy is not required for the standard configuration. Requests are sent directly unless proxy use is explicitly enabled.

### Limits and Data Usage

- The public iNaturalist API does not require an API key for these requests.
- The actor aims to stay near one request per second.
- The maximum page size is 200 records.
- The maximum number of records per run is 1000.
- `maxItems` defaults to 100.
- Date filters are not available as input parameters.
- Deep paging through `id_above` is not used.
- Observations and photos may have different licenses. Review the `licenseCode` field and the photo's `license_code` before reusing content.
- Attribution and reuse conditions depend on the license attached to each record or image.

### Frequently Asked Questions

#### Does the iNaturalist Observations Scraper need an API key?

No. Both observations mode and taxa mode use the public API without requiring a login or API key.

#### How many wildlife observations can one run export?

`maxItems` defaults to 100 and has a maximum of 1000. A run collecting 1000 records may require multiple pages, with a one-second pause between requests. Allow sufficient time by setting `timeoutSecs` to 300 or higher when needed.

#### What happens if the name matches nothing?

The run can complete with a dataset containing one error row that explains the unsuccessful search.

#### Can I collect taxa and observations in the same run?

No. Each run uses one mode. Run the actor again with `mode` set to `taxa` if you also need matching taxonomic records.

#### Will a proxy be used?

Only if proxy use is enabled in the actor configuration. Direct requests are used by default.

#### Can I export the results to CSV, JSON, and Excel?

The actor produces a JSON dataset that can be downloaded or exported as CSV or Excel for research and analysis.

#### Can I schedule recurring collections?

Yes. Schedule recurring runs to refresh observations or taxa results. Store previously collected IDs in your own database if you want to track new records over time.

### Pricing

The actor is free to run. Apify platform usage is billed separately according to your applicable plan.

### Trademark Notice

iNaturalist and related names, logos, and trademarks belong to their respective owners. They are referenced only to identify the data source and do not imply affiliation, sponsorship, endorsement, or partnership.

### Disclaimer

This actor is an independent tool and is not affiliated with, endorsed by, sponsored by, or officially associated with iNaturalist or its owners.

Data is collected from publicly available sources. Users are responsible for complying with applicable source terms, rate limits, attribution requirements, and licensing conditions.

### Is Web Scraping Legal?

This actor collects publicly available data from iNaturalist, primarily through its public API, without logging in or bypassing access controls. The legality and conditions of using public data depend on applicable laws, source terms, data licenses, and intended use.

- **Personal data:** If results include personal information, such as names, email addresses, or profile details, privacy laws may apply. Collect and store personal data only when you have an appropriate lawful basis.
- **Copyright and image licenses:** Observation notes, descriptions, and photographs may be subject to copyright or specific licenses. Check the license attached to each record and image before reuse.
- **Terms of use:** Respect applicable source policies, request limits, attribution requirements, and licensing conditions.
- **Legal advice:** This information is provided for general educational purposes and does not constitute legal advice. Consult a qualified professional if you need guidance on a specific use case.

# Actor input Schema

## `taxonName` (type: `string`):

Scientific or common name to search, such as Python or Coastal Carpet Python.

## `placeId` (type: `integer`):

Optional iNaturalist place id. Applied only in observations mode.

## `mode` (type: `string`):

Use observations for wildlife sightings or taxa for matching taxon names.

## `maxItems` (type: `integer`):

Maximum records to save this run. Values are clamped between 1 and 1000.

## `timeoutSecs` (type: `integer`):

Maximum run time in seconds. Fetching stops when this limit is reached.

## `requestTimeoutSecs` (type: `integer`):

Seconds to wait for each HTTP response before retrying or recording an error.

## `proxyConfiguration` (type: `object`):

Optional proxy for requests. Leave Apify Proxy off unless the API blocks your IP.

## Actor input object example

```json
{
  "taxonName": "Python",
  "placeId": 1,
  "mode": "observations",
  "maxItems": 100,
  "timeoutSecs": 300,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `datasetUrl` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "taxonName": "Python",
    "mode": "observations",
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("zerobreak/inaturalist-observations-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "taxonName": "Python",
    "mode": "observations",
    "maxItems": 100,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("zerobreak/inaturalist-observations-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "taxonName": "Python",
  "mode": "observations",
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call zerobreak/inaturalist-observations-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zerobreak/inaturalist-observations-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/C99kmtblR4VRmKXq6/builds/0M038QGiYjQ5acdNC/openapi.json
