> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fitsociety.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Documents

> Read and manage client document metadata.

Documents have a dedicated section in the API Reference. The current Public API
operates on documents belonging to a specific client in the authenticated company.

| Action                        | Endpoint                                                             | Scope             |
| :---------------------------- | :------------------------------------------------------------------- | :---------------- |
| List client folders           | `GET /public/v1/clients/{clientId}/document-folders`                 | `documents:read`  |
| List client documents         | `GET /public/v1/clients/{clientId}/documents`                        | `documents:read`  |
| Register an external document | `POST /public/v1/clients/{clientId}/documents/links`                 | `documents:write` |
| Update document metadata      | `PATCH /public/v1/clients/{clientId}/documents/{documentId}`         | `documents:write` |
| Archive document metadata     | `PATCH /public/v1/clients/{clientId}/documents/{documentId}/archive` | `documents:write` |

All writes require an `Idempotency-Key` header. Registering a document stores an
existing HTTP(S) URL; it does not upload a file. Archiving hides the document from
Public API and company MCP document listings; it does not delete the document or
its file from FITsociety or the external host.

Company-wide document listing and management are not currently exposed through
the Public API. Client routes do not provide a company-wide document collection.

## List document folders

```http theme={null}
GET /public/v1/clients/{clientId}/document-folders
Authorization: Bearer <access_token>
```

Required scope: `documents:read`

Response fields:

| Field                                 | Type    | Nullable | Description                 |
| :------------------------------------ | :------ | :------- | :-------------------------- |
| `data.page`                           | integer | no       | Current page.               |
| `data.limit`                          | integer | no       | Page size after cap.        |
| `data.total`                          | integer | no       | Total matching folders.     |
| `data.totalPages`                     | integer | no       | Total pages.                |
| `data.hasNextPage`                    | boolean | no       | Next page availability.     |
| `data.hasPrevPage`                    | boolean | no       | Previous page availability. |
| `data.folders[].folderId`             | string  | no       | Folder ID.                  |
| `data.folders[].name`                 | string  | no       | Folder name.                |
| `data.folders[].clientId`             | string  | yes      | Client ID.                  |
| `data.folders[].sharedWithAllClients` | boolean | no       | Shared-default flag.        |
| `data.folders[].createdAt`            | string  | yes      | Creation timestamp.         |

## List documents

```http theme={null}
GET /public/v1/clients/{clientId}/documents?folderId={folderId}
Authorization: Bearer <access_token>
```

Required scope: `documents:read`

Validation:

| Parameter  | Type    | Required | Rule                          |
| :--------- | :------ | :------- | :---------------------------- |
| `page`     | integer | no       | Minimum `1`.                  |
| `limit`    | integer | no       | Default `100`, maximum `100`. |
| `folderId` | string  | no       | Valid folder ObjectId.        |

Response fields:

| Field                                   | Type    | Nullable | Description                 |
| :-------------------------------------- | :------ | :------- | :-------------------------- |
| `data.page`                             | integer | no       | Current page.               |
| `data.limit`                            | integer | no       | Page size after cap.        |
| `data.total`                            | integer | no       | Total matching documents.   |
| `data.totalPages`                       | integer | no       | Total pages.                |
| `data.hasNextPage`                      | boolean | no       | Next page availability.     |
| `data.hasPrevPage`                      | boolean | no       | Previous page availability. |
| `data.documents[].documentId`           | string  | no       | Document ID.                |
| `data.documents[].name`                 | string  | no       | Document name.              |
| `data.documents[].mimeType`             | string  | no       | MIME type.                  |
| `data.documents[].fileSize`             | string  | no       | File size as stored.        |
| `data.documents[].folderId`             | string  | yes      | Folder ID.                  |
| `data.documents[].clientId`             | string  | yes      | Client ID.                  |
| `data.documents[].sharedWithAllClients` | boolean | no       | Shared-default flag.        |
| `data.documents[].addedByRole`          | string  | no       | Uploader role.              |
| `data.documents[].createdAt`            | string  | yes      | Creation timestamp.         |

Not exposed in v1: upload, delete, folder mutation, signed document download
URLs, raw `media`, provider metadata, message IDs, or file contents.

## Link external document metadata

```http theme={null}
POST /public/v1/clients/{clientId}/documents/links
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope: `documents:write`

This endpoint stores metadata for a document managed outside FITsociety. It does
not upload binary files and does not expose raw download links in list/read
responses.

Request body:

| Field                          | Type            | Required | Rule                                                                                |
| :----------------------------- | :-------------- | :------- | :---------------------------------------------------------------------------------- |
| `name`                         | string          | yes      | Trimmed, maximum 160 characters.                                                    |
| `documentUrl` or `externalUrl` | URL string      | yes      | Must be an absolute `http` or `https` URL.                                          |
| `mimeType` or `type`           | string          | no       | Trimmed, maximum 120 characters. Defaults to `application/octet-stream`.            |
| `fileSize`                     | string          | no       | Trimmed, maximum 40 characters.                                                     |
| `folderId`                     | ObjectId string | no       | Folder must belong to the client, be company-shared, or be a shared default folder. |

Validation:

| Rule          | Behavior                                                                   |
| :------------ | :------------------------------------------------------------------------- |
| Client access | Client must belong to the authenticated company.                           |
| Coach context | The OAuth client must have a usable default coach context for attribution. |
| Folder access | Invalid or inaccessible folders return a document-folder error.            |
| URL protocol  | Non-HTTP(S) URLs are rejected.                                             |

Response fields:

| Field                                | Type    | Nullable | Description          |
| :----------------------------------- | :------ | :------- | :------------------- |
| `data.document.documentId`           | string  | no       | Linked document ID.  |
| `data.document.name`                 | string  | no       | Document name.       |
| `data.document.mimeType`             | string  | no       | MIME type.           |
| `data.document.fileSize`             | string  | no       | File size as stored. |
| `data.document.folderId`             | string  | yes      | Folder ID.           |
| `data.document.clientId`             | string  | yes      | Client ID.           |
| `data.document.sharedWithAllClients` | boolean | no       | Shared-default flag. |
| `data.document.addedByRole`          | string  | no       | Uploader role.       |
| `data.document.createdAt`            | string  | yes      | Creation timestamp.  |

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.fitsociety.io/public/v1/clients/64b64c0f2f5f4c0012345678/documents/links" \
    -H "Authorization: Bearer $FITSOCIETY_ACCESS_TOKEN" \
    -H "Idempotency-Key: document-link-training-agreement-20260823" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Training agreement",
      "documentUrl": "https://files.example.com/agreements/jane-doe.pdf",
      "mimeType": "application/pdf",
      "fileSize": "182 KB",
      "folderId": "64b64c0f2f5f4c00123456e0"
    }'
  ```

  ```javascript JavaScript theme={null}
  const clientId = "64b64c0f2f5f4c0012345678";
  const response = await fetch(
    `https://api.fitsociety.io/public/v1/clients/${clientId}/documents/links`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.FITSOCIETY_ACCESS_TOKEN}`,
        "Idempotency-Key": "document-link-training-agreement-20260823",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        name: "Training agreement",
        documentUrl: "https://files.example.com/agreements/jane-doe.pdf",
        mimeType: "application/pdf",
        fileSize: "182 KB",
        folderId: "64b64c0f2f5f4c00123456e0",
      }),
    },
  );

  const body = await response.json();
  if (response.status === 201) {
    console.log("Linked document", body.data.document.documentId);
  }
  ```

  ```python Python theme={null}
  import os
  import requests

  client_id = "64b64c0f2f5f4c0012345678"
  response = requests.post(
      f"https://api.fitsociety.io/public/v1/clients/{client_id}/documents/links",
      headers={
          "Authorization": f"Bearer {os.environ['FITSOCIETY_ACCESS_TOKEN']}",
          "Idempotency-Key": "document-link-training-agreement-20260823",
      },
      json={
          "name": "Training agreement",
          "documentUrl": "https://files.example.com/agreements/jane-doe.pdf",
          "mimeType": "application/pdf",
          "fileSize": "182 KB",
          "folderId": "64b64c0f2f5f4c00123456e0",
      },
  )
  response.raise_for_status()

  body = response.json()
  if response.status_code == 201:
      print("Linked document", body["data"]["document"]["documentId"])
  ```
</CodeGroup>

Response (`201 Created`):

```json theme={null}
{
  "data": {
    "document": {
      "documentId": "64b64c0f2f5f4c00123456e1",
      "name": "Training agreement",
      "mimeType": "application/pdf",
      "fileSize": "182 KB",
      "folderId": "64b64c0f2f5f4c00123456e0",
      "clientId": "64b64c0f2f5f4c0012345678",
      "sharedWithAllClients": false,
      "addedByRole": "coach",
      "createdAt": "2026-08-23T10:00:00.000Z"
    }
  },
  "meta": {
    "requestId": "req_0123456789abcdef",
    "rateLimit": { "limit": 10, "remaining": 8, "resetSeconds": 1 }
  }
}
```

## Update document metadata

```http theme={null}
PATCH /public/v1/clients/{clientId}/documents/{documentId}
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope: `documents:write`

Allowed fields: `name`, `documentUrl`, `externalUrl`, `mimeType`, `type`,
`fileSize`, and `folderId`. At least one field is required. The same name, URL,
type, file-size, client, and folder validations as Link external document
metadata apply.

Response fields:

| Field                                | Type    | Nullable | Description          |
| :----------------------------------- | :------ | :------- | :------------------- |
| `data.document.documentId`           | string  | no       | Document ID.         |
| `data.document.name`                 | string  | no       | Document name.       |
| `data.document.mimeType`             | string  | no       | MIME type.           |
| `data.document.fileSize`             | string  | no       | File size as stored. |
| `data.document.folderId`             | string  | yes      | Folder ID.           |
| `data.document.clientId`             | string  | yes      | Client ID.           |
| `data.document.sharedWithAllClients` | boolean | no       | Shared-default flag. |
| `data.document.addedByRole`          | string  | no       | Uploader role.       |
| `data.document.createdAt`            | string  | yes      | Creation timestamp.  |

## Archive document metadata

```http theme={null}
PATCH /public/v1/clients/{clientId}/documents/{documentId}/archive
Authorization: Bearer <access_token>
```

Required scope: `documents:write`

Archive is a metadata-only soft archive. The document is excluded from Public
API list responses after archiving. It does not delete a binary file from
external storage.

Response fields:

| Field                      | Type    | Nullable | Description                             |
| :------------------------- | :------ | :------- | :-------------------------------------- |
| `data.document.documentId` | string  | no       | Document ID.                            |
| `data.document.archived`   | boolean | no       | Always `true` for successful responses. |
| `data.document.updatedAt`  | string  | yes      | Update timestamp.                       |
