Codia
العودة إلى جميع المقالات

NotebookLM-style PDF to Editable PPTX with Codia Open API

Engineering2026-05-29

NotebookLM-style slide exports are useful because each page is already a finished visual slide. They are also hard to edit because a PDF page is usually a flat page, not a PowerPoint object model. The Codia Open API pdf_to_ppt task is built for this specific case: a NotebookLM-style PDF where every page is a full-page image, scanned slide, or screenshot-like page, and the output needs to be an editable .pptx.

This post walks through a production-ready integration pattern:

  1. Upload a local or private NotebookLM-style PDF with /v1/open/uploads.
  2. Estimate credits before creating work.
  3. Create a pdf_to_ppt task through the unified Task API.
  4. Receive the terminal result through a webhook instead of tight polling.
  5. List task history, cancel active jobs, and download the generated PPTX.

The examples use Node.js because most web applications already handle file uploads through a server route. The same HTTP calls work from Go, Python, Ruby, Java, or any backend that can send multipart and JSON requests.

For the complete endpoint schema, request and response fields, and live OpenAPI specification, open the Codia API Reference alongside this guide.

What "NotebookLM-style PDF" means

The current PDF to PPT pipeline is optimized for image-only PDFs: each PDF page should be one full-page image. That includes NotebookLM-style exported source pages, screenshot-based slide decks, scanned reports, and other static slide visuals.

It is not intended for complex text-flow PDFs where paragraphs, vector drawings, tables, and inline objects need to be interpreted as a document layout. If your source is a normal text PDF, rasterize each page to a high-resolution PNG or JPEG first, then package those images into an image-only PDF. That gives the converter a clean page-per-slide input.

Good inputs:

  • NotebookLM-style PDF exports
  • Scanned or screenshot-based slide decks
  • Image-only PDFs where one page equals one slide
  • Static presentation pages that should become editable PPTX slides

Risky inputs:

  • Long-form text PDFs with flowing paragraphs
  • Vector-heavy reports with complex object stacks
  • Mixed PDFs where some pages are text documents and others are slides
  • Low-resolution scans where small text is unreadable

Architecture

Keep the Codia API key on your server. The browser uploads the PDF to your backend, your backend uploads it once to Codia, and every later call uses the returned opaque upload_id.

text
Browser -> your server: multipart PDF upload -> Codia /v1/open/uploads: multipart PDF upload <- upload_id -> Codia /v1/open/estimate: JSON with upload_id -> Codia /v1/open/tasks: JSON with upload_id and callback_url <- task_id Codia -> your webhook: terminal task event Browser -> your server: read task/result -> download ppt_url when succeeded

/v1/open/uploads does not return a public file URL. It returns an opaque upload_id scoped to the same user and API key, with a short retention window. That avoids turning the upload endpoint into free public file hosting.

Step 1: Upload the PDF and get an upload_id

For a local or private PDF, send multipart/form-data to /v1/open/uploads.

bash
curl 'https://api.codia.ai/v1/open/uploads' \ -H 'Authorization: Bearer {codia_api_key}' \ -F 'file=@./notebooklm-briefing.pdf'

Example response:

json
{ "code": 0, "message": "ok", "data": { "upload_id": "upl_550e8400-e29b-41d4-a716-446655440000", "filename": "notebooklm-briefing.pdf", "size": 1234567, "content_type": "application/pdf", "expires_at": 1764086400 } }

Use upload_id for Codia-managed uploads. Use pdf_url only when the PDF is hosted by your own storage and is reachable by Codia.

Step 2: Estimate credits before creating the task

pdf_to_ppt costs 13 credits per converted page. If you submit page_no, the estimate is deterministic from the selected page count. If you omit page_no, the service resolves the page count from the PDF.

bash
curl 'https://api.codia.ai/v1/open/estimate' \ -H 'Authorization: Bearer {codia_api_key}' \ -H 'Content-Type: application/json' \ --data '{ "operation": "pdf_to_ppt", "input": { "upload_id": "upl_550e8400-e29b-41d4-a716-446655440000", "page_no": [0, 1, 2] } }'

Example response:

json
{ "code": 0, "message": "ok", "data": { "operation": "pdf_to_ppt", "page_count": 3, "unit": "13 credits per page", "credits": 39, "available_credits": 120 } }

Page numbers are zero-based. Page 0 is the first PDF page.

Step 3: Create the pdf_to_ppt task

Create the task through the unified Task API. Use an Idempotency-Key if your server may retry after a timeout.

bash
curl 'https://api.codia.ai/v1/open/tasks' \ -H 'Authorization: Bearer {codia_api_key}' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: notebooklm-briefing-2026-05-29' \ --data '{ "operation": "pdf_to_ppt", "input": { "upload_id": "upl_550e8400-e29b-41d4-a716-446655440000", "page_no": [0, 1, 2], "title": "NotebookLM source briefing" }, "callback_url": "https://your-app.example.com/webhooks/codia-task" }'

Example response:

json
{ "code": 0, "message": "ok", "data": { "task_id": "550e8400-e29b-41d4-a716-446655440000", "operation": "pdf_to_ppt", "status": "pending", "created_at": 1780028799 } }

pending means the task is queued. processing means a worker has started it. Terminal states are succeeded, failed, and canceled.

Step 4: Receive the webhook

For production, prefer a webhook over a browser polling loop. Codia posts to callback_url when the task reaches a terminal state.

ts
import crypto from 'node:crypto'; import express from 'express'; const app = express(); app.post( '/webhooks/codia-task', express.raw({ type: 'application/json' }), (req, res) => { const signature = String(req.header('X-Codia-Signature') || ''); const expected = 'sha256=' + crypto .createHmac('sha256', process.env.CODIA_WEBHOOK_SECRET!) .update(req.body) .digest('hex'); const ok = signature.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); if (!ok) { res.sendStatus(401); return; } const event = JSON.parse(req.body.toString('utf8')); if (event.operation === 'pdf_to_ppt' && event.status === 'succeeded') { console.log('PPTX ready:', event.result?.ppt_url || event.ppt_url); } if (event.status === 'failed') { console.error('Conversion failed:', event.error_code, event.error); } res.sendStatus(200); }, );

Store the event in your database keyed by task_id. Your frontend can read that local row through your own API and update the task UI without exposing the Codia API key.

Step 5: Get status or list history

Webhook delivery should drive your main flow, but status endpoints are still useful for admin screens, recovery, and task history.

bash
curl 'https://api.codia.ai/v1/open/tasks/550e8400-e29b-41d4-a716-446655440000' \ -H 'Authorization: Bearer {codia_api_key}'

List active PDF to PPT tasks:

bash
curl 'https://api.codia.ai/v1/open/tasks?operation=pdf_to_ppt&status=pending,processing&limit=20' \ -H 'Authorization: Bearer {codia_api_key}'

List history with cursor pagination:

bash
curl 'https://api.codia.ai/v1/open/tasks?operation=pdf_to_ppt&limit=20&after=1780028799' \ -H 'Authorization: Bearer {codia_api_key}'

Task ownership is scoped to the API key that created the task. Query, list, cancel, and webhook reconciliation should use the same key.

Step 6: Cancel and handle partial refunds

Cancel a task that is still pending or processing:

bash
curl -X POST 'https://api.codia.ai/v1/open/tasks/550e8400-e29b-41d4-a716-446655440000/cancel' \ -H 'Authorization: Bearer {codia_api_key}'

For pdf_to_ppt, cancellation refunds only pages that have not finished conversion. If a 15-page NotebookLM-style PDF task has converted 10 pages, canceling refunds 65 credits and keeps 130 credits charged.

json
{ "code": 0, "message": "ok", "data": { "task_id": "550e8400-e29b-41d4-a716-446655440000", "operation": "pdf_to_ppt", "status": "canceled", "credits_reserved": 195, "credits_charged": 130, "credits_refunded": 65 } }

If the task is still pending, all reserved credits are refunded. A succeeded task cannot be canceled.

Step 7: Download the editable PPTX

When the task succeeds, read result.ppt_url and download the file promptly.

json
{ "code": 0, "message": "ok", "data": { "task_id": "550e8400-e29b-41d4-a716-446655440000", "operation": "pdf_to_ppt", "status": "succeeded", "progress": 100, "result": { "ppt_url": "https://static.codia.ai/pptx/notebooklm-source-briefing.pptx", "page_count": 3, "pages": [ { "index": 0, "source_page": 0, "editable": true, "status": "editable", "preview_url": "https://static.codia.ai/previews/page-0.png" } ] } } }

The PPTX is the deliverable your users care about. Store it in your own system if you need long-term access, attach it to the user's project, or hand it to PowerPoint, Keynote, or Google Slides workflows.

Full Node.js route

This minimal Express route accepts a browser upload and creates a Codia task. It keeps the Codia API key server-side and never sends it to the browser.

ts
import express from 'express'; import multer from 'multer'; import FormData from 'form-data'; import crypto from 'node:crypto'; const app = express(); const upload = multer({ storage: multer.memoryStorage() }); const CODIA_API_BASE = 'https://api.codia.ai'; app.post('/api/notebooklm-pdf-to-pptx', upload.single('file'), async (req, res) => { if (!req.file) { res.status(400).json({ error: 'file is required' }); return; } const form = new FormData(); form.append('file', req.file.buffer, { filename: req.file.originalname || 'notebooklm.pdf', contentType: req.file.mimetype || 'application/pdf', }); const uploadRsp = await fetch(`${CODIA_API_BASE}/v1/open/uploads`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.CODIA_API_KEY}`, ...form.getHeaders(), }, body: form as any, }).then((r) => r.json()); if (uploadRsp.code !== 0) { res.status(502).json(uploadRsp); return; } const uploadId = uploadRsp.data.upload_id; const idempotencyKey = crypto.randomUUID(); const taskRsp = await fetch(`${CODIA_API_BASE}/v1/open/tasks`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.CODIA_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey, }, body: JSON.stringify({ operation: 'pdf_to_ppt', input: { upload_id: uploadId, title: req.body.title || req.file.originalname?.replace(/\.pdf$/i, ''), }, callback_url: `${process.env.PUBLIC_APP_URL}/webhooks/codia-task`, }), }).then((r) => r.json()); if (taskRsp.code !== 0) { res.status(502).json(taskRsp); return; } res.status(202).json({ task_id: taskRsp.data.task_id, status: taskRsp.data.status, upload_id: uploadId, }); });

For real production code, add request size limits, MIME checks, user ownership checks, database persistence, and structured retry handling.

Operational checklist

  • Validate that the uploaded file is a PDF before forwarding it.
  • Keep CODIA_API_KEY and webhook secrets on the server.
  • Use /v1/open/estimate before task creation if your UI needs a cost preview.
  • Use Idempotency-Key when retrying task creation.
  • Prefer callback_url for terminal state updates.
  • Store task_id, status, ppt_url, and billing fields in your own database.
  • Use GET /v1/open/tasks with cursor pagination for history pages.
  • Explain clearly that NotebookLM-style image-only PDFs work best.

FAQ

Can I pass a URL instead of uploading?

Yes. If the PDF is hosted by your own storage, pass input.pdf_url when creating the task. Use input.upload_id for local or private PDFs uploaded through Codia.

Does /v1/open/uploads return a public file URL?

No. It returns an opaque upload_id, not a public URL. The task service resolves the upload internally and verifies ownership by user and API key.

Is the output really editable?

The goal is an editable PPTX with reconstructed text, layout, and visual elements. Some dense or low-quality pages may come back as image-only or partially editable. The task result includes per-page status so your UI can show which pages are editable.

How are credits charged?

pdf_to_ppt costs 13 credits per converted page. Failed tasks refund reserved credits. Canceled PDF to PPT tasks refund pages that have not been converted yet.

Should I poll?

Use a webhook for the main production path. Use GET /v1/open/tasks/{task_id} for recovery, admin views, and manual refresh.

#notebooklm#notebooklm-style-pdf#pdf-to-ppt#editable-pptx#codia-open-api#webhook