Skip to main content

Ledgentic Partner Integration Guide

Integrate your application with Ledgentic to automate supplier-invoice ingestion, bookkeeping analysis, and status synchronization across your own systems.

Introduction

The Partner Integration API enables you to upload supplier invoices, receive status updates through signed webhooks, and fetch granular bookkeeping results once processing is finished. This guide walks you through the concepts, workflow, and endpoints required for a full integration scenario.

Prerequisites

Make sure you have the following prerequisites before you start:

  • Organizations – You need to have at least one organization in the admin interface.
  • API Key – Generated an API key in the admin interface.
  • Organization External ID Mappings: Make sure you add the external mapping ID to each Organization in the amdin interface that you want to use the API for.
  • Webhooks: Set a webhook url and save the webhook sercret securely. Child organizations can inherit a parent webhook which is the recommended approach.
  • API Key Access Scopes: Make sure the api key has the correct access scopes to the parent and child orginaizations that will use the API.

Key Concepts

  • Organizations & delegated access – An API key is scoped to a root organization and can optionally include access to selected child organizations. Configure child access for each key inside the Ledgentic admin app before calling the API.
  • External organization ID (externalOrgId) – Provision the identifier in the Ledgentic admin interface during onboarding before you attempt any API calls. Provide it as the path parameter on every request so traffic is routed to the correct organization.
  • Invoice external ID (externalInvoiceId) – The unique identifier for each supplier invoice in your system. Supply it in the payload when creating an invoice and reuse the same value wherever the API path references {externalInvoiceId}.
  • Signed webhooks – When processing completes, Ledgentic calls the webhook configured for the organization in the admin interface (child organizations can inherit a parent webhook). Each payload is signed with the webhook secret and delivered in the X-Webhook-Signature header (t={timestamp},v1={signature}); verify it before acting on the request.
  • Processing lifecycle – Invoices transition through data extraction, accounting analysis, optional review, and completion. Use the provided endpoints to monitor progress and consume the bookkeeping output.

Authentication & Base URL

All endpoints accept API key authentication only. Include your key on every request via the X-API-Key header.

Base URL: https://api.ledgentic.com
Header: X-API-Key: YOUR_API_KEY

Rate Limiting

Partner endpoints enforce per-API-key rate limits. When a limit is exceeded the API returns HTTP 429 along with a Retry-After header.

  • Default production quota (subject to change): 500 requests per minute per API key. Contact us if you need a higher quota.
  • Upload, read, and change-request endpoints share the same quota; distribute calls accordingly.
  • Implement client-side backoff when you receive HTTP 429 responses and surface metrics so you can request higher limits if needed.

Integration Workflow

  1. Submit invoice for processing – Upload the supplier invoice document for a given externalOrgId.
  2. Handle the completion webhook – Ledgentic notifies your webhook endpoint when processing finishes (successfully or with errors).
  3. Retrieve bookkeeping data – Fetch the processed invoice and journal entry data using the original externalInvoiceId.

Repeat the workflow for each invoice you need to process. You can also list invoices, submit bookkeeping change requests, and delete drafts when required.

Submit a Supplier Invoice

Endpoint

POST /partner/v2/organizations/{externalOrgId}/supplier-invoices

Ensure the organization is onboarded and the externalOrgId is registered in the admin interface before making this request.

curl -X POST \
"https://api.ledgentic.com/partner/v2/organizations/acme-001/supplier-invoices" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalInvoiceId": "inv-87342",
"fileName": "supplier_invoice_87342.pdf",
"base64File": "JVBERi0xLjQKJcfs..."
}'
FieldTypeRequiredDescription
externalInvoiceIdstringYesYour unique invoice identifier (externalInvoiceId).
fileNamestringYesName of the uploaded invoice file.
base64FilestringYesBase64-encoded binary contents (PDF, image).

Response 201

{
"id": "inv_abc123",
"externalOrgId": "acme-001",
"fileName": "supplier_invoice_87342.pdf",
"state": "PENDING",
"status": "NOT_STARTED",
"createdAt": "2024-01-15T10:30:00Z",
"message": "Invoice uploaded successfully for organization 'acme-001'",
"metadata": {
"source": "PARTNER_API",
"partnerOrgId": "partner-42",
"uploadedAt": "2024-01-15T10:30:00Z"
}
}

Note: Batch uploads are not yet supported. Submit one invoice per request and parallelize responsibly within your rate limits.

Supplier Invoice States

The state field represents the business-level state of an invoice. Use this field for all business logic decisions.

StatePhaseWhat It MeansYour Action
PENDINGProcessingInvoice uploaded, waiting to startWait for webhook
PROCESSINGProcessingAI analysis in progressWait for webhook
PROCESSEDProcessingFully processedCall GET /bookkeeping
PARTIALLY_PROCESSEDProcessingPartially processedCall GET /bookkeeping (some data available)
ERRORProcessingFailedCheck error, re-process if needed
INVALIDProcessingNot an invoiceDelete and re-upload correct doc
NEEDS_REVIEWProcessingHuman review neededWait for manual resolution
IN_APPROVALApprovalAwaiting approvalWait (or approve via UI)
APPROVEDApprovalApprovedReady for payment/export
REJECTEDApprovalRejectedReview and resubmit
CANCELLEDApprovalCancelledNo action needed
PAIDPaymentPayment completeArchive
PARTIALLY_PAIDPaymentPartial paymentTrack remaining
OVERDUEPaymentPast due dateFollow up

Note: The analysisStatus field provides internal processing details and should only be used for diagnostics. See Analysis Status Reference if needed.

Webhook Events

Ledgentic delivers webhook events to notify your system of invoice processing updates. Configure webhook endpoints in the Ledgentic admin app and store the webhook secret securely.

Event Types

Event TypeDescription
PROCESSING_COMPLETERecommended. Unified event for all terminal processing states.
INVOICE_RECEIVEDInvoice received and queued for processing.
OCR_COMPLETEDOCR extraction completed successfully.
SUPPLIER_MATCHEDSupplier identified and matched to master data.
BOOKKEEPING_READYJournal entry ready for retrieval.
PROCESSING_ERRORProcessing failed at some step.
INVALID_DOCUMENTDocument classified as not a valid invoice.
TESTTest webhook for connectivity verification.

Recommendation: Subscribe to PROCESSING_COMPLETE for a simplified integration. This event fires for all terminal states (PROCESSED, PARTIALLY_PROCESSED, ERROR, INVALID) and includes all necessary data to determine the next action.

Webhook Payload Structure

{
"eventType": "PROCESSING_COMPLETE",
"externalOrgId": "acme-001",
"externalInvoiceId": "inv-87342",
"state": "PROCESSED",
"analysisStatus": "SUCCESS",
"message": "Invoice processing completed successfully, ready for sync",
"dataEndpoint": "/partner/v2/organizations/acme-001/supplier-invoices/inv-87342/bookkeeping",
"timestamp": "2024-01-15T10:45:00Z",
"eventId": "wh_evt_123456"
}

Webhook Field Reference

FieldTypeRequiredDescription
eventTypestringYesThe type of event (see Event Types table above).
externalOrgIdstringYesYour organization identifier.
externalInvoiceIdstringYesYour invoice identifier (the externalInvoiceId you provided at upload).
invoiceIdstringYesInternal Ledgentic invoice ID. Deprecated — use externalInvoiceId.
journalEntryIdstringNoJournal entry ID, if available. Deprecated — available in bookkeeping response.
statusstringYesEvent outcome (e.g. SUCCESS, PROCESSING_ERROR, INVALID_DOCUMENT). Deprecated — use state.
statestringNoCurrent business state of the invoice (see Supplier Invoice States). Recommended.
analysisStatusstringNoDetailed processing status (see Analysis Status Reference).
messagestringNoHuman-readable description of the event (includes error details on failure).
errorstringNoError message if processing failed. Deprecated — use message.
failedStepstringNoStep that failed during processing. Deprecated — use analysisStatus.
dataEndpointstringYesAPI path to retrieve the bookkeeping data.
timestampstringYesISO 8601 timestamp when the event occurred.
eventIdstringNoUnique identifier for this webhook event (use for idempotency).

Handling PROCESSING_COMPLETE

The PROCESSING_COMPLETE event is the recommended way to handle invoice processing results. Use the state field to determine the appropriate action:

app.post("/webhooks/ledgentic", async (req, res) => {
const rawBody = req.rawBody;
const signature = req.headers["x-webhook-signature"];

// Verify signature first (see Webhook Signature Verification)
const verification = verifyWebhookSignature(rawBody, signature, WEBHOOK_SECRET);
if (!verification.valid) {
return res.status(401).json({ error: verification.reason });
}

const event = req.body;

// Use eventId for idempotency
if (await isEventAlreadyProcessed(event.eventId)) {
return res.status(200).json({ status: "already_processed" });
}

if (event.eventType === "PROCESSING_COMPLETE") {
switch (event.state) {
case "PROCESSED":
case "PARTIALLY_PROCESSED":
// Data available - fetch the bookkeeping data
// PARTIALLY_PROCESSED means some steps completed; data is still usable
const bookkeepingData = await fetchBookkeeping(
event.externalOrgId,
event.externalInvoiceId
);
await syncToAccountingSystem(bookkeepingData);
break;

case "ERROR":
// Processing failed early
console.error(`Invoice ${event.externalInvoiceId} failed: ${event.message}`);
await handleProcessingError(event);
break;

case "INVALID":
// Not a valid invoice document
console.warn(`Document ${event.externalInvoiceId} is not a valid invoice`);
await markAsInvalidDocument(event);
break;

default:
console.log(`Unhandled invoice state: ${event.state}`);
}
}

await markEventAsProcessed(event.eventId);
res.status(200).json({ status: "ok" });
});

Example Webhook Payloads

PARTIALLY_PROCESSED example (some steps completed, data available):

{
"eventType": "PROCESSING_COMPLETE",
"externalOrgId": "acme-001",
"externalInvoiceId": "inv-87342",
"state": "PARTIALLY_PROCESSED",
"analysisStatus": "ACCOUNTING_ERROR",
"message": "Invoice data extracted, journal entry incomplete — unable to determine VAT classification for line item",
"dataEndpoint": "/partner/v2/organizations/acme-001/supplier-invoices/inv-87342/bookkeeping",
"timestamp": "2024-01-15T10:45:00Z",
"eventId": "wh_evt_123457"
}

Note: Even with PARTIALLY_PROCESSED, the dataEndpoint is available. You can fetch extracted invoice data; only specific steps (like journal entry generation) may be incomplete.

ERROR example (early processing failure):

{
"eventType": "PROCESSING_COMPLETE",
"externalOrgId": "acme-001",
"externalInvoiceId": "inv-87343",
"state": "ERROR",
"analysisStatus": "OCR_ERROR",
"message": "Failed to extract text from document — document appears to be corrupted or encrypted",
"dataEndpoint": "/partner/v2/organizations/acme-001/supplier-invoices/inv-87343/bookkeeping",
"timestamp": "2024-01-15T10:46:00Z",
"eventId": "wh_evt_123458"
}

INVALID example (not a valid invoice):

{
"eventType": "PROCESSING_COMPLETE",
"externalOrgId": "acme-001",
"externalInvoiceId": "inv-87344",
"state": "INVALID",
"analysisStatus": "SUCCESS",
"message": "Document classified as not a valid invoice — document appears to be a receipt, not a supplier invoice",
"dataEndpoint": "/partner/v2/organizations/acme-001/supplier-invoices/inv-87344/bookkeeping",
"timestamp": "2024-01-15T10:47:00Z",
"eventId": "wh_evt_123459"
}

Webhook Signature Verification

Ledgentic computes the webhook signature as HMAC_SHA256( {timestamp}.{raw_request_body} ) with your webhook secret.

  1. Read the X-Webhook-Signature header and parse t={timestamp},v1={signature} pairs.
  2. Concatenate the timestamp and raw JSON body with a dot separator to form the signed message.
  3. Hash the message with HMAC-SHA256 using your webhook secret.
  4. Compare the computed digest with the provided signature using a constant-time equality check.
  5. Reject requests where the timestamp is older than your configured tolerance (typically ≤5 minutes) to prevent replay attacks.

Node.js example

import crypto from "node:crypto";

export function verifyWebhookSignature(rawBody, signatureHeader, secret) {
const parts = signatureHeader.split(",");
const timestampPart = parts.find((part) => part.startsWith("t="));
const signaturePart = parts.find((part) => part.startsWith("v1="));

if (!timestampPart || !signaturePart) {
return { valid: false, reason: "Missing timestamp or signature" };
}

const timestamp = timestampPart.replace("t=", "");
const timestampNumber = Number(timestamp);
if (!Number.isFinite(timestampNumber)) {
return { valid: false, reason: "Invalid timestamp" };
}

const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestampNumber) > 5 * 60) {
return { valid: false, reason: "Timestamp outside tolerance" };
}

const providedSignature = signaturePart.replace("v1=", "");
const message = `${timestamp}.${rawBody}`;

const expectedSignature = crypto
.createHmac("sha256", secret)
.update(message)
.digest("hex");

const providedBuffer = Buffer.from(providedSignature, "hex");
const expectedBuffer = Buffer.from(expectedSignature, "hex");

const signaturesMatch =
providedBuffer.length === expectedBuffer.length &&
crypto.timingSafeEqual(providedBuffer, expectedBuffer);

return { valid: signaturesMatch, timestamp: timestampNumber };
}

Implement these checks in your webhook handler using your preferred language and ensure stale or invalid signatures are rejected before business logic runs.

Retrieve Processed Invoice & Bookkeeping Data

Endpoint

GET /partner/v2/organizations/{externalOrgId}/supplier-invoices/{externalInvoiceId}/bookkeeping

Use your invoice externalInvoiceId in place of {externalInvoiceId} when building the request URL.

curl \
"https://api.ledgentic.com/partner/v2/organizations/acme-001/supplier-invoices/inv-87342/bookkeeping" \
-H "X-API-Key: YOUR_API_KEY"

Bookkeeping Response Structure

The response contains four main objects:

  • supplierInvoice - Extracted invoice data including amounts, dates, line items, and payment methods
  • supplier - Matched supplier master data (name, VAT number, address)
  • bookkeeping - Aggregated journal entry ready for GL posting
  • detailedBookkeeping - Pre-aggregation view with line-item detail (useful for reconciliation)
{
"supplierInvoice": { /* Invoice details */ },
"supplier": { /* Supplier information */ },
"bookkeeping": { /* Aggregated journal entry */ },
"detailedBookkeeping": { /* Pre-aggregation view */ }
}

Tip: Use the state field on supplierInvoice to determine if data is ready. See the API Reference for complete field documentation.

List Supplier Invoices

Endpoint

GET /partner/v2/organizations/{externalOrgId}/supplier-invoices

Use this endpoint to page through all invoices for an organization or filter by state.

QueryTypeDescription
pagenumber1-based page index (default: 1).
limitnumberItems per page (default: 20, max: 100).
statestringRecommended. Filter by invoice state (see Supplier Invoice States).
statusstringDeprecated. Filter by analysis status. Use state instead.
showAllbooleanSet to true to include invoices without an external ID (default: false).

The response returns an items array and meta pagination object so you can iterate through result pages.

Submit Bookkeeping Change Requests

When you need to adjust extracted data or journal entries, submit a change request.

Endpoint

PATCH /partner/v2/organizations/{externalOrgId}/supplier-invoices/{externalInvoiceId}/bookkeeping

Use the same externalInvoiceId value you supplied during upload in the {externalInvoiceId} placeholder.

curl -X PATCH \
"https://api.ledgentic.com/partner/v2/organizations/acme-001/supplier-invoices/inv-87342/bookkeeping" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"supplierInvoice": {
"invoiceNumber": "87342",
"dueDate": "2025-05-28",
"currency": "SEK"
},
"bookkeeping": {
"lines": [
{
"accountNumber": "2440",
"credit": "12500.00",
"description": "Accounts payable"
}
]
}
}'

The API responds with a CreateChangeRequestResponseDto containing the change request ID and current status. You can poll for updates using:

  • GET /partner/v2/organizations/{externalOrgId}/supplier-invoices/{externalInvoiceId}/change-requests
  • GET /partner/v2/organizations/{externalOrgId}/supplier-invoices/{externalInvoiceId}/change-requests/{changeRequestId}

Use your invoice externalInvoiceId for the {externalInvoiceId} placeholder when calling these endpoints.

Multiple change requests can coexist for the same invoice. When a new request is submitted, it joins the review queue independently—track each changeRequestId until it resolves to APPLIED, REJECTED, or EXPIRED.

Delete an Invoice

Delete invoices that are still in draft bookkeeping state with:

DELETE /partner/v2/organizations/{externalOrgId}/supplier-invoices/{externalInvoiceId}

Replace {externalInvoiceId} with your invoice externalInvoiceId to target the correct record.

A successful deletion returns HTTP 200. Attempts to delete invoices with posted or reviewed journal entries result in HTTP 403, and unknown invoices return HTTP 404.

Analysis Status Reference (Internal)

Deprecation Notice: The analysisStatus field provides internal processing details and should be used for diagnostics only. For business logic and workflow decisions, use the state field instead (see Supplier Invoice States).

Processing States

These statuses indicate active processing steps:

StatusMeaning
NOT_STARTEDInvoice accepted and queued.
SUPPLIER_EXTRACTION_PROCESSINGSupplier data extraction in progress.
OCR_PROCESSINGOCR is running.
PROCESSINGBookkeeping analysis running.
ACCOUNTING_PROCESSINGJournal entry construction in progress.

Terminal States

These statuses indicate processing has completed or failed:

StatusMeaning
SUCCESSProcessing complete; data available.
OCR_DONEOCR finished successfully.
ACCOUNTING_DONEAccounting step finished.
WAITING_FOR_HUMANManual review required.

Error States

StatusMeaning
SUPPLIER_EXTRACTION_ERRORSupplier extraction failed.
OCR_ERROROCR failed.
PROCESSING_ERRORBookkeeping analysis failed.
ACCOUNTING_ERRORAccounting step failed.
ERRORGeneral processing failure.

Migration Guide

This section helps existing integrators update their implementations to use the v2 API fields and patterns.

Field Renames

The following fields have been renamed for clarity:

Old FieldNew FieldNotes
externalIdexternalInvoiceIdIn webhook payloads and request body.
bookkeepingEndpointdataEndpointIn webhook payloads.
completedAttimestampIn webhook payloads.

Deprecated Fields

The following fields are still present in webhook payloads but are deprecated and will be removed in a future version. Migrate to the recommended replacements at your convenience.

Deprecated FieldReplacementNotes
invoiceIdexternalInvoiceIdUse externalInvoiceId to reference invoices.
journalEntryIdAvailable in the bookkeeping response instead.
statusstateUse state for business logic decisions.
errormessageError details are now included in message.
failedStepanalysisStatusUse analysisStatus for diagnostics.
analysisStatus (in API responses)stateUse state for workflow; keep analysisStatus for diagnostics only.

New Fields Added

The following fields are new in v2:

FieldLocationDescription
eventTypeWebhook payloadType of webhook event.
stateWebhook payload & API responsesBusiness state of the invoice.
messageWebhook payloadHuman-readable event description (includes error details on failure).
eventIdWebhook payloadUnique event ID for idempotency.

Migration Checklist

  1. Update webhook handlers to parse the new payload structure (new fields are additive)
  2. Start using state instead of status for determining next actions (both fields are still sent)
  3. Start using message instead of error for failure details (both fields are still sent)
  4. Migrate away from deprecated fields (invoiceId, journalEntryId, status, error, failedStep) — they are still present but will be removed in a future version
  5. Implement idempotency using the eventId field
  6. Update list queries to use state parameter instead of status
  7. Handle new event types especially PROCESSING_COMPLETE

Error Handling & Retries

  • Rely on HTTP status codes for coarse error handling (400 invalid payload, 401/403 authentication issues, 404 resource missing, 429 rate limit, 500 unexpected error).
  • Responses include structured error payloads to help you recover quickly; log them for diagnostics.
  • Webhooks should be idempotent. If Ledgentic does not receive a 2xx response, it will retry delivery with exponential backoff. Always verify the signature on each retry.
  • HTTP 429 responses contain a Retry-After header (seconds) you can honor before resubmitting.