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 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.

Postman collection

Try the API without writing code. The collection carries both request shapes and a health check, with baseUrl and apiKey as variables.

Download the Postman collection pdf2json.postman_collection.json

  1. In Postman choose Import, pick Link, and paste https://developer.blueport.io/pdf2json/pdf2json.postman_collection.json — or drop the downloaded file.
  2. Open the collection's Variables tab and set apiKey to a key from your portal (Organisation → API keys). baseUrl is already https://pdf.blueport.io.
  3. Run Extract PDF (raw binary) with a file selected in the body, or Extract PDF (JSON base64) for the JSON shape. Health needs no key.

Request

Send the PDF either way — or use the Postman collection above:

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'));

Document types

You do not need to know what a document is before sending it. The service detects the type — from the PDF's own text where it can, otherwise with a short classification pass — and extracts into that type's fixed shape (a profile). Anything it does not recognise comes back in the generic_document profile, which is still a fixed shape: parties, reference numbers, dates, vehicles, line items, totals, payment details, remaining labelled values, free text and signatures.

meta.document_typeWhat it covers
vehicle_sale_contractDealer contract for the sale of a new, used or demonstrator vehicle or motorcycle: customer, driver, vehicle, accessories and charges, pricing, trade-in, deposit, bank details, special conditions, signatures
vehicle_tax_invoiceDealer tax invoice: invoice number and date, deal id, dealer, customer, vehicle, trade-in, GST pricing block including amount paid and any customer refund
nsw_form7_dealer_noticeNSW Form 7 motor dealer's notice: register and entry numbers, dealer, vehicle identification, dealer guarantee, sale details
nsw_registration_applicationTransport for NSW Application for Registration and Application for Transfer of Registration: plate number, applicant (customer) or corporation, dealer, purchase or acquisition details including the seller, vehicle identification, inspection report, declaration
generic_documentEverything else, in the canonical shape described above
customYou sent your own schema (below) and it was enforced instead

The full JSON Schema of every profile is public at GET https://pdf.blueport.io/pdf2json/profiles?schema=1 — code against it before you send a single document. Two optional controls on the JSON body (or as a header for raw uploads):

Force a type — skip detection when you already know what you are sending:

{ "pdf": "<base64>",
  "document_type": "vehicle_sale_contract" }

// raw upload: header x-document-type: vehicle_sale_contract

Bring your own shape — a JSON Schema is enforced exactly (every key present, nulls for blanks, nothing extra):

{ "pdf": "<base64>",
  "schema": { "type": "object", "properties": { … } } }

// meta.document_type becomes "custom"
An unknown document_type is rejected with 400 and the list of valid ids. A schema always wins over detection and over document_type.

Response

The data object is the document, understood: a documentType (the title as printed), then logical entity groups with camelCase keys, ISO dates, and real JSON numbers for money. The shape is fixed per document type — every vehicle sale contract comes back with the same keys, every tax invoice with the same keys — so your mapping code is written once. meta.document_type names the shape used and meta.classification says how it was chosen.

{
  "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,
            "document_type": "vehicle_tax_invoice", "profile_version": 1,
            "classification": { "method": "text", "confidence": 0.9 },
            "pages": 2, "billed_units": 1, "credits": 10,
            "resolved": true, "warnings": [] }
}
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.
• Stable shapes: the key set for a given meta.document_type does not change between calls. Profiles are versioned (meta.profile_version); a key added to a profile bumps the version and is announced before it ships, keys are never removed within a version.
• Classification cost: type detection from the text layer is free; the model classification pass that runs for scans or unfamiliar layouts adds about a second and is included in the document's credits.
• 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.