openapi: 3.2.0 info: title: BodySpec Availability 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: Availability description: Check appointment availability at locations paths: /api/v1/locations/{location_id}/availability: get: tags: - Availability summary: Get availability at a location description: Get available appointment slots at a specific location. Use start_time filters to narrow results. Availability is limited to 24 hours through 120 days in the future. operationId: _get_availability_api_v1_locations__location_id__availability_get parameters: - name: location_id in: path required: true schema: type: string title: Location Id - name: start_time[eq] in: query required: false schema: anyOf: - type: string - type: 'null' description: 'ISO 8601 datetime: start_time = value. Uses location timezone if not specified.' title: Start Time[Eq] description: 'ISO 8601 datetime: start_time = value. Uses location timezone if not specified.' - name: start_time[gt] in: query required: false schema: anyOf: - type: string - type: 'null' description: 'ISO 8601 datetime: start_time > value. Uses location timezone if not specified.' title: Start Time[Gt] description: 'ISO 8601 datetime: start_time > value. Uses location timezone if not specified.' - name: start_time[gte] in: query required: false schema: anyOf: - type: string - type: 'null' description: 'ISO 8601 datetime: start_time >= value. Uses location timezone if not specified.' title: Start Time[Gte] description: 'ISO 8601 datetime: start_time >= value. Uses location timezone if not specified.' - name: start_time[lt] in: query required: false schema: anyOf: - type: string - type: 'null' description: 'ISO 8601 datetime: start_time < value. Uses location timezone if not specified.' title: Start Time[Lt] description: 'ISO 8601 datetime: start_time < value. Uses location timezone if not specified.' - name: start_time[lte] in: query required: false schema: anyOf: - type: string - type: 'null' description: 'ISO 8601 datetime: start_time <= value. Uses location timezone if not specified.' title: Start Time[Lte] description: 'ISO 8601 datetime: start_time <= value. Uses location timezone if not specified.' - name: service_id in: query required: false schema: anyOf: - type: string - type: 'null' description: Optional service/product ID to filter by title: Service Id description: Optional service/product ID to filter by - 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: 100 title: Page Size description: Items per page (max 100) responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/AvailabilityResponse' example: slots: - appt_id: appt123 location_id: loc456 service_id: svc789 start_time: '2025-01-15T14:00:00-08:00' duration_minutes: 12 timezone: America/Los_Angeles is_available: true pagination: page: 1 page_size: 100 has_more: true max_availability_days: 120 '400': description: Invalid request data content: application/json: example: detail: - loc: - body - phone msg: string does not match regex type: value_error.str.regex '404': description: Resource not found content: application/json: example: detail: Location not found '500': description: Internal server error content: application/json: example: detail: Service type does not have a configured duration '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' security: [] /api/v1/locations/availability: get: tags: - Availability summary: Get availability across multiple locations description: Get available appointment slots across multiple locations in a single request. Slots are sorted by start time globally across all locations. operationId: _get_multi_availability_api_v1_locations_availability_get parameters: - name: location_ids in: query required: true schema: type: array items: type: string minItems: 1 description: Location IDs to search (1-10, repeated query param) title: Location Ids description: Location IDs to search (1-10, repeated query param) - name: start_time[eq] in: query required: false schema: anyOf: - type: string - type: 'null' description: 'ISO 8601 datetime with timezone: start_time = value' title: Start Time[Eq] description: 'ISO 8601 datetime with timezone: start_time = value' - name: start_time[gt] in: query required: false schema: anyOf: - type: string - type: 'null' description: 'ISO 8601 datetime with timezone: start_time > value' title: Start Time[Gt] description: 'ISO 8601 datetime with timezone: start_time > value' - name: start_time[gte] in: query required: false schema: anyOf: - type: string - type: 'null' description: 'ISO 8601 datetime with timezone: start_time >= value' title: Start Time[Gte] description: 'ISO 8601 datetime with timezone: start_time >= value' - name: start_time[lt] in: query required: false schema: anyOf: - type: string - type: 'null' description: 'ISO 8601 datetime with timezone: start_time < value' title: Start Time[Lt] description: 'ISO 8601 datetime with timezone: start_time < value' - name: start_time[lte] in: query required: false schema: anyOf: - type: string - type: 'null' description: 'ISO 8601 datetime with timezone: start_time <= value' title: Start Time[Lte] description: 'ISO 8601 datetime with timezone: start_time <= value' - name: service_id in: query required: false schema: anyOf: - type: string - type: 'null' description: Optional service/product ID to filter by title: Service Id description: Optional service/product ID to filter by - 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: 100 title: Page Size description: Items per page (max 100) responses: '200': description: Successful response content: application/json: schema: $ref: '#/components/schemas/MultiLocationAvailabilityResponse' example: slots: - appt_id: appt123 location_id: loc456 service_id: svc789 start_time: '2025-01-15T14:00:00-08:00' duration_minutes: 12 timezone: America/Los_Angeles is_available: true - appt_id: appt456 location_id: loc789 service_id: svc789 start_time: '2025-01-15T15:00:00-06:00' duration_minutes: 12 timezone: America/Chicago is_available: true pagination: page: 1 page_size: 100 results: 2 has_more: false max_availability_days: 120 skipped_location_ids: - bad_loc '400': description: Invalid request data content: application/json: example: detail: - loc: - body - phone msg: string does not match regex type: value_error.str.regex '500': description: Internal server error content: application/json: example: detail: Service type does not have a configured duration '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' security: [] components: schemas: MultiLocationAvailabilityResponse: properties: slots: items: $ref: '#/components/schemas/AvailabilitySlot' type: array title: Slots description: List of available appointment slots pagination: $ref: '#/components/schemas/Pagination' max_availability_days: type: integer title: Max Availability Days description: Maximum number of days in the future availability can be queried skipped_location_ids: items: type: string type: array title: Skipped Location Ids description: Location IDs that were skipped (not found or private) type: object required: - slots - pagination - max_availability_days title: MultiLocationAvailabilityResponse description: Response model for multi-location availability endpoint. x-internal: true AvailabilityResponse: properties: slots: items: $ref: '#/components/schemas/AvailabilitySlot' type: array title: Slots description: List of available appointment slots pagination: $ref: '#/components/schemas/Pagination' max_availability_days: type: integer title: Max Availability Days description: Maximum number of days in the future availability can be queried type: object required: - slots - pagination - max_availability_days title: AvailabilityResponse description: Response model for availability endpoint. x-internal: true HTTPValidationError: properties: detail: items: $ref: '#/components/schemas/ValidationError' type: array title: Detail type: object title: HTTPValidationError x-internal: true AvailabilitySlot: properties: appt_id: type: string title: Appt Id description: Unique identifier for the appointment slot location_id: type: string title: Location Id description: Location identifier where the appointment is available service_id: type: string title: Service Id description: Service/product identifier for this appointment start_time: type: string format: date-time title: Start Time description: Appointment start time in location's local timezone (ISO 8601 with offset) duration_minutes: type: integer title: Duration Minutes description: Duration of the appointment in minutes timezone: type: string title: Timezone description: IANA timezone identifier for the location (e.g., 'America/Los_Angeles') is_available: type: boolean title: Is Available description: Whether this slot is currently available for booking default: true type: object required: - appt_id - location_id - service_id - start_time - duration_minutes - timezone title: AvailabilitySlot description: Represents an available appointment slot. 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 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