REST · v1
API Reference
Integrá Rootproof con tus sistemas para emitir, verificar y revocar certificados digitales atestados en blockchain.
Overview
Rootproof es una API REST para emitir, verificar y revocar certificados digitales atestados en blockchain (Rootstock / EAS).
- • Base URL:
https://rootproof.io - • Formato: JSON · UTF-8
- • Autenticación: Bearer token (API key) en header
Authorization - • Rate limits: 5 requests / minuto / IP en endpoints de emisión y revocación
Autenticación
Las integraciones programáticas usan API keys por organización. Genera una key desde Dashboard → Perfil → API Keys. La key plana se muestra una sola vez al crearla — guárdala en un gestor de secretos.
Authorization: Bearer rp_live_a1b2c3d4e5f6...Las keys son por org y solo desbloquean el endpoint de emisión. Operaciones de configuración (firmas, plantilla, perfil, créditos) requieren login interactivo.
Emitir certificados
/api/org/{slug}/generateBearer · 5/minCrea uno o muchos certificados (hasta 50 por request). Cada uno consume 1 crédito de la organización.
Body — un certificado:
{
"certificateId": "CERT-2026-001",
"recipientName": "María González",
"programName": "Blockchain 101",
"issuerName": "Universidad Rootproof",
"issueDate": "2026-04-22",
"recipientEmail": "maria@example.com", // opcional, dispara email
"recipientWallet": "0xAbc…", // opcional, on-chain attestation target
"validUntil": "2027-04-22", // opcional
"signatureIds": ["sig_uuid_1"] // opcional, override de plantilla
}Body — batch (array):
[
{ "certificateId": "CERT-001", "recipientName": "...", ... },
{ "certificateId": "CERT-002", "recipientName": "...", ... }
]Respuesta 200:
{
"ok": true,
"creditsRemaining": 47,
"results": [
{
"certificateId": "CERT-2026-001",
"status": "OK",
"verificationUrl": "https://rootproof.io/verify?id=CERT-2026-001",
"pdfUrl": "https://blob…/CERT-2026-001.pdf",
"pdfHash": "9F3A…",
"attestationUID": "0x4a7f…",
"txHash": "0xe1c…",
"attestationUrl": "https://explorer…",
"explorerTxUrl": "https://explorer…"
}
]
}Si status === "ERROR" en alguna fila, el resto del batch sigue procesándose y los créditos solo se descuentan por los exitosos.
Códigos de error:
- •
401API key inválida o ausente - •
402Créditos insuficientes - •
400Batch > 50 o campos inválidos - •
429Rate limit excedido (headerRetry-After)
Verificar certificados
/api/public/verify?id={certificateId}Public · 60/minDevuelve el estado público de un certificado. CORS abierto.
{
"verified": true,
"type": "certificate",
"id": "CERT-2026-001",
"title": "Blockchain 101",
"recipient": "María González",
"issuer": "Universidad Rootproof",
"certifiedDate": "2026-04-22",
"verificationUrl": "https://rootproof.io/verify?id=CERT-2026-001",
"revoked": false,
"expired": false,
"blockchain": {
"network": "rootstock",
"attestationUID": "0x…",
"txHash": "0x…",
"attestationUrl": "https://explorer…",
"explorerTxUrl": "https://explorer…"
}
}verified=false cuando el cert está revocado, vencido o no existe. Consultá revoked y expired para diferenciarlos.
/api/public/verify?hash={sha256_hex}Public · 60/minVerificación por hash SHA-256 del PDF (64 caracteres hex). Útil para clientes que ya tienen el archivo localmente.
/api/public/issuer-info?name={issuer}Public · 60/minDevuelve verified, profileUrl y logoUrl de un emisor. Útil para mostrar el badge "Verified Issuer" al lado del nombre.
/api/cert/{certificateId}/openbadgePublic · 60/minExporta el certificado como Open Badges 3.0 (IMS Global / 1EdTech) — formato estándar que ingieren LMS (Moodle, Canvas, Blackboard), credential wallets (Walt.id, ProofSpace, Learner Wallet), servicios de badges (Credly, Badgr/CanvasBadges) y validadores oficiales como imsglobal.org/badge-validator.
El JSON-LD respeta tanto W3C VC 2.0 como el contexto OB 3.0:
{
"@context": [
"https://www.w3.org/ns/credentials/v2",
"https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json"
],
"id": "https://rootproof.io/api/cert/CERT-2026-001/openbadge",
"type": ["VerifiableCredential", "OpenBadgeCredential"],
"name": "Blockchain 101",
"issuer": {
"id": "did:web:rootproof.io:org:acme",
"type": ["Profile"],
"name": "ACME University",
"url": "https://acme.example",
"image": { "id": "https://...", "type": "Image" }
},
"validFrom": "2026-04-22T00:00:00.000Z",
"validUntil": "2027-04-22T23:59:59.999Z",
"credentialSubject": {
"type": ["AchievementSubject"],
"name": "María González",
"identifier": [
{ "type": "IdentityObject", "identityType": "emailAddress",
"hashed": true, "identityHash": "sha256$…", "salt": "…" }
],
"achievement": {
"id": "https://rootproof.io/cert/CERT-2026-001#achievement",
"type": ["Achievement"],
"achievementType": "Certificate",
"name": "Blockchain 101",
"description": "...",
"criteria": { "narrative": "..." },
"image": { "id": "https://blob.../cert.pdf", "type": "Image" }
}
},
"credentialStatus": {
"id": "https://rootproof.io/api/cert/CERT-2026-001/status",
"type": "1EdTechRevocationList"
},
"evidence": [{
"id": "https://rootproof.io/verify?id=CERT-2026-001",
"type": ["Evidence"],
"name": "On-chain attestation",
"narrative": "SHA-256(PDF) = ...\nAttestation UID = 0x..."
}],
"https://rootproof.io/ns#blockchainProof": {
"blockchain": "rootstock",
"attestationUID": "0x…",
"txHash": "0x…",
"verificationUrl":"https://rootproof.io/verify?id=CERT-2026-001"
}
}Por defecto sirve con Content-Disposition: attachment (descarga). Pasa ?format=inlinepara previsualizar en el browser. El email del destinatario nunca se expone en plano: se hashea con SHA-256 + salt aleatorio por credencial.
Revocar certificados
/api/org/{slug}/certificates/{certId}/revokeCookie · 5/minRevoca un certificado on-chain (EAS) y off-chain (registro). Sólo desde el dashboard interactivo.
{ "reason": "Datos incorrectos en el certificado" }Respuesta:
{
"ok": true,
"onChainRevoked": true,
"certificate": { /* certificado completo con revokedAt */ }
}Certificación de documentos
Además de certificados (con destinatario), Rootproof certifica documentos arbitrarios: subes el archivo, calculamos el hash SHA-256 y atestamos en blockchain. La verificación es por hash o por documentId.
/api/certify-docOTP · 5/minCrea un documento certificado. Requiere verificación previa por OTP del email del propietario.
Flujo:
- Calcular SHA-256 del archivo en el cliente.
- POST
/api/send-otpcon{ email } - POST
/api/verify-otpcon{ email, code }→ recibeotpToken - POST
/api/certify-doccon el documento y elotpToken
Body:
{
"ownerName": "ACME Inc.",
"ownerEmail": "legal@acme.com",
"ownerType": "organization", // "person" | "organization"
"documentTitle": "Contrato de servicios 2026",
"documentHash": "9F3A4B…", // SHA-256 hex (64 chars)
"fileSize": 48213, // opcional, bytes
"otpToken": "<token-de-verify-otp>"
}Respuesta 200:
{
"ok": true,
"document": {
"documentId": "DOC-2026-001",
"verificationUrl": "https://rootproof.io/doc/DOC-2026-001",
"attestationUID": "0x…",
"txHash": "0x…"
}
}/api/verify-doc?hash={sha256_hex}Public · 60/minVerifica un documento por hash. CORS abierto.
{
"found": true,
"documentId": "DOC-2026-001",
"documentTitle": "Contrato de servicios 2026",
"ownerName": "ACME Inc.",
"ownerType": "organization",
"issueDate": "2026-04-22",
"attestationUID": "0x…",
"txHash": "0x…",
"attestationUrl": "https://explorer…",
"explorerTxUrl": "https://explorer…"
}/api/public/verify?hash={sha256_hex}Public · 60/minEndpoint unificado: verifica certs y documentos por id o hash desde el mismo endpoint público.
/api/doc-pdf/{documentId}Public · 10/minDescarga un PDF de comprobante con el hash y los datos on-chain del documento.
Por privacidad, el contenido del documento nunca se sube a Rootproof — solo el hash. Si perdés el original, no se puede reconstruir.
Webhooks
Configura una URL HTTPS en Dashboard → Perfil → Webhooks y la recibirás un POST por cada evento.
Eventos:
- •
cert.issued— certificado emitido (UI o API) - •
cert.revoked— certificado revocado
Payload:
{
"event": "cert.issued",
"timestamp": "2026-04-22T12:34:56.789Z",
"org": { "slug": "acme", "name": "ACME Inc." },
"data": {
"certificateId": "CERT-2026-001",
"recipientName": "María González",
"programName": "Blockchain 101",
"verificationUrl": "https://rootproof.io/verify?id=CERT-2026-001",
"pdfUrl": "https://blob…/CERT-2026-001.pdf",
"attestationUID": "0x…",
"txHash": "0x…",
"pdfHash": "9F3A…"
}
}Verificación de firma:
Si configuras un secreto (≥16 chars) recibes el header X-Rootproof-Signature:
X-Rootproof-Signature: t=1714000000000,v1=9f3a4b…import crypto from "node:crypto";
function isValid(rawBody, signatureHeader, secret) {
const [tPart, v1Part] = signatureHeader.split(",");
const t = tPart.split("=")[1];
const v1 = v1Part.split("=")[1];
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(v1, "hex"),
Buffer.from(expected, "hex")
);
}Timeout: 8s. Si tu endpoint falla, el evento se registra en el audit log con el código HTTP. No reintentamos automáticamente — usá cert.revoked + el endpoint público /api/public/verify para reconciliación si se pierde un evento.
Ejemplos
cURL — emisión simple:
curl -X POST https://rootproof.io/api/org/acme/generate \
-H "Authorization: Bearer rp_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"certificateId": "CERT-2026-001",
"recipientName": "María González",
"programName": "Blockchain 101",
"issuerName": "ACME University",
"issueDate": "2026-04-22",
"recipientEmail": "maria@example.com"
}'Node — batch:
const r = await fetch("https://rootproof.io/api/org/acme/generate", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.ROOTPROOF_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(students.map((s, i) => ({
certificateId: `CERT-${i.toString().padStart(4, "0")}`,
recipientName: s.name,
recipientEmail: s.email,
programName: "Programa Anual 2026",
issuerName: "ACME University",
issueDate: "2026-12-15",
}))),
});
const { ok, results, creditsRemaining } = await r.json();Python — verificación:
import requests
r = requests.get("https://rootproof.io/api/public/verify",
params={"id": "CERT-2026-001"})
data = r.json()
if data["verified"]:
print(f"OK: {data['recipient']} — {data['title']}")
else:
if data.get("revoked"): print("Revocado")
elif data.get("expired"): print("Vencido")
else: print("No existe")