Jobber API Ruby client

A client for the Jobber GraphQL API. It needs the standard library and two files of Active Support, to tell a field Jobber answered empty from one it never answered at all.

Available methods

Credentials

Generate the URL for Jobber users to authorize the app:

url = Jbr.oauth_url_for redirect_uri:, state:
url # => 'https://api.getjobber.com/api/oauth/authorize?state=...&redirect_uri=...'

Create credentials with a code and a redirect URI:

oauth = Jbr.create_oauth code:, redirect_uri:

Initialize with existing credentials:

oauth = Jbr.oauth_for access_token:, refresh_token:, expires_at:, account_id:

Access OAuth attributes:

oauth.access_token # => 'eyJhbGciOiJIUzI1NiJ'
oauth.refresh_token # => 'ea02775958c5fca28d'
oauth.expires_at # => 2026-05-22 14:32:53
oauth. # => 'Z2lkOi8vSm9iYmV'

Revoke credentials:

oauth.delete

Requests

Create a Jobber request, finding or creating a Client with a matching phone number:

request = oauth.requests.create first_name: 'Jane', last_name: 'Doe', phone: '5553335555',
  email: 'jane@example.com', title: 'New Plumber Lead', instructions: 'Needs new faucet'
request.id # => 'Z2lkOi8vSm9iYmVyL'
request.client_id # => 'MwMTU0Mg'

Quotes

Fetch a quote from Jobber:

quote = oauth.quotes.find 'Z2lkOi8vS'
quote.id # => 'Z2lkOi8vS'
quote.request_id # => 'Z2lkOi8vSm9iYmVyL'

Jobs

Fetch a job from Jobber by the ID it is filed under:

job = oauth.jobs.find 'Njc5MTk5'
job.id # => 'Z2lkOi8vS'
job.quote_id # => 'Z2lkOi8vS'
job.scheduled_at # => 2026-05-14 23:02:52
job.completed_at # => 2026-05-18 11:36:13

Or walk the account's jobs, oldest first. Jobber is asked for a page at a time, and only once the page before it runs out, so first costs one request where to_a costs as many as the account has pages:

jobs = oauth.jobs # => an Enumerable of every job, nothing fetched yet
oauth.jobs.past # => an Enumerator of the ones dated before now
oauth.jobs.upcoming # => an Enumerator of the ones dated from now on

job = jobs.first
job.name # => 'Furnace tune-up', or the job's ID where nobody titled it. Never nil or empty
job.title # => 'Furnace tune-up'
job.instructions # => 'Ring the doorbell twice'
job.status # => 'requires_invoicing'
job.total # => 260.0
job.quote_total # => 240.0
job.created_at # => 2026-05-10 09:15:00

Invoices

Fetch a non-draft invoice from Jobber:

invoice = oauth.invoices.find 'MjU3ODA0'
invoice.id # => 'MjU3ODA0'
invoice.job_id # => 'Z2lkOi8vS'
invoice.total # => '40.30'
invoice.issued_at # => 2026-05-22 12:12:53
invoice.completed_at # => 2026-05-22 14:32:53

Visits

Walk the account's visits, oldest first, the same way as its jobs:

visits = oauth.visits # => an Enumerable of every visit, nothing fetched yet
oauth.visits.upcoming # => an Enumerator of the ones dated from now on
oauth.visits.past # => an Enumerator of the ones dated before now

visit = visits.first
visit.id # => 'Z2lkOi8vS'
visit.name # => 'Furnace tune-up', or the visit's ID where nobody titled it. Never nil or empty
visit.title # => 'Furnace tune-up'
visit.job_id # => 'Z2lkOi8vS'
visit.starts_at # => 2026-08-09 14:00:00
visit.ends_at # => 2026-08-09 16:00:00
visit.all_day? # => false
visit.client_confirmed? # => true

Clients and properties

Jobber prices a query by what it brings back, so nothing nested comes back unless it is asked for. Chain includes the way Active Record does, on visits or on jobs:

visit = oauth.visits.includes(:client, property: :client).upcoming.first

visit.client.name # => 'Jane', or the business's name where the client is a business.
                  # Never an empty string: a blank first name falls through to the company
visit.client.first_name # => 'Jane'
visit.client.last_name # => 'Doe'
visit.client.company_name # => nil
visit.client.email # => 'jane@example.com'
visit.client.phone # => '5553335555', the reachable North American number, or nil

visit.property.id # => 'Z2lkOi8vS'
visit.property.street # => '1 Main St'
visit.property.city # => 'Raleigh'
visit.property.zip # => '27601'
visit.property.address # => { street: '1 Main St', city: 'Raleigh', state: 'NC',
                       #      zip: '27601', latitude: 35.77, longitude: -78.63 }
visit.property.client.name # => whoever the place sits on the file of

Ask for nothing and nothing arrives: oauth.visits.first.client.name is nil where the query never named a client.

Events

Parse the payload of a Jobber event webhook:

event = Jbr::Event.new data: { webHookEvent: { topic: 'JOB_CREATE', appId: 'app-1',
  accountId: 'account-1', itemId: 'job-1', occurredAt: '2026-05-22T15:46:33Z' } }
event. # => 'account-1'
event.item_id # => 'job-1'

Available mocks

Use these methods to mock request to Jobber when testing an app:

Credentials

Mock successfully creating and revoking credentials:

Jbr.mock

Mock an error when creating credentials:

Jbr.mock.oauth_error = 'Flow rejected'

Mock a custom redirect URL:

Jbr.mock.oauth_url = 'https://example.com'

Requests

Mock successfully creating a request:

Jbr.mock.request = { id: 'request-01', client_id: 'client-01' }

Quotes

Mock successfully fetching a quote:

Jbr.mock.quote = { id: 'quote-01', request_id: 'request-01' }

Jobs

Mock successfully fetching a job by ID:

Jbr.mock.job = { id: 'job-01', quote_id: 'quote-01', scheduled_at: Date.tomorrow.noon }

Mock the jobs the account has. The mock dates nothing it was handed: what answers to past and to upcoming is whatever scheduled_at the app gave each one:

Jbr.mock.jobs = [ { id: 'job-01', title: 'Furnace tune-up', status: 'archived',
  total: 260.0, quote_total: 240.0, created_at: Date.yesterday.noon,
  scheduled_at: Date.yesterday.noon, completed_at: Date.today.noon,
  property: { id: 'property-01', street: '1 Main St',
    client: { id: 'client-01', company_name: 'Acme Property Management' } } } ]

Visits

Mock the visits the account has:

Jbr.mock.visits = [ { id: 'visit-01', title: 'Furnace tune-up', job_id: 'job-01',
  property: { id: 'property-01', street: '1 Main St',
    client: { id: 'client-01', first_name: 'Jane' } },
  client: { id: 'client-01', first_name: 'Jane' },
  starts_at: Date.tomorrow.noon, ends_at: Date.tomorrow.end_of_day,
  all_day: false, client_confirmed: true } ]

Invoices

Mock successfully fetching an invoice:

Jbr.mock.invoice = { id: 'invoice-01', job_id: 'job-01', total: 19.99, issued_at: Date.yesterday.noon }