> ## 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.

# Employee widgets

> Show an employee's level, XP progress, coins, ranks and badges on intranet profiles and dashboards.

A profile widget combines three reads, all with the `employees:read` scope:

| Endpoint | Returns |
| - | - |
| [`GET /v1/employees`](/api-reference/employees/list-employees) | Find an employee by `email` or `external_id` to get their `employee_id`. |
| [`GET /v1/employees/{id}/stats`](/api-reference/employees/retrieve-employee-stats) | Level, XP, XP to the next level, coin balance, ranks and badge count. |
| [`GET /v1/employees/{id}/badges`](/api-reference/employees/list-employee-badges) | The badges they hold, newest award first. |

## 1. Resolve the employee

Your intranet knows people by email or HR id; TeamBuilder identifies them by `employee_id`. Look them up once and store the mapping:

```bash theme={null}
curl "https://api.teambuilder.ge/v1/employees?email=nino.beridze@example.com" \
  -H "Authorization: Bearer $TEAMBUILDER_API_KEY"
```

```json Response theme={null}
{
  "data": [
    {
      "employee_id": "3f1c2a9e-8d4b-4c61-9a57-0e2b6f7d1a43",
      "external_id": "EMP-00042",
      "name": "Nino Beridze",
      "email": "nino.beridze@example.com",
      "department": {
        "id": "b7e0c4d2-1f3a-4e8b-9c6d-2a5f8e1b7c90",
        "external_id": "D-ENG",
        "name": "Engineering"
      }
    }
  ],
  "pagination": { "next_cursor": null }
}
```

`email` matches exactly and case-insensitively; `external_id` matches exactly. Filters combine with each other and with `department_id`. An empty `data` array means the person is not an active employee of your company. `employee_id` is stable for as long as they belong to your company, so it is safe to store.

<Tip>
  If you run a [roster sync](/guides/roster-sync), every synced employee has your HR id as `external_id`, so you can look people up by the key you already have.
</Tip>

## 2. Read their stats

```bash theme={null}
curl https://api.teambuilder.ge/v1/employees/3f1c2a9e-8d4b-4c61-9a57-0e2b6f7d1a43/stats \
  -H "Authorization: Bearer $TEAMBUILDER_API_KEY"
```

```json Response theme={null}
{
  "level": 12,
  "xp": 11800,
  "xp_to_next_level": 2200,
  "coins": 2400,
  "company_rank": 1,
  "department_rank": 1,
  "badges_count": 3
}
```

| Field | Meaning |
| - | - |
| `level` | Derived from all-time XP. Starts at 1. |
| `xp` | All-time XP. |
| `xp_to_next_level` | XP still needed to reach the next level; `0` at the maximum level. |
| `coins` | Coins available to spend in the shop. This is the only endpoint that exposes coins. |
| `company_rank` | Position among all active employees by all-time XP. |
| `department_rank` | Position within their department by all-time XP; `null` when they have no department. |
| `badges_count` | Badges currently held; revoked awards are not counted. |

Ranks here are always **all-time**. For a monthly position, read the [leaderboard](/guides/leaderboards) with a `period`.

## 3. Read their badges

```bash theme={null}
curl "https://api.teambuilder.ge/v1/employees/3f1c2a9e-8d4b-4c61-9a57-0e2b6f7d1a43/badges?limit=3" \
  -H "Authorization: Bearer $TEAMBUILDER_API_KEY"
```

```json Response theme={null}
{
  "data": [
    {
      "code": "team_player",
      "name": "Team Player",
      "description": "Played 10 team games",
      "category": "social",
      "rarity": "uncommon",
      "icon_url": "https://files.example.com/badges/team-player.png",
      "awarded_at": "2026-10-14T09:30:00.000Z"
    },
    {
      "code": "quick_thinker",
      "name": "Quick Thinker",
      "description": "Answered 50 trivia questions correctly",
      "category": "skill",
      "rarity": "rare",
      "icon_url": null,
      "awarded_at": "2026-09-30T16:05:12.000Z"
    },
    {
      "code": "first_game",
      "name": "First game",
      "description": "Played your first TeamBuilder game",
      "category": "milestone",
      "rarity": "common",
      "icon_url": null,
      "awarded_at": "2026-09-14T09:32:11.000Z"
    }
  ],
  "pagination": { "next_cursor": null }
}
```

`code` is a stable identifier you can map to your own artwork. `category` is one of `milestone`, `skill` or `social`; `rarity` is one of `common`, `uncommon`, `rare`, `epic` or `legendary`. `icon_url` and `description` can be `null`.

## Example: a profile card payload

Compose the three reads on your server into the shape your front end renders. Fetch stats and badges in parallel, and cache the card for a few minutes: XP and coins change only when people play or redeem.

```javascript Node.js theme={null}
const API = "https://api.teambuilder.ge/v1";
const headers = { Authorization: `Bearer ${process.env.TEAMBUILDER_API_KEY}` };

async function get(path) {
  const res = await fetch(`${API}${path}`, { headers });
  if (res.status === 404) return null; // deactivated or not in your company
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(`${error.code}: ${error.message} (${error.request_id})`);
  }
  return res.json();
}

export async function profileCard(email) {
  const list = await get(`/employees?email=${encodeURIComponent(email)}`);
  const employee = list?.data[0];
  if (!employee) return null;

  const [stats, badges] = await Promise.all([
    get(`/employees/${employee.employee_id}/stats`),
    get(`/employees/${employee.employee_id}/badges?limit=3`),
  ]);
  if (!stats || !badges) return null; // deactivated between calls

  return {
    name: employee.name ?? "New teammate",
    department: employee.department?.name ?? null,
    level: stats.level,
    nextLevelIn: stats.xp_to_next_level,
    coins: stats.coins,
    companyRank: stats.company_rank,
    departmentRank: stats.department_rank,
    badges: badges.data.map(({ code, name, rarity, icon_url }) => ({ code, name, rarity, iconUrl: icon_url })),
    moreBadges: Math.max(0, stats.badges_count - badges.data.length),
  };
}
```

```json Card payload theme={null}
{
  "name": "Nino Beridze",
  "department": "Engineering",
  "level": 12,
  "nextLevelIn": 2200,
  "coins": 2400,
  "companyRank": 1,
  "departmentRank": 1,
  "badges": [
    { "code": "team_player", "name": "Team Player", "rarity": "uncommon", "iconUrl": "https://files.example.com/badges/team-player.png" },
    { "code": "quick_thinker", "name": "Quick Thinker", "rarity": "rare", "iconUrl": null },
    { "code": "first_game", "name": "First game", "rarity": "common", "iconUrl": null }
  ],
  "moreBadges": 0
}
```

## Employees who have not signed in yet

The API lists every **active** employee, including people who were invited or synced but have not yet signed in to TeamBuilder. For them:

* `name` is the name your roster sync sent, or `null`. TeamBuilder never reveals the name on an existing account until that person signs in to your company.
* Stats are real but usually empty: level 1, no XP, no coins, no badges.

<Tip>
  Hide the widget, or show an invitation to join, when `name` is `null` or `xp` is `0`. A rank of 87 out of 90 on someone's first day helps nobody.
</Tip>

## Deactivated employees

Deactivated employees vanish from every endpoint: list filters skip them and `GET /v1/employees/{id}`, `/stats` and `/badges` return `404 not_found`. Treat a 404 as "hide the widget". If the person is reactivated later, the same `employee_id` returns with their history intact.

## Coins and privacy

`coins` reveals how much someone has earned and spent. Show it only to the employee themself, or to people your policy allows, and keep it off public leaderboards and team pages. That is why TeamBuilder's own [leaderboards](/guides/leaderboards#no-coins-on-leaderboards) omit it.


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