API Documentation
Integrate DeckExtract into your applications to extract documents from DocSend and Papermark programmatically.
Get your free API key
Sign up free (email code) to get a dk_… key — 5 extractions/month, or go unlimited with Pro.
Overview
One POST to /api/v2/extract turns a DocSend or Papermark link into JSON with a short-lived download link for the finished PDF or PPTX — it handles auth, email gates, and conversion for you. Responses use real HTTP status codes, so you branch on response.status directly.
Every request needs an API key (free plan: 5 extractions/month). "analyze": true adds a structured deck breakdown and requires Pro, which also lifts the rate limits.
Base URL:https://deckextract.com/api/v2/extract
Authentication
Send your dk_… key as a Bearer token on every request (the examples below fill it in when you're signed in with one):
Authorization: Bearer dk_your_api_key- Free — 5 extractions/month, per-IP rate limit.
- Pro — unlimited extractions (30/hour per key), unlocks
analyze, same key works on the MCP server. 401 invalid_keyon a bad key,429 rate_limitedwhen you hit the cap.
Endpoint
POST /api/v2/extract POST a JSON body with the document URL and optional parameters; the reply is always JSON. (The legacy POST /api is removed — see the note below.)
Request Format
{
"url": "https://docsend.com/view/...",
"format": "pdf",
"analyze": false,
"email": "optional@example.com",
"password": "optional-password",
"sessionId": "optional-session-id"
}Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The DocSend or Papermark document URL to extract |
format | string | No | Output format: "pdf" or "pptx". Defaults to "pdf". |
analyze | boolean | No | Pro only — also returns a structured deck analysis in the same response. Defaults to false. |
email | string | No | Email address (only required if the DocSend link requires email whitelist verification) |
password | string | No | Password for password-protected documents |
sessionId | string | No | Session ID returned by a 422 auth gate — pass it back when retrying with the missing email/password. |
Response Types
Every response is JSON with a real HTTP status code. The 200 success body carries a short-lived download.url; auth gates come back as 422 with a sessionId to retry; everything else is an error with its own status code.
1. Success (200)
download.url is a capability link to the finished file, valid ~1 hour. Single docs return a PDF/PPTX; data rooms return a ZIP. With analyze: true (Pro) an analysis object is included (see Deck Analysis).
{
"success": true,
"download": {
"url": "https://deckextract.com/dl/<token>.pdf",
"filename": "<token>.pdf",
"contentType": "application/pdf",
"bytes": 554790,
"expiresAt": "2026-06-13T22:24:33Z"
},
"meta": { "platform": "docsend.com", "format": "pdf", "analyzed": false }
}2. Auth gates (422)
Not failures — the document needs one more step. Retry with the returned sessionId plus the missing field, branching on the status string:
requires_password— retry withpassword.requires_email_confirmation— you supplied your ownemail; check that inbox and retry with the verification URL. (Omitemailand the server handles this for you, usually returning 200 directly.)requires_email_review— that email isn't authorized; retry with a different one.link_disabled— terminal; ask the owner for a fresh link.
HTTP/1.1 422 Unprocessable Entity
{
"success": false,
"status": "requires_password",
"error": "This deck is passcode-protected. Retry with `password` and `sessionId`.",
"sessionId": "abc123..."
}3. Errors (4xx / 5xx)
Failures carry a code string and the matching HTTP status (see Error Codes):
HTTP/1.1 400 Bad Request
{
"success": false,
"code": "unsupported_domain",
"error": "Domain not supported. Only DocSend and Papermark URLs are allowed."
}Usage Examples
Extract a deck
Add your Authorization: Bearer dk_… header (filled in below when you're signed in with a key). For gated decks, add password, email, or "format": "pptx" to the body.
curl -X POST https://deckextract.com/api/v2/extract \
-H "Content-Type: application/json" \
-d '{
"url": "https://docsend.com/view/abc123"
}'Extract and download the file
The response is JSON, so grab download.url and fetch it:
# 1. Request the extraction — the JSON reply carries a short-lived link
RESPONSE=$(curl -s -X POST https://deckextract.com/api/v2/extract \
-H "Content-Type: application/json" \
-d '{ "url": "https://docsend.com/view/abc123" }')
# 2. Pull download.url out of the JSON and fetch the file
echo "$RESPONSE" | jq -r '.download.url' | xargs curl -L -o document.pdfJavaScript — handle the auth gates
A complete loop that retries the 422 gates with the returned sessionId:
async function extractDocument(url, { email, password, format = 'pdf' } = {}) {
let sessionId = null;
while (true) {
const response = await fetch('https://deckextract.com/api/v2/extract', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ url, format, email, password, sessionId }),
});
const data = await response.json();
// Success: HTTP 200 with a short-lived download link.
if (response.ok && data.success) {
const file = await fetch(data.download.url);
return await file.blob();
}
// 422 auth gates: resupply the missing field with the returned sessionId.
if (response.status === 422) {
sessionId = data.sessionId;
if (data.status === 'requires_password') {
password = prompt('Password required:');
continue;
}
if (data.status === 'requires_email_review') {
email = prompt('That email is not authorized — try another:');
continue;
}
if (data.status === 'requires_email_confirmation' && data.isUserSuppliedEmail) {
url = prompt(data.message + '\n\nPaste the verification URL:');
if (url) continue;
}
}
// Any other status (400/401/402/429/5xx) is terminal.
throw new Error(data.error || `Extraction failed (${response.status})`);
}
}Deck Analysis (Pro)
Add "analyze": true (Pro only) and the same response also carries a structured breakdown of the deck — company, team, round, metrics, market, product, competition, and keywords, each tagged with its source slides.
Request
curl -X POST https://deckextract.com/api/v2/extract \
-H "Content-Type: application/json" \
-H "Authorization: Bearer dk_your_api_key" \
-d '{
"url": "https://docsend.com/view/...",
"analyze": true
}'Response
{
"success": true,
"download": {
"url": "https://deckextract.com/dl/<token>.pdf",
"filename": "<token>.pdf",
"contentType": "application/pdf",
"bytes": 554790,
"expiresAt": "2026-06-13T22:24:33Z"
},
"analysis": {
"company": { "name": "Acme", "description": "...", "founded_year": 2024 },
"team": { "founders": [ { "name": "Jane Doe", "roles": ["founder","ceo"] } ] },
"round": { "stage": "seed", "amount": { "value": 5000000, "currency": "USD" } },
"metrics": { "arr": { "value": 1200000, "currency": "USD" }, "growth_rate": "22% MoM" },
"market": { "industry": "FinTech & InsurTech", "business_model": "..." },
"product": { "problem": "...", "solution": "..." },
"competition": { "competitors": [], "differentiation": "..." },
"funding": { "previous_rounds": [], "total_raised": { "value": 800000, "currency": "USD" } },
"keywords": ["payments", "b2b", "..."],
"meta": { "num_slides": 12, "language": "English" }
},
"meta": { "platform": "docsend.com", "format": "pdf", "analyzed": true, "pages": 12 }
} The analysis object sits alongside the usual download link. 402 pro_required is returned if you pass analyze: true without a Pro key. Analysis runs on single documents only; a data room returns the ZIP download with an analysisError note, and an analysis hiccup never loses the file — you still get the download link plus an analysisError string.
Error Codes
Failures and auth gates carry a machine-readable code (or status for 422 gates) alongside the HTTP status:
| HTTP | code / status | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_body, invalid_url, unsupported_domain | Missing/invalid body, malformed URL, or a non-DocSend/Papermark host | Send a valid url from a supported platform |
| 401 | invalid_key | The dk_… API key is invalid or expired | Check the key at /account |
| 402 | pro_required | analyze: true without a Pro key | Get Pro, or drop analyze |
| 422 | requires_password, requires_email_confirmation, requires_email_review | An auth step is needed before extraction | Retry with the missing field + the returned sessionId |
| 422 | link_disabled, extraction_failed | The link is disabled, or the deck couldn't be rendered | Ask for a fresh link, or retry |
| 429 | rate_limited | Too many requests (per-IP or per-key window) | Back off and retry; a Pro key lifts the per-IP cap |
| 502 | upstream_error | An unexpected error reaching the extraction backend | Retry the request |
| 503 | busy, analysis_unavailable | Server at capacity, or analysis not configured | Retry shortly; analysis may be temporarily off |
Legacy v1 — POST /api (removed)
The old POST /api endpoint no longer extracts — it returns HTTP 410 Gone with a migration notice pointing at /api/v2/extract. Switch any old callers over.
HTTP/1.1 410 Gone
{
"success": false,
"code": "deprecated",
"error": "This endpoint has been removed. Use POST /api/v2/extract instead.",
"successor": "/api/v2/extract",
"docs": "https://deckextract.com/api"
}Best Practices
- Branch on HTTP status:
200= success (readdownload.url),422= an auth gate to retry with the returnedsessionId, everything else is terminal. - Download promptly: the
download.urllink expires after about an hour. - Rate limits: back off on
429, or use a Pro key to lift the per-IP cap.
Support
For questions, issues, or feature requests, please visit our FAQ page or check out our blog for guides and tutorials.