CertSeal.com API (1.0.0)

Download OpenAPI specification:

Issue, verify, and manage certificates programmatically.

The CertSeal.com REST API lets you issue certificates and look them up from your own backend, LMS, or any other system. Every request is authenticated with a per-workspace API key, created from the API Keys page in the CertSeal.com web app.

Authentication

Send your key in the Authorization header as a bearer token:

Authorization: Bearer csk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The key is bound to a single workspace; you don't need to pass a workspace id separately. Tokens are shown exactly once when you create them — store them in a secrets manager and rotate them by revoking + recreating.

Errors

All non-2xx responses use a single envelope:

{ "error": "QUOTA_EXCEEDED", "message": "...", "detail": { ... } }

The error code is stable and machine-readable; the message is safe to surface to end users; the optional detail object carries structured context (e.g. remaining quota for QUOTA_EXCEEDED).

Rate limits

Each API key is limited to 120 requests per minute by default. Exceeding the limit returns 429 RATE_LIMITED with a Retry-After header (in seconds). Standard RateLimit-* headers are returned on every authenticated response so clients can self-throttle.

Quotas

Issuing a certificate consumes one unit of your workspace owner's plan quota. Lookup, list, and update endpoints are free. Plan limits (e.g. maxBatches) also apply to API-driven actions exactly as they do in the web app.

Outbound webhooks

Instead of polling our list endpoints, register a WebhookSubscription and CertSeal.com will POST signed JSON to your URL when events happen in your workspace.

Event types

  • certificate.issued — a new recipient was created (POST /batches/{id}/recipients, the bulk variant, or a CSV import in the web app). The payload's data is the standard Recipient shape.
  • certificate.sent — the email worker successfully handed the certificate to Resend. Payload is the same Recipient shape with emailStatus = "sent".
  • certificate.failed — Resend rejected the message. Payload adds an error string describing the bounce.
  • certificate.viewed — someone opened the public viewer URL for the first time. Fires at most once per recipient. Payload adds a viewer sub-object with userAgent, referer, ip, and viewedAt.

Subscribe / unsubscribe

Subscribe and Zapier/Make-style unsubscribe both use the regular REST API:

# Create a subscription (returns the signing secret ONCE)
curl -X POST https://services.certseal.com/api/v1/webhooks/subscriptions \
  -H "Authorization: Bearer csk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://hooks.zapier.com/...", "events": ["certificate.issued"] }'

# Delete a subscription (also used by Zapier/Make on Zap-deleted)
curl -X DELETE https://services.certseal.com/api/v1/webhooks/subscriptions/{id} \
  -H "Authorization: Bearer csk_live_..."

Signature verification

Every delivery carries an X-CertSeal-Signature header in the Stripe-style t=<unixSeconds>,v1=<hexHmacSha256> format. The HMAC is computed over ${t}.${rawRequestBody} using the subscription's signing secret. Reject requests where |now - t| > 300 seconds to defeat replay attacks.

Node.js verifier (paste into a Zapier code step or your own handler):

const crypto = require("node:crypto");
function verify(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((p) => p.split("=")),
  );
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");
  const ok = crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(parts.v1, "hex"),
  );
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return ok && fresh;
}

Retries, idempotency, suspension

  • Deliveries are at-least-once. Use X-CertSeal-Delivery (the delivery id) as your idempotency key.
  • On non-2xx or timeout we retry with backoff: 1m, 5m, 30m, 2h, 12h, 24h. After the 7th failed attempt the delivery is marked failed.
  • HTTP 410 Gone is treated as the RESTHooks "unsubscribe" signal: we hard-delete the subscription so you can re-subscribe the same URL.
  • After 20 consecutive failures on any subscription we auto-suspended it. Re-activate from the Automations page in the web app after fixing your endpoint.

Auth

Test-authentication endpoint used by integrations (e.g. Zapier) to verify an API key and display a human-readable label for the connected workspace.

Test API key

Verifies the bearer API key and returns a human-readable username for the connected workspace. Intended for use as Zapier's "Test Auth" / connection-label endpoint — a 2xx response confirms the key is valid and the returned username is safe to surface in the integration UI.

Authorizations:
bearerAuth

Responses

Response samples

Content type
application/json
{
  • "workspaceId": "ws_abc123",
  • "username": "Jane Doe"
}

Batches

A batch is a group of recipients that share a single design (and optionally one email template). Create a batch first, then issue certificates into it.

List batches

Returns batches in the API key's workspace, newest first.

By default only active batches are returned. Use ?status=archived to list batches that have been soft-deleted via the archive endpoint, or ?status=all to merge both groups into one page.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque cursor returned in the previous response's pagination.nextCursor. Omit to start from the first page.

limit
integer [ 1 .. 100 ]
Default: 25

Page size (clamped to 100).

status
string
Default: "active"
Enum: "active" "archived" "all"

Filter by archive state. Defaults to active.

Responses

Response samples

Content type
application/json
{
  • "batches": [
    ],
  • "pagination": {
    }
}

Create a batch

Creates a new batch in the API key's workspace. The designId and (optional) emailTemplateId must already exist in the same workspace; create them from the web app first.

Authorizations:
bearerAuth
Request Body schema: application/json
required
title
required
string non-empty
description
string or null
designId
required
string

Must refer to a design in the same workspace as the API key.

emailTemplateId
string or null

Must refer to an email template in the same workspace.

courseName
string or null
issuerName
string or null

Responses

Request samples

Content type
application/json
Example
{
  • "title": "2026 Spring Cohort",
  • "designId": "clx1abcd0000xxxxabcd"
}

Response samples

Content type
application/json
{
  • "batch": {
    }
}

Get a batch

Returns the batch regardless of archive state — verification tooling can keep resolving archived batches forever. Use the archivedAt field on the response to detect frozen batches.

Authorizations:
bearerAuth
path Parameters
id
required
string

Batch id.

Responses

Response samples

Content type
application/json
{
  • "batch": {
    }
}

Archive a batch

Soft-delete the batch. The row is preserved, every issued certificate's public URL keeps resolving forever, and the rendered PDF cache stays valid — but the batch is frozen: any future recipient write (create / update / delete / send) returns 409 BATCH_ARCHIVED until you unarchive.

Idempotent: archiving an already-archived batch is a no-op that returns the current row.

Hard delete is intentionally not exposed by the API. See docs/api.md "Archiving batches" for the rationale.

Authorizations:
bearerAuth
path Parameters
id
required
string

Batch id.

Responses

Response samples

Content type
application/json
{
  • "batch": {
    }
}

Unarchive a batch

Clear the batch's archivedAt timestamp, returning it to the active list and re-enabling recipient writes / email sends.

Idempotent: unarchiving an active batch is a no-op that returns the current row.

Authorizations:
bearerAuth
path Parameters
id
required
string

Batch id.

Responses

Response samples

Content type
application/json
{
  • "batch": {
    }
}

Designs

A design is a reusable certificate template authored in the CertSeal editor. The API exposes designs read-only so integrations can pick a designId when creating a batch; designs are created and edited in the web app.

List designs

Returns designs in the API key's workspace, newest first. Read-only: designs are authored in the CertSeal web app. Use this to populate a design picker when creating a batch.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque cursor returned in the previous response's pagination.nextCursor. Omit to start from the first page.

limit
integer [ 1 .. 100 ]
Default: 25

Page size (clamped to 100).

Responses

Response samples

Content type
application/json
{
  • "designs": [
    ],
  • "pagination": {
    }
}

Recipients

A recipient represents one issued certificate inside a batch. Each recipient gets a certificateId (human code) and a shareToken (used to build the public viewer URL).

List recipients in a batch

Authorizations:
bearerAuth
path Parameters
batchId
required
string
query Parameters
cursor
string

Opaque cursor returned in the previous response's pagination.nextCursor. Omit to start from the first page.

limit
integer [ 1 .. 100 ]
Default: 25

Page size (clamped to 100).

emailStatus
string
Enum: "pending" "queued" "sending" "sent" "failed"

Filter by current email state.

Responses

Response samples

Content type
application/json
{
  • "recipients": [
    ],
  • "pagination": {
    }
}

Issue a single certificate

Creates one recipient (= one certificate) in the batch. A new certificateId and shareToken are minted automatically unless you supply your own. Set send: true to enqueue the certificate email immediately — the batch must have an emailTemplateId and the server must have Resend configured.

Consumes one unit of plan quota.

Authorizations:
bearerAuth
path Parameters
batchId
required
string
Request Body schema: application/json
required
name
string or null
email
string or null <email>
certificateId
string or null

Override the auto-generated certificate id. Must be unique across the whole system; duplicates return 409.

issueDate
string or null <date>

ISO date (or full ISO timestamp).

expiryDate
string or null <date>
object

Free-form key/value bag interpolated into the certificate template wherever {{key}} appears.

send
boolean
Default: false

When true, also queue the certificate email immediately. Requires the batch to have emailTemplateId set and the server to have Resend configured.

Responses

Request samples

Content type
application/json
Example
{
  • "name": "Jane Doe",
  • "email": "jane@example.com"
}

Response samples

Content type
application/json
{
  • "recipient": {
    }
}

Issue certificates in bulk

Issues up to 100 recipients in a single request. The whole batch is created in one database transaction — if any row fails (e.g. a duplicate certificateId or quota exhaustion) none of them are created and the response indicates which row caused the failure.

Authorizations:
bearerAuth
path Parameters
batchId
required
string
Request Body schema: application/json
required
required
Array of objects (RecipientInput) [ 1 .. 100 ] items
send
boolean
Default: false

Apply to every recipient in the batch.

Responses

Request samples

Content type
application/json
{
  • "send": true,
  • "recipients": [
    ]
}

Response samples

Content type
application/json
{
  • "recipients": [
    ],
  • "created": 0
}

Get a recipient

Authorizations:
bearerAuth
path Parameters
batchId
required
string
recipientId
required
string

Responses

Response samples

Content type
application/json
{
  • "recipient": {
    }
}

Update a recipient

Partial update — only the fields you include are changed. Pass null to clear an optional field (e.g. "email": null).

Authorizations:
bearerAuth
path Parameters
batchId
required
string
recipientId
required
string
Request Body schema: application/json
required
name
string or null
email
string or null <email>
certificateId
string or null
issueDate
string or null <date>
expiryDate
string or null <date>
object

Responses

Request samples

Content type
application/json
{
  • "name": "Jane S. Doe",
  • "issueDate": "2026-05-01"
}

Response samples

Content type
application/json
{
  • "recipient": {
    }
}

Delete a recipient

Permanently deletes the recipient. Plan quota is not refunded — the underlying certificate id is already burned.

Note: this only works on active batches. If the recipient's batch has been archived, you must unarchive it first; otherwise the request fails with 409 BATCH_ARCHIVED.

Authorizations:
bearerAuth
path Parameters
batchId
required
string
recipientId
required
string

Responses

Response samples

Content type
application/json
{
  • "ok": true
}

Queue the certificate email

Marks the recipient as queued so the background worker delivers the email at the configured rate. The batch must already have an emailTemplateId and the recipient must have an email.

Authorizations:
bearerAuth
path Parameters
batchId
required
string
recipientId
required
string

Responses

Response samples

Content type
application/json
{
  • "recipient": {
    }
}

Certificates

Authenticated certificate lookup — equivalent to the public viewer but workspace-scoped to your API key, and includes the recipient's email address.

Look up by certificate id

Look up a certificate by its human-readable certificateId (e.g. CERT-2026-003843-000001). Returns the recipient (with email), the batch metadata, the design template JSON, and the merged variable map — enough to render the cert client-side without scraping the public viewer.

Authorizations:
bearerAuth
path Parameters
certificateId
required
string
Example: CERT-2026-003843-000001

Responses

Response samples

Content type
application/json
{
  • "recipient": {
    },
  • "batch": {
    },
  • "design": {
    },
  • "variables": {
    }
}

Look up by share token

Look up a certificate by its shareToken — the opaque value used in public viewer URLs like /v/{shareToken}. Same response shape as the certificate-id lookup.

Authorizations:
bearerAuth
path Parameters
shareToken
required
string >= 8 characters

Responses

Response samples

Content type
application/json
{
  • "recipient": {
    },
  • "batch": {
    },
  • "design": {
    },
  • "variables": {
    }
}

Webhooks

Outbound webhooks deliver workspace events (certificate.issued, certificate.sent, certificate.failed, certificate.viewed) to a customer-supplied URL. Subscriptions are workspace-scoped via the API key; payloads are signed with HMAC-SHA256 (see the "Outbound webhooks" section in the introduction for verification and retry semantics).

List webhook subscriptions

Returns every webhook subscription in the API key's workspace, newest first.

Authorizations:
bearerAuth
query Parameters
cursor
string

Opaque cursor returned in the previous response's pagination.nextCursor. Omit to start from the first page.

limit
integer [ 1 .. 100 ]
Default: 25

Page size (clamped to 100).

Responses

Response samples

Content type
application/json
{
  • "subscriptions": [
    ],
  • "pagination": {
    }
}

Subscribe to events

Register a URL to receive POSTs when the specified events fire in this workspace. The response includes a one-time signingSecret — store it immediately, you cannot recover it later. URL constraints:

  • Must be https:// in production.
  • Must not resolve to a loopback, link-local, or RFC1918 address (re-checked after DNS at every delivery to defeat rebind attacks).
  • Max 20 subscriptions per workspace.
Authorizations:
bearerAuth
Request Body schema: application/json
required
url
required
string <uri>

Endpoint that receives webhook POSTs. Must be https:// in production and must not resolve to a private/loopback address.

events
required
Array of strings (WebhookEventType) non-empty
Items Enum: "certificate.issued" "certificate.sent" "certificate.failed" "certificate.viewed"

Responses

Request samples

Content type
application/json
Example
{}

Response samples

Content type
application/json
{
  • "subscription": {
    },
  • "signingSecret": "wh_sec_AbCdEfGh01234567890abcdefghijKLMN"
}

Get a webhook subscription

Authorizations:
bearerAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "subscription": {
    }
}

Unsubscribe

Hard-deletes the subscription. Used by Zapier and Make as the REST Hooks "unsubscribe" call; clients calling this directly get the same effect. Returns 204 No Content on success.

Authorizations:
bearerAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
Example
{
  • "error": "MISSING_API_KEY",
  • "message": "Missing Authorization header"
}

Send a sample event

Enqueues a synthetic event delivery for the requested event type so consumers can verify their endpoint and pull a sample payload. The delivery goes through the normal dispatcher — signature, headers, and retry behavior are all identical to a real event.

If eventType is omitted, the first event the subscription listens for is used. Returns 202 Accepted once the delivery row is persisted; the outbound POST happens asynchronously.

Authorizations:
bearerAuth
path Parameters
id
required
string
Request Body schema: application/json
optional
eventType
string (WebhookEventType)
Enum: "certificate.issued" "certificate.sent" "certificate.failed" "certificate.viewed"

Stable identifier for an event a subscription can listen to. Sent as the X-CertSeal-Event header on every delivery.

Responses

Request samples

Content type
application/json
{
  • "eventType": "certificate.issued"
}

Response samples

Content type
application/json
{
  • "delivery": {
    },
  • "sample": {
    }
}

Rotate the signing secret

Generates a new HMAC signing secret for this subscription and returns it. Update your verifier immediately — events posted AFTER this call are signed with the new secret. The old secret is invalidated atomically.

Authorizations:
bearerAuth
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "subscription": {
    },
  • "signingSecret": "string"
}