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

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.
  • For the Python examples, Python SDK version 7.13.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. 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.
  • 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. A user group filter follows the group’s current membership. A group that is empty, is no longer attached to the project, or that you cannot view matches no work. 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. Workspace reports do not take a user group 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.
Both are told from your side. To a workforce provider its own labelers are internal, and a customer’s project has the customer’s projectOrganizationId. Alignerr members are the exception: they are external to everyone, including to Alignerr’s own organization. Split a report. Use memberType and ownerOrganizationIds on either workspace report. These are the Monitor’s Member Type and Owned by filters.
A few rules apply:
  • A split covers members and projects with activity in the period. As on the Monitor, memberType 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 and ownerOrganizationIds 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 too. The labeler and reviewer tables of a project give each member’s memberType.
  • 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, and projectOrganizationId.
  • User groups belong to the project’s organization. A userGroupIds filter on a project shared with you matches no work.

Paging

The three tables (projects/{projectId}/labelers, projects/{projectId}/reviewers, and workspace/labelers) are paged. In the Python SDK, iterating fetches one page at a time, and total gives the number of rows without reading them all:
Python
In a request, use these parameters: Every 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, so they agree with each other and the rows are in a fixed order. cachedAt says when that reading was taken. With useCache=false every page reads the report again.
  • Sorting. Every table sorts by the figures in its rows, such as labelsCreated or avgTimePerLabel, except avgRating. 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.

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 you pass.
  • 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 or owner. On workspace/metrics, memberType and ownerOrganizationIds 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.
  • 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. The IDs are unchanged.
  • 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.

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.

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: