Skip to main content

sloppynotes

A small Python library and CLI that reuses an authenticated Evernote web session without API keys.

Install the PyPI distribution as sloppynotes.

What this package does

This project reuses an already authenticated Evernote web session and then talks to Evernote's legacy NoteStore APIs.

How login works, and how this package avoids a separate API login

This package does not ask for Evernote API keys and does not implement a separate username/password login flow. Instead, it reuses the login state that already exists in a Chromium-based browser.

High-level flow:

  1. Launch or reuse a CDP-enabled Chromium/Chrome browser profile.
  2. Check whether Evernote is already logged in in that browser.
  3. Read the Evernote auth cookie from the logged-in browser session.
  4. Extract the legacy monolith auth token and shard from that cookie.
  5. Save them to a local evernote-auth.json file.

Reverse-engineered details:

  1. The Evernote web client exchanges auth at:
    • https://accounts.evernote.com/auth/token
  2. That response contains tokens such as:
    • access_token
    • refresh_token
    • id_token
  3. The access_token payload contains mono_authn_token, which is the legacy auth token used by older Evernote services.
  4. The same monolith token is also available in the auth cookie for *.evernote.com.
  5. This package reads that browser cookie and saves the minimal auth data needed for later API calls.

How this package reads notebooks and notes

After saving the auth token and shard, the package talks to Evernote's legacy NoteStore service. The shard tells us which Evernote service URL to call, and the saved monolith token is used to authenticate those requests.

That is how the library and CLI can call methods such as:

  • listNotebooks
  • findNotesMetadata
  • getNote

Playwright is only needed for the browser side of the flow. It connects to a Chromium-based browser over CDP so this package can detect the logged-in Evernote session and read the auth cookie. Once evernote-auth.json has been saved, the later NoteStore API calls do not depend on Playwright; they use the saved auth together with the Thrift client code.

A short introduction to Thrift

Thrift is a remote procedure call and serialization system. In practice, that means a client can call named backend methods such as listNotebooks and send structured request data in a format the server understands.

Evernote's older APIs use Thrift for services such as NoteStore and UserStore. This project uses Python Thrift support to serialize those requests and parse the responses.

Legacy Evernote NoteStore/UserStore model:

Usage

Install

uv run --with sloppynotes -- evernote login

Login flow

For Faltoobot, reuse the shared browser so Evernote cookies come from the same browser session:

faltoobot browser https://www.evernote.com/client/web
uv run --with sloppynotes -- evernote login --use-existing-browser

The login command now navigates/reloads Evernote Web, waits for the legacy auth cookie, and verifies the captured token against NoteStore before saving it. For slow browser sessions, increase the wait:

uv run --with sloppynotes -- evernote login --use-existing-browser --auth-cookie-wait-seconds 45

You can also let evernote login launch its own browser profile when you are not using Faltoobot:

evernote login  # saves login credentials to ~/.sloppynotes/evernote-auth.json

Usage from Python

from evernote.client import get_note, get_notes, get_notebooks, search_notes

notebooks = get_notebooks()
matching_notes = search_notes('intitle:"Project"')
notes = get_notes(notebooks[0])
note = get_note(notes[0])

These helpers return plain Python dictionaries. Typical shapes look like this:

notebooks = get_notebooks()
notebook = notebooks[0]
print(notebook)
# {
#   "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
#   "guid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
#   "name": "Work",
#   "defaultNotebook": False,
#   "serviceCreated": 1710000000000,
#   "serviceUpdated": 1710000000000,
#   "updateSequenceNum": 12345,
# }

notes = get_notes(notebook)
note_summary = notes[0]
print(note_summary)
# {
#   "id": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
#   "guid": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
#   "title": "Project plan",
#   "contentLength": 2048,
#   "created": 1710000000000,
#   "updated": 1710003600000,
#   "notebookGuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
#   "tagGuids": ["zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz"],
#   "updateSequenceNum": 23456,
# }

note = get_note(note_summary)
print(note)
# {
#   "id": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
#   "guid": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
#   "title": "Project plan",
#   "content": "<?xml version=...>",
#   "content_text": "Project plan\n- task 1\n- task 2",
#   "contentLength": 2048,
#   "created": 1710000000000,
#   "updated": 1710003600000,
#   "notebookGuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
#   "tagGuids": ["zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz"],
#   "updateSequenceNum": 23456,
# }

Some optional keys may be missing when Evernote does not return them.

Downloading note attachments / PDFs

get_note() now includes resource metadata when a note has attachments. Binary bodies are omitted by default. Use get_note_resources() or download_note_attachments() to fetch PDFs and other attachments:

from pathlib import Path
from evernote.client import (
    download_note_attachments,
    get_note_resources,
    get_notebooks,
    get_notes,
)

notebook = next(nb for nb in get_notebooks() if nb["name"] == "Theatre XYZ")
note = next(n for n in get_notes(notebook) if n["title"] == "Niveshaay Theater report")

resources = get_note_resources(note)
print(resources[0]["mime"], resources[0]["data"]["size"])

pdf_paths = download_note_attachments(
    note,
    Path("./downloads"),
    mime_types={"application/pdf"},
)
print(pdf_paths)

You can also fetch a resource from the ENML <en-media hash="..."> value:

from evernote.client import get_resource_by_hash

resource = get_resource_by_hash(note, "694b999fae77de39da70e0215d39648e")
pdf_bytes = resource["data"]["body"]

Publishing a new version

The release workflow publishes version tags to PyPI using trusted publishing. From a clean working tree, publish the next patch release with:

uv version --bump patch
version=$(uv version --short)

uv run --group dev pre-commit run --all-files

git add pyproject.toml uv.lock CHANGELOG.md
git commit -m "Prepare $version release"
git tag -a "v$version" -m "v$version"
git push origin main "v$version"

The tag must match the version in pyproject.toml, with a v prefix; the workflow fails otherwise. Use --bump minor or --bump major when appropriate. PyPI versions are immutable, so every release needs a new version.

Metadata

Release files for sloppynotes 2.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sloppynotes 2.1.0
File Size Uploaded
sloppynotes-2.1.0.tar.gz 19.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sloppynotes 2.1.0
File Interpreter ABI Platform
sloppynotes-2.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 39.0 kB

Release files / sloppynotes-2.1.0.tar.gz

Download URL sloppynotes-2.1.0.tar.gz
Size 19.7 kB
Tags Source
SHA-256 checksum
How to use checksums
1ed1a9b38e065e41f9da5b06c24fe4e4af34b563d2287a8f6ca3dbd56097b7c1
BLAKE2b-256 checksum
How to use checksums
dd5fc6185bc369f3bd001ab0d5f010366c8e0b01b984bd6ef5a9f6902d8bcfb6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / sloppynotes-2.1.0-py3-none-any.whl

Download URL sloppynotes-2.1.0-py3-none-any.whl
Size 19.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b0d31febdb0dfa6b200837483dfb8b99bfe7cb0461462ff20531a0c153c800ef
BLAKE2b-256 checksum
How to use checksums
84bc67b7def5d65446410157d06f1145ab15e1184c7f00e327798f296e512a74
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.1.0 This release

2 release files

2.0.1

2 release files

2.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page