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 install

Or globally:

gem install freshjots

Use

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

# Organize: move into a folder (by id or name), delete (by id or filename), list folders.
client.move("cron-jobs-prod", folder: "Ops")
client.delete("old-note")
client.folders.each { |f| puts "#{f[:id]}\t#{f[:name]}" }

Client methods: notes(sort:, folder_id:, limit:, offset:), note(filename), note_by_id(id), create(title:, body:, client_encrypted:), append(filename, text, client_encrypted:), delete(id_or_filename), move(id_or_filename, folder:), and folders. Client-side crypto: Freshjots.encrypt(text, passphrase) / Freshjots.decrypt(token, passphrase) (see Encryption). note/note_by_id/create return the note hash directly (no { note: … } wrapper); notes and folders return arrays. 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
end

Stable 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 ~/.zshrc

Or pass explicitly:

Freshjots::Client.new(token: "mn_…")

License

MIT.