Ownware
Home›Certora›API
Certora · API

Certora API, as shipped in the download

Certora REST API, MCP & Webhooks (v3.0)

Certora ships a JSON REST API and signed webhooks so your LMS, course platform or HR system can issue certificates programmatically — and so anyone can verify a certificate by code without an account.

The batch endpoint is the point. A cohort finishes a course, your LMS makes one call with the whole roster, and every learner gets a certificate with its own verification code.

Authentication

Create a key in Settings → API & Webhooks. Send it on every call:

Authorization: Bearer apk_...

The key is shown once at creation and stored as a SHA-256 hash. Revoke any time.

One endpoint is deliberately public: GET /api/verify/{code} needs no key. A verification code that only its issuer can check would be pointless — employers and registrars must be able to verify. That endpoint reuses the public verify page's per-IP throttle (20 lookups / 60 s), so it cannot be used to guess codes.

Endpoints

Method & pathWhat it does
GET /api/pingVerify a key
GET /api/templatesCertificate templates with their tokens and validity period
GET /api/certificatesNewest 200 issued certificates; ?recipient= filters by name (substring)
POST /api/certificatesIssue one certificate or a whole batch
GET /api/verify/{code}PUBLIC — verify a code, no key required
GET /api/openapi.jsonOpenAPI 3.0 description of everything above

Issue one

curl -X POST https://certs.example.com/api/certificates \
  -H "Authorization: Bearer apk_..." -H "Content-Type: application/json" \
  -d '{"template_id":1,"recipient":{"name":"Ada Lovelace","course":"Fire Safety"}}'

Issue a batch (the LMS case)

curl -X POST https://certs.example.com/api/certificates \
  -H "Authorization: Bearer apk_..." -H "Content-Type: application/json" \
  -d '{"template_id":1,"issue_date":"2026-06-30","recipients":[
        {"name":"Ada Lovelace","course":"Fire Safety"},
        {"name":"Grace Hopper","course":"Fire Safety"},
        {"name":"Alan Turing","course":"Fire Safety"}]}'

Returns 201 with every issued certificate, each carrying its own code, verify_url and pdf_url. A batch is capped at 2000 recipients per call.

Validation is all-or-nothing. Every recipient is checked before anything is written, so a bad row never leaves you with half a cohort certified and a 422 you cannot interpret.

Recipients are accepted inline — you do not have to create recipient records first. Your LMS already owns the roster.

Verify (public)

curl https://certs.example.com/api/verify/ABCD-EFGH-JKMN
{ "found": true, "valid": true, "status": "valid",
  "code": "ABCDEFGHJKMN", "display_code": "ABCD-EFGH-JKMN",
  "recipient_name": "Ada Lovelace", "course": "Fire Safety",
  "issuer": "Ridgeline Training", "issue_date": "2026-06-30", "expires_at": "2029-06-30" }

status is one of valid, expired, revoked — computed by the same engine the web verify page uses. Unknown codes return 404 with {"found": false}. Too many lookups return 429.

MCP (Model Context Protocol) — new in 3.0

Certora speaks MCP over stateless streamable HTTP at POST /mcp. Authentication is the same revocable API key as the REST API (Authorization: Bearer <key>), and a key carries its owner's role, so an agent can never do more than the person whose key it is holding.

GET /mcp answers 405 with Allow: POST — deliberately without the key check, so a probing client is told "right endpoint, wrong verb" instead of a bare 401 that reads as "no MCP here".

POST /mcp   {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}
POST /mcp   {"jsonrpc":"2.0","id":2,"method":"tools/list"}
POST /mcp   {"jsonrpc":"2.0","id":3,"method":"tools/call",
             "params":{"name":"issue_certificates",
                       "arguments":{"template_id":1,"recipients":[4,5,6]}}}
ToolWrites?What it does
list_templatesnoTemplates with orientation, validity period and how many each has issued
template_detailnoOne template, its merge tokens, and the certificates issued from it
list_certificatesnoThe register; filter by template_id, status, search
issue_certificatesyesThe batch operation — one certificate per recipient, each with its own code and a snapshotted expiry
verify_certificatenoThe same lookup the public /verify page performs
expiring_reportnoExpiring within N days, plus those already expired

Argument hygiene. Tool arguments take the same scalar guard as the REST body: a value may not be an array or object unless the tool allowlists it. issue_certificates allowlists exactly one — recipients — because that is the list it exists to take. Unknown arguments are refused by name, and enum values are refused with the list of what is accepted.

One path, one answer. issue_certificates calls the same writer the Generate screen calls, so the merge, the code generation and the expiry snapshot cannot drift between surfaces. A certificate issued by an agent verifies identically through verify_certificate, GET /api/verify/{code} and the public page. verify_certificate returns the same shape this REST API already documents.

Connect it to an assistant

Mint the key in the app first: Settings → API keys. Choose Read only when the assistant should answer questions but never change anything — the endpoint then lists only the read tools and refuses the rest by name, so a careless prompt cannot write. Full access behaves as before.

Every client needs the same three facts, and nothing in the handshake is vendor-specific:

The addresshttps://certs.example.com/mcp
The keyheader Authorization: Bearer apk_xxxx
The transportMCP over streamable HTTP, stateless

Claude — one command, or the same URL and header as a custom connector in the desktop and web apps:

claude mcp add --transport http certora https://certs.example.com/mcp \
  --header "Authorization: Bearer apk_xxxx"

ChatGPT and the OpenAI API — one entry in the Responses API's tools array (in ChatGPT itself, the same URL and key go in as a connector):

{
  "type": "mcp",
  "server_label": "certora",
  "server_url": "https://certs.example.com/mcp",
  "authorization": "apk_xxxx",
  "require_approval": "never"
}

Own Your AI reads a list of servers in this shape:

{
  "mcpServers": [
    { "id": "certora", "name": "Certora", "url": "https://certs.example.com/mcp",
      "token": "apk_xxxx", "enabled": true }
  ]
}

**Every other client spells the same three facts differently — copy the shape from its own documentation, not from here.** VS Code is the clearest example of why: its configuration reference (read 6 September 2026) puts servers in .vscode/mcp.json under a "servers" object — *"an object that maps server names to their configurations"* — not an mcpServers array. Pasted as-is, the block above will not load there. The id, the URL and the token are what travel; the JSON around them belongs to whichever client you are configuring.

A local model, n8n, or your own code — n8n's MCP Client node takes the URL and the same Authorization: Bearer header; a model running on your own machine reaches it through any MCP client, so nothing leaves your network at all. Writing it yourself is one POST of JSON-RPC 2.0:

curl -X POST https://certs.example.com/mcp \
  -H "Authorization: Bearer apk_xxxx" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Any client that speaks MCP's streamable-HTTP transport works, including ones that do not exist yet: Certora implements the protocol, not an integration with a particular vendor.

Webhooks

Add a receiver in Settings → API & Webhooks and choose events:

EventFires when
certificate.issuedA certificate is issued — from Generate, the REST API or /mcp
certificate.revokedA certificate is revoked — one at a time or in bulk from the register (once per certificate; revoking one already revoked sends nothing)

A batch of N recipients fires N separate certificate.issued events — one per certificate, each with its own code. That keeps a single event shape whether you issued one or a thousand.

Every delivery is signed:

X-Certora-Event: certificate.issued
X-Certora-Signature: sha256=<hmac_sha256(raw_body, your_webhook_secret)>

Compare with hash_hmac('sha256', $rawBody, $secret) and reject on mismatch. Deliveries and their response codes are logged in the same settings page.

Errors

CodeMeaning
401Missing, unknown or revoked key
404No such certificate / code
422Validation — the detail field names the offending field
429Verification lookups throttled (public endpoint only)

← Back to Certora · Manual · Quickstart · Test run

Affiliate program
Recommend tools people own — earn 35% on every sale. 90-day tracking, instant delivery, payouts by Lemon Squeezy.
Become an affiliate →