Jobber
by @byungkyu
Jobber API integration with managed OAuth. Manage clients, jobs, invoices, quotes, properties, and team members for field service businesses. Use this skill...
clawhub install jobberπ About This Skill
name: jobber description: | Jobber API integration with managed OAuth. Manage clients, jobs, invoices, quotes, properties, and team members for field service businesses. Use this skill when users want to create and manage service jobs, clients, quotes, invoices, or access scheduling data. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway). Requires network access and valid Maton API key. metadata: author: maton version: "1.0" clawdbot: emoji: π§ requires: env: - MATON_API_KEY
Jobber
Access the Jobber API with managed OAuth authentication. Manage clients, jobs, invoices, quotes, properties, and team members for field service businesses.
Quick Start
# Get account information
python <<'EOF'
import urllib.request, os, json
query = '{"query": "{ account { id name } }"}'
req = urllib.request.Request('https://api.maton.ai/jobber/graphql', data=query.encode(), method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
API Type
Jobber uses a GraphQL API exclusively. All requests are POST requests to the /graphql endpoint with a JSON body containing the query field.
Base URL
https://api.maton.ai/jobber/graphql
Maton proxies requests to api.getjobber.com/api/graphql and automatically injects your OAuth token and API version header.
Authentication
All requests require the Maton API key in the Authorization header:
Authorization: Bearer $MATON_API_KEY
Maton automatically injects the X-JOBBER-GRAPHQL-VERSION header (currently 2025-04-16).
Environment Variable: Set your API key as MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"
Getting Your API Key
1. Sign in or create an account at maton.ai 2. Go to maton.ai/settings 3. Copy your API key
Connection Management
Manage your Jobber OAuth connections at https://api.maton.ai.
List Connections
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections?app=jobber&status=ACTIVE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Create Connection
python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'jobber'}).encode()
req = urllib.request.Request('https://api.maton.ai/connections', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Get Connection
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
{
"connection": {
"connection_id": "{connection_id}",
"status": "ACTIVE",
"creation_time": "2026-02-07T09:29:19.946291Z",
"last_updated_time": "2026-02-07T09:30:59.990084Z",
"url": "https://connect.maton.ai/?session_token=...",
"app": "jobber",
"metadata": {}
}
}
Open the returned url in a browser to complete OAuth authorization.
Delete Connection
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Specifying Connection
If you have multiple Jobber connections, specify which one to use with the Maton-Connection header:
python <<'EOF'
import urllib.request, os, json
query = '{"query": "{ account { id name } }"}'
req = urllib.request.Request('https://api.maton.ai/jobber/graphql', data=query.encode(), method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
req.add_header('Maton-Connection', '{connection_id}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple connections, always include this header to ensure requests go to the intended account.
Security & Permissions
API Reference
Account Operations
#### Get Account Information
POST /jobber/graphql
Content-Type: application/json{
"query": "{ account { id name } }"
}
Client Operations
#### List Clients
POST /jobber/graphql
Content-Type: application/json{
"query": "{ clients(first: 20) { nodes { id name emails { primary address } phones { primary number } } pageInfo { hasNextPage endCursor } } }"
}
#### Get Client by ID
POST /jobber/graphql
Content-Type: application/json{
"query": "query($id: EncodedId!) { client(id: $id) { id name emails { primary address } phones { primary number } billingAddress { street city } } }",
"variables": { "id": "CLIENT_ID" }
}
#### Create Client
POST /jobber/graphql
Content-Type: application/json{
"query": "mutation($input: ClientCreateInput!) { clientCreate(input: $input) { client { id name } userErrors { message path } } }",
"variables": {
"input": {
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com",
"phone": "555-1234"
}
}
}
#### Update Client
POST /jobber/graphql
Content-Type: application/json{
"query": "mutation($id: EncodedId!, $input: ClientUpdateInput!) { clientUpdate(clientId: $id, input: $input) { client { id name } userErrors { message path } } }",
"variables": {
"id": "CLIENT_ID",
"input": {
"email": "newemail@example.com"
}
}
}
Job Operations
#### List Jobs
POST /jobber/graphql
Content-Type: application/json{
"query": "{ jobs(first: 20) { nodes { id title jobNumber jobStatus client { name } } pageInfo { hasNextPage endCursor } } }"
}
#### Get Job by ID
POST /jobber/graphql
Content-Type: application/json{
"query": "query($id: EncodedId!) { job(id: $id) { id title jobNumber jobStatus instructions client { name } property { address { street city } } } }",
"variables": { "id": "JOB_ID" }
}
#### Create Job
POST /jobber/graphql
Content-Type: application/json{
"query": "mutation($input: JobCreateInput!) { jobCreate(input: $input) { job { id jobNumber title } userErrors { message path } } }",
"variables": {
"input": {
"clientId": "CLIENT_ID",
"title": "Lawn Maintenance",
"instructions": "Weekly lawn care service"
}
}
}
Invoice Operations
#### List Invoices
POST /jobber/graphql
Content-Type: application/json{
"query": "{ invoices(first: 20) { nodes { id invoiceNumber subject total invoiceStatus client { name } } pageInfo { hasNextPage endCursor } } }"
}
#### Get Invoice by ID
POST /jobber/graphql
Content-Type: application/json{
"query": "query($id: EncodedId!) { invoice(id: $id) { id invoiceNumber subject total amountDue invoiceStatus lineItems { nodes { name quantity unitPrice } } } }",
"variables": { "id": "INVOICE_ID" }
}
#### Create Invoice
POST /jobber/graphql
Content-Type: application/json{
"query": "mutation($input: InvoiceCreateInput!) { invoiceCreate(input: $input) { invoice { id invoiceNumber } userErrors { message path } } }",
"variables": {
"input": {
"clientId": "CLIENT_ID",
"subject": "Service Invoice",
"lineItems": [
{
"name": "Lawn Care",
"quantity": 1,
"unitPrice": 75.00
}
]
}
}
}
Quote Operations
#### List Quotes
POST /jobber/graphql
Content-Type: application/json{
"query": "{ quotes(first: 20) { nodes { id quoteNumber title quoteStatus client { name } } pageInfo { hasNextPage endCursor } } }"
}
#### Create Quote
POST /jobber/graphql
Content-Type: application/json{
"query": "mutation($input: QuoteCreateInput!) { quoteCreate(input: $input) { quote { id quoteNumber } userErrors { message path } } }",
"variables": {
"input": {
"clientId": "CLIENT_ID",
"title": "Landscaping Quote",
"lineItems": [
{
"name": "Garden Design",
"quantity": 1,
"unitPrice": 500.00
}
]
}
}
}
Property Operations
#### List Properties
POST /jobber/graphql
Content-Type: application/json{
"query": "{ properties(first: 20) { nodes { id address { street city state postalCode } client { name } } pageInfo { hasNextPage endCursor } } }"
}
Request Operations
#### List Requests
POST /jobber/graphql
Content-Type: application/json{
"query": "{ requests(first: 20) { nodes { id title requestStatus client { name } } pageInfo { hasNextPage endCursor } } }"
}
User/Team Operations
#### List Users
POST /jobber/graphql
Content-Type: application/json{
"query": "{ users(first: 50) { nodes { id name { full } email { raw } } } }"
}
Custom Field Operations
#### List Custom Fields
POST /jobber/graphql
Content-Type: application/json{
"query": "{ customFields(first: 50) { nodes { id name fieldType } } }"
}
Pagination
Jobber uses Relay-style cursor-based pagination:
# First page
POST /jobber/graphql
{
"query": "{ clients(first: 20) { nodes { id name } pageInfo { hasNextPage endCursor } } }"
}Next page using cursor
POST /jobber/graphql
{
"query": "{ clients(first: 20, after: \"CURSOR_VALUE\") { nodes { id name } pageInfo { hasNextPage endCursor } } }"
}
Response includes pageInfo:
{
"data": {
"clients": {
"nodes": [...],
"pageInfo": {
"hasNextPage": true,
"endCursor": "abc123"
}
}
}
}
Webhooks
Jobber supports webhooks for real-time event notifications:
CLIENT_CREATE - New client createdJOB_COMPLETE - Job marked completeQUOTE_CREATE - New quote createdQUOTE_APPROVAL - Quote approvedREQUEST_CREATE - New request createdINVOICE_CREATE - New invoice createdAPP_CONNECT - App connectedWebhooks include HMAC-SHA256 signatures for verification.
Code Examples
JavaScript
const query = { clients(first: 10) { nodes { id name emails { address } } } };const response = await fetch('https://api.maton.ai/jobber/graphql', {
method: 'POST',
headers: {
'Authorization': Bearer ${process.env.MATON_API_KEY},
'Content-Type': 'application/json'
},
body: JSON.stringify({ query })
});
const data = await response.json();
Python
import os
import requestsquery = '''
{
clients(first: 10) {
nodes { id name emails { address } }
}
}
'''
response = requests.post(
'https://api.maton.ai/jobber/graphql',
headers={
'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}',
'Content-Type': 'application/json'
},
json={'query': query}
)
data = response.json()
Notes
X-JOBBER-GRAPHQL-VERSION header2025-04-16 (latest)EncodedId type (base64 encoded) - pass as stringsemails/phones (arrays), jobStatus/invoiceStatus/quoteStatus/requestStatusjq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environmentsError Handling
| Status | Meaning | |--------|---------| | 400 | Missing Jobber connection or malformed query | | 401 | Invalid or missing Maton API key | | 403 | Not authorized (check OAuth scopes) | | 429 | Rate limited | | 4xx/5xx | Passthrough error from Jobber API |
GraphQL errors appear in the response body:
{
"errors": [
{
"message": "Error description",
"locations": [...],
"path": [...]
}
]
}
Mutation errors appear in userErrors:
{
"data": {
"clientCreate": {
"client": null,
"userErrors": [
{
"message": "Email is required",
"path": ["input", "email"]
}
]
}
}
}
Troubleshooting: API Key Issues
1. Check that the MATON_API_KEY environment variable is set:
echo $MATON_API_KEY
2. Verify the API key is valid by listing connections:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Troubleshooting: Invalid App Name
1. Ensure your URL path starts with jobber. For example:
https://api.maton.ai/jobber/graphqlhttps://api.maton.ai/graphqlResources
π‘ Examples
# Get account information
python <<'EOF'
import urllib.request, os, json
query = '{"query": "{ account { id name } }"}'
req = urllib.request.Request('https://api.maton.ai/jobber/graphql', data=query.encode(), method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
π Tips & Best Practices
X-JOBBER-GRAPHQL-VERSION header2025-04-16 (latest)EncodedId type (base64 encoded) - pass as stringsemails/phones (arrays), jobStatus/invoiceStatus/quoteStatus/requestStatusjq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environments