curl --request POST \
--url https://app.sapientpriors.com/api/v1/agents/{agent_id}/threads/{thread_id}/sources/{source_id}/finish-learning \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"here": false,
"max_chars": 2100,
"overlap_chars": 1
}
'import requests
url = "https://app.sapientpriors.com/api/v1/agents/{agent_id}/threads/{thread_id}/sources/{source_id}/finish-learning"
payload = {
"here": False,
"max_chars": 2100,
"overlap_chars": 1
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({here: false, max_chars: 2100, overlap_chars: 1})
};
fetch('https://app.sapientpriors.com/api/v1/agents/{agent_id}/threads/{thread_id}/sources/{source_id}/finish-learning', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"agent_id": "<string>",
"thread_id": "<string>",
"observation_id": "<string>",
"accepted": true,
"turns_total": 123,
"filename": "<string>",
"doc_format": "<string>",
"source_id": "<string>",
"deferred": false,
"figures": 123,
"figures_dropped": 1,
"words_total": 1,
"duplicate": false,
"document_state": "ingesting",
"queued": true
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}Finish a stopped or failed upload from its stored copy
Finishes an upload that stopped partway or failed, from the copy Priors stored at upload. You send no file. By default the upload resumes in the thread it was uploaded into, so only the part that was not learned is read and charged. The job can run in that thread instead of the thread in the path, and the response names it. To resume it there, you must be able to open that thread (can_finish on the sources listing). Otherwise the call returns 409, and you can send {"here": true} to read the whole file again in this thread, charged in full. When the agent is reading another file, the job waits its turn (queued).
curl --request POST \
--url https://app.sapientpriors.com/api/v1/agents/{agent_id}/threads/{thread_id}/sources/{source_id}/finish-learning \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"here": false,
"max_chars": 2100,
"overlap_chars": 1
}
'import requests
url = "https://app.sapientpriors.com/api/v1/agents/{agent_id}/threads/{thread_id}/sources/{source_id}/finish-learning"
payload = {
"here": False,
"max_chars": 2100,
"overlap_chars": 1
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({here: false, max_chars: 2100, overlap_chars: 1})
};
fetch('https://app.sapientpriors.com/api/v1/agents/{agent_id}/threads/{thread_id}/sources/{source_id}/finish-learning', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"agent_id": "<string>",
"thread_id": "<string>",
"observation_id": "<string>",
"accepted": true,
"turns_total": 123,
"filename": "<string>",
"doc_format": "<string>",
"source_id": "<string>",
"deferred": false,
"figures": 123,
"figures_dropped": 1,
"words_total": 1,
"duplicate": false,
"document_state": "ingesting",
"queued": true
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}{
"error": "<string>",
"code": "<string>",
"request_id": "<string>",
"details": "<string>"
}Authorizations
Your API key, shaped sp_<key_id>_<secret>, sent as Authorization: Bearer sp_.... It identifies your organization, not a person. Your organization owns the agents, holds the credit, and is billed. Every key your organization holds reaches the same things, so you rotate a key by minting the new one, deploying it, and revoking the old one.
Keep the key on your server. A request that carries a Sec-Fetch-Site header is refused with 400, because only a browser sets that header. Priors sends no CORS headers, so a browser blocks the call before it leaves the page. The secret is shown once when the key is minted and is stored only as a hash. A lost secret is replaced, never recovered.
An administrator of your organization mints and revokes keys in the Priors application, under Settings. There is no endpoint for it: a key reaches your whole organization, so issuing one is an act of administration.
Headers
Your own identifier for the person this request acts for: an account number, a row id, anything stable on your side. It works only with a partner API key. The value is not stored. Priors hashes it together with your organization id, so two clients that send the same string are still two different people.
With the header, you reach the threads that same person created and no others. A new thread is recorded against that person, and GET /v1/agents/{agent_id} returns that person's conversations only. Without the header, the key acts as the organization and reaches every thread on every agent it owns. Learned memory is shared across the agent either way. Only the thread list narrows.
Sent with anything that is not a partner API key, the header returns 400 instead of being ignored. Sent empty or as whitespace, it also returns 400, and it is never read as absent: absent means the whole organization, which is wider access than you asked for. Surrounding whitespace is removed. The 255-character limit is measured on the value as sent.
1 - 255\SPath Parameters
Unique managed memory-agent identifier.
32^agent_[0-9A-HJKMNP-TV-Z]{26}$Unique thread identifier scoped to the agent.
33^thread_[0-9A-HJKMNP-TV-Z]{26}$The stored document.
^src_[0-9a-f]{32}$Body
Read the whole file again in this thread, instead of finishing it in the thread where it was uploaded. This stores a second copy, charged in full.
The splitting settings the upload was sent with, as on observe-file (this field and the two below). Priors normally remembers them and ignores these. They matter only for an older upload whose settings were not recorded. A partial upload resumes only with the settings it was read with; other settings return 409.
200 <= x <= 4000x >= 0auto, length, sentence, code, structure Response
Accepted. The body is the same as the observe-file response. Poll the job it names.
Server-generated agent identifier.
^agent_[0-9A-HJKMNP-TV-Z]{26}$Server-generated thread identifier.
^thread_[0-9A-HJKMNP-TV-Z]{26}$Server-issued identifier for the asynchronous ingestion. Pass it to the observation status endpoint to follow progress.
^obs_[0-9A-HJKMNP-TV-Z]{26}$How many turns the document was split into. Exact when deferred is false, because conversion and splitting happened in-request. 0 when deferred is true; the status endpoint reports the real count once conversion finishes.
The uploaded filename, which also becomes each turn's speaker.
The canonical document format actually converted, e.g. pdf.
Identifier of the persisted original. Empty when ingestion succeeded but source storage was unavailable.
True when the document needed OCR and had more pages than can be read inside one request. It was accepted and is being converted in the background; poll the status endpoint. A conversion failure is that job's error.
Figures kept from the document. Each is stored as a crop and described inline in the turn where it appeared; figures add no turns of their own.
Figures omitted because the document exceeded extraction budgets.
x >= 0Words across every turn the upload produced (text chunks plus figure captions). The input side of what this upload is billed — the same count /observe would bill for the same turns.
x >= 0True when this agent had already ingested a document with these exact bytes, so nothing was read, stored or billed for this request. observation_id, turns_total and source_id then describe the ORIGINAL ingest, not this call. Show it as "already processed" rather than as a fresh upload — claiming new work happened would be untrue, and the progress it reports is already finished.
Where this document stands for this agent. completed with duplicate: true is a reuse; ingesting with duplicate: true means a job was already running and this request joined it rather than starting a second, so its progress is worth polling. Absent on a plain first upload, which is simply in progress.
ingesting, partial, completed, failed True when the agent was reading another file. This file is accepted and waits its turn (the status field queued), then starts by itself. You do not need to send it again.