Skip to content

Commit 4e9521c

Browse files
danh91Ansh-Dev-Nagarclaude
authored
Release/2026.1.19 (#1002)
* docs: add carrier testing report for MyDHL * fix(shipment): rename status 'purchased' to 'created' across codebase * fix(migration): data migration purchased→created + remove choices constraint from Shipment.status * fix(mydhl): fix date parsing in rate and shipment requests and dimension null check * fix(mydhl): add additionalDetails to error response schema and parser * fix(mydhl): replace invalid PackagingType codes with DHL API spec values and make typeCode optional * fix(mydhl): correct ShippingService product codes and services.csv to match DHL API spec * fix(mydhl): resolve service name in shipment response using request context * fix: mydhl pickup creation fails with extraneous countryName key * fix: mydhl bundle multi-piece shipment labels from per-package documents * fix: mydhl add missing product codes O and C to ShippingService enum * docs(readme): add MCP server section with quick install and usage examples * docs: update mydhl carrier testing report Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(usps): resolve KeyError 'PRIORITY_MAIL' in OptionEnum value lookup (#1003) * fix(usps): resolve KeyError 'PRIORITY_MAIL' in OptionEnum value lookup OptionEnum.__call__ was using name-based enum lookup (self.type[value].name) which fails when the USPS API returns the mail class as its string value (e.g. 'PRIORITY_MAIL') rather than the enum member name ('usps_priority_mail'). Fix: replace with self.type.map(value).name_or_key — the existing karrio SDK helper that resolves by both name and value. Flow that triggered the bug: rate.py:93 stores usps_mail_class=rate.mailClass ('PRIORITY_MAIL') ShipmentPurchaseSerializer passes option through shipping_options_initializer ShippingOptions.__init__ calls OptionEnum.__call__('PRIORITY_MAIL') ShippingService['PRIORITY_MAIL'] raises KeyError (member name is usps_priority_mail) Also adds USPS OpenAPI specs to vendor/: - usps-domestic-labels.yaml (Labels API 3.9.8) - usps-domestic-prices.yaml (Domestic Prices v3) - usps-tracking.yaml (Tracking v3) - usps-international-labels.yaml (International Labels v3) - usps-addresses.yaml (Addresses v3) - usps-carrier-pickup.yaml (Carrier Pickup v3) Reported by: Noel Ferreria <nferreria@ameripharma.us> * fix(usps): normalize mailClass to karrio name in rate meta The real failure point was gateway.py:296 merging selected_rate.meta directly into payload options before calling the USPS connector: options = {**(selected_rate.meta or {}), **(payload.get('options') or {})} rate.py stored the raw USPS API value (e.g. 'PRIORITY_MAIL') in meta, which then reached OptionEnum.__call__ expecting a member name ('usps_priority_mail'). Fix: normalize at the source — map the raw API value to the karrio member name when building rate meta: usps_mail_class=ShippingService.map(rate.mailClass).name_or_key Also reverts the incorrect enum.py change — enum.py was correct all along. * fix(core): handle enum API values in OptionEnum name resolution When selected_rate.meta is merged into options (gateway.py:296), USPS stores raw API values like 'PRIORITY_MAIL' in usps_mail_class. These reach OptionEnum.__call__ where the original else-branch did a pure name-based lookup (self.type[value]) that fails for API values. Fix: three-way resolution — 1. value has .map (enum object) → type.map(value).name_or_key 2. value is a member name → type[value].name (unchanged) 3. value is an API value → type.map(value).name_or_key (fallback) rate.py meta is left as-is (raw API values) so existing tests pass. * release: 2026.1.19 --------- Co-authored-by: Ansh Dev Nagar <anshdevnagar@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e7f43ef commit 4e9521c

108 files changed

Lines changed: 14043 additions & 341 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
# Karrio 2026.1.19
2+
3+
## Changes
4+
5+
### Fix
6+
7+
- fix(usps): resolve KeyError 'PRIORITY_MAIL' in OptionEnum value lookup (#1003)
8+
- fix(carriers/mydhl): carrier fixes and improvements
9+
- fix(shipment): rename status purchased→created, remove choices constraint, enrich tracker event
10+
- fix(mydhl): add missing product codes O and C to ShippingService enum
11+
- fix(mydhl): bundle multi-piece shipment labels from per-package documents
12+
- fix(mydhl): pickup creation fails with extraneous countryName key
13+
- fix(mydhl): resolve service name in shipment response using request context
14+
- fix(mydhl): correct ShippingService product codes and services.csv to match DHL API spec
15+
- fix(mydhl): replace invalid PackagingType codes with DHL API spec values and make typeCode optional
16+
- fix(mydhl): add additionalDetails to error response schema and parser
17+
- fix(mydhl): fix date parsing in rate and shipment requests and dimension null check
18+
- fix(migration): data migration purchased→created + remove choices constraint from Shipment.status
19+
20+
### Docs
21+
22+
- docs(mydhl): update carrier testing report
23+
- docs(readme): add MCP server section with quick install and usage examples
24+
25+
---
26+
127
# Karrio 2026.1.18
228

329
## Changes

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,59 @@ Default Login: `admin@example.com` | `demo`
6262
<img alt="Developer Tools" src="screenshots/developer-tools.png" width="800" style="max-width: 100%;" />
6363
</p>
6464

65+
## MCP Server
66+
67+
Karrio ships with a built-in [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server, letting AI assistants like Claude, Cursor, and Windsurf query rates, purchase labels, track shipments, and manage carriers directly from your IDE or chat interface.
68+
69+
### Quick install
70+
71+
**Claude Desktop / Cursor / Windsurf** — add to your MCP config:
72+
73+
```json
74+
{
75+
"mcpServers": {
76+
"karrio": {
77+
"command": "uvx",
78+
"args": ["karrio-mcp"],
79+
"env": {
80+
"KARRIO_API_KEY": "your_api_key",
81+
"KARRIO_API_URL": "https://api.karrio.io"
82+
}
83+
}
84+
}
85+
}
86+
```
87+
88+
**Self-hosted instance:**
89+
90+
```json
91+
{
92+
"mcpServers": {
93+
"karrio": {
94+
"command": "uvx",
95+
"args": ["karrio-mcp"],
96+
"env": {
97+
"KARRIO_API_KEY": "your_api_key",
98+
"KARRIO_API_URL": "http://localhost:5002"
99+
}
100+
}
101+
}
102+
}
103+
```
104+
105+
### What you can do
106+
107+
Once connected, ask your AI assistant things like:
108+
109+
- _"Get me shipping rates from DHL and FedEx for a 2kg package to Germany"_
110+
- _"Purchase a UPS label for order #1234"_
111+
- _"Track shipment 1Z999AA10123456784"_
112+
- _"List my connected carrier accounts"_
113+
114+
**[Full MCP server docs and tool reference](https://www.karrio.io/docs/developing/mcp-server)**
115+
116+
---
117+
65118
## Get started
66119

67120
### Quick start demo

apps/api/karrio/server/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2026.1.18
1+
2026.1.19

apps/web/public/carrier-integrations.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/www/openapi.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ info:
1414
## Versioning
1515
1616
When backwards-incompatible changes are made to the API, a new, dated version is released.
17-
The current version is `2026.1.18`.
17+
The current version is `2026.1.19`.
1818
1919
Read our API changelog to learn more about backwards compatibility.
2020
@@ -84,7 +84,7 @@ info:
8484
All API requests must be made over [HTTPS](http://en.wikipedia.org/wiki/HTTP_Secure).
8585
API requests without authentication will also fail.
8686
title: Karrio API
87-
version: 2026.1.18
87+
version: 2026.1.19
8888
paths:
8989
/:
9090
get:

bin/deploy-hobby

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
set -e
44

5-
export KARRIO_TAG="${KARRIO_TAG:-2026.1.18}"
5+
export KARRIO_TAG="${KARRIO_TAG:-2026.1.19}"
66
export SENTRY_DSN="${SENTRY_DSN:-'https://public@sentry.example.com/1'}"
77

88
SECRET_KEY=$(head -c 28 /dev/urandom | sha224sum -b | head -c 56)
@@ -22,7 +22,7 @@ echo ""
2222
if ! [ -z "$1" ]; then
2323
export KARRIO_TAG=$1
2424
else
25-
echo "What version of Karrio would you like to install? (We default to '2026.1.18')"
25+
echo "What version of Karrio would you like to install? (We default to '2026.1.19')"
2626
echo "You can check out available versions here: https://hub.docker.com/r/karrio/server/tags"
2727
read -r KARRIO_TAG_READ
2828
if [ -z "$KARRIO_TAG_READ" ]; then

bin/deploy-insiders

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
set -e
44

5-
export KARRIO_TAG="${KARRIO_TAG:-2026.1.18}"
5+
export KARRIO_TAG="${KARRIO_TAG:-2026.1.19}"
66
export SENTRY_DSN="${SENTRY_DSN:-'https://public@sentry.example.com/1'}"
77

88
SECRET_KEY=$(head -c 28 /dev/urandom | sha224sum -b | head -c 56)

bin/upgrade-hobby

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ else
4141
fi
4242

4343
[[ -f ".env" ]] && export $(cat .env | xargs) || (echo "No .env file found. Please create it with SECRET_KEY and DOMAIN set." && exit 1)
44-
export KARRIO_TAG="${KARRIO_TAG:-2026.1.18}"
44+
export KARRIO_TAG="${KARRIO_TAG:-2026.1.19}"
4545

4646
# get karrio scripts
4747
mkdir -p ./karrio

carrier-testing/mydhl.md

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# MyDHL Express — Carrier Testing
2+
3+
## Metadata
4+
5+
| Field | Value |
6+
|----------------------------|--------------------------------------|
7+
| **Carrier Name** | `mydhl` |
8+
| **Carrier Display Name** | MyDHL Express |
9+
| **Environment** | Production (`express.api.dhl.com/mydhlapi`) |
10+
| **Connection Credentials** | DE production account configured in Dashboard |
11+
| **Last Tested** | 2026-03-05 |
12+
13+
---
14+
15+
## 1. Connection Setup
16+
17+
> Verify the carrier can be connected and authenticated. MyDHL uses Basic Authentication with `username`, `password`, and `account_number`.
18+
19+
- [x] Carrier connection created successfully in Dashboard
20+
- [x] Authentication succeeds (no credential errors)
21+
- [x] Connection appears in carrier list
22+
23+
**Notes:** `account_number` (DHL billing number) is required — without it, rate/shipment requests fail with `"required key [number] not found"`.
24+
25+
---
26+
27+
## 2. Rating / Rate Fetching
28+
29+
> Test rate retrieval for shipments. MyDHL provides a **live rate endpoint** via `/rates`.
30+
31+
**Rate Type:** `Live API Endpoint`
32+
33+
### Live API Endpoint:
34+
- [x] Fetch domestic rates successfully
35+
- [x] Fetch international rates successfully
36+
- [x] Rates returned for all expected services
37+
38+
**DE Domestic (Bonn→Stuttgart):** 6 services returned in EUR — EXPRESS EASY/`mydhl_express_worldwide_b2c` (19.29), EXPRESS DOMESTIC/`mydhl_express_domestic` (27.26), EXPRESS DOMESTIC 12:00/`mydhl_express_domestic_12_00` (33.18), EXPRESS DOMESTIC 10:30/`mydhl_express_domestic_10_30` (45.00), EXPRESS DOMESTIC 9:00/`mydhl_express_domestic_9_00` (68.65), MEDICAL EXPRESS DOMESTIC/`mydhl_medical_express_domestic` (74.98).
39+
40+
**DE→US International (Bonn→New York):** 5 services returned in EUR — EXPRESS EASY/`mydhl_express_easy` (112.01), EXPRESS WORLDWIDE/`mydhl_express_worldwide` (170.12), EXPRESS 12:00/`mydhl_express_9_00` (176.64), EXPRESS 10:30/`mydhl_globalmail_business` (189.69), MEDICAL EXPRESS/`mydhl_medical_express` (206.53).
41+
42+
---
43+
44+
## 3. Shipping / Shipment Creation
45+
46+
> Test creating shipments and generating labels via `/shipments`.
47+
48+
**Supported:** `Yes`
49+
50+
### Basic Shipment
51+
- [x] Create domestic shipment with default service
52+
- [x] Label generated and downloadable as PDF
53+
- [x] Tracking number returned in response
54+
- [x] Shipment appears in shipment list
55+
56+
### Service Coverage
57+
- [x] Create shipment with available services (list services tested below)
58+
59+
| Service Code | Service Name | Product Code | Result |
60+
|---|---|---|---|
61+
| mydhl_express_worldwide_b2c | EXPRESS EASY | 7 | Pass (domestic) — tracking 4785851114, label PDF, 19.29 EUR |
62+
| mydhl_express_easy | EXPRESS EASY | 8 | Pass (international DE→US) — tracking 4960664505, label PDF, 112.01 EUR |
63+
| mydhl_express_worldwide | EXPRESS WORLDWIDE | P | Fail — `"Requested product(s) not available at origin"` (account limitation) |
64+
| mydhl_express_domestic | EXPRESS DOMESTIC | N | Fail — `"Requested product(s) not available at origin"` (account limitation) |
65+
66+
### Multi-Piece Shipment
67+
- [ ] Create shipment with 2+ parcels — Not available (EXPRESS EASY 7/8 are single-piece only; domestic services N/1/O/I support multi-piece but are not available for this account)
68+
69+
### International Shipment (if applicable)
70+
- [x] Create international shipment with customs info
71+
72+
**Notes:** International shipment DE→US with customs (merchandise, DDU, commodity declaration) succeeded with `mydhl_express_easy`. Express Worldwide (P) and Express Domestic (N) are not available for this DE production account.
73+
74+
---
75+
76+
## 4. Label
77+
78+
> Test label output options. MyDHL supports PDF, ZPL, LP2, and EPL label formats.
79+
80+
**Supported:** `Yes`
81+
82+
- [x] Label downloads as PDF (HTTP 200, 5414–9275 bytes)
83+
- [x] Label format matches requested format
84+
- [x] Label available via `label_url` and in `shipping_documents` base64
85+
86+
**Available label formats:** `PDF`, `ZPL`, `LP2`, `EPL`
87+
88+
---
89+
90+
## 5. Shipment Cancellation
91+
92+
> Test cancelling/voiding shipments.
93+
94+
**Supported:** `No` — connector does not implement shipment cancellation with DHL API. Karrio allows soft cancel (status update only) but the shipment is not voided with DHL.
95+
96+
---
97+
98+
## 6. Tracking
99+
100+
> Test tracking shipment status via `/tracking`.
101+
102+
**Supported:** `Yes`
103+
104+
- [x] Track a shipment by tracking number
105+
- [x] Tracking events returned with timestamps
106+
- [x] Tracking status reflects current state
107+
108+
**Notes:** Newly created shipments return `pending` status. Events include timestamps and descriptions from DHL.
109+
110+
---
111+
112+
## 7. Pickup Scheduling
113+
114+
> Test scheduling and managing pickups via `/pickups`.
115+
116+
**Supported:** `Yes`
117+
118+
### Schedule Pickup
119+
- [x] Schedule pickup with valid address and date
120+
- [x] Confirmation number returned
121+
- [x] Pickup appears in pickup list
122+
123+
### Pickup with Shipment
124+
- [x] Schedule pickup associated with a shipment
125+
- [ ] Schedule pickup without an associated shipment (standalone)
126+
127+
### Cancel Pickup
128+
- [x] Cancel a scheduled pickup
129+
- [x] Pickup status updates to cancelled
130+
131+
**Notes:** Standalone pickup fails — parcel data not forwarded to connector when no tracking numbers provided (framework-level issue, not connector bug).
132+
133+
---
134+
135+
## 8. Return Shipment
136+
137+
> Test return shipment creation.
138+
139+
**Supported:** `Yes`
140+
141+
- [x] Create return shipment
142+
- [x] Return label generated
143+
- [x] Return tracking number provided
144+
145+
**Notes:** Return shipment created with `is_return: true`. Tracking number, label PDF, and rate returned successfully.
146+
147+
---
148+
149+
## Shipping Options Reference
150+
151+
> Key shipping options available for testing.
152+
153+
| Option | Type | Description |
154+
|---|---|---|
155+
| `mydhl_saturday_delivery` | bool | Saturday delivery (AA) |
156+
| `mydhl_hold_for_collection` | bool | Hold at service point (LX) |
157+
| `mydhl_duty_tax_paid` | bool | DTP — Duty/Tax Paid (DD) |
158+
| `mydhl_shipment_insurance` | float | Insurance value (II) |
159+
| `mydhl_dangerous_goods` | bool | Dangerous goods (HE) |
160+
| `mydhl_direct_signature` | bool | Direct signature required (SF) |
161+
| `mydhl_paperless_trade` | bool | Paperless trade (WY) |
162+
| `mydhl_gogreen_climate_neutral` | bool | GoGreen Carbon Neutral (EE) |
163+
164+
---
165+
166+
## Edge Cases & Error Handling
167+
168+
> General robustness checks.
169+
170+
- [x] Invalid credentials show clear error message
171+
- [x] Missing required fields return descriptive validation errors
172+
- [x] Error responses include `additionalDetails` with specific DHL error codes
173+
174+
**Notes:** DHL returns `additionalDetails` array with granular error codes (e.g., `"1001: The requested product(s) (N) not available"`) — important to surface these to the user.
175+
176+
---
177+
178+
## Bugs Found
179+
180+
> Bugs discovered during testing.
181+
182+
### Bug 1: Date parsing error in rate and shipment requests — FIXED
183+
`lib.fdatetime` called without `current_format`. **Files:** `rate.py`, `shipment/create.py`
184+
185+
### Bug 2: Invalid package typeCode — FIXED
186+
`typeCode` defaulted to "YP" even when unspecified. Made optional. **Files:** `units.py`, `shipment/create.py`, `pickup/create.py`
187+
188+
### Bug 3: Label content not extracted — NOT A BUG
189+
Side effect of Bug 1+2 blocking shipment creation. Labels work correctly once those are fixed.
190+
191+
### Bug 4: Service code mapping mismatches — FIXED
192+
Vendor OpenAPI spec had wrong product codes. Reverted to codes verified against live API. **Files:** `units.py`
193+
194+
### Bug 5: Service name shows carrier name instead of actual service — FIXED
195+
Hardcoded `service=settings.carrier_name`. Fixed by forwarding service from request context. **Files:** `shipment/create.py`, `proxy.py`
196+
197+
### Bug 6: Error responses missing additionalDetails — FIXED
198+
`ErrorResponseType` schema missing `additionalDetails` field. **Files:** `error_response.py`, `error.py`
199+
200+
### Bug 7: Pickup fails with extraneous countryName key — FIXED
201+
DHL pickup API rejects `countryName` (shipment API accepts it). Removed from pickup create/update. **Files:** `pickup/create.py`, `pickup/update.py`
202+
203+
---
204+
205+
## Summary
206+
207+
| Feature | Status |
208+
|------------------|---------|
209+
| Connection Setup | Pass |
210+
| Rating | Pass |
211+
| Shipping | Pass |
212+
| Label | Pass |
213+
| Cancellation | Not supported |
214+
| Tracking | Pass |
215+
| Pickup | Pass |
216+
| Return Shipment | Pass |
217+
218+
**Overall Result:** Pass — all 7 bugs fixed
219+
220+
**Additional Notes:** Tested against `express.api.dhl.com/mydhlapi` with DE production credentials. All core flows work end-to-end. Multi-piece not testable — Express Easy is single-piece only, other services unavailable for this account. Express Worldwide (P) and Express Domestic (N) unavailable — account limitation, not a connector bug.

0 commit comments

Comments
 (0)