Skip to main content

API reference

Generate certificates, check batch status, and receive webhooks from your own backend. Available on the Business plan.

Authentication

Every request carries your API key as a bearer token. Create and revoke keys from Settings > API. A key is shown in full exactly once, at creation - CertPie only ever stores its hash.

curl https://certpie.com/api/v1/templates \
  -H "Authorization: Bearer cp_live_..."

A key starts with cp_live_. There is no separate test-mode key today.

Rate limits

60 requests a minute and 5,000 a day, per key. A request over either limit gets a 429 response with a Retry-After header, in seconds.

Identify your workspace

Confirms a key works and which workspace it belongs to - useful for a one-time connection check (it's what the Zapier integration's own "Test" step calls).

GET /api/v1/me

{ "organization": { "id": "b1a2c3d4-...", "name": "Acme Institute", "plan": "business" } }

List templates

Every template this key's workspace can generate from, with the field names each one expects.

GET /api/v1/templates

{
  "templates": [
    {
      "id": "b1a2c3d4-...",
      "name": "Course completion",
      "fields": ["{{recipient_name}}", "{{recipient_email}}", "{{course_name}}"]
    }
  ]
}

Generate certificates

Queues a batch for background generation - the same pipeline the dashboard's bulk-generate wizard uses. Each recipient object is keyed by the field names GET /api/v1/templates returned for that template. {{recipient_name}} is required on every recipient.

POST /api/v1/certificates/batches
Content-Type: application/json

{
  "templateId": "b1a2c3d4-...",
  "batchName": "October cohort",
  "recipients": [
    { "{{recipient_name}}": "Jane Doe", "{{recipient_email}}": "jane@example.com", "{{course_name}}": "AI 101" }
  ]
}

202 Accepted
{ "batchId": "f9e8d7c6-...", "status": "queued" }

Up to 2,000 recipients per request. A request that would exceed your plan's certificate allowance gets a 402 response instead of a partial batch.

List batches

Your most recent batches, newest first. Optional limit query parameter (default 25, maximum 100).

GET /api/v1/certificates/batches?limit=10

{
  "batches": [
    { "id": "f9e8d7c6-...", "name": "October cohort", "status": "completed", "total_count": 50, "generated_count": 50, "sent_count": 0, "error_message": null, "created_at": "2026-10-24T12:00:00.000Z" }
  ]
}

Check batch status

GET /api/v1/certificates/batches/{batchId}

{
  "batch": {
    "id": "f9e8d7c6-...",
    "name": "October cohort",
    "status": "completed",
    "total_count": 50,
    "generated_count": 50,
    "sent_count": 0,
    "error_message": null,
    "created_at": "2026-10-24T12:00:00.000Z"
  }
}

status is one of draft, generating, completed, partial, failed, or cancelled. Subscribe to the batch.completed webhook instead of polling, where possible.

Look up a certificate

GET /api/v1/certificates/{certificateId}

{
  "certificate": {
    "id": "a1b2c3d4-...",
    "certificate_number": "CERT-2026-00042",
    "recipient_name": "Jane Doe",
    "recipient_email": "jane@example.com",
    "status": "generated",
    "pdf_url": "https://certpie.com/api/storage/certificates/...",
    "verifyUrl": "https://certpie.com/verify/xK9pQ2mN8r",
    "issued_date": "2026-10-24",
    "expiry_date": null,
    "created_at": "2026-10-24T12:00:05.000Z"
  }
}

Webhooks

CertPie can notify your own endpoint when a batch finishes, instead of you polling for status - this is also what powers a Zapier "New batch completed" trigger. Subscribe and unsubscribe with the same API key used above.

POST /api/v1/webhooks/subscriptions
Content-Type: application/json

{ "url": "https://your-app.example.com/hooks/certpie", "event": "batch.completed" }

201 Created
{ "id": "c7b6a5d4-...", "secret": "whsec_..." }

The endpoint URL must use https. secretis returned exactly once, here - store it, it's what you use to verify a delivery actually came from CertPie (below). To stop delivery, call DELETE /api/v1/webhooks/subscriptions/{id}.

Delivery and signature

Every delivery is a signed POST, matching the Standard Webhooks shape:

POST https://your-app.example.com/hooks/certpie
webhook-id: evt_...
webhook-timestamp: 1761307200
webhook-signature: base64-encoded HMAC-SHA256

{
  "event": "batch.completed",
  "data": {
    "batchId": "f9e8d7c6-...",
    "status": "completed",
    "generatedCount": 50,
    "totalCount": 50
  }
}

Verify it by computing HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body} with the secret CertPie gave you when the subscription was created, base64-encoding it, and comparing that to webhook-signature (use a constant-time comparison, not ===).

Errors

Every error response is JSON, shaped the same way regardless of status code:

{ "error": { "message": "Template not found" } }
  • 400 - the request body or a field in it is invalid
  • 401 - the API key is missing, invalid, or revoked
  • 402- the request would exceed your plan's certificate allowance
  • 403- the key's workspace is not on the Business plan
  • 404 - the template, batch, or certificate was not found for this workspace
  • 429 - rate limit exceeded; see Retry-After