openapi: 3.1.0
info:
  title: Stafflion Partners Integration
  version: 1.5.0
servers:
  - url: https://app.stafflion.com
    description: Production
  - url: https://staging.stafflion.com
    description: Staging
paths:
  /oauth/token:
    post:
      operationId: auth_Access Token
      summary: Access Token
      description: This endpoint allows partner agencies to request an authentication token by providing their unique client ID and secret. The obtained JWT token can then be used as a Bearer token to authorize subsequent API requests, maintaining secure access to **Stafflion's** services.
      tags:
        - auth
      servers:
        - url: https://auth.stafflion.com
          description: Production
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Auth_Access_Token_Response_200'
              examples:
                token:
                  summary: Access token
                  value:
                    expires_in: 86400
                    token_type: Bearer
                    access_token: ACCESS_TOKEN
        '401':
          description: Invalid client ID or secret. Illustrative body, confirm against staging.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
              examples:
                invalid_client:
                  summary: Invalid client
                  value:
                    error: invalid_client
                    error_description: Invalid client credentials.
        '422':
          description: Validation failed. A required field is missing.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                missing_field:
                  summary: Missing client_id
                  value:
                    message: The given data was invalid.
                    errors:
                      client_id:
                        - The client_id field is required.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                audience:
                  type: string
                  description: 'Target API audience. Staging: `https://staging.stafflion.com/api/v1`. Production: `https://app.stafflion.com/api/v1`.'
                client_id:
                  type: string
                  description: Partner unique client ID.
                grant_type:
                  type: string
                  description: Must be `client_credentials`.
                client_secret:
                  type: string
                  description: Partner unique client secret.
              required:
                - audience
                - client_id
                - grant_type
                - client_secret
            examples:
              staging:
                summary: Staging (primary environment)
                value:
                  audience: https://staging.stafflion.com/api/v1
                  client_id: YOUR_STAGING_CLIENT_ID
                  grant_type: client_credentials
                  client_secret: YOUR_STAGING_CLIENT_SECRET
              production:
                summary: Production
                value:
                  audience: https://app.stafflion.com/api/v1
                  client_id: YOUR_PRODUCTION_CLIENT_ID
                  grant_type: client_credentials
                  client_secret: YOUR_PRODUCTION_CLIENT_SECRET
  /api/v1/webhooks/partners:
    post:
      operationId: shifts_Webhook
      summary: Webhook
      description: |-
        Send real-time updates about shifts and clinicians to Stafflion. Each request carries one event in `event_name`.

        | Event | When to send it |
        | --- | --- |
        | `shift_request` | A clinician requests to work a shift. Stafflion approves the request through the partner API. |
        | `shift_claimed` | A clinician claims a shift. Confirms the approved shift so Stafflion adds it to its schedule. |
        | `shift_update` | The time of a shift changes. Send only after a `shift_claimed` has been sent for that shift, that is, once the shift is taken by a clinician. |
        | `shift_call_out` | A clinician calls out or will no longer work the shift. We suggest closing this shift or position. Do not look for a replacement. |

        Error responses carry a `message` with one of these values:

        - `Shift not found` (404): the shift is unknown, deleted, or not shared with this agency. A `shift_claimed` for a shift Stafflion did not post also returns this when it has no `shift_start_time` and `shift_end_time`.
        - `Facility not found with the ID '<partner_facility_id>'` (404): the partner facility is not linked to a Stafflion facility.
        - `Shift has already ended` (400): the shift end time has passed.
        - `Shift is already taken` (400): another clinician already holds the shift.
        - `Clinician doesn't have the required position to claim this shift` (400): the clinician's position does not match the shift.
        - `More than one shift/slot found. Please provide a clinician identifier to disambiguate` (400): several shifts match and no clinician identifier was sent.
        - `No shift found for the given clinician` (400): no shift matches the given clinician.
        - `Shift is not taken by any clinician` (400): the shift has no clinician to call out.
        - `Missing param 'partner_facility_id'`, `Missing 'partner_shift_id' param`, `Missing param 'partner_clinician_id'` (400): a required identifier is missing from `shift_request`.
        - `Missing 'shift_id' or 'partner_shift_id' param` (400): `shift_update` has no shift identifier.
      tags:
        - shifts
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Shifts_Webhook_Response_200'
              examples:
                shift_request:
                  summary: shift_request
                  value:
                    message: success
                    shift:
                      id: 1048012
                      partner_shift_id: deb1b3c4-72a8-493a-ba34-46eebb81ef94
                shift_claimed:
                  summary: shift_claimed
                  value:
                    message: success
                    shift:
                      id: 1048012
                      partner_shift_id: deb1b3c4-72a8-493a-ba34-46eebb81ef94
                      partner_secondary_shift_id: null
                    clinician:
                      id: 139898
                      partner_clinician_id: a98600a7-6c73-4095-ab17-7c5d8c02870c
                shift_update:
                  summary: shift_update
                  value:
                    message: success
                    shift:
                      id: 1048012
                      partner_shift_id: deb1b3c4-72a8-493a-ba34-46eebb81ef94
                    clinician:
                      id: 139898
                      partner_clinician_id: a98600a7-6c73-4095-ab17-7c5d8c02870c
                shift_call_out:
                  summary: shift_call_out
                  value:
                    message: success
                    shift:
                      id: 1048012
                      partner_shift_id: deb1b3c4-72a8-493a-ba34-46eebb81ef94
                      partner_secondary_shift_id: null
                    clinician:
                      id: 139898
                      partner_clinician_id: a98600a7-6c73-4095-ab17-7c5d8c02870c
        '400':
          description: Bad request. The event is well formed but cannot be applied.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorMessage'
              examples:
                shift_already_taken:
                  summary: Shift is already taken
                  value:
                    message: Shift is already taken
        '404':
          description: Not found. The shift or facility is unknown to Stafflion.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorMessage'
              examples:
                shift_not_found:
                  summary: Shift not found
                  value:
                    message: Shift not found
        '422':
          description: Validation failed. `errors` lists each invalid field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                event_name:
                  type: string
                  enum:
                    - shift_request
                    - shift_claimed
                    - shift_update
                    - shift_call_out
                  description: Event name.
                event_data:
                  oneOf:
                    - $ref: '#/components/schemas/ShiftRequestEventData'
                    - $ref: '#/components/schemas/ShiftClaimedEventData'
                    - $ref: '#/components/schemas/ShiftUpdateEventData'
                    - $ref: '#/components/schemas/ShiftCallOutEventData'
                occurred_at:
                  type: string
                  format: date-time
                  description: 'Date and time when the event occurred, in UTC (RFC3339). Example: `2025-07-28T11:00:00.000Z`.'
              required:
                - event_name
                - event_data
                - occurred_at
      security:
        - bearerAuth: []
  /api/v1/partners/synced-shifts:
    get:
      operationId: shifts_Synced Shifts
      summary: Synced Shifts
      description: |-
        Get all the shifts synchronized with the partner's portal. This means retrieving all the shifts that are present in both Stafflion's system and the partner's system.

        Shifts with `is_play_mode` set to `true` exist only within Stafflion's system.
      tags:
        - shifts
      parameters:
        - name: shift_id
          in: query
          required: false
          description: Stafflion shift ID.
          schema:
            type: integer
        - name: partner_shift_id
          in: query
          required: false
          description: Partner shift ID.
          schema:
            type: string
        - name: partner_secondary_shift_id
          in: query
          required: false
          description: Partner secondary shift ID.
          schema:
            type: string
        - name: partner_facility_id
          in: query
          required: false
          description: Partner facility ID.
          schema:
            type: string
        - name: date
          in: query
          required: false
          description: Shift date (`Y-m-d`).
          example: '2026-07-24'
          schema:
            type: string
            format: date
        - name: start_time
          in: query
          required: false
          description: Shift start time (`H:i`).
          schema:
            type: string
        - name: end_time
          in: query
          required: false
          description: Shift end time (`H:i`).
          schema:
            type: string
        - name: position
          in: query
          required: false
          description: Position, for example `CNA`.
          schema:
            type: string
        - name: is_open
          in: query
          required: false
          description: Filter by open status.
          example: false
          schema:
            type: boolean
        - name: is_called_out
          in: query
          required: false
          description: Filter by called-out status.
          schema:
            type: boolean
        - name: is_play_mode
          in: query
          required: false
          description: Filter by play mode. `true` returns shifts that exist only within Stafflion's system.
          schema:
            type: boolean
        - name: is_deleted
          in: query
          required: false
          description: Filter by deleted status.
          schema:
            type: boolean
        - name: page
          in: query
          required: false
          description: Page number. Defaults to 1.
          example: 1
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          required: false
          description: Items per page. Defaults to 100. Maximum 500.
          example: 100
          schema:
            type: integer
            default: 100
            maximum: 500
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Shifts_Synced_Shifts_Response_200'
              examples:
                shifts:
                  summary: Synchronized shifts
                  value:
                    data:
                      - shift_id: 4582625
                        scheduled_shift_id: 10624927
                        partner_shift_id: deb1b3c4-72a8-493a-ba34-46eebb81ef94
                        partner_secondary_shift_id: null
                        partner_facility_id: efa61cc1-caf8-4544-9f3a-a8946d51026b
                        facility_name: Example Facility
                        shift_positions:
                          - CNA
                        date: '2026-07-24'
                        start_time: '07:00'
                        end_time: '15:00'
                        clinician:
                          id: 139898
                          partner_clinician_id: a98600a7-6c73-4095-ab17-7c5d8c02870c
                          fname: Jane
                          lname: Doe
                          email: jdoe@example.com
                          phone: 000-000-0000
                          position: CNA
                        is_open: false
                        is_called_out: false
                        is_play_mode: false
                        is_deleted: false
                    pagination:
                      current_page: 1
                      per_page: 100
                      total_items: 1
                      total_pages: 1
                      from: 1
                      to: 1
                      has_next_page: false
                      has_previous_page: false
                no_shifts:
                  summary: No shifts synchronized yet
                  value:
                    data: []
                    pagination:
                      current_page: 1
                      per_page: 100
                      total_items: 0
                      total_pages: 0
                      from: 0
                      to: 0
                      has_next_page: false
                      has_previous_page: false
        '401':
          description: Missing, invalid or expired Bearer token. Illustrative body, confirm against staging.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorMessage'
              examples:
                unauthenticated:
                  summary: Missing or invalid token
                  value:
                    message: Unauthenticated.
        '403':
          description: The token is valid but the client is not an agency client.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorMessage'
              examples:
                forbidden:
                  summary: Not an agency client
                  value:
                    message: Forbidden
        '422':
          description: Validation failed. Illustrative body, confirm which parameters are validated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                invalid_date:
                  summary: Invalid date
                  value:
                    message: The given data was invalid.
                    errors:
                      date:
                        - The date must be a valid date in the format Y-m-d.
      security:
        - bearerAuth: []
tags:
  - name: auth
  - name: shifts
components:
  schemas:
    Auth_Access_Token_Response_200:
      type: object
      properties:
        expires_in:
          type: integer
          description: Token lifetime in seconds. `86400` = 24 hours.
        token_type:
          type: string
          description: Always `Bearer`.
        access_token:
          type: string
          description: 'JWT. Send as `Authorization: Bearer <access_token>`.'
      required:
        - expires_in
        - token_type
        - access_token
      title: Auth_Access Token_Response_200
    ShiftPartnerBonus:
      type: object
      description: Bonus for the shift. A `fixed` value is an amount. A `percentage` value is applied to `base_hourly_rate` over the shift length.
      properties:
        type:
          type: string
          enum:
            - fixed
            - percentage
          description: Bonus type.
        value:
          type: number
          description: Fixed amount, or the percentage.
      required:
        - type
        - value
      title: ShiftPartnerBonus
    ShiftPartnerCompensation:
      type: object
      description: Pay details for the clinician. The latest event sent for a clinician replaces the previous values.
      properties:
        base_hourly_rate:
          type: number
          description: Base hourly rate the facility pays without bonuses.
        overtime_hourly_rate:
          type: number
          description: Overtime hourly rate billed to the facility.
        bonus:
          $ref: '#/components/schemas/ShiftPartnerBonus'
      required:
        - base_hourly_rate
        - overtime_hourly_rate
        - bonus
      title: ShiftPartnerCompensation
    ShiftPartnerClinician:
      type: object
      description: Clinician details.
      properties:
        id:
          type: string
          description: Partner clinician ID.
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
        phone:
          type: string
        position:
          type: string
          description: Exact position name, for example `LPN`.
        compensation:
          $ref: '#/components/schemas/ShiftPartnerCompensation'
        clinician_tier:
          type: string
          description: Clinician tier as free text, for example `Tier 1`.
      required:
        - id
        - first_name
        - last_name
        - email
        - phone
        - position
        - compensation
      title: ShiftPartnerClinician
    ShiftRequestEventData:
      type: object
      description: 'Payload for `event_name: shift_request`. The request carries the full shift and clinician details.'
      properties:
        partner_facility_id:
          type: string
          description: Partner facility ID.
        partner_shift_id:
          type: string
          description: Partner shift ID.
        shift_start_time:
          type: string
          format: date-time
          description: Shift start time in UTC (RFC3339).
        shift_end_time:
          type: string
          format: date-time
          description: Shift end time in UTC (RFC3339).
        partner_clinician:
          $ref: '#/components/schemas/ShiftPartnerClinician'
      required:
        - partner_facility_id
        - partner_shift_id
        - shift_start_time
        - shift_end_time
        - partner_clinician
      title: ShiftRequestEventData
    ShiftDetails:
      type: object
      deprecated: true
      description: 'Deprecated. Use `shift_start_time` and `shift_end_time`. Still accepted.'
      properties:
        start_date_time:
          type: string
          format: date-time
          description: Shift start time in UTC (RFC3339).
        end_date_time:
          type: string
          format: date-time
          description: Shift end time in UTC (RFC3339).
      required:
        - start_date_time
        - end_date_time
      title: ShiftDetails
    ShiftClaimedEventData:
      type: object
      description: 'Payload for `event_name: shift_claimed`.'
      properties:
        shift_id:
          type: string
          description: Optional. Stafflion shift ID, only if the partner maps it.
        partner_facility_id:
          type: string
          description: Partner facility ID.
        partner_shift_id:
          type: string
          description: Partner shift ID.
        partner_secondary_shift_id:
          type: string
          description: Optional. Secondary partner shift ID.
        shift_start_time:
          type: string
          format: date-time
          description: 'Shift start time in UTC (RFC3339). Required when Stafflion did not post the shift.'
        shift_end_time:
          type: string
          format: date-time
          description: 'Shift end time in UTC (RFC3339). Required when Stafflion did not post the shift.'
        shift_details:
          $ref: '#/components/schemas/ShiftDetails'
        partner_clinician:
          $ref: '#/components/schemas/ShiftPartnerClinician'
      required:
        - partner_facility_id
        - partner_shift_id
        - partner_clinician
      title: ShiftClaimedEventData
    ShiftUpdateEventData:
      type: object
      description: 'Payload for `event_name: shift_update`. Send only after a `shift_claimed` has been sent for this shift.'
      properties:
        shift_id:
          type: string
          description: Optional. Stafflion shift ID, only if the partner maps it.
        partner_facility_id:
          type: string
          description: Partner facility ID.
        partner_shift_id:
          type: string
          description: Partner shift ID.
        shift_start_time:
          type: string
          format: date-time
          description: New shift start time in UTC (RFC3339).
        shift_end_time:
          type: string
          format: date-time
          description: New shift end time in UTC (RFC3339).
        shift_details:
          $ref: '#/components/schemas/ShiftDetails'
      required:
        - partner_facility_id
        - partner_shift_id
        - shift_start_time
        - shift_end_time
      title: ShiftUpdateEventData
    ShiftCallOutEventData:
      type: object
      description: 'Payload for `event_name: shift_call_out`.'
      properties:
        partner_facility_id:
          type: string
          description: Partner facility ID.
        partner_shift_id:
          type: string
          description: Partner shift ID.
        partner_clinician_id:
          type: string
          description: Partner clinician ID.
      required:
        - partner_facility_id
        - partner_shift_id
        - partner_clinician_id
      title: ShiftCallOutEventData
    ShiftResponse:
      type: object
      description: Shift returned in a successful webhook response.
      properties:
        id:
          type: integer
          description: Stafflion shift ID. Returned when the shift is linked to a Stafflion shift.
        partner_shift_id:
          type: string
          description: Partner shift ID.
        partner_secondary_shift_id:
          description: Secondary partner shift ID, or null.
      required:
        - partner_shift_id
      title: ShiftResponse
    ClinicianResponse:
      type: object
      description: Clinician returned in a successful webhook response.
      properties:
        id:
          type: integer
          description: Stafflion clinician ID.
        partner_clinician_id:
          type: string
          description: Partner clinician ID.
      required:
        - id
        - partner_clinician_id
      title: ClinicianResponse
    Shifts_Webhook_Response_200:
      type: object
      properties:
        message:
          type: string
          description: '`success`.'
        shift:
          $ref: '#/components/schemas/ShiftResponse'
        clinician:
          $ref: '#/components/schemas/ClinicianResponse'
      required:
        - message
        - shift
      title: Shifts_Webhook_Response_200
    OAuthError:
      type: object
      properties:
        error:
          type: string
          description: OAuth error code.
        error_description:
          type: string
          description: Human-readable description.
      required:
        - error
        - error_description
      title: OAuthError
    ErrorMessage:
      type: object
      properties:
        message:
          type: string
          description: Error message. See the event descriptions for the possible values.
      required:
        - message
      title: ErrorMessage
    ValidationError:
      type: object
      properties:
        message:
          type: string
          description: Validation failed.
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Invalid fields, each with a list of messages.
      required:
        - message
      title: ValidationError
    SyncedShiftClinician:
      type: object
      properties:
        id:
          type: integer
        partner_clinician_id:
          type: string
        fname:
          type: string
        lname:
          type: string
        email:
          type: string
        phone:
          type: string
        position:
          type: string
      title: SyncedShiftClinician
    ApiV1PartnersSyncedShiftsGetResponsesContentApplicationJsonSchemaDataItems:
      type: object
      properties:
        date:
          type: string
          format: date
        is_open:
          type: boolean
        end_time:
          type: string
        shift_id:
          type: integer
        clinician:
          oneOf:
            - $ref: '#/components/schemas/SyncedShiftClinician'
            - type: 'null'
        is_deleted:
          type: boolean
        start_time:
          type: string
        is_play_mode:
          type: boolean
          description: When `true`, the shift exists only within Stafflion's system.
        facility_name:
          type: string
        is_called_out:
          type: boolean
        shift_positions:
          type: array
          items:
            type: string
        partner_shift_id:
          type: string
        scheduled_shift_id:
          description: Any type
        partner_facility_id:
          type: string
        partner_secondary_shift_id:
          description: Any type
      required:
        - date
        - is_open
        - end_time
        - shift_id
        - is_deleted
        - start_time
        - is_play_mode
        - facility_name
        - is_called_out
        - shift_positions
        - partner_shift_id
        - partner_facility_id
      title: ApiV1PartnersSyncedShiftsGetResponsesContentApplicationJsonSchemaDataItems
    ApiV1PartnersSyncedShiftsGetResponsesContentApplicationJsonSchemaPagination:
      type: object
      properties:
        to:
          type: integer
        from:
          type: integer
        per_page:
          type: integer
        total_items:
          type: integer
        total_pages:
          type: integer
        current_page:
          type: integer
        has_next_page:
          type: boolean
        has_previous_page:
          type: boolean
      required:
        - to
        - from
        - per_page
        - total_items
        - total_pages
        - current_page
        - has_next_page
        - has_previous_page
      title: ApiV1PartnersSyncedShiftsGetResponsesContentApplicationJsonSchemaPagination
    Shifts_Synced_Shifts_Response_200:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ApiV1PartnersSyncedShiftsGetResponsesContentApplicationJsonSchemaDataItems'
        pagination:
          $ref: '#/components/schemas/ApiV1PartnersSyncedShiftsGetResponsesContentApplicationJsonSchemaPagination'
      required:
        - data
        - pagination
      title: Shifts_Synced Shifts_Response_200
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
