API Reference
Complete guide to CertVerify's REST API for certificate verification, learner authentication, and admin operations.
Verification
Public endpoints for verifying certificates and generating share links.
Verify a certificate's authenticity and retrieve its details. Public endpoint — no authentication required.
Parameters
| Name | Type | Description |
|---|---|---|
certId | string | HMAC-protected certificate ID (e.g., CV-ELT-202606-dHtBMxjI-d40a1a4d) |
Response
{
"certId": "CV-ELT-202606-dHtBMxjI-d40a1a4d",
"holderName": "Jane Smith",
"courseName": "English Learning Course 2026",
"platform": "ELT",
"issuedAt": "2026-06-27T01:05:00Z",
"expiresAt": "2027-06-27T01:05:00Z",
"score": 92,
"passMark": 70,
"revoked": false,
"verified": true
}
Authentication & Signup
Learner authentication endpoints for the external user signup flow.
Send a 6-digit OTP code to verify certificate email. Rate limited: max 3 per email per hour.
Request Body
| Field | Type | |
|---|---|---|
email | string | Required Work/school email to verify |
Response
{
"otpToken": "uid_1719443450000",
"otpCode": "123456" // Returned only if Resend email fails (fallback for testing)
}
Verify the OTP code sent to the user's email. Valid for 10 minutes. Max 5 failed attempts before account deletion.
Request Body
| Field | Type | |
|---|---|---|
otpToken | string | Required Token from otp-send response |
code | string | Required 6-digit code entered by user |
Response
{
"success": true
}
Complete learner signup and create account with certificate email & preferences. Called after OTP verification succeeds.
Request Body
| Field | Type | |
|---|---|---|
certificateEmail | string | Required Verified work/school email |
preferences | object | Required |
preferences.productUpdates | boolean | Opt-in to product updates |
preferences.courseUpdates | boolean | Opt-in to course updates |
Response
{
"success": true
}
Errors
| Status | Error Code | Meaning |
|---|---|---|
| 403 | account_suspended | Account has been suspended by admin |
| 403 | Certificate email not verified | OTP verification not completed |
Check if the current user's account is suspended. Called immediately after Firebase login to block access for suspended users.
Response
{
"suspended": false // true if account is suspended
}
Admin Management
Protected endpoints (admin role required) for managing external learner accounts.
Search and list external learner accounts. Filters by account email or certificate email.
Request Body
| Field | Type | |
|---|---|---|
query | string | Required Min 2 characters (email search) |
limit | number | Default: 20 |
offset | number | Default: 0 For pagination |
Response
{
"results": [
{
"uid": "firebase-uid-123",
"accountEmail": "user@gmail.com",
"certificateEmail": "user@school.edu",
"oauthProvider": "google.com",
"preferences": {
"productUpdates": true,
"courseUpdates": false
},
"status": "active",
"createdAt": "2026-06-28T19:00:00Z",
"verifiedAt": "2026-06-28T19:05:00Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}
Suspend or reactivate a learner account. Suspended users cannot login or access certificates.
Request Body
| Field | Type | |
|---|---|---|
status | string | Required "active" or "suspended" |
Response
{
"success": true
}
Permanently delete a learner account and all associated data. This action is irreversible.
Response
{
"success": true
}
Export opted-in learners as CSV for email campaigns. Only includes users who consented to updates.
Response
Content-Type: text/csv
Email,Joined,Preferences
user@gmail.com,2026-06-28T19:00:00Z,"Product Updates, Course Updates"
another@school.edu,2026-06-27T15:30:00Z,"Course Updates"
Error Handling
All endpoints return standard HTTP status codes. Error responses include an error field:
{
"error": "error_code",
"detail": "Human-readable error message"
}
Common errors:
401 missing_token— Authorization header missing401 invalid_token— Firebase token verification failed403 forbidden— User lacks required role403 account_suspended— Learner account is suspended404 not_found— Certificate or resource not found429 too_many_requests— Rate limit exceeded500 server_error— Internal server error