ServiceTitan Job Planning & Management API

Create, retrieve, update, hold, cancel, and reschedule jobs, projects, appointments, job types, call reasons, job cancel reasons, and job history. JPM is the operational core of ServiceTitan — every work order and recurring service rolls through this surface.

OpenAPI Specification

servicetitan-jpm-api-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: ServiceTitan Job Planning & Management API
  description: |
    The JPM API manages jobs, projects, appointments, job types, call reasons, job cancel
    reasons, and job history. It is the operational core of ServiceTitan — every work order
    and recurring service rolls through this surface. Tenant-scoped; OAuth 2.0 + App Key.
  version: "2.0.0"
  contact:
    name: ServiceTitan Developer Support
    url: https://developer.servicetitan.io/
    email: [email protected]
  license:
    name: ServiceTitan Terms of Service
    url: https://www.servicetitan.com/legal/terms-of-service
servers:
  - url: https://api.servicetitan.io/jpm/v2/{tenant}
    description: Production
    variables:
      tenant:
        default: "0000000"
  - url: https://api-integration.servicetitan.io/jpm/v2/{tenant}
    description: Integration (Sandbox)
    variables:
      tenant:
        default: "0000000"
security:
  - OAuth2: []
    AppKey: []
tags:
  - name: Jobs
    description: Service jobs (work orders)
  - name: Appointments
    description: Job appointments and visits
  - name: Projects
    description: Multi-job projects
  - name: Job Types
    description: Job type definitions
  - name: Call Reasons
    description: Job booking call reasons
paths:
  /jobs:
    get:
      summary: List Jobs
      operationId: listJobs
      tags: [Jobs]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
        - $ref: '#/components/parameters/ModifiedOnOrAfter'
        - $ref: '#/components/parameters/JobStatus'
      responses:
        '200':
          description: Paginated list of jobs
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobPagedResponse'
    post:
      summary: Create Job
      operationId: createJob
      tags: [Jobs]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JobCreateRequest'
      responses:
        '200':
          description: Created job
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
  /jobs/{id}:
    get:
      summary: Get Job
      operationId: getJob
      tags: [Jobs]
      parameters:
        - $ref: '#/components/parameters/Id'
      responses:
        '200':
          description: Job record
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
    patch:
      summary: Update Job
      operationId: updateJob
      tags: [Jobs]
      parameters:
        - $ref: '#/components/parameters/Id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JobUpdateRequest'
      responses:
        '200':
          description: Updated job
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
  /jobs/{id}/cancel:
    put:
      summary: Cancel Job
      operationId: cancelJob
      tags: [Jobs]
      parameters:
        - $ref: '#/components/parameters/Id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                reasonId: { type: integer }
                memo: { type: string }
      responses:
        '200':
          description: Job cancelled
  /jobs/{id}/hold:
    put:
      summary: Hold Job
      operationId: holdJob
      tags: [Jobs]
      parameters:
        - $ref: '#/components/parameters/Id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                reasonId: { type: integer }
                memo: { type: string }
      responses:
        '200':
          description: Job placed on hold
  /jobs/{id}/history:
    get:
      summary: Get Job History
      operationId: getJobHistory
      tags: [Jobs]
      parameters:
        - $ref: '#/components/parameters/Id'
      responses:
        '200':
          description: Job history
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobHistory'
  /appointments:
    get:
      summary: List Appointments
      operationId: listAppointments
      tags: [Appointments]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
        - $ref: '#/components/parameters/ModifiedOnOrAfter'
        - name: jobId
          in: query
          schema: { type: integer }
      responses:
        '200':
          description: Paginated list of appointments
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AppointmentPagedResponse'
    post:
      summary: Create Appointment
      operationId: createAppointment
      tags: [Appointments]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AppointmentCreateRequest'
      responses:
        '200':
          description: Created appointment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Appointment'
  /appointments/{id}:
    get:
      summary: Get Appointment
      operationId: getAppointment
      tags: [Appointments]
      parameters:
        - $ref: '#/components/parameters/Id'
      responses:
        '200':
          description: Appointment record
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Appointment'
    patch:
      summary: Update Appointment
      operationId: updateAppointment
      tags: [Appointments]
      parameters:
        - $ref: '#/components/parameters/Id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AppointmentUpdateRequest'
      responses:
        '200':
          description: Updated appointment
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Appointment'
  /appointments/{id}/reschedule:
    patch:
      summary: Reschedule Appointment
      operationId: rescheduleAppointment
      tags: [Appointments]
      parameters:
        - $ref: '#/components/parameters/Id'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [start, end]
              properties:
                start: { type: string, format: date-time }
                end: { type: string, format: date-time }
                arrivalWindowStart: { type: string, format: date-time }
                arrivalWindowEnd: { type: string, format: date-time }
      responses:
        '200':
          description: Appointment rescheduled
  /projects:
    get:
      summary: List Projects
      operationId: listProjects
      tags: [Projects]
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
        - $ref: '#/components/parameters/ModifiedOnOrAfter'
      responses:
        '200':
          description: Paginated list of projects
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectPagedResponse'
    post:
      summary: Create Project
      operationId: createProject
      tags: [Projects]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectCreateRequest'
      responses:
        '200':
          description: Created project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
  /projects/{id}:
    get:
      summary: Get Project
      operationId: getProject
      tags: [Projects]
      parameters:
        - $ref: '#/components/parameters/Id'
      responses:
        '200':
          description: Project record
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
  /job-types:
    get:
      summary: List Job Types
      operationId: listJobTypes
      tags: [Job Types]
      responses:
        '200':
          description: Job types
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobTypePagedResponse'
  /job-cancel-reasons:
    get:
      summary: List Job Cancel Reasons
      operationId: listJobCancelReasons
      tags: [Jobs]
      responses:
        '200':
          description: Cancel reasons
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: integer }
                        name: { type: string }
                        active: { type: boolean }
components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://auth.servicetitan.io/connect/token
          scopes: {}
    AppKey:
      type: apiKey
      in: header
      name: ST-App-Key
  parameters:
    Id:
      name: id
      in: path
      required: true
      schema: { type: integer, format: int64 }
    Page:
      name: page
      in: query
      schema: { type: integer, default: 1 }
    PageSize:
      name: pageSize
      in: query
      schema: { type: integer, default: 50, maximum: 500 }
    ModifiedOnOrAfter:
      name: modifiedOnOrAfter
      in: query
      schema: { type: string, format: date-time }
    JobStatus:
      name: jobStatus
      in: query
      schema: { type: string, enum: [Scheduled, Dispatched, InProgress, Hold, Completed, Canceled] }
  schemas:
    Job:
      type: object
      properties:
        id: { type: integer, format: int64 }
        jobNumber: { type: string }
        customerId: { type: integer, format: int64 }
        locationId: { type: integer, format: int64 }
        jobStatus: { type: string }
        completedOn: { type: string, format: date-time, nullable: true }
        businessUnitId: { type: integer }
        jobTypeId: { type: integer }
        priority: { type: string }
        campaignId: { type: integer, nullable: true }
        summary: { type: string }
        customFields:
          type: array
          items:
            type: object
            properties:
              typeId: { type: integer }
              name: { type: string }
              value: { type: string }
        appointmentCount: { type: integer }
        firstAppointmentId: { type: integer, nullable: true }
        lastAppointmentId: { type: integer, nullable: true }
        recallForId: { type: integer, nullable: true }
        warrantyId: { type: integer, nullable: true }
        jobGeneratedLeadSource:
          type: object
          properties:
            jobId: { type: integer, nullable: true }
            employeeId: { type: integer, nullable: true }
        noCharge: { type: boolean }
        notificationsEnabled: { type: boolean }
        createdOn: { type: string, format: date-time }
        modifiedOn: { type: string, format: date-time }
    JobCreateRequest:
      type: object
      required: [customerId, locationId, businessUnitId, jobTypeId, priority, campaignId, appointments]
      properties:
        customerId: { type: integer }
        locationId: { type: integer }
        businessUnitId: { type: integer }
        jobTypeId: { type: integer }
        priority: { type: string }
        campaignId: { type: integer }
        appointments:
          type: array
          items:
            type: object
            properties:
              start: { type: string, format: date-time }
              end: { type: string, format: date-time }
              arrivalWindowStart: { type: string, format: date-time }
              arrivalWindowEnd: { type: string, format: date-time }
              technicianIds:
                type: array
                items: { type: integer }
        summary: { type: string }
        shouldUpdateInvoiceItems: { type: boolean }
    JobUpdateRequest:
      type: object
      properties:
        summary: { type: string }
        priority: { type: string }
        businessUnitId: { type: integer }
        jobTypeId: { type: integer }
        campaignId: { type: integer }
    JobPagedResponse:
      type: object
      properties:
        page: { type: integer }
        pageSize: { type: integer }
        hasMore: { type: boolean }
        data:
          type: array
          items: { $ref: '#/components/schemas/Job' }
    JobHistory:
      type: object
      properties:
        data:
          type: array
          items:
            type: object
            properties:
              id: { type: integer }
              eventType: { type: string }
              date: { type: string, format: date-time }
              employeeId: { type: integer, nullable: true }
              usedSchedulingTool: { type: string, nullable: true }
              usedSchedulingToolVersion: { type: string, nullable: true }
    Appointment:
      type: object
      properties:
        id: { type: integer, format: int64 }
        jobId: { type: integer, format: int64 }
        appointmentNumber: { type: string }
        start: { type: string, format: date-time }
        end: { type: string, format: date-time }
        arrivalWindowStart: { type: string, format: date-time }
        arrivalWindowEnd: { type: string, format: date-time }
        status: { type: string, enum: [Scheduled, Dispatched, InProgress, Hold, Done, Canceled] }
        specialInstructions: { type: string, nullable: true }
        createdOn: { type: string, format: date-time }
        modifiedOn: { type: string, format: date-time }
        customerId: { type: integer }
        unused: { type: boolean }
    AppointmentCreateRequest:
      type: object
      required: [jobId, start, end]
      properties:
        jobId: { type: integer }
        start: { type: string, format: date-time }
        end: { type: string, format: date-time }
        arrivalWindowStart: { type: string, format: date-time }
        arrivalWindowEnd: { type: string, format: date-time }
        technicianIds:
          type: array
          items: { type: integer }
        specialInstructions: { type: string }
    AppointmentUpdateRequest:
      type: object
      properties:
        specialInstructions: { type: string }
    AppointmentPagedResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/Appointment' }
    Project:
      type: object
      properties:
        id: { type: integer, format: int64 }
        number: { type: string }
        name: { type: string }
        summary: { type: string }
        status: { type: string }
        statusId: { type: integer, nullable: true }
        substatusId: { type: integer, nullable: true }
        customerId: { type: integer }
        locationId: { type: integer }
        projectManagerIds:
          type: array
          items: { type: integer }
        businessUnitIds:
          type: array
          items: { type: integer }
        startDate: { type: string, format: date, nullable: true }
        targetCompletionDate: { type: string, format: date, nullable: true }
        actualCompletionDate: { type: string, format: date, nullable: true }
        modifiedOn: { type: string, format: date-time }
    ProjectCreateRequest:
      type: object
      required: [customerId, locationId]
      properties:
        name: { type: string }
        summary: { type: string }
        customerId: { type: integer }
        locationId: { type: integer }
        projectManagerIds:
          type: array
          items: { type: integer }
        startDate: { type: string, format: date }
        targetCompletionDate: { type: string, format: date }
    ProjectPagedResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/Project' }
    JobType:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        businessUnitIds:
          type: array
          items: { type: integer }
        skills:
          type: array
          items: { type: string }
        tagTypeIds:
          type: array
          items: { type: integer }
        priority: { type: string }
        duration: { type: integer, description: Duration in minutes }
        soldThreshold: { type: number, nullable: true }
        class: { type: string }
        summary: { type: string }
        noCharge: { type: boolean }
        enforceRecurringServiceEventSelection: { type: boolean }
        invoiceSignaturesRequired: { type: boolean }
        modifiedOn: { type: string, format: date-time }
        externalData:
          type: array
          items: { type: object }
    JobTypePagedResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/JobType' }