blueport Integration guide · v1 · Sep 2026
Integration guide

PDF2JSON

PDF in. Clean JSON out.

One endpoint that reads any business document — invoices, contracts, registration forms — and returns structured, semantic JSON. No templates to configure, no fields to map.

Endpoint

POST https://pdf.blueport.io/pdf2json
auth: x-api-key header max 8 MB per PDF typical response 10–25 s nothing stored server-side x-model: hkm (default) · snm 1 document = up to 5 pages

Authentication

Every request carries your organisation's API key in the x-api-key header. Your organisation has one key; owners and admins can view (masked) and rotate it any time at app.blueport.io → PDF2JSON → API key. Rotation invalidates the old key immediately — treat the key as a secret, keep it server-side, never ship it in a browser or mobile app.

Choosing a model

An optional x-model header (or ?model=) picks the extraction engine: hkm — the default — is the fast, economical engine; snm is the premium engine for documents where maximum field accuracy matters (legal contracts, financial statements). The engine used is echoed back in meta.model.

Request

Send the PDF either way — or import the ready-made Postman collection (File → Import → paste the URL, then set the apiKey variable):

Raw binary — simplest, no encoding step:

curl -X POST https://pdf.blueport.io/pdf2json \
  -H "x-api-key: YOUR_API_KEY" \
  -H "content-type: application/pdf" \
  --data-binary @invoice.pdf

JSON body — when the PDF is already in memory:

// Node.js
const pdf = fs.readFileSync("invoice.pdf")
  .toString("base64");
const r = await fetch(
  "https://pdf.blueport.io/pdf2json", {
  method: "POST",
  headers: {
    "x-api-key": process.env.PDF2JSON_KEY,
    "content-type": "application/json",
  },
  body: JSON.stringify({ pdf }),
});
const { ok, data } = await r.json();

Salesforce Apex — e.g. converting an invoice attached to a record. Add https://pdf.blueport.io as a Remote Site (Setup → Remote Site Settings) and store the key in a protected Custom Setting or Named Credential, never in code:

// Callout limit is 120s - matches the extraction worst case.
public class Pdf2JsonService {
    public static Map<String, Object> extract(Blob pdfFile) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://pdf.blueport.io/pdf2json');
        req.setMethod('POST');
        req.setHeader('x-api-key', Pdf2Json_Settings__c.getOrgDefaults().Api_Key__c);
        req.setHeader('content-type', 'application/json');
        req.setTimeout(120000);
        req.setBody(JSON.serialize(new Map<String, String>{
            'pdf' => EncodingUtil.base64Encode(pdfFile)
        }));

        HttpResponse res = new Http().send(req);
        Map<String, Object> body =
            (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
        if (res.getStatusCode() != 200 || body.get('ok') != true) {
            throw new CalloutException('PDF2JSON failed: ' + body.get('error')
                + ' (request_id ' + body.get('request_id') + ')');
        }
        return (Map<String, Object>) body.get('data');
    }
}

// Usage - from a ContentVersion on the record, in a @future/queueable
// context (synchronous Apex should not hold a 10-25s callout):
ContentVersion cv = [SELECT VersionData FROM ContentVersion WHERE Id = :cvId];
Map<String, Object> doc = Pdf2JsonService.extract(cv.VersionData);
System.debug(doc.get('documentType'));

Response

The data object is the document, understood: a documentType, then logical entity groups with camelCase keys, ISO dates, and real JSON numbers for money. The shape follows the document — an invoice yields dealer/customer/vehicle/pricing groups, a registration form yields its own sections.

{
  "ok": true,
  "data": {
    "documentType": "TAX INVOICE",
    "pages": 2,
    "currency": "AUD",
    "dealer":  { "name": "Example Motors Pty Ltd", "abn": "12 345 678 901",  },
    "invoice": { "invoiceNo": "10023456", "date": "2026-08-01",  },
    "vehicle": { "vin": "6XY12345678900123", "odometer": 1,  },
    "pricing": {  }
  },
  "meta": { "request_id": "req_a1b2c3", "duration_ms": 17000,
            "model": "hkm", "pages": 2, "billed_units": 1 }
}
Quote meta.request_id in any support conversation — it identifies the exact request in our logs (the document itself is never stored).

Errors

StatusMeaningWhat to do
401Missing or invalid API keyCheck the header name and value; confirm the key wasn't rotated
400Body isn't a PDF / no PDF foundSend raw with content-type: application/pdf or JSON {"pdf": "<base64>"}
413PDF over 8 MBCompress or split the document
422Extraction declinedThe document was rejected by content safety — contact support with the request_id
502Extraction returned unusable outputRare and transient — retry once
500Service errorRetry with backoff; persistent failures → support

Good to know

Latency: extraction is synchronous and typically 10–25 seconds per document — set your HTTP client timeout to 120 s and call it from a background job, not a user-facing request.
Metering: a document includes up to 5 pages — longer files count one document per started block of 5 (a 20-page file counts as 4 documents), and premium-engine (snm) extractions count double. The exact charge is returned as meta.billed_units; failed calls are never billed.
Retries: safe to retry on 5xx; only successful calls count against your plan.
Usage: your document count and history are on your dashboard at app.blueport.io; you'll be notified as you approach your monthly allowance.
Privacy: documents are processed in-flight and never persisted — only usage counts and timings are recorded.