Skip to main content

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 .doqlo package as package.doqlo from 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

  1. Upload the representative PDF you used to set up the layout.
  2. Place and map the fields in the Bulk Fill editor.
  3. Preview enough rows to confirm the layout works for the data you expect.
  4. Export the .doqlo package.

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:

  • pdf
  • doqlo_file
  • exactly one of rows_json or csv_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 unacceptable
  • 5: use this when a small amount of failed-row loss is acceptable in exchange for best-effort output
  • 100: 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-Key is your caller-supplied logical create key for retries.
  • X-Request-Id is optional tracing and support correlation. If you omit it, Doqlo generates one and returns it in the response header.
  • pdf is the source PDF the package executes against.
  • doqlo_file is the .doqlo package exported from the web Bulk Fill editor.
  • rows_json or csv_file supplies the row data. Provide exactly one.
  • max_failed_row_percent is your required failed-row acceptance boundary.

Optional output controls:

  • flatten_forms=false leaves interactive PDF form fields as they are. Set it to true when the source PDF has interactive form fields and generated overlays or content could otherwise appear behind those layers.
  • include_stickers=false leaves visual stickers or markers out of the export. Set it to true when you want those visual markers, including sign-related markers where applicable, included in the output.

Delivery controls:

  • Omit delivery_mode to use response.
  • Set delivery_mode=webhook when you want terminal completion or failure sent to your own HTTPS receiver instead of relying only on polling.
  • webhook_url and webhook_secret are 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:

  • 202 with queued or processing if the job is still running
  • 500 with a failed job envelope in response mode if the job settled failed inside the sync wait window
  • 200 with status=failed and no result in 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 logical job_id
  • same Idempotency-Key + different effective body within 24 hours returns 409 IDEMPOTENCY_KEY_PAYLOAD_MISMATCH
  • same Idempotency-Key after 24 hours is treated as a new request
  • a new Idempotency-Key can still reuse an existing artifact under Doqlo reuse policy, but that reuse creates a new job_id
  • X-Request-Id remains tracing-only and may differ across retries of the same logical create

Replay examples:

  • replay while processing: 202 with the same job_id and status=processing
  • replay after completion: 200 with the same job_id and the latest result
  • replay after failure: 200 with the same job_id and the latest error
  • 429 with either RATE_LIMITED, REPEATED_FAILED_JOBS_COOLDOWN, or HIGH_FAIL_RATIO_COOLDOWN if Doqlo temporarily blocks new create requests; honor Retry-After and retry_after_seconds before 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:

  • queued
  • processing
  • completed
  • failed

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:

  • success
  • partial
  • failed

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: