Anvil Ruby
A Ruby gem for the Anvil API - the fastest way to build document workflows.
Anvil is a suite of tools for managing document workflows:
- 📝 PDF Filling - Fill PDF templates with JSON data
- 📄 PDF Generation - Generate PDFs from HTML/CSS or Markdown
- ✍️ E-signatures - Collect legally binding e-signatures
- 🔄 Webhooks - Real-time notifications for document events
Installation
Add this line to your application's Gemfile:
gem 'anvil-ruby'And then execute:
$ bundle installOr install it yourself:
$ gem install anvil-rubyFeature Status
Current coverage: ~30% of Anvil's API. See API_COVERAGE.md for detailed implementation status.
✅ Implemented
- PDF Operations - Fill templates, generate from HTML/Markdown
- E-Signatures (Basic) - Create packets, get signing URLs, track status
- Webhooks - Parse payloads, verify authenticity
- Core Infrastructure - Rate limiting, error handling, flexible configuration
🚧 Roadmap
Phase 1: Core Features (v0.2.0)
- Generic GraphQL support for custom queries
- Complete e-signature features (update, send, void packets)
- Basic workflow support (create, start workflows)
- Basic webform support (create forms, handle submissions)
Phase 2: Advanced Features (v0.3.0)
- Full workflow implementation with data management
- Full webform/Forge implementation
- Cast (PDF template) management
- Webhook management API
Phase 3: AI & Enterprise (v0.4.0)
- Document AI/OCR capabilities
- Organization management
- Embedded builders
- Advanced utilities
See our GitHub Projects for detailed progress tracking.
Quick Start
Configuration
Configure your API key (get one at Anvil Settings):
Rails (config/initializers/anvil.rb)
Anvil.configure do |config|
config.api_key = Rails.application.credentials.anvil[:api_key]
config.environment = Rails.env.production? ? :production : :development
endEnvironment Variable
export ANVIL_API_KEY="your_api_key_here"Direct Assignment
require 'anvil'
Anvil.api_key = "your_api_key_here"Usage
PDF Filling
Fill PDF templates with your data:
# Fill a PDF template
pdf = Anvil::PDF.fill(
template_id: "your_template_id",
data: {
name: "John Doe",
email: "john@example.com",
date: Date.today.strftime("%B %d, %Y")
}
)
# Save the filled PDF
pdf.save_as("contract.pdf")
# Get as base64 (for database storage)
base64_pdf = pdf.to_base64PDF Generation
Generate from HTML/CSS
pdf = Anvil::PDF.generate_from_html(
html: "<h1>Invoice #123</h1><p>Amount: $100</p>",
css: "h1 { color: blue; }",
title: "Invoice"
)
pdf.save_as("invoice.pdf")Generate from Markdown
pdf = Anvil::PDF.generate_from_markdown(
<<~MD
# Report
## Summary
This is a **markdown** document with:
- Bullet points
- *Italic text*
- [Links](https://anvil.com)
MD
)
pdf.save_as("report.pdf")E-Signatures
Create and manage e-signature packets:
# Create a signature packet
packet = Anvil::Signature.create(
name: "Employment Agreement",
signers: [
{
name: "John Doe",
email: "john@example.com",
role: "employee"
},
{
name: "Jane Smith",
email: "jane@company.com",
role: "manager"
}
],
files: [
{ type: :pdf, id: "template_id_here" }
]
)
# Get signing URL for a signer
signer = packet.signers.first
signing_url = signer.signing_url
# Check status
packet.reload!
if packet.complete?
puts "All signatures collected!"
endWebhooks
Handle webhook events from Anvil:
Rails Controller
class AnvilWebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def create
webhook = Anvil::Webhook.new(
payload: request.body.read,
token: params[:token]
)
if webhook.valid?
case webhook.action
when 'signerComplete'
handle_signer_complete(webhook.data)
when 'etchPacketComplete'
handle_packet_complete(webhook.data)
end
head :no_content
else
head :unauthorized
end
end
private
def handle_signer_complete(data)
# Process signer completion
SignerCompleteJob.perform_later(data)
end
def handle_packet_complete(data)
# All signatures collected
PacketCompleteJob.perform_later(data)
end
endSinatra/Rack
post '/webhooks/anvil' do
webhook = Anvil::Webhook.new(
payload: request.body.read,
token: params[:token]
)
halt 401 unless webhook.valid?
# Process webhook
case webhook.action
when 'signerComplete'
# Handle signer completion
end
status 204
endAdvanced Usage
Multi-tenant Applications
Use different API keys per request:
# Override API key for specific operations
pdf = Anvil::PDF.fill(
template_id: "template_123",
data: { name: "John" },
api_key: current_tenant.anvil_api_key
)
# Or create a custom client
client = Anvil::Client.new(api_key: tenant.api_key)
pdf = Anvil::PDF.new(client: client).fill(...)Error Handling
The gem provides specific error types for different scenarios:
begin
pdf = Anvil::PDF.fill(template_id: "123", data: {})
rescue Anvil::ValidationError => e
# Invalid data or parameters
puts "Validation failed: #{e.message}"
puts "Errors: #{e.errors}"
rescue Anvil::AuthenticationError => e
# Invalid or missing API key
puts "Auth failed: #{e.message}"
rescue Anvil::RateLimitError => e
# Rate limit exceeded
puts "Rate limited. Retry after: #{e.retry_after} seconds"
rescue Anvil::NotFoundError => e
# Resource not found
puts "Not found: #{e.message}"
rescue Anvil::NetworkError => e
# Network issues
puts "Network error: #{e.message}"
rescue Anvil::Error => e
# Generic Anvil error
puts "Error: #{e.message}"
endRate Limiting
The gem automatically handles rate limiting with exponential backoff:
# Configure custom retry behavior
client = Anvil::Client.new
client.rate_limiter = Anvil::RateLimiter.new(
max_retries: 5,
base_delay: 2.0
)Development Mode
Enable development mode for watermarked PDFs and debug output:
Anvil.configure do |config|
config.api_key = "your_dev_key"
config.environment = :development # Watermarks PDFs, verbose logging
endConfiguration Options
Anvil.configure do |config|
# Required
config.api_key = "your_api_key"
# Optional
config.environment = :production # :development or :production
config.base_url = "https://app.useanvil.com/api/v1" # API endpoint
config.timeout = 120 # Read timeout in seconds
config.open_timeout = 30 # Connection timeout
config.webhook_token = "your_webhook_token" # For webhook verification
endExamples
See the examples directory for complete working examples:
Development
After checking out the repo, run:
bundle install
bundle exec rspec # Run tests
bundle exec rubocop # Check code styleTo install this gem onto your local machine:
bundle exec rake installTesting
The gem uses RSpec for testing:
# Run all tests
bundle exec rspec
# Run specific test file
bundle exec rspec spec/anvil/pdf_spec.rb
# Run with coverage
bundle exec rspec --format documentationCI/CD with GitHub Actions
This project uses GitHub Actions for continuous integration and automated gem publishing.
Automated Workflows
- CI Pipeline - Runs tests, linting, and security checks on every push and PR
- Gem Publishing - Automatically publishes to RubyGems.org when you create a version tag
- Dependency Updates - Dependabot keeps dependencies up-to-date weekly
Quick Start
- Fork the repository
- Add your
RUBYGEM_API_KEYsecret to GitHub (Settings → Secrets) - Push your changes - CI will run automatically
- Create a version tag to publish:
git tag v0.2.0 && git push --tags
See .github/workflows/README.md for complete documentation on:
- Setting up secrets and authentication
- Running workflows manually
- Debugging CI failures
- Security best practices
- Customizing workflows
Philosophy
This gem embraces Ruby's philosophy of developer happiness:
- Zero runtime dependencies - Uses only Ruby's standard library
- Rails-friendly - Works great with Rails but doesn't require it
- Idiomatic Ruby - Follows Ruby conventions (predicates, bang methods, blocks)
- Progressive disclosure - Simple things are simple, complex things are possible
Contributing
- Fork it (https://github.com/nickMarz/Ruby-Anvil/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
Please make sure to:
- Add tests for new features
- Follow Ruby style guide (run
rubocop) - Update documentation
- Ensure all CI checks pass (tests, linting, security)
- Check the GitHub Actions tab to monitor your PR's build status
License
The gem is available as open source under the terms of the MIT License.
Support
Acknowledgments
Built with ❤️ by Ruby developers, for Ruby developers. Inspired by the elegance of Rails and the philosophy of Matz.
Special thanks to DHH and Matz for making Ruby a joy to work with.