voidnote
Official Python SDK for VoidNote: zero-knowledge self-destructing notes and live encrypted streams.
v0.5.0 · Python 3.9+ · no account needed for notes
Install
pip install voidnote
Quick start
import voidnote
# No account or API key needed. Content is encrypted locally; the server never sees it.
note = voidnote.create("my secret value")
print(note.url) # https://voidnote.net/n/<tokenId>#<secret>
print(voidnote.read(note.url).content)
API
voidnote.create(content, *, api_key=None, password=None, max_views=1, expires_in=1) → CreateResult
Encrypt content locally and store the ciphertext. expires_in is in hours.
result = voidnote.create("deploy key: abc123", max_views=3, expires_in=6)
print(result.url) # full shareable URL, includes #secret
print(result.token) # 32-char tokenId only
print(result.expires_at, result.view_limit, result.kdf)
Without an API key the note is anonymous: up to 168 hours (7 days) and 10 views, rate-limited per IP. An API key from a free account raises that to 720 hours and 100 views:
voidnote.create("deploy key: abc123", api_key="vn_...", max_views=50, expires_in=720)
Add a password and the reader needs it as well as the link. The key is then PBKDF2-HMAC-SHA256 (600,000 iterations) of the password, salted with the link's secret:
guarded = voidnote.create("db password: hunter2", password="correct horse")
# share guarded.url and the password through different channels
voidnote.read(url, *, password=None) → ReadResult
Fetch and decrypt a note. Consumes one view. Accepts v1 URLs (/n/<token>#<secret>) and legacy URLs.
note = voidnote.read("https://voidnote.net/n/<tokenId>#<secret>")
print(note.content) # decrypted plaintext
print(note.views_remaining) # views left after this read
print(note.destroyed) # True if this was the last view
# A note created with a password
guarded = voidnote.read(url, password="correct horse")
Before using a view, read checks the note's metadata. A missing note, a password note read without a password, or an unknown key derivation raises without touching the view. A wrong password can only be detected after decrypting, so it does use the view.
voidnote.peek(url) → PeekResult
Check metadata without consuming a view. kdf is "pbkdf2-sha256-600k" when the note needs a password.
meta = voidnote.peek("https://voidnote.net/n/<tokenId>#<secret>")
print(meta.exists, meta.views_remaining, meta.expires_at, meta.kdf)
Errors
Failures raise voidnote.VoidNoteError (a RuntimeError) with a code:
| code | meaning |
|---|---|
http |
the server refused; status and the server's message, plus retry_after (seconds) on 429 |
not_found |
the note doesn't exist, expired, or its views are used up |
password_required |
the note needs a password; no view was used |
unsupported_kdf |
the note uses a key derivation this version can't do; no view was used |
decryption_failed |
wrong password or a damaged link; the view was used |
try:
voidnote.create("x")
except voidnote.VoidNoteError as e:
if e.status == 429:
print(f"rate limited, retry in {e.retry_after}s: {e}")
Void Streams
Live encrypted real-time channels. Creating one needs an API key from a free account; watching doesn't.
import voidnote
# Create a channel
stream = voidnote.create_stream(api_key="vn_...", title="Deploy #42")
print(stream.url) # share this URL with watchers
# Write messages (write and close are authenticated with the same API key)
stream.write("Starting deployment...")
stream.write("Build complete.")
stream.close() # self-destructs all content
# Watch a stream: yields decrypted messages as they arrive
for message in voidnote.watch(stream.url):
print(message)
# Password-protected stream
for message in voidnote.watch(url, password="hunter2"):
print(message)
Security model
server generates tokenId (32-char hex, stored as lookup key)
client generates secret (32-char hex, NEVER sent to server)
key = SHA-256(bytes.fromhex(secret)) kdf "sha256"
key = PBKDF2-HMAC-SHA256(password, bytes.fromhex(secret), 600000) kdf "pbkdf2-sha256-600k"
ciphertext = AES-256-GCM(plaintext, key, random_12_byte_iv)
share URL = https://voidnote.net/n/{tokenId}#{secret}
The #fragment is never transmitted to HTTP servers per spec. The server has ciphertext; you have the key.
Requirements
- Python 3.9+
cryptography >= 41.0.0(installed automatically)
Tests
uv run --no-project --with pytest --with cryptography --with-editable . python -m pytest tests # mocked network
uv run --no-project --with cryptography --with-editable . python tests/live.py # real notes on voidnote.net
Links
Metadata
Release files for voidnote 0.5.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 | Provenance | Actions |
|---|---|---|---|---|
| voidnote-0.5.0.tar.gz | 12.7 kB | Without provenance | Details for voidnote-0.5.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| voidnote-0.5.0-py3-none-any.whl | Python 3 | none | any | Details for voidnote-0.5.0-py3-none-any.whl |
Total release size: 22.9 kB
Release files / voidnote-0.5.0.tar.gz
| Download URL | voidnote-0.5.0.tar.gz |
|---|---|
| Size | 12.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aca682452892344d016b9dd553e4b597c700da6af212897c97b0c5e150a00ef1
|
|
BLAKE2b-256 checksum How to use checksums |
baf9bde0066c480c91ced5cdb5142450edc72a76f21db1a8c30a8cae350254ec
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / voidnote-0.5.0-py3-none-any.whl
| Download URL | voidnote-0.5.0-py3-none-any.whl |
|---|---|
| Size | 10.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
68a6cd3d48ad20db4051665623a021ddc2858e13fa8b03f818b4ba65598dd305
|
|
BLAKE2b-256 checksum How to use checksums |
621b0300a5b5a0aad90c94f496d70479a192875f87fc1f9289e702af9e8a3941
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for voidnote 0.5.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.