Access the Jobber API with managed OAuth authentication. Manage clients, jobs, invoices, quotes, properties, and team members for field service businesses.
# 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
Jobber uses a GraphQL API exclusively. All requests are POST requests to the /graphql endpoint with a JSON body containing the query field.
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.
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"
Manage your Jobber OAuth connections at https://api.maton.ai.
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
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
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.
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
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.
POST /jobber/graphql
Content-Type: application/json
{
"query": "{ account { id name } }"
}
POST /jobber/graphql
Content-Type: application/json
{
"query": "{ clients(first: 20) { nodes { id name emails { primary address } phones { primary number } } pageInfo { hasNextPage endCursor } } }"
}
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" }
}
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"
}
}
}
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"
}
}
}
POST /jobber/graphql
Content-Type: application/json
{
"query": "{ jobs(first: 20) { nodes { id title jobNumber jobStatus client { name } } pageInfo { hasNextPage endCursor } } }"
}
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" }
}
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"
}
}
}
POST /jobber/graphql
Content-Type: application/json
{
"query": "{ invoices(first: 20) { nodes { id invoiceNumber subject total invoiceStatus client { name } } pageInfo { hasNextPage endCursor } } }"
}
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" }
}
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
}
]
}
}
}
POST /jobber/graphql
Content-Type: application/json
{
"query": "{ quotes(first: 20) { nodes { id quoteNumber title quoteStatus client { name } } pageInfo { hasNextPage endCursor } } }"
}
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
}
]
}
}
}
POST /jobber/graphql
Content-Type: application/json
{
"query": "{ properties(first: 20) { nodes { id address { street city state postalCode } client { name } } pageInfo { hasNextPage endCursor } } }"
}
POST /jobber/graphql
Content-Type: application/json
{
"query": "{ requests(first: 20) { nodes { id title requestStatus client { name } } pageInfo { hasNextPage endCursor } } }"
}
POST /jobber/graphql
Content-Type: application/json
{
"query": "{ users(first: 50) { nodes { id name { full } email { raw } } } }"
}
POST /jobber/graphql
Content-Type: application/json
{
"query": "{ customFields(first: 50) { nodes { id name fieldType } } }"
}
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"
}
}
}
}
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.
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();
import os
import requests
query = '''
{
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()
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| 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"]
}
]
}
}
}
MATON_API_KEY environment variable is set:echo $MATON_API_KEY
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
jobber. For example:https://api.maton.ai/jobber/graphqlhttps://api.maton.ai/graphql共 2 个版本