Plugin Development
Karrio plugins extend the platform with shipping carriers and logistics service providers (LSP). All plugins follow the same Mapper/Proxy/Settings pattern.
Plugin Types
| Type | service_type | Description | Examples |
|---|---|---|---|
| Carrier | "carrier" | Full shipping integration (rates, shipments, tracking) | UPS, FedEx, DHL |
| LSP | "LSP" | Logistics Service Provider (standalone services) | Smarty, InsureShield, Avalara |
When to Use Each Type
Carrier plugins (service_type="carrier") are for shipping carriers that provide core logistics operations. Some carriers also offer additional services:
- Address validation: UPS, FedEx, DHL Express, Easyship, EasyPost
- Insurance: UPS, FedEx, DHL Express, Easyship, EasyPost
- Duties & taxes: Teleship, Easyship
LSP plugins (service_type="LSP") are for standalone service providers that specialize in specific logistics capabilities:
- Address validation: Smarty, Google Geocoding, AddressComplete
- Insurance: InsureShield, Shipsurance
- Duties & taxes: Avalara, Zonos
Architecture Overview
Karrioβs plugin system allows carriers and LSP services to work together, supplementing core shipping operations with specialized capabilities.
How It Works
- Core operations (rating, shipping, tracking, pickup) are handled by carrier plugins
- Supplementary services can be provided by:
- The same carrier (e.g., UPS address validation during shipment)
- A dedicated LSP plugin (e.g., Smarty for address validation)
- Flexibility: Use carrier-provided services or integrate specialized LSP providers based on your needs
Example Flow: Shipment with Address Validation
Supported Capabilities
Plugins can implement any combination of features:
| Feature | Proxy Method | Description |
|---|---|---|
| Rating | get_rates | Fetch shipping rates |
| Shipping | create_shipment, cancel_shipment | Create/cancel shipments |
| Tracking | get_tracking | Track packages |
| Pickup | schedule_pickup, modify_pickup, cancel_pickup | Manage pickups |
| Address | validate_address | Validate addresses |
| Document | upload_document | Upload trade documents |
| Manifest | create_manifest | Create shipping manifests |
| Webhook | register_webhook, deregister_webhook | Carrier webhook management |
| Callback | OAuth and webhook event processing | Handle carrier callbacks |
| Duties | calculate_duties | Calculate duties and taxes |
| Insurance | apply_insurance | Apply shipment insurance |
Quick Start
Bootstrap with CLI
Activate environment1source ./bin/activate-env 2 3# Create carrier plugin 4./bin/cli sdk add-extension \ 5 --path modules/connectors \ 6 --carrier-slug my_carrier \ 7 --display-name "My Carrier" \ 8 --features "rating,shipping,tracking" \ 9 --confirm 10 11# Create LSP plugin (e.g., address validation service) 12./bin/cli sdk add-extension \ 13 --path plugins \ 14 --carrier-slug my_lsp \ 15 --display-name "My LSP Service" \ 16 --features "address" \ 17 --confirm
Generated Structure
1my_carrier/ 2βββ pyproject.toml # Package config with entrypoints 3βββ generate # Schema generation script 4βββ schemas/ # API schema files (JSON/XML) 5βββ karrio/ 6β βββ plugins/my_carrier/ # Plugin registration 7β β βββ __init__.py # METADATA definition 8β βββ mappers/my_carrier/ # Integration layer 9β β βββ mapper.py # Request/response routing 10β β βββ proxy.py # HTTP client 11β β βββ settings.py # Credentials config 12β βββ providers/my_carrier/ # Implementation 13β β βββ rate.py # Rating logic 14β β βββ tracking.py # Tracking logic 15β β βββ shipment/ # Shipping logic 16β β βββ units.py # Services, options 17β β βββ utils.py # Helpers 18β β βββ error.py # Error parsing 19β βββ schemas/my_carrier/ # Generated Python types 20βββ tests/my_carrier/ # Test suite
Plugin Metadata
Define your plugin in karrio/plugins/[name]/__init__.py:
Carrier Plugin
1from karrio.core.metadata import PluginMetadata 2from karrio.mappers.my_carrier.mapper import Mapper 3from karrio.mappers.my_carrier.proxy import Proxy 4from karrio.mappers.my_carrier.settings import Settings 5 6METADATA = PluginMetadata( 7 id="my_carrier", 8 label="My Carrier", 9 description="Ship with My Carrier", 10 status="beta", 11 service_type="carrier", # default 12 13 # Required components 14 Mapper=Mapper, 15 Proxy=Proxy, 16 Settings=Settings, 17 18 # Optional: callback handler 19 # Callback=Callback, 20 21 # Optional metadata 22 is_hub=False, # True for aggregators like Easyship 23 website="https://mycarrier.com", 24 documentation="https://api.mycarrier.com/docs", 25)
LSP Plugin (Logistics Service Provider)
1from karrio.core.metadata import PluginMetadata 2from karrio.mappers.smarty.mapper import Mapper 3from karrio.mappers.smarty.proxy import Proxy 4from karrio.mappers.smarty.settings import Settings 5 6METADATA = PluginMetadata( 7 id="smarty", 8 label="Smarty", 9 description="Address validation and verification service", 10 status="production-ready", 11 service_type="LSP", # Marks as Logistics Service Provider 12 13 Mapper=Mapper, 14 Proxy=Proxy, 15 Settings=Settings, 16 17 website="https://www.smarty.com", 18)
Note: The same Mapper/Proxy/Settings pattern applies to all LSP typesβwhether for address validation (Smarty), insurance (InsureShield), or duties calculation (Avalara).
Core Components
Settings
Define connection credentials:
karrio/mappers/my_carrier/settings.py1import attr 2import karrio.core.settings as settings 3 4@attr.s(auto_attribs=True) 5class Settings(settings.Settings): 6 api_key: str 7 secret_key: str = None 8 account_number: str = None 9 10 # Connection config 11 id: str = None 12 test_mode: bool = False 13 carrier_id: str = "my_carrier" 14 15 @property 16 def carrier_name(self): 17 return "my_carrier" 18 19 @property 20 def server_url(self): 21 return ( 22 "https://sandbox.api.mycarrier.com" 23 if self.test_mode 24 else "https://api.mycarrier.com" 25 )
Proxy
Implement HTTP communication:
karrio/mappers/my_carrier/proxy.py1import karrio.lib as lib 2import karrio.api.proxy as proxy 3from karrio.mappers.my_carrier.settings import Settings 4 5class Proxy(proxy.Proxy): 6 settings: Settings 7 8 def get_rates(self, request: lib.Serializable) -> lib.Deserializable: 9 response = lib.request( 10 url=f"{self.settings.server_url}/v1/rates", 11 data=lib.to_json(request.serialize()), 12 headers={ 13 "Authorization": f"Bearer {self.settings.api_key}", 14 "Content-Type": "application/json", 15 }, 16 method="POST", 17 trace=self.trace_as("json"), 18 ) 19 return lib.Deserializable(response, lib.to_dict) 20 21 def create_shipment(self, request: lib.Serializable) -> lib.Deserializable: 22 # Similar pattern... 23 pass
Mapper
Route requests to provider implementations:
karrio/mappers/my_carrier/mapper.py1import typing 2import karrio.lib as lib 3import karrio.api.mapper as mapper 4import karrio.core.models as models 5import karrio.providers.my_carrier as provider 6from karrio.mappers.my_carrier.settings import Settings 7 8class Mapper(mapper.Mapper): 9 settings: Settings 10 11 def create_rate_request(self, payload: models.RateRequest) -> lib.Serializable: 12 return provider.rate_request(payload, self.settings) 13 14 def parse_rate_response( 15 self, response: lib.Deserializable 16 ) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]: 17 return provider.parse_rate_response(response, self.settings)
Provider Implementation
Transform between Karrio models and carrier API:
karrio/providers/my_carrier/rate.py1import karrio.schemas.my_carrier.rate_request as rate_req 2import karrio.schemas.my_carrier.rate_response as rate_res 3import typing 4import karrio.lib as lib 5import karrio.core.models as models 6from karrio.providers.my_carrier import units 7from karrio.providers.my_carrier.utils import Settings 8 9def rate_request(payload: models.RateRequest, settings: Settings) -> lib.Serializable: 10 request = rate_req.RateRequestType( 11 origin=payload.shipper.postal_code, 12 destination=payload.recipient.postal_code, 13 weight=payload.parcels[0].weight, 14 # Map other fields... 15 ) 16 return lib.Serializable(request, lib.to_dict) 17 18def parse_rate_response( 19 response: lib.Deserializable[dict], 20 settings: Settings, 21) -> typing.Tuple[typing.List[models.RateDetails], typing.List[models.Message]]: 22 data = response.deserialize() 23 rates = [ 24 models.RateDetails( 25 carrier_id=settings.carrier_id, 26 carrier_name=settings.carrier_name, 27 service=units.ShippingService.map(r["serviceCode"]).name_or_key, 28 total_charge=lib.to_money(r["totalPrice"]), 29 currency=r["currency"], 30 ) 31 for r in data.get("rates", []) 32 ] 33 return rates, []
Plugin Registration
Entrypoint (Recommended)
In pyproject.toml:
1[project.entry-points."karrio.plugins"] 2my_carrier = "karrio.plugins.my_carrier"
Directory-based
Place plugin in ./plugins/ or set KARRIO_PLUGINS environment variable.
Development Workflow
1. Bootstrap1./bin/cli sdk add-extension --path modules/connectors ... 2 3# 2. Add schema files to schemas/ directory 4 5# 3. Generate Python types 6./bin/run-generate-on modules/connectors/my_carrier 7 8# 4. Implement provider logic 9 10# 5. Run tests 11python -m unittest discover -v modules/connectors/my_carrier/tests 12 13# 6. Verify registration 14./bin/cli plugins list | grep my_carrier 15./bin/cli plugins show my_carrier
Adding Features to Existing Plugins
1./bin/cli sdk add-features \ 2 --path modules/connectors \ 3 --carrier-slug my_carrier \ 4 --display-name "My Carrier" \ 5 --features "pickup,manifest" \ 6 --confirm
Testing
Follow the standard test pattern:
tests/my_carrier/test_rate.py1import unittest 2from unittest.mock import patch 3import karrio.sdk as karrio 4import karrio.lib as lib 5from .fixture import gateway 6 7class TestMyCarrierRating(unittest.TestCase): 8 def setUp(self): 9 self.RateRequest = karrio.core.models.RateRequest(**RatePayload) 10 11 def test_create_rate_request(self): 12 request = gateway.mapper.create_rate_request(self.RateRequest) 13 self.assertEqual(request.serialize(), RateRequest) 14 15 def test_get_rates(self): 16 with patch("karrio.mappers.my_carrier.proxy.lib.request") as mock: 17 mock.return_value = RateResponse 18 karrio.Rating.fetch(self.RateRequest).from_(gateway) 19 self.assertEqual(mock.call_args[1]["url"], "https://api...") 20 21 def test_parse_rate_response(self): 22 with patch("karrio.mappers.my_carrier.proxy.lib.request") as mock: 23 mock.return_value = RateResponse 24 result = karrio.Rating.fetch(self.RateRequest).from_(gateway).parse() 25 self.assertListEqual(lib.to_dict(result), ParsedRateResponse) 26 27# Test data at end of file 28RatePayload = {...} 29RateRequest = {...} 30RateResponse = """...""" 31ParsedRateResponse = [[...], []]
Resources
- Carrier Integration Guide - Detailed carrier implementation
- CLI Guide - CLI commands reference
- Schema Generation - Generate Python types
