Skip to content

Partner API Reference

The EasyDocForms Partner API turns a blank PDF intake form into a hosted, mobile-friendly fillable form — and returns the completed, pixel-exact PDF plus structured JSON answers. It wraps the same document-understanding pipeline EasyDocForms runs in production for healthcare intake: import a blank PDF, wait for the template, mint a hosted fill link, hand it to a patient, then retrieve the results.

Access

The Partner API is in beta. Access is enabled per organization — if your requests return 403 PARTNER_API_NOT_ENABLED, contact [email protected] to enable it for your organization.

Authentication

Every request carries an API key as a bearer token:

Authorization: Bearer edfk_live_...

Keys are created by an organization admin in the EasyDocForms app under Settings → Integrations → Partner API and are shown exactly once. Each key carries scopes; a request without the required scope fails with 403 SCOPE_REQUIRED.

ScopeGrants
imports:writeCreate and poll PDF imports
templates:readList templates and their field maps
fill_links:writeMint hosted fill links
submissions:readList and retrieve submissions and completed PDFs
webhooks:manageRegister, list, test, and delete webhooks

The PHI boundary (read this first)

  • Imports are blank forms only. Every import requires blank_form_attestation: true, asserting the uploaded PDF is a blank template with no patient-identifiable information. Do not upload filled forms.
  • external_ref must never contain PHI. It is an opaque correlation id (your visit or order id) that is echoed back on submissions and webhook events.
  • Webhook payloads are PHI-minimized by design. Events carry ids and retrieve URLs, never patient answers. Answers are only available over the authenticated API.

Quickstart

The full loop in curl (the SDKs wrap all of this, including polling):

sh
# 0. Verify your key
curl -H "Authorization: Bearer $EDF_KEY" \
  https://form.easydocforms.com/api/v1/ping

# 1. Import a blank PDF (async — returns 202 with an import_id)
curl -X POST -H "Authorization: Bearer $EDF_KEY" -H "Content-Type: application/json" \
  -d '{"pdf_url":"https://example.com/new-patient-intake.pdf",
       "filename":"new-patient-intake.pdf",
       "blank_form_attestation":true}' \
  https://form.easydocforms.com/api/v1/imports

# 2. Poll until status is "succeeded" (typically 1–10 minutes)
curl -H "Authorization: Bearer $EDF_KEY" \
  https://form.easydocforms.com/api/v1/imports/IMPORT_ID

# 3. Mint a hosted fill link and hand its url to the patient
curl -X POST -H "Authorization: Bearer $EDF_KEY" -H "Content-Type: application/json" \
  -d '{"template_id":"TEMPLATE_ID","external_ref":"visit-8675309"}' \
  https://form.easydocforms.com/api/v1/fill-links

# 4. After the patient submits: structured answers…
curl -H "Authorization: Bearer $EDF_KEY" \
  https://form.easydocforms.com/api/v1/submissions/SUBMISSION_ID

# …and a time-limited download link for the completed, pixel-exact PDF
curl -H "Authorization: Bearer $EDF_KEY" \
  https://form.easydocforms.com/api/v1/submissions/SUBMISSION_ID/pdf-link

Rather than polling for submissions, register a webhook and receive submission.created the moment a patient submits.

Imports

POST /imports — Import a blank PDF (async)

Scope: imports:write. Stages the PDF and queues processing. Returns 202 immediately with an import_id.

FieldTypeNotes
pdf_base64stringThe blank PDF, standard base64. Exactly one of pdf_base64 / pdf_url.
pdf_urlstringPublic HTTPS URL of the blank PDF (private/internal addresses are rejected).
filenamestring, requiredOriginal filename, e.g. "new-patient-intake.pdf".
titlestringOptional display title for the resulting template.
blank_form_attestationboolean, requiredMust be true: you attest this PDF is a blank form template containing no patient-identifiable information.

Maximum PDF size 10 MB. Imports are bounded per organization per UTC day in addition to the per-minute rate limit; a 429 quota response carries quota and retry_after (also sent as a Retry-After header).

GET /imports/{import_id} — Poll an import

Scope: imports:write. Returns the job state:

json
{
  "import_id": "imp_...", "status": "succeeded",
  "filename": "new-patient-intake.pdf",
  "template_id": "tpl_...", "page_count": 4, "field_count": 62,
  "detector": "azure",
  "review_required": true,
  "review_reasons": ["Check the signature block on page 4"],
  "warnings": [],
  "created_at": "2026-08-22T10:00:00Z", "updated_at": "2026-08-22T10:03:00Z"
}

status is queued | processing | succeeded | failed. Result fields appear only when succeeded; error only when failed. Imports never fail for quality reasons: the template is always created, and review_required / review_reasons tell your staff what to double-check in the EasyDocForms editor before the form goes to patients. Prefer the import.completed / import.failed webhooks over polling.

Templates

GET /templates — List templates

Scope: templates:read. The organization's active PDF templates, newest first — templates created via this API and templates created in the EasyDocForms app alike. Each entry carries template_id, title, source_filename, page_count, field_count, detector, version, and timestamps.

GET /templates/{template_id}/fields — List a template's fields

Scope: templates:read. The template's field map: every PDF field id the import pipeline produced, with the human-readable question prompt and detected label bound to it. Submission answers and fill-link values are keyed by these field ids.

Field ids do not survive a re-import

Field ids are regenerated when a template is re-imported. After a re-import, re-fetch this list and rebuild any stored mapping (version identifies the field-map generation). The endpoint works on retired templates so historical submissions stay interpretable.

Each field carries field_id, kind (text, date, select_one, select_many, signature, …), prompt, label, choice_label (for choice fields), question_id (groups fields belonging to one question), page, and prefillable.

POST /fill-links — Create a hosted fill link

Scope: fill_links:write. Mints a shareable URL where a patient fills the form — no EasyDocForms account needed on their side. Links always serve the template's latest version, so re-importing an updated PDF propagates to live links.

FieldTypeNotes
template_idstring, requiredFrom an import or GET /templates.
expires_in_daysintegerDays until the link stops accepting responses. 0 or omitted = no expiry.
max_responsesintegerMaximum number of submissions. 0 or omitted = unlimited.
external_refstring ≤256Opaque correlation id echoed on submissions and webhook events. Must not contain PHI.
valuesarray ≤200Prefill values, keyed by field id (only prefillable fields are accepted).

Prefill values entries are {field_id, value, locked}. Non-locked entries are editable defaults the filler can correct. Locked entries are sender-authored terms (a contract price, an effective date): rendered read-only and re-asserted server-side at submit, so the signer cannot alter them. Unlike external_ref, values may contain PHI — they are applied server-side and never appear in the link URL.

A 422 response means one or more values reference fields the template's current field map does not accept — usually a stale mapping after a re-import; the body lists each field_id with a reason (unknown_field or not_prefillable).

The 201 response carries fill_link_id, short_code, and the hosted url to hand to the patient.

Submissions

GET /submissions — List submissions

Scope: submissions:read. The organization's submissions, newest first, as PHI-light summaries (correlation ids + PDF status, never answers). This is the fallback polling surface for missed webhooks: pass submitted_since (RFC 3339) to catch up from a timestamp, then follow next_cursor until it disappears. Parameters: limit (1–100, default 25), submitted_since, cursor.

GET /submissions/{submission_id} — Retrieve a submission

Scope: submissions:read. Structured answers plus correlation back to the fill link that produced the submission:

json
{
  "submission_id": "sub_...", "submitted_at": "2026-08-22T11:00:00Z",
  "answers": { "fld_a1": "…", "fld_b2": "…" },
  "template_id": "tpl_...", "completed_pdf_status": "ready",
  "fill_link_id": "fl_...", "external_ref": "visit-8675309"
}

answers maps the template's field ids to submitted values; signatures and drawings are rendered into the completed PDF, not included here. completed_pdf_status is ready (the frozen PDF exists and /pdf-link can sign it) or pending (fetch via /pdf, which renders on demand — never "lost").

GET /submissions/{submission_id}/pdf — Download the completed PDF

Scope: submissions:read. Streams the completed, pixel-exact PDF. Serves the artifact frozen at submission when available, falling back to an on-demand render — this endpoint works even while completed_pdf_status is pending (the fetch is just slower).

Scope: submissions:read. Returns a signed URL (valid ~10 minutes) that downloads the completed PDF without any Authorization header — a handoff link safe to pass to a browser, an EMR, or an AI agent's user without embedding your API key. Treat it as a bearer credential for this one document. Only the artifact frozen at submission can be signed: while completed_pdf_status is pending this returns 409 with a hint to stream via /pdf instead. Every call is audit-logged.

Webhooks

Register delivery URLs with POST /webhooks (scope webhooks:manage) and receive signed import.completed, import.failed, submission.created, and submission.pdf_ready events instead of polling. The signing secret is returned exactly once at registration; every delivery is HMAC-signed and PHI-minimized. See the full webhook reference for the envelope, signature verification, and retry semantics.

Errors and rate limits

Errors are {"error": "human-readable message"}, with a machine-readable code on authorization failures:

StatusMeaning
400Malformed request; the message says exactly what to fix.
401Missing, invalid, or revoked API key.
403Key authenticated but may not do this: SCOPE_REQUIRED (key lacks the route's scope) or PARTNER_API_NOT_ENABLED (organization not enrolled).
404No such resource in your organization.
413PDF exceeds the 10 MB limit.
422Prefill values reference fields the current field map does not accept.
429Rate limit or daily import quota exceeded — back off per Retry-After.

Support

Questions, access requests, or a walkthrough for your integration: [email protected].

HIPAA-aware documentation for independent healthcare practices.