---
updatedAt: 2026-10-15T14:19:59.000Z
agentTools:
  projectIndex: https://developer.connecteam.com/llms.txt
---

# Track Document Compliance

> 🚧 Beta
>
> **The Documents API is in Beta.** Endpoints, fields, and behavior may change in the upcoming weeks. See [Beta Endpoints](https://developer.connecteam.com/docs/beta-endpoints) for what this means. Questions, feedback, or issues: <omer.vered@connecteam.com>.

Use the two read endpoints together to answer compliance questions: who still owes a document, what one employee is missing, and what is about to expire.

## Endpoints

| Method | Endpoint                     | Description                              |
| :----- | :--------------------------- | :--------------------------------------- |
| GET    | /documents/v1/documents      | List the document templates you can view |
| GET    | /documents/v1/user-documents | List users' documents and their status   |

***

## Overview

* `GET /documents` is the **catalog**: names, types, review and expiration settings. It does not list users.
* `GET /user-documents` is the **per-user view**: one row per user per assigned document, with that user's `status`.
* Join the two on `documentId` to show a document's name next to each user's status.
* Get user documents needs **at least one** of `userIds` or `documentIds`. Each accepts up to 100 IDs.
* Every assigned document appears in the per-user view, including ones nobody has touched yet (`pendingUserUpload`).
* Rows are sorted by `userId`, then `documentId`.
* Fields with no value, such as `uploadedAt` before anything is submitted, are omitted rather than returned as `null`.

> 👍 Good to know
>
> **`paging.total` is always returned on both endpoints.** Use it to get a count, for example "how many employees are missing their license", without paging through every row.

> 📘 Did you know?
>
> **`pendingYourCompletion` is relative to the caller.** It means the document is waiting for the user behind the API key or OAuth token to sign. Another admin reading the same row sees `pendingAdminFilling`.

***

## Authentication

* **API Key** (`X-API-KEY` header), or
* **OAuth 2.0** with scope `documents.read`

***

## Get documents

### Query Parameters

| Parameter     | Type              | Required | Description                           |
| :------------ | :---------------- | :------- | :------------------------------------ |
| `documentIds` | array of integers | No       | Only include these document IDs       |
| `limit`       | integer           | No       | Results per page, 1–200. Default `50` |
| `offset`      | integer           | No       | Pagination offset. Default `0`        |

### Example Request

```bash
curl --request GET \
  --url 'https://api.connecteam.com/documents/v1/documents?limit=50' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

### Response

```json
{
  "requestId": "7a1c9e3b-2d4f-4a6b-8c0d-1e2f3a4b5c6d",
  "data": {
    "documents": [
      {
        "documentId": 52114,
        "name": "Driver's License",
        "description": "Front and back of a valid license",
        "documentType": "document",
        "isReviewRequired": true,
        "expiration": { "type": "entry" },
        "createdAt": 1767225600,
        "modifiedAt": 1772409600
      },
      {
        "documentId": 52115,
        "name": "Food Handler Certificate",
        "documentType": "document",
        "isReviewRequired": false,
        "expiration": { "type": "document", "frequency": "yearly", "nextExpiresAt": 1798761600 },
        "createdAt": 1767225600,
        "modifiedAt": 1767225600
      },
      {
        "documentId": 52116,
        "name": "Employee Handbook Acknowledgement",
        "documentType": "eSign",
        "templateType": "sameForAll",
        "isReviewRequired": false,
        "createdAt": 1767312000,
        "modifiedAt": 1767312000
      },
      {
        "documentId": 52117,
        "name": "W-4",
        "documentType": "governmentDocument",
        "templateType": "w4",
        "isReviewRequired": false,
        "createdAt": 1767312000,
        "modifiedAt": 1767312000
      }
    ]
  },
  "paging": { "offset": 4, "total": 4 }
}
```

### Response Fields

| Field                      | Type    | Description                                                                                                                            |
| :------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------- |
| `documentId`               | integer | Unique identifier of the document                                                                                                      |
| `name`                     | string  | Document name                                                                                                                          |
| `description`              | string  | Description. Omitted when not set                                                                                                      |
| `documentType`             | string  | `document`, `eSign`, or `governmentDocument`                                                                                           |
| `templateType`             | string  | `sameForAll`, `perUser`, `w4`, `w9`, or `i9`. Omitted for `document`                                                                   |
| `isReviewRequired`         | boolean | Whether an employee's upload needs admin approval                                                                                      |
| `expiration.type`          | string  | `document` (shared recurring date) or `entry` (each entry has its own date). `expiration` is omitted when the document does not expire |
| `expiration.frequency`     | string  | `biWeekly`, `monthly`, or `yearly`. Only when `type` is `document`                                                                     |
| `expiration.nextExpiresAt` | integer | Next shared expiration, epoch seconds. Only when `type` is `document`                                                                  |
| `createdAt` / `modifiedAt` | integer | Epoch seconds                                                                                                                          |

***

## Get user documents

### Query Parameters

| Parameter        | Type              | Required                         | Description                                                                                                                      |
| :--------------- | :---------------- | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------- |
| `documentIds`    | array of integers | One of `documentIds` / `userIds` | Only include these documents, up to 100                                                                                          |
| `userIds`        | array of integers | One of `documentIds` / `userIds` | Only include these users, up to 100                                                                                              |
| `status`         | string            | No                               | Only include rows with this status. See [Statuses](documents-overview#statuses)                                                  |
| `expiringBefore` | integer           | No                               | Epoch seconds. Only include rows whose expiration date is set and earlier than this. Covers both shared and per-entry expiration |
| `limit`          | integer           | No                               | Results per page, 1–200. Default `50`                                                                                            |
| `offset`         | integer           | No                               | Pagination offset. Default `0`                                                                                                   |

Pass arrays by repeating the parameter: `?documentIds=52114&documentIds=52115`.

### Response Fields

| Field          | Type    | Description                                                                                                                                                                |
| :------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `userId`       | integer | The user the document is assigned to                                                                                                                                       |
| `documentId`   | integer | The document                                                                                                                                                               |
| `documentType` | string  | `document`, `eSign`, or `governmentDocument`                                                                                                                               |
| `status`       | string  | The user's status on this document                                                                                                                                         |
| `uploadedAt`   | integer | When the latest entry was submitted, epoch seconds. Omitted when nothing was submitted                                                                                     |
| `signedAt`     | integer | When the final signature was applied, epoch seconds. Only for `eSign` and `governmentDocument` whose signing is complete                                                   |
| `expiresAt`    | integer | When this user's document expires, epoch seconds. The shared date for `document` expiration, or the entry's own date for `entry` expiration. Omitted when there is no date |

***

## Recipe 1 — Who is still missing a document?

Filter by the document and by `pendingUserUpload` to list everyone who has not submitted it.

```bash
curl --request GET \
  --url 'https://api.connecteam.com/documents/v1/user-documents?documentIds=52114&status=pendingUserUpload' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

```json
{
  "requestId": "c2e4a6b8-0d1f-4a3b-8c5d-7e9f1a2b3c4d",
  "data": {
    "documents": [
      { "userId": 3067712, "documentId": 52114, "documentType": "document", "status": "pendingUserUpload" },
      { "userId": 3067715, "documentId": 52114, "documentType": "document", "status": "pendingUserUpload" }
    ]
  },
  "paging": { "offset": 2, "total": 2 }
}
```

Drop the `status` filter to see everyone assigned the document and where each person stands. Repeat the call with `status=rejected` to find people who need to re-submit.

***

## Recipe 2 — One employee's documents

Filter by user to build an employee's compliance card.

```bash
curl --request GET \
  --url 'https://api.connecteam.com/documents/v1/user-documents?userIds=3067713' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

```json
{
  "requestId": "e4a6c8d0-2f3b-4c5d-9e7f-1a2b3c4d5e6f",
  "data": {
    "documents": [
      {
        "userId": 3067713,
        "documentId": 52114,
        "documentType": "document",
        "status": "approved",
        "uploadedAt": 1775001600,
        "expiresAt": 1793836800
      },
      {
        "userId": 3067713,
        "documentId": 52115,
        "documentType": "document",
        "status": "pendingApproval",
        "uploadedAt": 1791936000,
        "expiresAt": 1798761600
      },
      {
        "userId": 3067713,
        "documentId": 52116,
        "documentType": "eSign",
        "status": "approved",
        "uploadedAt": 1775088000,
        "signedAt": 1775091600
      },
      {
        "userId": 3067713,
        "documentId": 52117,
        "documentType": "governmentDocument",
        "status": "pendingAdminFilling",
        "uploadedAt": 1775174400
      }
    ]
  },
  "paging": { "offset": 4, "total": 4 }
}
```

Reading this: the license is approved and expires on its own date; the food handler certificate is waiting for approval and follows the shared yearly date; the handbook is signed; the W-4 is waiting for an admin signature.

***

## Recipe 3 — What expires in the next 30 days?

Pass the cutoff as epoch seconds. Rows without an expiration date are excluded. Already-expired documents also match, because their date is earlier than the cutoff.

```bash
CUTOFF=$(( $(date +%s) + 30*24*3600 ))
curl --request GET \
  --url "https://api.connecteam.com/documents/v1/user-documents?documentIds=52114&documentIds=52115&expiringBefore=${CUTOFF}" \
  --header 'X-API-KEY: YOUR_API_KEY'
```

```json
{
  "requestId": "a8c0e2f4-6b7d-4e9f-8a1b-3c4d5e6f7a8b",
  "data": {
    "documents": [
      {
        "userId": 3067713,
        "documentId": 52114,
        "documentType": "document",
        "status": "approved",
        "uploadedAt": 1775001600,
        "expiresAt": 1793836800
      }
    ]
  },
  "paging": { "offset": 1, "total": 1 }
}
```

> 📘 Shared expiration dates
>
> **For `document` expiration, every assigned user has the same `expiresAt`.** Once the cycle rolls over, `nextExpiresAt` and each row's `expiresAt` move forward to the next date.

***

## Pagination

Both endpoints use offset pagination. `paging.offset` in the response is where the next page starts; `paging.total` is the number of matching rows. Keep requesting with `offset` set to the returned `paging.offset` until it reaches `paging.total`.

```bash
curl --request GET \
  --url 'https://api.connecteam.com/documents/v1/user-documents?documentIds=52114&limit=200&offset=200' \
  --header 'X-API-KEY: YOUR_API_KEY'
```

***

## Integration Example — Daily compliance report

Joins the catalog with the per-user view and lists missing and expiring documents per employee.

```javascript
const BASE = 'https://api.connecteam.com/documents/v1';
const headers = { 'X-API-KEY': 'YOUR_API_KEY' };

async function getAll(path, params, key) {
  const rows = [];
  let offset = 0;
  while (true) {
    const qs = new URLSearchParams(params);
    qs.set('limit', '200');
    qs.set('offset', String(offset));
    const res = await fetch(`${BASE}/${path}?${qs}`, { headers });
    if (!res.ok) throw new Error(JSON.stringify(await res.json()));
    const body = await res.json();
    rows.push(...body.data[key]);
    offset = body.paging.offset;
    if (offset >= body.paging.total) return rows;
  }
}

const documents = await getAll('documents', [], 'documents');
const names = Object.fromEntries(documents.map((d) => [d.documentId, d.name]));

const trackedIds = documents
  .filter((d) => d.documentType === 'document')
  .map((d) => d.documentId);

const cutoff = Math.floor(Date.now() / 1000) + 30 * 24 * 3600;
const report = {};

for (let i = 0; i < trackedIds.length; i += 100) {
  const ids = trackedIds.slice(i, i + 100).map((id) => ['documentIds', String(id)]);
  const rows = await getAll('user-documents', ids, 'documents');
  for (const row of rows) {
    const missing = ['pendingUserUpload', 'rejected'].includes(row.status);
    const expiring = row.expiresAt !== undefined && row.expiresAt < cutoff;
    if (!missing && !expiring) continue;
    (report[row.userId] ??= []).push({
      document: names[row.documentId],
      status: row.status,
      expiresAt: row.expiresAt,
    });
  }
}

console.log(report);
```

***

## Notes

> 📝 Important Considerations
>
> * Results only include packs the caller can view, and users within the caller's scope.
> * A document only appears for a user when it is in a pack assigned to that user.
> * Deleted documents are not returned.
> * `signedAt` is best-effort enrichment; if it cannot be resolved, the row is still returned without it.
> * To upload files onto these documents, see [Upload Documents for Employees](documents-upload-for-employees).

***

[Get documents](https://developer.connecteam.com/reference/get_documents_documents_v1_documents_get) · [Get user documents](https://developer.connecteam.com/reference/get_user_documents_documents_v1_user_documents_get)