PDF Merge and Compress

2 endpoints · Core API

Merge several PDFs (5 by default, up to 20 per account) into one, in the order you send them, and optionally compress the result. Use it to prepare a loan kit or any multi-document pack as a single PDF before starting eSign.

How it works

The call is asynchronous: you get 202 Accepted with a request_id straight away, and the result PDF is POSTed to your webhook_url as base64 when processing finishes. You can also collect it from the status endpoint while it is held.

What you can do

  • Merge 2 to 5 PDFs by default, or up to 20 if your account allows it (50 MB total)
  • Compress the merged PDF, or a single PDF, at low, medium or high level

Notes

Merging or compressing invalidates existing digital signatures, so merge first and sign after. Files are held only temporarily and deleted once the result is delivered.

POST

/api/v1/pdf/process

#

Merge and/or compress PDFs (async — returns 202, result by webhook)

docs.signcare.io/core/api/document-services/pdf-merge-and-compress/#post-api-v1-pdf-process

Merges PDFs in the order given and, with compress: true, compresses the result. A single file with compress: true compresses that file only.

Asynchronous. Every valid request returns 202 Accepted with a request_id. When processing finishes, the result is POSTed to webhook_url with the PDF as base64 (event: pdf.process.completed), or an error (event: pdf.process.failed). A failed webhook is retried after 30 s, 2 min and 10 min. The PDF can also be collected from GET /api/v1/pdf/process/{request_id} while it is still held.

Limits: 1–5 files by default (your account can be set up for more, up to 20; ask your account manager), 50 MB in total, not password-protected. reference_id must be unique — sending it again returns the original request (duplicate: true) and is not charged again. webhook_url must be https and must accept large bodies (up to ~70 MB).

Compression levels: compression makes the file smaller by reducing the quality of images and scanned pages inside the PDF. Typed text stays sharp at every level. The higher the level, the smaller the file, but images look less detailed. low: light compression (images kept at 300 dpi, print quality). The file shrinks the least, and images and scans still print clearly. Use it when the document will be printed or has fine detail, such as small print on scanned pages, stamps or signatures. medium (default): balanced compression (150 dpi). The file is noticeably smaller and still easy to read on screen and in ordinary prints. Suits most documents, such as agreements, forms and KYC packs. high: strong compression (72 dpi, screen quality). The file is the smallest, but images and scans may look blurry when you zoom in or print. Use it only when file size matters most, for example email attachments or portal upload limits. How much the file shrinks depends on its content. PDFs with many photos or scanned pages shrink a lot, while PDFs that are mostly typed text are already small and shrink very little. If compression cannot make the file smaller, you receive the original (uncompressed) PDF and a warning in the response.

Storage: files are held temporarily in encrypted private storage only while the request is in progress, and deleted as soon as the webhook is delivered (or 1 hour after delivery finally fails).

Notes: merging or compressing invalidates existing digital signatures — merge first, then eSign. Compression may flatten form fields. One flat charge per successful request.

Headers

  • X-API-KEY
    stringrequired
    Your secret API key for the environment you are calling (Stage and Live use different keys).
  • X-API-APP-ID
    stringrequired
    The App ID issued together with the key.
  • Content-Type
    stringrequired
    application/json

Body parameters

application/json
  • reference_id
    stringrequired
    Required. Your unique reference for this request (max 100 characters). Sending the same value again returns the original request instead of starting a second job.
    minLength 1
  • webhook_url
    stringrequired
    Required. HTTPS URL that receives the result (max 500 characters). The success payload carries the PDF as base64, so the endpoint must accept large request bodies (up to ~70 MB).
    minLength 1
  • compress
    booleanoptional
    Optional. Compress the result. Default false. Required to be true when only one file is sent.
    default false
  • compression_level
    stringoptional
    Optional. How much to shrink the file. Only images and scanned pages lose quality; typed text stays sharp. low = slightly smaller, print quality (300 dpi); medium = noticeably smaller, good for screen and ordinary print (150 dpi, default); high = smallest file, images may look blurry when zoomed or printed (72 dpi). Ignored when compress is false.
    nullabledefault medium
  • output_file_name
    stringoptional
    Optional. Name of the result file. Path characters are removed and .pdf is forced. Default merged.pdf (or compressed.pdf for a single file).
    nullable
  • files
    array<object>required
    Required. 1 to 5 PDFs by default (your account may be set up for more, up to 20), merged in array order. Total decoded size max 50 MB.
    Each item: 2 fields
    • file_name
      stringoptional
      Optional. Used only in error and warning messages.
      nullable
    • data
      string (byte)required
      Required. Base64-encoded PDF. Must not be password-protected.
Sample request body
{ "reference_id": "string", "webhook_url": "string", "compress": false, "compression_level": "medium", "files": [ { "data": "string" } ] }

Responses

202Accepted
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    objectoptional
    Returned with 202 Accepted when a PDF Process request is queued.
    4 fields
    • request_id
      stringoptional
      No description.
      nullable
    • reference_id
      stringoptional
      No description.
      nullable
    • status
      stringoptional
      Current status — accepted, processing, completed, delivered, delivery_failed, expired or failed.
      nullable
    • duplicate
      booleanoptional
      True when this reference_id was already submitted; the original request is returned.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": { "request_id": "string", "reference_id": "string", "status": "string", "duplicate": false }, "messages": [ "string" ], "request_id": "string", "isBillable": false }
400Bad Request
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    anyoptional
    No description.
    nullable
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": null, "messages": [ "string" ], "request_id": "string", "isBillable": false }
402Payment Requiredsame body as 400
403Forbiddensame body as 400
413Content Too Largesame body as 400
Code samplescurl · Node.js · Python
Stage: uat-ext.signcare.io
curl -X POST 'https://uat-ext.signcare.io/api/v1/pdf/process' \ -H 'X-API-KEY: YOUR_API_KEY' \ -H 'X-API-APP-ID: YOUR_APP_ID' \ -H 'Content-Type: application/json' \ -d '{ "reference_id": "string", "webhook_url": "string", "compress": false, "compression_level": "medium", "files": [ { "data": "string" } ] }'
GET

/api/v1/pdf/process/{requestId}

#

Get the status of a PDF Process request (includes the PDF while it is held)

docs.signcare.io/core/api/document-services/pdf-merge-and-compress/#get-api-v1-pdf-process-requestid

Returns the same body as the webhook. data.pdf (base64) is included while the result is held: status completed (webhook not yet delivered or retrying) and delivery_failed (for 1 hour). After a successful webhook the status is delivered and the PDF has been deleted. Other values: accepted, processing, expired, failed (see error.message). Reading the status is not charged.

Headers

  • X-API-KEY
    stringrequired
    Your secret API key for the environment you are calling (Stage and Live use different keys).
  • X-API-APP-ID
    stringrequired
    The App ID issued together with the key.

Path parameters

  • requestId
    string (uuid)required
    No description.

Responses

200OK
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    objectoptional
    The result of a PDF Process request. Sent as the webhook body and returned (inside the standard response envelope) by the status endpoint.
    7 fields
    • event
      stringoptional
      pdf.process.completed or pdf.process.failed (webhook); null on the status endpoint.
      nullable
    • request_id
      stringoptional
      No description.
      nullable
    • reference_id
      stringoptional
      No description.
      nullable
    • status
      stringoptional
      accepted, processing, completed, delivered, delivery_failed, expired or failed.
      nullable
    • message
      stringoptional
      Human-readable note, e.g. why the PDF is no longer available.
      nullable
    • data
      objectoptional
      No description.
      10 fields
      • file_name
        stringoptional
        No description.
        nullable
      • pdf
        string (byte)optional
        The result PDF as base64. Present only while the result is still held (see status values).
        nullable
      • page_count
        integer (int32)optional
        No description.
      • file_count
        integer (int32)optional
        No description.
      • input_size_bytes
        integer (int64)optional
        No description.
      • output_size_bytes
        integer (int64)optional
        No description.
      • reduction_percent
        number (double)optional
        No description.
      • merged
        booleanoptional
        No description.
      • compression_applied
        stringoptional
        none, low, medium or high — what was actually applied (none if compression gave no gain).
        nullable
      • warnings
        array<string>optional
        No description.
        nullable
    • error
      objectoptional
      No description.
      1 field
      • message
        stringoptional
        No description.
        nullable
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": { "event": "string", "request_id": "string", "reference_id": "string", "status": "string", "message": "string", "data": { "file_name": "string", "pdf": "string", "page_count": 0, "file_count": 0, "input_size_bytes": 0, "output_size_bytes": 0, "reduction_percent": 0, "merged": false, "compression_applied": "string", "warnings": [ "string" ] }, "error": { "message": "string" } }, "messages": [ "string" ], "request_id": "string", "isBillable": false }
404Not Found
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    anyoptional
    No description.
    nullable
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": null, "messages": [ "string" ], "request_id": "string", "isBillable": false }
Code samplescurl · Node.js · Python
Stage: uat-ext.signcare.io
curl -X GET 'https://uat-ext.signcare.io/api/v1/pdf/process/{requestId}' \ -H 'X-API-KEY: YOUR_API_KEY' \ -H 'X-API-APP-ID: YOUR_APP_ID'