This document describes the public API endpoints for the EduProof platform.
Base URL: http://localhost:3001 (development) or your deployed backend URL
- Health Check
- OCR (Optical Character Recognition)
- IPFS Upload
- IPFS Preview Proxy
- Certificate Management
Check the health status of all backend services.
Request:
curl http://localhost:3001/api/healthResponse (200 OK):
{
"ok": true,
"timestamp": "2025-01-20T17:00:00.000Z",
"gemini": {
"configured": true,
"model": "gemini-flash-latest"
},
"pinata": {
"configured": true
},
"supabase": {
"status": "connected"
}
}Response Fields:
ok(boolean) - Overall health statustimestamp(string) - ISO 8601 timestampgemini.configured(boolean) - Gemini API key presentgemini.model(string) - Current Gemini model namepinata.configured(boolean) - Pinata credentials presentsupabase.status(string) - Database connection status
Use Cases:
- Monitoring and alerting
- Pre-flight checks before operations
- Debugging configuration issues
Extract certificate data from an uploaded image or PDF using Google Gemini AI.
Request:
curl -X POST http://localhost:3001/api/ocr \
-F "file=@certificate.pdf"Supported File Types:
image/pngimage/jpegimage/jpgimage/webpimage/heicimage/heifimage/svg+xmlapplication/pdf
File Size Limit: 15MB (configurable via MAX_UPLOAD_MB)
Response (200 OK):
{
"student_name": "John Doe",
"course_name": "Introduction to Machine Learning",
"institution": "Stanford University",
"issue_date": "2024-12-15",
"fields_confidence": {
"student_name": 0.95,
"course_name": 0.92,
"institution": 0.98,
"issue_date": 0.88
},
"verification_notes": "All fields extracted with high confidence. Date format normalized to YYYY-MM-DD.",
"verification_score": 93
}Response Fields:
student_name(string) - Extracted student name (empty string if not found)course_name(string) - Extracted course/program nameinstitution(string) - Extracted institution nameissue_date(string) - Extracted date in YYYY-MM-DD formatfields_confidence(object) - Confidence scores (0.0-1.0) for each fieldverification_notes(string) - AI-generated notes about extraction qualityverification_score(number) - Overall confidence score (0-100)
Error Responses:
400 Bad Request - Missing file
{
"error": "Missing file (field \"file\")"
}415 Unsupported Media Type - Invalid file type
{
"error": "Unsupported type: application/zip"
}502 Bad Gateway - Gemini API error
{
"error": "Gemini HTTP error",
"details": {
"error": {
"code": 503,
"message": "The model is overloaded. Please try again later.",
"status": "UNAVAILABLE"
}
}
}Model Fallback Strategy:
- Try
gemini-flash-latest(primary) - If fails, try
gemini-2.5-flash - If fails, try
gemini-2.5-pro - Return error if all models fail
Timeout: 30 seconds (configurable via GEMINI_TIMEOUT_MS)
Upload an image or PDF to IPFS via Pinata.
Request:
curl -X POST http://localhost:3001/api/ipfs/upload-image \
-F "file=@certificate.pdf" \
-F "name=John Doe Certificate"Form Fields:
file(required) - Image or PDF filename(optional) - Descriptive name for pinning metadata
Response (200 OK) - Image:
{
"cid": "QmX7Zd9...",
"mime": "image/png",
"url": "https://gateway.pinata.cloud/ipfs/QmX7Zd9..."
}Response (200 OK) - PDF with Preview:
{
"cid": "QmY8Ae3...",
"previewCid": "QmZ9Bf4...",
"mime": "application/pdf",
"previewMime": "image/svg+xml",
"url": "https://gateway.pinata.cloud/ipfs/QmY8Ae3...",
"previewUrl": "https://gateway.pinata.cloud/ipfs/QmZ9Bf4..."
}Response Fields:
cid(string) - IPFS content identifier for original filepreviewCid(string, PDF only) - IPFS CID for SVG previewmime(string) - Original file MIME typepreviewMime(string, PDF only) - Preview MIME type (alwaysimage/svg+xml)url(string) - IPFS gateway URL for original filepreviewUrl(string, PDF only) - IPFS gateway URL for preview
Error Responses:
400 Bad Request
{
"error": "No file uploaded"
}500 Internal Server Error
{
"error": "IPFS upload failed",
"details": "..."
}Upload ERC-721 metadata JSON to IPFS.
Request:
curl -X POST http://localhost:3001/api/ipfs/upload-metadata \
-H "Content-Type: application/json" \
-d '{
"name": "Certificate #12345",
"description": "Machine Learning Certificate for John Doe",
"image": "ipfs://QmX7Zd9...",
"attributes": [
{"trait_type": "Student", "value": "John Doe"},
{"trait_type": "Course", "value": "Introduction to ML"},
{"trait_type": "Institution", "value": "Stanford University"},
{"trait_type": "Issue Date", "value": "2024-12-15"}
]
}'Request Body (ERC-721 Metadata Standard):
{
name: string; // NFT name
description: string; // NFT description
image: string; // IPFS URI (ipfs://...)
attributes: Array<{ // Certificate attributes
trait_type: string;
value: string;
}>;
}Response (200 OK):
{
"cid": "QmA1Bc2...",
"url": "https://gateway.pinata.cloud/ipfs/QmA1Bc2..."
}Error Responses:
400 Bad Request
{
"error": "Invalid metadata: missing required field 'name'"
}Proxy endpoint for PDF preview SVGs stored on IPFS.
Purpose:
- Avoid CORS issues with direct IPFS gateway access
- Provide consistent preview URLs
- Enable HEAD pre-checks for preview availability
Request:
curl http://localhost:3001/api/ipfs/preview/QmZ9Bf4....svgResponse (200 OK):
Content-Type: image/svg+xml
Access-Control-Allow-Origin: *
<svg xmlns="http://www.w3.org/2000/svg" ...>
<!-- SVG content -->
</svg>
Error Responses:
404 Not Found
{
"error": "Preview not found on IPFS"
}500 Internal Server Error
{
"error": "Failed to fetch preview from IPFS"
}HEAD Request Support:
Frontend uses HEAD requests to check preview availability before rendering:
curl -I http://localhost:3001/api/ipfs/preview/QmZ9Bf4....svgResponse:
HTTP/1.1 200 OK
Content-Type: image/svg+xml
Content-Length: 45678
Debug endpoint to inspect preview generation details.
Request:
curl http://localhost:3001/api/ipfs/preview/QmY8Ae3....debugResponse (200 OK):
{
"cid": "QmY8Ae3...",
"ipfsUrl": "https://gateway.pinata.cloud/ipfs/QmY8Ae3...",
"previewGenerated": true,
"previewCid": "QmZ9Bf4...",
"error": null
}Check if a certificate ID is already registered (anti-duplicate check).
Query Parameters:
certId(required) - Certificate ID to check
Request:
curl "http://localhost:3001/api/certificates/availability?certId=CERT-2024-12345"Response (200 OK) - Available:
{
"available": true,
"certId": "CERT-2024-12345"
}Response (200 OK) - Already Exists:
{
"available": false,
"certId": "CERT-2024-12345",
"existingCertificate": {
"id": "uuid-...",
"cert_id": "CERT-2024-12345",
"student_name": "John Doe",
"institution": "Stanford University",
"owner_address": "0x123...",
"created_at": "2024-12-15T10:00:00.000Z"
}
}Error Responses:
400 Bad Request
{
"error": "Missing certId parameter"
}Index a newly minted certificate in the database.
Request:
curl -X POST http://localhost:3001/api/certificates/index \
-H "Content-Type: application/json" \
-H "x-idempotency-key: abc123..." \
-d '{
"certId": "CERT-2024-12345",
"studentName": "John Doe",
"courseName": "Introduction to ML",
"institution": "Stanford University",
"issueDate": "2024-12-15",
"ownerAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bfbE",
"tokenId": "1",
"metadataUri": "ipfs://QmA1Bc2...",
"imageUri": "ipfs://QmX7Zd9...",
"txHash": "0xabc123...",
"verificationScore": 93
}'Request Headers:
x-idempotency-key(optional) - Prevents duplicate indexing
Request Body:
{
certId: string; // Unique certificate ID
studentName: string; // Student name
courseName: string; // Course/program name
institution: string; // Institution name
issueDate: string; // ISO date string
ownerAddress: string; // Ethereum address (0x...)
tokenId: string; // NFT token ID
metadataUri: string; // IPFS metadata URI
imageUri: string; // IPFS image URI
txHash: string; // Blockchain transaction hash
verificationScore: number; // OCR confidence (0-100)
}Response (200 OK):
{
"success": true,
"certificateId": "uuid-..."
}Error Responses:
400 Bad Request
{
"error": "Missing required field: certId"
}409 Conflict - Duplicate certificate ID
{
"error": "Certificate with this ID already exists"
}Get all certificates owned by a specific Ethereum address.
URL Parameters:
address(required) - Ethereum address (0x...)
Request:
curl http://localhost:3001/api/certificates/owner/0x742d35Cc6634C0532925a3b844Bc9e7595f0bfbEResponse (200 OK):
{
"certificates": [
{
"id": "uuid-1",
"cert_id": "CERT-2024-12345",
"student_name": "John Doe",
"course_name": "Introduction to ML",
"institution": "Stanford University",
"issue_date": "2024-12-15T00:00:00.000Z",
"owner_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bfbE",
"token_id": "1",
"metadata_uri": "ipfs://QmA1Bc2...",
"image_uri": "ipfs://QmX7Zd9...",
"tx_hash": "0xabc123...",
"verification_score": 93,
"created_at": "2024-12-15T10:00:00.000Z"
}
],
"count": 1
}Error Responses:
400 Bad Request
{
"error": "Invalid Ethereum address"
}Verify a certificate by ID (searches database, falls back to blockchain).
Query Parameters:
certId(required) - Certificate ID to verify
Request:
curl "http://localhost:3001/api/certificates/verify?certId=CERT-2024-12345"Response (200 OK) - Found in Database:
{
"found": true,
"source": "database",
"certificate": {
"id": "uuid-1",
"cert_id": "CERT-2024-12345",
"student_name": "John Doe",
"course_name": "Introduction to ML",
"institution": "Stanford University",
"issue_date": "2024-12-15T00:00:00.000Z",
"owner_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bfbE",
"token_id": "1",
"metadata_uri": "ipfs://QmA1Bc2...",
"image_uri": "ipfs://QmX7Zd9...",
"tx_hash": "0xabc123...",
"verification_score": 93,
"created_at": "2024-12-15T10:00:00.000Z"
}
}Response (200 OK) - Not Found:
{
"found": false,
"certId": "CERT-2024-99999"
}Error Responses:
400 Bad Request
{
"error": "Missing certId parameter"
}Current Limits:
- OCR: Limited by Gemini API (15 requests/minute on free tier)
- IPFS Upload: No hard limit (Pinata account limits apply)
- Other endpoints: No rate limiting (consider implementing in production)
Allowed Origins:
- Development:
http://localhost:5173 - Production: Configured via
FRONTEND_URLenvironment variable
Allowed Methods: GET, POST, PATCH, DELETE, OPTIONS
Allowed Headers: Content-Type, x-admin-key, x-idempotency-key
All errors follow this structure:
{
"error": "Human-readable error message",
"details": { /* Optional additional context */ }
}Common HTTP Status Codes:
200- Success400- Bad request (invalid input)401- Unauthorized (admin endpoints)404- Resource not found409- Conflict (duplicate resource)413- Payload too large415- Unsupported media type500- Internal server error502- Bad gateway (external service error)503- Service unavailable (rate limit, overload)