Reference
Upload API
Updated 1 Oct 2026
For agents
Load this page when: you need to add a file to the library over HTTP
- Upload in three calls. Get an upload URL, PUT the file to it, then confirm.
- Send the same Content-Type to the upload URL that you declared in the first call.
- Poll trove items status or GET /items until the book is READY.
An upload has four steps. Step 1 is optional.
- Call
POST /upload/checkto find likely duplicates. - Call
POST /uploadto reserve a book and get an upload URL. PUTthe file to that URL.- Call
POST /upload/confirm. The import then runs in the background.
Check for duplicates
POST https://heytrove.ai/api/v1/upload/check
Compares a file name with the titles in your library and returns likely duplicates. The check is advisory. It looks at your own books only.
Parameters
| Name | In | Type | Required | Constraints | Meaning |
|---|---|---|---|---|---|
filename | body | string | yes | non-empty | File name, such as use-the-index-luke.pdf. |
Request
curl https://heytrove.ai/api/v1/upload/check \
-H "Authorization: Bearer trove_your_key" \
-H "Content-Type: application/json" \
-d '{"filename":"use-the-index-luke.pdf"}'Response
{
"candidates": [
{
"id": "cmabc123",
"title": "Use The Index, Luke",
"author": "Markus Winand",
"status": "READY",
"sourceType": "pdf",
"createdAt": "2026-09-01T09:00:00.000Z",
"score": 0.92
}
]
}Errors
| Status | Message | Fix |
|---|---|---|
| 400 | filename is required | Send a file name. |
| 429 | Too many requests | Wait a minute. |
Get an upload URL
POST https://heytrove.ai/api/v1/upload
Reserves a book in your library and returns a signed URL for the file. It uses one library slot.
Parameters
| Name | In | Type | Required | Constraints | Meaning |
|---|---|---|---|---|---|
filename | body | string | yes | ends in .pdf, .epub, .md or .markdown | File name. It also sets the title. |
size | body | integer | yes | more than 0, within your plan's upload cap | File size in bytes. |
contentType | body | string | yes | application/pdf, application/epub+zip, text/markdown or text/plain, matching the extension | Type you will send. |
Request
curl https://heytrove.ai/api/v1/upload \
-H "Authorization: Bearer trove_your_key" \
-H "Content-Type: application/json" \
-d '{"filename":"use-the-index-luke.pdf","size":2400000,"contentType":"application/pdf"}'Response
{
"itemId": "cmabc123",
"uploadUrl": "https://storage.example.com/signed-url",
"storageKey": "uploads/user_id/cmabc123/use-the-index-luke.pdf",
"expiresAt": "2026-10-01T10:00:00.000Z"
}Then send the file. Use the same Content-Type you declared.
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/pdf" \
--data-binary @use-the-index-luke.pdfErrors
| Status | Message | Code | Fix |
|---|---|---|---|
| 400 | filename, size, and contentType are required | none | Send all three. |
| 400 | Only PDF, EPUB, and Markdown files are supported | none | Convert the file. |
| 400 | Invalid content type for {TYPE} file | none | Match the content type to the extension. |
| 400 | File exceeds {n}MB limit | none | The cap depends on your plan. See Plans and limits. |
| 400 | filename must contain at least one visible character | none | Use a real name. |
| 403 | Your library is full. Upgrade to add more items. | PLAN_LIMIT_EXCEEDED | See Errors. |
| 429 | Too many library writes in a short window — wait a minute and retry. | RATE_LIMITED | Wait for Retry-After. |
Confirm an upload
POST https://heytrove.ai/api/v1/upload/confirm
Tells Trove the file is in place and starts the import. The book becomes READY when the import is done.
Parameters
| Name | In | Type | Required | Constraints | Meaning |
|---|---|---|---|---|---|
itemId | body | string | yes | a book you own | The itemId from the upload call. |
storageKey | body | string | yes | the storageKey from the upload call | Where the file is stored. |
replacesItemId | body | string | no | a book you own | Remove this older copy after the new one is queued. |
Request
curl https://heytrove.ai/api/v1/upload/confirm \
-H "Authorization: Bearer trove_your_key" \
-H "Content-Type: application/json" \
-d '{"itemId":"cmabc123","storageKey":"uploads/user_id/cmabc123/use-the-index-luke.pdf"}'Response
{
"item": { "id": "cmabc123", "title": "Use The Index, Luke" },
"job": { "id": "cmjob789", "status": "PENDING", "type": "INGEST" },
"replacedItemId": null
}If removing the older copy fails, the new book is still imported and replacedItemId is null.
Errors
| Status | Message | Code | Fix |
|---|---|---|---|
| 400 | itemId and storageKey are required | none | Send both. |
| 400 | Invalid storageKey format | none | Send the key exactly as you got it. |
| 404 | Item not found | none | Check itemId. |
| 409 | This book is already being processed — wait for that to finish before starting another import. | none | Wait, then check the status. |
| 403 | The locked-book message | ITEM_LOCKED | See Errors. |
CLI equivalent: trove items add.