πŸ“– Looking for karrio's legacy docs? Visit docs.karrio.io

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

Typeservice_typeDescriptionExamples
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

  1. Core operations (rating, shipping, tracking, pickup) are handled by carrier plugins
  2. 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)
  3. 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:

FeatureProxy MethodDescription
Ratingget_ratesFetch shipping rates
Shippingcreate_shipment, cancel_shipmentCreate/cancel shipments
Trackingget_trackingTrack packages
Pickupschedule_pickup, modify_pickup, cancel_pickupManage pickups
Addressvalidate_addressValidate addresses
Documentupload_documentUpload trade documents
Manifestcreate_manifestCreate shipping manifests
Webhookregister_webhook, deregister_webhookCarrier webhook management
CallbackOAuth and webhook event processingHandle carrier callbacks
Dutiescalculate_dutiesCalculate duties and taxes
Insuranceapply_insuranceApply shipment insurance

Quick Start

Bootstrap with CLI

Activate environment
1source ./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.py
1import 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.py
1import 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.py
1import 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.py
1import 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

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. Bootstrap
1./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.py
1import 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