A framework-agnostic Ruby SDK for the Printavo GraphQL API (v2).
I use Printavo every day at Texas Embroidery Ranch. This gem was created to bridge Printavo with other operational systems—CRM, marketing, finance, and automation—so that print shops can build integrated workflows without writing raw GraphQL by hand.
Features
- Full Printavo v2 GraphQL API support
- Resource-oriented interface:
client.customers.all,client.orders.find(id) - Raw GraphQL access:
client.graphql.query("{ ... }") - Rich domain models:
order.status,order.status?(:in_production),order.customer - Rack-compatible webhook signature verification
- Multi-client support — no globals required
- Ruby 3.0+ required
Installation
Add to your Gemfile:
gem "printavo-ruby"or
bundle add printavo-rubyor install directly:
gem install printavo-rubyAuthentication
Printavo authenticates via your account email and API token (found at My Account → API Token on printavo.com).
require "printavo"
client = Printavo::Client.new(
email: ENV["PRINTAVO_EMAIL"],
token: ENV["PRINTAVO_TOKEN"]
)Rails Initializer
# config/initializers/printavo.rb
PRINTAVO = Printavo::Client.new(
email: ENV["PRINTAVO_EMAIL"],
token: ENV["PRINTAVO_TOKEN"]
)Usage
Customers
# List customers (25 per page by default)
customers = client.customers.all
customers.each { |c| puts "#{c.full_name} — #{c.email}" }
# Paginate
page_2 = client.customers.all(first: 25, after: cursor)
# Find a specific customer
customer = client.customers.find("12345")
puts customer.full_name # => "Jane Smith"
puts customer.company # => "Acme Shirts"Orders
# List orders
orders = client.orders.all
orders.each { |o| puts "#{o.nickname}: #{o.status}" }
# Find an order
order = client.orders.find("99")
puts order.status # => "In Production"
puts order.status_key # => :in_production
puts order.status?(:in_production) # => true
puts order.total_price # => "1250.00"
puts order.customer.full_name # => "Bob Johnson"Jobs (Line Items)
# List jobs for an order
jobs = client.jobs.all(order_id: "99")
jobs.each { |j| puts "#{j.name} x#{j.quantity} @ #{j.price}" }
# Find a specific job
job = client.jobs.find("77")
puts job.taxable? # => truePagination
All list resources support each_page and all_pages in addition to all.
# Iterate page by page (cursor-based, memory-efficient)
client.customers.each_page(first: 50) do |records|
records.each { |c| puts c.full_name }
end
# Collect every record across all pages into one array
all_orders = client.orders.all_pages
# Jobs require order_id
client.jobs.each_page(order_id: "99") do |records|
records.each { |j| puts j.name }
end
all_jobs = client.jobs.all_pages(order_id: "99")Mutations
Customers
# Create
customer = client.customers.create(
primary_contact: { firstName: "Jane", lastName: "Smith", email: "jane@example.com" },
company_name: "Acme Shirts"
)
puts customer.full_name # => "Jane Smith"
puts customer.company # => "Acme Shirts"
# Update
customer = client.customers.update("42", company_name: "New Name Inc")Orders
# Create (Printavo creates orders as quotes first)
order = client.orders.create(
contact: { id: "456" },
due_at: "2026-06-01T09:00:00Z",
customer_due_at: "2026-06-01",
nickname: "Summer Rush"
)
# Update
order = client.orders.update("99", nickname: "Rush Job", production_note: "Ships Friday")
# Move to a new status
registry = client.statuses.registry
order = client.orders.update_status("99", status_id: registry[:in_production].id)
puts order.status # => "In Production"Inquiries
# Create
inquiry = client.inquiries.create(
name: "Bob Johnson",
email: "bob@example.com",
request: "100 hoodies, front + back print"
)
# Update
inquiry = client.inquiries.update("55", nickname: "Hoodies Rush")Statuses
# List all statuses
statuses = client.statuses.all
statuses.each { |s| puts "#{s.name} (#{s.color})" }
# Build a registry for O(1) lookup by symbol key
registry = client.statuses.registry
registry[:in_production] # => <Printavo::Status>
registry[:in_production].color # => "#ff6600"
# Pair with an order's status_key
order = client.orders.find("99")
status = registry[order.status_key]
puts "#{order.status} — #{status.color}"Inquiries
# List inquiries (quotes / leads)
inquiries = client.inquiries.all
inquiries.each { |i| puts "#{i.nickname}: #{i.status}" }
# Find a specific inquiry
inquiry = client.inquiries.find("55")
puts inquiry.status?(:new_inquiry) # => true
puts inquiry.customer.full_name # => "Jane Smith"Raw GraphQL
For queries not yet wrapped by a resource, use the raw GraphQL client directly:
result = client.graphql.query(<<~GQL)
{
customers(first: 5) {
nodes {
id
firstName
lastName
}
}
}
GQL
result["customers"]["nodes"].each { |c| puts c["firstName"] }With variables:
result = client.graphql.query(
"query Customer($id: ID!) { customer(id: $id) { id email } }",
variables: { id: "42" }
)Mutations
Use mutate for GraphQL write operations:
result = client.graphql.mutate(
<<~GQL,
mutation UpdateOrder($id: ID!, $nickname: String!) {
updateOrder(id: $id, input: { nickname: $nickname }) {
order { id nickname }
errors
}
}
GQL
variables: { id: "99", nickname: "Rush Job" }
)
result["updateOrder"]["order"]["nickname"] # => "Rush Job"Raw GraphQL Pagination
Paginate any custom query without a resource wrapper using paginate.
The path: option is dot-separated and maps to the connection in the response:
client.graphql.paginate(MY_QUERY, path: "orders", first: 50) do |nodes|
nodes.each { |n| puts n["nickname"] }
end
# Nested connection (e.g. order.lineItems)
client.graphql.paginate(JOBS_QUERY, path: "order.lineItems",
variables: { orderId: "99" }) do |nodes|
nodes.each { |j| puts j["name"] }
endWebhooks
Printavo::Webhooks.verify provides Rack-compatible HMAC-SHA256 signature verification.
No extra dependencies required.
# Pure Ruby / Rack
valid = Printavo::Webhooks.verify(
signature, # X-Printavo-Signature header value
payload, # raw request body string
secret # your webhook secret
)Rails Controller Example
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def printavo
if Printavo::Webhooks.verify(
request.headers["X-Printavo-Signature"],
request.raw_post,
ENV["PRINTAVO_WEBHOOK_SECRET"]
)
event = JSON.parse(request.raw_post)
# process event["type"] ...
head :ok
else
head :unauthorized
end
end
endError Handling
begin
client.orders.find("not_a_real_id")
rescue Printavo::AuthenticationError => e
# Bad email/token
rescue Printavo::RateLimitError => e
# Exceeded 10 req/5 sec — back off and retry
rescue Printavo::NotFoundError => e
# Resource doesn't exist
rescue Printavo::ApiError => e
# GraphQL error — e.message contains details, e.response has raw data
rescue Printavo::Error => e
# Catch-all for any Printavo error
endVersioning Roadmap
- CHANGELOG — what shipped in each release
- TODO — planned versions, API coverage gaps, and stretch goals
Rules: PATCH = bug fix · MINOR = new backward-compatible feature · MAJOR = breaking change
API Documentation
Development
git clone https://github.com/scarver2/printavo-ruby.git
cd printavo-ruby
bundle install
# Run specs
bundle exec rspec
# Lint
bundle exec rubocop
# Guard DX (watches files, re-runs tests + lint on save)
bundle exec guard
# Interactive console
PRINTAVO_EMAIL=you@example.com PRINTAVO_TOKEN=your_token bin/consoleSee CONTRIBUTING.md for full contribution guidelines.
Colophon
©2026 Stan Carver II

