API Quickstart
Use this page when you want the full intended workflow from the web editor to a working Public API request.
The Public API does not start from scratch. It executes the .doqlo package
you exported from the Bulk Fill editor.
If you want the product rationale and real-world examples behind reusing one
.doqlo across changed or related PDFs, read
Reuse .doqlo Project Files Across PDFs.
Before You Start
- You have a Doqlo account on an eligible plan.
- You have created a BF export API key from your Doqlo account page.
- You have a source PDF file available as
source.pdf. - You have exported a Bulk Fill
.doqlopackage aspackage.doqlofrom the web Bulk Fill editor. - You have either JSON row data or a CSV file ready to submit.
- You know the current base URL:
https://api.doqlo.com.
If you are not sure whether your current plan includes API access, read Plans & Limits first.
1. Build The Layout In The Web Editor
- Upload the representative PDF you used to set up the layout.
- Place and map the fields in the Bulk Fill editor.
- Preview enough rows to confirm the layout works for the data you expect.
- Export the
.doqlopackage.
That package is what the Public API executes later. The API does not accept the raw field-authoring state directly.
The web editor keeps manual project-file export strict. If QR/barcode overlays
are empty, invalid, or malformed, fix them before saving the .doqlo package.
The Public API still treats static QR/barcode issues defensively if one slips
through later: it skips those overlays and reports them in manifest.json.
2. Choose Your Data Input And Delivery Mode
Every create request needs:
pdfdoqlo_file- exactly one of
rows_jsonorcsv_file max_failed_row_percent
Choose response mode when you want the create call to return the terminal
result inline when possible. Choose webhook mode when you want terminal
notification sent to your own endpoint.
3. Choose max_failed_row_percent
Think of the threshold as your acceptance boundary for failed rows:
0: use this when even one failed row makes the whole job unacceptable5: use this when a small amount of failed-row loss is acceptable in exchange for best-effort output100: use this when you want the job to continue even if many rows fail, as long as Doqlo can still produce the archive and manifest
Threshold accounting is based on failed rows only. Skipped overlays inside a produced row do not make that row failed.
Static QR/barcode overlays with empty, invalid, or malformed data are skipped
at execution time when a public upload still contains them. Those skips can
make a produced row partial, but they do not count as failed rows by
themselves.
4. Request Fields
Use this short guide when you need to understand what each create request field does before copying the examples.
Required inputs:
Authorization: Bearer <token>authenticates the request with your BF export API key.Idempotency-Keyis your caller-supplied logical create key for retries.X-Request-Idis optional tracing and support correlation. If you omit it, Doqlo generates one and returns it in the response header.pdfis the source PDF the package executes against.doqlo_fileis the.doqlopackage exported from the web Bulk Fill editor.rows_jsonorcsv_filesupplies the row data. Provide exactly one.max_failed_row_percentis your required failed-row acceptance boundary.
Optional output controls:
flatten_forms=falseleaves interactive PDF form fields as they are. Set it totruewhen the source PDF has interactive form fields and generated overlays or content could otherwise appear behind those layers.include_stickers=falseleaves visual stickers or markers out of the export. Set it totruewhen you want those visual markers, including sign-related markers where applicable, included in the output.
Delivery controls:
- Omit
delivery_modeto useresponse. - Set
delivery_mode=webhookwhen you want terminal completion or failure sent to your own HTTPS receiver instead of relying only on polling. webhook_urlandwebhook_secretare required only in webhook mode.
5. Set Environment Variables
export DOQLO_BF_EXPORT_API_KEY='doqlo_bfexp_<public_id>.<secret>'
export DOQLO_RESPONSE_IDEMPOTENCY_KEY='2f6403c7-4e52-4c7b-b435-96542ccbf4bc'
export DOQLO_RESPONSE_REQUEST_ID='support-create-001'
export DOQLO_WEBHOOK_IDEMPOTENCY_KEY='c7c130e0-2dbb-4975-a4b3-54c3dbec5e3d'
export DOQLO_WEBHOOK_REQUEST_ID='support-webhook-001'
export DOQLO_MAX_FAILED_ROW_PERCENT='5'
export DOQLO_WEBHOOK_URL='https://example.com/hooks/doqlo'
export DOQLO_WEBHOOK_SECRET='replace-me'
6. Submit A Basic Response-Mode Job
curl -X POST https://api.doqlo.com/v1/bulkfill/export-jobs \
-H "Authorization: Bearer $DOQLO_BF_EXPORT_API_KEY" \
-H "Idempotency-Key: $DOQLO_RESPONSE_IDEMPOTENCY_KEY" \
-H "X-Request-Id: $DOQLO_RESPONSE_REQUEST_ID" \
-F "[email protected];type=application/pdf" \
-F "[email protected];type=application/octet-stream" \
-F "max_failed_row_percent=$DOQLO_MAX_FAILED_ROW_PERCENT" \
-F 'rows_json=[{"column_0":"Alice Nguyen","column_1":"INV-1001"},{"column_0":"Marco Silva","column_1":"INV-1002"}]' \
-F 'flatten_forms=false' \
-F 'include_stickers=false'
7. Submit A Basic Webhook-Mode Job
curl -X POST https://api.doqlo.com/v1/bulkfill/export-jobs \
-H "Authorization: Bearer $DOQLO_BF_EXPORT_API_KEY" \
-H "Idempotency-Key: $DOQLO_WEBHOOK_IDEMPOTENCY_KEY" \
-H "X-Request-Id: $DOQLO_WEBHOOK_REQUEST_ID" \
-F "[email protected];type=application/pdf" \
-F "[email protected];type=application/octet-stream" \
-F "[email protected];type=text/csv" \
-F "max_failed_row_percent=$DOQLO_MAX_FAILED_ROW_PERCENT" \
-F "delivery_mode=webhook" \
-F "webhook_url=$DOQLO_WEBHOOK_URL" \
-F "webhook_secret=$DOQLO_WEBHOOK_SECRET"
8. Read The Create Response
A completed response looks like this:
{
"job_id": "<job_id>",
"status": "completed",
"created_at": "<timestamp>",
"started_at": "<timestamp>",
"completed_at": "<timestamp>",
"result": {
"delivery_mode": "direct",
"download_url": "/v1/bulkfill/export-jobs/<job_id>/download",
"expires_at": "<timestamp>",
"file_size_bytes": "<file_size_bytes>"
}
}
The create call can also return:
202withqueuedorprocessingif the job is still running500with a failed job envelope in response mode if the job settled failed inside the sync wait window200withstatus=failedand noresultin webhook mode if the job settled failed before the response returned
9. Understand Create Idempotency
Front-door auth, security checks, and create-side rate limiting run before
idempotency replay is evaluated. A retry can therefore still return 429
before Doqlo looks up a prior logical job.
Once a request is admitted past those front-door protections:
- same
Idempotency-Key+ same effective body within 24 hours returns the current state of the same logicaljob_id - same
Idempotency-Key+ different effective body within 24 hours returns409 IDEMPOTENCY_KEY_PAYLOAD_MISMATCH - same
Idempotency-Keyafter 24 hours is treated as a new request - a new
Idempotency-Keycan still reuse an existing artifact under Doqlo reuse policy, but that reuse creates a newjob_id X-Request-Idremains tracing-only and may differ across retries of the same logical create
Replay examples:
- replay while processing:
202with the samejob_idandstatus=processing - replay after completion:
200with the samejob_idand the latestresult - replay after failure:
200with the samejob_idand the latesterror 429with eitherRATE_LIMITED,REPEATED_FAILED_JOBS_COOLDOWN, orHIGH_FAIL_RATIO_COOLDOWNif Doqlo temporarily blocks new create requests; honorRetry-Afterandretry_after_secondsbefore retrying
Repeated failed jobs and other failure-heavy usage patterns may be subject to
temporary abuse-control rate limiting or cooldown. When that happens, the API
response tells you when retry is allowed via Retry-After and
retry_after_seconds.
Read Idempotency for the full contract, header validation
rules, and webhook examples that include both idempotency_key and
request_id.
max_failed_row_percent is integer-only in 0..100. Threshold accounting is
based on failed rows, not skipped overlays. Threshold-failed jobs expose no
download artifact and do not debit quota.
These temporary safeguards apply to new create requests only. They do not affect status polling, downloads, or webhook delivery.
9. Poll If The Job Is Still Running
curl https://api.doqlo.com/v1/bulkfill/export-jobs/$JOB_ID \
-H "Authorization: Bearer $DOQLO_BF_EXPORT_API_KEY"
The job lifecycle uses these statuses:
queuedprocessingcompletedfailed
10. Download The Completed Result
Use -L so curl follows the Doqlo-controlled download handoff when the
delivery mode redirects:
curl -L https://api.doqlo.com/v1/bulkfill/export-jobs/$JOB_ID/download \
-H "Authorization: Bearer $DOQLO_BF_EXPORT_API_KEY" \
-o bulkfill-export.zip
The ZIP contains row PDFs plus manifest.json.
11. Inspect manifest.json
The manifest is the transparency layer for best-effort execution. A row can be:
successpartialfailed
A produced row with 0 applied overlays is still partial, not success.
Simple example:
{
"version": 2,
"summary": {
"produced_rows": 2,
"success_rows": 1,
"partial_rows": 1,
"failed_rows": 1,
"applied_overlay_count": 5,
"skipped_overlay_count": 1
},
"warnings": [
{
"code": "SOURCE_PDF_FINGERPRINT_MISMATCH",
"message": "The submitted PDF differs from the package source context."
}
],
"row_results": [
{
"row_number": 1,
"status": "success"
},
{
"row_number": 2,
"status": "partial"
},
{
"row_number": 3,
"status": "failed"
}
]
}
In this example:
- the job still completed because the failed-row total stayed within the chosen threshold
- one row produced a PDF with skipped placements, so it is
partial - one row failed completely and produced no row PDF
If the job exceeds your threshold, there is no ZIP and no manifest download.
Read the failed job's error.details instead.
If The Request Fails
Check these pages first: