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.1.1.tar.gz (200.6 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.1.1-py3-none-any.whl (14.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: jobber_python_client-0.1.1.tar.gz
  • Upload date:
  • Size: 200.6 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.1.1.tar.gz
Algorithm Hash digest
SHA256 b62e43c838ec936b611043fa8743b8f20378d6013ec1511681c2956d020eb099
MD5 e601dbfbc165ed26648c63bc4423489d
BLAKE2b-256 771ac4671378782e6d945e0d0326fe90b3928dcda75089da96bb31b59b3792d3

See more details on using hashes here.

File details

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

File metadata

  • Download URL: jobber_python_client-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 14.7 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.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9b37e6408bfee813da355100ae141951e8fdc741f718e7c088d076a8df9b791e
MD5 411b41328cc90f2775ebf506ab458491
BLAKE2b-256 d9528fae08351dbe7df0d5a400b1ea95015a78bf94653cb840ae59b7664a572d

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