Skip to content

Extracting Fields From a Certificate of Origin

10 min read · updated August 11, 2026

A certificate of origin is a one-page form with about a dozen boxes, and extracting it in isolation is nearly pointless. Its value to a customs broker is that it agrees with the commercial invoice and the packing list — so the extraction that matters is the one that produces fields comparable across all three documents.

The boxes, and the two that are not addresses

The general-form certificate, typically issued or stamped by a chamber of commerce, carries a stable set of boxes even though no single global layout exists: exporter or consignor, consignee, means of transport and route, country of origin, marks and numbers, number and kind of packages, description of goods, quantity, gross and net weight, invoice number and date, and the issuing body’s certification with a signature, date and stamp.

Two of those are traps. “Marks and numbers” looks like an address block and is not — it is the shipping marks stencilled on the cartons plus the container and seal numbers, and it is the field most often merged into the consignee box by a model reading top to bottom. And “means of transport and route” is free text (“By sea, Rotterdam to Santos via Algeciras”) that contains a port pair worth parsing out separately.

Where a container number appears in the marks box you get an arithmetic check for free: ISO 6346 container numbers are four letters, six digits and a check digit computed from a documented letter-value table, so a misread is detectable on the page in the same way an ISBN check digit is. Do that arithmetic; it is cheap and it catches the confusions OCR makes on stencilled marks.

HS codes are harmonised to six digits and no further

The Harmonized System is maintained by the World Customs Organization, and the part every member country shares is the first six digits: two for the chapter, two for the heading, two for the subheading. Beyond six, countries add their own digits — eight or ten in most tariffs — and those extensions are national, not international.

The extraction consequences are direct. Store the code as a string, not a number, because leading zeros are meaningful and 0901.21 is not 901.21. Store the raw code and a derived six-digit prefix, because the six-digit prefix is the only part you can compare against a code from another country’s document. And record which nomenclature the code claims to be in, because an eight-digit European CN code and a ten-digit US HTS code beginning with the same six digits are not interchangeable below that point.

The other thing to record is the edition. The WCO revises the nomenclature on a multi-year cycle — HS 2017 and HS 2022 are recent editions — and codes are created, merged and deleted at each revision. A code that was valid on a certificate issued three years ago may not exist today, and correlation tables between editions are published for exactly that reason. A validator that checks a code against the current tariff and rejects anything missing will reject historically correct documents. Check against the edition in force at the certificate’s date.

The nomenclature and its revision schedule are published by the World Customs Organization; national extensions come from each country’s own tariff authority. Treat any code list embedded in your pipeline as something with an expiry date.

Origin is not shipment, and preferential is not general

Country of origin is where the goods were produced or last substantially transformed. It is routinely different from the country the shipment left, from the exporter’s country of incorporation, and from the manufacturer’s head office address. Goods made in Vietnam, consolidated in Singapore and sold by a Dutch company are Vietnamese in origin, and all three countries appear on the page. If your schema has one country field, it will be wrong on exactly the shipments where it matters.

Model country_of_origin, country_of_export and exporter_country as three fields, normalised to ISO 3166-1 alpha-2 codes with the raw text kept. The raw text matters because “Made in EU”, “European Union” and “Various — see attached” are all things certificates actually say, and none of them normalises to a country.

The other split is between certificate types. A general (non-preferential) certificate simply attests origin. A preferential certificate is issued under a specific trade agreement and is what allows a reduced duty rate to be claimed: a EUR.1 movement certificate, a Form A under the Generalised System of Preferences, or — under USMCA — a certification of origin with a prescribed set of data elements and no prescribed form at all. That last one is the awkward case for extraction, because the document can be a paragraph on an invoice rather than a form, and the correct extraction is “are the required data elements present”, not “read box 7”.

So certificate_type and agreement belong in the schema alongside the fields, and a certificate claiming preference without naming the agreement is an exception worth raising. Which agreement it is also determines which additional fields are mandatory, so the schema is conditionally required rather than uniformly required — a shape discussed in schema edge cases.

The cross-check against the commercial invoice

This is what the extraction is for. A broker or a compliance reviewer is asking whether the documents in a shipment file describe the same shipment. The joins that are worth automating:

  • Invoice number and date on the certificate must match the commercial invoice. This is the primary key of the check and the field most often mistyped by whoever prepared the certificate.
  • Goods description should be consistent, though not identical — the certificate is usually terser. Compare on a normalised form or by embedding similarity rather than by string equality, and set the threshold expecting legitimate paraphrase.
  • HS code at six digits must match. Below six digits, a difference is expected if the two documents were prepared for different jurisdictions.
  • Quantity and net weight must match, unit conversion allowed. This catches transcription errors and partial shipments.
  • Consignee must match, with the caveat that “to order” consignments name a bank on one document and a buyer on another, legitimately.

Run the check as a set of named comparisons with individual outcomes, not as an overall pass or fail. “Certificate disagrees with invoice” sends a human back to read both documents; “net weight differs by 4kg, HS six-digit prefix matches, invoice number matches” tells them what to look at.

Where it breaks

The stamp lands on the data. Chamber of commerce certification is a physical stamp, often in blue or red, applied over the lower third of the form. It overlaps the signature block and frequently the last rows of the goods description. Colour-channel separation helps here in a way it rarely does elsewhere: the stamp ink and the printed text differ in hue, so isolating a channel before OCR recovers text the composite image loses.

The goods description continues on an annex. When there are more line items than the box holds, the certificate says “see attached” and the real content is on a continuation sheet with the certificate number in a header. Detect the reference and treat the absence of the annex as a hard error, because a certificate whose description says “as per attached” carries no goods data at all.

Numbers are formatted for a market you are not in. 1.250,50 is one thousand two hundred and fifty and a half on a European form and something else entirely if parsed with a decimal-point assumption. Decide the separator convention per documentfrom the whole page rather than per field, because a document is internally consistent even when your corpus is not.

Handwriting in the quantity boxes. Certificates are frequently completed partly by hand, and the handwritten fields are precisely the numeric ones the cross-check depends on. Route low-agreement numeric fields to review rather than to the comparison; the guidance in handwriting recognition applies directly.