curl --request POST \
--url 'https://api-prod.usefini.com/v2/documents/public' \
--header 'Authorization: Bearer fini_your_api_key' \
--header 'Content-Type: application/json' \
--data '{
"source": "web",
"documentIdsToAdd": [
"https://docs.example.com/billing/refunds",
"https://docs.example.com/billing/invoices"
],
"documentIdsToRefresh": [],
"english": true,
"baser": false
}'
curl --request POST \
--url 'https://api-prod.usefini.com/v2/documents/public' \
--header 'Authorization: Bearer fini_your_api_key' \
--header 'Content-Type: application/json' \
--data '{
"source": "notion",
"documentIdsToAdd": [
"5d9f67a8-d853-4af4-b7ce-23ebba1245e5"
],
"documentIdsToRefresh": [],
"english": true,
"baser": false
}'
import requests
# Web import — values are URLs
response = requests.post(
"https://api-prod.usefini.com/v2/documents/public",
headers={
"Authorization": "Bearer fini_your_api_key",
"Content-Type": "application/json",
},
json={
"source": "web",
"documentIdsToAdd": [
"https://docs.example.com/billing/refunds",
],
"documentIdsToRefresh": [],
"english": True,
"baser": False,
},
)
data = response.json()
// Provider import — values are document IDs from Register
const response = await fetch(
"https://api-prod.usefini.com/v2/documents/public",
{
method: "POST",
headers: {
Authorization: "Bearer fini_your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
source: "notion",
documentIdsToAdd: ["5d9f67a8-d853-4af4-b7ce-23ebba1245e5"],
documentIdsToRefresh: [],
english: true,
baser: false,
}),
}
);
const data = await response.json();
{
"jobs": [
{
"id": "job_01j4fq3f0p7mef4p2j2h2vp4my",
"documentId": "4f5ef695-d03b-4d56-8fef-7f2bd5c17ef3",
"status": "PENDING"
}
],
"documents": [
{
"id": "4f5ef695-d03b-4d56-8fef-7f2bd5c17ef3",
"source": "web",
"url": "https://help.example.com/refunds",
"title": "Refund policy",
"success": true,
"changed": false,
"linkedJobStatus": "COMPLETED",
"linkedKnowledgeId": "7fa27325-e2f6-4420-b705-436f9106f908",
"createdAt": "2026-07-28T08:55:32.000Z",
"updatedAt": "2026-07-28T09:10:18.000Z"
}
]
}
Sources
Ingest sources
Queue first-time ingestion or refresh jobs for web links or existing source IDs. The call returns immediately because processing is async.
POST
/
v2
/
documents
/
public
curl --request POST \
--url 'https://api-prod.usefini.com/v2/documents/public' \
--header 'Authorization: Bearer fini_your_api_key' \
--header 'Content-Type: application/json' \
--data '{
"source": "web",
"documentIdsToAdd": [
"https://docs.example.com/billing/refunds",
"https://docs.example.com/billing/invoices"
],
"documentIdsToRefresh": [],
"english": true,
"baser": false
}'
curl --request POST \
--url 'https://api-prod.usefini.com/v2/documents/public' \
--header 'Authorization: Bearer fini_your_api_key' \
--header 'Content-Type: application/json' \
--data '{
"source": "notion",
"documentIdsToAdd": [
"5d9f67a8-d853-4af4-b7ce-23ebba1245e5"
],
"documentIdsToRefresh": [],
"english": true,
"baser": false
}'
import requests
# Web import — values are URLs
response = requests.post(
"https://api-prod.usefini.com/v2/documents/public",
headers={
"Authorization": "Bearer fini_your_api_key",
"Content-Type": "application/json",
},
json={
"source": "web",
"documentIdsToAdd": [
"https://docs.example.com/billing/refunds",
],
"documentIdsToRefresh": [],
"english": True,
"baser": False,
},
)
data = response.json()
// Provider import — values are document IDs from Register
const response = await fetch(
"https://api-prod.usefini.com/v2/documents/public",
{
method: "POST",
headers: {
Authorization: "Bearer fini_your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
source: "notion",
documentIdsToAdd: ["5d9f67a8-d853-4af4-b7ce-23ebba1245e5"],
documentIdsToRefresh: [],
english: true,
baser: false,
}),
}
);
const data = await response.json();
{
"jobs": [
{
"id": "job_01j4fq3f0p7mef4p2j2h2vp4my",
"documentId": "4f5ef695-d03b-4d56-8fef-7f2bd5c17ef3",
"status": "PENDING"
}
],
"documents": [
{
"id": "4f5ef695-d03b-4d56-8fef-7f2bd5c17ef3",
"source": "web",
"url": "https://help.example.com/refunds",
"title": "Refund policy",
"success": true,
"changed": false,
"linkedJobStatus": "COMPLETED",
"linkedKnowledgeId": "7fa27325-e2f6-4420-b705-436f9106f908",
"createdAt": "2026-07-28T08:55:32.000Z",
"updatedAt": "2026-07-28T09:10:18.000Z"
}
]
}
Queues ingestion or refresh jobs for sources. This is the call that actually starts processing, both for web links, which skip registration entirely, and for provider content, where you have already called Register provider resources.
Watch these fields on the document record:
This route is asynchronous. The response only confirms the job was queued. Poll List sources or Get source and watch
linkedJobStatus, success, and error to track progress.The request body still uses
documentIdsToAdd and documentIdsToRefresh because that is the current API contract. On this page, those values are described as source inputs or source IDs.Add sources with this endpoint
POST /v2/documents/public is the add-sources endpoint for both web content and connected providers:
- For
web, place URLs directly indocumentIdsToAdd. - For
zendesk,confluence, andnotion, first use List provider resources and Register provider resources, then place the returned source IDs indocumentIdsToAdd.
| Input type | What goes in documentIdsToAdd | Notes |
|---|---|---|
web | URLs | You can optionally crawl first with POST /v2/documents/public/deep-crawl/links. |
zendesk | Source IDs returned after register | Discovery response is a nested category tree. |
confluence | Source IDs returned after register | Discovery response is an array of spaces with nested pages. |
notion | Source IDs returned after register | Discovery response is a flat array of resources. |
Crawling is not part of
POST /v2/documents/public itself. If you want Fini to expand a seed URL into more pages first, call POST /v2/documents/public/deep-crawl/links, then send the returned URLs into documentIdsToAdd on this route.Headers
string
required
Bearer token containing your Fini workspace API key. Format:
Bearer fini_... The key needs write scope.string
required
application/jsonBody parameters
string
default:"web"
Source type. Supported values:
web, googledrive, notion, zendesk, confluence. Determines what the documentIdsToAdd and documentIdsToRefresh arrays contain.array
Items to ingest for the first time. For
source: "web", these values are URLs. For every other source, these values are source IDs returned by Register provider resources or already in your workspace.array
Items to refresh. Same value semantics as
documentIdsToAdd: URLs for web, source IDs for everything else.boolean
default:"true"
Whether to apply English-specific processing.
boolean
default:"false"
Whether to apply BASER processing.
The same field names carry different value types depending on
source. This is the most common cause of failed web imports.curl --request POST \
--url 'https://api-prod.usefini.com/v2/documents/public' \
--header 'Authorization: Bearer fini_your_api_key' \
--header 'Content-Type: application/json' \
--data '{
"source": "web",
"documentIdsToAdd": [
"https://docs.example.com/billing/refunds",
"https://docs.example.com/billing/invoices"
],
"documentIdsToRefresh": [],
"english": true,
"baser": false
}'
curl --request POST \
--url 'https://api-prod.usefini.com/v2/documents/public' \
--header 'Authorization: Bearer fini_your_api_key' \
--header 'Content-Type: application/json' \
--data '{
"source": "notion",
"documentIdsToAdd": [
"5d9f67a8-d853-4af4-b7ce-23ebba1245e5"
],
"documentIdsToRefresh": [],
"english": true,
"baser": false
}'
import requests
# Web import — values are URLs
response = requests.post(
"https://api-prod.usefini.com/v2/documents/public",
headers={
"Authorization": "Bearer fini_your_api_key",
"Content-Type": "application/json",
},
json={
"source": "web",
"documentIdsToAdd": [
"https://docs.example.com/billing/refunds",
],
"documentIdsToRefresh": [],
"english": True,
"baser": False,
},
)
data = response.json()
// Provider import — values are document IDs from Register
const response = await fetch(
"https://api-prod.usefini.com/v2/documents/public",
{
method: "POST",
headers: {
Authorization: "Bearer fini_your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
source: "notion",
documentIdsToAdd: ["5d9f67a8-d853-4af4-b7ce-23ebba1245e5"],
documentIdsToRefresh: [],
english: true,
baser: false,
}),
}
);
const data = await response.json();
Response
array
Source IDs for items that were queued for first-time ingestion.
array
Source IDs for items that were queued for refresh.
{
"jobs": [
{
"id": "job_01j4fq3f0p7mef4p2j2h2vp4my",
"documentId": "4f5ef695-d03b-4d56-8fef-7f2bd5c17ef3",
"status": "PENDING"
}
],
"documents": [
{
"id": "4f5ef695-d03b-4d56-8fef-7f2bd5c17ef3",
"source": "web",
"url": "https://help.example.com/refunds",
"title": "Refund policy",
"success": true,
"changed": false,
"linkedJobStatus": "COMPLETED",
"linkedKnowledgeId": "7fa27325-e2f6-4420-b705-436f9106f908",
"createdAt": "2026-07-28T08:55:32.000Z",
"updatedAt": "2026-07-28T09:10:18.000Z"
}
]
}
Polling for completion
The response above means jobs are queued, not finished. Poll the read routes:Poll
curl --request GET \
--url 'https://api-prod.usefini.com/v2/documents/public/5d9f67a8-d853-4af4-b7ce-23ebba1245e5' \
--header 'Authorization: Bearer fini_your_api_key'
| Field | Meaning |
|---|---|
linkedJobStatus | PENDING → IN_PROGRESS → COMPLETED or FAILED |
success | true when the latest run produced usable content |
error | Non-empty string when the latest run failed |
paragraphs | Populated after a successful run |
linkedKnowledgeId | Set once the document has been linked to an article |
A
COMPLETED job means the source was processed. It does not mean its content is live in agent answers. Source ingestion still needs to flow through Knowledge, and review is the recommended step before publishing.Errors
400 Bad Request
400 Bad Request
Common causes: unsupported
source value, missing required fields, or passing source IDs in documentIdsToAdd when source: "web" because web imports expect URLs.401 Unauthorized
401 Unauthorized
The API key is missing, malformed, revoked, or invalid. Confirm
Authorization: Bearer fini_... with the full key.403 Forbidden
403 Forbidden
The API key doesn’t include the
write scope, or it’s scoped to a different workspace.A web import was accepted but fails during processing
A web import was accepted but fails during processing
The job was queued successfully but failed during fetch. Check
error on the document record. Common causes: URL behind authentication, robots.txt blocks, or content type Fini doesn’t extract.A provider import was accepted but fails during processing
A provider import was accepted but fails during processing
The provider connection may have expired or lost permissions on the resource. Reconnect the provider in the dashboard and refresh the source.
A job stays PENDING for a long time
A job stays PENDING for a long time
Ingestion is queued. Under heavy workspace load,
PENDING can persist longer than usual. If a job sits for more than 15 minutes, contact support with the linkedJobId from the source record.Was this page helpful?

