From 12141068300cc8b538f129c1b86b1e4509184bf8 Mon Sep 17 00:00:00 2001 From: novadyne-hq Date: Sat, 20 Jun 2026 19:29:31 -1000 Subject: [PATCH 1/7] Add components/attestify/attestify.app.mjs --- components/attestify/attestify.app.mjs | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 components/attestify/attestify.app.mjs diff --git a/components/attestify/attestify.app.mjs b/components/attestify/attestify.app.mjs new file mode 100644 index 0000000000000..872e49cfd2723 --- /dev/null +++ b/components/attestify/attestify.app.mjs @@ -0,0 +1,64 @@ +import { axios } from "@pipedream/platform"; + +// Attestify needs no credentials (free, no signup), so this app declares no auth. +// It exists as the branded namespace + shared HTTP method/prop definitions for the actions. +export default { + type: "app", + app: "attestify", + propDefinitions: { + issuer: { + type: "string", + label: "Organization / Issuer", + description: + "The organization issuing the certificate (shown on the certificate and the public verify page). Example: `Acme Real Estate Academy`.", + }, + course: { + type: "string", + label: "Course / Credential", + description: + "The course or credential being certified. Example: `4-Hour Continuing Education — Ethics`.", + }, + recipientName: { + type: "string", + label: "Recipient Name", + description: "Name of the certificate recipient.", + }, + recipientEmail: { + type: "string", + label: "Recipient Email", + description: + "Optional. Echoed back so you can join it to the verify URL for your mail-merge or LMS. It is **never** stored in the signed record and **never** shown on the public verify page — recipient PII stays on your side.", + optional: true, + }, + completionDate: { + type: "string", + label: "Completion Date", + description: + "Optional. The date the credential was earned, shown on the certificate (format `YYYY-MM-DD`). Leave empty to stamp today.", + optional: true, + }, + }, + methods: { + _baseUrl() { + return "https://attestify.novadyne.ai"; + }, + async _request($, { path, headers, ...opts } = {}) { + return axios($, { + ...opts, + url: `${this._baseUrl()}${path}`, + headers: { + ...headers, + // identify Pipedream-sourced issuances in telemetry (= the demand signal) + "User-Agent": "pipedream-attestify/0.0.1", + }, + }); + }, + async issueCertificate($, data) { + return this._request($, { + method: "POST", + path: "/cert/issue", + data, + }); + }, + }, +}; From 84906c11ecac31a77358f520b56a28c62e85a81c Mon Sep 17 00:00:00 2001 From: novadyne-hq Date: Sat, 20 Jun 2026 19:29:32 -1000 Subject: [PATCH 2/7] Add components/attestify/package.json --- components/attestify/package.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 components/attestify/package.json diff --git a/components/attestify/package.json b/components/attestify/package.json new file mode 100644 index 0000000000000..c162db7a3401f --- /dev/null +++ b/components/attestify/package.json @@ -0,0 +1,21 @@ +{ + "name": "@pipedream/attestify", + "version": "0.0.1", + "description": "Pipedream Attestify Components — issue tamper-evident, cryptographically-verifiable certificates with a permanent public verify page.", + "main": "attestify.app.mjs", + "type": "module", + "keywords": [ + "pipedream", + "attestify", + "certificates", + "verifiable-credentials" + ], + "homepage": "https://pipedream.com/apps/attestify", + "author": "Novadyne", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" + } +} From ccf42c8b7f6903a0df2e7cd284ba808c5783ba4c Mon Sep 17 00:00:00 2001 From: novadyne-hq Date: Sat, 20 Jun 2026 19:29:34 -1000 Subject: [PATCH 3/7] Add components/attestify/README.md --- components/attestify/README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 components/attestify/README.md diff --git a/components/attestify/README.md b/components/attestify/README.md new file mode 100644 index 0000000000000..f23b3cd9fb9de --- /dev/null +++ b/components/attestify/README.md @@ -0,0 +1,7 @@ +# Attestify + +Attestify issues tamper-evident, cryptographically-verifiable certificates. Each certificate gets a +permanent public verify page anyone can check — Ed25519-signed, so it can't be forged after issuance. +Free, no signup required. + +Learn more: https://attestify.novadyne.ai From 2efa9cce68912317b6d5924422b05d2d16473393 Mon Sep 17 00:00:00 2001 From: novadyne-hq Date: Sat, 20 Jun 2026 19:29:35 -1000 Subject: [PATCH 4/7] Add components/attestify/actions/issue-certificate/issue-certificate.mjs --- .../issue-certificate/issue-certificate.mjs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 components/attestify/actions/issue-certificate/issue-certificate.mjs diff --git a/components/attestify/actions/issue-certificate/issue-certificate.mjs b/components/attestify/actions/issue-certificate/issue-certificate.mjs new file mode 100644 index 0000000000000..b11b14d8a13ae --- /dev/null +++ b/components/attestify/actions/issue-certificate/issue-certificate.mjs @@ -0,0 +1,67 @@ +import app from "../../attestify.app.mjs"; + +export default { + key: "attestify-issue-certificate", + name: "Issue Certificate", + description: + "Issue a tamper-evident, cryptographically-verifiable certificate. Each certificate gets a permanent public verify page anyone can check — Ed25519-signed, so it can't be forged after issuance. Free, no signup. [See the docs](https://attestify.novadyne.ai).", + version: "0.0.1", + type: "action", + props: { + app, + issuer: { propDefinition: [app, "issuer"] }, + course: { propDefinition: [app, "course"] }, + recipientName: { propDefinition: [app, "recipientName"] }, + recipientEmail: { propDefinition: [app, "recipientEmail"] }, + completionDate: { propDefinition: [app, "completionDate"] }, + }, + async run({ $ }) { + if (this.completionDate && !/^\d{4}-\d{2}-\d{2}$/.test(this.completionDate)) { + throw new Error( + `Completion Date must be in YYYY-MM-DD format (got "${this.completionDate}")`, + ); + } + + const recipient = { name: this.recipientName }; + if (this.recipientEmail) recipient.email = this.recipientEmail; + + const body = { + issuer: this.issuer, + course: this.course, + recipients: [recipient], + }; + if (this.completionDate) body.date = this.completionDate; + + const resp = await this.app.issueCertificate($, body); + + if (!resp || resp.ok !== true) { + const err = resp?.error ?? "unknown"; + const detail = resp?.detail ? ` — ${resp.detail}` : ""; + throw new Error(`Attestify error: ${err}${detail}`); + } + + const cert = resp.certs && resp.certs[0]; + if (!cert) { + throw new Error( + "Attestify returned no certificate (the recipient may have been empty, or the request was treated as an automated crawler).", + ); + } + + $.export( + "$summary", + `Issued certificate ${cert.cert_id} for ${cert.recipient_name} — verify at ${cert.verify_url}`, + ); + + return { + cert_id: cert.cert_id, + recipient_name: cert.recipient_name, + recipient_email: cert.recipient_email ?? (this.recipientEmail || undefined), + course: cert.course, + issuer: cert.issuer, + completion_date: this.completionDate || undefined, + verify_url: cert.verify_url, + cert_image_url: cert.cert_url, + signed_record_url: cert.json_url, + }; + }, +}; From 23f5c385a696993a9cd2f81b078aabdf0fcc8bb4 Mon Sep 17 00:00:00 2001 From: novadyne-hq Date: Sat, 20 Jun 2026 19:29:36 -1000 Subject: [PATCH 5/7] Add components/attestify/actions/issue-certificate/README.md --- .../actions/issue-certificate/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 components/attestify/actions/issue-certificate/README.md diff --git a/components/attestify/actions/issue-certificate/README.md b/components/attestify/actions/issue-certificate/README.md new file mode 100644 index 0000000000000..fc0ee499de8c2 --- /dev/null +++ b/components/attestify/actions/issue-certificate/README.md @@ -0,0 +1,18 @@ +# Issue Certificate + +Issue a tamper-evident, cryptographically-verifiable certificate with Attestify. Each certificate +gets a permanent public verify page anyone can check — Ed25519-signed, so it can't be forged after +issuance. Free, no signup. + +**Props** +- **Organization / Issuer** (required) — shown on the certificate and the public verify page. +- **Course / Credential** (required) — what the certificate is for. +- **Recipient Name** (required) — name on the certificate. +- **Recipient Email** (optional) — echoed back so you can join it to the verify URL for a mail-merge + or LMS step. Never stored in the signed record and never shown on the public verify page. +- **Completion Date** (optional, `YYYY-MM-DD`) — defaults to today. + +**Returns** the issued certificate: `cert_id`, `verify_url` (the permanent public page), +`signed_record_url` (the Ed25519-signed JSON), `cert_image_url`, plus the echoed fields. + +Docs: https://attestify.novadyne.ai From fc0dadad62cd03c1a7863052c609f51d0dd3e4eb Mon Sep 17 00:00:00 2001 From: novadyne-hq Date: Sat, 20 Jun 2026 19:34:05 -1000 Subject: [PATCH 6/7] Address review: components/attestify/attestify.app.mjs --- components/attestify/attestify.app.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/attestify/attestify.app.mjs b/components/attestify/attestify.app.mjs index 872e49cfd2723..a33f76f10bef0 100644 --- a/components/attestify/attestify.app.mjs +++ b/components/attestify/attestify.app.mjs @@ -34,7 +34,7 @@ export default { type: "string", label: "Completion Date", description: - "Optional. The date the credential was earned, shown on the certificate (format `YYYY-MM-DD`). Leave empty to stamp today.", + "Optional. The date the credential was earned, shown on the certificate (format `YYYY-MM-DD`, for example `2026-06-21`). Leave empty to stamp today.", optional: true, }, }, From b09b4beb06cea47a5cbc7d6f98c787570aac7ffd Mon Sep 17 00:00:00 2001 From: novadyne-hq Date: Sat, 20 Jun 2026 19:34:06 -1000 Subject: [PATCH 7/7] Address review: components/attestify/actions/issue-certificate/issue-certificate.mjs --- .../actions/issue-certificate/issue-certificate.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/attestify/actions/issue-certificate/issue-certificate.mjs b/components/attestify/actions/issue-certificate/issue-certificate.mjs index b11b14d8a13ae..71c4cfe873831 100644 --- a/components/attestify/actions/issue-certificate/issue-certificate.mjs +++ b/components/attestify/actions/issue-certificate/issue-certificate.mjs @@ -4,9 +4,14 @@ export default { key: "attestify-issue-certificate", name: "Issue Certificate", description: - "Issue a tamper-evident, cryptographically-verifiable certificate. Each certificate gets a permanent public verify page anyone can check — Ed25519-signed, so it can't be forged after issuance. Free, no signup. [See the docs](https://attestify.novadyne.ai).", + "Issue a tamper-evident, cryptographically-verifiable certificate. Each certificate gets a permanent public verify page anyone can check — Ed25519-signed, so it can't be forged after issuance. Free, no signup. [See the documentation](https://attestify.novadyne.ai)", version: "0.0.1", type: "action", + annotations: { + readOnlyHint: false, + destructiveHint: false, + openWorldHint: true, + }, props: { app, issuer: { propDefinition: [app, "issuer"] },