Craftkitdocs

Engagement & analytics

Read aggregate engagement counts and recent activity for a render, and record partner-side events.

Read aggregate engagement counts and recent activity for a render, and record partner-side events from your own viewer UI. Engagement tracks who viewed, downloaded, printed, or was emailed a rendered document.

GET  /v1/renders/:id/engagement
POST /v1/renders/:id/events

:id is the render id (UUIDv7) within the authenticated project.

Quick Start

curl

# Read the engagement summary
curl https://api.craftkit.dev/v1/renders/0193c2c3/engagement \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY"

# Record a partner-side event
curl -X POST https://api.craftkit.dev/v1/renders/0193c2c3/events \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "eventType": "downloaded", "metadata": { "source": "dashboard" } }'

Node.js

const headers = { Authorization: `Bearer ${process.env.CRAFTKIT_API_KEY}` };

const summary = await (
  await fetch('https://api.craftkit.dev/v1/renders/0193c2c3/engagement', { headers })
).json();
console.log(summary.counts.viewed, summary.linkOpens, summary.total);

const res = await fetch('https://api.craftkit.dev/v1/renders/0193c2c3/events', {
  method: 'POST',
  headers: { ...headers, 'Content-Type': 'application/json' },
  body: JSON.stringify({ eventType: 'printed', metadata: { source: 'dashboard' } }),
});
const { recorded } = await res.json();

Python

import os, requests

headers = {"Authorization": f"Bearer {os.environ['CRAFTKIT_API_KEY']}"}

summary = requests.get(
    "https://api.craftkit.dev/v1/renders/0193c2c3/engagement",
    headers=headers,
).json()

res = requests.post(
    "https://api.craftkit.dev/v1/renders/0193c2c3/events",
    headers=headers,
    json={"eventType": "viewed", "metadata": {"source": "dashboard"}},
)
recorded = res.json()["recorded"]

Path parameters

Field Type Description Default
id string Render id (UUIDv7). Must belong to the API key's project.

GET /v1/renders/:id/engagement

Returns aggregate counts across all event types, a linkOpens tally, the grand total, and the most recent events (newest first, capped at 25).

Response

{
  "counts": {
    "viewed": 12,
    "downloaded": 3,
    "printed": 1,
    "email_opened": 4,
    "email_sent": 2,
    "share_created": 1,
    "share_revoked": 0
  },
  "linkOpens": 8,
  "total": 23,
  "recent": [
    {
      "id": "0193c2d0-...",
      "eventType": "viewed",
      "actorKind": "recipient",
      "shareId": "0193c2cf-...",
      "sourceIp": "203.0.113.7",
      "userAgent": "Mozilla/5.0 ...",
      "metadata": null,
      "createdAt": "2026-05-03T10:20:00.000Z"
    }
  ]
}
Field Type Description
counts object Per-type event counts. Always includes all seven keys, zero-filled.
counts.viewed integer Document opens.
counts.downloaded integer PDF downloads.
counts.printed integer Print actions.
counts.email_opened integer Tracking-pixel opens on a sent email.
counts.email_sent integer Emails dispatched (partner audit).
counts.share_created integer Share links minted (partner audit).
counts.share_revoked integer Share links revoked (partner audit).
linkOpens integer Subset of viewed events that originated from a shared link (shareId is non-null). Distinguishes link traffic from in-dashboard views.
total integer Sum of all counts.
recent array Up to 25 most recent events, newest first.
recent[].id string Event id.
recent[].eventType string One of viewed, downloaded, printed, email_opened, email_sent, share_created, share_revoked.
recent[].actorKind string Who triggered it: recipient (the share recipient), partner (you, via the events endpoint or dashboard), or system (server-side automation).
recent[].shareId string | null The originating share link, when the event came through one.
recent[].sourceIp string | null Best-effort client IP (x-forwarded-for first hop, else x-real-ip).
recent[].userAgent string | null Client user-agent string.
recent[].metadata object | null Arbitrary key/value bag attached when the event was recorded.
recent[].createdAt string ISO-8601 timestamp.

POST /v1/renders/:id/events

Records a partner-side engagement event from your own viewer UI. Every event written here is stamped actorKind: "partner" — you cannot record recipient-side events through this route. Recipient-side viewed/downloaded/printed events are written server-side by the public share page.

Request body

{
  "eventType": "downloaded",
  "metadata": { "source": "dashboard", "userId": "u_123" }
}
Field Type Description Default
eventType string Required. One of viewed, downloaded, printed. Other engagement types (email_*, share_*) are recorded by the system, not this route.
metadata object Optional free-form key/value bag (string keys, any JSON values) stored verbatim with the event.

Dedupe. viewed, downloaded, and printed are deduped within a 5-minute window keyed on (shareId, eventType, sourceIp). A duplicate inside that window returns { "recorded": false } and is not written.

Response

{ "recorded": true }
Field Type Description
recorded boolean true if the event was inserted, false if it was deduped within the 5-minute window.

Errors

HTTP Code Meaning Fix
400 invalid_json POST body wasn't valid JSON Check Content-Type and JSON.stringify
400 invalid_request POST body didn't match { eventType, metadata? } Inspect issues.fieldErrors; eventType must be viewed/downloaded/printed
401 unauthorized Missing, invalid, or revoked API key Send a valid Authorization: Bearer key
403 forbidden Key's project no longer exists Use a key from an active project
404 not_found No render with that id in this key's project Check the id and the API key's project scope

See Errors for the envelope shape and full code list.