Twinbook for Developers
Twinbook turns what you are learning from (papers, textbooks, slides, articles, videos, your own notes) into study material: structured notes, concept maps, quizzes, flashcards and a tutor that knows the document.
There are two ways to bring content into Twinbook programmatically:
| What it is | Status | |
|---|---|---|
| Twinbook MCP Server | A remote Model Context Protocol server that lets an AI assistant (ChatGPT today) save documents, links and notes straight into a learner's Twinbook spaces. | Live at https://mcp.twinbook.ai/mcp |
| Notes API | The REST API behind it: prepare a document, commit the import, follow its progress. | Private preview. Available today through OAuth (it is what the MCP server calls). Developer API keys, webhooks and read-back of generated materials are planned; see Roadmap. |
Both share one contract, one set of limits and one vocabulary of statuses and errors. Everything an integration does happens as the learner, inside spaces the learner created. An integration can never read or write another account, and it can never bypass the learner's plan limits.
1. Twinbook MCP Server
1.1 What it does
Once a learner connects Twinbook to ChatGPT, they can say things like:
- "Save this paper to my Biology space."
- "Push this link into Twinbook as a problem set."
- "Add my notes from this conversation to a new space called Organic Chemistry."
- "What happened to the book I imported?"
ChatGPT checks the learner's remaining allowance, asks which space to use, previews what will be imported, confirms once, and submits. Twinbook accepts within seconds and generates the study material in the background, exactly as an upload inside the app does.
1.2 Connecting (ChatGPT)
- In ChatGPT, add the Twinbook plugin (from the plugin directory once listed, or via
Settings → Connectors → Add custom MCP server with the URL
https://mcp.twinbook.ai/mcp). - ChatGPT opens the Twinbook sign-in, the same sign-in as the web app at
app.twinbook.ai(email/password, Google, Apple). If you are already signed in to Twinbook in that browser, there is no form. - If you have not yet accepted Twinbook's AI data-sharing terms, the app's consent screen appears first.
- Twinbook asks you to allow ChatGPT the permissions below. Done.
Disconnect at any time from Twinbook → Settings → Account → ChatGPT connection → Disconnect. All tokens issued to that connection stop working within a minute.
1.3 Authentication
The MCP server is an OAuth 2.1 protected resource with its own authorization server at the same origin.
| Protocol | OAuth 2.1: authorization code + PKCE (S256), audience-bound access tokens (RFC 8707 resource) |
| Issuer | https://mcp.twinbook.ai |
| Authorization server metadata | https://mcp.twinbook.ai/.well-known/oauth-authorization-server and /.well-known/openid-configuration |
| Protected resource metadata | https://mcp.twinbook.ai/.well-known/oauth-protected-resource/mcp |
| Client registration | Client ID Metadata Documents (CIMD) or Dynamic Client Registration (POST /reg) |
| Access tokens | JWT, 15 minutes; refresh tokens rotate |
| Sign-in | Delegated to the Twinbook web app. The MCP server hosts no login form and never sees a password |
| Revocation | Disconnect in Twinbook settings; RFC 7009 /token/revocation also supported |
Scopes
| Scope | Allows |
|---|---|
twinbook.profile | Read the connected learner's id, name and email; read plan allowance |
twinbook.spaces.read | List the learner's study spaces |
twinbook.spaces.write | Create a new private study space |
twinbook.import | Prepare, commit, follow, retry and cancel imports into spaces the learner created |
Other MCP clients. The server speaks standard Streamable HTTP + OAuth 2.1 and any compliant client can implement the flow. Client registration is currently allow-listed for ChatGPT's redirect URIs; if you are building another client, contact us to be added.
1.4 Tools
Nine tools. All are scoped to the connected learner; none can touch a space the learner did not create.
| Tool | Purpose | Reads/writes |
|---|---|---|
get_profile | Who is connected (stable opaque id, name, email) | read |
get_upload_context | Plan, remaining monthly allowance (pages, uploads, AI credits), per-import limits, offered content kinds and note styles, whether AI terms are accepted | read |
list_spaces | The learner's spaces, most recently opened first, with canUpload (only spaces the learner created accept imports) | read |
create_space | A new private space (idempotent per idempotencyKey) | write |
prepare_import | Stage and inspect one source (text, a link, or an attached file) for a destination space; returns a bound plan with real page count, how it will be processed, estimated usage, warnings. Imports nothing yet | write (staging only) |
commit_import | Submit a plan. Returns accepted within seconds; material generates in the background. Idempotent per idempotencyKey | write |
get_import_status | Outcome, stage, per-chapter parts and counts, whether retry/cancel are possible, link to open | read |
retry_import | Resume a paused book, or re-submit an upload that failed for a transient reason | write |
cancel_import | Stop a book import; chapters already saved stay | write |
1.5 The import flow
get_upload_context ─▶ list_spaces ─▶ prepare_import ─▶ (confirm with the learner) ─▶ commit_import ─▶ get_import_status
prepare_import is advisory: it stages a copy of the source for 24 hours, counts its pages, decides how it
will be processed, and reports what the import will consume. It reserves nothing. commit_import is the
only call that consumes allowance, and it is idempotent: calling it again with the same idempotencyKey
returns the same import and never charges twice (this is also how a lost response is recovered).
1.6 Sources and how they are processed
| Source | Sent as | Notes |
|---|---|---|
| PDF, PNG, JPEG, WebP | attached file | up to 60 MB |
| Word (.docx), PowerPoint (.pptx) | attached file | converted to PDF; the original stays downloadable in Twinbook |
| Web page | link | captured as PDF (up to 60 pages) |
| PDF / Word / PowerPoint link | link | taken as the document |
| YouTube video | link | processed from its captions; not counted against pages |
| Text, or notes the assistant wrote | text | up to 50,000 characters; labelled original or assistant_generated |
| Document length | Processing (processing) | What the learner gets |
|---|---|---|
| ≤ 20 pages, images, text, video | quick | one chapter, notes in a few minutes |
| 21 – 60 pages | chapters | Twinbook detects the chapters itself (research papers, long articles) |
| 61 – 1500 pages | book | Twinbook maps the chapters first (usually 5–15 minutes), then imports them one by one; each chapter counts as one upload |
any, saveOnly: true | save_only | stored without AI processing (no notes, no credits) |
Options: contentKind (auto default, reading, problem-solving, lecture), isProblemSolving,
noteStyle (default, original, simple-to-complex, high-level, in-depth, organize; soap-note in
healthcare spaces), free-text instructions (≤ 2000 chars), saveOnly. Books are always imported in auto
mode with default notes.
1.7 Limits
Monthly allowances come from the learner's plan (pages, uploads, AI credits; see the pricing) and are enforced exactly as in the app. Per-import limits:
| Limit | Value |
|---|---|
Pages processed as one chapter (quick) | 20 |
| Pages processed as several chapters | 60 |
| Pages imported as a book | 1,500 |
| File size downloaded from the assistant or a link | 60 MB |
| Text | 50,000 characters |
| Web page captured | 60 pages |
| Link imports | 10 per hour per learner |
| Staged source lifetime (uncommitted plan) | 24 hours |
1.8 Privacy and security
- The integration acts only as the connected learner and only inside spaces that learner created.
- The MCP server stores no documents: sources are staged by Twinbook's backend for the import and processed under the same AI data-sharing terms the learner accepted in the app.
- Sign-in happens in the Twinbook web app; the MCP server never handles a password and no token ever travels in a URL.
- Content inside a document or web page is never treated as an instruction: it cannot change the destination space or the options.
- Disconnecting revokes every token of the connection.
More on how Twinbook protects learner data: Trust & Security.
2. Notes API (REST) · Private preview
The Notes API is the contract the MCP server uses. It is a JSON API under
https://app.twinbook.ai/api/integrations, authenticated with an OAuth 2.1 bearer token issued by the
Twinbook authorization server (§1.3). Every call is made as a learner, for that
learner's own spaces.
Private preview. Access today is through the OAuth flow of the Twinbook authorization server (the ChatGPT plugin uses this). Developer API keys, webhooks for import completion, and endpoints to read back generated materials are planned. Request preview access.
2.1 Conventions
- Base URL
https://app.twinbook.ai/api/integrations;Authorization: Bearer <access token>; JSON bodies. - Ids are UUIDs. Times are ISO-8601 UTC.
- Errors are
{ "error": "<code>", "message": "<plain words>", ...details }with a meaningful HTTP status. - Writes that may be retried carry an
idempotencyKey(8–200 characters, chosen by the caller).
2.2 Endpoints
| Method | Path | Scope | Purpose |
|---|---|---|---|
| GET | /upload-context | twinbook.profile | Plan, remaining allowance, per-import limits, offered options, consent state |
| GET | /spaces?query= | twinbook.spaces.read | Spaces the learner is in, most recently opened first, with canUpload |
| POST | /spaces | twinbook.spaces.write | Create a private space (title, description?, learningMode?, tags?, idempotencyKey?) |
| POST | /imports/prepare | twinbook.import | Stage and inspect one source; returns a plan |
| POST | /imports/commit | twinbook.import | Submit a plan (planId, idempotencyKey) |
| GET | /imports/{importId} | twinbook.import | Status |
| POST | /imports/{importId}/retry | twinbook.import | Resume a paused book / re-submit a transient failure |
| POST | /imports/{importId}/cancel | twinbook.import | Stop a book import |
2.3 Prepare an import
POST /api/integrations/imports/prepare
Authorization: Bearer <token>
Content-Type: application/json
{
"destination": { "spaceId": "11111111-2222-4333-8444-555555555555" },
"source": { "url": { "href": "https://arxiv.org/pdf/2302.13971" } },
"options": { "contentKind": "auto", "noteStyle": "default", "saveOnly": false }
}
source is exactly one of:
{ "text": { "title": "…", "body": "…", "authorship": "original" | "assistant_generated" } }
{ "url": { "href": "https://…" } } // web page, PDF/Office link, YouTube
{ "file": { "downloadUrl": "https://…", "fileId": "…", "mimeType": "…", "fileName": "…" } }
Response 201 Created:
{
"planId": "c0a8…",
"expiresAt": "2026-10-08T03:12:00.000Z",
"destination": { "spaceId": "8378…", "title": "Machine Learning" },
"source": { "kind": "pdf", "title": "LLaMA: Open and Efficient Foundation Language Models", "pages": 27,
"bytes": 710000, "authorship": "original", "sourceUrl": "https://arxiv.org/pdf/2302.13971",
"originalFormat": "url" },
"processing": "chapters",
"options": { "contentKind": "auto", "isProblemSolving": false, "noteStyle": "default", "instructions": "", "saveOnly": false },
"parts": [ { "index": 0, "title": "LLaMA: …", "fromPage": 1, "toPage": 27, "boundaryKind": "single" } ],
"estimatedUsage": { "pages": 27, "uploads": 1 },
"warnings": [ { "code": "multi_chapter", "message": "27 pages: longer than a quick session, so Twinbook will process it as several chapters." },
{ "code": "url_captured", "message": "The page was captured as 27 PDF page(s) (the link pointed at a PDF)." } ],
"blockedBy": null
}
blockedBy (when present) names the monthly quota or consent that would stop the commit, with resetsAt.
For a book, estimatedUsage.uploads is an estimate of the chapter count (uploadsEstimated: true).
2.4 Commit and follow
POST /api/integrations/imports/commit
{ "planId": "c0a8…", "idempotencyKey": "3f6d9a2e-…" }
Response 200 OK, within seconds:
{
"importId": "9b1e…", "planId": "c0a8…", "status": "accepted", "mode": "direct", "processing": "chapters",
"outcome": "processing", "stage": "processing", "dispatched": true,
"spaceId": "8378…", "blockId": "b73b…", "title": "LLaMA: …",
"openUrl": "https://app.twinbook.ai/?spaceId=8378…&blockId=b73b…",
"error": null, "canRetry": false, "canCancel": false,
"counts": { "total": 1, "active": 1, "ready": 0, "saved": 0, "failed": 0, "cancelled": 0 },
"parts": [ { "index": 0, "title": "LLaMA: …", "status": "processing", "blockId": "b73b…", "retryable": false } ],
"acceptedAt": "2026-10-07T03:12:04.000Z", "updatedAt": "2026-10-07T03:12:04.000Z"
}
GET /imports/{importId} returns the same shape as processing advances. For a chapters import the
parts become the chapters Twinbook registered; for a book, stage moves mapping_chapters →
importing_chapters → done and parts list the chapters from the book's map.
Status vocabulary
outcome | Meaning |
|---|---|
processing | accepted; material is being generated |
materials_ready | notes, quizzes and flashcards are ready |
saved_without_ai | stored, AI skipped (credits exhausted or saveOnly) |
partially_completed | some chapters ready, some failed or cancelled |
failed | nothing was produced; see error |
cancelled | stopped before anything was saved |
Part status values: queued · processing · materials_ready · saved_without_ai · failed · cancelled.
2.5 Errors
| Code | HTTP | When |
|---|---|---|
space_not_found_or_not_owned | 404 | the space does not exist or was not created by the learner |
invalid_option | 400 | contradictory or unknown options (e.g. isProblemSolving with contentKind: reading; options on a book) |
mode_not_allowed | 403 | soap-note outside a healthcare space |
source_unreadable | 400 | not a PDF/image/Word/PowerPoint, damaged, encrypted, or the file reference was incomplete |
source_too_large | 413 | over the size or text limits |
source_expired | 400 / 410 | the file link expired, or the plan is older than 24 h |
import_page_limit_exceeded | 413 | longer than the book limit (or books disabled) |
url_page_limit_exceeded | 413 | a web page rendering to more than 60 pages |
url_blocked | 400 | private, local or non-https address |
url_rate_limited | 429 | more than 10 link imports in an hour |
share_unavailable | 400 | a ChatGPT/Claude share link; send the conversation as text instead |
transcript_unavailable | 400 | a video without captions |
transcript_check_failed, converter_unavailable | 503 | temporary; retry later |
monthly_page_quota_exceeded, upload_quota_exceeded, storage_exceeded | 402 | plan allowance used up (resetsAt) |
ai_consent_required | 403 | the learner has not accepted the AI terms (import with saveOnly, or accept in the app) |
plan_not_found, plan_stale | 404 / 409 | unknown plan, or the destination changed since it was prepared |
idempotency_conflict | 409 | the same idempotencyKey was used for a different plan |
import_not_found | 404 | unknown import (or not this learner's) |
import_not_retryable, import_not_cancellable, import_busy | 409 | nothing to retry/cancel; or the book is being advanced right now; try again in a moment |
insufficient_scope | 403 | the token lacks the scope the route needs |
2.6 Roadmap
| Status | |
|---|---|
| OAuth 2.1 access as a learner (used by the ChatGPT plugin) | Live |
| Prepare / commit / status / retry / cancel | Live (private preview) |
| Developer API keys (server-to-server, per developer account) | Planned |
Webhooks on materials_ready / failed | Planned; today, poll GET /imports/{importId} |
| Read back generated materials (notes, quiz, flashcards) | Planned |
| Client registration for MCP clients other than ChatGPT | On request |
Questions, preview access and feedback: contact us. Last updated 2026-10-07.