Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions components/emboss/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Overview

[Emboss](https://getemboss.ai) turns flat PDFs into fillable forms and fills
them with AI. The Emboss API detects the fields on any flat PDF — text
fields, checkboxes, signatures, tables — and can pre-fill them from context
documents you provide. These components wrap the three core operations:
create a fillable form, fill a PDF from context in one step, and fill a form
you already created.

# Example Use Cases

- **Intake automation** — a client uploads a government or insurance PDF;
the workflow fills it from your CRM record and files the completed copy.
- **Document generation at scale** — fill the same permit or application
form for hundreds of records from a spreadsheet or database.
- **Email-to-filled-form** — trigger on an inbound attachment, fill it from
the email body as context, and send the completed PDF back.

# Getting Started

1. Create an Emboss account at [getemboss.ai](https://getemboss.ai) and
generate an API key from the dashboard.
2. Connect your Emboss account in Pipedream by pasting the API key.
3. Add one of the Emboss actions to your workflow. Form processing is
asynchronous — the action polls until the PDF is ready, then returns the
file path.

# Troubleshooting

- **"not a readable PDF"** — the File/URL you mapped must point at the PDF
bytes, not a filename string. Map the upstream step's file URL or `/tmp`
path.
- **Job failed with a detail message** — the Emboss API reports per-job
errors verbatim; the action surfaces them as the step error.
- **Long-running forms** — large PDFs can take a few minutes; the action
re-checks every 5 seconds for up to ~12 minutes, then errors so the workflow
never proceeds silently.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- **Step times out while polling** — raise the workflow's execution timeout
(Settings → Execution Controls) to at least 750 seconds; form processing takes
minutes and the default timeout can cut the poll short.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import FormData from "form-data";
import { ConfigurationError } from "@pipedream/platform";
import emboss from "../../emboss.app.mjs";
import {
resolveFileRef, writePdf, errorDetail,
} from "../../common/utils.mjs";

const POLL_DELAY_MS = 5000;
const MAX_RETRIES = 144;

export default {
key: "emboss-create-fillable-form",
name: "Create Fillable Form",
description: "Turn a flat (non-fillable) PDF into a fillable form. Emboss detects text fields, checkboxes, signatures, and tables, and returns a fillable PDF plus a form ID. Use **Fill Existing Form** to fill it later, or **Fill PDF From Context** to detect and fill in one step. Polls up to ~12 minutes for large documents. [See the documentation](https://getemboss.ai/docs)",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description doc link is a generic 'See the documentation' (redirects to quickstart) rather than deep-linking to the relevant reference page. House rules require deep-linked doc URLs. Applies to all three actions (fill-existing-form and fill-from-pdf-context should point to /docs/reference/fill-with-context; create-fillable-form to /docs/reference/create-form).-> All three action description fields end with [See the documentation](https://getemboss.ai/docs); deep pages exist, e.g. https://getemboss.ai/docs/reference/fill-with-context and https://getemboss.ai/docs/reference/create-form.

version: "0.0.1",
type: "action",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: false,
},
props: {
emboss,
file: {
type: "string",
label: "File Path Or Url",
description: "Provide either a file URL or a path to a file in the `/tmp` directory (for example, `/tmp/form.pdf`).",
format: "file-ref",
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add optional props "Idempotency-Key" and "callback_url"

syncDir: {
type: "dir",
accessMode: "read-write",
sync: true,
},
},
async run({ $ }) {
const {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The action's core artifact - a downloadable 'fillable PDF' via getFillablePdf() → GET /forms/{formId}/fillable - cannot be verified. The Emboss API reference documents POST /forms (explicitly 'Detect fields only (no fill)') and GET /forms/{id} and GET /forms/{id}/contract, but NO /forms/{id}/fillable endpoint anywhere. POST /forms produces a structured field contract (JSON), not a fillable interactive PDF, so both the endpoint and the action's stated output shape are unconfirmed against the docs. Confirm this endpoint exists (it may be undocumented) or rework the action around the documented contract/session model.-> https://getemboss.ai/docs/reference/create-form (documents only POST /forms, GET /forms/{id}); https://getemboss.ai/docs/reference/get-contract (GET /forms/{id}/contract returns field JSON, not a PDF); quickstart 'Pick your endpoint' table: 'Detect fields only (no fill) | POST /forms'. No /forms/{id}/fillable in any API Reference page.

runs, context,
} = $.context.run;
if (runs === 1) {
if (!this.file) {
throw new ConfigurationError("File Path Or Url is required.");
}
const f = await resolveFileRef(this.file, "form.pdf");
const form = new FormData();
form.append("file", f.stream, {
filename: f.filename,
contentType: "application/pdf",
knownLength: f.size,
});
const created = await this.emboss.createForm({
$,
headers: form.getHeaders(),
data: form,
maxBodyLength: Infinity,
});
$.flow.rerun(POLL_DELAY_MS, {
form_id: created.form_id,
}, MAX_RETRIES);
$.export("$summary", `Processing PDF — Emboss form \`${created.form_id}\` queued`);
return;
}
const { form_id: formId } = context;
const status = await this.emboss.getForm({
$,
formId,
});
if (status.status === "failed") {
throw new Error(`Emboss create failed: ${errorDetail(status.error)}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use configuration Error to throw the error status

}
if (status.status !== "ready") {
if (runs >= MAX_RETRIES) {
throw new Error("Emboss job still processing after the polling limit (~12 minutes) — re-run with a smaller PDF or check the job in your Emboss dashboard.");
}
$.flow.rerun(POLL_DELAY_MS, {
form_id: formId,
}, MAX_RETRIES);
$.export("$summary", `Still processing form \`${formId}\` (check ${runs} of ${MAX_RETRIES})`);
return;
}
const pdf = await this.emboss.getFillablePdf({
$,
formId,
});
const { filepath } = await writePdf(Buffer.from(pdf), `emboss-${formId}.pdf`);
$.export("$summary", `Successfully created fillable form \`${formId}\``);
return {
form_id: formId,
status: "ready",
filepath,
};
},
};
113 changes: 113 additions & 0 deletions components/emboss/actions/fill-existing-form/fill-existing-form.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import FormData from "form-data";
import { ConfigurationError } from "@pipedream/platform";
import emboss from "../../emboss.app.mjs";
import {
resolveFileRef, contextParts, writePdf, errorDetail,
} from "../../common/utils.mjs";

const POLL_DELAY_MS = 5000;
const MAX_RETRIES = 144;

export default {
key: "emboss-fill-existing-form",
name: "Fill Existing Form",
description: "Fill a form you previously created in Emboss, using context text and/or a context file; Emboss populates the detected fields from your context and returns the completed PDF. Create the form first with **Create Fillable Form**. Provide at least one context input. Polls up to ~12 minutes. [See the documentation](https://getemboss.ai/docs)",
version: "0.0.1",
type: "action",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: false,
},
props: {
emboss,
formId: {
propDefinition: [
emboss,
"formId",
],
},
contextText: {
type: "string",
label: "Context (Text)",
description: "Information to fill the form with, e.g. `Applicant: Jane Doe, 38 Birchwood Court, Mooresville, IN — phone (317) 555-0126`.",
optional: true,
},
contextFile: {
type: "string",
label: "Context File",
description: "A URL or `/tmp` path to a context document (PDF, DOCX, CSV, image, or text), e.g. `/tmp/customer-data.pdf` or `https://example.com/invoice.pdf`.",
format: "file-ref",
optional: true,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
syncDir: {
type: "dir",
accessMode: "read-write",
sync: true,
},
},
async run({ $ }) {
const {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the context job reaches ready (which already yields a session_id), the code calls fillSession() (POST /sessions/{id}/fill) before downloading. The documented with-context flow (quickstart step 3) downloads directly from GET /sessions/{sid}/pdf after the job is ready, and POST /sessions/{id}/fill returns 409 'Session already completed' if the session is already rendered - which would make @pipedream/platform axios throw and fail the action. Verify the with-context session is left un-rendered (requiring the fill call); if it is auto-rendered, this extra call risks a 409. Same pattern in fill-from-pdf-context.mjs.-> https://getemboss.ai/docs/quickstart step 3 downloads via GET /sessions/{sid}/pdf immediately after job status=ready; https://getemboss.ai/docs/reference/sessions POST /sessions/{id}/fill → '409 Session already completed, or the form has no fillable PDF'.

runs, context,
} = $.context.run;
if (runs === 1) {
if (!this.contextText && !this.contextFile) {
throw new ConfigurationError("Provide Context (Text) and/or a Context File — at least one is required to fill the form.");
}
const cf = await resolveFileRef(this.contextFile, "context");
const form = new FormData();
for (const p of contextParts(this.contextText, cf)) {
form.append("context", p.value, {
filename: p.filename,
contentType: p.contentType,
knownLength: p.knownLength,
});
}
const created = await this.emboss.fillExistingForm({
$,
formId: this.formId,
headers: form.getHeaders(),
data: form,
maxBodyLength: Infinity,
});
$.flow.rerun(POLL_DELAY_MS, {
job_id: created.job_id,
}, MAX_RETRIES);
$.export("$summary", `Filling form — Emboss job \`${created.job_id}\` queued`);
return;
}
const { job_id: jobId } = context;
const status = await this.emboss.getContextJob({
$,
jobId,
});
if (status.status === "failed") {
throw new Error(`Emboss fill failed: ${errorDetail(status.error)}`);
}
if (status.status !== "ready") {
if (runs >= MAX_RETRIES) {
throw new Error("Emboss job still processing after the polling limit (~12 minutes) — re-run with a smaller PDF or check the job in your Emboss dashboard.");
}
$.flow.rerun(POLL_DELAY_MS, {
job_id: jobId,
}, MAX_RETRIES);
$.export("$summary", `Still filling (check ${runs} of ${MAX_RETRIES})`);
return;
}
await this.emboss.fillSession({
$,
sessionId: status.session_id,
});
const pdf = await this.emboss.getSessionPdf({
$,
sessionId: status.session_id,
});
const { filepath } = await writePdf(Buffer.from(pdf), `emboss-${status.session_id}.pdf`);
$.export("$summary", `Successfully filled form \`${this.formId}\` (session \`${status.session_id}\`)`);
return {
session_id: status.session_id,
report: status.report || {},
filepath,
};
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import FormData from "form-data";
import { ConfigurationError } from "@pipedream/platform";
import emboss from "../../emboss.app.mjs";
import {
resolveFileRef, contextParts, writePdf, errorDetail,
} from "../../common/utils.mjs";

const POLL_DELAY_MS = 5000;
const MAX_RETRIES = 144;

export default {
key: "emboss-fill-from-pdf-context",
name: "Fill PDF From Context",
description: "Upload a flat (non-fillable) PDF plus context (text and/or a file); Emboss detects the fields and fills them with AI in one step, returning the completed PDF. Provide at least one context input. Use **Create Fillable Form** + **Fill Existing Form** instead to reuse the same form repeatedly. Polls up to ~12 minutes for large documents. [See the documentation](https://getemboss.ai/docs)",
version: "0.0.1",
type: "action",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: false,
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
props: {
emboss,
file: {
type: "string",
label: "File Path Or Url",
description: "Provide either a file URL or a path to a file in the `/tmp` directory (for example, `/tmp/form.pdf`).",
format: "file-ref",
},
contextText: {
type: "string",
label: "Context (Text)",
description: "Information to fill the form with, e.g. `Applicant: Jane Doe, 38 Birchwood Court, Mooresville, IN — phone (317) 555-0126`.",
optional: true,
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
contextFile: {
type: "string",
label: "Context File",
description: "A URL or `/tmp` path to a context document (PDF, DOCX, CSV, image, or text), e.g. `/tmp/customer-data.pdf` or `https://example.com/invoice.pdf`.",
format: "file-ref",
optional: true,
},
syncDir: {
type: "dir",
accessMode: "read-write",
sync: true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add optional: true to syncDir. Keep accessMode: "read-write"; the
prop is typically optional, and leaving that flag out can make the sync
directory required in the UI.

},
},
async run({ $ }) {
const {
runs, context,
} = $.context.run;
if (runs === 1) {
if (!this.file) {
throw new ConfigurationError("File Path Or Url is required.");
}
if (!this.contextText && !this.contextFile) {
throw new ConfigurationError("Provide Context (Text) and/or a Context File — at least one is required to fill the form.");
}
const f = await resolveFileRef(this.file, "form.pdf");
const cf = await resolveFileRef(this.contextFile, "context");
const form = new FormData();
form.append("file", f.stream, {
filename: f.filename,
contentType: "application/pdf",
knownLength: f.size,
});
for (const p of contextParts(this.contextText, cf)) {
form.append("context", p.value, {
filename: p.filename,
contentType: p.contentType,
knownLength: p.knownLength,
});
}
const created = await this.emboss.createWithContext({
$,
headers: form.getHeaders(),
data: form,
maxBodyLength: Infinity,
});
$.flow.rerun(POLL_DELAY_MS, {
job_id: created.job_id,
}, MAX_RETRIES);
$.export("$summary", `Filling form — Emboss job \`${created.job_id}\` queued`);
return;
}
const { job_id: jobId } = context;
const status = await this.emboss.getContextJob({
$,
jobId,
});
if (status.status === "failed") {
throw new Error(`Emboss fill failed: ${errorDetail(status.error)}`);
}
if (status.status !== "ready") {
if (runs >= MAX_RETRIES) {
throw new Error("Emboss job still processing after the polling limit (~12 minutes) — re-run with a smaller PDF or check the job in your Emboss dashboard.");
}
$.flow.rerun(POLL_DELAY_MS, {
job_id: jobId,
}, MAX_RETRIES);
$.export("$summary", `Still filling (check ${runs} of ${MAX_RETRIES})`);
return;
}
await this.emboss.fillSession({
$,
sessionId: status.session_id,
});
const pdf = await this.emboss.getSessionPdf({
$,
sessionId: status.session_id,
});
const { filepath } = await writePdf(Buffer.from(pdf), `emboss-${status.session_id}.pdf`);
$.export("$summary", `Successfully filled form (session \`${status.session_id}\`)`);
return {
session_id: status.session_id,
report: status.report || {},
filepath,
};
},
};
Loading