INTEL
Status: blockedCLUSTERbushehr shipping company limited added — likelyStatus: blockedCLUSTERNovorossiysk-Turkish-Med Dark Fleet Cluster added — confirmedStatus: blockedCLUSTERPinnacle Petrol LLC added — likelyStatus: blockedCLUSTERArrakis Development added — likelyStatus: blockedCLUSTERExxon Global Distributor added — likelyStatus: pendingCORPUS427 entities · 63 countries
← All recipes

LC discrepancy detection in a trade-finance workflow

UCP 600 + cross-doc consistency across LC + invoice + B/L in one call.

Recipe 05 — LC discrepancy detection in a trade-finance workflow

20 minutes. Result: a single call validates a letter of credit + commercial invoice + bill of lading against UCP 600 + cross-document consistency and returns every discrepancy with the offending rule citation.

What this recipe does

Before a bank pays out on a letter of credit, every supporting document is examined for discrepancies. OilFlow's /api/v1/lc/validate runs the UCP 600 rule engine plus cross-document amount, currency, port, and date checks in one call.

Step 1 — Gather the three documents

You'll be sending structured JSON, not the source PDFs. Extract whatever fields you have:

lc = {
    "amount": 1_000_000,
    "currency": "USD",
    "beneficiary": "Acme Trading FZE",
    "port_of_loading": "Fujairah",
    "expiry_date": "2026-07-15",
}

invoice = {
    "total_amount": 1_000_000,
    "currency": "USD",
    "seller_name": "Acme Trading FZE",
    "presentation_date": "2026-06-15",
}

bl = {
    "currency": "USD",
    "port_of_loading": "Fujairah",
    "issue_date": "2026-06-01",
    "shipped_on_board": True,
}

(If your input is PDFs, run them through your existing OCR + extraction layer first.)

Step 2 — Validate

from oilflow import Client

client = Client()

report = client.lc.validate(lc=lc, invoice=invoice, bl=bl)
print(f"report_id: {report['report_id']}")
print(f"discrepancies: {len(report['discrepancies'])}")

Node

import OilFlow from "@oilflow/sdk";
const client = new OilFlow();
const report = await client.lc.validate({ lc, invoice, bl });

Step 3 — Branch on severity

blocking = [d for d in report["discrepancies"] if d["severity"] == "blocking"]
major = [d for d in report["discrepancies"] if d["severity"] == "major"]
minor = [d for d in report["discrepancies"] if d["severity"] == "minor"]

if blocking:
    print(f"REJECT: {len(blocking)} blocking discrepancies")
    for d in blocking:
        print(f"  - {d['rule']}: {d['detail']}")
elif major:
    print(f"REVIEW: {len(major)} major discrepancies (human review required)")
    for d in major:
        print(f"  - {d['rule']}: {d['detail']}")
else:
    print(f"OK: {len(minor)} minor notes only")

Severity meanings:

  • blocking — must reject the presentation (UCP 600 hard stop).
  • major — needs your reviewer's eyes. The LC officer decides whether to waive or reject.
  • minor — cosmetic; flagged for the audit trail.

Step 4 — Pull the PDF report for your audit pack

fetched = client.lc.report(report["report_id"])
print(fetched["download_url"])  # signed URL, 10-minute TTL

The PDF is suitable for inclusion in the bank's audit pack. Discrepancies are listed with rule citations.

Step 5 — Full LC officer workflow

def review_presentation(lc, invoice, bl):
    report = client.lc.validate(lc=lc, invoice=invoice, bl=bl)

    blocking = [d for d in report["discrepancies"] if d["severity"] == "blocking"]
    if blocking:
        return {
            "decision": "reject",
            "discrepancies": blocking,
            "report_url": client.lc.report(report["report_id"])["download_url"],
        }

    major = [d for d in report["discrepancies"] if d["severity"] == "major"]
    if major:
        return {
            "decision": "human_review",
            "discrepancies": major,
            "report_url": client.lc.report(report["report_id"])["download_url"],
        }

    return {"decision": "approve", "discrepancies": [], "report_url": None}

UCP 600 articles covered

The rule engine covers (non-exhaustive):

  • Art. 14(a) — Standard for examination of documents
  • Art. 14(d) — Data consistency across documents
  • Art. 14(e) — Description of goods
  • Art. 18 — Commercial invoice
  • Art. 19 — Transport documents
  • Art. 20 — Bill of lading
  • Art. 23 — Sea waybill
  • Art. 28 — Insurance documents

Each discrepancy returned cites the article number in rule.

Common gotchas

  • Date formats: pass ISO 8601 (YYYY-MM-DD). Other formats are parsed best-effort but may silently drop info.
  • Currency mismatch: a USD LC against a EUR invoice surfaces as blocking even with otherwise matching amounts.
  • Partial shipments: the engine assumes a single shipment unless the LC permits partial shipments (you'd indicate this in your LC structured input — see the OpenAPI spec for the full field list).

Next steps