Skip to content

Certificate API

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:

  1. JWT Token (recommended for web apps):

    Authorization: Bearer <token>
  2. 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/json

Request Body:

json
{
  "signature": "0x...",
  "address": "0x1234567890123456789012345678901234567890",
  "message": "Authenticate with Pocketbook: 1234567890",
  "timestamp": "1234567890"
}

Parameters:

  • signature (string, required): Wallet signature of the message
  • address (string, required): Ethereum wallet address
  • message (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 timestamp
  • 401 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/json

Request 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 fields
  • 401 Unauthorized: Invalid or missing authentication token
  • 422 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 format
  • 401 Unauthorized: Invalid or missing authentication token
  • 404 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 format
  • 401 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 category
  • from (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 format
  • 401 Unauthorized: Invalid or missing authentication token
  • 403 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: 1640995200

When 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

CodeDescriptionSolution
AUTH_REQUIREDAuthentication requiredProvide valid token or API key
AUTH_INVALIDInvalid authenticationCheck token validity, re-authenticate if expired
AUTH_EXPIREDToken expiredRe-authenticate to get new token
PERMISSION_DENIEDInsufficient permissionsVerify account has necessary permissions
INVALID_PARAMETERSInvalid request parametersCheck request format and required fields
RESOURCE_NOT_FOUNDCertificate not foundVerify certificate ID is correct
RATE_LIMIT_EXCEEDEDToo many requestsImplement backoff strategy
VALIDATION_ERRORData validation failedFix validation errors in request
BLOCKCHAIN_ERRORBlockchain transaction failedRetry 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 });
  }
});


Support

Need help with the Certificate API?

Enterprise customers receive priority support with SLA guarantees.

Released under the MIT License.