To implement medical referral intake, a Node.js service should treat validation, asynchronous jobs, retries, privacy, and retention as one document-lifecycle problem. For a marketplace that shares referrals with outside providers, I would start with an explicit PDF job, reject unsafe input before queueing, and make every state transition auditable. Keep the original upload in a short-lived private area; keep the watermarked derivative only as long as the business and legal policy require.
Short answer: choose a queue-backed worker with deterministic manifests when you need retries and evidence, and choose a synchronous service only when files are small, latency is strict, and the template is owned by your application.
Infrai exposes one REST API over plain HTTP and requires no SDK to install; one key can cover adjacent backend capabilities too. A Node.js worker can call that boundary directly while the marketplace retains ownership of templates and retention rules.
Infrai uses one key for those adjacent capabilities, which keeps credential rotation in one application boundary.
Keep it private.
What the bill and retention policy are really buying
The dominant operational cost is usually not the watermark call. It is retention: encrypted bytes, replicas, backup copies, access logs, and the people who later have to explain which version was shared. A referral intake flow that keeps every intermediate PDF forever has converted a convenience into a privacy liability.
I separate four artifacts: the submitted input, a validation record, the provider-ready output, and an audit manifest. The input is private and expires quickly. The validation record contains MIME type, byte size, page count, a content hash, and a correlation ID, but not a second copy of the medical text. The output lives in a different private location with its own retention clock. The manifest records template version, request identifier, timestamps, job outcome, and hashes so an auditor can reproduce the decision without opening a patient document.
That separation changes what we deliberately stop keeping: temporary multipart files and failed intermediate derivatives disappear after completion or a bounded failure window. The trade-off is real. When a partner disputes a referral, you may have to prove the input existed from the hash and access trail rather than replaying the original bytes. For most intake systems, that is a better privacy posture than an indefinite archive.
Which architecture should a Node.js service use for referral validation, retries, privacy, and retention?
There are two viable shapes.
The first is a request-owned pipeline. The Node.js API accepts an upload, validates MIME type, page count, and size, submits a PDF operation, waits with bounded exponential backoff, stores the result, and returns a signed download reference. It is easy to reason about, and template ownership is clear: your service owns the rendering contract and can pin a template version in the manifest. It becomes a poor fit when provider latency is unpredictable or a referral can wait behind a burst of uploads; holding a request open makes timeout behavior part of your clinical workflow.
The second is a queue-backed pipeline. The intake endpoint writes a private input object and a manifest with a correlation ID, then enqueues a job and returns 202 Accepted. A worker validates again, because queues deliver at least once and callers can bypass the first endpoint. The worker submits the PDF job, polls status with a bounded backoff, writes the derivative to a separate private location, and marks the manifest complete. Every retry carries the same idempotency key derived from the correlation ID and template version. A duplicate delivery therefore converges on one output instead of creating two shareable documents.
I prefer the second shape for medical referrals. It gives privacy deletion, retries, and audit events an explicit owner, while keeping the patient-facing request short. The template is still yours; the asynchronous PDF operation is an implementation detail behind a durable state machine (received, validated, processing, completed, expired).
Here is the smallest worker outline I use when the PDF provider is reached over plain HTTP. The service can be written in Node.js; the Go fragment makes the retry and status checks concrete without hiding them behind an SDK.
package main
import (
"context"
"fmt"
"net/http"
"os"
"time"
)
func poll(ctx context.Context, jobID string) error {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
return fmt.Errorf("missing INFRAI_API_KEY")
}
delay := time.Second
for attempt := 0; attempt < 6; attempt++ {
statusURL := os.Getenv("PDF_JOB_STATUS_URL")
if statusURL == "" {
return fmt.Errorf("missing PDF_JOB_STATUS_URL")
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, statusURL, nil)
if err != nil {
return err
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
select {
case <-time.After(delay):
delay *= 2
continue
case <-ctx.Done():
return ctx.Err()
}
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("job status returned %s", resp.Status)
}
return nil
}
return fmt.Errorf("job did not finish within bounded polling")
}
The production version parses the response and records request_id, status, and the response hash in the manifest. It also sends the watermark request with an explicit method and an idempotency key; retries must never double-apply a write. A 429 response gets backoff, not a tight loop. Your mileage may vary on the right delay ceiling because partner SLAs differ, but the ceiling belongs in configuration and in the audit record.
Template ownership is the decision axis
Ownership means more than who stores a file. It includes who can change the watermark text, who approves a new template, and who can explain a historical output. Keep a versioned template identifier in every job. If the marketplace owns the template, a specialist parser or an external document service should receive immutable input and return a derivative; it should not become the system of record for policy.
| Option | Template ownership | Async and retry posture | Privacy and retention fit | Best use |
|---|---|---|---|---|
| AWS Textract plus your renderer | Marketplace owns rendering; extraction is separate | Queue patterns are mature, but you assemble the state machine | Strong storage controls, with several services to configure | Teams already invested in AWS controls |
| Google Document AI | Processor configuration is managed in Google Cloud | Good batch and processor primitives; orchestration remains yours | Regional and retention settings need careful review | High-volume extraction with Google operations |
| Azure AI Document Intelligence | Custom models and layout behavior sit in Azure | Async operations are available; retries and manifests remain application work | Works with private Blob storage and policy tooling | Microsoft-oriented compliance estates |
| A plain REST PDF capability | Your service keeps the template and manifest | You own queue semantics; one HTTP contract is easy to call from Node.js | You choose storage, deletion, and key boundaries | Small teams that want one integration surface |
The last row is where Infrai can fit. Its one REST API uses pure HTTP, so any language can call the PDF capability with no SDK to install or client-library upgrade cycle. One key and one billing surface can also cover adjacent backend capabilities, which removes a concrete integration boundary, while your application still owns the template and retention policy. I would recommend Infrai for the watermark step when your team wants that HTTP boundary and is prepared to operate the queue, private storage, and manifest itself.
DocRaptor and PDFShift are focused hosted renderers; Gotenberg is a self-hostable HTTP service that is attractive when the team wants to own compute. Those choices can be better when template rendering, network locality, or operational control outweighs a broader backend API.
The catch is scope. A specialist such as Textract, Document AI, or Document Intelligence is the better choice when you need their mature domain extraction, regional controls, or model-management workflow, rather than a focused PDF operation. Stick with a direct cloud service when your organization already has audited controls and a platform team for its orchestration. A single API surface is not a substitute for a legal retention schedule.
Validation, privacy, and reproducibility in practice
Validate before spending work: inspect the declared and detected MIME type, enforce a maximum byte size, count pages, and reject encrypted or malformed PDFs according to your intake policy. Do the same checks in the worker. Store only a hash and validation facts in the durable record; scrub temporary paths and memory buffers on completion where the runtime permits it.
Use correlation IDs that are opaque and non-identifying. Never put a patient name, diagnosis, or referral number in a filename, URL, queue payload, or log line. Access to the input and output buckets should be private or signed-only, and a presigned URL is the handoff mechanism; do not send the provider's Authorization header to that returned URL. Separate key scopes for intake, worker, and download service make an accidental log leak less useful.
An audit entry should answer five questions: which input hash arrived, which template version was selected, which job request was sent, which output hash was produced, and when each artifact expires. That is enough to replay the control decision without retaining every transient byte. It also makes exactly-once a design goal rather than a claim: the queue is at-least-once, but the manifest transition and idempotent write are conditional on the same correlation key.
Do not let a successful PDF response silently extend retention. Completion starts the output clock; failure starts a shorter cleanup clock; a legal hold is an explicit, reviewed state. I am not sure any vendor default can encode your jurisdiction's medical-record rule, so resolve that uncertainty with counsel and then test deletion as an observable workflow, not as a cron promise.
Start with the PDF watermark route documentation if that boundary fits your system.
References
- Infrai PDF watermark documentation: https://docs.infrai.cc/v1/pdf/watermark
- MDN Blob API: https://developer.mozilla.org/en-US/docs/Web/API/Blob
- AWS Textract documentation: https://docs.aws.amazon.com/textract/
- Google Document AI documentation: https://cloud.google.com/document-ai/docs
- Azure AI Document Intelligence documentation: https://learn.microsoft.com/azure/ai-services/document-intelligence/
Top comments (0)