Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 248 additions & 0 deletions content/manuals/admin/usage-reports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
---
title: Usage reports
Comment thread
akristen marked this conversation as resolved.
linkTitle: Usage reports
description: Learn how to retrieve enterprise usage reports for your Docker organization using the Reports API.
keywords: docker, enterprise, reports, usage, pulls, api, csv, organization access token, oat
toc_max: 4
weight: 50
---

{{< summary-bar feature_name="Usage reports" >}}

Docker generates daily pull activity reports for your organization as CSV downloads, accessible through the Reports API.

## Prerequisites

- A [Docker Business subscription](https://www.docker.com/pricing/)
- Organization owner role, or a [custom role](/manuals/enterprise/security/roles-and-permissions/custom-roles.md)
with the report read permission

> [!NOTE]
> Personal Access Tokens
> (PATs) are unsupported for usage reports.

## Retrieve and download usage reports

These procedures walk you through fetching and downloading usage reports for your organization.

### Step 1. Create an OAT for the Reports API

OATs are
org-scoped tokens designed for machine-to-machine access, making them
suitable for automated report retrieval workflows.

1. Go to [Docker Home](https://app.docker.com/) to [create your OAT](/enterprise/security/access-tokens/#create-an-organization-access-token). To add the report read scope:
- Go to **Resources** and select the **Organization scopes** drop-down.
- Select **Report read** from the drop-down.
2. Set your variables so the report read OAT is associated with the correct organization:

```bash
ORG="<your-org-name>"
OAT="<your-organization-access-token>"
```
3. Exchange the OAT for a JWT bearer token:

```bash
TOKEN=$(curl -s https://hub.docker.com/v2/auth/token \
-H 'Content-Type: application/json' \
-d "{\"identifier\":\"$ORG\",\"secret\":\"$OAT\"}" \
| jq -r '.access_token')
```

4. Validate the JWT bearer token against the list reports endpoint:

```console
$ curl -s "https://api.docker.com/enterprise-data/v1/orgs/$ORG/reports" \
-H "Authorization: Bearer $TOKEN" | jq .
```

You use this `TOKEN` value in the `Authorization: Bearer` header for all
subsequent API calls.

> [!IMPORTANT]
> The JWT token expires after a period,
> so re-run the OAT exchange step to
> refresh the JWT token.

### Step 2. List usage reports types

#### List available reports

Fetch the available report types and cadences for your organization:

```console
Comment thread
akristen marked this conversation as resolved.
$ curl -s "https://api.docker.com/enterprise-data/v1/orgs/$ORG/reports" \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] curl examples include /reports path segment not present in OpenAPI spec

All curl examples in this guide include /reports in the path (e.g., /orgs/$ORG/reports, /orgs/$ORG/reports/usage_pulls/daily), but the OpenAPI spec defines paths without this segment (e.g., /orgs/{org_name}, /orgs/{org_name}/{type}/{cadence}).

Both documents are new and were published together, yet they describe different URL structures. Please reconcile: either add the missing /reports segment to all four paths in api.yaml, or remove it from the guide's curl examples and reference table to match the spec.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8fe0a6. The OpenAPI spec paths now include /reports and match the curl examples.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, thank you! I verified the commit and all four paths in the OpenAPI spec now correctly include "/reports" to match the curl examples and documentation. The discrepancy is resolved.

-H "Authorization: Bearer $TOKEN" | jq .
```

Example response:
Comment thread
akristen marked this conversation as resolved.

```json
{
"report_types": [
{
"ReportType": "usage_pulls",
"Cadence": "daily"
}
]
}
```

Each entry represents a distinct combination of report type and cadence. Use
Comment thread
akristen marked this conversation as resolved.
these values in subsequent calls.

#### List reports for a type and cadence

List reports for a specific type and cadence. Results are in reverse chronological order:

```console
$ curl -s "https://api.docker.com/enterprise-data/v1/orgs/$ORG/reports/usage_pulls/daily" \
-H "Authorization: Bearer $TOKEN" | jq .
```

Example response:
Comment thread
akristen marked this conversation as resolved.

```json
{
"reports": [
{
"ReportType": "usage_pulls",
"Cadence": "daily",
"Date": "2026-06-16",
"SizeBytes": 48210,
"Key": "my-org/usage_pulls/daily/2026-06-16.csv"
},
{
"ReportType": "usage_pulls",
"Cadence": "daily",
"Date": "2026-06-15",
"SizeBytes": 51003,
"Key": "my-org/usage_pulls/daily/2026-06-15.csv"
}
],
"next_page_token": ""
}
```

#### Set pagination for queries

Results are paginated with a default page size of 30 and a maximum of 100.

Use the `page_size` and `page_token` query parameters to control pagination:

```console
$ curl -s "https://api.docker.com/enterprise-data/v1/orgs/$ORG/reports/usage_pulls/daily?page_size=10" \
-H "Authorization: Bearer $TOKEN" | jq .
```

When `next_page_token` is non-empty, pass it as `page_token` to fetch the next page:

```console
$ curl -s "https://api.docker.com/enterprise-data/v1/orgs/$ORG/reports/usage_pulls/daily?page_size=10&page_token=NEXT_TOKEN" \
-H "Authorization: Bearer $TOKEN" | jq .
```

### Step 3. Download a report

#### Download a specific report

Download the CSV file for a specific date. The API responds with a `302`
Comment thread
akristen marked this conversation as resolved.
redirect to a pre-signed URL. With `curl -L`, the redirect is followed
automatically and the file is saved locally.

```console
$ curl -L -o "usage_pulls_2026-06-16.csv" \
"https://api.docker.com/enterprise-data/v1/orgs/$ORG/reports/usage_pulls/daily/2026-06-16/download" \
-H "Authorization: Bearer $TOKEN"
```

The pre-signed download URL expires after 15 minutes. If the link expires,
call the endpoint again to get a fresh URL.

#### Download latest report

Combine the list and download steps to always fetch the most recent report:

```bash
DATE=$(
curl -s "https://api.docker.com/enterprise-data/v1/orgs/$ORG/reports/usage_pulls/daily?page_size=1" \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Download-latest script silently produces broken output when no reports exist

When the API returns an empty reports list, jq -r '.reports[0].Date' outputs the literal string null (not an empty string). The script then silently:

  • Creates a file named usage_pulls_null.csv
  • Makes a download request to .../daily/null/download, which returns a 404

Consider adding a guard to catch this case:

DATE=$(
  curl -s "https://api.docker.com/enterprise-data/v1/orgs/$ORG/reports/usage_pulls/daily?page_size=1" \
    -H "Authorization: Bearer $TOKEN" \
  | jq -r '.reports[0].Date // empty'
)

if [ -z "$DATE" ]; then
  echo "No reports available."
  exit 1
fi

curl -L -o "usage_pulls_${DATE}.csv" \
  "https://api.docker.com/enterprise-data/v1/orgs/$ORG/reports/usage_pulls/daily/${DATE}/download" \
  -H "Authorization: Bearer $TOKEN"

Using // empty makes jq output nothing (empty string) instead of null, so the shell check [ -z "$DATE" ] works correctly for both the no-reports and API-error cases.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e983461. Added // empty to the jq expression and a shell guard that exits early with a message when no reports are available.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect! Thanks for the quick fix. The guard now correctly handles the empty reports case and prevents the script from creating broken files.

-H "Authorization: Bearer $TOKEN" \
| jq -r '.reports[0].Date // empty'
)

if [ -z "$DATE" ]; then
echo "No reports available."
exit 1
fi

curl -L -o "usage_pulls_${DATE}.csv" \
"https://api.docker.com/enterprise-data/v1/orgs/$ORG/reports/usage_pulls/daily/${DATE}/download" \
-H "Authorization: Bearer $TOKEN"
```

## Get report schema

Fetch the schema for a specific report date to discover available columns and their types:

```console
$ curl -s "https://api.docker.com/enterprise-data/v1/orgs/$ORG/reports/usage_pulls/daily/2026-06-16/schema" \
-H "Authorization: Bearer $TOKEN" | jq .
```

Example response:

```json
{
"category": "usage_pulls",
"fields": [
{
"name": "date",
"type": "string",
"description": "The date of the pull event (YYYY-MM-DD)."
},
{
"name": "repository",
"type": "string",
"description": "The repository that was pulled."
},
{
"name": "pull_count",
"type": "integer",
"description": "Number of pulls for the repository on this date."
}
]
}
```

## API reference

| Endpoint | Description |
| ----------------------------------------------------------------------------- | -------------------------------- |
| `GET /enterprise-data/v1/orgs/{org}/reports` | List available report types |
| `GET /enterprise-data/v1/orgs/{org}/reports/{type}/{cadence}` | List reports with pagination |
| `GET /enterprise-data/v1/orgs/{org}/reports/{type}/{cadence}/{date}/download` | Download a report (302 redirect) |
| `GET /enterprise-data/v1/orgs/{org}/reports/{type}/{cadence}/{date}/schema` | Get report column schema |

For the full API specification, see the
[Enterprise Data API reference](/reference/api/enterprise-data/latest/).

## Troubleshooting

### 401 Unauthorized
Comment thread
akristen marked this conversation as resolved.

Your token is missing or invalid. Verify that you are passing the token as a
Bearer token in the `Authorization` header and that the token has not expired.

### 403 Forbidden

The authenticated user or token does not have permission to access reports for
this organization. Verify that:

- The user has the organization owner role or a custom role with the
**report-read** permission.
- The organization has an active Docker Business subscription.

### 404 Not Found

The requested report type, cadence, or date does not exist. Use the list
endpoints to discover available reports before attempting a download.
5 changes: 5 additions & 0 deletions content/reference/api/enterprise-data/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Enterprise Data API
build:
render: never
---
22 changes: 22 additions & 0 deletions content/reference/api/enterprise-data/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
description: Docker Enterprise Data API changelog
title: Docker Enterprise Data API changelog
linkTitle: Changelog
keywords: docker enterprise, data api, whats new, release notes, api, changelog
weight: 2
toc_min: 1
toc_max: 2
---

Here you can learn about the latest changes, new features, bug fixes, and known
issues for the Docker Enterprise Data API.

---

## 2026-06-17

### New

- Initial release of the Enterprise Data API
- Usage reports endpoints: list report types, list reports, download, schema
- Authentication via Personal Access Tokens (PAT) and Organization Access Tokens (OAT)
26 changes: 26 additions & 0 deletions content/reference/api/enterprise-data/deprecated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
description: Deprecated Docker Enterprise Data API endpoints
keywords: deprecated
title: Deprecated Docker Enterprise Data API endpoints
linkTitle: Deprecated
weight: 3
---

This page provides an overview of endpoints that are deprecated in the Docker Enterprise Data API.

## Endpoint deprecation policy

As changes are made to the Docker Enterprise Data API there may be times when existing endpoints need to be removed or replaced with newer endpoints. Before an existing endpoint is removed it is labeled as "deprecated" within the documentation. After some time it may be removed.

## Deprecated endpoints

The following table provides an overview of the current status of deprecated endpoints:

**Deprecated**: the endpoint is marked "deprecated" and should no longer be used.
The endpoint may be removed, disabled, or change behavior in a future release.

**Removed**: the endpoint was removed, disabled, or hidden.

---

No endpoints are currently deprecated.
7 changes: 7 additions & 0 deletions content/reference/api/enterprise-data/latest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
layout: api
description: Reference documentation and OpenAPI specification for the Docker Enterprise Data API.
title: Docker Enterprise Data API reference
linkTitle: Latest
weight: 1
---
Loading