API Documentation

Manage your documents, leads, and analytics programmatically. All endpoints require an API key.

Contents Authentication Rate Limits Errors List Documents Get Document Get Document Stats Get Document Leads Upload Document Update Document Delete Document Webhooks

Authentication

Generate an API key from your dashboard settings. Include it as a Bearer token in all requests:

curl https://trackpdf.co/api/v1/documents \
  -H "Authorization: Bearer bd_live_your_key_here"

API keys are shown once on generation. Store them securely. You can revoke and regenerate keys from your dashboard.

Rate Limits

100 requests per minute per API key. Every response includes rate limit headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 97
X-RateLimit-Reset: 1680000060

When rate limited, you'll receive a 429 response. Wait until the reset timestamp before retrying.

Errors

All errors follow a standard format:

{
  "error": {
    "code": "not_found",
    "message": "Document not found"
  }
}
StatusCodeDescription
400bad_requestInvalid parameters or file type
401unauthorizedMissing or invalid API key
403volume_exceededVolume limit exceeded
404not_foundResource not found
429rate_limitedToo many requests
500internal_errorServer error

List Documents

GET /api/v1/documents

Returns all documents for the authenticated user.

ParameterDefaultDescription
page1Page number
per_page20Results per page (max 100)
curl https://trackpdf.co/api/v1/documents \
  -H "Authorization: Bearer bd_live_your_key_here"
{
  "documents": [
    {
      "id": "uuid",
      "title": "Series A Deck",
      "short_code": "ab12-cd34",
      "url": "https://trackpdf.co/ab12/cd34",
      "page_count": 12,
      "views": 47,
      "unique_viewers": 12,
      "leads_count": 5
    }
  ],
  "pagination": { "page": 1, "per_page": 20, "total": 3, "total_pages": 1 }
}

Get Document

GET /api/v1/documents/{short_code}

Get document details. Use - separator in the short code (e.g., ab12-cd34).

curl https://trackpdf.co/api/v1/documents/ab12-cd34 \
  -H "Authorization: Bearer bd_live_your_key_here"
{
  "id": "uuid",
  "title": "Series A Deck",
  "short_code": "ab12-cd34",
  "url": "https://trackpdf.co/ab12/cd34",
  "page_count": 12,
  "settings": {
    "email_gate": true,
    "gate_type": "preview",
    "gate_page": 3,
    "allow_download": true
  }
}

Get Document Stats

GET /api/v1/documents/{short_code}/stats

Get analytics for a document.

curl https://trackpdf.co/api/v1/documents/ab12-cd34/stats \
  -H "Authorization: Bearer bd_live_your_key_here"
{
  "views": 47,
  "unique_viewers": 12,
  "total_time_seconds": 3420,
  "avg_time_seconds": 285.0,
  "avg_completion": 0.72,
  "downloads": 3,
  "page_analytics": [
    { "page": 1, "views": 47, "avg_time_seconds": 12.0 }
  ],
  "engagement_breakdown": { "label": "Read", "type": "warm" }
}

Get Document Leads

GET /api/v1/documents/{short_code}/leads

Get leads captured for a document.

curl https://trackpdf.co/api/v1/documents/ab12-cd34/leads \
  -H "Authorization: Bearer bd_live_your_key_here"
{
  "leads": [
    {
      "email": "jane@acme.com",
      "name": "Jane Smith",
      "company_domain": "acme.com",
      "captured_at": "2026-04-03T09:15:00Z",
      "engagement_label": "High Interest"
    }
  ],
  "pagination": { "page": 1, "per_page": 20, "total": 5, "total_pages": 1 }
}

Upload Document

POST /api/v1/documents

Upload a PDF document.

curl https://trackpdf.co/api/v1/documents \
  -H "Authorization: Bearer bd_live_your_key_here" \
  -F "file=@deck.pdf" \
  -F "title=Series A Deck"
{
  "id": "uuid",
  "title": "Series A Deck",
  "short_code": "ab12-cd34",
  "url": "https://trackpdf.co/ab12/cd34",
  "page_count": 12,
  "created_at": "2026-04-07T14:30:00Z"
}

Update Document

PATCH /api/v1/documents/{short_code}

Update document settings. All fields are optional.

curl -X PATCH https://trackpdf.co/api/v1/documents/ab12-cd34 \
  -H "Authorization: Bearer bd_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"title": "Updated Title", "allow_download": false}'

Delete Document

DELETE /api/v1/documents/{short_code}

Soft delete a document. Returns 204 No Content.

curl -X DELETE https://trackpdf.co/api/v1/documents/ab12-cd34 \
  -H "Authorization: Bearer bd_live_your_key_here"

Webhooks

Configure webhooks from your dashboard settings. BitDoc sends POST requests to your URL when events occur.

Events

EventTrigger
document.viewedSomeone views a document (debounced per viewer, 30min)
lead.capturedSomeone enters their email through the gate
document.uploadedA document is uploaded
document.downloadedSomeone downloads a document

Payload Format

Every webhook POST uses the same envelope:

{
  "id": "evt_a1b2c3d4e5f6",
  "event": "lead.captured",
  "created_at": "2026-04-07T14:33:00Z",
  "data": {
    "document": {
      "id": "uuid",
      "title": "Series A Deck",
      "short_code": "ab12/cd34",
      "url": "https://trackpdf.co/ab12/cd34"
    },
    "lead": {
      "email": "jane@acme.com",
      "name": "Jane Smith",
      "company_domain": "acme.com"
    }
  }
}

Signature Verification

Every webhook includes a signature header for verification:

X-BitDoc-Signature: t=1680000000,v1=5257a869e7ecebeda32affa62cdca3fa...

Python verification:

import hmac, hashlib, time

def verify_webhook(body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    timestamp = parts["t"]
    signature = parts["v1"]

    # Check timestamp freshness (reject > 5 min old)
    if abs(time.time() - int(timestamp)) > 300:
        return False

    # Reconstruct and verify
    signed_payload = f"{timestamp}.".encode() + body
    expected = hmac.new(
        secret.encode(), signed_payload, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Node.js verification:

const crypto = require('crypto');

function verifyWebhook(body, header, secret) {
  const parts = Object.fromEntries(
    header.split(',').map(p => p.split('=', 2))
  );
  const timestamp = parts.t;
  const signature = parts.v1;

  // Check freshness
  if (Math.abs(Date.now()/1000 - parseInt(timestamp)) > 300) return false;

  // Verify
  const payload = `${timestamp}.${body}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  if (expected.length !== signature.length) return false;
  return crypto.timingSafeEqual(
    Buffer.from(expected), Buffer.from(signature)
  );
}

Retry Behavior

Questions? Contact support@trackpdf.co