Appearance
The Certificate API provides endpoints for creating, managing, and querying digital certificates on the blockchain. Certificates are represented as NFTs (Non-Fungible Tokens) and contain metadata, images, and verifiable ownership information.
Core Concepts
Digital certificates in Pocketbook are blockchain-based NFTs with the following properties:
- Metadata: Name, description, category, and custom attributes
- Image: Visual representation (IPFS-hosted)
- Owner: Ethereum wallet address of the certificate holder
- Token ID: Unique blockchain identifier
- Status: Minted, pending, or burned
Authentication
All certificate endpoints (except public queries) require authentication. You can use:
JWT Token (recommended for web apps):
Authorization: Bearer <token>API Key (recommended for server-to-server):
x-api-key: pk_000...
See the Authentication Guide for details on obtaining tokens.
Endpoints
Authenticate
Generate a JWT token for subsequent API requests using wallet signature authentication.
http
POST /api/certificate/auth
Content-Type: application/jsonRequest Body:
json
{
"signature": "0x...",
"address": "0x1234567890123456789012345678901234567890",
"message": "Authenticate with Pocketbook: 1234567890",
"timestamp": "1234567890"
}Parameters:
signature(string, required): Wallet signature of the messageaddress(string, required): Ethereum wallet addressmessage(string, required): Message that was signed (format:Authenticate with Pocketbook: <timestamp>)timestamp(string, required): Unix timestamp (must be within 5 minutes)
Response (200 OK):
json
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresIn": "24h"
}
}Example:
javascript
// Sign message with wallet
const timestamp = Math.floor(Date.now() / 1000);
const message = `Authenticate with Pocketbook: ${timestamp}`;
const signature = await wallet.signMessage(message);
// Exchange for JWT token
const response = await fetch('https://api.pocketbook.studio/api/certificate/auth', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
signature,
address: wallet.address,
message,
timestamp: timestamp.toString()
})
});
const { data } = await response.json();
const token = data.token;Error Responses:
400 Bad Request: Invalid parameters or timestamp401 Unauthorized: Invalid signature or expired timestamp
Create Certificate
Create a new certificate with metadata and image. The certificate is immediately minted on the blockchain.
http
POST /api/certificate/create
Authorization: Bearer <token>
Content-Type: application/jsonRequest Body:
json
{
"name": "Certificate Name",
"description": "Certificate description",
"imageUrl": "https://example.com/image.png",
"metadata": {
"category": "achievement",
"attributes": [
{ "trait_type": "Level", "value": "Expert" },
{ "trait_type": "Date", "value": "2025-01-15" }
]
}
}Parameters:
name(string, required): Certificate name (max 100 characters)description(string, required): Certificate description (max 500 characters)imageUrl(string, required): URL to certificate image (will be uploaded to IPFS)metadata.category(string, optional): Certificate category (e.g., "achievement", "education", "membership")metadata.attributes(array, optional): Custom attributes with trait_type and value pairs
Response (201 Created):
json
{
"success": true,
"data": {
"certificateId": "507f1f77bcf86cd799439011",
"tokenId": "123456",
"transactionHash": "0xabc123...",
"status": "minted",
"ipfsUrl": "ipfs://QmHash...",
"createdAt": "2025-01-15T10:30:00Z"
}
}Example:
javascript
const response = await fetch('https://api.pocketbook.studio/api/certificate/create', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Achievement Certificate',
description: 'Awarded for completing the Web Development 101 course',
imageUrl: 'https://example.com/certificate.png',
metadata: {
category: 'education',
attributes: [
{ trait_type: 'Course', value: 'Web Development 101' },
{ trait_type: 'Completion Date', value: '2025-01-15' },
{ trait_type: 'Grade', value: 'A+' }
]
}
})
});
const result = await response.json();
console.log('Certificate created:', result.data.certificateId);Error Responses:
400 Bad Request: Invalid parameters or missing required fields401 Unauthorized: Invalid or missing authentication token422 Unprocessable Entity: Validation failed (invalid image URL, etc.)500 Internal Server Error: Blockchain transaction failed
Best Practices:
- Use high-quality images (recommended: 1200x1200 pixels, PNG or JPG)
- Include relevant metadata attributes for better searchability
- Store certificate IDs for future reference
- Handle blockchain transaction delays gracefully
Get Certificate
Retrieve detailed information about a specific certificate by its ID.
http
GET /api/certificate/:id
Authorization: Bearer <token>Path Parameters:
id(string, required): Certificate ID (MongoDB ObjectId)
Response (200 OK):
json
{
"success": true,
"data": {
"id": "507f1f77bcf86cd799439011",
"name": "Certificate Name",
"description": "Certificate description",
"imageUrl": "https://ipfs.io/ipfs/QmHash...",
"owner": "0x1234567890123456789012345678901234567890",
"tokenId": "123456",
"burned": false,
"createdAt": "2025-01-15T10:30:00Z",
"transactionHash": "0xabc123...",
"metadata": {
"category": "achievement",
"attributes": [
{ "trait_type": "Level", "value": "Expert" }
]
}
}
}Example:
javascript
const certificateId = '507f1f77bcf86cd799439011';
const response = await fetch(
`https://api.pocketbook.studio/api/certificate/${certificateId}`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
const { data } = await response.json();
console.log('Certificate owner:', data.owner);
console.log('Token ID:', data.tokenId);Error Responses:
400 Bad Request: Invalid certificate ID format401 Unauthorized: Invalid or missing authentication token404 Not Found: Certificate does not exist
Get Certificates by Owner
Retrieve all certificates owned by a specific wallet address.
http
GET /api/certificate/owner/:address
Authorization: Bearer <token>Path Parameters:
address(string, required): Ethereum wallet address
Query Parameters:
page(integer, optional): Page number (default: 1)limit(integer, optional): Items per page (default: 20, max: 100)
Response (200 OK):
json
{
"success": true,
"data": {
"certificates": [
{
"id": "507f1f77bcf86cd799439011",
"name": "Certificate Name",
"description": "Certificate description",
"imageUrl": "https://ipfs.io/ipfs/QmHash...",
"tokenId": "123456",
"burned": false,
"createdAt": "2025-01-15T10:30:00Z",
"metadata": { ... }
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 45,
"totalPages": 3,
"hasMore": true
}
}
}Example:
javascript
const ownerAddress = '0x1234567890123456789012345678901234567890';
const response = await fetch(
`https://api.pocketbook.studio/api/certificate/owner/${ownerAddress}?page=1&limit=20`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
const { data } = await response.json();
console.log(`Total certificates: ${data.pagination.total}`);
data.certificates.forEach(cert => {
console.log(`- ${cert.name} (Token ID: ${cert.tokenId})`);
});Error Responses:
400 Bad Request: Invalid wallet address format401 Unauthorized: Invalid or missing authentication token
Search Enterprise Certificates
Search and filter certificates created by your enterprise account. Requires enterprise plan.
http
GET /api/certificate/search/enterprise
Authorization: Bearer <token>Query Parameters:
query(string, optional): Search term (searches name and description)category(string, optional): Filter by categoryfrom(string, optional): Start date (ISO 8601 format)to(string, optional): End date (ISO 8601 format)page(integer, optional): Page number (default: 1)limit(integer, optional): Items per page (default: 20, max: 100)
Response (200 OK):
json
{
"success": true,
"data": {
"certificates": [
{
"id": "507f1f77bcf86cd799439011",
"name": "Certificate Name",
"description": "Certificate description",
"imageUrl": "https://ipfs.io/ipfs/QmHash...",
"owner": "0x1234...",
"tokenId": "123456",
"burned": false,
"createdAt": "2025-01-15T10:30:00Z",
"metadata": {
"category": "achievement",
"attributes": []
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 150,
"totalPages": 8,
"hasMore": true
},
"filters": {
"query": "web development",
"category": "education",
"from": "2025-01-01T00:00:00Z",
"to": "2025-01-31T23:59:59Z"
}
}
}Example:
javascript
// Search for education certificates created in January 2025
const params = new URLSearchParams({
query: 'web development',
category: 'education',
from: '2025-01-01T00:00:00Z',
to: '2025-01-31T23:59:59Z',
page: '1',
limit: '50'
});
const response = await fetch(
`https://api.pocketbook.studio/api/certificate/search/enterprise?${params}`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
const { data } = await response.json();
console.log(`Found ${data.pagination.total} certificates matching criteria`);Error Responses:
400 Bad Request: Invalid query parameters or date format401 Unauthorized: Invalid or missing authentication token403 Forbidden: Enterprise plan required
Best Practices:
- Use date ranges to limit results and improve performance
- Combine filters for more specific searches
- Cache results when appropriate
- Use pagination for large result sets
Rate Limiting
Certificate endpoints are subject to the following rate limits:
- Authenticated Endpoints: 500 requests per 15 minutes per user
- API Key Endpoints: Custom limits per key (default: 1000/hour)
- Certificate Creation: Additional gas limits may apply
Monitor these headers in responses:
http
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 498
X-RateLimit-Reset: 1640995200When rate limited, you'll receive:
json
{
"success": false,
"error": "Rate limit exceeded",
"retryAfter": 60
}Recommendation: Implement exponential backoff when receiving 429 responses.
Error Handling
All certificate endpoints follow the standard error format:
json
{
"success": false,
"error": "Error message",
"code": "ERROR_CODE",
"message": "Detailed error description"
}Common Error Codes
| Code | Description | Solution |
|---|---|---|
AUTH_REQUIRED | Authentication required | Provide valid token or API key |
AUTH_INVALID | Invalid authentication | Check token validity, re-authenticate if expired |
AUTH_EXPIRED | Token expired | Re-authenticate to get new token |
PERMISSION_DENIED | Insufficient permissions | Verify account has necessary permissions |
INVALID_PARAMETERS | Invalid request parameters | Check request format and required fields |
RESOURCE_NOT_FOUND | Certificate not found | Verify certificate ID is correct |
RATE_LIMIT_EXCEEDED | Too many requests | Implement backoff strategy |
VALIDATION_ERROR | Data validation failed | Fix validation errors in request |
BLOCKCHAIN_ERROR | Blockchain transaction failed | Retry or contact support |
Example Error Handling
javascript
async function createCertificateWithRetry(data, token) {
const maxRetries = 3;
let retries = 0;
while (retries < maxRetries) {
try {
const response = await fetch('https://api.pocketbook.studio/api/certificate/create', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.ok) {
return await response.json();
}
const error = await response.json();
switch (response.status) {
case 401:
// Re-authenticate
token = await authenticate();
continue; // Retry with new token
case 429:
// Rate limited
const retryAfter = error.retryAfter || 60;
await sleep(retryAfter * 1000);
continue; // Retry after delay
case 400:
case 422:
// Bad request - don't retry
throw new Error(`Validation error: ${error.message}`);
case 500:
// Server error - retry with exponential backoff
await sleep(Math.pow(2, retries) * 1000);
retries++;
continue;
default:
throw error;
}
} catch (error) {
if (retries >= maxRetries - 1) {
throw error;
}
retries++;
}
}
throw new Error('Max retries exceeded');
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}Best Practices
1. Image Management
- Resolution: Use high-quality images (recommended: 1200x1200 pixels)
- Format: PNG or JPG formats work best
- Size: Keep images under 5MB for faster uploads
- CDN: Images are automatically uploaded to IPFS for permanent storage
2. Metadata Design
- Attributes: Include relevant metadata for filtering and searching
- Categories: Use consistent category names across your certificates
- Standards: Follow OpenSea metadata standards for better NFT marketplace compatibility
Example metadata structure:
json
{
"category": "education",
"attributes": [
{ "trait_type": "Course", "value": "Web Development 101" },
{ "trait_type": "Instructor", "value": "Jane Doe" },
{ "trait_type": "Completion Date", "value": "2025-01-15" },
{ "trait_type": "Score", "value": "95/100" },
{ "trait_type": "Level", "value": "Beginner" }
]
}3. Performance Optimization
- Bulk Operations: Use Bulk Mint API for creating multiple certificates
- Caching: Cache certificate data that doesn't change frequently
- Pagination: Always use pagination for large result sets
- Rate Limiting: Monitor rate limit headers and implement backoff strategies
4. Security
- Token Storage: Never expose JWT tokens or API keys in client-side code
- HTTPS: Always use HTTPS in production
- Validation: Validate all input data before sending to API
- Error Handling: Don't expose sensitive error details to end users
5. Blockchain Considerations
- Gas Costs: Certificate creation incurs blockchain gas fees
- Confirmation Time: Allow time for blockchain confirmations (typically 30-60 seconds)
- Transaction Failures: Implement retry logic for failed transactions
- Immutability: Remember that minted certificates cannot be modified (only burned)
Code Examples
Complete Certificate Creation Flow
javascript
const PocketbookClient = {
baseUrl: 'https://api.pocketbook.studio/api',
token: null,
async authenticate(wallet) {
const timestamp = Math.floor(Date.now() / 1000);
const message = `Authenticate with Pocketbook: ${timestamp}`;
const signature = await wallet.signMessage(message);
const response = await fetch(`${this.baseUrl}/certificate/auth`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
signature,
address: wallet.address,
message,
timestamp: timestamp.toString()
})
});
const { data } = await response.json();
this.token = data.token;
return data.token;
},
async createCertificate(certificateData) {
const response = await fetch(`${this.baseUrl}/certificate/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(certificateData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
return await response.json();
},
async getCertificate(certificateId) {
const response = await fetch(`${this.baseUrl}/certificate/${certificateId}`, {
headers: {
'Authorization': `Bearer ${this.token}`
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
return await response.json();
},
async getCertificatesByOwner(address, page = 1, limit = 20) {
const response = await fetch(
`${this.baseUrl}/certificate/owner/${address}?page=${page}&limit=${limit}`,
{
headers: {
'Authorization': `Bearer ${this.token}`
}
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
return await response.json();
}
};
// Usage
async function main() {
// 1. Authenticate
await PocketbookClient.authenticate(wallet);
// 2. Create certificate
const certificate = await PocketbookClient.createCertificate({
name: 'Web Development Certification',
description: 'Successfully completed advanced web development course',
imageUrl: 'https://example.com/certificate.png',
metadata: {
category: 'education',
attributes: [
{ trait_type: 'Course', value: 'Advanced Web Development' },
{ trait_type: 'Date', value: '2025-01-15' },
{ trait_type: 'Score', value: '95%' }
]
}
});
console.log('Certificate created:', certificate.data.certificateId);
// 3. Retrieve certificate details
const details = await PocketbookClient.getCertificate(certificate.data.certificateId);
console.log('Certificate details:', details.data);
}Server-Side API Key Usage
javascript
const fetch = require('node-fetch');
const API_KEY = process.env.POCKETBOOK_API_KEY;
const BASE_URL = 'https://api.pocketbook.studio/api';
async function createCertificateWithApiKey(data) {
const response = await fetch(`${BASE_URL}/certificate/create`, {
method: 'POST',
headers: {
'x-api-key': API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API Error: ${error.message}`);
}
return await response.json();
}
// Usage in a server endpoint
app.post('/api/issue-certificate', async (req, res) => {
try {
const result = await createCertificateWithApiKey({
name: req.body.name,
description: req.body.description,
imageUrl: req.body.imageUrl,
metadata: req.body.metadata
});
res.json(result);
} catch (error) {
console.error('Certificate creation failed:', error);
res.status(500).json({ error: error.message });
}
});Related Documentation
- Bulk Mint API - Create multiple certificates efficiently
- Voucher API - Lazy minting with vouchers
- Authentication Guide - Detailed authentication methods
- Webhooks - Real-time certificate event notifications
- API Overview - Complete API reference
Support
Need help with the Certificate API?
- Email: seth@pocketbook.studio
- Discord: Join our community
- GitHub: Report issues
Enterprise customers receive priority support with SLA guarantees.
