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.
1curl https://certpie.com/api/v1/templates \2 -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.
1429 Too Many Requests2Retry-After: 42Identify 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).
/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. supportsBackgroundGeneration is false for a template using a gradient color - background generation can't render these yet, so POST /api/v1/certificates/batches rejects a batch for one immediately.
ready is false for a template with no {{recipient_name}} field yet - typically a system template (or a duplicate of one) that hasn't been opened in the editor to add fields. Generating from it would produce certificates with no recipient name on them, so POST /api/v1/certificates/batches rejects it (422) instead. not_ready_reason is present only when ready is false.
/api/v1/templates1{2 "templates": [3 {4 "id": "b1a2c3d4-...",5 "name": "Course completion",6 "fields": ["{{recipient_name}}", "{{recipient_email}}", "{{course_name}}"],7 "supportsBackgroundGeneration": true,8 "ready": true9 },10 {11 "id": "e5f6a7b8-...",12 "name": "Navy Gold Geometric",13 "fields": ["{{recipient_email}}"],14 "supportsBackgroundGeneration": true,15 "ready": false,16 "not_ready_reason": "This template has no recipient name field yet. Duplicate it and add fields in the editor before generating."17 }18 ]19}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. A template with supportsBackgroundGeneration: false or ready: false is rejected immediately (422) rather than failing later or generating certificates with no name on them.
/api/v1/certificates/batches1Content-Type: application/json2 3{4 "templateId": "b1a2c3d4-...",5 "batchName": "October cohort",6 "recipients": [7 { "{{recipient_name}}": "Jane Doe", "{{recipient_email}}": "jane@example.com", "{{course_name}}": "AI 101" }8 ]9}10 11202 Accepted12{ "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. A recipient key that matches none of the template's fields - a typo, usually - doesn't fail the request; it comes back as a warning instead, so it's worth checking for:
1202 Accepted2{3 "batchId": "f9e8d7c6-...",4 "status": "queued",5 "warnings": { "unmatchedRecipientKeys": ["{{cours_name}}"] }6}List batches
Your most recent batches, newest first. Optional limit query parameter (default 25, maximum 100).
/api/v1/certificates/batches?limit=101{2 "batches": [3 { "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" }4 ]5}Check batch status
/api/v1/certificates/batches/{batchId}1{2 "batch": {3 "id": "f9e8d7c6-...",4 "name": "October cohort",5 "status": "partial",6 "total_count": 50,7 "generated_count": 47,8 "sent_count": 0,9 "error_message": "3 rows weren't generated.",10 "created_at": "2026-10-24T12:00:00.000Z",11 "skipped_count": 1,12 "blocked_count": 2,13 "failed_count": 0,14 "row_outcomes": [15 { "row_index": 12, "kind": "skipped", "reason": "Skipped: no recipient name provided for this row.", "recipient_name": null },16 { "row_index": 30, "kind": "blocked", "reason": "This row has Hindi (Devanagari) characters that Lora can't print. Change the font or fix the data for this row.", "recipient_name": "Aanya Sharma" }17 ]18 }19}status is one of draft, generating, completed, partial, failed, or cancelled. Subscribe to the batch.completed webhook instead of polling, where possible.
skipped_count/blocked_count/failed_count break down total_count - generated_count by why each row didn't generate: skipped (no recipient name), blocked (a character the template's font can't print), or a real failure. Every row in that gap also appears in row_outcomes, with its original 0-based row index and a factual reason.
Look up a certificate
Fetches one certificate once it has been generated: its PDF URL, its public verification link, and its status.
/api/v1/certificates/{certificateId}1{2 "certificate": {3 "id": "a1b2c3d4-...",4 "certificate_number": "CERT-2026-00042",5 "recipient_name": "Jane Doe",6 "recipient_email": "jane@example.com",7 "status": "generated",8 "pdf_url": "https://certpie.com/api/storage/certificates/...",9 "verifyUrl": "https://certpie.com/verify/xK9pQ2mN8r",10 "issued_date": "2026-10-24",11 "expiry_date": null,12 "created_at": "2026-10-24T12:00:05.000Z"13 }14}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.
/api/v1/webhooks/subscriptions1Content-Type: application/json2 3{ "url": "https://your-app.example.com/hooks/certpie", "event": "batch.completed" }4 5201 Created6{ "id": "c7b6a5d4-...", "secret": "whsec_..." }The endpoint URL must use https. secret is 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:
https://your-app.example.com/hooks/certpie1webhook-id: evt_...2webhook-timestamp: 17613072003webhook-signature: base64-encoded HMAC-SHA2564 5{6 "event": "batch.completed",7 "data": {8 "batchId": "f9e8d7c6-...",9 "status": "completed",10 "generatedCount": 50,11 "totalCount": 5012 }13}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 ===).
Connect via Claude (MCP)
Everything above is also available as an MCP (Model Context Protocol) connector, so Claude can generate certificates, check batch status, and manage webhooks directly from a chat. No API key to copy, no terminal - it signs in with your CertPie account the same way any other app you'd connect to Claude does.
https://certpie.com/api/mcpIn Claude, open Settings > Connectors > Add custom connector, paste that URL, and approve the connection when prompted. Claude handles the rest (registration and sign-in) on its own.
Available tools
certpie_whoami- identify the connected workspacecertpie_list_templates- list templates and their fieldscertpie_generate_certificates- queue a batchcertpie_list_batches- list recent batchescertpie_get_batch_status- check one batchcertpie_get_certificate- look up one certificatecertpie_list_webhooks- list webhook subscriptionscertpie_subscribe_webhook- subscribe a webhook endpointcertpie_unsubscribe_webhook- remove a webhook subscription
Errors
Every error response is JSON, shaped the same way regardless of status code:
{ "error": { "code": "TEMPLATE_NOT_FOUND", "message": "Template not found" } }message is a human-readable sentence - stable in meaning, but not guaranteed to be byte-for-byte identical between API versions. code is the stable identifier to branch your integration on.
400- the request body or a field in it is invalid401- the API key is missing, invalid, or revoked402- the request would exceed your plan's certificate allowance403- the key's workspace is not on the Business plan, or the key is read-only404- the template, batch, certificate, or subscription was not found for this workspace422- the template can't be used to generate certificates as it stands429- rate limit exceeded; seeRetry-After500- something went wrong on our end; the request can be retried
Error codes
Match on error.code, not error.message.
MISSING_AUTHORIZATION- no (or a malformed) Authorization headerINVALID_API_KEY- the key is unrecognized or revokedPLAN_UPGRADE_REQUIRED- the workspace isn't on the Business planRATE_LIMIT_EXCEEDED- too many requests; seeRetry-AfterREAD_ONLY_API_KEY- the key can't perform write actionsINVALID_JSON_BODY- the request body isn't valid JSONVALIDATION_ERROR- a field in the request body failed validation (seemessagefor which one)TEMPLATE_NOT_FOUND- no template with that id in this workspaceTEMPLATE_NOT_RENDERABLE- the template uses something the background renderer can't draw yetTEMPLATE_NOT_READY- the template has no recipient-name fieldTEMPLATE_STATIC_TEXT_UNPRINTABLE- the template has fixed text its font can't printPLAN_LIMIT_REACHED- the request would exceed the plan's certificate allowanceBATCH_NOT_FOUND- no batch with that id in this workspaceORGANIZATION_NOT_FOUND- the key's workspace couldn't be resolvedCERTIFICATE_NOT_FOUND- no certificate with that id in this workspaceSUBSCRIPTION_NOT_FOUND- no webhook subscription with that id in this workspaceINTERNAL_ERROR- something went wrong on our end; the request can be retried