Manage your documents, leads, and analytics programmatically. All endpoints require an API key.
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.
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.
All errors follow a standard format:
{
"error": {
"code": "not_found",
"message": "Document not found"
}
}
| Status | Code | Description |
|---|---|---|
| 400 | bad_request | Invalid parameters or file type |
| 401 | unauthorized | Missing or invalid API key |
| 403 | volume_exceeded | Volume limit exceeded |
| 404 | not_found | Resource not found |
| 429 | rate_limited | Too many requests |
| 500 | internal_error | Server error |
GET /api/v1/documents
Returns all documents for the authenticated user.
| Parameter | Default | Description |
|---|---|---|
page | 1 | Page number |
per_page | 20 | Results 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 /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 /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 /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 }
}
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"
}
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 /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"
Configure webhooks from your dashboard settings. BitDoc sends POST requests to your URL when events occur.
| Event | Trigger |
|---|---|
document.viewed | Someone views a document (debounced per viewer, 30min) |
lead.captured | Someone enters their email through the gate |
document.uploaded | A document is uploaded |
document.downloaded | Someone downloads a document |
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"
}
}
}
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)
);
}
Questions? Contact support@trackpdf.co