Reference

Upload API

Updated 1 Oct 2026

On this page

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.

  1. Call POST /upload/check to find likely duplicates.
  2. Call POST /upload to reserve a book and get an upload URL.
  3. PUT the file to that URL.
  4. 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

NameInTypeRequiredConstraintsMeaning
filenamebodystringyesnon-emptyFile name, such as use-the-index-luke.pdf.

Request

bash
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

json
{
  "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

StatusMessageFix
400filename is requiredSend a file name.
429Too many requestsWait 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

NameInTypeRequiredConstraintsMeaning
filenamebodystringyesends in .pdf, .epub, .md or .markdownFile name. It also sets the title.
sizebodyintegeryesmore than 0, within your plan's upload capFile size in bytes.
contentTypebodystringyesapplication/pdf, application/epub+zip, text/markdown or text/plain, matching the extensionType you will send.

Request

bash
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

json
{
  "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.

bash
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/pdf" \
  --data-binary @use-the-index-luke.pdf

Errors

StatusMessageCodeFix
400filename, size, and contentType are requirednoneSend all three.
400Only PDF, EPUB, and Markdown files are supportednoneConvert the file.
400Invalid content type for {TYPE} filenoneMatch the content type to the extension.
400File exceeds {n}MB limitnoneThe cap depends on your plan. See Plans and limits.
400filename must contain at least one visible characternoneUse a real name.
403Your library is full. Upgrade to add more items.PLAN_LIMIT_EXCEEDEDSee Errors.
429Too many library writes in a short window — wait a minute and retry.RATE_LIMITEDWait 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

NameInTypeRequiredConstraintsMeaning
itemIdbodystringyesa book you ownThe itemId from the upload call.
storageKeybodystringyesthe storageKey from the upload callWhere the file is stored.
replacesItemIdbodystringnoa book you ownRemove this older copy after the new one is queued.

Request

bash
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

json
{
  "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

StatusMessageCodeFix
400itemId and storageKey are requirednoneSend both.
400Invalid storageKey formatnoneSend the key exactly as you got it.
404Item not foundnoneCheck itemId.
409This book is already being processed — wait for that to finish before starting another import.noneWait, then check the status.
403The locked-book messageITEM_LOCKEDSee Errors.

CLI equivalent: trove items add.

    Upload API | Trove