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

# Documents

> List knowledge documents, discover importable resources from connected providers, and queue document ingestion jobs.

The documents API is the public REST surface for knowledge ingestion. Use it to list document records, inspect one document in detail, discover importable resources from connected providers, register those resources as Fini documents, and queue ingestion or refresh jobs.

<Note>
  The three `GET` routes on this page require `read`. The two `POST` routes require `write`. The write routes are asynchronous.
</Note>

## Endpoint map

| Method | Path                                       | Scope   | Purpose                                                  |
| ------ | ------------------------------------------ | ------- | -------------------------------------------------------- |
| `GET`  | `/v2/documents/public`                     | `read`  | List document records in the workspace.                  |
| `GET`  | `/v2/documents/public/:id`                 | `read`  | Fetch one document record by ID.                         |
| `GET`  | `/v2/documents/public/resources/:provider` | `read`  | Discover importable resources from a connected provider. |
| `POST` | `/v2/documents/public/resources/:provider` | `write` | Register provider resources and receive document IDs.    |
| `POST` | `/v2/documents/public`                     | `write` | Queue ingestion or refresh jobs.                         |

## Two import flows

### Web links

Call `POST /v2/documents/public` with `source: "web"`. In that case, the values inside `documentIdsToAdd` and `documentIdsToRefresh` are **URLs**, not existing document IDs.

### Connected providers

For `notion`, `zendesk`, and `confluence`, the flow is:

1. `GET /v2/documents/public/resources/:provider`
2. `POST /v2/documents/public/resources/:provider`
3. `POST /v2/documents/public`

The first `POST` only creates or updates document records. The second `POST` is the one that actually queues ingestion.

<Info>
  `POST /v2/documents/public` also accepts `source: "googledrive"`, but there is no public resource-discovery route for Google Drive. Use that source only if you already have Google Drive document IDs from another flow.
</Info>

## List documents

```http theme={null}
GET /v2/documents/public
Authorization: Bearer fini_xxxxxxxxxxxxxxxxx
```

Results are ordered by `updatedAt` descending, then `externalId` descending.

### Query parameters

<ParamField header="Authorization" type="string" required>
  Bearer token containing your Fini workspace API key. Format: `Bearer fini_...`
</ParamField>

<ParamField query="limit" type="integer" default="50">
  Maximum number of documents to return.
</ParamField>

<ParamField query="cursor" type="string">
  Document ID to paginate from. Use the last document's `id` for the next page or the first document's `id` for the previous page.
</ParamField>

<ParamField query="direction" type="string" default="next">
  Pagination direction when a cursor is supplied. `next` moves toward older documents. `previous` moves back toward newer ones.
</ParamField>

<ParamField query="source" type="string">
  Optional document-source filter. Supported values are `web`, `files`, `googledrive`, `zendesk`, `notion`, and `confluence`.
</ParamField>

<ParamField query="english" type="boolean">
  Optional English-processing filter.
</ParamField>

<ParamField query="id" type="string">
  Exact document ID filter.
</ParamField>

<ParamField query="title" type="string">
  Case-insensitive partial match on the document title.
</ParamField>

<ParamField query="url" type="string">
  Case-insensitive partial match on the original source URL.
</ParamField>

<ParamField query="articleId" type="string">
  Filter by linked article or knowledge node ID.
</ParamField>

<ParamField query="linkedOperation" type="string">
  Filter by linked knowledge operation. Supported values are `ADD_ARTICLE_TO_FOLDER`, `UPDATE_ARTICLE`, and `DO_NOTHING`.
</ParamField>

<ParamField query="changed" type="boolean">
  Return only linked documents flagged as changed.
</ParamField>

<Note>
  The response includes `hasMore`, but not a `nextCursor`. Use the returned document IDs themselves as cursors.
</Note>

<CodeGroup>
  ```bash cURL theme={null}
  curl --request GET \
    --url 'https://api-prod.usefini.com/v2/documents/public?limit=25&source=notion' \
    --header 'Authorization: Bearer fini_your_api_key'
  ```

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

  response = requests.get(
      "https://api-prod.usefini.com/v2/documents/public",
      headers={"Authorization": "Bearer fini_your_api_key"},
      params={
          "limit": 25,
          "source": "notion",
      },
  )

  data = response.json()
  ```

  ```javascript Node.js theme={null}
  const params = new URLSearchParams({
    limit: "25",
    source: "notion",
  });

  const response = await fetch(
    `https://api-prod.usefini.com/v2/documents/public?${params.toString()}`,
    {
      headers: {
        Authorization: "Bearer fini_your_api_key",
      },
    }
  );

  const data = await response.json();
  ```
</CodeGroup>

### Response shape

<ResponseField name="documents" type="array">
  Array of document records.

  <Expandable title="document object">
    <ResponseField name="id" type="string">
      Document ID.
    </ResponseField>

    <ResponseField name="originalUrl" type="string">
      Original provider URL or web link.
    </ResponseField>

    <ResponseField name="title" type="string">
      Document title.
    </ResponseField>

    <ResponseField name="storageUrl" type="string | null">
      Internal storage URL when the document has been uploaded into Fini's storage layer.
    </ResponseField>

    <ResponseField name="source" type="string">
      Source type such as `web`, `notion`, `zendesk`, or `confluence`.
    </ResponseField>

    <ResponseField name="externalId" type="string">
      Provider-specific external identifier, or the URL itself for `web`.
    </ResponseField>

    <ResponseField name="english" type="boolean">
      Whether English processing is enabled for the document.
    </ResponseField>

    <ResponseField name="baser" type="boolean">
      Whether BASER processing is enabled for the document.
    </ResponseField>

    <ResponseField name="success" type="boolean">
      Whether the latest processing run succeeded.
    </ResponseField>

    <ResponseField name="error" type="string">
      Latest processing error, if any.
    </ResponseField>

    <ResponseField name="createdAt" type="string">
      ISO 8601 creation timestamp.
    </ResponseField>

    <ResponseField name="updatedAt" type="string">
      ISO 8601 last-update timestamp.
    </ResponseField>

    <ResponseField name="mimeType" type="string">
      Document MIME type.
    </ResponseField>

    <ResponseField name="type" type="string | null">
      Provider-specific type, when available.
    </ResponseField>

    <ResponseField name="linkedKnowledgeId" type="string | null">
      Linked article or knowledge node ID, if this document has already been turned into knowledge.
    </ResponseField>

    <ResponseField name="linkedOperation" type="string | null">
      Operation used when the document last linked into knowledge.
    </ResponseField>

    <ResponseField name="linkedReason" type="string | null">
      Additional reason metadata for the linked operation, when present.
    </ResponseField>

    <ResponseField name="linkedJobId" type="string | null">
      Background job ID for the latest queued ingest or refresh.
    </ResponseField>

    <ResponseField name="linkedJobStatus" type="string | null">
      Background job status for the latest queued ingest or refresh. Values include `PENDING`, `IN_PROGRESS`, `COMPLETED`, and `FAILED`.
    </ResponseField>

    <ResponseField name="paragraphs" type="array">
      Extracted paragraph payload, if the document has already been processed. This can be large.
    </ResponseField>

    <ResponseField name="oldParagraphs" type="array">
      Previous paragraph snapshot used for change detection, when available.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="hasMore" type="boolean">
  Whether more results exist beyond the current page.
</ResponseField>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "documents": [
      {
        "id": "5d9f67a8-d853-4af4-b7ce-23ebba1245e5",
        "originalUrl": "https://www.notion.so/fini/Billing-FAQ-123456",
        "title": "Billing FAQ",
        "storageUrl": "gs://example/documents/5d9f67a8-d853-4af4-b7ce-23ebba1245e5",
        "source": "notion",
        "externalId": "123456",
        "english": true,
        "baser": false,
        "paragraphs": [
          "You can update your billing email from Settings."
        ],
        "oldParagraphs": [],
        "success": true,
        "error": "",
        "createdAt": "2026-05-22T06:05:34.221Z",
        "updatedAt": "2026-05-22T06:10:58.710Z",
        "mimeType": "text/markdown",
        "type": "page",
        "linkedJobId": "9f347ddb-1f90-43a2-ac9f-4f77f37a2c26",
        "linkedKnowledgeId": "8a4bb11a-0ef2-45df-9956-631e41e6cf16",
        "linkedOperation": "UPDATE_ARTICLE",
        "linkedReason": null,
        "linkedJobStatus": "COMPLETED"
      }
    ],
    "hasMore": false
  }
  ```
</ResponseExample>

## Get one document

```http theme={null}
GET /v2/documents/public/:id
Authorization: Bearer fini_xxxxxxxxxxxxxxxxx
```

This route returns the same document shape as the list endpoint, but scoped to one document ID.

```bash cURL theme={null}
curl --request GET \
  --url 'https://api-prod.usefini.com/v2/documents/public/5d9f67a8-d853-4af4-b7ce-23ebba1245e5' \
  --header 'Authorization: Bearer fini_your_api_key'
```

## List provider resources

```http theme={null}
GET /v2/documents/public/resources/:provider
Authorization: Bearer fini_xxxxxxxxxxxxxxxxx
```

This route lists resources that can be imported from a connected provider. The public route currently accepts `notion`, `zendesk`, and `confluence`.

<Warning>
  The provider must already be connected in the workspace. A disconnected or unsupported provider returns `400 Bad Request`.
</Warning>

The response shape is provider-specific:

* **Notion**: a flat list of resources with `externalId`, `title`, `originalUrl`, `mimeType`, `iconLink`, and `object`.
* **Zendesk**: category trees. Categories contain section `children`, and sections can contain article `children`.
* **Confluence**: spaces with `key`, `title`, `originalUrl`, and nested `pages`.

Resources that have already been imported successfully from that provider are excluded from the returned list.

## Register provider resources

```http theme={null}
POST /v2/documents/public/resources/:provider
Authorization: Bearer fini_xxxxxxxxxxxxxxxxx
Content-Type: application/json
```

Use this route after resource discovery. It creates or updates Fini document records for the resources you selected and returns the resulting document IDs.

<Warning>
  This route does **not** queue ingestion by itself. Call [`POST /v2/documents/public`](#queue-document-ingestion) next.
</Warning>

### Request body

```json theme={null}
{
  "resources": [
    {
      "originalUrl": "https://www.notion.so/fini/Billing-FAQ-123456",
      "title": "Billing FAQ",
      "externalId": "123456",
      "mimeType": "text/markdown",
      "object": "page"
    }
  ]
}
```

Each resource object accepts:

* `originalUrl`: provider URL
* `title`: resource title
* `externalId`: provider-specific identifier
* `mimeType`: resource MIME type
* `storageUrl`: optional storage URL
* `object`: optional provider-specific type, for example a Notion object type

### Response

```json 200 OK theme={null}
{
  "addedDocumentIds": [
    "5d9f67a8-d853-4af4-b7ce-23ebba1245e5"
  ],
  "updatedDocumentIds": []
}
```

Pass those IDs into `documentIdsToAdd` or `documentIdsToRefresh` on the next call to `POST /v2/documents/public`.

## Queue document ingestion

```http theme={null}
POST /v2/documents/public
Authorization: Bearer fini_xxxxxxxxxxxxxxxxx
Content-Type: application/json
```

This route queues ingestion or refresh jobs.

### Request body

```json theme={null}
{
  "source": "web",
  "documentIdsToAdd": [
    "https://docs.example.com/billing/refunds"
  ],
  "documentIdsToRefresh": [],
  "english": true,
  "baser": false
}
```

| Field                  | Type       | Description                                                                                                                             |
| ---------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `source`               | `string`   | Supported values are `web`, `googledrive`, `zendesk`, `notion`, and `confluence`. Defaults to `web`.                                    |
| `documentIdsToAdd`     | `string[]` | Items to ingest for the first time. For `source = "web"`, these values are URLs. For every other source, these values are document IDs. |
| `documentIdsToRefresh` | `string[]` | Items to refresh. For `source = "web"`, these values are URLs. For every other source, these values are document IDs.                   |
| `english`              | `boolean`  | Optional. Defaults to `true`.                                                                                                           |
| `baser`                | `boolean`  | Optional. Defaults to `false`.                                                                                                          |

<CodeGroup>
  ```json Web theme={null}
  {
    "source": "web",
    "documentIdsToAdd": [
      "https://docs.example.com/billing/refunds",
      "https://docs.example.com/billing/invoices"
    ],
    "documentIdsToRefresh": [],
    "english": true,
    "baser": false
  }
  ```

  ```json Provider theme={null}
  {
    "source": "notion",
    "documentIdsToAdd": [
      "5d9f67a8-d853-4af4-b7ce-23ebba1245e5"
    ],
    "documentIdsToRefresh": [],
    "english": true,
    "baser": false
  }
  ```
</CodeGroup>

### Response

```json 200 OK theme={null}
{
  "addedDocumentIds": [
    "5d9f67a8-d853-4af4-b7ce-23ebba1245e5"
  ],
  "updatedDocumentIds": []
}
```

The response only confirms which document records were queued. It does not mean processing has finished. Poll the read routes and inspect `linkedJobStatus`, `success`, and `error`.

## Troubleshooting

<AccordionGroup>
  <Accordion title="403 Forbidden" icon="ban">
    The key is valid but does not include `write`. The two `POST` routes on this page require `write`.
  </Accordion>

  <Accordion title="The provider-resources route returns 400" icon="triangle-exclamation">
    The provider is invalid for the public route or not connected in the workspace. The public provider routes accept `notion`, `zendesk`, and `confluence`.
  </Accordion>

  <Accordion title="POST /resources worked but nothing changed in knowledge" icon="circle-question">
    Expected. Resource registration only creates or updates document records. You still need to call `POST /v2/documents/public` to queue ingestion.
  </Accordion>

  <Accordion title="A queued write stays pending" icon="clock">
    Writes are asynchronous. Poll the document read routes and watch `linkedJobStatus`, `success`, and `error`.
  </Accordion>

  <Accordion title="A web import fails immediately" icon="link-slash">
    When `source` is `web`, the values in `documentIdsToAdd` and `documentIdsToRefresh` must be URLs, not existing document IDs.
  </Accordion>
</AccordionGroup>
