PT
PT365Partner documentation

API reference · v2026-08-23

Build home exercise
into your workflow.

Find the right exercises, open a prescribing workspace, and keep your catalog current. Explore the interface your development team can build on.

01 / ACCESS

Start with a partner account.

Request credentials and an approved launch origin. Available content and capabilities depend on your partner access.

Discuss your integration →
02 / AUTHENTICATE

Connect from your server.

Exchange client credentials for a short-lived access token. Keep secrets on your backend and send the token as a Bearer header.

Authentication endpoint →
03 / SYNC EFFICIENTLY

Sync once. Follow changes.

For a native catalog, import the snapshot pages, then apply incremental changes. Serve your cached catalog to clinicians.

Catalog sync guide →

The integration surface

Explore the API.

Production base URLhttps://www.physicaltherapy365.com/api/v1

Exercise content only: do not send patient, encounter, diagnosis, or contact data. Examples below are templates; reading this page does not make API calls.

11 endpoints

POST/oauth/token#

Exchange client credentials for an access token

Uses partner client credentials to issue a token
Example request

Run on your server. Replace example values and placeholders with your integration’s values.

curl --request POST 'https://www.physicaltherapy365.com/api/v1/oauth/token' \
  --user "$PT365_CLIENT_ID:$PT365_CLIENT_SECRET" \
  --data 'grant_type=client_credentials'
Request body
{
  "required": true,
  "content": {
    "application/x-www-form-urlencoded": {
      "schema": {
        "type": "object",
        "required": [
          "grant_type"
        ],
        "properties": {
          "grant_type": {
            "const": "client_credentials"
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string",
            "format": "password"
          }
        }
      }
    }
  }
}
Look up referenced data models →
Responses
200

Access token

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Token"
    }
  }
}
400

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
401

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
Look up referenced data models →
GET/exercises#

Search and page through exercises

Required scope: catalog:read
Example request

Run on your server. Replace example values and placeholders with your integration’s values.

curl --request GET 'https://www.physicaltherapy365.com/api/v1/exercises?q=quad&status=active&limit=10' \
  --header "Authorization: Bearer $PT365_ACCESS_TOKEN"
Parameters 10
NameLocationDetails
qOptionalquerySearch names, descriptions, targets, tags, and synonyms{"type":"string"}
regionOptionalqueryBody region{"type":"string"}
categoryOptionalqueryExercise category{"type":"string"}
levelOptionalqueryDifficulty level{"type":"string"}
equipmentOptionalqueryEquipment text{"type":"string"}
tagsOptionalqueryComma-separated tags{"type":"string"}
statusOptionalqueryLifecycle status{"type":"string"}
updated_afterOptionalqueryISO 8601 date-time{"type":"string"}
cursorOptionalqueryOpaque page cursor{"type":"string"}
limitOptionalquery{"type":"integer","minimum":1,"maximum":100,"default":50}
Responses
200

Exercise page

{
  "application/json": {
    "schema": {
      "type": "object",
      "properties": {
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/components/schemas/Exercise"
          }
        },
        "meta": {
          "$ref": "#/components/schemas/PageMeta"
        }
      }
    }
  }
}
401

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
429

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
503

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
Look up referenced data models →
GET/exercises/{id}#

Retrieve a current or historical exercise revision

Required scope: catalog:read
Example request

Run on your server. Replace example values and placeholders with your integration’s values.

curl --request GET 'https://www.physicaltherapy365.com/api/v1/exercises/REPLACE_WITH_EXERCISE_ID' \
  --header "Authorization: Bearer $PT365_ACCESS_TOKEN"
Parameters 2
NameLocationDetails
idRequiredpath{"type":"string"}
revisionOptionalqueryImmutable content revision{"type":"string"}
Responses
200

Exercise

{
  "application/json": {
    "schema": {
      "type": "object",
      "properties": {
        "data": {
          "$ref": "#/components/schemas/Exercise"
        }
      }
    }
  }
}
401

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
404

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
429

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
503

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
Look up referenced data models →
GET/taxonomies#

List taxonomy values

Required scope: catalog:read
Example request

Run on your server. Replace example values and placeholders with your integration’s values.

curl --request GET 'https://www.physicaltherapy365.com/api/v1/taxonomies' \
  --header "Authorization: Bearer $PT365_ACCESS_TOKEN"
Responses
200

Taxonomies

401

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
429

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
503

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
Look up referenced data models →
GET/catalog/manifest#

Create or retrieve a fixed catalog snapshot manifest

Entitlement is independent of sandbox/production. A full-catalog pilot sees all published lifecycle records; ordinary sandbox clients see only the sample catalog. Begin change synchronization at changeCursor after importing every snapshot page.

Required scope: catalog:read
Example request

Run on your server. Replace example values and placeholders with your integration’s values.

curl --request GET 'https://www.physicaltherapy365.com/api/v1/catalog/manifest' \
  --header "Authorization: Bearer $PT365_ACCESS_TOKEN"
Responses
200

Immutable snapshot manifest

{
  "application/json": {
    "schema": {
      "type": "object",
      "properties": {
        "data": {
          "$ref": "#/components/schemas/CatalogManifest"
        }
      }
    }
  }
}
401

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
429

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
503

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
Look up referenced data models →
GET/catalog/snapshots/{id}#

Download a fixed snapshot page

Required scope: catalog:read
Example request

Run on your server. Replace example values and placeholders with your integration’s values.

curl --request GET 'https://www.physicaltherapy365.com/api/v1/catalog/snapshots/REPLACE_WITH_SNAPSHOT_ID?offset=0&limit=100' \
  --header "Authorization: Bearer $PT365_ACCESS_TOKEN"
Parameters 3
NameLocationDetails
idRequiredpath{"type":"string","format":"uuid"}
offsetOptionalquery{"type":"integer","minimum":0,"default":0}
limitOptionalquery{"type":"integer","minimum":1,"maximum":100,"default":100}
Responses
200

Snapshot contents never change. Follow nextOffset until null; verify total and every image sha256 before activation.

{
  "application/json": {
    "schema": {
      "type": "object",
      "properties": {
        "data": {
          "type": "array",
          "items": {
            "$ref": "#/components/schemas/Exercise"
          }
        },
        "meta": {
          "type": "object",
          "properties": {
            "snapshotId": {
              "type": "string"
            },
            "catalogRevision": {
              "type": "string"
            },
            "total": {
              "type": "integer"
            },
            "nextOffset": {
              "type": [
                "integer",
                "null"
              ]
            },
            "changeCursor": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}
400

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
401

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
404

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
429

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
503

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
Look up referenced data models →
GET/catalog/changes#

Synchronize catalog lifecycle events

Required scope: catalog:read
Example request

Run on your server. Replace example values and placeholders with your integration’s values.

curl --request GET 'https://www.physicaltherapy365.com/api/v1/catalog/changes?cursor=REPLACE_WITH_CHANGE_CURSOR&limit=100' \
  --header "Authorization: Bearer $PT365_ACCESS_TOKEN"
Parameters 2
NameLocationDetails
cursorOptionalqueryOpaque change cursor{"type":"string"}
limitOptionalquery{"type":"integer","minimum":1,"maximum":250,"default":100}
Responses
200

Ordered immutable lifecycle events. Fetch the exact event revision, then atomically persist the applied batch and nextCursor. Webhooks are reconciliation hints; poll every 15 minutes as recovery.

{
  "application/json": {
    "schema": {
      "type": "object",
      "properties": {
        "data": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "sequence": {
                "type": "integer"
              },
              "type": {
                "enum": [
                  "exercise.published",
                  "exercise.updated",
                  "exercise.deprecated",
                  "exercise.withdrawn"
                ]
              },
              "exerciseId": {
                "type": "string"
              },
              "revision": {
                "type": "string"
              },
              "status": {
                "enum": [
                  "active",
                  "deprecated",
                  "withdrawn"
                ]
              },
              "occurredAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        },
        "meta": {
          "type": "object",
          "properties": {
            "nextCursor": {
              "type": "string"
            },
            "hasMore": {
              "type": "boolean"
            },
            "catalogRevision": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}
401

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
429

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
503

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
Look up referenced data models →
POST/picker/sessions#

Create a short-lived origin-bound picker session

Required scope: picker:create
Example request

Run on your server. Replace example values and placeholders with your integration’s values.

curl --request POST 'https://www.physicaltherapy365.com/api/v1/picker/sessions' \
  --header "Authorization: Bearer $PT365_ACCESS_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{
  "origin": "https://your-emr.example",
  "clinicianId": "clinician-demo",
  "initialSelection": []
}'
Request body
{
  "required": true,
  "content": {
    "application/json": {
      "schema": {
        "$ref": "#/components/schemas/PickerSessionRequest"
      }
    }
  }
}
Look up referenced data models →
Responses
201

Picker launch URL

400

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
401

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
429

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
503

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
Look up referenced data models →
GET/webhooks#

List webhook endpoints

Required scope: webhooks:manage
Example request

Run on your server. Replace example values and placeholders with your integration’s values.

curl --request GET 'https://www.physicaltherapy365.com/api/v1/webhooks' \
  --header "Authorization: Bearer $PT365_ACCESS_TOKEN"
Responses
200

Webhook list

401

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
429

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
503

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
Look up referenced data models →
POST/webhooks#

Create a signed webhook endpoint

Required scope: webhooks:manage
Example request

Run on your server. Replace example values and placeholders with your integration’s values.

curl --request POST 'https://www.physicaltherapy365.com/api/v1/webhooks' \
  --header "Authorization: Bearer $PT365_ACCESS_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{
  "url": "https://your-emr.example/webhooks/pt365",
  "events": [
    "exercise.updated"
  ]
}'
Request body
{
  "required": true,
  "content": {
    "application/json": {
      "schema": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "url",
          "events"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "pattern": "^https://"
          },
          "events": {
            "type": "array",
            "uniqueItems": true,
            "items": {
              "enum": [
                "exercise.published",
                "exercise.updated",
                "exercise.deprecated",
                "exercise.withdrawn"
              ]
            }
          }
        }
      }
    }
  }
}
Look up referenced data models →
Responses
201

Webhook and one-time signing secret

400

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
401

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
429

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
503

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
Look up referenced data models →
DELETE/webhooks/{id}#

Delete a webhook

Required scope: webhooks:manage
Example request

Run on your server. Replace example values and placeholders with your integration’s values.

curl --request DELETE 'https://www.physicaltherapy365.com/api/v1/webhooks/REPLACE_WITH_WEBHOOK_ID' \
  --header "Authorization: Bearer $PT365_ACCESS_TOKEN"
Parameters 1
NameLocationDetails
idRequiredpath{"type":"string","format":"uuid"}
Responses
204

Deleted

401

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
404

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
429

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
503

Request failed

{
  "application/json": {
    "schema": {
      "$ref": "#/components/schemas/Error"
    }
  }
}
Look up referenced data models →

Shared structures

Data models.

These schemas define the fields used in requests and responses. Both this reference and the downloadable JSON use the same OpenAPI source.

CatalogManifest
{
  "type": "object",
  "required": [
    "schemaVersion",
    "snapshotId",
    "catalogRevision",
    "total",
    "changeCursor",
    "createdAt"
  ],
  "properties": {
    "schemaVersion": {
      "const": 1
    },
    "snapshotId": {
      "type": "string",
      "format": "uuid"
    },
    "catalogRevision": {
      "type": "string"
    },
    "total": {
      "type": "integer",
      "minimum": 0
    },
    "changeCursor": {
      "type": "string",
      "description": "Opaque event cursor captured atomically with this snapshot"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time"
    }
  }
}
Token
{
  "type": "object",
  "required": [
    "access_token",
    "token_type",
    "expires_in",
    "scope",
    "environment"
  ],
  "properties": {
    "access_token": {
      "type": "string"
    },
    "token_type": {
      "const": "Bearer"
    },
    "expires_in": {
      "type": "integer"
    },
    "scope": {
      "type": "string"
    },
    "environment": {
      "enum": [
        "sandbox",
        "production"
      ]
    }
  }
}
Error
{
  "type": "object",
  "required": [
    "error"
  ],
  "properties": {
    "error": {
      "type": "object",
      "required": [
        "code",
        "message",
        "requestId"
      ],
      "properties": {
        "code": {
          "type": "string"
        },
        "message": {
          "type": "string"
        },
        "requestId": {
          "type": "string"
        },
        "details": {}
      }
    }
  }
}
PageMeta
{
  "type": "object",
  "properties": {
    "environment": {
      "enum": [
        "sandbox",
        "production"
      ]
    },
    "total": {
      "type": "integer"
    },
    "limit": {
      "type": "integer"
    },
    "nextCursor": {
      "type": [
        "string",
        "null"
      ]
    },
    "catalogRevision": {
      "type": "string"
    }
  }
}
Prescription
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "exerciseId",
    "revision",
    "side"
  ],
  "properties": {
    "exerciseId": {
      "type": "string"
    },
    "revision": {
      "type": "string"
    },
    "side": {
      "enum": [
        "left",
        "right",
        "bilateral",
        "not_applicable"
      ]
    },
    "sets": {
      "type": "integer",
      "minimum": 1,
      "maximum": 99
    },
    "repetitions": {
      "type": "integer",
      "minimum": 1,
      "maximum": 999
    },
    "holdSeconds": {
      "type": "integer",
      "minimum": 1,
      "maximum": 3600
    },
    "durationMinutes": {
      "type": "integer",
      "minimum": 1,
      "maximum": 240
    },
    "frequency": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "times",
        "period"
      ],
      "properties": {
        "times": {
          "type": "integer",
          "minimum": 1,
          "maximum": 20
        },
        "period": {
          "enum": [
            "day",
            "week"
          ]
        }
      }
    },
    "resistance": {
      "type": "string",
      "maxLength": 80
    },
    "restSeconds": {
      "type": "integer",
      "minimum": 0,
      "maximum": 3600
    },
    "customDosage": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200,
      "description": "Written dosage overrides structured sets, repetitions, hold, duration, rest and frequency. The picker returns only one dosage format."
    }
  }
}
PickerSessionRequest
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "origin",
    "clinicianId"
  ],
  "description": "No patient or encounter fields permitted",
  "properties": {
    "origin": {
      "type": "string",
      "format": "uri",
      "description": "Exact registered HTTPS origin; sandbox credentials also permit HTTP localhost or loopback for local development. No path or trailing slash."
    },
    "clinicianId": {
      "type": "string",
      "pattern": "^[A-Za-z0-9._:-]{8,128}$"
    },
    "theme": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "brandName": {
          "type": "string",
          "maxLength": 80
        },
        "primaryColor": {
          "type": "string",
          "pattern": "^#[0-9A-Fa-f]{6}$"
        },
        "backgroundColor": {
          "type": "string",
          "pattern": "^#[0-9A-Fa-f]{6}$"
        }
      }
    },
    "initialSelection": {
      "type": "array",
      "maxItems": 20,
      "items": {
        "$ref": "#/components/schemas/Prescription"
      }
    }
  }
}
Media
{
  "type": "object",
  "required": [
    "role",
    "url",
    "mimeType",
    "width",
    "height",
    "sha256",
    "alt"
  ],
  "properties": {
    "role": {
      "enum": [
        "start",
        "end"
      ]
    },
    "url": {
      "type": "string",
      "format": "uri"
    },
    "mimeType": {
      "const": "image/png"
    },
    "width": {
      "type": "integer"
    },
    "height": {
      "type": "integer"
    },
    "sha256": {
      "type": "string"
    },
    "alt": {
      "type": "string"
    }
  }
}
Exercise
{
  "type": "object",
  "required": [
    "id",
    "slug",
    "revision",
    "status",
    "name",
    "description",
    "steps",
    "media"
  ],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^ex_[0-9a-f]{20}$"
    },
    "slug": {
      "type": "string"
    },
    "revision": {
      "type": "string"
    },
    "status": {
      "enum": [
        "active",
        "deprecated",
        "withdrawn"
      ]
    },
    "publishedAt": {
      "type": "string",
      "format": "date-time"
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time"
    },
    "replacementId": {
      "type": "string"
    },
    "changeReason": {
      "type": "string"
    },
    "name": {
      "type": "string"
    },
    "description": {
      "type": "string"
    },
    "steps": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "cue": {
      "type": "string"
    },
    "region": {
      "enum": [
        "Knee",
        "Shoulder",
        "Low Back",
        "Neck",
        "Hip",
        "Ankle",
        "Elbow / Wrist / Hand"
      ]
    },
    "category": {
      "enum": [
        "Mobility",
        "Strength",
        "Balance",
        "Stretch"
      ]
    },
    "level": {
      "enum": [
        "Early",
        "Foundation",
        "Functional"
      ]
    },
    "target": {
      "type": "string"
    },
    "equipment": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "searchSynonyms": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "defaultPrescription": {
      "type": "object"
    },
    "media": {
      "type": "array",
      "minItems": 2,
      "maxItems": 2,
      "items": {
        "$ref": "#/components/schemas/Media"
      }
    }
  }
}