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:
- Launch or reuse a CDP-enabled Chromium/Chrome browser profile.
- Check whether Evernote is already logged in in that browser.
- Read the Evernote
authcookie from the logged-in browser session. - Extract the legacy monolith auth token and shard from that cookie.
- Save them to a local
evernote-auth.jsonfile.
Reverse-engineered details:
- The Evernote web client exchanges auth at:
https://accounts.evernote.com/auth/token
- That response contains tokens such as:
access_tokenrefresh_tokenid_token
- The
access_tokenpayload containsmono_authn_token, which is the legacy auth token used by older Evernote services. - The same monolith token is also available in the
authcookie for*.evernote.com. - 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:
listNotebooksfindNotesMetadatagetNote
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)
| File | Size | Uploaded | |
|---|---|---|---|
| sloppynotes-2.1.0.tar.gz | 19.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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