📖 Looking for karrio's legacy docs? Visit docs.karrio.io

Metadata & Configuration

For Karrio to recognize and properly utilize a carrier extension, you must define its metadata, connection settings, and data units. These components tell Karrio how to interact with your integration, what settings to expose to users, and which specific services and options are available.

Plugin Metadata (__init__.py)

Karrio supports two plugin registration structures:

New Structure (Preferred)

File: karrio/plugins/[carrier_name]/__init__.py

1from karrio.core.metadata import PluginMetadata 2from karrio.mappers.[carrier_name].mapper import Mapper 3from karrio.mappers.[carrier_name].proxy import Proxy 4from karrio.mappers.[carrier_name].settings import Settings 5import karrio.providers.[carrier_name].units as units 6import karrio.providers.[carrier_name].utils as utils 7 8METADATA = PluginMetadata( 9 id="[carrier_name]", 10 label="[Carrier Display Name]", 11 description="[Carrier Name] shipping integration for Karrio", 12 13 # Core Integration Components 14 Mapper=Mapper, 15 Proxy=Proxy, 16 Settings=Settings, 17 18 # Data Units 19 is_hub=False, # True for hub carriers like Easyship, ShipEngine 20 options=units.ShippingOption, 21 services=units.ShippingService, 22 connection_configs=utils.ConnectionConfig, 23 24 # Additional Info 25 website="https://www.carrier.com", 26 documentation="https://docs.carrier.com", 27)

Legacy Structure

File: karrio/mappers/[carrier_name]/__init__.py

1from karrio.core.metadata import Metadata 2from karrio.mappers.[carrier_name].mapper import Mapper 3from karrio.mappers.[carrier_name].proxy import Proxy 4from karrio.mappers.[carrier_name].settings import Settings 5import karrio.providers.[carrier_name].units as units 6import karrio.providers.[carrier_name].utils as utils 7 8METADATA = Metadata( 9 id="[carrier_name]", 10 label="[Carrier Display Name]", 11 12 # Core Integration Components 13 Mapper=Mapper, 14 Proxy=Proxy, 15 Settings=Settings, 16 17 # Data Units 18 is_hub=False, 19 connection_configs=utils.ConnectionConfig, 20)

Key Properties:

  • id: Unique lowercase identifier (e.g., dhl_express, fedex)
  • label: Human-readable name for UI display
  • is_hub: False for direct carriers, True for aggregators
  • Mapper: Main integration class handling request/response transformation
  • Proxy: HTTP client for API communication
  • Settings: Connection configuration class

Connection Settings (settings.py)

The Settings class defines the configuration fields required to connect to the carrier’s API. This class corresponds directly to the connection form in the Karrio Dashboard.

File: karrio/mappers/[carrier_name]/settings.py

1import attr 2import karrio.providers.[carrier_name].utils as provider_utils 3 4@attr.s(auto_attribs=True) 5class Settings(provider_utils.Settings): 6 """[Carrier Name] connection settings.""" 7 8 # Carrier-specific credentials (required fields) 9 api_key: str # For modern JSON APIs 10 # OR for XML/legacy APIs: 11 # username: str 12 # password: str 13 14 # Optional fields 15 account_number: str = None 16 secret_key: str = None 17 18 # Standard Karrio fields (DO NOT MODIFY) 19 id: str = None 20 test_mode: bool = False 21 carrier_id: str = "[carrier_name]" 22 account_country_code: str = None 23 metadata: dict = {} 24 config: dict = {}

Field Requirements:

  • Fields without defaults (e.g., api_key: str) are required
  • Fields with defaults (e.g., account_number: str = None) are optional
  • Standard Karrio fields should not be modified

Provider Utilities (utils.py)

The provider utilities extend the mapper settings with computed properties and helper methods.

File: karrio/providers/[carrier_name]/utils.py

1import base64 2import datetime 3import karrio.lib as lib 4import karrio.core as core 5 6class Settings(core.Settings): 7 """[Carrier Name] provider settings.""" 8 9 # Carrier-specific credentials 10 api_key: str 11 account_number: str = None 12 13 @property 14 def carrier_name(self): 15 return "[carrier_name]" 16 17 @property 18 def server_url(self): 19 return ( 20 "https://api.sandbox.[carrier].com" 21 if self.test_mode 22 else "https://api.[carrier].com" 23 ) 24 25 # For XML/Basic Auth APIs 26 @property 27 def authorization(self): 28 pair = f"{self.username}:{self.password}" 29 return base64.b64encode(pair.encode("utf-8")).decode("ascii") 30 31 # For OAuth APIs 32 @property 33 def access_token(self): 34 """Retrieve and cache OAuth access token.""" 35 cache_key = f"{self.carrier_name}|{self.api_key}|{self.secret_key}" 36 now = datetime.datetime.now() + datetime.timedelta(minutes=30) 37 38 auth = self.connection_cache.get(cache_key) or {} 39 token = auth.get("access_token") 40 expiry = lib.to_date(auth.get("expiry"), current_format="%Y-%m-%d %H:%M:%S") 41 42 if token is not None and expiry is not None and expiry > now: 43 return token 44 45 self.connection_cache.set(cache_key, lambda: login(self)) 46 new_auth = self.connection_cache.get(cache_key) 47 return new_auth["access_token"] 48 49 @property 50 def connection_config(self) -> lib.units.Options: 51 return lib.to_connection_config( 52 self.config or {}, 53 option_type=ConnectionConfig, 54 ) 55 56# OAuth login implementation (if needed) 57def login(settings: Settings): 58 import karrio.providers.[carrier_name].error as error 59 60 result = lib.request( 61 url=f"{settings.server_url}/oauth/token", 62 method="POST", 63 headers={"Content-Type": "application/x-www-form-urlencoded"}, 64 data=lib.to_query_string({ 65 "grant_type": "client_credentials", 66 "client_id": settings.api_key, 67 "client_secret": settings.secret_key, 68 }), 69 ) 70 71 response = lib.to_dict(result) 72 messages = error.parse_error_response(response, settings) 73 74 if any(messages): 75 raise errors.ParsedMessagesError(messages) 76 77 expiry = datetime.datetime.now() + datetime.timedelta( 78 seconds=float(response.get("expires_in", 0)) 79 ) 80 return {**response, "expiry": lib.fdatetime(expiry)} 81 82class ConnectionConfig(lib.Enum): 83 """Carrier-specific connection configuration options.""" 84 label_type = lib.OptionEnum("label_type", str, "PDF") 85 shipping_options = lib.OptionEnum("shipping_options", list) 86 shipping_services = lib.OptionEnum("shipping_services", list)

Data Units (units.py)

The units file defines carrier-specific enumerations for services, options, and package types.

File: karrio/providers/[carrier_name]/units.py

1import karrio.lib as lib 2import karrio.core.units as units 3 4class PackagingType(lib.StrEnum): 5 """Carrier-specific packaging types.""" 6 carrier_envelope = "ENVELOPE" 7 carrier_pak = "PAK" 8 carrier_box = "BOX" 9 carrier_tube = "TUBE" 10 11 """Unified Packaging type mapping.""" 12 envelope = carrier_envelope 13 pak = carrier_pak 14 small_box = carrier_box 15 medium_box = carrier_box 16 large_box = carrier_box 17 tube = carrier_tube 18 your_packaging = carrier_box 19 20class ShippingService(lib.StrEnum): 21 """Carrier-specific services.""" 22 carrier_standard = "Standard Service" 23 carrier_express = "Express Service" 24 carrier_overnight = "Overnight Service" 25 carrier_ground = "Ground Service" 26 27class ShippingOption(lib.Enum): 28 """Carrier-specific shipping options.""" 29 carrier_insurance = lib.OptionEnum("insurance", float) 30 carrier_signature_required = lib.OptionEnum("signature_required", bool) 31 carrier_saturday_delivery = lib.OptionEnum("saturday_delivery", bool) 32 carrier_cod = lib.OptionEnum("cash_on_delivery", float) 33 34 """Unified Option type mapping.""" 35 insurance = carrier_insurance 36 signature_required = carrier_signature_required 37 saturday_delivery = carrier_saturday_delivery 38 cash_on_delivery = carrier_cod 39 40def shipping_options_initializer( 41 options: dict, 42 package_options: units.ShippingOptions = None, 43) -> units.ShippingOptions: 44 """Apply default values to shipping options.""" 45 if package_options is not None: 46 options.update(package_options.content) 47 48 def items_filter(key: str) -> bool: 49 return key in ShippingOption 50 51 return units.ShippingOptions(options, ShippingOption, items_filter=items_filter) 52 53class TrackingStatus(lib.Enum): 54 """Map carrier tracking statuses to Karrio unified statuses.""" 55 on_hold = ["ON_HOLD", "HELD"] 56 delivered = ["DELIVERED", "COMPLETED"] 57 in_transit = ["IN_TRANSIT", "PROCESSING"] 58 delivery_failed = ["DELIVERY_FAILED", "FAILED"] 59 delivery_delayed = ["DELAYED"] 60 out_for_delivery = ["OUT_FOR_DELIVERY"] 61 ready_for_pickup = ["READY_FOR_PICKUP"] 62 63# Weight and dimension units (if carrier uses specific units) 64class WeightUnit(lib.Enum): 65 KG = "KG" 66 LB = "LB" 67 68class DimensionUnit(lib.Enum): 69 CM = "CM" 70 IN = "IN"

Hub Carrier Configuration

For hub carriers (multi-carrier aggregators), additional configuration is needed:

In metadata __init__.py
1METADATA = PluginMetadata( 2 # ... other fields 3 is_hub=True, # Mark as hub carrier 4 # ... rest of configuration 5) 6 7# In units.py for hub carriers 8class ShippingService(lib.StrEnum): 9 """Hub carrier services - dynamically discovered.""" 10 hub_standard = "Hub Standard" 11 12 @classmethod 13 def discover_services(cls, api_response: dict): 14 """Dynamically add services from API response.""" 15 for service in api_response.get("available_services", []): 16 service_key = f"hub_{service['carrier'].lower()}_{service['code'].lower()}" 17 service_name = f"{service['carrier']} {service['name']}" 18 if not hasattr(cls, service_key): 19 setattr(cls, service_key, service_name)

Configuration Validation

Ensure your configuration is valid:

Test metadata imports
1python -c " 2import karrio.plugins.[carrier_name] as plugin 3print(f'Plugin ID: {plugin.METADATA.id}') 4print(f'Plugin Label: {plugin.METADATA.label}') 5print(f'Is Hub: {plugin.METADATA.is_hub}') 6" 7 8# Test settings 9python -c " 10from karrio.mappers.[carrier_name].settings import Settings 11settings = Settings(api_key='test', test_mode=True) 12print(f'Carrier: {settings.carrier_id}') 13print(f'Server URL: {settings.server_url}') 14"

Best Practices

  1. Use Descriptive Names: Make service and option names clear and consistent
  2. Map to Unified Types: Always provide mappings from carrier-specific to unified types
  3. Handle Authentication: Implement appropriate authentication patterns (API key, OAuth, Basic Auth)
  4. Provide Defaults: Set sensible defaults for optional configuration
  5. Use Enums: Define all carrier-specific values as enums for type safety
  6. Document Options: Comment complex configuration options
  7. Test Configuration: Verify all imports and instantiation work correctly

Common Patterns

API Key Authentication

1class Settings(core.Settings): 2 api_key: str 3 4 @property 5 def auth_headers(self): 6 return {"Authorization": f"Bearer {self.api_key}"}

Basic Authentication

1class Settings(core.Settings): 2 username: str 3 password: str 4 5 @property 6 def authorization(self): 7 pair = f"{self.username}:{self.password}" 8 return base64.b64encode(pair.encode("utf-8")).decode("ascii")

Custom Headers

1class Settings(core.Settings): 2 api_key: str 3 client_id: str 4 5 @property 6 def headers(self): 7 return { 8 "X-API-Key": self.api_key, 9 "X-Client-ID": self.client_id, 10 "Content-Type": "application/json" 11 }

These metadata and configuration files form the foundation of your carrier integration, providing Karrio with all the information needed to interact with your carrier’s API and present appropriate options to users.