A dependency-free (stdlib-only) Go CLI for the Instapaper Full API.
Features:
- OAuth 1.0a signed requests (HMAC-SHA1)
- xAuth login (
/api/1/oauth/access_token) - Bookmarks: add, list, export/import, archive/unarchive, star/unstar, move, delete, get_text
- Folders: list, add, delete, set_order
- Highlights: list, add, delete
- Health/verify checks, JSON schema output
- NDJSON/JSON/plain output, structured stderr (
--stderr-json), retries, dry-run, idempotent mode - Incremental sync (cursor files or bounds), bulk operations, and progress events
- Client-side filtering (
--select), verbose summaries, and paged exports
go install github.com/vburojevic/instapaper-cli/cmd/ip@latest
ip versionHomebrew (via tap):
brew tap vburojevic/tap
brew install instapaper-cliOr build from source:
go build ./cmd/ip
./ip versionRelease process: see docs/release-checklist.md.
export INSTAPAPER_CONSUMER_KEY="..."
export INSTAPAPER_CONSUMER_SECRET="..."
printf '%s' "your-password" | ./ip auth login --username "you@example.com" --password-stdin
./ip list --ndjson --limit 10You need an Instapaper API consumer key/secret.
Set them as env vars (recommended):
export INSTAPAPER_CONSUMER_KEY="..."
export INSTAPAPER_CONSUMER_SECRET="..."Or pass them to auth login.
# Interactive (password may echo depending on your OS; prefer --password-stdin)
./ip auth login --username "you@example.com" --password-stdin
# Example with stdin password:
printf '%s' "your-password" | ./ip auth login --username "you@example.com" --password-stdin
# Non-interactive (disable prompts):
printf '%s' "your-password" | ./ip auth login --username "you@example.com" --password-stdin --no-inputThis stores only the OAuth access token + secret in your user config directory. It does not store your Instapaper password.
Config location:
./ip config path./ip config show
./ip config get defaults.format
./ip config set defaults.list_limit 100
./ip config unset defaults.resolve_final_url./ip add https://example.com/article \
--title "Example" \
--tags "go,readlater" \
--folder unreadAdd from stdin:
cat urls.txt | ./ip add -./ip list --folder unread --limit 25
./ip list --folder archive --format json
./ip list --folder archive --format table
./ip list --folder archive --json
./ip list --ndjson
./ip list --have "123:0.5:1700000000" --highlights "123,456"
./ip list --fields "bookmark_id,title,url" --ndjson
./ip list --cursor ~/.config/ip/cursor.json
./ip list --cursor-dir ~/.config/ip/cursors
./ip list --since bookmark_id:12345
./ip list --until time:2025-01-01T00:00:00Z
./ip list --updated-since 2025-01-01T00:00:00Z
./ip list --limit 0 --max-pages 50
./ip list --select "starred=1,tag~news"
./ip list --plain --output bookmarks.txt
./ip list --folder "My Folder" # resolves folder titleBy default, list returns all bookmarks (no limit) unless defaults.list_limit is set in config.
Bounds format for --since/--until:
bookmark_id:<id>(default when no prefix is supplied)time:<rfc3339|unix>progress_timestamp:<rfc3339|unix>
Select format for --select:
- Comma-separated filters:
<field><op><value> - Operators:
=,!=,~(contains, case-insensitive) - Fields:
bookmark_id,time,progress,progress_timestamp,starred,title,url,description,tags
--ndjson(default): one JSON object per line (stream-friendly).--json: a single JSON array/object.--plain: stable, tab-delimited text (for pipes).--format table: human table (avoid for parsing).
Use --output <file> to write results to a file. Use - for stdout.
Use --output-dir <dir> on export to write each page as its own NDJSON file.
Use --verbose to emit summary counts to stderr (keeps stdout clean).
./ip archive 123456
./ip unarchive 123456
./ip star 123456
./ip unstar 123456
./ip move --folder "Work" 123456
# Permanent delete (requires explicit flag)
./ip delete 123456 --yes-really-delete
./ip delete 123456 --confirm 123456
# Bulk mutations
./ip archive --ids 1,2,3
printf "10\n11\n12\n" | ./ip unarchive --stdin
./ip delete --ids 5,6 --yes-really-delete
./ip archive --ids 1,2,3 --batch 2Dry-run and idempotent modes:
./ip --dry-run archive 123456
./ip --idempotent highlights add 123456 --text "Some quote"./ip text 123456 --out article.html
./ip text 123456 --out article.html --open
printf "1\n2\n3\n" | ./ip text --stdin --out ./articles./ip progress 123456 --progress 0.5 --timestamp 1700000000./ip folders list
./ip folders add "New Folder"
./ip folders delete "New Folder" --yes
# Reorder folders: folder_id:position pairs (must include all folders)
./ip folders order "100:1,200:2,300:3"./ip highlights list 123456
./ip highlights add 123456 --text "Some quote" --position 0
./ip highlights delete 98765# Export all bookmarks (NDJSON by default)
./ip export --cursor ~/.config/ip/cursor.json
./ip export --cursor-dir ~/.config/ip/cursors
./ip export --since time:2025-01-01T00:00:00Z
# Export with specific fields
./ip export --fields "bookmark_id,title,url" --ndjson
# Export into a directory (paged NDJSON files)
./ip export --output-dir ./exports --cursor-dir ~/.config/ip/cursors
# Note: --output-dir requires NDJSON output (default)
# Import from plain text (one URL per line)
./ip import --input urls.txt --input-format plain
# Import from NDJSON
./ip import --input bookmarks.ndjson --input-format ndjson
# Import with progress events on stderr
./ip import --input bookmarks.ndjson --input-format ndjson --progress-jsonUse --progress-json to emit progress lines to stderr for long operations:
./ip import --input bookmarks.ndjson --input-format ndjson --progress-jsonWrite output to a file:
./ip list --format json --output bookmarks.json./ip health
./ip verify./ip schema bookmarks
./ip schema authThis CLI is optimized for agent workflows. Default output is NDJSON; use structured output and exit codes for reliable parsing.
--jsonfor single objects (auth status, config, or single operations).--ndjson(or--jsonl) for streaming lists; each line is a full JSON object.--plainfor stable, line-oriented text output.--stderr-jsonfor structured errors, error codes, and hints on stderr.--outputto write results to a file (use-for stdout).- Run
ip help aifor agent-focused tips. - Run
ip doctorto preflight config/auth/network before long workflows. - Use
--since/--untilor--updated-sincefor deterministic incremental pulls. - Use
--cursor-dirfor auto cursor files per folder/tag. - Use
--idsor--stdinfor bulk mutations;--progress-jsonfor progress events. - Use
--selectfor client-side filtering when the API doesn't support it.
Examples:
./ip --json auth status
./ip --json config show
./ip list --ndjson --limit 0
./ip list --plain --output bookmarks.txt
./ip --stderr-json list --limit 1
./ip doctor --json./ip --help
./ip help list
./ip help aiINSTAPAPER_CONSUMER_KEYINSTAPAPER_CONSUMER_SECRETINSTAPAPER_API_BASE(optional; defaults tohttps://www.instapaper.com)INSTAPAPER_TIMEOUT(optional; Go duration like10s,1m)
- Auth errors: run
./ip auth statusor./ip --json auth statusto verify tokens. - Rate limits: error code
1040means retry later; consider backing off. - Config issues:
./ip config pathto locate your config;./ip --json config showto inspect values. - Network problems: try
./ip --debug list --limit 1to see request timing and status codes. - Preflight: run
./ip doctorto see config/auth/network readiness in one shot.
0success1generic failure2invalid usage10rate limited11premium required12application suspended13invalid request14server error
When you pass --stderr-json, errors include a stable code string and exit_code.
rate_limited,premium_required,app_suspendedinvalid_request,server_error,api_errorauth_error,config_errortimeout,network_errorinvalid_usage,unknown
- Instapaper's API Terms of Use prohibit storing user passwords. This CLI only stores OAuth tokens.
- For Windows users,
--password-stdinis strongly recommended.
| Instapaper endpoint | CLI command(s) | Notes |
|---|---|---|
/api/1/oauth/access_token |
ip auth login |
XAuth; use --password-stdin for secrets. |
/api/1/account/verify_credentials |
ip auth status, ip health, ip verify, ip doctor |
Credential and network checks. |
/api/1/bookmarks/list |
ip list, ip export |
Supports paging/cursors/bounds. |
/api/1/bookmarks/add |
ip add, ip import |
--tags, --folder, --content. |
/api/1/bookmarks/update_read_progress |
ip progress |
Progress + timestamp. |
/api/1/bookmarks/delete |
ip delete |
Requires explicit confirmation. |
/api/1/bookmarks/archive |
ip archive |
|
/api/1/bookmarks/unarchive |
ip unarchive |
|
/api/1/bookmarks/star |
ip star |
|
/api/1/bookmarks/unstar |
ip unstar |
|
/api/1/bookmarks/move |
ip move |
Folder ID or title. |
/api/1/bookmarks/get_text |
ip text |
Writes text to stdout or --out. |
/api/1/folders/list |
ip folders list |
|
/api/1/folders/add |
ip folders add |
|
/api/1/folders/delete |
ip folders delete |
Requires confirmation. |
/api/1/folders/set_order |
ip folders order |
Comma-separated id:position. |
/api/1.1/bookmarks/{id}/highlights |
ip highlights list |
|
/api/1.1/bookmarks/{id}/highlight |
ip highlights add |
|
/api/1.1/highlights/{id}/delete |
ip highlights delete |
|
| (no tag management API) | ip tags |
Instapaper API does not support tag CRUD. |
https://www.instapaper.com/api