Craftkitdocs

Shares & delivery

Share a succeeded render: create/list/revoke durable revokable share links (channel link|email, recipientEmail/message/expiresAt, shareUrl/shareToken), and email the document via Resend returning emailMessageId/sentAt. Covers 409 not_ready and 503 email_not_configured.

Share a succeeded render with recipients — mint durable revokable share links, list and revoke them, or send the document straight to an inbox via Resend. Shares are scoped to the render's project; the render must have status succeeded before any share or email can be created.

POST   /v1/renders/:id/shares
GET    /v1/renders/:id/shares
DELETE /v1/renders/:id/shares/:shareId
POST   /v1/renders/:id/email

:id is a render id (UUIDv7) within the authenticated project. :shareId is a share id returned by create or list.

Create a share

POST /v1/renders/:id/shares

Mints a guest-facing share link. The render must have succeeded (409 not_ready otherwise). For channel: "email" the API only records the share row — it does not send mail; use POST /v1/renders/:id/email to actually deliver.

Quick Start

curl

curl -X POST https://api.craftkit.dev/v1/renders/$RENDER_ID/shares \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "link",
    "message": "Here is your signed agreement.",
    "expiresAt": "2026-12-31T23:59:59Z"
  }'

Node.js

const res = await fetch(`https://api.craftkit.dev/v1/renders/${renderId}/shares`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CRAFTKIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    channel: 'link',
    message: 'Here is your signed agreement.',
    expiresAt: '2026-12-31T23:59:59Z',
  }),
});
const { shareUrl, shareToken } = await res.json();

Python

import os, requests

res = requests.post(
    f"https://api.craftkit.dev/v1/renders/{render_id}/shares",
    headers={"Authorization": f"Bearer {os.environ['CRAFTKIT_API_KEY']}"},
    json={
        "channel": "link",
        "message": "Here is your signed agreement.",
        "expiresAt": "2026-12-31T23:59:59Z",
    },
)
share = res.json()

Path parameters

Field Type Description Default
id string Render id (UUIDv7) within the authenticated project. Required. —

Request body

{
  "channel": "link",
  "recipientEmail": "client@acme.com",
  "message": "Here is your signed agreement.",
  "expiresAt": "2026-12-31T23:59:59Z"
}
Field Type Description Default
channel string link or email. link is a plain copy-and-paste URL; email tags the share as email-destined but does not send — it requires recipientEmail and you must call POST /v1/renders/:id/email to deliver. link
recipientEmail string Recipient email (must be a valid address). Required when channel is email, otherwise optional metadata. —
message string Optional note shown to the recipient. Max 2000 chars. —
expiresAt string ISO-8601 timestamp. After this instant the share resolves to not-found on the public side. Omit for no auto-expiry. No expiry

Response — 201 Created

{
  "id": "0193c2c3-...",
  "shareToken": "cks_8sd9...",
  "shareUrl": "https://www.craftkit.dev/share/cks_8sd9...",
  "channel": "link",
  "recipientEmail": null,
  "message": "Here is your signed agreement.",
  "expiresAt": "2026-12-31T23:59:59.000Z",
  "revokedAt": null,
  "createdAt": "2026-06-05T10:05:00.000Z"
}
Field Type Description
id string Share id. Pass to DELETE to revoke.
shareToken string Opaque token (prefixed cks_) embedded in shareUrl. Stored hashed server-side.
shareUrl string Public link: {shareBase}/share/{shareToken}. The base resolves to the partner's custom domain, then SHARE_BASE_URL, then APP_URL, then the request origin.
channel string link or email.
recipientEmail string | null Echoed recipient, or null for a plain link.
message string | null Echoed message.
expiresAt string | null ISO-8601 expiry, or null.
revokedAt string | null Always null on create.
createdAt string ISO-8601 creation timestamp.

Errors

HTTP Code Meaning Fix
400 invalid_json Body wasn't valid JSON Check Content-Type and JSON.stringify
400 invalid_request Body failed schema validation, or channel="email" without recipientEmail Inspect issues; supply recipientEmail for the email channel
401 unauthorized Missing/invalid/revoked key Send a valid Authorization: Bearer key
404 not_found No render with that id in this key's project Check the id and the key's project scope
409 not_ready Render has not succeeded yet Poll the render until status is succeeded

List shares

GET /v1/renders/:id/shares

Returns every share for the render, newest first, including revoked ones. Each row carries a click count (recipient viewed events attributed to the share).

Quick Start

curl

curl https://api.craftkit.dev/v1/renders/$RENDER_ID/shares \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY"

Node.js

const res = await fetch(`https://api.craftkit.dev/v1/renders/${renderId}/shares`, {
  headers: { Authorization: `Bearer ${process.env.CRAFTKIT_API_KEY}` },
});
const { shares } = await res.json();

Python

import os, requests

res = requests.get(
    f"https://api.craftkit.dev/v1/renders/{render_id}/shares",
    headers={"Authorization": f"Bearer {os.environ['CRAFTKIT_API_KEY']}"},
)
shares = res.json()["shares"]

Path parameters

Field Type Description Default
id string Render id (UUIDv7) within the authenticated project. Required. —

Response — 200 OK

{
  "shares": [
    {
      "id": "0193c2c3-...",
      "channel": "link",
      "recipientEmail": null,
      "message": "Here is your signed agreement.",
      "revokedAt": null,
      "revokedReason": null,
      "expiresAt": "2026-12-31T23:59:59.000Z",
      "createdAt": "2026-06-05T10:05:00.000Z",
      "shareToken": "cks_8sd9...",
      "clickCount": 3,
      "shareUrl": "https://www.craftkit.dev/share/cks_8sd9..."
    }
  ]
}
Field Type Description
shares array Shares for the render, newest first. Revoked shares are included.
shares[].id string Share id.
shares[].channel string link or email.
shares[].recipientEmail string | null Recipient, or null.
shares[].message string | null Message shown to the recipient.
shares[].revokedAt string | null ISO-8601 revocation timestamp, or null if active.
shares[].revokedReason string | null Reason recorded at revoke time (e.g. revoked_by_partner), or null.
shares[].expiresAt string | null ISO-8601 expiry, or null.
shares[].createdAt string ISO-8601 creation timestamp.
shares[].shareToken string Plain token, re-exposed so a dashboard can re-display copy-link rows.
shares[].clickCount integer Count of recipient viewed events attributed to this share.
shares[].shareUrl string {shareBase}/share/{shareToken}.

Errors

HTTP Code Meaning Fix
401 unauthorized Missing/invalid/revoked key Send a valid Authorization: Bearer key
404 not_found No render with that id in this key's project Check the id and the key's project scope

Revoke a share

DELETE /v1/renders/:id/shares/:shareId

Soft-deletes the share by stamping revokedAt (reason revoked_by_partner). The public /share/:token URL stops resolving immediately. Revoking is idempotent only in the sense that an already-revoked or unknown share returns 404 — a share can be revoked once.

Quick Start

curl

curl -X DELETE https://api.craftkit.dev/v1/renders/$RENDER_ID/shares/$SHARE_ID \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY"

Node.js

const res = await fetch(
  `https://api.craftkit.dev/v1/renders/${renderId}/shares/${shareId}`,
  {
    method: 'DELETE',
    headers: { Authorization: `Bearer ${process.env.CRAFTKIT_API_KEY}` },
  },
);
const { revokedAt } = await res.json();

Python

import os, requests

res = requests.delete(
    f"https://api.craftkit.dev/v1/renders/{render_id}/shares/{share_id}",
    headers={"Authorization": f"Bearer {os.environ['CRAFTKIT_API_KEY']}"},
)
result = res.json()

Path parameters

Field Type Description Default
id string Render id (UUIDv7) within the authenticated project. Required. —
shareId string Share id to revoke. Required. —

Response — 200 OK

{
  "id": "0193c2c3-...",
  "revokedAt": "2026-06-06T09:00:00.000Z"
}
Field Type Description
id string The revoked share id.
revokedAt string ISO-8601 revocation timestamp.

Errors

HTTP Code Meaning Fix
401 unauthorized Missing/invalid/revoked key Send a valid Authorization: Bearer key
404 not_found Render not found, or share not found / already revoked Confirm both ids; a share can only be revoked once

Email a render

POST /v1/renders/:id/email

Creates an email-channel share and sends the document via Resend in one call. The render must have succeeded. Returns the Resend message id. This endpoint requires email to be configured: RESEND_API_KEY plus a sender address (EMAIL_FROM, or a per-partner emailFrom) — otherwise it returns 503 email_not_configured.

Quick Start

curl

curl -X POST https://api.craftkit.dev/v1/renders/$RENDER_ID/email \
  -H "Authorization: Bearer $CRAFTKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "client@acme.com",
    "recipientName": "Jane Doe",
    "message": "Here is your signed agreement.",
    "expiresAt": "2026-12-31T23:59:59Z"
  }'

Node.js

const res = await fetch(`https://api.craftkit.dev/v1/renders/${renderId}/email`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CRAFTKIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    to: 'client@acme.com',
    recipientName: 'Jane Doe',
    message: 'Here is your signed agreement.',
    expiresAt: '2026-12-31T23:59:59Z',
  }),
});
const { emailMessageId, sentAt } = await res.json();

Python

import os, requests

res = requests.post(
    f"https://api.craftkit.dev/v1/renders/{render_id}/email",
    headers={"Authorization": f"Bearer {os.environ['CRAFTKIT_API_KEY']}"},
    json={
        "to": "client@acme.com",
        "recipientName": "Jane Doe",
        "message": "Here is your signed agreement.",
        "expiresAt": "2026-12-31T23:59:59Z",
    },
)
result = res.json()

Path parameters

Field Type Description Default
id string Render id (UUIDv7) within the authenticated project. Required. —

Request body

{
  "to": "client@acme.com",
  "recipientName": "Jane Doe",
  "message": "Here is your signed agreement.",
  "expiresAt": "2026-12-31T23:59:59Z"
}
Field Type Description Default
to string Recipient email (must be a valid address). Required. —
recipientName string Recipient display name used in the email greeting. Max 200 chars. —
message string Optional note included in the email body. Max 2000 chars. —
expiresAt string ISO-8601 timestamp. Sets an expiry on the underlying share link. Omit for no auto-expiry. No expiry

Response — 201 Created

{
  "id": "0193c2c3-...",
  "shareToken": "cks_8sd9...",
  "shareUrl": "https://www.craftkit.dev/share/cks_8sd9...",
  "emailMessageId": "re_abc123",
  "sentAt": "2026-06-06T09:05:00.000Z"
}
Field Type Description
id string The created share id (channel email).
shareToken string Opaque token embedded in shareUrl.
shareUrl string The link delivered in the email: {shareBase}/share/{shareToken}.
emailMessageId string Resend provider message id for the sent email.
sentAt string ISO-8601 timestamp the email was dispatched.

Errors

HTTP Code Meaning Fix
400 invalid_json Body wasn't valid JSON Check Content-Type and JSON.stringify
400 invalid_request Body failed schema validation (missing/invalid to, etc.) Inspect issues; provide a valid to address
401 unauthorized Missing/invalid/revoked key Send a valid Authorization: Bearer key
404 not_found No render with that id in this key's project Check the id and the key's project scope
409 not_ready Render has not succeeded yet Poll the render until status is succeeded
502 email_send_failed Resend rejected the send Inspect the error message; verify sender domain and recipient
503 email_not_configured Resend isn't wired on this deployment Set RESEND_API_KEY + EMAIL_FROM (or a per-partner sender)