openapi: 3.2.0 info: title: BodySpec Partner Appointments API description: This API allows BodySpec users to integrate their DEXA scan data with other platforms. license: name: Proprietary version: 0.18.2 servers: - url: https://app.bodyspec.com description: Production server security: - OAuth2: - openid - profile - email - BearerAuth: [] tags: - name: Partner Appointments description: Partner integration endpoints for user appointments paths: /api/v1/partners/{partner_id}/users/{user_id}/appts: get: tags: - Partner Appointments summary: List partner user appts description: Retrieve paginated list of appointments for a specific user linked to the partner. operationId: _list_partner_user_appointments_api_v1_partners__partner_id__users__user_id__appts_get security: - HTTPBasic: [] - PartnerAuth: [] parameters: - name: partner_id in: path required: true schema: type: string description: Partner ID title: Partner Id description: Partner ID - name: user_id in: path required: true schema: type: string description: User ID title: User Id description: User ID - name: page in: query required: false schema: type: integer minimum: 1 description: Page number (starts at 1) default: 1 title: Page description: Page number (starts at 1) - name: page_size in: query required: false schema: type: integer maximum: 100 minimum: 1 description: Items per page (max 100) default: 20 title: Page Size description: Items per page (max 100) - name: status in: query required: false schema: anyOf: - $ref: '#/components/schemas/ApptStatus' - type: 'null' description: 'Filter by appointment outcome: ''scheduled'', ''pending_scan'' (started but results not published yet, or no evidence the location was operating), ''completed'', or ''no_show''' title: Status description: 'Filter by appointment outcome: ''scheduled'', ''pending_scan'' (started but results not published yet, or no evidence the location was operating), ''completed'', or ''no_show''' - name: sort_order in: query required: false schema: $ref: '#/components/schemas/SortOrder' description: 'Sort order: ''newest_first'' or ''oldest_first''' default: newest_first description: 'Sort order: ''newest_first'' or ''oldest_first''' - name: start_time[eq] in: query required: false schema: anyOf: - type: string format: date-time - type: 'null' description: 'ISO 8601 datetime: start_time = value' title: Start Time[Eq] description: 'ISO 8601 datetime: start_time = value' - name: start_time[gt] in: query required: false schema: anyOf: - type: string format: date-time - type: 'null' description: 'ISO 8601 datetime: start_time > value' title: Start Time[Gt] description: 'ISO 8601 datetime: start_time > value' - name: start_time[gte] in: query required: false schema: anyOf: - type: string format: date-time - type: 'null' description: 'ISO 8601 datetime: start_time >= value' title: Start Time[Gte] description: 'ISO 8601 datetime: start_time >= value' - name: start_time[lt] in: query required: false schema: anyOf: - type: string format: date-time - type: 'null' description: 'ISO 8601 datetime: start_time < value' title: Start Time[Lt] description: 'ISO 8601 datetime: start_time < value' - name: start_time[lte] in: query required: false schema: anyOf: - type: string format: date-time - type: 'null' description: 'ISO 8601 datetime: start_time <= value' title: Start Time[Lte] description: 'ISO 8601 datetime: start_time <= value' - name: reserve_time[eq] in: query required: false schema: anyOf: - type: string format: date-time - type: 'null' description: 'ISO 8601 datetime: reserve_time = value' title: Reserve Time[Eq] description: 'ISO 8601 datetime: reserve_time = value' - name: reserve_time[gt] in: query required: false schema: anyOf: - type: string format: date-time - type: 'null' description: 'ISO 8601 datetime: reserve_time > value' title: Reserve Time[Gt] description: 'ISO 8601 datetime: reserve_time > value' - name: reserve_time[gte] in: query required: false schema: anyOf: - type: string format: date-time - type: 'null' description: 'ISO 8601 datetime: reserve_time >= value' title: Reserve Time[Gte] description: 'ISO 8601 datetime: reserve_time >= value' - name: reserve_time[lt] in: query required: false schema: anyOf: - type: string format: date-time - type: 'null' description: 'ISO 8601 datetime: reserve_time < value' title: Reserve Time[Lt] description: 'ISO 8601 datetime: reserve_time < value' - name: reserve_time[lte] in: query required: false schema: anyOf: - type: string format: date-time - type: 'null' description: 'ISO 8601 datetime: reserve_time <= value' title: Reserve Time[Lte] description: 'ISO 8601 datetime: reserve_time <= value' responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/ApptListResponse' example: appts: - appt_id: a5950beea2a7487fb79cfdbeb77fa555 start_time: '2024-03-15T10:30:00-07:00' duration_minutes: 12 location: location_id: loc123 name: BodySpec - Culver City location_type: storefront service: name: DEXA description: DEXA Scan status: completed reserve_time: '2024-03-01T15:00:00Z' pagination: page: 1 page_size: 20 results: 1 has_more: false '401': description: Invalid partner authentication '403': description: Partner ID mismatch or user not linked to partner '404': description: User not found '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' components: schemas: ApptStatus: type: string enum: - scheduled - pending_scan - completed - no_show title: ApptStatus description: 'Derived appointment outcome. BodySpec does not record attendance, so the outcome is inferred from whether published results exist and from what happened at the same location that day.' x-internal: true ApptListResponse: properties: appts: items: $ref: '#/components/schemas/Appointment' type: array title: Appts description: List of appointments pagination: $ref: '#/components/schemas/Pagination' description: Pagination information type: object required: - appts - pagination title: ApptListResponse description: Paginated appointment list response. x-internal: true Appointment: properties: appt_id: type: string title: Appt Id description: Unique identifier for the appointment start_time: type: string format: date-time title: Start Time description: Appointment start time in location timezone duration_minutes: type: integer title: Duration Minutes description: Duration of the appointment in minutes location: $ref: '#/components/schemas/Location' description: Location details service: $ref: '#/components/schemas/Service' description: Service information status: $ref: '#/components/schemas/ApptStatus' description: 'Appointment outcome. `scheduled` — upcoming, results not expected yet. `pending_scan` — the appointment time has passed but results are not published yet, or no other scan at that location later that day produced results either (which reads as a site problem rather than a missed appointment). `completed` — results are published and retrievable. `no_show` — the appointment time has passed with no results, and a later scan at the same location that day did produce results, so the location was operating normally. Note that `pending_scan` can be a lasting state: BodySpec does not record attendance, so an appointment is only reported as `no_show` when there is positive evidence the location was operating. When BodySpec makes the `no_show` determination for a partner appointment, a `reservation_no_show` webhook is emitted.' reserve_time: anyOf: - type: string format: date-time - type: 'null' title: Reserve Time description: UTC timestamp when the appointment was reserved, if applicable type: object required: - appt_id - start_time - duration_minutes - location - service - status title: Appointment description: Appointment information. Address: properties: address_line1: anyOf: - type: string - type: 'null' title: Address Line1 description: Primary address line address_line2: anyOf: - type: string - type: 'null' title: Address Line2 description: Secondary address line (e.g., suite number) city: anyOf: - type: string - type: 'null' title: City description: City name state: anyOf: - type: string - type: 'null' title: State description: State or province postal_code: anyOf: - type: string - type: 'null' title: Postal Code description: Postal/ZIP code country: type: string title: Country description: Two-letter ISO country code default: US type: object title: Address description: Address information for a location. x-internal: true HTTPValidationError: properties: detail: items: $ref: '#/components/schemas/ValidationError' type: array title: Detail type: object title: HTTPValidationError x-internal: true Coordinates: properties: latitude: type: number title: Latitude description: Latitude in decimal degrees longitude: type: number title: Longitude description: Longitude in decimal degrees type: object required: - latitude - longitude title: Coordinates description: Geographic coordinates for a location. x-internal: true Location: properties: location_id: type: string title: Location Id description: Unique identifier for the location name: type: string title: Name description: Location name location_type: type: string title: Location Type description: 'Type of location: ''mobile'' or ''storefront''' address: $ref: '#/components/schemas/Address' description: Address information coordinates: anyOf: - $ref: '#/components/schemas/Coordinates' - type: 'null' description: Geographic coordinates timezone: anyOf: - type: string - type: 'null' title: Timezone description: IANA timezone identifier (e.g., 'America/Los_Angeles') description: anyOf: - type: string - type: 'null' title: Description description: Public notes or description last_updated: type: string format: date-time title: Last Updated description: Last update timestamp in ISO 8601 UTC format distance_miles: anyOf: - type: number - type: 'null' title: Distance Miles description: Distance from search coordinates in miles. Only present when lat/lng search parameters are provided. type: object required: - location_id - name - location_type - address - last_updated title: Location description: Location model representing a scan location with full details. Pagination: properties: page: type: integer title: Page description: Current page number page_size: type: integer title: Page Size description: Number of items per page results: type: integer title: Results description: Number of results in this page has_more: type: boolean title: Has More description: Whether more results exist after this page type: object required: - page - page_size - results - has_more title: Pagination description: Pagination information for list responses. x-internal: true ValidationError: properties: loc: items: anyOf: - type: string - type: integer type: array title: Location msg: type: string title: Message type: type: string title: Error Type type: object required: - loc - msg - type title: ValidationError x-internal: true SortOrder: type: string enum: - newest_first - oldest_first title: SortOrder description: Sort order options for appointment lists. x-internal: true Service: properties: name: type: string title: Name description: Short name of the service (e.g., 'DEXA') description: type: string title: Description description: Full descriptive name of the service service_id: anyOf: - type: string - type: 'null' title: Service Id description: Unique identifier for the service service_code: anyOf: - type: string - type: 'null' title: Service Code description: Service code (e.g., 'DXA', 'RMR', 'VO2') duration_minutes: anyOf: - type: integer - type: 'null' title: Duration Minutes description: Typical duration in minutes last_updated: anyOf: - type: string format: date-time - type: 'null' title: Last Updated description: Last update timestamp in ISO 8601 UTC format type: object required: - name - description title: Service description: Service model representing a bookable scan or test type. securitySchemes: OAuth2: type: oauth2 description: OAuth2 authentication via Keycloak with PKCE flows: authorizationCode: authorizationUrl: https://auth.bodyspec.com/realms/bodyspec/protocol/openid-connect/auth tokenUrl: https://auth.bodyspec.com/realms/bodyspec/protocol/openid-connect/token scopes: openid: OpenID Connect scope profile: Access to user profile email: Access to user email x-usePkce: SHA-256 x-scalar-client-id: bodyspec-api-ext-v1 BearerAuth: type: http scheme: bearer bearerFormat: JWT description: JWT Bearer token for authentication PartnerAuth: type: http scheme: basic description: For partner integrations only. Contact BodySpec to obtain credentials. x-tagGroups: - name: 👤 User Data tags: - Users - Appointments - Results - name: 📅 Availability tags: - Locations - Services - Availability - name: 🤝 Partners tags: - Reservations - Partner Users - Partner Appointments - Partner Results - Partner Intake - Partner Orders - Partner Webhooks - name: 🏥 API Status tags: - API Status