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.
| Method | Address | What it does | Optional parameters |
|---|---|---|---|
| GET | /v1/datasets | The 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}/current | The 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/tools | The 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}/compute | Run 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.json | This 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.
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)
| Input | Type | Required |
|---|---|---|
mode | choice | optional |
price | decimal | required |
quantity | decimal | required |
unit | choice | optional |
taxRate | decimal | required |
refundRate | decimal | required |
domestic | decimal | optional |
domesticBasis | choice | optional |
currency | currency | optional |
fxRate | decimal | required |
fxPer | choice | optional |
freight | decimal | optional |
freightBasis | choice | optional |
containers | integer | optional |
volume | decimal | optional |
insuranceRate | decimal | optional |
markup | decimal | optional |
commission | decimal | optional |
margin | decimal | optional |
marginBasis | choice | optional |
term | choice | optional |
quotedPrice | decimal | optional |
port | text | optional |
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)
| Input | Type | Required |
|---|---|---|
cartons | rows | required |
lengthUnit | choice | optional |
weightUnit | choice | optional |
mode | choice | optional |
airDivisor | integer | optional |
expressDivisor | integer | optional |
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)
| Input | Type | Required |
|---|---|---|
lengthUnit | choice | optional |
weightUnit | choice | optional |
length | decimal | required |
width | decimal | required |
height | decimal | required |
grossWeight | decimal | required |
quantity | integer | optional |
fill | boolean | optional |
upright | boolean | optional |
maxLayers | integer | optional |
containerType | choice | optional |
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
| Input | Type | Required |
|---|---|---|
value | decimal | required |
from | choice | required |
to | choice | required |
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)
| Input | Type | Required |
|---|---|---|
enterpriseType | choice | required |
mode | choice | optional |
exports | rows | required |
outputTax | decimal | optional |
inputTax | decimal | optional |
carriedCredit | decimal | optional |
declaredEndCredit | decimal | optional |
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)
| Input | Type | Required |
|---|---|---|
currency | currency | required |
amount | decimal | optional |
bankRate | decimal | optional |
keptRate | decimal | optional |
keptMidpoint | decimal | optional |
keptOn | text | optional |
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)
| Input | Type | Required |
|---|---|---|
receipts | rows | required |
bankFee | decimal | optional |
useB | boolean | optional |
nameB | text | optional |
feePercentB | decimal | optional |
feePerReceiptB | decimal | optional |
useC | boolean | optional |
nameC | text | optional |
feePercentC | decimal | optional |
feePerReceiptC | decimal | optional |
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
- –
| Input | Type | Required |
|---|---|---|
cartons | integer | required |
length | decimal | required |
width | decimal | required |
height | decimal | required |
grossWeight | decimal | required |
netWeight | decimal | required |
lines | rows | optional |
capitals | boolean | optional |
Want a file instead? Every dataset has a citable page with CSV and JSON downloads:Data sources and licence