Certificate API

Reference documentation and a live tester for the internal certificate service.

Connection settings
No token set. Use the Authentication tester below to get one.

Overview

This API serves certificate records for two views in Postgres and builds links to the external certificate viewer. There are two certificate types.

TypeSource viewMeaning
ujikomassesment_certificates_viewCompetency assessment certificates
pklcertificateviewInternship (Praktik Kerja Lapangan) certificates

All routes below except /health and /auth/token require a Bearer token in the Authorization header.

Authentication

Exchange the shared client credentials for a token once. The token stays valid for 30 days by default, set by JWT_EXPIRES_IN in the API's environment. Store it and reuse it for every request until it expires.

  1. Send clientId and clientSecret to POST /auth/token.
  2. Save the returned token value.
  3. Attach it as Authorization: Bearer <token> on every other request.
  4. If a request returns 401, request a new token and try again.
GET /health No auth

Checks that the server is running and can reach the database.

curl http://localhost:3000/health
Example response
{
  "status": "ok",
  "database": true
}
Try it
POST /auth/token No auth

Exchanges client credentials for a Bearer token.

FieldTypeRequiredDescription
clientIdstringRequiredMatches CLIENT_ID in the server env.
clientSecretstringRequiredMatches CLIENT_SECRET in the server env.
curl -X POST http://localhost:3000/auth/token \
  -H "Content-Type: application/json" \
  -d '{"clientId":"internal-frontend-team","clientSecret":"your-secret"}'
Example response
{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "tokenType": "Bearer",
  "expiresIn": "30d"
}
Try it
GET /api/certificates/:type Requires token

Lists certificates from the chosen view, with search and pagination.

ParamTypeRequiredDescription
type (path)stringRequiredujikom or pkl.
searchstringOptionalMatches against name or student_id.
pageintegerOptionalDefaults to 1.
limitintegerOptionalDefaults to 20, max 100.
curl "http://localhost:3000/api/certificates/pkl?search=Arfa&page=1&limit=20" \
  -H "Authorization: Bearer <token>"
Example response
{
  "data": [
    { "document_id": "...", "student_id": "...", "name": "Arfa Nurzahra" }
  ],
  "meta": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
}
Try it
GET /api/certificates/:type/:documentId Requires token

Returns a single certificate record by document_id.

ParamTypeRequiredDescription
type (path)stringRequiredujikom or pkl.
documentId (path)stringRequiredThe record's document_id.
curl "http://localhost:3000/api/certificates/pkl/002/JL-DIR-EXAMPLE" \
  -H "Authorization: Bearer <token>"
Example response
{ "data": { "document_id": "002/JL-DIR-EXAMPLE", "name": "Arfa Nurzahra" } }
Try it
GET /api/certificates/:type/:documentId/certificate-url Requires token

Looks up the record and returns the built external certificate URL, without fetching it.

ParamTypeRequiredDescription
type (path)stringRequiredujikom or pkl.
documentId (path)stringRequiredThe record's document_id.
curl "http://localhost:3000/api/certificates/pkl/002/JL-DIR-EXAMPLE/certificate-url" \
  -H "Authorization: Bearer <token>"
Example response
{
  "documentId": "002/JL-DIR-EXAMPLE",
  "name": "Arfa Nurzahra",
  "type": "pkl",
  "url": "https://sertifikat.lontarlab.com/certificate/item/Arfa+Nurzahra/pkl"
}
Try it
GET /api/certificates/:type/:documentId/certificate-image Requires token

Fetches the certificate source server side and returns it as is. Note the source is an HTML page rendered client side, not a static image file, so this returns HTML, not a picture.

ParamTypeRequiredDescription
type (path)stringRequiredujikom or pkl.
documentId (path)stringRequiredThe record's document_id.
curl "http://localhost:3000/api/certificates/pkl/002/JL-DIR-EXAMPLE/certificate-image" \
  -H "Authorization: Bearer <token>" \
  --output certificate.html
Try it

Embedding certificates via iframe

How to show the certificate itself on your own frontend, without going through the API.

The certificate source at sertifikat.lontarlab.com is not a static image file. It is a page rendered by its own JavaScript, with front and back tabs and a download button built in. Because of that, the right way to show it on your client is to embed the real page in an iframe rather than to request a picture from it.

This means you do not need to call certificate-image for display purposes. You only need the record's name, which you already get from the list or detail endpoints, plus the certificate type.

1. Build the URL on the client

The same rule the API uses internally: spaces in the name become +, then the type is appended at the end.

function buildCertificateUrl(name, type) {
  const encodedName = name
    .trim()
    .split(/\s+/)
    .map((part) => encodeURIComponent(part))
    .join("+");

  return "https://sertifikat.lontarlab.com/certificate/item/" + encodedName + "/" + type;
}

// type is "pkl" or "ujikom"
const url = buildCertificateUrl("Arfa Nurzahra", "pkl");
// https://sertifikat.lontarlab.com/certificate/item/Arfa+Nurzahra/pkl

2. Embed it directly

A plain iframe pointed at that URL is enough for a full sized view. The site's own front, back, and download controls keep working inside the frame.

<div style="width: 100%; height: 600px; border: 1px solid #ddd; border-radius: 8px; overflow: hidden;">
  <iframe
    src="https://sertifikat.lontarlab.com/certificate/item/Arfa+Nurzahra/pkl"
    style="width: 100%; height: 100%; border: 0;"
    title="Certificate for Arfa Nurzahra"
  ></iframe>
</div>

3. Small thumbnail in a list or grid

To show many certificates at once without loading full sized pages, render the iframe larger than its visible box and scale it down with CSS, then clip the overflow. Add loading="lazy" so the browser skips loading cards that are off screen.

<style>
  .cert-thumb {
    position: relative;
    width: 220px;
    height: 150px;
    overflow: hidden;
    border-radius: 8px;
    border: 1px solid #ddd;
  }
  .cert-thumb iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 400%;
    height: 400%;
    border: 0;
    transform: scale(0.25);
    transform-origin: top left;
    pointer-events: none; /* clicks pass through to a wrapper, not the frame */
  }
</style>

<div class="cert-thumb" onclick="openFullCertificate('https://sertifikat.lontarlab.com/certificate/item/Arfa+Nurzahra/pkl')">
  <iframe
    src="https://sertifikat.lontarlab.com/certificate/item/Arfa+Nurzahra/pkl"
    loading="lazy"
    title="Certificate preview"
  ></iframe>
</div>

Clicking the thumbnail can then open the same URL full sized in a modal, or in a new tab.

One thing to check first

Embedding only works if the certificate site allows itself to be framed. If it sends an X-Frame-Options or Content-Security-Policy: frame-ancestors header that blocks embedding, the iframe will stay blank instead of showing the page. Since this is your own team's site, that setting is usually something you can adjust on that end. If it cannot be changed, the fallback is a server side headless browser that screenshots the rendered certificate into a real image file, which is a heavier setup than the iframe approach above.

Try it