InoreaderExportTool

Tool for backing up tagged items from Inoreader using the official API and local JSON backups.

Label naming convention (folders vs tags)

Inoreader’s API refers to both folders and tags as “labels,” but this toolset relies on a naming convention to tell them apart:

Only lowercase labels are treated as “exportable” tags for backup and clearing:

If you follow the same convention (folders Title Case, tags lowercase), the tools will avoid touching your folder structure and will only operate on your tag-style labels.

Setup

  1. Install Python (3.11+ recommended) and optionally create/activate a virtual env.
  2. Install dependencies (managed by uv):

     uv sync
    
  3. Create .env from the example:

     copy .env.example .env   # Windows
     # or
     cp .env.example .env     # macOS/Linux
    
  4. Run the setup helper to configure OAuth:

     uv run ino_setup.py
    
    • First run (no .env yet) does full setup:
      • Prompts for INOREADER_CLIENT_ID, INOREADER_CLIENT_SECRET, INOREADER_REDIRECT_URI.
      • Prompts for INOREADER_SCOPE with: Scope [Enter=keep, r=read, rw=readwrite, custom=type value]:
      • Prints an auth URL, you open it, authorize, and paste back the code.
      • Saves INOREADER_ACCESS_TOKEN and INOREADER_REFRESH_TOKEN into .env.
    • Later, to refresh tokens:

        uv run ino_setup.py
      

      This uses the existing .env and refresh token to get a new access token and update .env. - To force a clean full setup (e.g. new app or redirect URI):

        uv run ino_setup.py --full
      
  5. Ensure your .env scope allows tag editing. For batch label clears, you need Zone 2 access, for example:

     INOREADER_SCOPE="read write"
    

    so that edit-tag operations are permitted.

Rate limiting and batching

Inoreader enforces strict daily limits per client app:

This tool is designed to:

The effective pattern is:

When the daily Zone 1 limit is exhausted (HTTP 429 “Daily request limit reached!”), the batch driver exits cleanly and you can still run ino_clear.py to perform Zone 2-only cleanup using IDs already stored in state/state.json.

Core tools

ino_process_tag.py

Fetch items for a single Inoreader label via stream/contents and write per-run JSON snapshots.

Input

Behavior

Usage

uv run --env-file .env ino_process_tag.py travel
uv run --env-file .env ino_process_tag.py travel --max-items 5000

ino_merge_outputs.py

Turn per-run outputs into backups for a label and keep backups tidy.

Input

Behavior per run for a label

  1. Batch backup from outputs, then clear outputs
    • Merge all output/<label>_*.json into a dict keyed by id (deduped).
    • Write a dated batch backup:
      • backup/<label>_<YYYY-MM-DDTHH-MM-SSZ>.json
    • Delete the merged output/<label>_*.json files.
  2. Full backup from existing full + all dated
    • Start from existing backup/<label>.json if present.
    • Merge in all backup/<label>_*.json.
    • Dedupe by id.
    • Write updated full backup:
      • backup/<label>.json (always “everything so far” for that label).

Usage

uv run --env-file .env ino_merge_outputs.py travel

After running:


ino_clear.py

Clear labels from items using the IDs tracked in state/state.json, using batched edit-tag calls in Zone 2.

Input

Behavior

Usage

# Clear a specific label with large batched Zone 2 calls
uv run --env-file .env ino_clear.py travel --batch-size 5000

# Cap the number of edit-tag calls for this run
uv run --env-file .env ino_clear.py cpp --batch-size 5000 --max-calls 5

# See how many calls a given batch size would use, without hitting the API
uv run --env-file .env ino_clear.py cpp --batch-size 5000 --dry-run

# Show per-label pending/done counts from state/state.json
uv run --env-file .env ino_clear.py --summary

Flags:

Usage

# Daily backup for all lowercase “exportable” labels
uv run --env-file .env ino_run_batch.py

# Daily backup + post-backup label clearing (Zone2 batched)
uv run --env-file .env ino_run_batch.py --clear-after

With --clear-after, a single daily run can:

Auth and state helpers

ino_setup.py

Single entry point for Inoreader OAuth:

See Setup section above for details and usage.

ino_api.py

Low-level helpers around the Inoreader API:

ino_state.py

State management for label runs (backed by state/state.json):

Typical helpers:

Used by:

Test helpers

test_auth_url.py

Small helper to exercise build_auth_url from ino_setup.py with your current .env.

Usage

uv run test_auth_url.py

Prints the auth URL that ino_setup.py uses internally.

test_token_flow.py

Interactive helper for testing token exchange and refresh functions.

Usage

uv run test_token_flow.py

Typical daily workflow

A common way to use these tools together is:

  1. Run the batch driver to fetch and back up items for all “exportable” labels (lowercase names):

     uv run --env-file .env ino_run_batch.py
    

    This walks labels via stream/contents (Zone 1), writes per-run snapshots under output/, and updates state/state.json with pending_ids per label.

  2. (Optional) Immediately clear labels after backup in the same run:

     uv run --env-file .env ino_run_batch.py --clear-after
    

    With --clear-after, each processed label is also passed to ino_clear.clear_label_from_state, which removes that label from items using batched edit-tag calls in Zone 2.

  3. At any later point (even after Zone 1 is exhausted for the day), run targeted clears for individual labels using only Zone 2:

    # Clear a single lowercase label based on pending IDs already stored in state.json
    uv run --env-file .env ino_clear.py cpp --batch-size 5000 --max-calls 5
    
    # Inspect current state without clearing anything
    uv run --env-file .env ino_clear.py --summary
    

    Because ino_clear.py only uses stored IDs and calls edit-tag (Zone 2), you can keep clearing labels as long as there is Zone 2 quota, even when Zone 1 (read) requests have hit their daily limit.

    For extra safety, run with --dry-run first to see which lowercase labels and how many items would be affected before making changes.