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.

Get your free API key

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_key on a bad key, 429 rate_limited when 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
urlstringYes The DocSend or Papermark document URL to extract
formatstringNo Output format: "pdf" or "pptx". Defaults to "pdf".
analyzebooleanNo Pro only — also returns a structured deck analysis in the same response. Defaults to false.
emailstringNo Email address (only required if the DocSend link requires email whitelist verification)
passwordstringNo Password for password-protected documents
sessionIdstringNo 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 with password.
  • requires_email_confirmation — you supplied your own email; check that inbox and retry with the verification URL. (Omit email and 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.pdf

JavaScript — 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
400invalid_body, invalid_url, unsupported_domain Missing/invalid body, malformed URL, or a non-DocSend/Papermark host Send a valid url from a supported platform
401invalid_key The dk_… API key is invalid or expired Check the key at /account
402pro_requiredanalyze: true without a Pro key Get Pro, or drop analyze
422requires_password, requires_email_confirmation, requires_email_review An auth step is needed before extraction Retry with the missing field + the returned sessionId
422link_disabled, extraction_failed The link is disabled, or the deck couldn't be rendered Ask for a fresh link, or retry
429rate_limited Too many requests (per-IP or per-key window) Back off and retry; a Pro key lifts the per-IP cap
502upstream_error An unexpected error reaching the extraction backend Retry the request
503busy, 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 (read download.url), 422 = an auth gate to retry with the returned sessionId, everything else is terminal.
  • Download promptly: the download.url link 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.