> ## Documentation Index
> Fetch the complete documentation index at: https://api-docs.tribalhabits.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Batch create or update

> This endpoint creates or updates multiple users in one call. A user whose `user_name` does not match an existing user is created with all the fields given. If the `user_name` already exists in the portal, that user is updated with only the fields that are given. To clear an optional field, send an empty string, eg `"manager_email": ""`.

Each user object accepts the same fields as [Create a new user](/api-reference/users/create-a-new-user), plus the enrolment fields below. `user_name` matching is case-insensitive. New users need `user_name`, `first_name`, `last_name` and `email`.

In the response, `created`, `updated` and `unchanged` count users the batch attempted to create or update, including users that then failed with an error. A failed user is listed in `error_list` and is not saved. `enrolled` counts enrolments successfully made, and `invitations_sent` and `invitations_failed` count invitation emails. `errors` is the number of entries in `error_list`, which includes enrolment errors as well as users with errors.

### Errors

If there are no errors, the `errors` count in the response will be 0. If there are errors in the batch run, there will be an error count above 0. The batch run will execute all the rows that do not have any errors, and those that do have errors it will return a list of who the user was (`user_name` and `email`), and the error reason. Enrolment errors are also added to `error_list`, with only an `error_reason`. You will only need to fix those records with errors, and run in a batch again, but you can run the whole list again.

A request without a `people` array, or with more than 100 people, returns `400` and nothing is saved.

### Enrolment fields

Each user object can also enrol that user in up to 5 topics or pathways, using the `enrolment_code_N` fields where N is from 1..5. See [How can I enrol people or import enrolments via a spreadsheet (CSV)](https://support.tribalhabits.com/en/articles/5735753-how-can-i-enrol-people-or-import-enrolments-via-a-spreadsheet-csv) for an explanation of enrolment codes.

<Note>Maximum permitted batch size is 100.</Note>




## OpenAPI

````yaml /openapi.yaml post /users/batch_save
openapi: 3.1.0
info:
  title: Tribal Habits Public API
  version: '1'
  description: >
    The Tribal Habits Public API V1. Use it to manage your Tribal Habits user
    accounts (list, show, create and update) and other entities.


    Replace `myorg` in the server URL with your organisation's subdomain. See
    [Vanity URL](/vanity-url) if you use your own domain.
servers:
  - url: https://myorg.tribalhabits.com/papi
security:
  - ApiKeyHeader: []
tags:
  - name: Users
    description: Users
  - name: Topics
    description: Topics
  - name: Pathways
    description: Pathways
  - name: Enrolments
    description: Topic and Pathway Enrolments
  - name: Certificates
    description: >-
      Certificate generation and retrieval for completed topic and pathway
      enrolments.
  - name: External Knowledge
    description: Knowledge obtained from a source external of Tribal Habits.
  - name: Webhooks
    description: >-
      Webhooks triggered from certain actions in code that will call URLs
      assigned by you.
paths:
  /users/batch_save:
    post:
      tags:
        - Users
      summary: Batch create or update
      description: >
        This endpoint creates or updates multiple users in one call. A user
        whose `user_name` does not match an existing user is created with all
        the fields given. If the `user_name` already exists in the portal, that
        user is updated with only the fields that are given. To clear an
        optional field, send an empty string, eg `"manager_email": ""`.


        Each user object accepts the same fields as [Create a new
        user](/api-reference/users/create-a-new-user), plus the enrolment fields
        below. `user_name` matching is case-insensitive. New users need
        `user_name`, `first_name`, `last_name` and `email`.


        In the response, `created`, `updated` and `unchanged` count users the
        batch attempted to create or update, including users that then failed
        with an error. A failed user is listed in `error_list` and is not saved.
        `enrolled` counts enrolments successfully made, and `invitations_sent`
        and `invitations_failed` count invitation emails. `errors` is the number
        of entries in `error_list`, which includes enrolment errors as well as
        users with errors.


        ### Errors


        If there are no errors, the `errors` count in the response will be 0. If
        there are errors in the batch run, there will be an error count above 0.
        The batch run will execute all the rows that do not have any errors, and
        those that do have errors it will return a list of who the user was
        (`user_name` and `email`), and the error reason. Enrolment errors are
        also added to `error_list`, with only an `error_reason`. You will only
        need to fix those records with errors, and run in a batch again, but you
        can run the whole list again.


        A request without a `people` array, or with more than 100 people,
        returns `400` and nothing is saved.


        ### Enrolment fields


        Each user object can also enrol that user in up to 5 topics or pathways,
        using the `enrolment_code_N` fields where N is from 1..5. See [How can I
        enrol people or import enrolments via a spreadsheet
        (CSV)](https://support.tribalhabits.com/en/articles/5735753-how-can-i-enrol-people-or-import-enrolments-via-a-spreadsheet-csv)
        for an explanation of enrolment codes.


        <Note>Maximum permitted batch size is 100.</Note>
      operationId: batchSaveUsers
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - people
              properties:
                people:
                  type: array
                  maxItems: 100
                  description: >-
                    An array of user objects whereby each user must include a
                    `user_name` attribute denoting the unique id of the user to
                    be created or updated.
                  items:
                    $ref: '#/components/schemas/BatchPerson'
                send_invitations:
                  type: string
                  enum:
                    - new
                    - all
                    - 'no'
                  description: >-
                    Whether to send invitation emails. `"new"` sends them only
                    to newly created people, `"all"` sends them to everyone in
                    the batch, and `"no"` (or leaving out this field) sends
                    none.
            examples:
              create_and_enrol:
                summary: Create or update users, with one error
                value:
                  people:
                    - user_name: '12345'
                      first_name: Bilbo
                      last_name: Baggins
                      email: bilbo@myorg.com
                      language: fr
                    - user_name: '12346'
                      first_name: Frodo
                      last_name: Baggins
                      email: frodo@myorg.com
                      enrolment_code_1: TH-4-77-NEW
                      enrolment_code_1_due_date: 31/12/2025
                    - user_name: '12347'
                      first_name: Samwise
                      last_name: Gamgee
                      email: not-an-email
                  send_invitations: new
              deactivate:
                summary: Deactivate 2 existing users
                value:
                  people:
                    - user_name: '12345'
                      active: false
                    - user_name: '12346'
                      active: false
      responses:
        '200':
          description: Batch results
          content:
            application/json:
              schema:
                type: object
                properties:
                  created:
                    type: integer
                    description: Number of users created
                  updated:
                    type: integer
                    description: Number of users updated
                  unchanged:
                    type: integer
                    description: Number of users unchanged
                  enrolled:
                    type: integer
                    description: Number of enrolments made
                  invitations_sent:
                    type: integer
                    description: Number of invitation emails sent
                  invitations_failed:
                    type: integer
                    description: Number of invitation emails that failed
                  errors:
                    type: integer
                    description: Number of errors. `0` if there are no errors
                  error_list:
                    type: array
                    description: >-
                      One entry per error. Enrolment errors have only an
                      `error_reason`
                    items:
                      type: object
                      properties:
                        user_name:
                          type: string
                        email:
                          type: string
                        error_reason:
                          type: string
              examples:
                create_and_enrol:
                  summary: Create or update users, with one error
                  value:
                    created: 3
                    updated: 0
                    unchanged: 0
                    enrolled: 1
                    invitations_sent: 2
                    invitations_failed: 0
                    errors: 1
                    error_list:
                      - user_name: '12347'
                        email: not-an-email
                        error_reason: Email is invalid
                deactivate:
                  summary: Deactivate 2 existing users
                  value:
                    created: 0
                    updated: 2
                    unchanged: 0
                    enrolled: 0
                    invitations_sent: 0
                    invitations_failed: 0
                    errors: 0
                    error_list: []
        '400':
          $ref: '#/components/responses/BadRequestGeneric'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '406':
          $ref: '#/components/responses/NotAcceptable'
        '429':
          $ref: '#/components/responses/TooManyRequests'
components:
  schemas:
    BatchPerson:
      description: >-
        A user object. Accepts the same fields as [Create a new
        user](/api-reference/users/create-a-new-user), plus the enrolment
        fields. New users need `user_name`, `first_name`, `last_name` and
        `email`. Existing users are updated with only the fields that are given.
      allOf:
        - $ref: '#/components/schemas/UserCreateFields'
        - $ref: '#/components/schemas/BatchEnrolmentFields'
        - type: object
          required:
            - user_name
          patternProperties:
            ^X-:
              description: Custom field. See [Custom fields](/custom-fields)
    UserCreateFields:
      type: object
      properties:
        user_name:
          type: string
          description: Unique identifier for the new user. Stored in lower case
        first_name:
          type: string
          description: User's first name - used for display purposes
        last_name:
          type: string
          description: User's last name - used for display purposes
        email:
          type: string
          description: >-
            User's email address (must be unique within your organisation) -
            used for email notifications
        type:
          type: string
          description: >-
            Type of user. One of `Client`, `Contractor`, `Customer`, `Director`,
            `Employee`, `Lead`, `Member`, `Parent`, `Partner`, `Practitioner`,
            `Prospect`, `Staff`, `Student`, `Subscriber`, `Supporter`,
            `Volunteer`, `Worker`. Case-insensitive. Defaults to `Staff`
        manager_email:
          type: string
          description: >-
            Email address of the user's manager - used for email notifications
            and team reporting
        manager_2_email:
          type: string
          description: >-
            Email address of the user's 2nd manager - used for email
            notifications and team reporting
        manager_3_email:
          type: string
          description: >-
            Email address of the user's 3rd manager - used for email
            notifications and team reporting
        manager_4_email:
          type: string
          description: >-
            Email address of the user's 4th manager - used for email
            notifications and team reporting
        manager_5_email:
          type: string
          description: >-
            Email address of the user's 5th manager - used for email
            notifications and team reporting
        mentor_email:
          type: string
          description: Email address of the user's mentor - used for email notifications
        language:
          type: string
          description: >-
            One of `en`, `fj`, `fr`, `de`, `hi-IN`, `id`, `it`, `ja`, `ko`,
            `mi`, `ms`, `ne`, `pa-IN`, `pt`, `sm`, `zh-Hans`, `es`, `tl`, `ta`,
            `te`, `th`, `to`, `zh-Hant-HK`, `vi`. Case-insensitive. Unrecognised
            values are ignored
        timezone:
          type: string
          description: >-
            User's time zone, eg `Australia/Sydney`. Defaults to your
            organisation's time zone
        lms_learner_id:
          type: string
          description: >-
            User's id in company LMS - used for matching users in Tribal Habits.
            The combination of `lms_reference` and `lms_learner_id` must be
            unique
        lms_reference:
          type: string
          description: Company LMS name - used for matching users in Tribal Habits
        login:
          type: string
          enum:
            - internal
            - external
          description: '`internal` (Tribal Habits login) or `external` (single sign-on)'
        active:
          type: boolean
          description: Specify `false` to create the user account but disable login
        creator:
          type: boolean
          description: Specify `true` to allow this user to create topics
        user_notifications_enabled:
          type: boolean
          description: User will receive email notifications
        force_pwd_reset_next_login:
          type: boolean
          description: User must change their password at next login
        manager_email_report_1st_month:
          type: boolean
          description: Send the manager email report on the 1st of the month
        manager_email_report_15th_month:
          type: boolean
          description: Send the manager email report on the 15th of the month
        password:
          type: string
          description: >-
            8 to 72 characters, with at least one number, one upper case and one
            lower case character
        deactivate_scheduled_date:
          type: string
          description: >-
            A future date this account will be deactivated. Formats:
            'yyyy-mm-dd', 'dd/mm/yyyy' or 'dd/mm/yy'
        reactivate_scheduled_date:
          type: string
          description: >-
            A future date this account will be reactivated. Same formats as
            `deactivate_scheduled_date`
        team_leader_report_frequency:
          type: string
          description: >-
            Frequency of team leader report emails, if the user is a team
            leader. One of `every_half`, `every_quarter`, `once_a_month`,
            `twice_a_month`, `every_monday`, `every_tuesday`, `every_wednesday`,
            `every_thursday`, `every_friday`, `every_weekday`, `every_day`.
            `none` or an empty value clears it. Any other value returns `400`
        learner_report_frequency:
          type: string
          description: >-
            Frequency of learner report emails. Same values as
            `team_leader_report_frequency`
        assessor_report_frequency:
          type: string
          description: >-
            Frequency of assessor report emails, if the user is an assessor or
            creator. Same values as `team_leader_report_frequency`
        default_landing_page:
          type: string
          description: >-
            One of `Assess`, `Create`, `Admin`, `Dashboard - Overview`,
            `Dashboard - Calendar`, `Training - Enrolments`, `Profile`.
            Case-insensitive. Unrecognised values are ignored
        role_1:
          type: string
          description: >-
            Name of the role assigned to the user in position 1. The role must
            already exist, otherwise `400` is returned. An empty value removes
            the role in that position
        role_2:
          type: string
          description: >-
            Name of the role assigned to the user in position 2. The role must
            already exist, otherwise `400` is returned. An empty value removes
            the role in that position
        role_3:
          type: string
          description: >-
            Name of the role assigned to the user in position 3. The role must
            already exist, otherwise `400` is returned. An empty value removes
            the role in that position
        role_4:
          type: string
          description: >-
            Name of the role assigned to the user in position 4. The role must
            already exist, otherwise `400` is returned. An empty value removes
            the role in that position
        role_5:
          type: string
          description: >-
            Name of the role assigned to the user in position 5. The role must
            already exist, otherwise `400` is returned. An empty value removes
            the role in that position
        groups:
          description: >-
            Group names separated with a semicolon, eg `"groups":
            "Brisbane;Bundaberg"`, or an array of group names, eg `"groups":
            ["Brisbane", "Bundaberg"]`. Groups must already exist, otherwise
            `400` is returned. Replaces all of the user's current groups; an
            empty value removes the user from all groups
          oneOf:
            - title: Semicolon-separated
              type: string
            - title: Array
              type: array
              items:
                type: string
        goals:
          description: >-
            Goal names separated with a semicolon, eg `"goals": "Goal1;Goal2"`,
            or an array of goal names, eg `"goals": ["Goal1", "Goal2"]`.
            Replaces all of the user's current goals. Goals must already exist,
            otherwise `400` is returned. An empty value removes all goals
          oneOf:
            - title: Semicolon-separated
              type: string
            - title: Array
              type: array
              items:
                type: string
        X-[Custom]:
          description: >-
            Custom field. Replace `[Custom]` with the custom field name. See
            [Custom fields](/custom-fields)
        send_invitation:
          type: boolean
          description: Specify `true` to send an invitation email to the user
      patternProperties:
        ^X-:
          description: Custom field. See [Custom fields](/custom-fields)
    BatchEnrolmentFields:
      type: object
      properties:
        enrolment_code_1:
          type: string
          description: Enrolment code for enrolment 1, eg `TH-4-77-NEW`
        enrolment_code_1_due_date:
          type: string
          description: >-
            Due date for enrolment 1. Formats: 'yyyy-mm-dd', 'dd/mm/yyyy' or
            'dd/mm/yy'
        enrolment_code_1_conquered_date:
          type: string
          description: >-
            Completion date for enrolment 1, used with completion codes. Same
            formats as the due date
        enrolment_code_1_certified_date:
          type: string
          description: >-
            Date the certification for enrolment 1 is valid until. Same formats
            as the due date
        enrolment_code_1_skill_attained:
          type: string
          description: Skill names attained for enrolment 1, separated with a semicolon
        enrolment_code_2:
          type: string
          description: Enrolment code for enrolment 2, eg `TH-4-77-NEW`
        enrolment_code_2_due_date:
          type: string
          description: >-
            Due date for enrolment 2. Formats: 'yyyy-mm-dd', 'dd/mm/yyyy' or
            'dd/mm/yy'
        enrolment_code_2_conquered_date:
          type: string
          description: >-
            Completion date for enrolment 2, used with completion codes. Same
            formats as the due date
        enrolment_code_2_certified_date:
          type: string
          description: >-
            Date the certification for enrolment 2 is valid until. Same formats
            as the due date
        enrolment_code_2_skill_attained:
          type: string
          description: Skill names attained for enrolment 2, separated with a semicolon
        enrolment_code_3:
          type: string
          description: Enrolment code for enrolment 3, eg `TH-4-77-NEW`
        enrolment_code_3_due_date:
          type: string
          description: >-
            Due date for enrolment 3. Formats: 'yyyy-mm-dd', 'dd/mm/yyyy' or
            'dd/mm/yy'
        enrolment_code_3_conquered_date:
          type: string
          description: >-
            Completion date for enrolment 3, used with completion codes. Same
            formats as the due date
        enrolment_code_3_certified_date:
          type: string
          description: >-
            Date the certification for enrolment 3 is valid until. Same formats
            as the due date
        enrolment_code_3_skill_attained:
          type: string
          description: Skill names attained for enrolment 3, separated with a semicolon
        enrolment_code_4:
          type: string
          description: Enrolment code for enrolment 4, eg `TH-4-77-NEW`
        enrolment_code_4_due_date:
          type: string
          description: >-
            Due date for enrolment 4. Formats: 'yyyy-mm-dd', 'dd/mm/yyyy' or
            'dd/mm/yy'
        enrolment_code_4_conquered_date:
          type: string
          description: >-
            Completion date for enrolment 4, used with completion codes. Same
            formats as the due date
        enrolment_code_4_certified_date:
          type: string
          description: >-
            Date the certification for enrolment 4 is valid until. Same formats
            as the due date
        enrolment_code_4_skill_attained:
          type: string
          description: Skill names attained for enrolment 4, separated with a semicolon
        enrolment_code_5:
          type: string
          description: Enrolment code for enrolment 5, eg `TH-4-77-NEW`
        enrolment_code_5_due_date:
          type: string
          description: >-
            Due date for enrolment 5. Formats: 'yyyy-mm-dd', 'dd/mm/yyyy' or
            'dd/mm/yy'
        enrolment_code_5_conquered_date:
          type: string
          description: >-
            Completion date for enrolment 5, used with completion codes. Same
            formats as the due date
        enrolment_code_5_certified_date:
          type: string
          description: >-
            Date the certification for enrolment 5 is valid until. Same formats
            as the due date
        enrolment_code_5_skill_attained:
          type: string
          description: Skill names attained for enrolment 5, separated with a semicolon
  responses:
    BadRequestGeneric:
      description: >-
        Bad Request - The request is missing a required parameter or has an
        invalid value. See the response body for more details
      content:
        application/json:
          schema:
            type: string
    Unauthorized:
      description: >-
        Unauthorized - The request does not include the API key, the API key is
        incorrect, or API access is not enabled on your plan.
    NotAcceptable:
      description: >-
        Not Acceptable - A `POST` or `PUT` request had a `Content-Type` header
        other than `application/json`
    TooManyRequests:
      description: >-
        Too Many Requests - You have exceeded the rate limit. See [Rate
        limits](/rate-limits)
      headers:
        Retry-After:
          description: Number of seconds until the rate limit window resets
          schema:
            type: integer
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
              retry_after_seconds:
                type: integer
          example:
            error: Rate limit exceeded. Please slow down.
            retry_after_seconds: 23
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: Authorization
      description: >
        Your organisation's API key, sent as `Authorization: my_api_key`. The
        key can also be passed as an `api_key` query parameter. See
        [Authentication](/authentication).

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.