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

# Documents

> Create, upload, read, update, organize and delete the compliance documents of your company with the Connect API

A **document** is a file or a record about a file that your company keeps in its document library, for example an insurance certificate or a permit. A document has a title, a type, dates, a visibility setting and, optionally, a folder.

Read [Connect API conventions](/connect-api/content/api-conventions) first. It explains the base URL, authentication, `Idempotency-Key`, `If-Match`, pending operations and the error format. This page uses `$CONNECT_API_BASE_URL` for the base URL. Ask GRiDD support ([support@grdd.net](mailto:support@grdd.net)) for the Connect API base URL for your environment.

<Note>
  The core route `GET /api/content/{griddid}/documents` is a different, read-only route. See [Get Documents](/connect-api/content/get-documents).
</Note>

## What is available

The Document routes are implemented as owner routes (your own company) and as authenticated reads of another company's documents. The Connect API README names only Fleets and Vehicles as complete production implementations, and says not to infer completeness for other content types. This page describes the route contracts in the source and does not claim more.

## Endpoints

### Owner routes

These routes act on the company in your token. For an update, rename, visibility change, move or delete, `If-Match` and `Idempotency-Key` are both required.

| Method | Path | Purpose | `Idempotency-Key` | `If-Match` |
| - | - | - | - | - |
| `GET` | `/api/documents` | List your documents. | – | – |
| `POST` | `/api/documents` | Create a document record from JSON metadata. | Required | – |
| `POST` | `/api/documents/upload` | Create a document from a file, or replace the file of an existing document. | Required | – (`expectedVersion` is in the metadata) |
| `GET` | `/api/documents/{documentId}` | Read one document. Returns an `ETag`. | – | – |
| `PATCH` | `/api/documents/{documentId}` | Update document fields. | Required | Required |
| `DELETE` | `/api/documents/{documentId}` | Delete one document, or one language version. | Required | Required |
| `PATCH` | `/api/documents/{documentId}/name` | Change the title. Body `{ "title": "..." }`. | Required | Required |
| `PATCH` | `/api/documents/{documentId}/visibility` | Change the visibility. Body `{ "visibility": "...", "sharewith": [...] }`. | Required | Required |
| `PATCH` | `/api/documents/{documentId}/folder` | Move the document to a folder. Body `{ "folderId": 12 }`, or `{ "folderId": null }` for no folder. | Required | Required |
| `PATCH` | `/api/documents/order` | Set the sort order of several documents. | Required | – (versions are in the body) |
| `GET` | `/api/documents/count` | Count your documents. Response `{ "count": 1 }`. | – | – |
| `GET` | `/api/documents/links` | List the fleets, vehicles, locations, services and members that link to your documents. | – | – |
| `GET` | `/api/documents/{documentId}/signed-url?ownerGriddid=` | Get a temporary download URL for a document that you may read. | – | – |
| `POST` | `/api/documents/validity` | Check a document requirement through core. Body: `id`, `title`, `description`, `required` and optional `externalLink`. Response: `found`, `isValid`, `answer`. | – | – |
| `POST` | `/api/documents/operations/{operationId}` | Resume a pending operation. See [Pending operations](/connect-api/content/api-conventions#pending-operations). | – | – |

### Read the documents of another company

| Method | Path | Purpose |
| - | - | - |
| `GET` | `/api/documents/public/{griddid}` | List the `public` documents of another company. |
| `GET` | `/api/documents/partner/{griddid}` | List the documents of another company that you may read as a viewer. |

Both routes need your Bearer token. They return a reduced record. See [Public record](#public-record). Query: `locale`, `force`.

A viewer may read a document if one of these is true: its visibility is `public`; the viewer is the owner; the owner shared it with the viewer in `sharewith`; its visibility is `gnet`; or its visibility is `partners` and core confirms that the viewer is a partner of the owner. The `public/{griddid}` route returns only `public` documents.

### Folders

Documents can be organized in folders. The folder routes follow the same rules as the document routes. This page lists them without detail.

| Method | Path | Purpose |
| - | - | - |
| `GET` | `/api/document-folders` | List your folders. |
| `POST` | `/api/document-folders` | Create a folder. Body: `name` (required), `visibility` (required), `parentId`, `sharewith`, `locale`. |
| `GET` | `/api/document-folders/tree` | Read your folders and documents as a tree. |
| `GET` | `/api/document-folders/{folderId}/children` | Read one folder with its sub-folders and documents. |
| `PATCH` | `/api/document-folders/{folderId}/name` | Rename a folder. |
| `PATCH` | `/api/document-folders/{folderId}/parent` | Move a folder. |
| `PATCH` | `/api/document-folders/{folderId}/visibility` | Change the folder visibility. |
| `PATCH` | `/api/document-folders/order` | Set the sort order of several folders. |
| `DELETE` | `/api/document-folders/{folderId}?disposition=delete-subtree` | Delete a folder and everything below it. |
| `GET` | `/api/document-folders/partner/{griddid}` | Read the folders of another company that you may see. |

The folder `PATCH` and `DELETE` routes need `If-Match` and `Idempotency-Key`. `POST /api/document-folders` and the order route need `Idempotency-Key`.

### Not for external use or not described

* `GET /api/documents/published/{griddid}` and `GET /api/document-folders/public/{griddid}` use the `Service` credential of the public website renderer.
* `/api/documents/advisor/**` and `GET /api/document-folders/advisor/{griddid}` are for the compliance advisor company only.
* `/api/documents/{documentId}/tracking` is a document tracking route that depends on an external provider. It is not described here.
* `/api/document-media/**` (albums and photos) is a separate part of the API. It is not described here.

## Authentication and access

* Send `Authorization: Bearer YOUR_ACCESS_TOKEN`. See [Authentication](/connect-api/content/api-conventions#authentication).
* The owner routes do not check a role in the token. A token with the role `ADMIN` can read documents of other companies through the viewer rules.
* Creating a document has no subscription check in the source at the reviewed commit. The subscription contract sets no limit for document uploads.

## Document fields

### Read record

| Field | Type | Notes |
| - | - | - |
| `id` | integer | |
| `slug` | string | |
| `griddid` | string | Your company. |
| `title` | string | |
| `documentUrl` | string or null | An internal storage locator. Use `signed-url` to download. |
| `badgeUrl` | string or null | |
| `issueDate`, `expiryDate`, `verification_date` | string or null | |
| `description` | string or null | |
| `sharewith` | array or null | Items are `{ "griddid": string, "id": string }`. |
| `documentType` | string or null | For example `insurance_commerical_auto`, `permit_airpot`, `certificate`. |
| `visibility` | `private`, `partners`, `gnet`, `unlisted`, `public` | |
| `text_content` | any | |
| `document_type_confidence` | string or null | |
| `issued_to`, `document_number`, `agent_email`, `issuer` | string or null | |
| `folder` | integer, object or null | The folder, or its ID. |
| `sort_order` | number or null | |
| `certificialTracking`, `compliance_status` | string or null | Set by a tracking or review process. |
| `policyRequestId` | number or null | |
| `version` | integer | Use it in `If-Match`. |
| `createdAt`, `updatedAt` | string (ISO 8601) | |

### Public record

`public/{griddid}` and `partner/{griddid}` return these fields only: `id`, `title`, `griddid`, `badgeUrl`, `issueDate`, `expiryDate`, `description`, `documentType`, `visibility`, `issuer`, `folder`, `sort_order`, `compliance_status`, `slug`, `updatedAt`, `createdAt`. They do not return `documentUrl`.

### Write body

All write bodies are strict. Unknown keys return `400 invalid_request`.

**Create** (`POST /api/documents`):

| Field | Type | Required | Notes |
| - | - | - | - |
| `document` | object | Yes | The fields below. |
| `locale` | `en`, `es`, `de`, `fr` | No | |
| `contentLanguage` | object | No | `{ "intent": "create_record", "language": "fr" }`. |

`document` accepts the read fields, except `id`, `griddid`, `slug`, `createdAt` and `updatedAt`. `title` is required. It is trimmed and must not be empty. All other fields are optional. If you omit `visibility`, the API sets it from `documentType`. For example `ein` and `contract` default to `private`, `certificate` and `ada` to `public`, and `insurance_*` and `permit_airpot` to `gnet`. An unknown or missing type defaults to `private`.

**Update** (`PATCH /api/documents/{documentId}`): the body is `{ "document": { ... } }` with any subset of those fields, plus optional `locale` and `contentLanguage` (`update_version` or `add_version`).

* A document that a tracking provider manages cannot change `certificialTracking`, `policyRequestId`, `issueDate` or `expiryDate`. The response is `422 domain_rejected`.
* Every `sharewith` entry needs a `griddid`. The IDs must be unique company IDs. Otherwise the response is `422 domain_rejected`.

**Order** (`PATCH /api/documents/order`): body `{ "documents": [{ "id": 41, "sortOrder": 1, "version": 1, "folderId": null }], "locale": "en" }`. At least one document. The IDs must be unique. `folderId` is optional.

## Create a document record

The body is the fixture `document-create.json`:

```bash theme={null}
curl -X POST "$CONNECT_API_BASE_URL/api/documents" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6a1d3c90-4b7e-4f28-9e05-d2c8b1a7f364" \
  -d '{
    "document": {
      "title": "Commercial Auto Policy",
      "visibility": "private"
    },
    "locale": "en"
  }'
```

The response is `201 Created` (or `202` if pending) with an `ETag`. The body has `status`, `data` (the document), `effects` and `invalidated`, and can have `operationId`.

## Upload a file

`POST /api/documents/upload` takes `multipart/form-data` with exactly two parts: `file` and `metadata`. Other parts return `400 invalid_request`.

* `file` is the file. The maximum size is 10 MiB. The allowed formats are PDF, PNG, JPG/JPEG, WEBP, DOCX, DOC, XLS, XLSX, CSV and ZIP. The API checks the file extension and the first bytes of the file. A file that does not match returns `415 unsupported_media_type`, and one that is too large returns `413 payload_too_large`.
* `metadata` is a JSON string with the same fields as the create body (`document`, `locale`, `contentLanguage`). To replace the file of an existing document, add `documentId` and `expectedVersion`.

```bash theme={null}
curl -X POST "$CONNECT_API_BASE_URL/api/documents/upload" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Idempotency-Key: 0e5b7d21-93c4-4a68-b1f0-7c2d9e4a6b13" \
  -F 'file=@commercial-auto.pdf;type=application/pdf' \
  -F 'metadata={"document":{"title":"Commercial Auto Policy","documentType":"insurance_commerical_auto"},"locale":"en"}'
```

The response is `201` for a new document, `200` when a file is replaced, and `202` when pending. Do not set the `Content-Type` header yourself. The client must add the multipart boundary.

## Read documents

```bash theme={null}
curl "$CONNECT_API_BASE_URL/api/documents/41" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

Response `200` with the header `ETag: "v1"`. This is the fixture `document-record.json`:

```json theme={null}
{
  "id": 41,
  "title": "Commercial Auto Insurance",
  "griddid": "demo-company",
  "documentUrl": "r2://documents/demo-company/commercial-auto.pdf",
  "badgeUrl": null,
  "issueDate": "2026-01-01T00:00:00.000Z",
  "expiryDate": "2027-01-01T00:00:00.000Z",
  "description": "Current certificate",
  "sharewith": [{ "griddid": "partner-company", "id": "share-1" }],
  "documentType": "insurance_commerical_auto",
  "visibility": "public",
  "text_content": { "private": "policy content" },
  "document_type_confidence": "0.99",
  "issued_to": "Demo Company",
  "document_number": "PRIVATE-123",
  "verification_date": "2026-02-01T00:00:00.000Z",
  "agent_email": "private@example.com",
  "issuer": "Example Insurer",
  "folder": null,
  "sort_order": 1,
  "certificialTracking": null,
  "compliance_status": "certified",
  "policyRequestId": null,
  "slug": "commercial-auto",
  "version": 1,
  "updatedAt": "2026-08-30T12:00:00.000Z",
  "createdAt": "2026-01-01T00:00:00.000Z"
}
```

`GET /api/documents` returns `{ "items": [...], "meta": {...} }`. The list has no pagination parameters. Query: `locale` and `force`.

A document of another company returns `404 not_found` on the owner routes.

`GET /api/documents/public/demo-company` returns the reduced record. This is the fixture `document-public-list.json`:

```json theme={null}
{
  "items": [
    {
      "id": 41,
      "title": "Commercial Auto Insurance",
      "griddid": "demo-company",
      "badgeUrl": null,
      "issueDate": "2026-01-01T00:00:00.000Z",
      "expiryDate": "2027-01-01T00:00:00.000Z",
      "description": "Current certificate",
      "documentType": "insurance_commerical_auto",
      "visibility": "public",
      "issuer": "Example Insurer",
      "folder": null,
      "sort_order": 1,
      "compliance_status": "certified",
      "slug": "commercial-auto",
      "updatedAt": "2026-08-30T12:00:00.000Z",
      "createdAt": "2026-01-01T00:00:00.000Z"
    }
  ],
  "meta": { "partial": false, "paginationComplete": true, "rejectedCount": 0, "rejected": [] }
}
```

## Update a document

```bash theme={null}
curl -X PATCH "$CONNECT_API_BASE_URL/api/documents/41" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "If-Match: \"v1\"" \
  -H "Idempotency-Key: b4e92a07-1d6c-4c35-8a7f-e05d3f1c9b82" \
  -d '{ "document": { "description": "Current certificate" } }'
```

`200 OK` with the header `ETag: "v2"`. This is the fixture `document-mutation.json`:

```json theme={null}
{
  "status": "completed",
  "data": {
    "id": 41,
    "title": "Commercial Auto Insurance",
    "griddid": "demo-company",
    "documentUrl": "r2://documents/demo-company/commercial-auto.pdf",
    "badgeUrl": null,
    "issueDate": "2026-01-01T00:00:00.000Z",
    "expiryDate": "2027-01-01T00:00:00.000Z",
    "description": "Current certificate",
    "sharewith": [{ "id": "share-1", "griddid": "partner-company" }],
    "documentType": "insurance_commerical_auto",
    "visibility": "public",
    "text_content": { "private": "policy content" },
    "document_type_confidence": "0.99",
    "issued_to": "Demo Company",
    "document_number": "PRIVATE-123",
    "verification_date": "2026-02-01T00:00:00.000Z",
    "agent_email": "private@example.com",
    "issuer": "Example Insurer",
    "folder": null,
    "sort_order": 1,
    "certificialTracking": null,
    "compliance_status": "certified",
    "policyRequestId": null,
    "slug": "commercial-auto",
    "updatedAt": "2026-08-31T12:00:00.000Z",
    "createdAt": "2026-01-01T00:00:00.000Z",
    "version": 2
  },
  "effects": [
    { "name": "cache", "status": "completed", "retryable": false },
    { "name": "DOCUMENT_UPDATED", "status": "completed", "retryable": false },
    { "name": "audit", "status": "completed", "retryable": false }
  ],
  "invalidated": ["document"]
}
```

A pending update returns `202` with `status: "pending"` and an `operationId`. A stale `If-Match` returns `412 version_conflict`.

## Download a document

`documentUrl` is not a download link. To download a document, ask for a signed URL. `ownerGriddid` is the company that owns the document:

```bash theme={null}
curl "$CONNECT_API_BASE_URL/api/documents/41/signed-url?ownerGriddid=demo-company" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

This is the fixture `document-signed-read.json`:

```json theme={null}
{
  "signedUrl": "https://objects.example.test/demo-company/commercial-auto.pdf?signature=test",
  "contentType": "application/pdf",
  "disposition": "inline; filename=\"commercial-auto.pdf\"",
  "contentLength": 2048,
  "headers": { "X-Content-Type-Options": "nosniff" }
}
```

The signed URL is temporary. If the viewer rules do not allow you to read the document, the response is `403 forbidden`. If the document does not exist or has no file, the response is `404 not_found`.

## Delete a document

```bash theme={null}
curl -X DELETE "$CONNECT_API_BASE_URL/api/documents/41" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "If-Match: \"v2\"" \
  -H "Idempotency-Key: 5d0a8e63-7c14-4b92-a3f1-9e6b2c8d4017"
```

`204 No Content` when the delete is complete. If follow-up work is not finished, the response is `202` with `status: "pending"`, `data: null`, `operationId` and `effects`.

## Errors

Error bodies use the [problem format](/connect-api/content/api-conventions#errors). These codes come from the document routes.

| Status | `code` | When |
| - | - | - |
| 400 | `invalid_json`, `invalid_request` | The body or query is not valid. Includes unknown keys, a bad `If-Match` or `Idempotency-Key` value, and a bad upload form. |
| 401 | `unauthenticated` | The token is missing, invalid or expired. |
| 403 | `forbidden` | The viewer rules do not allow the read. |
| 404 | `not_found` | No document with this ID for your company, no file for a download, or no such operation. |
| 409 | `conflict` | A content language rule refused the change. |
| 409 | `idempotency_conflict` | The key was used for a different request. |
| 412 | `version_conflict` | `If-Match` is stale. The body has `currentVersion`. |
| 413 | `payload_too_large` | The body or the file is too large. |
| 415 | `unsupported_media_type` | The file type is not allowed, or the content does not match the extension. |
| 422 | `domain_rejected` | A relationship or provider rule was violated, for example a bad `sharewith` entry. |
| 428 | `version_required`, `idempotency_key_required` | A required header is missing. |
| 202 | `partial_success` | An idempotent retry found the first request still running. Use `operationId`. |
| 502 | `downstream_failure` | The object store failed (signed URL). |
| 503 | `temporarily_unavailable` | Core, the cache or the data store is not available. Creating a document with a title whose slug already exists also returns this status, with `retryable: false` and `reason` set to `document_title_exists` (your account) or `document_title_in_use` (another account). Use a more specific title. |
| 500 | `internal_error` | Unexpected error. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.