API Reference

Three endpoints. Every request is HMAC-SHA256 signed. Bearer tokens are not accepted.

Endpoints

GET/api/public/v1/subjects/{country}/{id_type}/{id_value}scope: read
POST/api/public/v1/subjects/{country}/{id_type}/{id_value}/validatescope: validate
POST/api/public/v1/parsescope: parse

Authentication: HMAC signed requests

Each credential consists of a public Key ID and a 256-bit signing secret. The secret never leaves your servers — it is used only to compute an HMAC signature for each request.

Every request must include these four headers:

x-api-key-id : pjk_<32-hex>
x-timestamp  : <unix seconds>          # must be within ±300s of server time
x-nonce      : <16–128 char random>    # unique per request (replay protection)
x-signature  : hex( HMAC-SHA256( secret, canonical ) )

The canonical string is:

METHOD + "\n" +
PATH   + "\n" +
TIMESTAMP + "\n" +
NONCE + "\n" +
SHA256_HEX( RAW_BODY )      # use SHA256_HEX("") for GET requests

Example: Node.js

import { createHash, createHmac, randomBytes } from "crypto";

const KEY_ID = "pjk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const SECRET = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const HOST   = "https://your-domain.lovable.app";

async function call(method, path, body = "") {
  const ts    = Math.floor(Date.now() / 1000).toString();
  const nonce = randomBytes(16).toString("hex");
  const bodyHash = createHash("sha256").update(body).digest("hex");
  const canonical = [method, path, ts, nonce, bodyHash].join("\n");
  const signature = createHmac("sha256", SECRET).update(canonical).digest("hex");

  const r = await fetch(HOST + path, {
    method,
    headers: {
      "content-type": "application/json",
      "x-api-key-id": KEY_ID,
      "x-timestamp":  ts,
      "x-nonce":      nonce,
      "x-signature":  signature,
    },
    body: body || undefined,
  });
  return r.json();
}

// Look up a subject
await call("GET", "/api/public/v1/subjects/MY/nric/910101015555");

// Validate fields against our record
const body = JSON.stringify({ fields: { full_name: "Ali bin Ahmad", monthly_income: 8500 } });
await call("POST", "/api/public/v1/subjects/MY/nric/910101015555/validate", body);

Security guarantees

  • 256-bit signing secret generated with a CSPRNG, shown once, stored encrypted at rest.
  • HMAC-SHA256 over method, path, timestamp, nonce and body hash — tampering with any of these breaks the signature.
  • ±300s timestamp window + per-key nonce ledger reject replays.
  • Constant-time signature compare prevents timing attacks.
  • Scoped credentials — each key carries explicit scopes (read / validate / parse).
  • Expiry + revocation — keys auto-expire (default 90 days) and can be revoked instantly.
  • Rate limit 60 req/min per credential.
  • Optional IP allowlist per credential.

Errors

  • 401 — missing headers, expired/revoked key, bad signature, replay, or timestamp skew
  • 403 — key lacks the required scope or source IP not allowlisted
  • 429 — rate limit exceeded (60 req/min/key)
  • 400 — malformed body / PDF too large (8 MB max)
  • 502 — extraction engine failure