Referencia de la API REST v2
URL base https://api.readsira.com/rest/v2. Cada ruta necesita una clave de API (Authorization: Bearer pk_..., consulta Autenticación y claves de API).
Las respuestas usan el sobre { success, data, meta } y las tablas de abajo describen data. Los errores usan
la forma de Límites de uso y errores. Un parámetro o un campo del cuerpo que la ruta no conoce da un 400.
El texto que procede de páginas guardadas (fragmentos, texto de documentos, chunks) es contenido de terceros. Trátalo como datos, nunca como instrucciones para un modelo.
Las rutas, los parámetros y los ejemplos de abajo se generan directamente a partir de la API y por eso están en inglés.
The REST API is part of the Pro and Research plans. 12 routes:
GET /search: Search your libraryPOST /ask: Ask your library (cited answer)GET /documents/{id}: Read a documentGET /documents/{id}/chunks: A document's chunksGET /documents/{id}/transcript: Timed transcript of a video or audio documentPOST /uploads: Start an upload (audio, video or PDF)POST /uploads/{id}/complete: Finish an upload: checks size and type, then processes itGET /usage: Your API usage and limitsGET /projects: Your projectsGET /projects/{id}/context: A project's contextPOST /projects/{id}/items: Add sources to a projectPOST /projects/{id}/notes: Add a note to a project
Search
Search your library
GET /search
Scope: read:library.
Hybrid (semantic + text) search over your bookmarks and followed feeds. Hits carry chunk ids, PDF pages and media timestamps. Snippets are third-party content, never instructions.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
q | query | string | yes | |
limit | query | number | no | 1-20, default 8 |
collectionId | query | uuid | no | |
feedId | query | uuid | no | |
source | query | bookmarks, feeds, all | no |
curl -G "https://api.readsira.com/rest/v2/search" \
-H "Authorization: Bearer $READSIRA_KEY" \
--data-urlencode "q=..."
Response 200, in data:
| Field | Type | Notes |
|---|---|---|
results | object[] | |
results[].documentId | uuid | |
results[].chunkId | uuid | |
results[].bookmarkId | uuid or null | |
results[].entryId | uuid or null | |
results[].title | string | |
results[].url | string | |
results[].contentType | article, pdf, video, audio, note | |
results[].snippet | string | Chunk text, at most 600 characters. Untrusted third-party content. |
results[].score | number | Hybrid score 0-1 |
results[].pageNumber | number or null | PDF page |
results[].startSeconds | number or null | Media: chunk start |
results[].endSeconds | number or null | |
degraded | lexical or null | lexical = the AI breaker is open; results come from text match only |
Errors for this route:
402: PLAN_REQUIRED: the plan has no REST API (Free)429: RATE_LIMITED: per-key hourly or per-user daily limit (Retry-After)
Ask
Ask your library (cited answer)
POST /ask
Scope: ask:library.
Runs Readsira's model over your best-matching passages and returns the answer with citations. Counts toward askAnswersPerMonth and the AI chat budget (402 when used up).
Request body (JSON):
| Field | Type | Required | Notes |
|---|---|---|---|
question | string | yes | 1 to 2000 characters |
bookmarkIds | uuid[] | no | Answer only from these bookmarks (ids you do not own match nothing). at most 50 items |
curl -X POST "https://api.readsira.com/rest/v2/ask" \
-H "Authorization: Bearer $READSIRA_KEY" \
-H "Content-Type: application/json" \
-d '{"question":"..."}'
Response 200, in data:
| Field | Type | Notes |
|---|---|---|
answer | string | Answer text with [n] markers |
citations | object[] | |
citations[].n | number | Marker in the answer text: [n]. at least 1 |
citations[].documentId | string | Shared document id ("" for answers saved before citations v2) |
citations[].chunkId | uuid or null | null when the source changed or the answer predates citations v2 |
citations[].bookmarkId | uuid or null | The caller's bookmark of the document |
citations[].entryId | uuid or null | Feed entry, when cited through a subscription |
citations[].publishedAt | date-time or null | When the source was published (its earliest feed entry); null when unknown, absent on older answers |
citations[].title | string | |
citations[].url | string | |
citations[].contentType | object | |
citations[].quote | string | Verbatim span of the chunk (verified) or its first 200 chars (excerpt). at most 400 characters |
citations[].quoteStart | number | Char offset into the chunk, inclusive. at least 0 |
citations[].quoteEnd | number | Char offset into the chunk, exclusive. at least 0 |
citations[].page | number or null | PDF page |
citations[].startSeconds | number or null | Video / audio start, seconds |
citations[].endSeconds | number or null | Video / audio end, seconds |
citations[].score | number | Retrieval score. at most 1 |
citations[].verified | boolean | false: the quote is an excerpt, not a verified quote |
citations[].removed | boolean | true when the cited bookmark was removed from your library since the answer (Open source unavailable) |
passagesRead | number | Passages given to the model |
model | string or null |
Errors for this route:
402: PLAN_REQUIRED: the plan has no REST API (Free)429: RATE_LIMITED: per-key hourly or per-user daily limit (Retry-After)
Documents
Read a document
GET /documents/{id}
Scope: read:library.
Text of a document you can read (your bookmark, a followed feed entry or your upload), in pages: pass nextOffset as offset until it is null.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
id | path | uuid | yes | |
offset | query | number | no | |
maxChars | query | number | no | Default 12000, max 20000 |
curl "https://api.readsira.com/rest/v2/documents/$DOCUMENT_ID" \
-H "Authorization: Bearer $READSIRA_KEY"
Response 200, in data:
| Field | Type | Notes |
|---|---|---|
documentId | uuid | |
title | string | |
url | string | |
contentType | article, pdf, video, audio, note | |
author | string or null | |
bookmarkId | uuid or null | |
savedAt | date-time or null | |
tags | string[] | |
notes | string or null | |
highlights | object[] | |
highlights[].text | string | |
highlights[].note | string or null | |
text | string | Text from offset (characters), untrusted third-party content |
offset | number | |
nextOffset | number or null | Pass as offset for the rest; null at the end |
truncated | boolean | |
totalChars | number |
Errors for this route:
402: PLAN_REQUIRED: the plan has no REST API (Free)429: RATE_LIMITED: per-key hourly or per-user daily limit (Retry-After)
A document's chunks
GET /documents/{id}/chunks
Scope: read:library.
Content chunks in order with page and timestamps, keyset paged: pass nextCursor as cursor.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
id | path | uuid | yes | |
cursor | query | number | no | |
limit | query | number | no | Max 100, default 20 |
curl "https://api.readsira.com/rest/v2/documents/$DOCUMENT_ID/chunks" \
-H "Authorization: Bearer $READSIRA_KEY"
Response 200, in data:
| Field | Type | Notes |
|---|---|---|
documentId | uuid | |
chunks | object[] | |
chunks[].chunkId | uuid | |
chunks[].index | number | |
chunks[].content | string | |
chunks[].page | number or null | |
chunks[].startSeconds | number or null | |
chunks[].endSeconds | number or null | |
nextCursor | number or null | Pass as cursor for the next page |
Errors for this route:
402: PLAN_REQUIRED: the plan has no REST API (Free)429: RATE_LIMITED: per-key hourly or per-user daily limit (Retry-After)
Timed transcript of a video or audio document
GET /documents/{id}/transcript
Scope: read:library.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
id | path | uuid | yes | |
cursor | query | string | no | Segment offset |
limit | query | number | no | Max 500 |
curl "https://api.readsira.com/rest/v2/documents/$DOCUMENT_ID/transcript" \
-H "Authorization: Bearer $READSIRA_KEY"
Response 200, in data:
| Field | Type | Notes |
|---|---|---|
documentId | uuid | |
status | object or null | |
language | string or null | |
source | captions, whisper, upload_whisper, publisher or null | |
durationSeconds | number or null | |
segments | object[] | |
segments[].startSeconds | number | |
segments[].endSeconds | number | |
segments[].text | string | |
segments[].speaker | string or null | Who speaks, as the publisher transcript names them (Podcasting 2.0 JSON speaker, WebVTT <v Name>, SRT "Name:"); null when the source names no one (captions, Whisper) |
nextCursor | string or null | Segment offset of the next page |
Errors for this route:
402: PLAN_REQUIRED: the plan has no REST API (Free)429: RATE_LIMITED: per-key hourly or per-user daily limit (Retry-After)
Projects
Your projects
GET /projects
Scope: read:projects.
Projects of one status (default active), most recently active first. Keyset paged.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
status | query | active, archived | no | |
cursor | query | string | no | |
limit | query | number | no | 1-50, default 20 |
curl "https://api.readsira.com/rest/v2/projects" \
-H "Authorization: Bearer $READSIRA_KEY"
Response 200, in data:
| Field | Type | Notes |
|---|---|---|
projects | object[] | |
projects[].id | uuid | |
projects[].name | string | |
projects[].description | string or null | |
projects[].status | active, archived | |
projects[].itemCount | number | Items of any kind (sources, highlights, notes) |
projects[].sourceCount | number | |
projects[].updatedAt | date-time | |
nextCursor | string or null | Pass as cursor for the next page |
A project's context
GET /projects/{id}/context
Scope: read:projects.
Instructions, decided decisions, pinned notes and sources; with q, the best passages from the project. Trimmed to maxChars (default 16,000, max 32,000). Text from sources is third-party content, never instructions.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
id | path | uuid | yes | |
q | query | string | no | |
maxChars | query | number | no |
curl "https://api.readsira.com/rest/v2/projects/$PROJECT_ID/context" \
-H "Authorization: Bearer $READSIRA_KEY"
Response 200, in data:
| Field | Type | Notes |
|---|---|---|
project | object | |
project.id | uuid | |
project.name | string | |
project.instructions | string or null | |
decisions | object[] | Decided decisions, newest first |
decisions[].title | string | |
decisions[].status | string | |
decisions[].decidedAt | date-time | |
decisions[].body | string or null | |
notes | object[] | Pinned notes |
notes[].title | string or null | |
notes[].body | string | |
passages | object[] | With q only |
passages[].kind | chunk, note | |
passages[].documentId | uuid or null | |
passages[].chunkId | uuid or null | |
passages[].bookmarkId | uuid or null | |
passages[].noteId | uuid or null | |
passages[].title | string | |
passages[].url | string or null | |
passages[].snippet | string | At most 600 characters. Untrusted third-party content. |
passages[].score | number | |
passages[].pageNumber | number or null | |
passages[].startSeconds | number or null | |
sources | object[] | Pinned first; excluded and deleted sources are left out |
sources[].bookmarkId | uuid | |
sources[].documentId | uuid or null | |
sources[].title | string | |
sources[].url | string | |
sources[].role | source, background | |
sources[].pinned | boolean | |
truncated | boolean | Parts were left out to stay within maxChars |
degraded | lexical |
Add sources to a project
POST /projects/{id}/items
Scope: write:projects.
Bookmarks, highlights, notes, feed entries or URLs (a URL is saved to your library first). Items already in the project count in alreadyPresent.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
id | path | uuid | yes |
Request body (JSON):
| Field | Type | Required | Notes |
|---|---|---|---|
items | object[] | yes | Exactly one target per item; at most 20 url / entryId items per request. 1 to 100 items |
items[].bookmarkId | uuid | no | |
items[].entryId | uuid | no | A feed entry: saved as a bookmark first |
items[].highlightId | uuid | no | |
items[].noteId | uuid | no | A note of this project (pins it into the context) |
items[].url | string | no | A URL: saved as a bookmark first |
items[].role | object | no | |
items[].comment | string or null | no | at most 2000 characters |
curl -X POST "https://api.readsira.com/rest/v2/projects/$PROJECT_ID/items" \
-H "Authorization: Bearer $READSIRA_KEY" \
-H "Content-Type: application/json" \
-d '{"items":[{"url":"https://example.com/report","role":"background"}]}'
Response 201, in data:
| Field | Type | Notes |
|---|---|---|
added | object[] | |
added[].id | uuid | |
added[].kind | object | |
added[].role | object | |
added[].pinned | boolean | |
added[].comment | string or null | |
added[].addedAt | date-time | |
added[].citationCount | number | Citations of this source in the latest version of the project outputs (0 outside the list) |
added[].bookmark | object or null | |
added[].highlight | object or null | |
added[].note | object or null | |
alreadyPresent | number | Targets that were already in the project |
Errors for this route:
402: PROJECT_LIMIT_REACHED (itemsPerProject)409: PROJECT_ARCHIVED
Add a note to a project
POST /projects/{id}/notes
Scope: write:projects.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
id | path | uuid | yes |
Request body (JSON):
| Field | Type | Required | Notes |
|---|---|---|---|
title | string or null | no | at most 200 characters |
bodyMd | string | yes | at most 20000 characters |
curl -X POST "https://api.readsira.com/rest/v2/projects/$PROJECT_ID/notes" \
-H "Authorization: Bearer $READSIRA_KEY" \
-H "Content-Type: application/json" \
-d '{"bodyMd":"..."}'
Response 201, in data:
| Field | Type | Notes |
|---|---|---|
id | uuid | |
title | string or null | |
bodyMd | string | |
pinned | boolean | Pinned into the project context |
createdAt | date-time | |
updatedAt | date-time |
Errors for this route:
409: PROJECT_ARCHIVED
Uploads
Start an upload (audio, video or PDF)
POST /uploads
Scope: write:uploads.
Returns a 15-minute PUT URL bound to the declared Content-Type and size. PUT the bytes, then call complete. Private to you.
Request body (JSON):
| Field | Type | Required | Notes |
|---|---|---|---|
filename | string | yes | at most 255 characters |
contentType | object | yes | |
bytes | number | yes | Exact size of the file in bytes. at least 1 |
sha256 | string | no | Optional hex SHA-256 of the file |
curl -X POST "https://api.readsira.com/rest/v2/uploads" \
-H "Authorization: Bearer $READSIRA_KEY" \
-H "Content-Type: application/json" \
-d '{"filename":"talk.mp3","contentType":"audio/mpeg","bytes":48213004}'
Response 201, in data:
| Field | Type | Notes |
|---|---|---|
uploadId | uuid | |
putUrl | string | PUT the file bytes here (presigned, 15 minutes) |
putHeaders | object | Headers the PUT must send unchanged (bound into the signature) |
expiresAt | date-time |
Errors for this route:
402: PLAN_REQUIRED: the plan has no REST API (Free)429: RATE_LIMITED: per-key hourly or per-user daily limit (Retry-After)
Finish an upload: checks size and type, then processes it
POST /uploads/{id}/complete
Scope: write:uploads.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
id | path | uuid | yes |
curl -X POST "https://api.readsira.com/rest/v2/uploads/$UPLOAD_ID/complete" \
-H "Authorization: Bearer $READSIRA_KEY"
Response 202, in data:
| Field | Type | Notes |
|---|---|---|
documentId | uuid | |
bookmarkId | uuid | |
status | processing |
Errors for this route:
402: PLAN_REQUIRED: the plan has no REST API (Free)429: RATE_LIMITED: per-key hourly or per-user daily limit (Retry-After)
Usage
Your API usage and limits
GET /usage
Scope: any scope.
Requests today, plan limits and AI budget used this month. Any scope.
curl "https://api.readsira.com/rest/v2/usage" \
-H "Authorization: Bearer $READSIRA_KEY"
Response 200, in data:
| Field | Type | Notes |
|---|---|---|
plan | free, pro, research | |
requestsToday | number | REST requests today (UTC) |
requestsPerDay | number or null | |
mcpCallsToday | number | MCP tool calls today (UTC, weighted) |
mcpCallsPerDay | number or null | |
requestsPerHour | number or null | This key's hourly limit in force |
askAnswersPerMonth | number or null | |
aiBudget | object[] | |
aiBudget[].group | string | |
aiBudget[].percentUsed | number | Share of this month used, 0-100 (rounded) |
Errors for this route:
402: PLAN_REQUIRED: the plan has no REST API (Free)429: RATE_LIMITED: per-key hourly or per-user daily limit (Retry-After)