Check a customs description in PHP
Check customs goods descriptions before you ship. Flags the vague wording EU customs (ICS2) rejects, category-only descriptions, product codes and missing materials. Here is the whole call in PHP, with the output it produced when we ran it.
Before you start
- An API key. Create a free account; it comes with free tokens every month. Put the key in an environment variable so it stays out of your code:
export BLOODWEB_KEY=your_key - PHP 7.4 or later with the curl extension, which most installs have.
The code
Save it as customs-description-check.php and run php customs-description-check.php.
<?php
$ch = curl_init('https://bloodweb.net/api/v1/customs-description-check?' . http_build_query(['descriptions' => 'Gifts']));
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('BLOODWEB_KEY')],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($body === false) {
exit(curl_error($ch) . "\n");
}
if ($status !== 200) {
exit($status . ': ' . json_decode($body, true)['error']['message'] . "\n");
}
print_r(json_decode($body, true));
What you get
Tested 19 October 2026: this exact code ran against our test copy of the API, which runs the same service code as bloodweb.net, and produced this.
The API answers with JSON, which print_r shows as an array:
{
"results": [
{
"description": "Gifts",
"level": "bad",
"issues": [
"Too vague. \"gifts\" does not say what is in the parcel, and EU customs (ICS2) rejects descriptions like this."
],
"tips": [
"\"Gift\" is a reason for sending, not a description, and goods a customer paid for are not gifts."
],
"cut": null,
"length": 5
}
],
"summary": {
"good": 0,
"warn": 0,
"bad": 1
}
}
Change the call
Every option the Customs description checker takes. Add or change them the same way as the ones in the code.
descriptionsrequired: One description per line, or a JSON array. Up to 50.max_length: Flag descriptions longer than this many characters and show where they would be cut.
If it fails
A failed call is not charged. The answer is JSON with an error object; the code above prints its message. The usual ones:
- 401
missing_keyorinvalid_key:BLOODWEB_KEYis not set in the shell that runs the code, or the key was revoked. - 400
bad_param: a value is wrong or missing. The message names the parameter. - 402
insufficient_tokens: the balance does not cover the call. Top up in the dashboard. - 429
rate_limited: too many calls at once. Wait the seconds in theRetry-Afterheader and send it again.
Every code and its fix is in the docs.
Next
- The Customs description checker page: every option, the limits, and Try it now in your browser.
- Getting started: keys, errors and rate limits for all the services.