Proudify
code Developer Docs

API Documentation

Bring the certificates you issue on Proudify into your own website or app: import the full list, look up any certificate, and display it with a link back to official verification.

How it works

You issue certificates in Proudify as usual. The API lets your own website or app pull those certificates in and show them to your visitors, for example a "Our graduates" wall, a student portal, or a certificate page in your mobile app.

sync

1. Import

Your server calls GET /certificates with your token and saves the list (or caches it).

article

2. Show

Your pages render that data. Images, PDFs and QR codes load from public links, no token in the browser.

verified

3. Verify

Visitors click "Verify" and land on the official Proudify page, which confirms the certificate is genuine.

shield

Always call the API from your server (PHP, Node, Python, a serverless function). Never from JavaScript running in a visitor's browser: anyone could copy your token from the page.

Quick start (5 minutes)

  1. Create a token. In Proudify go to Profile → API Tokens, give it a name (e.g. "Website"), click Create Token and copy it. It is shown only once.
  2. Make your first call from a terminal:
    curl https://proudify.in/api/v1/certificates?per_page=5 \
      -H "Authorization: Bearer YOUR_API_TOKEN" \
      -H "Accept: application/json"
  3. Check the response. You get a data array of certificates plus links and meta for paging. Copy any image_url into your browser: it opens the certificate image with no token. That's the link your website will use.

Integration guide

A complete, working pattern: import every certificate, keep it up to date, and render an index and a detail page. Pick your language:

Step 1: Import all certificates

Request pages of 100 until links.next is null. Store each certificate by its uuid so re-running the import updates rows instead of duplicating them.

// sync-certificates.php (run from cron or your framework's scheduler)
$token = getenv('PROUDIFY_TOKEN');
$url   = 'https://proudify.in/api/v1/certificates?per_page=100&sort=updated_at&direction=asc';

// For later runs, only fetch what changed since the last sync:
// $url .= '&updated_since=' . urlencode($lastSyncTime);

while ($url) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => ["Authorization: Bearer $token", 'Accept: application/json'],
    ]);
    $page = json_decode(curl_exec($ch), true);
    curl_close($ch);

    foreach ($page['data'] as $cert) {
        saveCertificate($cert);   // INSERT ... ON DUPLICATE KEY UPDATE on uuid
    }

    $url = $page['links']['next'];   // null on the last page
}
// sync-certificates.js (Node 18+, run on a schedule)
const token = process.env.PROUDIFY_TOKEN;
let url = 'https://proudify.in/api/v1/certificates?per_page=100&sort=updated_at&direction=asc';

// For later runs: url += `&updated_since=${encodeURIComponent(lastSyncTime)}`;

while (url) {
  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
  });
  if (res.status === 429) {                       // rate limited: wait and retry
    await new Promise(r => setTimeout(r, (+res.headers.get('Retry-After') || 60) * 1000));
    continue;
  }
  const page = await res.json();

  for (const cert of page.data) {
    await saveCertificate(cert);                  // upsert by cert.uuid
  }

  url = page.links.next;                          // null on the last page
}

Step 2: Choose what to store

FieldUse it for
uuidYour primary key for upserts.
verification_codeFriendly ID for your own URLs, e.g. /certificates/K7Q2XM4P.
recipient_name, title, date_of_issueCard text, search and sorting on your index page.
statusHide or badge revoked and expired certificates.
image_url, pdf_url, verify_url, embed_urlPublic links your pages use directly.
recipient_emailMatching certificates to logged-in users. Don't show it publicly.

Step 3: Index page (list of certificates)

<?php $certs = db()->query("SELECT * FROM certificates WHERE status = 'active' ORDER BY date_of_issue DESC"); ?>
<div class="cert-grid">
<?php foreach ($certs as $c): ?>
  <a href="/certificates/<?= htmlspecialchars($c['verification_code']) ?>">
    <img src="<?= htmlspecialchars($c['image_url']) ?>" alt="<?= htmlspecialchars($c['title']) ?>" loading="lazy">
    <h3><?= htmlspecialchars($c['recipient_name']) ?></h3>
    <p><?= htmlspecialchars($c['title']) ?></p>
  </a>
<?php endforeach; ?>
</div>
// Express route
app.get('/certificates', async (req, res) => {
  const certs = await db.certificates.findMany({ where: { status: 'active' }, orderBy: { date_of_issue: 'desc' } });
  res.render('certificates/index', { certs });
});

// certificates/index.ejs
<% certs.forEach(c => { %>
  <a href="/certificates/<%= c.verification_code %>">
    <img src="<%= c.image_url %>" alt="<%= c.title %>" loading="lazy">
    <h3><%= c.recipient_name %></h3>
  </a>
<% }) %>

Step 4: Show page (one certificate)

Read it from your database, or fetch it live with GET /certificates/{verification_code} if you don't store data. Either build the page yourself, or drop in the ready-made embed:

<!-- Option A: your own layout -->
<img src="{image_url}" alt="{title} awarded to {recipient_name}">
<a href="{pdf_url}">Download PDF</a>
<a href="{verify_url}" target="_blank" rel="noopener">Verify authenticity</a>

<!-- Option B: one line, styled for you -->
<iframe src="{embed_url}" style="width:100%;max-width:960px;aspect-ratio:1.25;border:0" loading="lazy"></iframe>

Step 5: Keep it in sync

  • Run the Step 1 script on a schedule (hourly or nightly) with updated_since set to the time of the previous successful run. Revoked certificates come through as changes.
  • When a certificate's status becomes revoked, hide it or show a "Revoked" badge on your site.
  • If assets_available is false, your annual membership has lapsed and image/PDF links are paused until you renew. Show a placeholder.
  • Stay under 60 requests per minute. With 100 per page that's 6,000 certificates per minute, so a full import is quick.

FAQ

Can I call the API from JavaScript in the browser?

No. Your token would be visible to every visitor. Call it from your server and pass the results to your pages. The image, PDF, QR and embed links are public, so those are fine in the browser.

Which certificates does my token see?

Only the certificates issued by your own account. Each token belongs to the user who created it.

Do the image links ever change?

No. They are tied to the certificate itself, so you can store them. They stop working only if the certificate is deleted, or are paused while your annual membership is inactive.

How do I revoke a token?

Profile → API Tokens → Revoke. Any app using it gets 401 Unauthenticated straight away. If your account is suspended, all its tokens stop working too (403).

Can I issue certificates through the API?

Not yet. The API is read-only for now; issue certificates from the Proudify dashboard or bulk upload.

Introduction

All API requests are made over HTTPS. The API returns JSON for all responses. Requests and responses are UTF-8 encoded. Timestamps are in ISO 8601 format.

info

The API currently provides read access to certificates you have issued. Write endpoints (issuing, updating, deleting) are on the roadmap.

Authentication

The API uses Bearer token authentication (Laravel Sanctum personal access tokens). Every request must include an Authorization header:

Authorization: Bearer YOUR_API_TOKEN

Generating a token

  1. Sign in to your Proudify account.
  2. Go to Profile → API Tokens.
  3. Enter a name for the token and click Create Token.
  4. Copy the token immediately — it will not be shown again.
warning

Treat API tokens like passwords. Do not embed them in client-side code or commit them to version control. Use environment variables.

Base URL

All endpoints are prefixed with:

https://proudify.in/api/v1

Rate Limiting

The API is rate-limited to 60 requests per minute per token. When you exceed the limit you receive a 429 Too Many Requests response. The following headers are included in every response:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed per window.
X-RateLimit-RemainingRequests remaining in the current window.
Retry-AfterSeconds to wait before retrying (only on 429).

Errors

All error responses follow the same shape:

{
  "message": "Unauthenticated."
}
StatusMeaning
400Bad Request — invalid parameters.
401Unauthorized — missing or invalid token.
403Forbidden — your account is not active (e.g. suspended).
404Not Found — resource does not exist.
422Validation Error — see errors key for details.
429Too Many Requests — rate limit exceeded.
500Server Error — something went wrong on our end.
GET

/certificates

Returns a paginated list of all certificates issued by the authenticated user, sorted newest first.

Query Parameters

ParameterTypeRequiredDescription
per_page integer optional Results per page. Default: 15. Max: 100.
page integer optional Page number to retrieve. Default: 1.
status string optional Only active, expired or revoked certificates.
template_id integer optional Only certificates issued from this template.
search string optional Matches recipient name, email or title, or an exact Credential ID.
issued_from date optional Issued on or after this date (YYYY-MM-DD).
issued_to date optional Issued on or before this date.
updated_since datetime optional Only certificates created or changed (e.g. revoked) since this time. Use for incremental syncs.
sort string optional created_at (default), updated_at or date_of_issue.
direction string optional desc (default) or asc.

Example Request

# List the first 25 certificates
curl https://proudify.in/api/v1/certificates?per_page=25 \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

# Nightly sync: everything changed since the last run
curl "https://proudify.in/api/v1/certificates?updated_since=2025-06-01T00:00:00Z&sort=updated_at&direction=asc&per_page=100" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

{
  "data": [
    {
      "uuid":               "01940e3d-bfa2-7b2e-aef0-c3db35e12abc",
      "title":              "Data Science – Batch 2025",
      "recipient_name":     "Priya Mehta",
      "recipient_email":    "priya@example.com",
      "description":        null,
      "date_of_issue":      "2025-06-01",
      "date_of_expiry":     null,
      "status":             "active",
      "verification_code":  "K7Q2XM4P",
      "verify_url":         "https://proudify.in/certificates/verify/...",
      "template":           { "id": 3, "name": "Classic Navy" },
      "assets_available":   true,
      "image_url":          "https://proudify.in/certificates/verify/.../image",
      "pdf_url":            "https://proudify.in/certificates/verify/.../download",
      "qr_url":             "https://proudify.in/certificates/verify/.../qr",
      "embed_url":          "https://proudify.in/certificates/embed/...",
      "created_at":         "2025-06-01T10:30:00+05:30"
    }
  ],
  "links": {
    "first": "https://proudify.in/api/v1/certificates?page=1",
    "last":  "https://proudify.in/api/v1/certificates?page=4",
    "prev":  null,
    "next":  "https://proudify.in/api/v1/certificates?page=2"
  },
  "meta": {
    "current_page": 1,
    "per_page":     25,
    "total":        87,
    "last_page":    4
  }
}
GET

/certificates/{uuid}

Returns a single certificate. The {uuid} segment accepts either the certificate's UUID or the human-readable Credential ID (e.g. K7Q2XM4P) printed on the certificate.

Path Parameters

ParameterTypeRequiredDescription
uuid string required The certificate UUID or Credential ID.

Example Request

curl https://proudify.in/api/v1/certificates/01940e3d-bfa2-7b2e-aef0-c3db35e12abc \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

# Also works with the Credential ID printed on the certificate
curl https://proudify.in/api/v1/certificates/K7Q2XM4P \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"

Response

{
  "data": {
    "uuid":               "01940e3d-bfa2-7b2e-aef0-c3db35e12abc",
    "title":              "Data Science – Batch 2025",
    "recipient_name":     "Priya Mehta",
    "recipient_email":    "priya@example.com",
    "description":        null,
    "date_of_issue":      "2025-06-01",
    "date_of_expiry":     null,
    "status":             "active",
    "verification_code":  "K7Q2XM4P",
    "verify_url":         "https://proudify.in/certificates/verify/...",
    "created_at":         "2025-06-01T10:30:00+05:30"
  }
}
GET

/certificates/{uuid}/image

Streams the rendered certificate as a PNG image. Returns 404 if the image has not been generated yet.

Example Request

curl https://proudify.in/api/v1/certificates/01940e3d-bfa2-7b2e-aef0-c3db35e12abc/image \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  --output certificate.png
GET

/certificates/{uuid}/download

Downloads the certificate as a PDF file with a Content-Disposition: attachment header. Returns 404 with "The PDF is still being generated." if the file is not ready yet.

Example Request

curl https://proudify.in/api/v1/certificates/01940e3d-bfa2-7b2e-aef0-c3db35e12abc/download \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -L --output "certificate.pdf"
GET

/certificates/{uuid}/qr

Streams the QR code for the certificate as a PNG image. Scanning the QR code opens the public verification page for the credential.

Example Request

curl https://proudify.in/api/v1/certificates/01940e3d-bfa2-7b2e-aef0-c3db35e12abc/qr \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  --output qr.png

Certificate Object

Fields returned inside data for every certificate endpoint.

FieldTypeDescription
uuidstringInternal UUID of the certificate.
titlestringName of the certificate / program.
recipient_namestringFull name of the certificate recipient.
recipient_emailstringEmail address of the recipient.
descriptionstring|nullOptional description or achievement text.
date_of_issuestring (date)Issue date in YYYY-MM-DD format.
date_of_expirystring|nullExpiry date if set, otherwise null.
statusstringCurrent status: active, expired, or revoked.
verification_codestringHuman-readable Credential ID (e.g. K7Q2XM4P).
verify_urlstring (url)Public verification URL for this certificate.
templateobjectid and name of the template it was issued from.
assets_availablebooleanfalse while your annual membership is inactive; the asset URLs below are then null.
image_urlstring|nullPublic PNG/JPG of the certificate. Safe to use directly in an <img src>: no token needed. null while still generating.
pdf_urlstring|nullPublic PDF download. null for revoked or expired certificates.
qr_urlstring|nullPublic QR code image linking to the verify page.
embed_urlstring (url)Ready-made card (image, name, status, verify link) to place in an <iframe>.
created_atstring (ISO 8601)When the certificate record was created.
updated_atstring (ISO 8601)Last change, including revocation. Use with updated_since.

Showing certificates on your website

Call the API from your server (your token stays secret), save or cache the results, then render them in your pages. The image_url, pdf_url, qr_url and embed_url fields are public links, so your visitors' browsers can load them directly without your token.

Option 1: build your own list and detail pages

<!-- Index: one card per certificate from GET /certificates -->
<a href="/certificates/K7Q2XM4P">
  <img src="{image_url}" alt="{title} – {recipient_name}" loading="lazy">
  <p>{recipient_name} · {title}</p>
</a>

<!-- Show: data from GET /certificates/{verification_code} -->
<img src="{image_url}" alt="{title}">
<a href="{pdf_url}">Download PDF</a>
<a href="{verify_url}" target="_blank">Verify</a>

Option 2: drop in the ready-made embed

No styling needed. Add ?image=0 to show only the name, status and verify link.

<iframe src="{embed_url}"
        style="width:100%;max-width:960px;aspect-ratio:1.25;border:0"
        loading="lazy" title="Certificate"></iframe>
info Revoked and expired certificates keep their image but show the new status, so re-sync with updated_since (for example nightly) to keep your pages current.

Ready to build?

Generate your API token from the Profile page and start making requests.

Create Free Account arrow_forward