Developer Center
Getting Started
Using Jobber’s API
Building Your App
Publishing Your App
App Template Project
Custom Integrations
Changelog

API Queries and Mutations

Make an Endpoint Request

All API requests are sent via POST to a single endpoint: https://api.getjobber.com/api/graphql

Every request must include the following headers:

  • Authorization: Bearer <ACCESS_TOKEN> (obtained via OAuth 2.0)
  • X-JOBBER-GRAPHQL-VERSION (set to an active API version)
  • Content-Type: application/json

All request bodies must be JSON. No other content types are accepted.

Example request:

curl -X POST \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "X-JOBBER-GRAPHQL-VERSION: 2025-04-16" \
  -H "Content-Type: application/json" \
  -d '{"query": "{ account { id name } }"}' \
  https://api.getjobber.com/api/graphql

If the access token is valid, the API processes the request and returns the result. If the access token is invalid or expired, the API returns a 401 error.

Queries

Queries read data from the Jobber API. The following example fetches the first 25 clients with their name and billing city:

query SampleQuery {
  clients(first: 25) {
    nodes {
      id
      firstName
      lastName
      billingAddress {
        city
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
    totalCount
  }
}

Always include a first or last argument on collection queries. Without one, the API assumes up to 100 nodes and charges the full estimated cost. See API Rate Limits for details.

Mutations

Mutations create, update, or delete data in Jobber. The following example creates a new client:

mutation SampleMutation {
  clientCreate(
    input: {
      firstName: "Jane"
      lastName: "Doe"
      companyName: "Jane's Automotive"
      emails: [
        { description: MAIN, primary: true, address: "[email protected]" }
      ]
    }
  ) {
    client {
      id
      firstName
      lastName
    }
    userErrors {
      message
      path
    }
  }
}

The userErrors field should always be included in your mutation's return fields and checked in your response handling. An empty userErrors array indicates the mutation succeeded. A non-empty array means the mutation was rejected and the message and path fields describe what went wrong.

Note: When using clientCreate, the name of your app is automatically captured in Jobber's Lead Source field. The lead source for an app-created client cannot be edited by Jobber users. This applies to all clients created after September 16, 2024. The lead source field was not tracked automatically on clients created before Sept. 16, 2024, but this data may be backfilled in the future.

Response structure

Every successful API response includes a data object containing the requested fields, and an extensions object containing cost and versioning metadata:

{
  "data": {
    "account": {
      "id": "Q2xpZW50LTEyMzQ1",
      "name": "Acme Lawn Care"
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 1,
      "actualQueryCost": 1,
      "throttleStatus": {
        "maximumAvailable": 10000,
        "currentlyAvailable": 9999,
        "restoreRate": 500
      }
    },
    "versioning": {
      "version": "2025-04-16"
    }
  }
}

There are two types of errors to handle:

Top-level errors: Present when the request could not be executed at all, such as throttling, authentication failures, or invalid arguments. The HTTP status code will still be 200. Always check for this field in your response handling.

{
  "errors": [
    {
      "message": "Argument 'firstName' on InputObject 'ClientCreateAttributes' has an invalid value.",
      "extensions": {
        "code": "argumentLiteralsIncompatible"
      }
    }
  ]
}

userErrors inside data: Returned by mutations when the request was valid but the operation was rejected due to business logic (e.g. a required field was missing or a value failed validation). These appear inside the mutation's response object:

{
  "data": {
    "clientCreate": {
      "client": null,
      "userErrors": [
        {
          "message": "Last name is required",
          "path": ["input", "lastName"]
        }
      ]
    }
  }
}

When calling mutations, always check userErrors even when the top-level response returns data successfully.