API
Records API endpoints
Every /v1 endpoint partners can call, with auth notes and example request shapes. Reach for the Postman collection (downloadable from your /portal/overview) for runnable versions.
Authorization: Bearer pf_test_sk_… (or pf_live_sk_…). Issue keys from /portal/keys. Keys are server-side only — never embed them in mobile or browser code.Organizations#
Each request acts on a customer organization your key is linked to via a PartnerOrganizationLink. Use these to list and inspect the orgs available to you.
| Method · Path | Purpose |
|---|---|
| GET /v1/organizations | List orgs this key may act on |
| GET /v1/organizations/:orgId | One organization, including mrnSystem |
curl -H "Authorization: Bearer $PIERFLOW_KEY" \
https://www.pierflow.com/v1/organizationsCapture and ingest#
Use these to push paper records through extraction. Image bytes go direct to Cloudinary using a signed URL — Pierflow never proxies files.
| Method · Path | Purpose |
|---|---|
| POST /v1/uploads/sign | Get a one-shot Cloudinary upload signature |
| POST /v1/scan-batches | Create a batch (groups pages from one session) |
| POST /v1/ingest/documents | Register an uploaded asset as a ProcessingJob |
| GET /v1/ingest/jobs/:jobId | Poll ProcessingJob state + extracted records |
Typical ingest flow
# 1. Create a batch
curl -X POST https://www.pierflow.com/v1/scan-batches \
-H "Authorization: Bearer $PIERFLOW_KEY" \
-H "Content-Type: application/json" \
-d '{
"organizationId": "org_lagoon_hospital",
"label": "Ward A migration · cohort 1",
"priority": "NORMAL"
}'
# 2. Sign a Cloudinary upload
curl -X POST https://www.pierflow.com/v1/uploads/sign \
-H "Authorization: Bearer $PIERFLOW_KEY" \
-H "Content-Type: application/json" \
-d '{
"organizationId": "org_lagoon_hospital",
"batchId": "bat_3xMA…"
}'
# 3. Upload directly to Cloudinary
# (multipart-form upload using the fields returned in step 2)
# 4. Tell Pierflow about the asset
curl -X POST https://www.pierflow.com/v1/ingest/documents \
-H "Authorization: Bearer $PIERFLOW_KEY" \
-H "Content-Type: application/json" \
-d '{
"organizationId": "org_lagoon_hospital",
"batchId": "bat_3xMA…",
"source": {
"publicId": "pierflow/org_…/page_001",
"secureUrl": "https://res.cloudinary.com/…/page_001.png"
},
"documentType": "OUTPATIENT_CARD"
}'
# 5. Poll
curl https://www.pierflow.com/v1/ingest/jobs/job_4HD… \
-H "Authorization: Bearer $PIERFLOW_KEY"chartFolderId on every /v1/ingest/documents call for that chart. Identity is resolved at folder level — see Patient mapping.Patients#
After extraction, validated records roll up into a per-patient FHIR Bundle.
| Method · Path | Purpose |
|---|---|
| GET /v1/organizations/:orgId/patients | List patients for an org |
| GET /v1/organizations/:orgId/patients/:patientId/fhir | Merged FHIR R4 Bundle |
| GET /v1/organizations/:orgId/patients/by-external/:externalId/fhir | Bundle by your own EMR id (via PartnerPatientLink) |
Both FHIR endpoints accept the same query params: include (comma-separated resource types), date_from, date_to (YYYY-MM-DD filters on Encounter.period.start). When a PartnerPatientLink exists, the Patient.identifier array in the response includes your external_id as a secondary identifier so your EMR can round-trip without out-of-band state.
Partner patient links#
Map Pierflow Patient ids to your EMR's patient ids in one place. Cohort imports go through /bulk; per-patient mappings from acknowledge calls flow in automatically (see Acknowledge). Once linked, you can query Pierflow with your own id via the by-external FHIR endpoint above.
| Method · Path | Purpose |
|---|---|
| POST /v1/partner-patient-links | Create or update one mapping (by MRN or patient id) |
| POST /v1/partner-patient-links/bulk | Up to 500 mappings in one call |
| GET /v1/partner-patient-links?external_id=… | Lookup by your id |
| GET /v1/partner-patient-links?patient_id=… | Lookup by Pierflow id |
curl -X POST https://www.pierflow.com/v1/partner-patient-links \
-H "Authorization: Bearer $PIERFLOW_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "by_mrn",
"organization_id": "org_lagoon_hospital",
"mrn": "LH-00143-26",
"external_id": "emr_patient_8821",
"external_system": "https://your-emr.example.com/patients/",
"placeholder_name": "Adaeze Margaret Nwosu"
}'If no Patient exists under that MRN yet, we create a placeholder Patient + identifier + link with source PLACEHOLDER_FROM_MRN. Future extracted records carrying that MRN auto-attach to the same Patient — the link survives the switch.
Import packages#
Nightly ZIP of validated records per (partner, org) tuple. Download once, acknowledge once.
| Method · Path | Purpose |
|---|---|
| GET /v1/organizations/:orgId/import-packages | List packages for an org (status, counts, expiresAt) |
| GET /v1/import-packages/:packageId/download | Short-lived signed Cloudinary URL for the ZIP |
| POST /v1/import-packages/:packageId/acknowledge | Confirm import + register patient_id_mappings |
curl -X POST https://www.pierflow.com/v1/import-packages/pkg_4HD…/acknowledge \
-H "Authorization: Bearer $PIERFLOW_KEY" \
-H "Content-Type: application/json" \
-d '{
"imported_patient_count": 47,
"failed_patient_ids": [],
"partner_import_reference": "emr-import-2026-06-06-001",
"patient_id_mappings": [
{
"pierflow_patient_id": "pat_b3f9c21a",
"external_id": "emr_8821",
"external_system": "https://your-emr.example.com/patients/"
}
]
}'patient_id_mappings is optional. When present, each pair becomes a PartnerPatientLink with source IMPORT_ACK. Per-item outcomes return in the response so you retry only the failures.
Webhooks#
Subscribe to events so you don't have to poll. Endpoints are signed with HMAC-SHA256; verify X-Pierflow-Signature before trusting any payload.
| Event | Fired when |
|---|---|
| processing_job.completed | Extraction finished — record is AUTO_APPROVED or AWAITING_REVIEW |
| processing_job.failed | Extraction errored; ProcessingJob.status = FAILED |
| import_package.ready | A new ImportPackage moved to READY |
| test.ping | Sent on demand from /portal/webhooks |
Manage endpoints from your /portal/webhooks page. Synchronous delivery with one retry after 30s today; persistent delivery audit ships next.