Introduction
# Jobber
Access the Jobber API with managed OAuth authentication. Manage clients, jobs, invoices, quotes, properties, and team members for field service businesses.
## Quick Start
```bash # 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`).
**Environment Variable:** Set your API key as `MATON_API_KEY`:
```bash 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
```bash 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
```bash 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
```bash 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:** ```json { "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
```bash 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:
```bash 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
```bash POST /jobber/graphql Content-Type: application/json
{ "query": "{ account { id name } }" } ```
### Client Operations
#### List Clients
```bash 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
```bash 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
```bash 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": "[email protected]", "phone": "555-1234" } } } ```
#### Update Client
```bash 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": "[email protected]" } } } ```
### Job Operations
#### List Jobs
```bash 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
```bash 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
```bash 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
```bash 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
```bash 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
```bash 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
```bash POST /jobber/graphql Content-Type: application/json
{ "query": "{ quotes(first: 20) { nodes { id quoteNumber title quoteStatus client { name } } pageInfo { hasNextPage endCursor } } }" } ```
#### Create Quote
```bash 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
```bash 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
```bash 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
```bash POST /jobber/graphql Content-Type: application/json
{ "query": "{ users(first: 50) { nodes { id name { full } email { raw } } } }" } ```
### Custom Field Operations
#### List Custom Fields
```bash POST /jobber/graphql Content-Type: application/json
{ "query": "{ customFields(first: 50) { nodes { id name fieldType } } }" } ```
## Pagination
Jobber uses Relay-style cursor-based pagination:
```bash # 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`: ```json { "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
```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
```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-VERSION` header - 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 `EncodedId` type (base64 encoded) - pass as strings - Field naming: use `emails`/`phones` (arrays), `jobStatus`/`invoiceStatus`/`quoteStatus`/`requestStatus` - Rate limits: - DDoS protection: 2,500 requests per 5 minutes per app/account - 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 `jq` or other commands, environment variables like `$MATON_API_KEY` may 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: ```json { "errors": [ { "message": "Error description", "locations": [...], "path": [...] } ] } ```
Mutation errors appear in `userErrors`: ```json { "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:
```bash echo $MATON_API_KEY ```
2. Verify the API key is valid by listing connections:
```bash 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:[email protected]) - [Maton Community](https://discord.com/invite/dBfFAcefs2) - [Maton Support](mailto:[email protected])