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 (
externalId) – 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-Signatureheader (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
- Submit invoice for processing – Upload the supplier invoice document for a given
externalOrgId. - Handle the completion webhook – Ledgentic notifies your webhook endpoint when processing finishes (successfully or with errors).
- Retrieve bookkeeping data – Fetch the processed invoice and journal entry data using the original
externalId.
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 '{
"externalId": "inv-87342",
"fileName": "supplier_invoice_87342.pdf",
"base64File": "JVBERi0xLjQKJcfs..."
}'
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | Yes | Your unique invoice identifier (externalId). |
fileName | string | Yes | Name of the uploaded invoice file. |
base64File | string | Yes | Base64-encoded binary contents (PDF, image). |
Response 201
{
"id": "inv_abc123",
"externalOrgId": "acme-001",
"fileName": "supplier_invoice_87342.pdf",
"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.
Processing Status Reference
Invoices move through the following status values:
| Status | Meaning |
|---|---|
NOT_STARTED | Invoice accepted and queued. |
SUPPLIER_EXTRACTION_PROCESSING | Supplier data extraction in progress. |
SUPPLIER_EXTRACTION_ERROR | Supplier extraction failed. |
OCR_PROCESSING | OCR is running. |
OCR_DONE / OCR_ERROR | OCR finished successfully or failed. |
PROCESSING / PROCESSING_ERROR | Bookkeeping analysis running or failed. |
ACCOUNTING_PROCESSING | Journal entry construction in progress. |
ACCOUNTING_DONE / ACCOUNTING_ERROR | Accounting step finished or failed. |
WAITING_FOR_HUMAN | Manual review required. |
SUCCESS | Processing complete; data available for retrieval. |
ERROR | Processing failed; investigate error details. |
Configure and Verify Webhooks
Create webhook endpoints in the Ledgentic admin app. Each webhook has a dedicated secret that you must store securely.
- Ledgentic delivers events with the
externalOrgIdandexternalIdso that you can correlate them in your system. - Every payload is signed with HMAC-SHA256 using the webhook secret and sent in the
X-Webhook-Signatureheader (t={timestamp},v1={signature}). - Validate the signature and ensure the timestamp is within an acceptable tolerance (for example, five minutes) before acknowledging the request. Return HTTP 200 within 30 seconds.
- Configuring a webhook on the parent organization is sufficient; child organizations that inherit the parent webhook will receive callbacks at the same endpoint.
Example webhook payload
{
"externalOrgId": "acme-001",
"externalId": "inv-87342",
"status": "SUCCESS",
"completedAt": "2024-01-15T10:45:00Z",
"bookkeepingEndpoint": "/partner/v2/organizations/acme-001/supplier-invoices/inv-87342/bookkeeping"
}
Webhook Signature Verification
Ledgentic computes the webhook signature as HMAC_SHA256( {timestamp}.{raw_request_body} ) with your webhook secret.
- Read the
X-Webhook-Signatureheader and parset={timestamp},v1={signature}pairs. - Concatenate the timestamp and raw JSON body with a dot separator to form the signed message.
- Hash the message with HMAC-SHA256 using your webhook secret.
- Compare the computed digest with the provided signature using a constant-time equality check.
- 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.
If the webhook indicates failure (status ending with _ERROR), fetch the bookkeeping resource to inspect the error details and decide whether to resubmit or create a change request.
Retrieve Processed Invoice & Bookkeeping Data
Endpoint
GET /partner/v2/organizations/{externalOrgId}/supplier-invoices/{externalInvoiceId}/bookkeeping
Use your invoice externalId 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"
The response bundles supplier invoice metadata, supplier master data, and the generated journal entry. Fields such as line items, VAT breakdowns, and journal lines are included only when available.
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 status.
| Query | Type | Description |
|---|---|---|
page | number | 1-based page index (default: 1). |
limit | number | Items per page (default: 20, max: 100). |
status | string | Filter by processing status (see table above). |
showAll | boolean | Set 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 externalId 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-requestsGET /partner/v2/organizations/{externalOrgId}/supplier-invoices/{externalInvoiceId}/change-requests/{changeRequestId}
Use your invoice externalId 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 externalId 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.
Error Handling & Retries
- Rely on HTTP status codes for coarse error handling (
400invalid payload,401/403authentication issues,404resource missing,429rate limit,500unexpected 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-Afterheader (seconds) you can honor before resubmitting.