Karrio Carrier Integration
Expert guidance for building Karrio shipping carrier integrations. Covers all features, API patterns (JSON/XML/SOAP), and the complete development lifecycle.
Before You Start
- Read CARRIER_INTEGRATION_GUIDE.md for the comprehensive step-by-step guide
- Read CARRIER_INTEGRATION_FAQ.md for common pitfalls and patterns
- Read AGENTS.md for project conventions and coding style
Core Principles
- CLI Tooling is Mandatory: Always use
./bin/cli sdk add-extension for scaffolding. NEVER create files manually.
- Environment First: Run
source ./bin/activate-env before any operation.
- Schema-Driven: Use generated schema types for ALL request/response handling. NEVER use raw dict manipulation.
- Functional Style: Use list comprehensions,
lib.* utilities, and declarative patterns. No imperative loops.
- Pattern Replication: Study existing integrations before implementing.
- Type Safety: Always use
lib.to_object(SchemaType, data) for response parsing.
Reference Carriers
| Pattern |
Carrier |
Path |
| JSON API (direct) |
SEKO |
modules/connectors/seko |
| JSON API (full) |
UPS |
modules/connectors/ups |
| XML API |
Canada Post |
modules/connectors/canadapost |
| Hub carrier |
Easyship |
community/plugins/easyship |
| OAuth flow |
FedEx |
modules/connectors/fedex |
Integration Workflow
Phase 1: Setup & Scaffolding
# 1. Activate environment (ALWAYS FIRST)
source ./bin/activate-env
# 2. Bootstrap extension
./bin/cli sdk add-extension \
--path [modules/connectors OR community/plugins] \
--carrier-slug [carrier_slug] \
--display-name "[Carrier Name]" \
--features "rating,shipping,tracking" \
--no-is-xml-api \
--version "2025.5" \
--confirm
Phase 2: Schema Generation
- Add API request/response JSON samples to
schemas/ directory
- Configure
generate script with correct CLI flags:
--nice-property-names for snake_case APIs
--no-nice-property-names for camelCase APIs
--no-append-type-suffix --no-nice-property-names for PascalCase APIs
- Run:
chmod +x [path]/generate && ./bin/run-generate-on [path]
- Verify:
python -c "import karrio.schemas.[carrier] as s; print(dir(s))"
Phase 3: Implementation
Implement files in this order:
- Settings (
karrio/providers/[carrier]/utils.py): Carrier credentials, server URLs, OAuth
- Units (
karrio/providers/[carrier]/units.py): Services, options, tracking status enums
- Error (
karrio/providers/[carrier]/error.py): Error response parser
- Proxy (
karrio/mappers/[carrier]/proxy.py): HTTP communication layer
- Provider functions: Rate, tracking, shipment, etc.
For detailed implementation patterns, see:
- reference/lib-reference.md - Complete karrio.lib API
- reference/response-models.md - All response model shapes
- reference/implementation-patterns.md - Provider function patterns
- reference/testing-patterns.md - Test writing guide
- reference/units-template.md - Units file template
Phase 4: Testing
Every feature requires the 4-method test pattern:
class TestCarrierFeature(unittest.TestCase):
def setUp(self):
self.maxDiff = None
self.Request = models.FeatureRequest(**Payload)
def test_create_request(self):
request = gateway.mapper.create_feature_request(self.Request)
print(request.serialize()) # ALWAYS print before assert
self.assertEqual(lib.to_dict(request.serialize()), ExpectedRequest)
def test_api_call(self):
with patch("karrio.mappers.[carrier].proxy.lib.request") as mock:
mock.return_value = "{}"
karrio.Feature.action(self.Request).from_(gateway)
self.assertEqual(mock.call_args[1]["url"], ExpectedURL)
def test_parse_response(self):
with patch("karrio.mappers.[carrier].proxy.lib.request") as mock:
mock.return_value = SuccessResponse
result = karrio.Feature.action(self.Request).from_(gateway).parse()
print(result) # ALWAYS print before assert
self.assertListEqual(lib.to_dict(result), ExpectedResult)
def test_parse_error(self):
with patch("karrio.mappers.[carrier].proxy.lib.request") as mock:
mock.return_value = ErrorResponse
result = karrio.Feature.action(self.Request).from_(gateway).parse()
print(result) # ALWAYS print before assert
self.assertListEqual(lib.to_dict(result), ExpectedError)
Phase 5: Validation
# All must pass before integration is complete
python -m unittest discover -v -f [path]/tests # Carrier tests
./bin/run-sdk-tests # SDK tests
./bin/cli plugins list | grep [carrier] # Plugin registered
./bin/cli plugins show [carrier] # Plugin details
pip install -e [path] # Installation works
Connector Directory Structure
[carrier]/
├── pyproject.toml
├── generate # Schema generation script
├── schemas/ # JSON/XML API samples
│ ├── rate_request.json
│ ├── rate_response.json
│ └── ...
├── karrio/
│ ├── plugins/[carrier]/__init__.py # Plugin METADATA
│ ├── mappers/[carrier]/
│ │ ├── __init__.py # Exports Mapper, Proxy, Settings
│ │ ├── mapper.py # DO NOT MODIFY (auto-generated)
│ │ ├── proxy.py # HTTP communication
│ │ └── settings.py # Connection settings
│ ├── providers/[carrier]/
│ │ ├── __init__.py # Public exports
│ │ ├── utils.py # Settings + server URLs
│ │ ├── units.py # Enums (services, options)
│ │ ├── error.py # Error parsing
│ │ ├── rate.py # Rating implementation
│ │ ├── tracking.py # Tracking implementation
│ │ └── shipment/
│ │ ├── create.py # Shipment creation
│ │ └── cancel.py # Shipment cancellation
│ └── schemas/[carrier]/ # Generated Python types (DO NOT EDIT)
└── tests/[carrier]/
├── fixture.py # Test gateway + mock data
├── test_rate.py
├── test_tracking.py
└── test_shipment.py
Supported Features
| Feature |
Request Model |
Response Model |
Provider File |
| Rating |
RateRequest |
RateDetails |
rate.py |
| Shipping |
ShipmentRequest |
ShipmentDetails |
shipment/create.py, cancel.py |
| Tracking |
TrackingRequest |
TrackingDetails |
tracking.py |
| Pickup |
PickupRequest |
PickupDetails |
pickup/create.py, update.py, cancel.py |
| Manifest |
ManifestRequest |
ManifestDetails |
manifest.py |
| Document |
DocumentUploadRequest |
DocumentUploadDetails |
document.py |
| Address |
AddressValidationRequest |
AddressValidationDetails |
address.py |
Anti-Patterns
- Manual file creation instead of CLI scaffolding
- Raw dict manipulation instead of generated schema types
- Imperative for-loops instead of list comprehensions
- Modifying
mapper.py (auto-generated, do not touch)
- Editing generated schema files in
karrio/schemas/
- Using pytest instead of unittest
- Bare exception handling
- Reinventing
karrio.lib utilities
- Not using
lib.to_object() for response parsing
- Not using
lib.to_address() for address handling
- Hardcoding service/option codes instead of enums
1---2name: carrier-integration3description: Builds Karrio shipping carrier integrations from scratch. Covers scaffolding, schema generation, provider implementation (rating, shipping, tracking, pickup, manifest, document upload, address validation), and testing. Triggers on requests to add a new carrier, implement shipping features, build carrier connectors, or integrate carrier APIs with Karrio.4---56# Karrio Carrier Integration78Expert guidance for building Karrio shipping carrier integrations. Covers all features, API patterns (JSON/XML/SOAP), and the complete development lifecycle.910## Before You Start11121. Read [CARRIER_INTEGRATION_GUIDE.md](../../CARRIER_INTEGRATION_GUIDE.md) for the comprehensive step-by-step guide132. Read [CARRIER_INTEGRATION_FAQ.md](../../CARRIER_INTEGRATION_FAQ.md) for common pitfalls and patterns143. Read [AGENTS.md](../../AGENTS.md) for project conventions and coding style1516## Core Principles17181. **CLI Tooling is Mandatory**: Always use `./bin/cli sdk add-extension` for scaffolding. NEVER create files manually.192. **Environment First**: Run `source ./bin/activate-env` before any operation.203. **Schema-Driven**: Use generated schema types for ALL request/response handling. NEVER use raw dict manipulation.214. **Functional Style**: Use list comprehensions, `lib.*` utilities, and declarative patterns. No imperative loops.225. **Pattern Replication**: Study existing integrations before implementing.236. **Type Safety**: Always use `lib.to_object(SchemaType, data)` for response parsing.2425## Reference Carriers2627| Pattern | Carrier | Path |28|---------|---------|------|29| JSON API (direct) | SEKO | `modules/connectors/seko` |30| JSON API (full) | UPS | `modules/connectors/ups` |31| XML API | Canada Post | `modules/connectors/canadapost` |32| Hub carrier | Easyship | `community/plugins/easyship` |33| OAuth flow | FedEx | `modules/connectors/fedex` |3435## Integration Workflow3637### Phase 1: Setup & Scaffolding3839```bash40# 1. Activate environment (ALWAYS FIRST)41source ./bin/activate-env4243# 2. Bootstrap extension44./bin/cli sdk add-extension \45 --path [modules/connectors OR community/plugins] \46 --carrier-slug [carrier_slug] \47 --display-name "[Carrier Name]" \48 --features "rating,shipping,tracking" \49 --no-is-xml-api \50 --version "2025.5" \51 --confirm52```5354### Phase 2: Schema Generation55561. Add API request/response JSON samples to `schemas/` directory572. Configure `generate` script with correct CLI flags:58 - `--nice-property-names` for snake_case APIs59 - `--no-nice-property-names` for camelCase APIs60 - `--no-append-type-suffix --no-nice-property-names` for PascalCase APIs613. Run: `chmod +x [path]/generate && ./bin/run-generate-on [path]`624. Verify: `python -c "import karrio.schemas.[carrier] as s; print(dir(s))"`6364### Phase 3: Implementation6566Implement files in this order:67681. **Settings** (`karrio/providers/[carrier]/utils.py`): Carrier credentials, server URLs, OAuth692. **Units** (`karrio/providers/[carrier]/units.py`): Services, options, tracking status enums703. **Error** (`karrio/providers/[carrier]/error.py`): Error response parser714. **Proxy** (`karrio/mappers/[carrier]/proxy.py`): HTTP communication layer725. **Provider functions**: Rate, tracking, shipment, etc.7374For detailed implementation patterns, see:75- [reference/lib-reference.md](reference/lib-reference.md) - Complete karrio.lib API76- [reference/response-models.md](reference/response-models.md) - All response model shapes77- [reference/implementation-patterns.md](reference/implementation-patterns.md) - Provider function patterns78- [reference/testing-patterns.md](reference/testing-patterns.md) - Test writing guide79- [reference/units-template.md](reference/units-template.md) - Units file template8081### Phase 4: Testing8283Every feature requires the **4-method test pattern**:8485```python86class TestCarrierFeature(unittest.TestCase):87 def setUp(self):88 self.maxDiff = None89 self.Request = models.FeatureRequest(**Payload)9091 def test_create_request(self):92 request = gateway.mapper.create_feature_request(self.Request)93 print(request.serialize()) # ALWAYS print before assert94 self.assertEqual(lib.to_dict(request.serialize()), ExpectedRequest)9596 def test_api_call(self):97 with patch("karrio.mappers.[carrier].proxy.lib.request") as mock:98 mock.return_value = "{}"99 karrio.Feature.action(self.Request).from_(gateway)100 self.assertEqual(mock.call_args[1]["url"], ExpectedURL)101102 def test_parse_response(self):103 with patch("karrio.mappers.[carrier].proxy.lib.request") as mock:104 mock.return_value = SuccessResponse105 result = karrio.Feature.action(self.Request).from_(gateway).parse()106 print(result) # ALWAYS print before assert107 self.assertListEqual(lib.to_dict(result), ExpectedResult)108109 def test_parse_error(self):110 with patch("karrio.mappers.[carrier].proxy.lib.request") as mock:111 mock.return_value = ErrorResponse112 result = karrio.Feature.action(self.Request).from_(gateway).parse()113 print(result) # ALWAYS print before assert114 self.assertListEqual(lib.to_dict(result), ExpectedError)115```116117### Phase 5: Validation118119```bash120# All must pass before integration is complete121python -m unittest discover -v -f [path]/tests # Carrier tests122./bin/run-sdk-tests # SDK tests123./bin/cli plugins list | grep [carrier] # Plugin registered124./bin/cli plugins show [carrier] # Plugin details125pip install -e [path] # Installation works126```127128## Connector Directory Structure129130```131[carrier]/132├── pyproject.toml133├── generate # Schema generation script134├── schemas/ # JSON/XML API samples135│ ├── rate_request.json136│ ├── rate_response.json137│ └── ...138├── karrio/139│ ├── plugins/[carrier]/__init__.py # Plugin METADATA140│ ├── mappers/[carrier]/141│ │ ├── __init__.py # Exports Mapper, Proxy, Settings142│ │ ├── mapper.py # DO NOT MODIFY (auto-generated)143│ │ ├── proxy.py # HTTP communication144│ │ └── settings.py # Connection settings145│ ├── providers/[carrier]/146│ │ ├── __init__.py # Public exports147│ │ ├── utils.py # Settings + server URLs148│ │ ├── units.py # Enums (services, options)149│ │ ├── error.py # Error parsing150│ │ ├── rate.py # Rating implementation151│ │ ├── tracking.py # Tracking implementation152│ │ └── shipment/153│ │ ├── create.py # Shipment creation154│ │ └── cancel.py # Shipment cancellation155│ └── schemas/[carrier]/ # Generated Python types (DO NOT EDIT)156└── tests/[carrier]/157 ├── fixture.py # Test gateway + mock data158 ├── test_rate.py159 ├── test_tracking.py160 └── test_shipment.py161```162163## Supported Features164165| Feature | Request Model | Response Model | Provider File |166|---------|--------------|----------------|---------------|167| Rating | RateRequest | RateDetails | rate.py |168| Shipping | ShipmentRequest | ShipmentDetails | shipment/create.py, cancel.py |169| Tracking | TrackingRequest | TrackingDetails | tracking.py |170| Pickup | PickupRequest | PickupDetails | pickup/create.py, update.py, cancel.py |171| Manifest | ManifestRequest | ManifestDetails | manifest.py |172| Document | DocumentUploadRequest | DocumentUploadDetails | document.py |173| Address | AddressValidationRequest | AddressValidationDetails | address.py |174175## Anti-Patterns176177- Manual file creation instead of CLI scaffolding178- Raw dict manipulation instead of generated schema types179- Imperative for-loops instead of list comprehensions180- Modifying `mapper.py` (auto-generated, do not touch)181- Editing generated schema files in `karrio/schemas/`182- Using pytest instead of unittest183- Bare exception handling184- Reinventing `karrio.lib` utilities185- Not using `lib.to_object()` for response parsing186- Not using `lib.to_address()` for address handling187- Hardcoding service/option codes instead of enums