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.
Install
Documentation
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://gateway.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://gateway.maton.ai/jobber/graphql
The gateway 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
The gateway automatically injects the X-JOBBER-GRAPHQL-VERSION header (currently 2025-04-16).
MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"
Getting Your API Key
1. Sign in or create an account at [maton.ai](https://maton.ai)
2. Go to [maton.ai/settings](https://maton.ai/settings)
3. Copy your API key
Connection Management
Manage your Jobber OAuth connections at https://ctrl.maton.ai.
List Connections
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://ctrl.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://ctrl.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://ctrl.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": "cc61da85-8bf7-4fbc-896b-4e4eb9a5aafd",
"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://ctrl.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://gateway.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', 'cc61da85-8bf7-4fbc-896b-4e4eb9a5aafd')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If omitted, the gateway uses the default (oldest) active connection.
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 created - -
JOB_COMPLETE- Job marked complete - -
QUOTE_CREATE- New quote created - -
QUOTE_APPROVAL- Quote approved - -
REQUEST_CREATE- New request created - -
INVOICE_CREATE- New invoice created - -
APP_CONNECT- App connected
Webhooks include HMAC-SHA256 signatures for verification.
Code Examples
JavaScript
const query = { clients(first: 10) { nodes { id name emails { address } } } };
const response = await fetch('https://gateway.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 requests
query = '''
{
clients(first: 10) {
nodes { id name emails { address } }
}
}
'''
response = requests.post(
'https://gateway.maton.ai/jobber/graphql',
headers={
'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}',
'Content-Type': 'application/json'
},
json={'query': query}
)
data = response.json()
Notes
- -Jobber uses GraphQL exclusively (no REST API)
- -The gateway automatically injects the
X-JOBBER-GRAPHQL-VERSIONheader - -Current gateway API version:
2025-04-16(latest) - -Old API versions are supported for 12-18 months from release
- -Use the GraphiQL explorer in Jobber's Developer Center for schema discovery
- -IDs use
EncodedIdtype (base64 encoded) - pass as strings - -Field naming: use
emails/phones(arrays),jobStatus/invoiceStatus/quoteStatus/requestStatus - -Rate limits:
- Query cost: Points-based using leaky bucket algorithm (max 10,000 points, restore 500/sec)
- -Avoid deeply nested queries to reduce query cost
- -IMPORTANT: When piping curl output to
jqor other commands, environment variables like$MATON_API_KEYmay not expand correctly in some shell environments
Error 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://ctrl.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:
- -Correct:
https://gateway.maton.ai/jobber/graphql - -Incorrect:
https://gateway.maton.ai/graphql
Resources
- -[Jobber Developer Documentation](https://developer.getjobber.com/docs/)
- -[Getting Started Guide](https://developer.getjobber.com/docs/getting_started/)
- -[API Support](mailto:api-support@getjobber.com)
- -[Maton Community](https://discord.com/invite/dBfFAcefs2)
- -[Maton Support](mailto:support@maton.ai)
Launch an agent with Jobber on Termo.