API

The same data and calculations as the site, as a read-only JSON API. Every tool computes through the same code as its page, so an answer here is the page's answer, and the datasets are the ones the site shows, with their versions and sources.

It is for people who build their own tools or checks around export quotes, freight volume and tax refunds, and for software that needs the figures without scraping a page. Nothing here writes anything: you send inputs and get a result.

Base addresshttps://chukoudan.com/v1/

Endpoints

Every endpoint is under /v1/ and answers JSON.

MethodAddressWhat it doesOptional parameters
GET/v1/datasetsThe datasets the site may show, each with its current version.?lang
GET/v1/datasets/{id}One dataset and all its versions, newest first.?lang
GET/v1/datasets/{id}/currentThe rows of the current version, in pages.?offset ?limit ?lang
GET/v1/datasets/{id}/{version}The rows of one version, in pages; a published version never changes.?offset ?limit ?lang
GET/v1/toolsThe tools: ids, names and the data each one reads.?lang
GET/v1/tools/{id}One tool: its names, inputs and the datasets it reads.?lang
POST/v1/tools/{id}/computeRun a tool on your inputs; the answer is the one the site page gives. Send the inputs as JSON in the body.?lang
GET/v1/openapi.jsonThis API as an OpenAPI 3.1 document.–

No key, no account

There is nothing to sign up for: send a request. The API sets no cookie and does not store what you send to a calculation. A limit for each sender takes the place of a key.

The limit

Each sender may make a burst of up to 60 requests, refilled at 60 a minute. A sender is an IP address (for IPv6, its /64 network). Every answer says where you stand in its headers: RateLimit-Limit is the size of a full bucket, RateLimit-Remaining what is left, RateLimit-Reset the seconds until it is full again. When it is empty the answer is 429 with a Retry-After header: the seconds to wait before asking again.

A page of rows counts as more than one request: one for each 1000 rows it asks for. A request may ask for at most 5000 rows (limit), and offset skips rows. The body of a calculation may be up to 256 KB and must be JSON.

What a sender whose bucket held one request gets on its second request:

curl 'https://chukoudan.com/v1/tools'

HTTP 429
access-control-allow-origin: *
cache-control: no-store
content-type: application/json
ratelimit-limit: 1
ratelimit-remaining: 0
ratelimit-reset: 60
retry-after: 60

{
  "issues": [
    {
      "path": "",
      "code": "api.rate_limited",
      "values": {}
    }
  ],
  "provenance": {
    "api": "v1",
    "site": "chukoudan.com",
    "generated_at": "2026-10-07T04:00:00.000Z",
    "language": "zh-CN",
    "datasets": [],
    "license": {
      "status": "draft",
      "ours": "dataset.license.ours",
      "underlying": "dataset.license.underlying"
    }
  }
}

Caching

Answers that may be kept carry an ETag and Cache-Control: the dataset list, a dataset’s versions and the current version’s rows for 1 minute, the tools for 5 minutes, the rows of a version that is not current for 1 day. Send If-None-Match and, if nothing changed, you get a 304 with no body (still counted against the limit). Errors and every calculation are no-store.

From a browser

Any website may call these routes from a visitor's browser (GET and POST, no credentials). Calculations accept JSON only: a request of another content type is refused with 415.

What an answer looks like

A calculation returns tool (its id), outputs (each figure by name), steps (the worked example, line by line), warnings and provenance. An input that is refused returns 400 with issues instead of outputs. There are no sentences in an answer: steps, warnings and issues carry copy keys (like cbm.more_than_20gp) and the values that fill them, so you write the words in your own language.

Figures

Every figure is tagged so you know how to write it, and numbers are exact decimal strings, never floating point. A quantity has value, unit and sometimes places (keep that many decimals: pad with zeros to it, do not round again); money has amount and currency; ratio is a fraction (0.13 is 13 %); rate says per units of base cost value units of quote; count, code (a token from a fixed list) and date complete the set.

Where the figures come from

Every answer, errors included, carries provenance: api (the version, v1), site, generated_at, language, datasets (each dataset version the answer drew on, with its published_at day, its origin and a source line you can cite) and license. Adding lang=zh-CN, en, ja or ko to any request chooses the language of the source lines and changes nothing else. The license is a draft until its text is approved: for now it holds keys, not words.

Errors

An error answer has issues (each with path, code and values) and provenance. 400: the input was refused (path says which field, code why; codes are copy keys); 404: no such tool, dataset or version; 413: the body is too big; 415: it is not JSON; 429: over the limit; 500: something failed on our side, and the answer says nothing more about the cause.

A calculation

POST a tool's inputs to /v1/tools/ID/compute. This example asks for the cubic metres of 450 cartons of 60 × 40 × 50 cm at 18 kg, shipped LCL. The same inputs on the CBM page give the same figures.

Request

curl -X POST 'https://chukoudan.com/v1/tools/cbm/compute' -H 'content-type: application/json' -d '{"inputs":{"cartons":[{"length":"60","width":"40","height":"50","grossWeight":"18","quantity":"450"}],"lengthUnit":"cm","weightUnit":"kg","mode":"lcl"}}'

Response

HTTP 200
access-control-allow-origin: *
cache-control: no-store
content-type: application/json
ratelimit-limit: 60
ratelimit-remaining: 59
ratelimit-reset: 1

{
  "tool": "cbm",
  "outputs": {
    "totalVolume": {
      "kind": "quantity",
      "value": "54",
      "unit": "m3"
    },
    "totalGrossWeight": {
      "kind": "quantity",
      "value": "8100",
      "unit": "kg"
    },
    "perCartonVolume": {
      "kind": "quantity",
      "value": "0.12",
      "unit": "m3"
    },
    "airVolumetric": {
      "kind": "quantity",
      "value": "9000",
      "unit": "kg"
    },
    "airChargeable": {
      "kind": "quantity",
      "value": "9000",
      "unit": "kg"
    },
    "airBy": {
      "kind": "code",
      "value": "volume"
    },
    "expressVolumetric": {
      "kind": "quantity",
      "value": "10800",
      "unit": "kg"
    },
    "expressChargeable": {
      "kind": "quantity",
      "value": "10800",
      "unit": "kg"
    },
    "expressBy": {
      "kind": "code",
      "value": "volume"
    },
    "lclChargeable": {
      "kind": "quantity",
      "value": "54",
      "unit": "m3"
    },
    "lclBy": {
      "kind": "code",
      "value": "volume"
    },
    "chargeable": {
      "kind": "quantity",
      "value": "54",
      "unit": "m3"
    }
  },
  "steps": [
    {
      "id": "cbm.row",
      "values": {
        "row": {
          "kind": "count",
          "value": "1"
        },
        "length": {
          "kind": "quantity",
          "value": "60",
          "unit": "cm"
        },
        "width": {
          "kind": "quantity",
          "value": "40",
          "unit": "cm"
        },
        "height": {
          "kind": "quantity",
          "value": "50",
          "unit": "cm"
        },
        "grossWeight": {
          "kind": "quantity",
          "value": "18",
          "unit": "kg"
        },
        "quantity": {
          "kind": "count",
          "value": "450"
        },
        "cartonVolume": {
          "kind": "quantity",
          "value": "120000",
          "unit": "cm3"
        },
        "perCartonVolume": {
          "kind": "quantity",
          "value": "0.12",
          "unit": "m3"
        },
        "volume": {
          "kind": "quantity",
          "value": "54",
          "unit": "m3"
        },
        "weight": {
          "kind": "quantity",
          "value": "8100",
          "unit": "kg"
        },
        "airVolumetric": {
          "kind": "quantity",
          "value": "9000",
          "unit": "kg"
        },
        "airDivisor": {
          "kind": "count",
          "value": "6000",
          "grouping": false
        },
        "expressVolumetric": {
          "kind": "quantity",
          "value": "10800",
          "unit": "kg"
        },
        "expressDivisor": {
          "kind": "count",
          "value": "5000",
          "grouping": false
        }
      }
    },
    {
      "id": "cbm.total",
      "values": {
        "totalVolume": {
          "kind": "quantity",
          "value": "54",
          "unit": "m3"
        },
        "totalGrossWeight": {
          "kind": "quantity",
          "value": "8100",
          "unit": "kg"
        }
      }
    },
    {
      "id": "cbm.lcl",
      "values": {
        "volume": {
          "kind": "quantity",
          "value": "54",
          "unit": "m3"
        },
        "weight": {
          "kind": "quantity",
          "value": "8.1",
          "unit": "t",
          "places": 3
        },
        "chargeable": {
          "kind": "quantity",
          "value": "54",
          "unit": "m3"
        },
        "by": {
          "kind": "code",
          "value": "volume"
        }
      }
    },
    {
      "id": "cbm.air",
      "values": {
        "volumetric": {
          "kind": "quantity",
          "value": "9000",
          "unit": "kg"
        },
        "weight": {
          "kind": "quantity",
          "value": "8100",
          "unit": "kg"
        },
        "chargeable": {
          "kind": "quantity",
          "value": "9000",
          "unit": "kg"
        },
        "by": {
          "kind": "code",
          "value": "volume"
        }
      }
    },
    {
      "id": "cbm.express",
      "values": {
        "volumetric": {
          "kind": "quantity",
          "value": "10800",
          "unit": "kg"
        },
        "weight": {
          "kind": "quantity",
          "value": "8100",
          "unit": "kg"
        },
        "chargeable": {
          "kind": "quantity",
          "value": "10800",
          "unit": "kg"
        },
        "by": {
          "kind": "code",
          "value": "volume"
        }
      }
    }
  ],
  "warnings": [
    {
      "code": "cbm.more_than_20gp",
      "values": {
        "totalVolume": {
          "kind": "quantity",
          "value": "54",
          "unit": "m3"
        },
        "gp20Volume": {
          "kind": "quantity",
          "value": "33.115",
          "unit": "m3"
        }
      },
      "level": "hint",
      "next": "zhuanggui"
    }
  ],
  "provenance": {
    "api": "v1",
    "site": "chukoudan.com",
    "generated_at": "2026-10-07T04:00:00.000Z",
    "language": "zh-CN",
    "datasets": [
      {
        "id": "divisors",
        "version": "2026-09-24-2",
        "published_at": "2026-09-24",
        "origin": "chukoudan-divisors",
        "source": "体积重系数:常用系数(2026-09-24 整理),以承运商为准"
      },
      {
        "id": "containers",
        "version": "2026-09-24-2",
        "published_at": "2026-09-24",
        "origin": "chukoudan-container-reference",
        "source": "柜型尺寸和最大载重:常用参考值(2026-09-24 整理),以承运商为准"
      }
    ],
    "license": {
      "status": "draft",
      "ours": "dataset.license.ours",
      "underlying": "dataset.license.underlying"
    }
  }
}

The warning is a hint (cbm.more_than_20gp): 54 m³ does not fit one 20GP, and next is the slug of a page that carries on from there.

A dataset

GET /v1/datasets lists the datasets the site shows; /v1/datasets/ID lists a dataset's versions; /v1/datasets/ID/current gives the rows of the current version and /v1/datasets/ID/VERSION a named one, which never changes once published. Each version has a content_hash, so you can tell whether what you hold is the same. Dataset answers link the CSV and JSON downloads.

Request

curl 'https://chukoudan.com/v1/datasets/ports/current?limit=2&lang=en'

Response

HTTP 200
access-control-allow-origin: *
cache-control: public, max-age=60
content-type: application/json
etag: W/"5e81138d2bd087290f4dbc776003b1cf"
ratelimit-limit: 60
ratelimit-remaining: 59
ratelimit-reset: 1

{
  "dataset": "ports",
  "version": "2026-09-25",
  "published_at": "2026-09-25",
  "fetched_at": "2026-09-24T19:59:07Z",
  "content_hash": "sha256:a222c1be3c0bd7146efc3a2b6a5f9ac0d246edfd4570b6075cfd6b0494eb37af",
  "rows": 38,
  "columns": [
    "id",
    "name.zh-CN",
    "name.en",
    "name.ja",
    "name.ko",
    "region",
    "search.zh",
    "search.pinyin",
    "search.en"
  ],
  "current": true,
  "links": {
    "self": "/v1/datasets/ports/2026-09-25",
    "csv": "/data/ports/2026-09-25.csv",
    "json": "/data/ports/2026-09-25.json"
  },
  "license": {
    "ours": "dataset.license.ours",
    "underlying": "dataset.license.underlying.free",
    "draft": true
  },
  "offset": 0,
  "limit": 2,
  "next": 2,
  "data": [
    {
      "id": "shanghai",
      "name": {
        "zh-CN": "上海",
        "en": "Shanghai",
        "ja": "上海",
        "ko": "상하이"
      },
      "region": "上海",
      "search": {
        "zh": [
          "上海港",
          "洋山"
        ],
        "pinyin": [
          "sh",
          "shg"
        ],
        "en": [
          "Yangshan"
        ]
      }
    },
    {
      "id": "ningbo",
      "name": {
        "zh-CN": "宁波",
        "en": "Ningbo",
        "ja": "寧波",
        "ko": "닝보"
      },
      "region": "浙江",
      "search": {
        "zh": [
          "宁波舟山",
          "北仑",
          "舟山"
        ],
        "pinyin": [
          "nb",
          "nbzs"
        ],
        "en": [
          "Ningbo-Zhoushan",
          "Beilun"
        ]
      }
    }
  ],
  "provenance": {
    "api": "v1",
    "site": "chukoudan.com",
    "generated_at": "2026-10-07T04:00:00.000Z",
    "language": "en",
    "datasets": [
      {
        "id": "ports",
        "version": "2026-09-25",
        "published_at": "2026-09-25",
        "origin": "chukoudan-port-list",
        "source": null
      }
    ],
    "license": {
      "status": "draft",
      "ours": "dataset.license.ours",
      "underlying": "dataset.license.underlying"
    }
  }
}

A row keeps the stored strings exactly as published. Page with offset and limit; next is null on the last page.

These examples were captured by running the API’s own code on 2026-10-07, unedited.

The full description

GET /v1/openapi.json is the machine-readable description (OpenAPI 3.1) of every path, answer and tool input. It is generated from the tools themselves, so it cannot differ from what the API does. The tables of each tool's inputs below come from it.

/v1/openapi.json

Tools and their inputs

Each tool is at /v1/tools/ plus its id, then /compute. Below are each tool’s inputs, the figures it returns and the data it reads.

FOB/CIF/CFR quotesbaojia

Quotes and profit on all three terms, from your cost

Address
POST /v1/tools/baojia/compute
Page on this site
FOB/CIF/CFR quotes
Reads
central-parity@current, ports@current, refund-rates@current
Returns
priceFob, totalFob, commissionPriceFob*, commissionTotalFob*, profitFob, marginFob*, marginAltFob*, breakEvenFob, priceCfr*, totalCfr*, commissionPriceCfr*, commissionTotalCfr*, profitCfr*, marginCfr*, marginAltCfr*, breakEvenCfr*, priceCif*, totalCif*, commissionPriceCif*, commissionTotalCif*, profitCif*, marginCif*, marginAltCif*, breakEvenCif*, refundTotal, costTotal * (not always present)
InputTypeRequired
modechoiceoptional
pricedecimalrequired
quantitydecimalrequired
unitchoiceoptional
taxRatedecimalrequired
refundRatedecimalrequired
domesticdecimaloptional
domesticBasischoiceoptional
currencycurrencyoptional
fxRatedecimalrequired
fxPerchoiceoptional
freightdecimaloptional
freightBasischoiceoptional
containersintegeroptional
volumedecimaloptional
insuranceRatedecimaloptional
markupdecimaloptional
commissiondecimaloptional
margindecimaloptional
marginBasischoiceoptional
termchoiceoptional
quotedPricedecimaloptional
porttextoptional
CBM & volumetric weightcbm

CBM, gross weight and chargeable weight for air and express

Address
POST /v1/tools/cbm/compute
Page on this site
CBM & volumetric weight
Reads
divisors@current, containers@current
Returns
totalVolume, lclChargeable, perCartonVolume*, totalGrossWeight, airChargeable, expressChargeable, airVolumetric, expressVolumetric, chargeable, airBy, expressBy, lclBy * (not always present)
InputTypeRequired
cartonsrowsrequired
lengthUnitchoiceoptional
weightUnitchoiceoptional
modechoiceoptional
airDivisorintegeroptional
expressDivisorintegeroptional
Container loading calculatorcontainer-load

Cartons per container, containers needed, and how to stack them

Address
POST /v1/tools/container-load/compute
Page on this site
Container loading calculator
Reads
containers@current
Returns
perContainer20GP*, containers20GP*, lastContainer20GP*, rows20GP*, columns20GP*, layers20GP*, along20GP*, across20GP*, up20GP*, volumeUse20GP*, weightUse20GP*, limit20GP*, rank20GP*, spareLength20GP*, spareWidth20GP*, spareHeight20GP*, spareLengthIn20GP*, spareWidthIn20GP*, spareHeightIn20GP*, perContainer40GP*, containers40GP*, lastContainer40GP*, rows40GP*, columns40GP*, layers40GP*, along40GP*, across40GP*, up40GP*, volumeUse40GP*, weightUse40GP*, limit40GP*, rank40GP*, spareLength40GP*, spareWidth40GP*, spareHeight40GP*, spareLengthIn40GP*, spareWidthIn40GP*, spareHeightIn40GP*, perContainer40HQ*, containers40HQ*, lastContainer40HQ*, rows40HQ*, columns40HQ*, layers40HQ*, along40HQ*, across40HQ*, up40HQ*, volumeUse40HQ*, weightUse40HQ*, limit40HQ*, rank40HQ*, spareLength40HQ*, spareWidth40HQ*, spareHeight40HQ*, spareLengthIn40HQ*, spareWidthIn40HQ*, spareHeightIn40HQ*, perContainer45HQ*, containers45HQ*, lastContainer45HQ*, rows45HQ*, columns45HQ*, layers45HQ*, along45HQ*, across45HQ*, up45HQ*, volumeUse45HQ*, weightUse45HQ*, limit45HQ*, rank45HQ*, spareLength45HQ*, spareWidth45HQ*, spareHeight45HQ*, spareLengthIn45HQ*, spareWidthIn45HQ*, spareHeightIn45HQ*, best* * (not always present)
InputTypeRequired
lengthUnitchoiceoptional
weightUnitchoiceoptional
lengthdecimalrequired
widthdecimalrequired
heightdecimalrequired
grossWeightdecimalrequired
quantityintegeroptional
fillbooleanoptional
uprightbooleanoptional
maxLayersintegeroptional
containerTypechoiceoptional
Unit converterunit-convert

Length, weight and volume, with the internationally defined factors

Address
POST /v1/tools/unit-convert/compute
Page on this site
Unit converter
Reads
Nothing; rates and the like are inputs
Returns
result
InputTypeRequired
valuedecimalrequired
fromchoicerequired
tochoicerequired
Export tax refund calculatortuishui

Trading companies (exempt-and-refund) and manufacturers (exempt-credit-refund)

Address
POST /v1/tools/tuishui/compute
Page on this site
Export tax refund calculator
Reads
refund-rates@current, central-parity@current
Returns
refund*, basis*, inputVat*, costAddition*, nonCreditableOffset*, nonCreditable*, taxPayable*, endCredit*, exemptionOffset*, exemption*, exempted*, carriedForward*, taxDue* * (not always present)
InputTypeRequired
enterpriseTypechoicerequired
modechoiceoptional
exportsrowsrequired
outputTaxdecimaloptional
inputTaxdecimaloptional
carriedCreditdecimaloptional
declaredEndCreditdecimaloptional
Settlement exchange ratesfx-convert

RMB central parity, and what you receive at your own bank rate

Address
POST /v1/tools/fx-convert/compute
Page on this site
Settlement exchange rates
Reads
central-parity@current
Returns
midpoint, bankRate*, gap*, gapShown*, estimated*, atMidpoint*, atBank*, difference* * (not always present)
InputTypeRequired
currencycurrencyrequired
amountdecimaloptional
bankRatedecimaloptional
keptRatedecimaloptional
keptMidpointdecimaloptional
keptOntextoptional
FX conversion cost calculatorfx-cost

Compare banks and other options: how much RMB you actually get in a year

Address
POST /v1/tools/fx-cost/compute
Page on this site
FX conversion cost calculator
Reads
central-parity@current
Returns
annualBank, feesBank, costBank, gainBank, rankBank, annualB*, feesB*, costB*, gainB*, rankB*, annualC*, feesC*, costC*, gainC*, rankC*, annualMidpoint, best * (not always present)
InputTypeRequired
receiptsrowsrequired
bankFeedecimaloptional
useBbooleanoptional
nameBtextoptional
feePercentBdecimaloptional
feePerReceiptBdecimaloptional
useCbooleanoptional
nameCtextoptional
feePercentCdecimaloptional
feePerReceiptCdecimaloptional
Shipping mark generatormaitou

Main and side marks, printed on A4 or 100×150 mm labels

Address
POST /v1/tools/maitou/compute
Page on this site
Shipping mark generator
Reads
Nothing; rates and the like are inputs
Returns
–
InputTypeRequired
cartonsintegerrequired
lengthdecimalrequired
widthdecimalrequired
heightdecimalrequired
grossWeightdecimalrequired
netWeightdecimalrequired
linesrowsoptional
capitalsbooleanoptional

Want a file instead? Every dataset has a citable page with CSV and JSON downloads:Data sources and licence