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)