freshjots — Ruby
Tiny Ruby client for the Fresh Jots API. One file,
no runtime dependencies (uses net/http from stdlib).
Install
# Gemfile
gem "freshjots"bundle installOr globally:
gem install freshjotsUse
require "freshjots"
# Reads FRESHJOTS_TOKEN from the environment by default.
client = Freshjots::Client.new
# Append text to a note (creates it if missing).
client.append("cron-jobs-prod", "backup ok #{Time.now.utc.iso8601}")
# Read a note's body.
puts client.note("cron-jobs-prod")[:plain_body]
# List your notes (most recent activity first). Sort and filter with options:
client.notes(sort: "created", folder_id: 3, limit: 20).each do |note|
puts "#{note[:id]}\t#{note[:filename]}\t#{note[:title]}"
end
# Create a note. The API derives the filename from the title — for a
# note addressable by an exact filename, use append instead.
created = client.create(title: "Research 2026 Q2", body: "Initial outline.")
puts created[:filename] # server-derived stream name
# Update a note's fields (only the keys you pass change). By id or by filename:
client.update(42, title: "Q2 research", body: "Revised outline.")
client.set("cron-jobs-prod", folder: "Ops", deadline: 26) # metadata only, no body needed
# Create many notes in one atomic batch (up to 50 — all land or none do).
client.bulk([{ title: "a", plain_body: "1" }, { title: "b", plain_body: "2" }])
# Organize: move into a folder (by id or name), delete (by id or filename).
client.move("cron-jobs-prod", folder: "Ops")
client.delete("old-note")
# Folders: list, read one, create, rename, delete (its notes move to the built-in default folder).
client.folders.each { |f| puts "#{f[:id]}\t#{f[:name]}" }
ops = client.create_folder("Ops")
client.rename_folder(ops[:id], "Operations")
client.delete_folder(ops[:id])Client methods: notes(sort:, folder_id:, limit:, offset:), note(filename), note_by_id(id), create(title:, body:, client_encrypted:), append(filename, text, client_encrypted:), update(id, **fields), set(filename, **fields), bulk(notes), delete(id_or_filename), move(id_or_filename, folder:), folders, folder(id), create_folder(name), rename_folder(id, name), and delete_folder(id). Client-side crypto: Freshjots.encrypt(text, passphrase) / Freshjots.decrypt(token, passphrase) (see Encryption). note/note_by_id/create/update/set and the single-folder methods return the hash directly (no { note: … } / { folder: … } wrapper); notes and folders return arrays, and bulk returns { created: [...] }. For update/set, the fields are title:, body:, folder: (id or name — root: true un-folders), deadline:, alert_email:, webhook_url:, webhook_secret:; changing title: rewrites the body as a unit, so pass body: too. For notes, sort is created|updated|appended and folder_id may be a folder id or "none" (un-foldered only).
Encryption
Keep notes the server can't read: encrypt locally with your own passphrase,
store the ciphertext, decrypt locally on read. Built in on Ruby's stdlib
openssl (no gem dependencies), and interoperable with the JS and Python
clients.
require "freshjots"
client = Freshjots::Client.new
pw = ENV.fetch("FRESHJOTS_PASSPHRASE")
# Store an encrypted note: encrypt the body, flag it client_encrypted.
client.create(title: "Recovery codes", body: Freshjots.encrypt("1234-5678", pw), client_encrypted: true)
# Read it back and decrypt locally.
puts Freshjots.decrypt(client.note("recovery-codes")[:plain_body], pw)The format is fj1 (AES-256-CBC + HMAC-SHA256, PBKDF2-HMAC-SHA256), interoperable
with the JS, Python, MCP, and shell (brew) clients. You hold the only key —
Fresh Jots never receives it and cannot recover the note if you lose it, so
back the passphrase up somewhere safe. Encryption is per-note and personal-only
(not team notes); the title and metadata stay in the clear, so keep secrets out
of the title. Freshjots.decrypt raises Freshjots::EncryptionError on a wrong
passphrase. See https://freshjots.com/encrypted-notes.
Errors
Any non-2xx response raises Freshjots::ApiError with status, code,
message, and (when present) details from the API's stable error
envelope:
begin
client.append("huge", "x" * 5_000_000)
rescue Freshjots::ApiError => e
puts "#{e.status} #{e.code}: #{e.message}"
# 413 content_too_large: body exceeds the per-note 3 MB cap
endStable error codes: unauthenticated, forbidden, not_found,
validation_failed, cap_exceeded, storage_cap_exceeded,
content_too_large, content_type_mismatch, rate_limited. Full list:
https://freshjots.com/docs.
Auth
Mint a token at https://freshjots.com/settings/api_tokens (Dev or
Team tier required). Set it once, persisted for every new shell
(macOS defaults to zsh; use ~/.bashrc on bash):
echo 'export FRESHJOTS_TOKEN=<your-token>' >> ~/.zshrc && source ~/.zshrcOr pass explicitly:
Freshjots::Client.new(token: "mn_…")License
MIT.