EposNowClient
A lightweight, fully-tested Ruby client for the Epos Now REST API (V4).
131 specs | 100% line coverage | 100% branch coverage | 0 RuboCop offenses
Features
- Basic Auth with API Key + Secret (Base64-encoded)
- 10 resource classes: Products, Categories, Transactions, TenderTypes, TaxGroups, Customers, Staff, Devices, Brands, Locations
- Automatic pagination (200 items/page, fetches all pages)
- Retry with exponential backoff on transient network errors
- Structured error hierarchy for clean exception handling
- Search & filtering using Epos Now query syntax
- Zero runtime dependencies beyond HTTParty
Installation
Add to your Gemfile:
gem 'epos_now_client'Then run:
bundle installOr install directly:
gem install epos_now_clientQuick Start
require 'epos_now_client'
client = EposNowClient::Client.new(
api_key: 'your_api_key',
api_secret: 'your_api_secret'
)
# Fetch all products
products = client.products.all
# Create a category
client.categories.create_category({ 'Name' => 'Drinks' })
# Search products
client.products.search('(Name|contains|Burger)')Authentication
Epos Now uses Basic Auth with an API Key and Secret. Get your credentials from the Epos Now Backoffice:
- Navigate to Web Integrations > REST API
- Click Add Device
- Copy the API Key and API Secret
Note: Epos Now uses a single API endpoint (
https://api.eposnowhq.com) for all environments. There is no separate sandbox URL. Developer accounts use the same API with test credentials.
Configuration
Global (recommended for single-account apps)
EposNowClient.configure do |config|
config.api_key = ENV['EPOS_NOW_API_KEY']
config.api_secret = ENV['EPOS_NOW_API_SECRET']
config.base_url = 'https://api.eposnowhq.com' # default
config.timeout = 30 # default (seconds)
config.open_timeout = 10 # default (seconds)
config.max_retries = 3 # default
config.retry_delay = 1.0 # default (seconds, doubles each retry)
config.per_page = 200 # default
config.logger = Logger.new($stdout) # optional
end
client = EposNowClient::Client.newPer-client (for multi-account apps)
client = EposNowClient::Client.new(
api_key: 'merchant_api_key',
api_secret: 'merchant_api_secret',
timeout: 60
)Rails initializer
# config/initializers/epos_now.rb
EposNowClient.configure do |config|
config.api_key = Rails.application.credentials.dig(:epos_now, :api_key)
config.api_secret = Rails.application.credentials.dig(:epos_now, :api_secret)
config.logger = Rails.logger
endUsage
Categories
client.categories.all # List all (paginated)
client.categories.find(42) # Get by ID
client.categories.create_category({ 'Name' => 'Drinks' }) # Create
client.categories.update_category(42, { 'Name' => 'Bev' }) # Update
client.categories.delete_category(42) # DeleteProducts
client.products.all # List all
client.products.find(10) # Get by ID
client.products.create_product({ # Create
'Name' => 'Burger',
'SalePrice' => 9.99,
'CategoryId' => 1
})
client.products.update_product(10, { 'SalePrice' => 11.99 }) # Update
client.products.delete_product(10) # Delete
client.products.search('(Name|contains|Burg)') # SearchTransactions
client.transactions.all # List all
client.transactions.find(100) # Get by ID
client.transactions.by_date( # By date range
start_date: '2026-03-01',
end_date: '2026-03-13'
)
client.transactions.latest # Latest
client.transactions.create_transaction({ # Create
'TransactionItems' => [{ 'ProductId' => 1, 'Quantity' => 2 }],
'Tenders' => [{ 'TenderTypeId' => 1, 'Amount' => 25.50 }],
'ServiceType' => 0 # 0=EatIn, 1=Takeaway, 2=Delivery
})
client.transactions.delete_transaction(200) # Delete
client.transactions.validate(payload) # ValidateTender Types
client.tender_types.all
client.tender_types.find(1)
client.tender_types.create_tender_type({ 'Name' => 'Gift Card' })Tax Groups
client.tax_groups.all
client.tax_groups.find(1)Customers
client.customers.all
client.customers.find(1)
client.customers.create_customer({ 'FirstName' => 'Jane', 'LastName' => 'Doe' })
client.customers.update_customer(1, { 'FirstName' => 'Janet' })
client.customers.delete_customer(1)
client.customers.search('(FirstName|contains|Jane)')Staff
client.staff.all
client.staff.find(1)
client.staff.create_staff({ 'FirstName' => 'Bob', 'Pin' => '1234' })
client.staff.update_staff(1, { 'FirstName' => 'Robert' })
client.staff.delete_staff(1)Devices
client.devices.all
client.devices.find(1)Brands
client.brands.all
client.brands.find(1)
client.brands.create_brand({ 'Name' => 'Coca-Cola' })
client.brands.update_brand(1, { 'Name' => 'PepsiCo' })
client.brands.delete_brand(1)Locations
client.locations.all
client.locations.find(1)Pagination
All .all methods automatically paginate through every page (200 items per page):
# Fetches ALL products, regardless of how many pages exist
all_products = client.products.allSearch & Filtering
Epos Now supports search filters using the syntax (PropertyName|Operator|Value):
# Available operators: contains, StartsWith, EndsWith, >, >=, <, <=, like
client.products.search('(Name|contains|Burger)')
client.customers.search('(FirstName|StartsWith|J)')
client.products.search('(SalePrice|>|10.00)')Error Handling
All errors inherit from EposNowClient::Error and include status and body attributes:
begin
client.products.find(999)
rescue EposNowClient::AuthenticationError => e
# 401 - Invalid API key or secret
rescue EposNowClient::NotFoundError => e
# 404 - Resource not found
rescue EposNowClient::ValidationError => e
# 422 - Invalid request payload
rescue EposNowClient::RateLimitError => e
# 429 - API rate limit exceeded
rescue EposNowClient::ServerError => e
# 500-599 - Epos Now server error
rescue EposNowClient::ConnectionError => e
# Network errors (after all retries exhausted)
rescue EposNowClient::Error => e
# Catch-all
e.status # HTTP status code (Integer or nil)
e.body # Parsed response body (Hash, String, or nil)
e.message # Human-readable error message
endRetry Logic
The client automatically retries on transient network errors with exponential backoff:
-
Errno::ECONNRESET,Errno::ECONNREFUSED,Errno::ETIMEDOUT -
Net::OpenTimeout,Net::ReadTimeout SocketError
EposNowClient.configure do |config|
config.max_retries = 3 # Retry up to 3 times (default)
config.retry_delay = 1.0 # 1s, 2s, 4s backoff (default)
endAPI Reference
| Resource | Methods |
|---|---|
client.categories |
all, find, create_category, update_category, delete_category
|
client.products |
all, find, create_product, update_product, delete_product, search
|
client.transactions |
all, find, create_transaction, delete_transaction, by_date, latest, validate
|
client.tender_types |
all, find, create_tender_type
|
client.tax_groups |
all, find
|
client.customers |
all, find, create_customer, update_customer, delete_customer, search
|
client.staff |
all, find, create_staff, update_staff, delete_staff
|
client.devices |
all, find
|
client.brands |
all, find, create_brand, update_brand, delete_brand
|
client.locations |
all, find
|
Development
git clone https://github.com/dan1d/epos_now_client.git
cd epos_now_client
bundle install
# Run the full suite
bundle exec rake # RuboCop + RSpec
# Or individually
bundle exec rspec # 131 specs, 100% line + branch coverage
bundle exec rubocop # 0 offenses
# View coverage report
open coverage/index.htmlContributing
- Fork it
- Create your feature branch (
git checkout -b feature/my-feature) - Write tests first (TDD)
- Ensure 100% coverage (
bundle exec rspec) - Ensure 0 RuboCop offenses (
bundle exec rubocop) - Commit your changes (
git commit -m 'Add my feature') - Push to the branch (
git push origin feature/my-feature) - Create a Pull Request
License
MIT License. See LICENSE.txt for details.