openapi: 3.0.0
info:
  title: Person
  version: 2.0.0
  description: >
    A **Person** represents a worker or individual registered in a project
    (company).

    People belong to a project and are assigned to one or more offices via
    PersonOffice.


    **Hierarchy:** Project (company) → Office (work location) → Person (worker).


    People are the main subjects of the compliance schedule: action
    subscriptions (ActionSubscription)

    and certificates (Certificate) are typically linked to a person via their
    `person_id`.


    Use this API to:

    - Find a person by name, last name, or tax code within a project

    - Get the `person_id` needed to create certificates, compliance schedule
    entries, or office assignments

    - Look up personal and employment details of a worker


    A person can have a linked user account via `entity_id` (email) and
    `related_user` (permission level).
servers:
  - url: http://service.4hse.com/
security:
  - AccessToken: []
  - OAuth2: []
webhooks:
  PERSON::CREATE:
    post:
      summary: Person created
      security: []
      description: |
        Triggered when a new person is created.
      requestBody:
        description: The newly created person
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Person"
            example:
              person_id: e3a1f5d2-8c4b-4e7a-9f6d-2b1c3d4e5f6a
              first_name: John
              last_name: Smith
              code: JS001
              birth_date: 1985-03-15
              entity_id: john.smith@example.com
              project_id: b5a7d602-3fc9-4e2a-9d1a-6e5b4c8f7a01
              related_user: relatedUser_user
      responses:
        "200":
          description: Acknowledgment of the webhook event
  PERSON::UPDATE:
    post:
      summary: Person updated
      security: []
      description: |
        Triggered when a person is updated. Provides the person ID and an object
        containing old and new values for each updated field.
      requestBody:
        description: Updated person with old and new values for changed fields
        content:
          application/json:
            schema:
              type: object
              properties:
                entity_id:
                  type: string
                  format: uuid
                  description: The person_id of the updated person.
                updated_fields:
                  type: object
                  additionalProperties:
                    type: object
                    properties:
                      old: {}
                      new: {}
                  description: Each key is an updated field name, with its old and new values.
              required:
                - entity_id
                - updated_fields
            example:
              entity_id: e3a1f5d2-8c4b-4e7a-9f6d-2b1c3d4e5f6a
              updated_fields:
                last_name:
                  new: Johnson
                  old: Smith
      responses:
        "200":
          description: Acknowledgment of the webhook event
  PERSON::DELETE:
    post:
      summary: Person deleted
      security: []
      description: |
        Triggered when a person is deleted.
      requestBody:
        description: The deleted person
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Person"
            example:
              person_id: e3a1f5d2-8c4b-4e7a-9f6d-2b1c3d4e5f6a
              first_name: John
              last_name: Smith
              code: JS001
              project_id: b5a7d602-3fc9-4e2a-9d1a-6e5b4c8f7a01
      responses:
        "200":
          description: Acknowledgment of the webhook event
paths:
  /v2/person/index:
    post:
      summary: index
      operationId: indexPerson
      tags:
        - Persons
      description: >
        Returns a paginated list of persons.


        Most useful filters:

        - `project_id` + `last_name`: find a person by last name within a
        company

        - `project_id` + `tax_code`: find a person by tax code (exact match)

        - `project_id` + `code`: find a person by company employee code

        - `is_employee: 1`: filter employees only
      requestBody:
        description: Parameters for searching persons
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PersonIndexParams"
            example:
              filter:
                project_id: b5a7d602-3fc9-4e2a-9d1a-6e5b4c8f7a01
                last_name: Smith
                is_employee: 1
              per-page: 50
              page: 1
              sort: last_name
      responses:
        "200":
          description: List of persons
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ViewPerson"
              example:
                - person_id: e3a1f5d2-8c4b-4e7a-9f6d-2b1c3d4e5f6a
                  first_name: John
                  last_name: Smith
                  code: JS001
                  tax_code: SMTJHN85C15H501Z
                  birth_date: 1985-03-15
                  is_employee: 1
                  is_prevention_people: 0
                  entity_id: john.smith@example.com
                  project_id: b5a7d602-3fc9-4e2a-9d1a-6e5b4c8f7a01
                  project_name: Acme Construction Ltd
                  project_status: active
                  owned_active: true
                  parent_active: true
          headers:
            X-Pagination-Current-Page:
              schema:
                type: integer
              description: Current page
            X-Pagination-Page-Count:
              schema:
                type: integer
              description: Total number of pages
            X-Pagination-Per-Page:
              schema:
                type: integer
              description: Number of items per page
            X-Pagination-Total-Count:
              schema:
                type: integer
              description: Total number of items
  /v2/person/view/{id}:
    get:
      summary: view
      operationId: viewPerson
      tags:
        - Persons
      description: >
        Retrieves a single person. Can be looked up in two ways:

        - By `person_id` (passed as path parameter `id`)

        - By `code` + `project_id` (passed as query parameters, if the
        `person_id` is not known)
      parameters:
        - name: id
          in: path
          description: The person_id to retrieve. Required if `code` and `project_id` are
            not provided.
          required: true
          schema:
            type: string
            format: uuid
        - name: code
          in: query
          description: Employee code. Required together with `project_id` if `id` is not
            provided.
          required: false
          schema:
            type: string
        - name: project_id
          in: query
          description: Required together with `code` if `id` is not provided.
          required: false
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Person found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ViewPerson"
              example:
                person_id: e3a1f5d2-8c4b-4e7a-9f6d-2b1c3d4e5f6a
                first_name: John
                last_name: Smith
                code: JS001
                tax_code: SMTJHN85C15H501Z
                birth_date: 1985-03-15
                is_employee: 1
                is_prevention_people: 0
                entity_id: john.smith@example.com
                project_id: b5a7d602-3fc9-4e2a-9d1a-6e5b4c8f7a01
                project_name: Acme Construction Ltd
                project_status: active
                owned_active: true
                parent_active: true
        "404":
          description: Person not found
  /v2/person/create:
    post:
      summary: create
      operationId: createPerson
      tags:
        - Persons
      description: >
        Creates a new person within a project.


        Requires `first_name`, `last_name`, and `project_id`. The `person_id` is
        auto-generated if not provided.


        Creating a person does **not** assign them to any office. To assign a
        person to an office,

        create a PersonOffice after creating the person.


        Special parameter `unique_code`: if `true` and a person with the same
        `code` already exists

        in the project, the existing person is updated instead of creating a new
        one. If the person

        was historicized, they are reactivated with a new period.
      requestBody:
        description: Person object to be created
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePerson"
            example:
              first_name: John
              last_name: Smith
              code: JS001
              tax_code: SMTJHN85C15H501Z
              birth_date: 1985-03-15
              is_employee: 1
              entity_id: john.smith@example.com
              project_id: b5a7d602-3fc9-4e2a-9d1a-6e5b4c8f7a01
              related_user: relatedUser_user
      responses:
        "201":
          description: Person created successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Person"
              example:
                person_id: e3a1f5d2-8c4b-4e7a-9f6d-2b1c3d4e5f6a
                first_name: John
                last_name: Smith
                code: JS001
                tax_code: SMTJHN85C15H501Z
                birth_date: 1985-03-15
                is_employee: 1
                entity_id: john.smith@example.com
                project_id: b5a7d602-3fc9-4e2a-9d1a-6e5b4c8f7a01
                related_user: relatedUser_user
  /v2/person/update/{id}:
    put:
      summary: update
      operationId: updatePerson
      tags:
        - Persons
      description: >
        Updates an existing person. Can be identified by `person_id` or by
        `code` + `project_id`.
      parameters:
        - name: id
          in: path
          description: The person_id to update. Required if `code` and `project_id` are
            not provided.
          required: true
          schema:
            type: string
            format: uuid
        - name: code
          in: query
          description: Employee code. Required together with `project_id` if `id` is not
            provided.
          required: false
          schema:
            type: string
        - name: project_id
          in: query
          description: Required together with `code` if `id` is not provided.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        description: Person fields to update
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Person"
            example:
              last_name: Johnson
              code: JJ002
      responses:
        "200":
          description: Person updated successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Person"
              example:
                person_id: e3a1f5d2-8c4b-4e7a-9f6d-2b1c3d4e5f6a
                first_name: John
                last_name: Johnson
                code: JJ002
        "404":
          description: Person not found
  /v2/person/historicize/{id}:
    post:
      summary: historicize
      operationId: historicizePerson
      tags:
        - Persons
      description: >
        Historicizes a person — marks them as no longer active from a specified
        date.

        The person and their data remain in the system but will not appear in
        normal queries.

        Use the `history: true` parameter in the index to include historicized
        persons.


        If no date is specified, the current date is used.


        Historicization propagates automatically: all child entities of the
        person (office assignments,

        compliance schedule entries, etc.) are excluded from normal queries
        without any direct changes

        to their data. The `parent_active` field in child entity responses
        reflects this state.

        This applies at all levels of the hierarchy.
      parameters:
        - name: id
          in: path
          description: The person_id to historicize. Required if `code` and `project_id`
            are not provided.
          required: true
          schema:
            type: string
            format: uuid
        - name: code
          in: query
          description: Employee code. Required together with `project_id` if `id` is not
            provided.
          required: false
          schema:
            type: string
        - name: project_id
          in: query
          description: Required together with `code` if `id` is not provided.
          required: false
          schema:
            type: string
            format: uuid
      requestBody:
        description: Historicization date
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                date:
                  type: string
                  format: date
                  description: The date from which the person is no longer active. Defaults to the
                    current date.
            example:
              date: 2025-06-10
      responses:
        "200":
          description: Person historicized successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Person"
        "404":
          description: Person not found
  /v2/person/delete/{id}:
    delete:
      summary: delete
      operationId: deletePerson
      tags:
        - Persons
      description: >
        Deletes a person. Can be identified by `person_id` or by `code` +
        `project_id`.


        With `historicize=true`, the person is historicized instead of deleted
        (equivalent to the historicize endpoint).

        If `force=false` (default), the operation is interrupted and the
        response lists related entities.

        If `force=true`, the person and all related entities are deleted.
      parameters:
        - name: id
          in: path
          description: The person_id to delete. Required if `code` and `project_id` are
            not provided.
          required: true
          schema:
            type: string
            format: uuid
        - name: code
          in: query
          description: Employee code. Required together with `project_id` if `id` is not
            provided.
          required: false
          schema:
            type: string
        - name: project_id
          in: query
          description: Required together with `code` if `id` is not provided.
          required: false
          schema:
            type: string
            format: uuid
        - name: historicize
          in: query
          description: If true, the person is historicized instead of deleted.
          required: false
          schema:
            type: boolean
            default: false
        - name: force
          in: query
          description: If true, deletes the person and all related entities. If false,
            returns a preview of affected entities.
          schema:
            type: boolean
            default: false
      responses:
        "204":
          description: Person deleted successfully
        "400":
          description: Preview of entities that would be deleted (returned when force=false)
  /v2/person/verify/{id}:
    get:
      summary: verify
      operationId: verifyPerson
      tags:
        - Persons
      description: >
        Returns aggregated person data for public badge verification.

        Includes personal details, employer info, profile photo, and
        certificates with status.


        Accessible by authenticated users and via UrlSign (pre-signed URL).

        Attachment and photo URLs in the response are pre-signed with a 24h TTL.
      parameters:
        - name: id
          in: path
          description: The person_id to verify.
          required: true
          schema:
            type: string
            format: uuid
        - name: urlsign
          in: query
          description: URL signature token for unauthenticated access.
          required: false
          schema:
            type: string
      responses:
        "200":
          description: Person verification data
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PersonVerifyResponse"
        "404":
          description: Person not found
components:
  schemas:
    Person:
      type: object
      properties:
        person_id:
          type: string
          format: uuid
          description: Unique identifier of the person. Auto-generated if not provided on
            creation. Certificates reference the person via the `resource_id`
            field — pass this value. Compliance schedule entries reference the
            person via the `subscriber_id` field — pass this value. If unknown,
            use the index endpoint filtering by `project_id` and `last_name` or
            `tax_code` to find it.
        code:
          type: string
          maxLength: 50
          description: Alternative identifier, typically the company employee code. Can be
            used with `project_id` as an alternative to `person_id` for view,
            update, historicize, and delete operations.
        first_name:
          type: string
          maxLength: 70
          description: First name.
        last_name:
          type: string
          maxLength: 70
          description: Last name.
        street:
          type: string
          maxLength: 255
          description: Residential address.
        locality:
          type: string
          maxLength: 50
          description: City.
        postal_code:
          type: string
          maxLength: 50
          description: Postal code.
        region:
          type: string
          maxLength: 150
          description: Region or province.
        sex:
          type: string
          maxLength: 10
          description: Sex.
        country:
          type: string
          maxLength: 50
          description: ISO 3166-1 alpha-2 country code.
        birth_date:
          type: string
          format: date
          description: Date of birth.
        birth_place:
          type: string
          maxLength: 150
          description: Place of birth.
        tax_code:
          type: string
          maxLength: 30
          description: Tax code (fiscal code). Useful for unique lookup within a project.
        note:
          type: string
          description: Free-text notes.
        contract_type:
          type: string
          description: Employment contract type.
        is_employee:
          type: integer
          enum:
            - 0
            - 1
          description: >
            - `1`: the person is a company employee

            - `0`: the person is not an employee

            Normally a person is either an employee (`is_employee: 1`) or
            external prevention staff (`is_prevention_people: 1`), not both.
        is_prevention_people:
          type: integer
          enum:
            - 0
            - 1
          description: >
            - `1`: the person is external prevention and safety staff (e.g.
            external safety officer, occupational physician)

            - `0`: the person is not external prevention staff

            Normally a person is either an employee (`is_employee: 1`) or
            external prevention staff (`is_prevention_people: 1`), not both.
        entity_id:
          type: string
          maxLength: 50
          description: The email of the linked user account. When set together with
            `related_user`, the person is connected to a platform account that
            can authenticate.
        project_id:
          type: string
          format: uuid
          description: The project (company) this person belongs to. Pass the `project_id`
            obtained from the Project API.
        related_user:
          type: string
          description: |
            The permission level of the linked user account on this person:
            - `relatedUser_user`: basic access to their own profile
            - `relatedUser_manager`: management access
            If not specified, inherited from the project configuration.
      required:
        - first_name
        - last_name
        - project_id
    CreatePerson:
      allOf:
        - $ref: "#/components/schemas/Person"
        - type: object
          properties:
            unique_code:
              type: boolean
              description: If true and a person with the same `code` already exists in the
                project, the existing person is updated instead of creating a
                new one. If the person was historicized, they are reactivated
                with a new period.
              default: false
    ViewPerson:
      type: object
      properties:
        person_id:
          type: string
          format: uuid
          description: Unique identifier of the person. Certificates reference the person
            via `resource_id`, compliance schedule entries via `subscriber_id` —
            pass this value.
        code:
          type: string
          maxLength: 50
          description: Alternative identifier, typically the company employee code.
        first_name:
          type: string
          maxLength: 70
          description: First name.
        last_name:
          type: string
          maxLength: 70
          description: Last name.
        street:
          type: string
          maxLength: 255
          description: Residential address.
        locality:
          type: string
          maxLength: 50
          description: City.
        postal_code:
          type: string
          maxLength: 50
          description: Postal code.
        region:
          type: string
          maxLength: 150
          description: Region or province.
        sex:
          type: string
          maxLength: 10
          description: Sex.
        country:
          type: string
          maxLength: 50
          description: ISO 3166-1 alpha-2 country code.
        birth_date:
          type: string
          format: date
          description: Date of birth.
        birth_place:
          type: string
          maxLength: 150
          description: Place of birth.
        tax_code:
          type: string
          maxLength: 30
          description: Tax code (fiscal code).
        note:
          type: string
          description: Free-text notes.
        contract_type:
          type: string
          description: Employment contract type.
        is_employee:
          type: integer
          enum:
            - 0
            - 1
          description: |
            - `1`: company employee
            - `0`: not an employee
        is_prevention_people:
          type: integer
          enum:
            - 0
            - 1
          description: |
            - `1`: external prevention and safety staff
            - `0`: not external prevention staff
        entity_id:
          type: string
          description: The email of the linked user account.
        project_id:
          type: string
          format: uuid
          description: The project (company) this person belongs to.
        project_name:
          type: string
          description: Name of the project (company) this person belongs to.
        project_status:
          type: string
          enum:
            - active
            - suspended
            - pending_delete
            - deleted
          description: Status of the parent project.
        project_country:
          type: string
          maxLength: 50
          description: Country of the parent project.
        related_user:
          type: string
          description: |
            Permission level of the linked user account:
            - `relatedUser_user`: basic access
            - `relatedUser_manager`: management access
        owned_active:
          type: boolean
          nullable: true
          description: Whether this person is currently active in their validity period.
        parent_active:
          type: boolean
          nullable: true
          description: Whether the parent project is currently active.
    PersonIndexParams:
      type: object
      properties:
        filter:
          $ref: "#/components/schemas/ViewPerson"
        per-page:
          type: integer
          minimum: 1
          default: 100
        page:
          type: integer
          minimum: 1
          default: 1
        sort:
          type: string
          description: Field to sort by. Prefix with minus for descending order (e.g.
            "-last_name").
          enum:
            - first_name
            - last_name
            - code
            - tax_code
            - birth_date
        history:
          type: boolean
          description: If true, includes historicized (no longer active) entries in the
            results. By default only current entries are returned.
      example:
        filter:
          project_id: b5a7d602-3fc9-4e2a-9d1a-6e5b4c8f7a01
          last_name: Smith
          is_employee: 1
        per-page: 50
        page: 1
        sort: last_name
    PersonVerifyResponse:
      type: object
      properties:
        person:
          type: object
          properties:
            first_name:
              type: string
            last_name:
              type: string
            tax_code:
              type: string
              nullable: true
            birth_date:
              type: string
              format: date
              nullable: true
        profile_photo_url:
          type: string
          nullable: true
          description: Pre-signed URL to the profile photo (24h TTL)
        employer:
          type: object
          properties:
            name:
              type: string
            project_id:
              type: string
              format: uuid
        certificates:
          type: array
          items:
            type: object
            properties:
              certificate_id:
                type: string
                format: uuid
              name:
                type: string
              action_type:
                type: string
                enum:
                  - TRAINING
                  - MAINTENANCE
                  - HEALTH
                  - CHECK
                  - PER
              date_release:
                type: string
                format: date
              date_expire:
                type: string
                format: date
                nullable: true
              status:
                type: string
                enum:
                  - valid
                  - expired
                  - expiring
              attachment_urls:
                type: array
                items:
                  type: string
                description: Pre-signed URLs to certificate attachments (24h TTL)
  securitySchemes:
    AccessToken:
      type: apiKey
      in: query
      name: access-token
    OAuth2:
      type: oauth2
      flows:
        password:
          tokenUrl: https://auth.4hse.com/realms/4hse/protocol/openid-connect/token
          scopes: {}
