> ## Documentation Index
> Fetch the complete documentation index at: https://developers.teambuilder.ge/llms.txt
> Use this file to discover all available pages before exploring further.

# Get the leaderboard by department

> Each department's top members for a period, with informational totals.

<Tip>
  Departments are listed by name, not ranked against each other. See [Grouped by department](/guides/leaderboards#grouped-by-department).
</Tip>


## OpenAPI

````yaml api-reference/openapi.json GET /v1/leaderboard/by-department
openapi: 3.0.0
info:
  title: TeamBuilder API
  description: >-
    The TeamBuilder API gives your own systems access to your company's
    gamification data (employees, leaderboards, departments and the rewards
    shop) and lets you push your employee roster from your HR system. Send every
    request to `https://api.teambuilder.ge/v1` with your API key as
    `Authorization: Bearer <key>`: live keys (`tb_live_…`) work on your real
    data, test keys (`tb_test_…`) on a sandbox company with sample data.
    Responses are JSON with snake_case fields, cursor-based pagination and
    `X-RateLimit-*` headers. For keys, scopes or help, contact
    [contact@teambuilder.ge](mailto:contact@teambuilder.ge).
  version: '1.0'
  contact:
    name: TeamBuilder support
    url: https://teambuilder.ge
    email: contact@teambuilder.ge
servers:
  - url: https://api.teambuilder.ge
    description: Production
security: []
tags:
  - name: Status
    description: Check that your API key works and see what it can access.
  - name: Employees
    description: >-
      Your active employees, their gamification stats and the badges they hold.
      Requires the `employees:read` scope.
  - name: Leaderboard
    description: >-
      XP rankings of your employees for a chosen period, company-wide or grouped
      by department. Requires the `leaderboard:read` scope.
  - name: Departments
    description: Your company's department tree. Requires the `departments:read` scope.
  - name: Shop
    description: >-
      The rewards your employees can redeem with their coins. Requires the
      `shop:read` scope.
  - name: Roster sync
    description: >-
      Keep TeamBuilder in step with your HR system by pushing your employee
      roster. Requires the `employees:write` scope, which is issued on request.
paths:
  /v1/leaderboard/by-department:
    get:
      tags:
        - Leaderboard
      summary: Get the leaderboard by department
      description: >-
        Returns every active department with its XP total for the chosen
        `period`, its number of active employees and its top members, followed
        by a group for employees without a department if there are any. An
        archived department that still has employees is listed too.


        Use it to show team standings side by side, each with its leaders.


        **Notes**


        - Departments are listed by name, not ranked: the totals are for
        information.

        - Each leader keeps their company-wide `rank`.

        - The whole result comes in one response; `pagination.next_cursor` is
        always null.

        - Rankings are refreshed at most every 30 seconds.

        - The response names all your active departments, so a key with
        `leaderboard:read` can read them without `departments:read`.
      operationId: getLeaderboardByDepartment
      parameters:
        - name: period
          required: false
          in: query
          description: >-
            The window XP is counted over. Months are calendar months in UTC.

            - `all_time`: all XP ever earned.

            - `this_month`: XP earned since the 1st of the current month.

            - `last_3_months`: XP earned in the current month and the two before
            it.

            - `last_year`: XP earned in the current month and the eleven before
            it.
          schema:
            default: all_time
            example: this_month
            type: string
            enum:
              - all_time
              - this_month
              - last_3_months
              - last_year
        - name: limit_per_department
          required: false
          in: query
          description: How many top members to list for each department, from 1 to 25.
          schema:
            minimum: 1
            maximum: 25
            default: 5
            example: 3
            type: integer
      responses:
        '200':
          description: The departments and their leaders.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DepartmentLeaderboard'
        '400':
          description: '`period` or `limit_per_department` is invalid.'
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: validation_failed
                  message: limit_per_department must not be greater than 25
                  request_id: req_7Hq2LmX9vT4kPz1RbN8sWc
        '401':
          description: The API key is missing, unknown, revoked or expired.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            WWW-Authenticate:
              $ref: '#/components/headers/WWW-Authenticate'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: unauthorized
                  message: Invalid API key
                  request_id: req_7Hq2LmX9vT4kPz1RbN8sWc
        '403':
          description: Your API key does not have the `leaderboard:read` scope.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: forbidden
                  message: 'This API key lacks the required scope: leaderboard:read'
                  request_id: req_7Hq2LmX9vT4kPz1RbN8sWc
        '429':
          description: >-
            Your API key made more requests than its limit allows (60 per minute
            unless agreed otherwise), or too many requests came from your IP
            address. Wait `Retry-After` seconds, then retry.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/X-Request-Id'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            Retry-After:
              $ref: '#/components/headers/Retry-After'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: rate_limit_exceeded
                  message: Too many requests. Retry after 30 seconds.
                  request_id: req_7Hq2LmX9vT4kPz1RbN8sWc
      security:
        - api-key: []
components:
  headers:
    X-Request-Id:
      description: Unique id of this request. Quote it when you contact support.
      schema:
        type: string
        example: req_7Hq2LmX9vT4kPz1RbN8sWc
    X-RateLimit-Limit:
      description: Requests your API key may make per minute.
      schema:
        type: integer
        example: 60
    X-RateLimit-Remaining:
      description: Requests left in the current one-minute window.
      schema:
        type: integer
        example: 57
    X-RateLimit-Reset:
      description: When the current window ends, as a Unix timestamp in seconds.
      schema:
        type: integer
        example: 1792483260
    WWW-Authenticate:
      description: The authentication scheme to use.
      schema:
        type: string
        example: Bearer realm="TeamBuilder API"
    Retry-After:
      description: Seconds to wait before you retry.
      schema:
        type: integer
        example: 30
  schemas:
    DepartmentLeaderboard:
      type: object
      properties:
        data:
          description: >-
            One entry per active department (empty ones included, plus any
            archived department that still has members), ordered by name
            ignoring case, then one for employees without a department if there
            are any.
          type: array
          items:
            $ref: '#/components/schemas/DepartmentStanding'
        pagination:
          description: 'Always a single page: `next_cursor` is always null.'
          example:
            next_cursor: null
          allOf:
            - $ref: '#/components/schemas/Pagination'
      required:
        - data
        - pagination
    Error:
      type: object
      properties:
        error:
          description: What went wrong, and the id that traces the request.
          allOf:
            - $ref: '#/components/schemas/ErrorDetail'
      description: Every error response has this shape, whatever its status code.
      required:
        - error
    DepartmentStanding:
      type: object
      properties:
        department:
          nullable: true
          description: >-
            The department. Null for the group of employees without a
            department, which always comes last.
          type: object
          allOf:
            - $ref: '#/components/schemas/DepartmentRef'
        department_total_xp:
          type: integer
          description: >-
            Sum of the members' XP for the period. Informational: departments
            are not ranked by it.
          example: 48210
        member_count:
          type: integer
          minimum: 0
          description: Active employees in the department.
          example: 14
        leaders:
          description: >-
            Top members by XP for the period, highest first, with their
            company-wide `rank`.
          type: array
          items:
            $ref: '#/components/schemas/LeaderboardEntry'
      description: A department's XP total and its top members for the period.
      required:
        - department
        - department_total_xp
        - member_count
        - leaders
    Pagination:
      type: object
      properties:
        next_cursor:
          type: string
          nullable: true
          description: Pass it as `cursor` to read the next page. Null on the last page.
          example: djEuQ2hhY2hhMjAuZXhhbXBsZS1jdXJzb3I
      required:
        - next_cursor
    ErrorDetail:
      type: object
      properties:
        code:
          description: >-
            Machine-readable error type. Branch on this, not on `message`:

            - `validation_failed` (400): a parameter, cursor or body field is
            invalid.

            - `unauthorized` (401): the API key is missing, unknown, revoked or
            expired.

            - `forbidden` (403): the key does not have the scope this endpoint
            requires.

            - `not_found` (404): the resource does not exist in your company.

            - `conflict` (409): the request clashes with work in progress, such
            as another roster sync.

            - `length_required` (411): the request body was sent without a
            `Content-Length` header.

            - `payload_too_large` (413): the request body is larger than the
            endpoint accepts.

            - `rate_limit_exceeded` (429): the key made too many requests; wait
            for `Retry-After` seconds.

            - `bad_request` (other 4xx): the request could not be accepted as
            sent.

            - `internal_error` (500): something failed on our side; contact
            support with the `request_id`.
          example: validation_failed
          allOf:
            - $ref: '#/components/schemas/ErrorCode'
        message:
          type: string
          description: >-
            Human-readable explanation for developers. It may change; do not
            parse it.
          example: limit must not be greater than 100
        request_id:
          type: string
          description: >-
            Unique id of the request, also sent as the `X-Request-Id` header.
            Quote it when you contact support.
          example: req_7Hq2LmX9vT4kPz1RbN8sWc
      required:
        - code
        - message
        - request_id
    DepartmentRef:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: TeamBuilder id of the department.
          example: b3e1f7a2-6c4d-4e8f-9a1b-2c3d4e5f6a7b
        external_id:
          type: string
          nullable: true
          description: The department's key in your HR system. Null if none was set.
          example: D-ENG
        name:
          type: string
          description: Department name.
          example: Engineering
      description: The department an employee belongs to.
      required:
        - id
        - external_id
        - name
    LeaderboardEntry:
      type: object
      properties:
        rank:
          type: integer
          minimum: 1
          description: >-
            Position among all your employees for the period. 1 is the top. A
            department filter keeps it.
          example: 3
        employee_id:
          type: string
          format: uuid
          description: >-
            TeamBuilder id of the employee. Stable for as long as they are in
            your company.
          example: 8d2f6b1e-4c3a-4f5e-b7d9-1a2c3e4f5a6b
        external_id:
          type: string
          nullable: true
          description: The employee's key in your HR system. Null if none was set.
          example: EMP-00042
        name:
          type: string
          nullable: true
          description: >-
            Full name. Until the employee first signs in, this is the name your
            roster sync sent, or null if it sent none; afterwards, the name on
            their TeamBuilder account.
          example: Nino Beridze
        department:
          nullable: true
          description: The department the employee belongs to. Null if they have none.
          type: object
          allOf:
            - $ref: '#/components/schemas/DepartmentRef'
        level:
          type: integer
          minimum: 1
          description: Current level, earned through all-time XP whatever the period.
          example: 12
        xp:
          type: integer
          description: >-
            XP earned in the period (all-time XP for `all_time`). The ranking is
            by this value.
          example: 12450
      description: An employee and their position on the leaderboard.
      required:
        - rank
        - employee_id
        - external_id
        - name
        - department
        - level
        - xp
    ErrorCode:
      type: string
      enum:
        - bad_request
        - validation_failed
        - unauthorized
        - forbidden
        - not_found
        - conflict
        - length_required
        - payload_too_large
        - rate_limit_exceeded
        - internal_error
      description: >-
        Machine-readable error type. Branch on this, not on `message`:

        - `validation_failed` (400): a parameter, cursor or body field is
        invalid.

        - `unauthorized` (401): the API key is missing, unknown, revoked or
        expired.

        - `forbidden` (403): the key does not have the scope this endpoint
        requires.

        - `not_found` (404): the resource does not exist in your company.

        - `conflict` (409): the request clashes with work in progress, such as
        another roster sync.

        - `length_required` (411): the request body was sent without a
        `Content-Length` header.

        - `payload_too_large` (413): the request body is larger than the
        endpoint accepts.

        - `rate_limit_exceeded` (429): the key made too many requests; wait for
        `Retry-After` seconds.

        - `bad_request` (other 4xx): the request could not be accepted as sent.

        - `internal_error` (500): something failed on our side; contact support
        with the `request_id`.
  securitySchemes:
    api-key:
      scheme: bearer
      bearerFormat: API key
      type: http
      description: API key — `tb_live_…` for production data, `tb_test_…` for the sandbox.

````

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