Skip to main content
The Performance API returns the numbers shown on a project’s Performance dashboard and on the workspace Monitor: throughput, time, and quality metrics over a period, and totals for each labeler and reviewer. Use it to bring those numbers into your own reporting. It also shows when your organization’s members signed in and out, and when they were active or idle in the editor: see Sign-in sessions and Active and idle periods. The API runs the same reports as the pages. For the same dates and filters it returns the same values, and the same rules decide what you can see. Sign-ins and activity periods have rules of their own, described in their sections.

Before you start

You need:
  • A Labelbox API key.
  • Access to performance data. If you can open a project’s Performance dashboard, you can read that project through the API. Workspace reports need permission to view performance across your organization, as the Monitor does. Sign-ins and activity periods need permission to view all performance across your organization.
  • For the Python examples, Python SDK version 7.13.0 or later. Member organizations (the member_organization_ids filter and the member_organization_id and member_organization_name fields) and user groups on workspace reports need 7.14.0 or later. Sign-ins (get_sign_in_sessions() and get_sign_in_events()) need 7.15.0 or later.
All endpoints are GET requests under this base URL, authenticated with your API key as a bearer token:

Quick start

Get the number of labels created per day in a project:
The response has one bucket per day:

Endpoints

Every table is paged. Filters lists which parameters each report takes, Sign-in parameters those of the sign-in endpoints, and Activity parameters those of activity periods. See Paging and Reliability before you build on the API.

Units and dates

  • Time is in seconds.
  • Percentages and agreement scores are fractions from 0 to 1. A value of 0.9 means 90%.
  • Dates are calendar days in UTC, written as 2026-01-31. Reports are bucketed by UTC day. startDate and endDate are the first and last day of the period, both included, and a request can cover at most 366 days, or 31 days for sign-ins and activity periods.
  • Times, such as generatedAt and when a session started, are in UTC, written as 2026-11-03T08:59:41.120Z.
  • Wider buckets keep their calendar bounds: a week runs from Monday, a month from the 1st. The first and last bucket of a series can reach beyond your period, but they count only the days inside it. For counts and time totals, a wider bucket is the sum of its days. Averages and percentages are worked out over the whole bucket, so they are not.
In the Python SDK you can pass a date, a datetime, or a string. A datetime with a time zone is converted to its UTC day.

Metrics

List the metrics available to you, with their units and definitions:

Throughput

Efficiency

Average handling time includes creating, reviewing, and reworking time. The aht_ metrics are listed and can be queried only when average handling time charts are enabled for your organization. Their buckets also carry modes, the handling time split into creating, reviewing, and reworking.

Quality

The two distribution metrics return score ranges instead of time intervals. Their response has "bucketType": "score" and ten buckets, each with rangeStart, rangeEnd, and value, the number of labels whose score falls in that range. The ranges are fractions like the scores themselves: 0 to 0.1, up to 0.9 to 1.

Filters

These parameters narrow a report. Lists are comma-separated in a request, or a Python list in the SDK. The sign-in endpoints take their own: see Sign-in parameters. Filters narrow each other: a report counts only the work that matches every filter you give. With userIds and userGroupIds, for example, only users in both are counted. If nothing matches, every bucket is zero and every table is empty. It is never the unfiltered report. If you can only view your own performance, see Access and privacy.

User groups

A user group filter counts the work of the groups’ current members, whenever that work was done. A member who has left a group is no longer counted with it, and one who has joined is counted for the whole period.
  • On a project report, the groups are those attached to the project. A group that is empty, is not attached to the project, or that you cannot view matches no work.
  • On a workspace report, the groups are your organization’s groups. A group of another organization, or one you cannot view, matches no work. You need permission to view user groups across your organization, and a restricted API key must include it.
  • At most 5,000 members. If the groups you name have more than 5,000 members between them, the request is refused with 400. The count is of the groups’ current members, whatever the dates or other filters, so narrowing the report does not help. Name fewer groups in each request. A single group with more than 5,000 members cannot be used as a filter.
A parameter that an endpoint does not have is refused by name, so a misspelled filter never runs an unfiltered report. A list that is sent must name at least one ID. An empty list is refused with 400 (userIds=), and the Python SDK raises ValueError for one (user_ids=[]). It is not read as “no filter”, which would report on everyone. To leave a filter off, leave the parameter out, or pass None in the SDK.

Labeler and reviewer totals

These are the two tables on the Performance dashboard: one row per member with their totals for the period.
Both tables are paged and sortable. See Paging. The response wraps the rows in data and adds page, perPage, and total:

Labeler fields

The Python SDK uses the same names in snake case, such as labels_created.

Reviewer fields

Download the report

Get a link to the project’s performance report as a file. This is the file the Download button on the Performance dashboard gives you.
The response has the url and expiresInSeconds. The link works for 24 hours.
Anyone who has the link can open the file until it expires. Treat it like the data it points to.

Workspace reports

Workspace reports are the Monitor’s data. They cover every project your organization owns and every project another organization shares with yours, or only the projects you name in projectIds.
workspace/labelers returns one row for each labeler on each project they worked on in the period. A row has the labeler fields, without avgRating, plus: The table is paged like the project tables. Without sort, rows are ordered by project, then member. Both workspace reports take a user group filter. Use it to report on a team, or on a vendor whose staff have accounts in your own organization:
In the SDK, user_group_ids takes IDs or UserGroup objects; see User groups to find a group. A workspace response repeats projectIds, ownerOrganizationIds, memberOrganizationIds, memberType, and userGroupIds, or null for one you did not use. It does not repeat userIds, batchIds, or deletedLabels. In the SDK, a workspace metric has them as project_ids, owner_organization_ids, member_organization_ids, member_type, and user_group_ids. The result of get_workspace_labeler_performance() does not: it gives the rows, total, and cached_at, and a row’s member_type is that member’s own, not the filter.

Shared projects and workforce providers

A project belongs to one organization and can be shared with another, for example when you add a workforce provider to it. The provider’s members then work in your project, and the project appears in the provider’s workspace as a shared project. The API reflects both sides of that connection. What a workspace report covers. The projects your organization owns, and the projects shared with it. Two fields tell them apart on every row of workspace/labelers:
  • projectOrganizationId is the organization that owns the project.
  • memberType is whose member did the work: internal for a member of your organization, external for a member of another organization or an Alignerr member. The Monitor calls external members Workforce members.
Only memberType is told from your side: to a workforce provider its own labelers are internal. Alignerr members are the exception: they are external to everyone, including to Alignerr’s own organization. A third field, memberOrganizationId, is the organization the member’s account belongs to, so it tells one workforce provider from another. Both organization IDs are the same whoever asks: a customer’s project has the customer’s projectOrganizationId in the workforce provider’s report too. Split a report. Use memberType and ownerOrganizationIds on either workspace report. These are the Monitor’s Member Type and Owned by filters. To report on one workforce provider, use memberOrganizationIds with the provider’s organization ID, as found in memberOrganizationId on the rows. If a vendor’s staff have accounts in your own organization, they are internal members of your organization: put them in a user group and filter by userGroupIds instead.
A few rules apply:
  • A split covers members and projects with activity in the period. As on the Monitor, memberType, memberOrganizationIds, and ownerOrganizationIds select from the members and projects that have rows in the report for your dates and other filters. If none match, every bucket is zero. It is never the unfiltered report.
  • A split by owner leaves out projects that are gone. A project that can no longer be found has no owner, so ownerOrganizationIds never selects it, and the splits by owner can add up to slightly less than the whole workspace.
  • A split by owner leaves out projects you cannot report on. Your workspace can include a project you have no role in. A metric split by ownerOrganizationIds does not count it, and lists it in skippedProjectIds so that you know.
  • A split of a metric needs permission to view everyone’s performance. With projectIds, memberType, memberOrganizationIds, ownerOrganizationIds, and userGroupIds on workspace/metrics need permission to view all performance in every project you name. Otherwise the request returns 403. On workspace/labelers they filter the rows you can already see.
  • Project reports carry memberType and memberOrganizationId too. The labeler and reviewer tables of a project give each member’s memberType, memberOrganizationId, and memberOrganizationName. Project reports do not take memberOrganizationIds.
  • Names and emails are masked between a customer and a workforce provider. See Access and privacy. IDs are never masked, so you can always group and filter by userId, projectId, projectOrganizationId, and memberOrganizationId.
  • User groups belong to one organization. On a project report, the groups are the project’s; a userGroupIds filter on a project shared with you matches no work. On a workspace report, the groups are your own organization’s.

Sign-in sessions

Sign-in sessions show when your organization’s members signed in to Labelbox, and how and when each sign-in ended. Reading them needs permission to view all performance across your organization. The workspace-wide roles Admin, Read-only Admin, Project Lead, and Team Manager have it, and so does a custom role that includes it: see Access and privacy. A session is one sign-in in one browser, or a switch into your workspace from the same person’s user in another workspace. It covers every tab in that browser, the label editor included. Two browsers, or a private window, are two sessions. A session lasts until one of these ends it:
  • the member signs out;
  • Labelbox records another end: a new sign-in in the same browser, a switch to another workspace, or the member’s removal;
  • otherwise, the session reaches its maximum duration. That end is derived when you read the session.
Two endpoints read them:
  • workspace/sign-in-sessions lists the sessions that started in the period, one row per session.
  • workspace/sign-in-events lists the sign-ins and sign-outs in the period, in the order they happened. Use it for a timeline, or to keep another system up to date.

Sign-in parameters

A list that is sent must name at least one ID, as for the reports. Groups that add no members give no rows, never every member’s.

List sessions

The response wraps the sessions in data:
Sessions come in the order they started, oldest first. A session that started before startDate is not listed, even if it was still open in the period. To see everything that happened in a period, read the events. The response repeats userIds, userGroupIds, and status, or null for one you did not use. rules gives the lifetimes that expiresAt is worked out from: see Derived ends and the maximum duration. In the Python SDK, the result of get_sign_in_sessions() has neither: it gives only the rows.

Session fields

The Python SDK uses the same names in snake case, such as started_at. A session is OPEN while nothing has ended it and its expiresAt is still ahead. It could still be used. It does not mean the member is using Labelbox.

Sign-in and sign-out events

The events are a SIGN_IN when a session starts, at its startedAt, and a SIGN_OUT when it ends, at its endedAt, recorded or derived. They are ordered by occurredAt. The period applies to occurredAt, so a session that started before the period can sign out in it.
The feed stops at completeThrough, the earlier of the end of endDate and five minutes ago. Every event before it is listed. Events at or after it are not listed yet: they come in a later read. A derived sign-out is listed once its time is before completeThrough, so the feed never lists a sign-out ahead of time. In the Python SDK, the result of get_sign_in_events() gives only the rows and complete_through, which is None until you have read the events to the end.

Event fields

The Python SDK uses the same names in snake case, such as occurred_at.

Follow the feed

To keep a copy of the events up to date, read the feed again each time from where the last read stopped:
  1. Ask for the events from the UTC day that holds one hour before the last completeThrough you stored, through today.
  2. Drop the events whose eventId you already have.
  3. Store the new completeThrough for the next read.
A request starts at midnight UTC, so each read repeats between one and 25 hours of events. To drop them, keep the eventId of every event in at least the 25 hours before the completeThrough you stored. The overlap also picks up an event that took unusually long to be recorded. A request covers at most 31 days, so after a longer gap, read the gap in parts.

How a session starts

How a session ends

Derived ends and the maximum duration

When nothing ends a session, it is reported as ended at its expiresAt, with endReason MAX_DURATION_REACHED and endDerived true. This end is derived when you read the session. It is never recorded, and nothing happens at that moment. By then every credential the sign-in issued had lapsed, so the member would have had to sign in again to go on.
A derived end is when the session could last no longer. It is not when the member stopped working, which may have been much earlier. Durations are therefore not time worked. Sessions in two browsers can also overlap, so adding up durations can count the same time twice.
expiresAt is worked out from the lifetimes in rules, given in seconds:
  • 84 hours from sign-in. A session lasts at most the lifetime of the sign-in cookie (cookieLifetimeSeconds, 302,400 seconds), plus a minute, from when the cookie was last set. The sign-in’s own token (signInTokenLifetimeSeconds, 24 hours) lapses before the cookie, so it does not change the end.
  • Later when extended. Opening a link to the workspace the member is already in, or connecting an application to their account, sets the cookie again and moves an open session’s expiresAt later. A session extended again and again has no fixed maximum.
  • 7 days after a workspace switch, where your organization uses bearer tokens. If your organization signs members in with tokens instead of cookies, a switch into your workspace issues a workspace token (workspaceTokenLifetimeSeconds, 7 days), and the session can last as long as that token.
expiresAt is an upper bound. It assumes the browser kept the cookie: a member who cleared their cookies lost access earlier.

What is not recorded

Sessions record sign-ins and how they ended, not activity. These leave no record:
  • Closing the tab or the browser, a computer going to sleep, or a lost connection. The session stays open until something else ends it, or until its derived end.
  • A session expiring. That is the derived end.
  • The label editor’s idle sign-out when the editor is open in its own tab, as in queue labeling.
  • A sign-out clicked after the session had expired. The derived end, which came first, stands.
  • Impersonation. Nothing done while a Labelbox admin impersonates a member is recorded as that member’s.
  • API keys. Requests made with an API key, such as through the Python SDK, are not sign-ins and start no session.
No IP address, browser, or location is stored. Labelbox began recording sign-ins on 2026-10-15. Earlier sign-ins are not available, and a member who was already signed in then appears from their next sign-in. Sessions are deleted 400 days after they started.

Active and idle periods

Activity periods show when your organization’s members were working in the label editor and when they were idle there, each with a start and an end. Together with sign-in sessions they give you the time to compare with your own shift schedule: time signed in, time active, and time idle. Reading them needs the same permission as sign-ins: to view all performance across your organization.
  • A member is active (ACTIVE) while the editor’s timer is counting their time.
  • A member is idle (IDLE) while the editor is open but its timer is paused. The usual reason is no input for five minutes (idleReason INACTIVITY), when the project pauses the timer on inactivity. The editor shows “Inactivity detected” then, and the period ends at the next key press or click. idleReason names other reasons the timer can be paused for.
  • Time signed in but outside the editor, such as on a project’s overview, is neither. So signed-in time minus active and idle time is time spent elsewhere in Labelbox.
Periods belong to the member, not to a browser tab. While the member is active in one tab, another tab that is idle adds no idle time.

Activity parameters

List periods

cURL
The response wraps the periods in data:
Every period that overlaps the period you ask for is listed, oldest start first, including one that started the day before and ran into it. To add up a day, clip each period to the day. An open period has status OPEN and endedAt null.

Period fields

Why a period is idle

For idle time in the strict sense, filter on INACTIVITY (and POSSIBLE_INACTIVITY where a time tracker is required).

How a period ends

What is recorded

Periods are recorded in the organization the member works in. A workforce provider’s members, or Alignerr accounts, working on your projects are recorded in their own organization, not yours. Nothing done while a Labelbox admin impersonates a member is recorded. Recording is being turned on organization by organization. Earlier time is not available, and periods are deleted 400 days after they started.

Paging

The three tables (projects/{projectId}/labelers, projects/{projectId}/reviewers, and workspace/labelers) are paged by page number. In the Python SDK, iterating a table fetches one page at a time, and total gives the number of rows without reading them all:
Python
The sign-in endpoints and activity periods are paged by cursor instead, with no page and no total: see Paging by cursor. In a request, use these parameters: Every table response has page, perPage, and total, the number of rows across all pages. Request pages until you have total rows. A page past the end returns an empty data.
cURL
Keep these in mind when you read a whole table:
  • Leave the cache on. The pages of workspace/labelers are cut from one reading of the report, kept for five minutes from when it was taken, so they agree with each other while it lasts and the rows are in a fixed order. cachedAt says when that reading was taken. With useCache=false every page reads the report again, and so does a very large table, which is not kept.
  • A user group filter is applied to each page. On every table, who is in the groups is worked out again for each page. If someone joins or leaves one of the groups while you read the table, a row can be missed or repeated. If total changes from one page to the next, start again from page 1. The Python SDK checks this for you: from 7.14.0, iterating raises PerformanceTableChangedError when a page’s total differs from the first page’s. A change that leaves total as it was cannot be seen this way, such as a member joining a group as another with the same number of rows leaves.
  • Sorting. Every table sorts by the figures in its rows, such as labelsCreated or avgTimePerLabel, except avgRating. Rows cannot be sorted by member organization. Without sort, the project tables come ordered by email, as on the Performance dashboard, and rows that tie under a sort are in that order too. A masked email keeps the place of the real one. workspace/labelers comes ordered by project, then member.
  • Metrics are not paged. A metric returns at most one bucket per day of the period, and the period is limited to 366 days.

Paging by cursor

workspace/sign-in-sessions, workspace/sign-in-events, and workspace/activity-periods are paged by cursor, not by page number. In the Python SDK, iterating fetches one page at a time, as for the tables:
Python
In a request, use these parameters: Every response has nextCursor, or null on the last page. Request pages, with the same other parameters, until nextCursor is null. There is no page, no total, and no sort: sessions and activity periods come in the order they started, and events in the order they happened. A cursor is not meant to be read or built, and a value the endpoint did not return is refused with 400.
cURL
A user group filter is applied to each page here too. A member who joins or leaves one of the groups while you read is listed only on the pages read while they were in it.

Access and privacy

The API shows you what the pages show you.
  • Your own performance only. If you can only view your own performance on a project, every project report returns only your data, whatever userIds or userGroupIds you pass. The same goes for a workspace report when you name such a project in projectIds: it counts only your own work, whatever userIds you pass. workspace/metrics then refuses userGroupIds, memberType, memberOrganizationIds, and ownerOrganizationIds with 403.
  • Workspace reports. Without projectIds, you need permission to view performance across your organization. With projectIds, you need performance access to each project you name.
  • Projects you cannot see. A project you have no performance access to returns 404, the same as a project that does not exist.
  • Splitting by member type, organization, or user group. On workspace/metrics, memberType, memberOrganizationIds, ownerOrganizationIds, and userGroupIds need permission to view all performance in every project you name in projectIds. A project found through its owner that you cannot report on is left out and listed in skippedProjectIds.
  • User groups. A userGroupIds filter on a workspace report needs permission to view user groups across your organization. Without it, the groups match no one: the metric is zero and the table is empty, never the unfiltered report. If you can only view your own performance, workspace/labelers returns your own rows, and with userGroupIds only if you are in one of the groups.
  • Masked emails. The email of a member you may not identify is replaced with a masked value, as on the pages. For example, a workforce provider’s members appear masked to the project’s owner, and the owner’s members appear masked to the provider.
  • Masked project and organization names. A workforce provider does not see the names of the projects shared with it or of the organizations that own them. In workspace/labelers, projectName and projectOrganizationName are replaced with masked values for those projects. memberOrganizationName, in all three tables, is masked by the same rules, as org.name.<ID>: a workforce provider sees its customers’ organization names masked, and a customer sees the names of its workforce providers. The IDs are unchanged.
  • Sign-ins. The sign-in endpoints need permission to view all performance across your organization. Performance access to projects does not count, and neither does permission to view your own performance: a member cannot read their own sign-ins, and gets 403. Only sessions started in your organization are listed. Accounts of other organizations, such as a workforce provider’s members or Alignerr accounts, have none here, even when they work on your projects or are in your user groups. A member who has since moved to another organization keeps the sessions they started in yours, with email null and memberDeleted true.
  • Activity periods. The same as sign-ins: they need permission to view all performance across your organization, a member cannot read their own, and only periods recorded in your organization are listed.
  • Restricted API keys. A key created with a limited set of permissions needs a performance permission, and is limited to the ones it has. A key that only has permission to view your own performance returns only your own. For sign-ins and activity periods, such a key needs permission to view all performance, or it is refused with 403.

Data freshness

The API reads the same data as the Performance dashboard and the Monitor, so it is never more current than those pages. That data is rebuilt periodically, not continuously: the current day’s figures can trail real activity by anything from a quarter of an hour to over an hour. A response served from the cache can be up to five minutes older again. generatedAt is when the response was produced. cachedAt, on the labeler tables, is set when the rows came from a result computed in the last few minutes. Neither tells you how recent the underlying activity data is, and no response carries an as-of time for it. Two things follow for the current day:
  • Today’s bucket keeps growing. Read settled days for totals you will not revisit.
  • Charts and tables do not refresh together. For settled days a metric and the labeler table normally add up to the same total. For the current day they can differ until both have caught up.
Sign-ins and activity periods are not part of that data. They are read as they are recorded, and the events feed lists everything up to five minutes ago: see Sign-in and sign-out events.

Limits

Requests count toward your API rate limit, like the rest of the Labelbox API.

Reliability

  • Requests are safe to repeat. Every endpoint is a GET and changes nothing, so you can retry any request.
  • Retry 5xx responses and timeouts with a growing delay between attempts. The Python SDK does this for you, for up to four minutes, and tries once more after a request that timed out. When it stops retrying, it raises the error that kept happening.
  • Do not retry 4xx responses without changing the request. For 429, wait before sending more.
  • Allow for long requests. Reports are computed when you ask, and a long period over a large workspace can take a while. The Python SDK waits up to 120 seconds for a response.
  • Prefer fewer, wider requests. One request for a month at interval=day costs less than a request for each day. To read every member’s totals for a project as a file, use Download the report.
  • Expect new fields. Responses can gain fields over time. Ignore the ones you do not know. The Python SDK does.

Errors

The Python SDK raises TimeoutError when a request gets no response in time, and NetworkError when it cannot connect. All of these exceptions are in lbox.exceptions. It raises Python’s ValueError, before sending anything, for a filter given as an empty list. A 400 response names each parameter that was refused: