Skip to main content

Minimal Python client for Jobber GraphQL API with OAuth 2.0 support

Project description

Jobber Python Client

Minimal Python client for Jobber GraphQL API with OAuth 2.0 support.

Features

  • One-time authentication: Browser-based OAuth flow stores tokens in Doppler
  • Visual confirmation URLs: Get web links to verify API operations in Jobber UI (Quick Win Guide)
  • Fail-fast errors: All failures raise exceptions with context
  • Token auto-refresh: Transparent token refresh before expiration
  • Rate limit awareness: Exposes throttle status, raises before exceeding limits
  • Minimal dependencies: Only requests and oauthlib required

Installation

# Install from PyPI
pip install jobber-python-client

# Or clone repository for development
git clone https://github.com/tainora/jobber-python-client.git
cd jobber-python-client
uv sync

Prerequisites

  1. Jobber Developer Account: Create app at https://developer.getjobber.com/
  2. Doppler CLI: Install from https://docs.doppler.com/docs/install-cli
  3. OAuth Credentials: Store in Doppler:
    doppler secrets set JOBBER_CLIENT_ID="your_client_id" \
      JOBBER_CLIENT_SECRET="your_client_secret" \
      --project claude-config --config dev
    

Quick Start

Step 1: Authenticate (one-time)

uv run jobber_auth.py

This will:

  1. Open browser for Jobber authorization
  2. Exchange authorization code for tokens
  3. Store tokens in Doppler

Step 2: Use in AI agent code

from jobber import JobberClient

# Create client (loads credentials from Doppler)
client = JobberClient.from_doppler()

# Execute GraphQL queries
result = client.execute_query("""
    query {
        clients(first: 10) {
            nodes {
                id
                firstName
                lastName
            }
            totalCount
        }
    }
""")

print(f"Total clients: {result['clients']['totalCount']}")

Visual Confirmation URLs (Quick Win!)

Problem: API operations feel abstract. You create a client via API, but can you see it in Jobber's web interface?

Solution: YES! Include jobberWebUri in your queries to get clickable web links.

Example: Create Client with Web Link

from jobber import JobberClient

client = JobberClient.from_doppler()

# IMPORTANT: Include jobberWebUri in mutation response
mutation = """
    mutation CreateClient($input: ClientCreate!) {
        clientCreate(input: $input) {
            client {
                id
                firstName
                lastName
                jobberWebUri  # ← Returns web URL!
            }
        }
    }
"""

result = client.execute_query(mutation, {
    'input': {
        'firstName': 'John',
        'lastName': 'Doe'
    }
})

created = result['clientCreate']['client']

# Show clickable link for visual confirmation
print(f"✅ Client created: {created['firstName']} {created['lastName']}")
print(f"🔗 View in Jobber: {created['jobberWebUri']}")
# Click link → See client in Jobber web UI → Instant verification!

Available URL Fields

Field Available On Purpose
jobberWebUri Most resources (Client, Job, Quote, Invoice) Direct link to resource in Jobber web UI
previewUrl Quotes Client Hub link for customer approval

Quick Start

Run the complete example:

uv run examples/visual_confirmation_urls.py

Learn more: Visual Confirmation URLs Guide - Comprehensive patterns, best practices, and use cases.

Pro tip: ALWAYS include jobberWebUri in mutations for instant visual verification!

API Reference

JobberClient

Main client for executing GraphQL queries.

JobberClient.from_doppler(project, config)

Create client loading credentials from Doppler.

Parameters:

  • project (str): Doppler project name (default: "claude-config")
  • config (str): Doppler config name (default: "dev")

Returns: JobberClient instance

Raises:

  • ConfigurationError: Doppler secrets not found
  • AuthenticationError: Token loading fails

Example:

client = JobberClient.from_doppler("claude-config", "dev")

client.execute_query(query, variables=None, operation_name=None)

Execute GraphQL query.

Parameters:

  • query (str): GraphQL query string
  • variables (dict, optional): Query variables
  • operation_name (str, optional): Operation name for multi-operation queries

Returns: dict - Response data (response['data'])

Raises:

  • AuthenticationError: Token invalid or expired
  • RateLimitError: Rate limit threshold exceeded (< 20% points available)
  • GraphQLError: Query execution failed
  • NetworkError: HTTP request failed

Example:

result = client.execute_query(
    query="""
        query GetClients($first: Int!) {
            clients(first: $first) {
                nodes { id firstName }
            }
        }
    """,
    variables={'first': 50}
)

client.get_throttle_status()

Get last known rate limit status.

Returns: dict or None

  • currentlyAvailable: Points available now
  • maximumAvailable: Total capacity (typically 10,000)
  • restoreRate: Points restored per second (typically 500)

Example:

status = client.get_throttle_status()
if status:
    print(f"{status['currentlyAvailable']} points available")

Error Handling

All methods raise exceptions on failure. Handle appropriately:

from jobber import (
    JobberClient,
    AuthenticationError,
    RateLimitError,
    GraphQLError,
    NetworkError,
)

try:
    client = JobberClient.from_doppler()
    result = client.execute_query("{ clients { totalCount } }")

except AuthenticationError as e:
    # Resolution: Run jobber_auth.py
    print(f"Auth error: {e}")

except RateLimitError as e:
    # Resolution: Wait for points to restore
    wait_seconds = e.context.get('wait_seconds', 0)
    print(f"Rate limited. Wait {wait_seconds:.1f}s")

except GraphQLError as e:
    # Resolution: Check query syntax
    print(f"Query error: {e.errors}")

except NetworkError as e:
    # Resolution: Check connectivity
    print(f"Network error: {e}")

Examples

See examples/ directory:

Run examples:

uv run --with . examples/basic_usage.py

Architecture

See ADR-0001: Jobber API Client Architecture for design decisions.

Key Principles

  1. Fail-fast: Raise exceptions immediately, no retry or fallback
  2. Caller control: AI agent decides error recovery strategy
  3. Minimal abstraction: Thin wrapper over GraphQL HTTP requests
  4. Explicit configuration: No default values or silent assumptions

Module Structure

jobber/
├── __init__.py       # Public API exports
├── client.py         # JobberClient class
├── auth.py           # TokenManager (Doppler integration)
├── graphql.py        # GraphQLExecutor (HTTP requests)
└── exceptions.py     # Exception hierarchy

Rate Limiting

Jobber API limits:

  • 10,000 points available
  • 500 points/second restore rate
  • Query costs vary (9-50 points typical)

This library:

  • Raises RateLimitError when available points < 20% of maximum
  • Exposes throttle_status in exception context
  • Caller decides when to wait or abort

Token Management

Tokens expire after 60 minutes. This library:

  • Proactively refreshes 5 minutes before expiration (background thread)
  • Reactively refreshes on 401 errors (retry once)
  • Updates Doppler with new tokens automatically

Token storage in Doppler:

JOBBER_ACCESS_TOKEN=eyJhbGc...
JOBBER_REFRESH_TOKEN=def502...
JOBBER_TOKEN_EXPIRES_AT=1731873600

SLOs

  • Availability: Simple code paths, minimal failure modes
  • Correctness: Type-safe responses, validation at boundaries
  • Observability: Exceptions include full context and stack traces
  • Maintainability: < 500 LOC core library, clear contracts

Development

Running Tests

pytest -v

Type Checking

mypy jobber/

Linting

ruff check jobber/

Contributing

This project uses Conventional Commits and semantic-release.

Commit format:

feat: add custom field support
fix: handle token refresh race condition
docs: update error handling examples

License

MIT

Support

Changelog

See CHANGELOG.md (auto-generated by semantic-release).

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jobber_python_client-0.2.1.tar.gz (268.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jobber_python_client-0.2.1-py3-none-any.whl (21.6 kB view details)

Uploaded Python 3

File details

Details for the file jobber_python_client-0.2.1.tar.gz.

File metadata

  • Download URL: jobber_python_client-0.2.1.tar.gz
  • Upload date:
  • Size: 268.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.11 {"installer":{"name":"uv","version":"0.9.11"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for jobber_python_client-0.2.1.tar.gz
Algorithm Hash digest
SHA256 f9af5a344779040480013bc95b76ec52f8848e657673c9559f6c84452b5df162
MD5 40bddd17cc62bcb1d346e30f1e1c5dc4
BLAKE2b-256 f2d9f49c6ba647ef279191cfbafd9f097c365284195e5bc9ca8aaeb4d51413cb

See more details on using hashes here.

File details

Details for the file jobber_python_client-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: jobber_python_client-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 21.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.11 {"installer":{"name":"uv","version":"0.9.11"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for jobber_python_client-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 442ca5fb4d17cbb6945f7fdfcca81787ec53f79957dd1829d2157990b522c41c
MD5 bb1aa96674aa93d1fa3dc3ce6e3372a7
BLAKE2b-256 a254e19ad748e3136b1069e5f91b96aa10e6d79b0f5927b83dd462ad767140b0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page