The match runs on two legs, both anchored on the bill:
- Bill ↔ PO — the price leg. Billed unit price vs the ordered unit price, and billed quantity vs the ordered quantity (over-ordering).
- Bill ↔ DN — the receipt leg. Billed quantity vs the quantity actually delivered (short/over delivery).
billId, poNumber, poId, delivery-note ids); the optional externalRef on each object
is the reverse pointer back to your own record.
All requests below require the
x-dolfin-api-key and x-dolfin-organisation-id headers.
See Authentication.Concepts
The flow end to end
1
Push the purchase order
POST /v1/purchase-orders with the supplier and its lines. poNumber is unique per
organisation. To re-ingest a changed PO idempotently, PUT /v1/purchase-orders/by-number/{poNumber}.2
Push delivery notes as goods arrive
POST /v1/delivery-notes with the received lines. A supplier can send many partial
deliveries against one PO — push each as its own note.3
Dolfin builds the match group
When a bill for the same supplier enters review, Dolfin lazily creates a match group
and aligns lines against both the POs and the DNs: exact
sku matches auto-allocate,
close description matches surface as suggestions to confirm, and anything else is
allocated manually. PO, DN, and bill can arrive in any order.4
Read the derived view
GET /v1/bills/{billId}/match-group (or GET /v1/match-groups/{id}) returns every bill
line with its allocations, the three variance kinds, and fuzzy PO and DN suggestions.5
Confirm suggestions and fix allocations
Confirm a suggestion or add a manual match with
POST /v1/match-groups/{id}/allocations;
unmatch with DELETE /v1/match-groups/{id}/allocations/{allocationId}.6
Resolve variances
For each varianced line,
POST /v1/match-groups/{id}/resolve records a human decision.
Once every varianced line is accept-resolved the group settles.7
The gate clears
A bill cannot leave review for approval while its match group is unresolved. Matching
never moves or pays the bill itself — it produces a fact your approval policy consumes.
Push a purchase order
id update, new lines are added, omitted lines are removed:
Push a delivery note
One note per delivery. Thesku is what lets Dolfin auto-match the received line to the bill
line; dnNumber is required.
Read the match group
The derived view is the heart of the integration. Per bill line it carries the billed figures, the PO-side numbers (allocatedNet, orderedQuantity, priceVarianceTotal), the DN-side
numbers (receivedQuantity, quantityVariance), the three has…Variance flags, the backing
allocations, and any recorded resolution. It also returns two suggestion lists —
suggestions (fuzzy PO-line matches) and dnSuggestions (fuzzy DN-line matches) — so
your UI can ask “which order?” and “which delivery?” without the caller knowing line ids.
Allocate a bill line (or confirm a suggestion)
An allocation is the single primitive for both legs. Pin a PO line for the price leg, a DN line for the receipt leg, or both at once — at least one is required. Setmethod to
Fuzzy and pass the suggestion’s confidence when you’re confirming a suggested match;
method defaults to Manual. matchedAmount is optional — omit it and the service derives
it from the bill line’s unit price.
poLineId with "method": "Fuzzy" and the
suggestion’s confidence; a DN suggestion is the same with dnLineId.
Unmatch by deleting the allocation — the allocation → line foreign keys are Restrict, so this
is the only unmatch path. The group’s status re-derives on the response:
Move a delivery line to a different bill
A delivery-note line’s received quantity is counted across every bill, whichever state those bills are in. So when a second bill legitimately covers a delivery that another bill already matched, your allocation is refused — even though that other bill is still in review. Two different situations refuse it, and your next step is the opposite in each:Match.DnLineHeld carries everything you need to release the hold, so you never have to search
for it:
A match can only be changed while the bill holding it is in
PendingReview. Once that bill is
approved its allocations are frozen and the delete returns 409 Match.BillNotInReview — handle
that as “this line can’t be reassigned”, not as a retryable failure.match_group.variance) and holds it at the approval gate
again. That is correct — it no longer covers the delivery — but it is visible to whoever is
reviewing it, so surface it rather than doing it silently.
To ask the question ahead of time rather than by hitting the error, GET /v1/delivery-notes/{id}
reports matchedQuantity and fullyMatched per line, and
GET /v1/delivery-notes/{id}/matched-bills lists the bills holding the note.
The three variances
All three are derived at read time and only flag when they exceed the organisation’s rounding tolerance:Resolve a variance
Resolution is per bill line — put thebillLineId in the body. One action settles that
line’s variance whatever its kind:
Re-posting overrides a line’s prior decision. To undo, clear the resolution — idempotent, and
the group’s status re-derives:
BillMatchResolved fact. Resolving does not auto-approve the bill: the bill stays in
review, and the match gate re-evaluates the now-clean group when you re-submit it.
The approval gate
A bill cannot leave review while its match group is unresolved. When youPOST /v1/bills/{id}/submit-review, the submit-time gate re-checks the group; a clean or
fully-resolved group lets the bill proceed to approval, an unresolved one holds it. Matching
produces the fact; your approval policy decides what to do with it.
Cross-referencing
From either side of the match you can list the bills it touches — this is also how you find which bill is holding a delivery line you want to reassign:.../close (done/fulfilled) or
.../cancel (void). Either is terminal and immutable; existing allocations are kept.
Webhooks
Drive an event-based integration off match-group lifecycle changes instead of polling. See the webhooks guide for how to subscribe, verify signatures, and handle retries.
Each payload carries a
matchGroup summary — id, billId, and status only. Fetch
GET /v1/match-groups/{id} for the full derived view with lines, allocations, and variances.
Notes
- Allocate on net. Tax is computed separately, so per-line tax rounding never shows up as a fake price variance. The PO allocation owns the net; a DN-only allocation is receipt-only.
- SKUs drive automation. Exact
skumatches auto-allocate (method: Exact). Without SKUs, every line falls to fuzzy suggestions or manual allocation — populateskuon PO and DN lines wherever you can. - Tolerance is per organisation. The rounding tolerance (default 1p) feeds both line reconciliation and the clean-match threshold, so a difference within it is treated as clean.