1. Quick start
- Subscribe to a PDF Merge API plan at https://datacorpapi.com.
- Open your subscription in the customer portal and click Create key. Copy the token (it starts with
dca_sk_). It is shown only once. - Send your files to the API with the token in the
Authorizationheader:
curl -X POST https://convert.datacorpapi.com/api/v1/merge \
-H "Authorization: Bearer dca_sk_YOUR_TOKEN" \
-F "files[]=@passport.pdf" \
-F "files[]=@visa.jpg" \
-F "output_name=merged.pdf" \
-o merged.pdf
If the request succeeds, the response body is the merged PDF file.
2. Your token
- Where to get it: customer portal → your PDF Merge API subscription → Create key.
- Shown once: copy it right away and store it in a secure place (server configuration, secret manager, Zoho connection or org variable).
- How to send it:
Authorization: Bearer <token>on every request.X-API-Key: <token>also works. - Keep it on the server: never put the token in browser JavaScript, mobile apps or public repositories.
- Lost or exposed? Revoke it in the portal and create a new one. The old token stops working immediately.
- One token per subscription: to replace a token, revoke the old one first, then create the new one and update your system.
3. Users & tokens — how pricing works
- You pay per user. A user is a person in your organization who uses the merge feature (for example, a Zoho CRM user who can click your "Merge documents" button).
- You get one token per subscription, no matter how many users you pay for.
- All your users share that single token. Install it once in your system and every user works through it.
- Unlimited merges on paid plans. The free trial includes 100 merges per billing cycle.
- Only successful merges are counted. Requests rejected because of a problem file are not counted.
Keep the number of users on your subscription equal to the number of people who use the service. When your team grows, contact us to add users. Your token stays the same.
4. Merge files
POSThttps://convert.datacorpapi.com/api/v1/merge
Request (multipart/form-data)
| Field | Required | Description |
|---|---|---|
files[] | Yes | 2–20 files (PDF, JPG, JPEG, PNG). Merged in the order you send them. |
Files[0], Files[1], … | Alternative | Indexed field names are also accepted and are sorted by index (0, 1, 2 … 10). |
output_name | No | File name of the result, for example CA306445_documents.pdf. Thai and other Unicode names are supported. .pdf is added if missing, and / \ : * ? " < > | are removed. Default: merged.pdf. |
response_type | No | file (default): the response is the PDF itself. link: the response is JSON with a temporary download link — see Download link. |
output_name and response_type can also be sent in the query string, for example /api/v1/merge?response_type=link&output_name=documents.pdf. This is easier from Zoho Deluge.
What the API does with your files
- Detects the real file type from the content, so a PNG saved as
.jpgstill works. - Places images on A4 pages. Transparent PNG backgrounds become white.
- Checks every PDF first. Password-protected or damaged PDFs are rejected with the file name, before any merging.
- PDFs that are only restricted (for example, printing disabled) but open without a password are merged normally.
Success response — 200
The body is the merged PDF (binary). Useful headers:
Content-Type: application/pdf
Content-Disposition: attachment; filename=documents.pdf; filename*=utf-8''CA306445_%E0%B8%A3%E0%B8%A7%E0%B8%A1.pdf
X-Merged-Count: 3 # number of source files merged
X-Page-Count: 7 # pages in the merged PDF
X-Plan: Monthly
X-Quota-Used: 152 # successful merges in this billing cycle
X-Quota-Remaining: 48 # only on plans with a limit (trial)
Error response — JSON
{
"success": false,
"error_code": "ENCRYPTED_PDF",
"message": "ไฟล์ VISA.pdf มีรหัสผ่าน ไม่สามารถรวมได้",
"file": "VISA.pdf"
}
Always check the HTTP status first. Save the body as a PDF only when the status is 200. For any other status, read the body as JSON. message is in Thai so you can show it to your users as is. file is present when one specific file caused the error.
5. Temporary download link
Use response_type=link when your system cannot receive large files (Zoho Deluge accepts at most 5 MB) or when you want the user's browser to download the PDF directly. The merge works exactly the same; only the response changes.
curl -X POST "https://convert.datacorpapi.com/api/v1/merge?response_type=link" \
-H "Authorization: Bearer $PDF_MERGE_TOKEN" \
-F "files[]=@passport.pdf" -F "files[]=@visa.jpg" \
-F "output_name=CA306445_documents.pdf"
Response — 200 (JSON)
{
"success": true,
"download_url": "https://convert.datacorpapi.com/d/8f3kQ…(48 random characters)",
"expires_at": "2026-11-09T11:40:00+07:00",
"expires_in": 600,
"max_downloads": 3,
"output_name": "CA306445_documents.pdf",
"merged_count": 2,
"page_count": 3,
"size_bytes": 2201000
}
How the link works
- Open it in a browser (or redirect your user to it). The PDF downloads immediately with your
output_name, served from Cloudflare. No token or login is needed — the link itself is the secret. - The merged PDF is stored only in private cloud storage (Cloudflare R2), never on our server. If that storage is unavailable you get
STORAGE_UNAVAILABLE(503) and the merge is not counted — retry, or use the defaultresponse_type=file. - Valid for 10 minutes and up to 3 downloads (in case of a double click or a dropped connection). After that the file is deleted.
- An expired or used-up link shows a short page: "ลิงก์หมดอายุแล้ว กรุณากดรวมเอกสารใหม่" (HTTP 410). An unknown link returns 404. Just merge again.
- Treat the link like a password until it expires: anyone who has it can download the file. Do not post it in public places or save it in records.
- Each merge is counted once, when it succeeds — downloading the link does not count again.
6. Code examples
# Merge three files; save the PDF only on success
status=$(curl -s -w "%{http_code}" -o result.bin -X POST https://convert.datacorpapi.com/api/v1/merge \
-H "Authorization: Bearer $PDF_MERGE_TOKEN" \
-F "files[]=@passport.pdf" \
-F "files[]=@visa.pdf" \
-F "files[]=@photo.png" \
-F "output_name=CA306445_documents.pdf")
if [ "$status" = "200" ]; then mv result.bin merged.pdf; else cat result.bin; fi
<?php
// Laravel HTTP client (Guzzle works the same way)
use Illuminate\Support\Facades\Http;
$response = Http::withToken(env('PDF_MERGE_TOKEN'))
->timeout(120)
->attach('files[]', fopen('passport.pdf', 'r'), 'passport.pdf')
->attach('files[]', fopen('visa.jpg', 'r'), 'visa.jpg')
->post('https://convert.datacorpapi.com/api/v1/merge', ['output_name' => 'CA306445_documents.pdf']);
if ($response->successful()) {
file_put_contents('merged.pdf', $response->body());
$pages = $response->header('X-Page-Count');
} else {
$error = $response->json(); // ['error_code' => ..., 'message' => ..., 'file' => ...]
throw new RuntimeException($error['message'] ?? 'Merge failed');
}
// Node.js 18+ (built-in fetch, FormData and Blob)
import { readFile, writeFile } from "node:fs/promises";
const form = new FormData();
for (const name of ["passport.pdf", "visa.jpg"]) {
form.append("files[]", new Blob([await readFile(name)]), name);
}
form.append("output_name", "CA306445_documents.pdf");
const res = await fetch("https://convert.datacorpapi.com/api/v1/merge", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PDF_MERGE_TOKEN}` },
body: form,
signal: AbortSignal.timeout(120_000),
});
if (res.ok) {
await writeFile("merged.pdf", Buffer.from(await res.arrayBuffer()));
} else {
const err = await res.json(); // { error_code, message, file? }
throw new Error(`${err.error_code}: ${err.message}`);
}
import os, requests
paths = ["passport.pdf", "visa.jpg"]
files = [("files[]", (os.path.basename(p), open(p, "rb"))) for p in paths]
r = requests.post(
"https://convert.datacorpapi.com/api/v1/merge",
headers={"Authorization": f"Bearer {os.environ['PDF_MERGE_TOKEN']}"},
files=files,
data={"output_name": "CA306445_documents.pdf"},
timeout=120,
)
if r.status_code == 200:
with open("merged.pdf", "wb") as f:
f.write(r.content)
else:
err = r.json() # {"error_code": ..., "message": ..., "file": ...}
raise RuntimeError(f"{err['error_code']}: {err['message']}")
using System.Net.Http.Headers;
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(120) };
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("PDF_MERGE_TOKEN"));
using var form = new MultipartFormDataContent();
foreach (var path in new[] { "passport.pdf", "visa.jpg" })
form.Add(new ByteArrayContent(await File.ReadAllBytesAsync(path)), "files[]", Path.GetFileName(path));
form.Add(new StringContent("CA306445_documents.pdf"), "output_name");
var res = await http.PostAsync("https://convert.datacorpapi.com/api/v1/merge", form);
if (res.IsSuccessStatusCode)
await File.WriteAllBytesAsync("merged.pdf", await res.Content.ReadAsByteArrayAsync());
else
throw new Exception(await res.Content.ReadAsStringAsync()); // JSON error
// Zoho CRM function — merge WorkDrive files and upload the result back to WorkDrive.
// Store the token in an org variable (Setup → Developer Hub → Variables), not in code.
// "workdrive" = your Zoho OAuth connection with WorkDrive scopes.
token = zoho.crm.getOrgVariable("pdf_merge_token");
fileIds = {"ohwaefac3f900faf84631ae44da6d1e011c1a", "wlxr7284e29343984456eaf9f2feafb694ae4"};
files = List();
for each fileId in fileIds
{
f = invokeurl
[
url :"https://download.zoho.com/v1/workdrive/download/" + fileId
type :GET
connection :"workdrive"
response-format :FILE
];
f.setParamName("files[]");
files.add(f);
}
headers = Map();
headers.put("Authorization", "Bearer " + token);
// response_type=link -> the reply is small JSON, so the 5 MB Deluge limit does not apply
resp = invokeurl
[
url :"https://convert.datacorpapi.com/api/v1/merge?response_type=link&output_name=" + encodeUrl("CA306445_documents.pdf")
type :POST
files :files
headers :headers
detailed :true
];
body = resp.get("responseText").toString().toMap();
if (resp.get("responseCode") == 200)
{
// Give this link to the user (e.g. return it to your widget / button and open it).
// It works for 10 minutes, up to 3 downloads.
return body.get("download_url");
}
return body.get("message"); // Thai error message for the user
invokeurl waits at most 40 seconds, so the merge itself must finish within that time. See Zoho CRM notes. Test this function with your own connection names before going live.
7. Convert one image to PDF
POSThttps://convert.datacorpapi.com/api/v1/convert/image-to-pdf
- Send one image in the field
file(JPG, JPEG or PNG).output_nameis optional; by default the image's name is used with.pdf. - The response is the same as
/merge: a PDF on success (or a download link withresponse_type=link), JSON on error. Sending a PDF returnsUNSUPPORTED_FILE.
curl -X POST https://convert.datacorpapi.com/api/v1/convert/image-to-pdf \
-H "Authorization: Bearer $PDF_MERGE_TOKEN" \
-F "file=@id-card.png" -o id-card.pdf
8. Errors
Every error has the same JSON shape: success, error_code, message (Thai) and, when relevant, file or quota.
| HTTP | error_code | What to do |
|---|---|---|
| 401 | UNAUTHORIZED |
Check the token. It may be mistyped, revoked, or belong to another product. Create a new token in the customer portal if needed. |
| 422 | VALIDATION_ERROR |
Fix the request: number of files, field names or output_name. Do not retry unchanged. |
| 415 | UNSUPPORTED_FILE |
Only PDF, JPG, JPEG and PNG are accepted. The file type is detected from the content, not the extension. |
| 413 | FILE_TOO_LARGE |
Reduce the file size or send fewer files per request. |
| 422 | CORRUPTED_PDF |
The PDF named in "file" is damaged. Re-export or re-scan it. |
| 422 | ENCRYPTED_PDF |
The PDF named in "file" needs a password to open. Remove the password first. |
| 422 | CONVERT_FAILED |
The image named in "file" could not be converted. Re-save it as JPG or PNG. |
| 500 | MERGE_FAILED |
Unexpected merge error. Retry once; if it persists, contact support. |
| 403 | HTTPS_REQUIRED |
Call the API with https://, never http://. |
| 429 | TOO_MANY_REQUESTS |
Slow down. Wait a few seconds (see the Retry-After header) and try again. |
| 404 | NOT_FOUND |
Check the URL and HTTP method. |
| 500 | INTERNAL_ERROR |
Unexpected error. Retry once; if it persists, contact support. |
| 402 | PAYMENT_REQUIRED |
A payment failed. Update your payment method in the customer portal. |
| 403 | SUBSCRIPTION_INACTIVE |
Your plan has expired, is not active yet, or the account is suspended. Renew in the customer portal or contact us. |
| 429 | QUOTA_EXCEEDED |
Trial limit reached for this billing cycle. Upgrade to a paid plan. The response includes a "quota" object. |
| 503 | AUTH_SERVICE_UNAVAILABLE |
Temporary problem verifying your token. Retry after a short wait. |
| 503 | STORAGE_UNAVAILABLE |
Only with response_type=link: the download link could not be created. The merge was not counted. Retry after a short wait. |
Retry only on 429, 500 and 503, with a short wait (for example 2 s, then 5 s, then 15 s). Other errors will fail again until the request or the file is fixed.
9. Limits
| Limit | Value |
|---|---|
| Files per request | 2 – 20 |
| Size per file | 20 MB |
| Total size per request | 100 MB |
| Accepted types | PDF, JPG, JPEG, PNG (detected from content) |
| Requests per token | 120 per minute |
| Recommended client timeout | 120 seconds |
Download link (response_type=link) | 10 minutes · up to 3 downloads |
| Merges | Unlimited on paid plans · 100 per billing cycle on the trial |
10. Zoho CRM notes
- One token for the whole organization. Save it once as an org variable (for example
pdf_merge_token). Every CRM user who clicks your button uses that same token. - 40-second limit. Deluge stops waiting after 40 seconds. Merging many large scans may take longer, so merge fewer or smaller files per click.
- 5 MB response limit. Deluge can receive files up to 5 MB from external services. Use
response_type=link: you get a small JSON reply with a download link instead of the file, so the size of the merged PDF no longer matters. - Order matters. Add files to the list in the order the pages should appear, for example sorted by your "No" field.
- Show our message. On failure, show the Thai
messagefrom the response to the user. It names the file that caused the problem.
11. Check your plan and usage
GEThttps://datacorpapi.com/api/v1/usage with the same token. Returns your plan, renewal date, usage in this billing cycle and payment links.
curl https://datacorpapi.com/api/v1/usage -H "Authorization: Bearer $PDF_MERGE_TOKEN"
Service status (no token needed): GEThttps://convert.datacorpapi.com/api/v1/health
12. Best practices
- Call the API from your server or from Zoho functions, never from a web browser, so the token stays private.
- Use a timeout of about 120 seconds and retry only on 429, 500 and 503.
- Check the HTTP status before saving the response as a PDF.
- Send files in the final page order. The API never reorders them.
- Compress large scans (150–200 dpi is enough for documents) to keep requests fast.
- Watch
X-Quota-Remainingon the trial so you can upgrade before you reach the limit.
Privacy: your files are used only to build your PDF. Uploaded files are deleted as soon as the merge finishes. The merged PDF is deleted right after the response is sent; with response_type=link it is kept only in private cloud storage (Cloudflare R2, never on our server) for at most 10 minutes (or until it has been downloaded 3 times) and then deleted. We do not look at or share your documents, and file names and download links are not written to our logs.
13. Support
- Customer portal (plans, tokens, invoices): https://datacorpapi.com/portal
- Email: info@datacorpapi.com. Include the time of the request, the
error_codeand the last 4 characters of your token. Never send the full token.