From 8aab391939047daa064613d266c5dd4103f5bc8c Mon Sep 17 00:00:00 2001 From: Daniel K Date: Tue, 23 Jun 2026 06:03:30 -0400 Subject: [PATCH] sync: latest JTL karrio subtree (2026-06-23) --- .github/workflows/build.yml | 24 +- .github/workflows/insiders-build.yml | 28 +- .github/workflows/platform-build.yml | 11 +- apps/api/gunicorn-cfg.py | 12 +- apps/api/karrio/server/__init__.py | 2 +- apps/api/karrio/server/__main__.py | 5 +- apps/api/karrio/server/asgi.py | 52 +- apps/api/karrio/server/lib/otel_huey.py | 146 - apps/api/karrio/server/settings/__init__.py | 9 + apps/api/karrio/server/settings/apm.py | 292 +- apps/api/karrio/server/settings/base.py | 171 +- apps/api/karrio/server/settings/cache.py | 33 +- apps/api/karrio/server/settings/constance.py | 205 +- apps/api/karrio/server/settings/debug.py | 9 +- apps/api/karrio/server/settings/email.py | 11 +- apps/api/karrio/server/settings/workers.py | 144 +- apps/api/karrio/server/urls/__init__.py | 17 +- apps/api/karrio/server/urls/jwt.py | 137 +- apps/api/karrio/server/urls/mfa.py | 246 + apps/api/karrio/server/urls/tokens.py | 16 +- apps/api/karrio/server/wsgi.py | 2 +- apps/api/pyproject.toml | 2 +- apps/web/public/carrier-integrations.json | 2 +- bin/regenerate-rate-sheets | 398 + modules/admin/karrio/server/admin/admin.py | 2 - modules/admin/karrio/server/admin/forms.py | 4 +- .../server/admin/migrations/0001_initial.py | 1 - ...admin_task__queued__be444e_idx_and_more.py | 1 - .../0003_deduplicate_and_unique_task_id.py | 7 +- modules/admin/karrio/server/admin/models.py | 2 - modules/admin/karrio/server/admin/schema.py | 14 +- .../server/admin/schemas/base/__init__.py | 145 +- .../server/admin/schemas/base/inputs.py | 155 +- .../server/admin/schemas/base/mutations.py | 244 +- .../karrio/server/admin/schemas/base/types.py | 417 +- modules/admin/karrio/server/admin/signals.py | 8 +- .../karrio/server/admin/tests/__init__.py | 6 + .../admin/karrio/server/admin/tests/base.py | 20 +- .../server/admin/tests/test_admin_mfa.py | 94 + .../admin/tests/test_admin_route_gate.py | 105 + .../karrio/server/admin/tests/test_auth.py | 72 +- .../server/admin/tests/test_connections.py | 141 +- .../server/admin/tests/test_create_user.py | 66 + .../karrio/server/admin/tests/test_markups.py | 35 +- .../server/admin/tests/test_rate_sheets.py | 880 +- modules/admin/karrio/server/admin/urls.py | 2 +- modules/admin/karrio/server/admin/utils.py | 19 +- modules/admin/karrio/server/admin/views.py | 119 +- .../karrio/server/admin/worker/signals.py | 115 +- .../admin/karrio/server/admin/worker/tasks.py | 1 + modules/admin/karrio/server/settings/admin.py | 2 +- modules/cli/karrio_cli/__main__.py | 16 +- modules/cli/karrio_cli/commands/codegen.py | 157 +- modules/cli/karrio_cli/commands/login.py | 9 +- modules/cli/karrio_cli/commands/plugins.py | 36 +- modules/cli/karrio_cli/commands/sdk.py | 466 +- modules/cli/karrio_cli/commands/studio.py | 129 +- modules/cli/karrio_cli/common/utils.py | 54 +- modules/cli/karrio_cli/resources/carriers.py | 12 +- .../cli/karrio_cli/resources/connections.py | 29 +- modules/cli/karrio_cli/resources/events.py | 47 +- modules/cli/karrio_cli/resources/logs.py | 49 +- modules/cli/karrio_cli/resources/orders.py | 55 +- modules/cli/karrio_cli/resources/shipments.py | 95 +- modules/cli/karrio_cli/resources/trackers.py | 87 +- modules/cli/karrio_cli/templates/__init__.py | 1 + modules/cli/karrio_cli/templates/location.py | 324 + modules/cli/karrio_cli/templates/sdk.py | 42 +- modules/connectors/amazon_shipping/README.md | 31 + modules/connectors/amazon_shipping/SPECS.md | 465 + modules/connectors/amazon_shipping/generate | 20 + .../mappers/amazon_shipping/__init__.py | 3 + .../karrio/mappers/amazon_shipping/mapper.py | 65 + .../karrio/mappers/amazon_shipping/proxy.py | 178 + .../mappers/amazon_shipping/settings.py | 26 + .../plugins/amazon_shipping/__init__.py | 19 + .../providers/amazon_shipping/__init__.py | 11 + .../karrio/providers/amazon_shipping/error.py | 29 + .../karrio/providers/amazon_shipping/rate.py | 193 + .../amazon_shipping/shipment/__init__.py | 8 + .../amazon_shipping/shipment/cancel.py | 38 + .../amazon_shipping/shipment/create.py | 194 + .../providers/amazon_shipping/tracking.py | 119 + .../karrio/providers/amazon_shipping/units.py | 149 + .../karrio/providers/amazon_shipping/utils.py | 62 + .../schemas/amazon_shipping/__init__.py | 0 .../cancel_shipment_response.py | 13 + .../schemas/amazon_shipping/error_response.py | 15 + .../one_click_shipment_request.py | 61 + .../one_click_shipment_response.py | 65 + .../purchase_shipment_request.py | 29 + .../purchase_shipment_response.py | 55 + .../schemas/amazon_shipping/rate_request.py | 61 + .../schemas/amazon_shipping/rate_response.py | 102 + .../amazon_shipping/tracking_response.py | 59 + .../connectors/amazon_shipping/pyproject.toml | 39 + .../schemas/cancel_shipment_response.json | 3 + .../schemas/create_shipment_request.json | 141 + .../schemas/create_shipment_response.json | 28 + .../schemas/error_response.json | 9 + .../schemas/one_click_shipment_request.json | 71 + .../schemas/one_click_shipment_response.json | 60 + .../schemas/purchase_label_request.json | 7 + .../schemas/purchase_label_response.json | 38 + .../schemas/purchase_shipment_request.json | 18 + .../schemas/purchase_shipment_response.json | 36 + .../amazon_shipping/schemas/rate_request.json | 71 + .../schemas/rate_response.json | 93 + .../schemas/shipping_label.json | 7 + .../schemas/tracking_response.json | 79 + .../amazon_shipping/tests/__init__.py | 4 + .../tests/amazon_shipping/__init__.py | 0 .../tests/amazon_shipping/fixture.py | 31 + .../amazon_shipping/test_authentication.py | 51 + .../tests/amazon_shipping/test_rate.py | 189 + .../tests/amazon_shipping/test_shipment.py | 226 + .../tests/amazon_shipping/test_tracking.py | 192 + .../amazon_shipping/vendor/openapi.yaml | 5210 ++ .../amazon_shipping/vendor/postman.json | 927 + .../amazon_shipping/vendor/shippingV2.json | 10496 ++++ modules/connectors/asendia/SPECS.md | 644 + .../karrio/mappers/asendia/__init__.py | 2 +- .../asendia/karrio/mappers/asendia/mapper.py | 60 +- .../asendia/karrio/mappers/asendia/proxy.py | 17 +- .../karrio/mappers/asendia/settings.py | 17 +- .../karrio/plugins/asendia/__init__.py | 6 +- .../karrio/providers/asendia/__init__.py | 16 +- .../asendia/karrio/providers/asendia/error.py | 11 +- .../karrio/providers/asendia/manifest.py | 12 +- .../karrio/providers/asendia/services.csv | 17 +- .../providers/asendia/shipment/__init__.py | 9 +- .../providers/asendia/shipment/cancel.py | 5 +- .../providers/asendia/shipment/create.py | 99 +- .../asendia/shipment/return_shipment.py | 5 +- .../karrio/providers/asendia/tracking.py | 33 +- .../asendia/karrio/providers/asendia/units.py | 210 +- .../asendia/karrio/providers/asendia/utils.py | 4 +- modules/connectors/asendia/tests/__init__.py | 5 +- .../asendia/tests/asendia/__init__.py | 2 +- .../asendia/tests/asendia/fixture.py | 1 - .../asendia/tests/asendia/test_manifest.py | 76 +- .../asendia/tests/asendia/test_rate.py | 221 + .../asendia/tests/asendia/test_shipment.py | 449 +- .../asendia/tests/asendia/test_tracking.py | 118 +- modules/connectors/asendia/vendor/README.md | 61 + .../connectors/asendia/vendor/openapi.json | 1 + .../connectors/asendia/vendor/openapi.yaml | 1705 + modules/connectors/australiapost/SPECS.md | 617 + .../karrio/mappers/australiapost/__init__.py | 4 +- .../karrio/mappers/australiapost/mapper.py | 37 +- .../karrio/mappers/australiapost/proxy.py | 12 +- .../karrio/plugins/australiapost/__init__.py | 2 - .../providers/australiapost/__init__.py | 15 +- .../karrio/providers/australiapost/error.py | 13 +- .../providers/australiapost/manifest.py | 21 +- .../karrio/providers/australiapost/rate.py | 34 +- .../providers/australiapost/services.csv | 116 +- .../australiapost/shipment/__init__.py | 1 - .../australiapost/shipment/cancel.py | 6 +- .../australiapost/shipment/create.py | 54 +- .../australiapost/shipment/return_shipment.py | 5 +- .../providers/australiapost/tracking.py | 34 +- .../karrio/providers/australiapost/units.py | 11 +- .../karrio/providers/australiapost/utils.py | 9 +- .../tests/australiapost/test_manifest.py | 13 +- .../tests/australiapost/test_rate.py | 19 +- .../tests/australiapost/test_shipment.py | 31 +- .../tests/australiapost/test_tracking.py | 32 +- modules/connectors/bpost/SPECS.md | 633 + .../bpost/karrio/mappers/bpost/__init__.py | 2 + .../bpost/karrio/mappers/bpost/mapper.py | 31 +- .../bpost/karrio/mappers/bpost/proxy.py | 6 +- .../bpost/karrio/mappers/bpost/settings.py | 9 +- .../bpost/karrio/plugins/bpost/__init__.py | 1 - .../bpost/karrio/providers/bpost/__init__.py | 8 +- .../bpost/karrio/providers/bpost/error.py | 9 +- .../providers/bpost/shipment/__init__.py | 1 - .../karrio/providers/bpost/shipment/cancel.py | 8 +- .../karrio/providers/bpost/shipment/create.py | 548 +- .../bpost/shipment/return_shipment.py | 5 +- .../bpost/karrio/providers/bpost/tracking.py | 54 +- .../bpost/karrio/providers/bpost/units.py | 54 +- .../bpost/karrio/providers/bpost/utils.py | 5 +- modules/connectors/bpost/tests/__init__.py | 3 +- .../bpost/tests/bpost/test_shipment.py | 37 +- .../bpost/tests/bpost/test_tracking.py | 17 +- modules/connectors/canadapost/SPECS.md | 642 + .../karrio/mappers/canadapost/mapper.py | 51 +- .../karrio/mappers/canadapost/proxy.py | 50 +- .../karrio/mappers/canadapost/settings.py | 1 - .../karrio/plugins/canadapost/__init__.py | 1 - .../karrio/providers/canadapost/__init__.py | 30 +- .../karrio/providers/canadapost/error.py | 21 +- .../karrio/providers/canadapost/manifest.py | 30 +- .../providers/canadapost/pickup/cancel.py | 11 +- .../providers/canadapost/pickup/create.py | 112 +- .../providers/canadapost/pickup/update.py | 26 +- .../karrio/providers/canadapost/rate.py | 60 +- .../karrio/providers/canadapost/services.csv | 128 +- .../providers/canadapost/shipment/cancel.py | 22 +- .../providers/canadapost/shipment/create.py | 118 +- .../canadapost/shipment/return_shipment.py | 30 +- .../karrio/providers/canadapost/tracking.py | 37 +- .../karrio/providers/canadapost/units.py | 26 +- .../karrio/providers/canadapost/utils.py | 22 +- .../canadapost/tests/canadapost/fixture.py | 8 +- .../tests/canadapost/test_manifest.py | 44 +- .../tests/canadapost/test_pickup.py | 28 +- .../canadapost/tests/canadapost/test_rate.py | 34 +- .../tests/canadapost/test_return_shipment.py | 38 +- .../tests/canadapost/test_shipment.py | 64 +- .../tests/canadapost/test_tracking.py | 20 +- modules/connectors/chronopost/SPECS.md | 522 + .../karrio/mappers/chronopost/mapper.py | 25 +- .../karrio/mappers/chronopost/proxy.py | 7 +- .../karrio/plugins/chronopost/__init__.py | 1 - .../karrio/providers/chronopost/__init__.py | 6 +- .../karrio/providers/chronopost/error.py | 9 +- .../karrio/providers/chronopost/rate.py | 25 +- .../providers/chronopost/shipment/__init__.py | 8 +- .../providers/chronopost/shipment/cancel.py | 5 +- .../providers/chronopost/shipment/create.py | 44 +- .../karrio/providers/chronopost/tracking.py | 31 +- .../karrio/providers/chronopost/units.py | 56 +- .../karrio/providers/chronopost/utils.py | 3 +- .../chronopost/tests/chronopost/fixture.py | 4 +- .../chronopost/tests/chronopost/test_rate.py | 17 +- .../tests/chronopost/test_shipment.py | 39 +- .../tests/chronopost/test_tracking.py | 17 +- modules/connectors/dhl_express/SPECS.md | 701 + .../karrio/mappers/dhl_express/mapper.py | 51 +- .../karrio/plugins/dhl_express/__init__.py | 1 - .../karrio/providers/dhl_express/__init__.py | 30 +- .../karrio/providers/dhl_express/error.py | 7 +- .../providers/dhl_express/pickup/cancel.py | 14 +- .../providers/dhl_express/pickup/create.py | 58 +- .../providers/dhl_express/pickup/update.py | 64 +- .../providers/dhl_express/return_shipment.py | 5 +- .../karrio/providers/dhl_express/shipment.py | 176 +- .../karrio/providers/dhl_express/tracking.py | 33 +- .../karrio/providers/dhl_express/units.py | 586 +- .../karrio/providers/dhl_express/utils.py | 14 +- .../connectors/dhl_express/tests/__init__.py | 6 +- .../dhl_express/tests/dhl_express/fixture.py | 8 +- .../tests/dhl_express/test_address.py | 27 +- .../tests/dhl_express/test_pickup.py | 28 +- .../tests/dhl_express/test_rate.py | 28 +- .../tests/dhl_express/test_shipment.py | 46 +- .../tests/dhl_express/test_tracking.py | 32 +- modules/connectors/dhl_freight/MANIFEST.in | 1 + modules/connectors/dhl_freight/PRD.md | 526 + modules/connectors/dhl_freight/README.md | 75 + modules/connectors/dhl_freight/SPECS.md | 576 + modules/connectors/dhl_freight/generate | 20 + .../karrio/mappers/dhl_freight/__init__.py | 3 + .../karrio/mappers/dhl_freight/mapper.py | 39 + .../karrio/mappers/dhl_freight/proxy.py | 130 + .../karrio/mappers/dhl_freight/settings.py | 37 + .../karrio/plugins/dhl_freight/__init__.py | 20 + .../karrio/providers/dhl_freight/__init__.py | 9 + .../karrio/providers/dhl_freight/error.py | 69 + .../karrio/providers/dhl_freight/i18n.py | 55 + .../karrio/providers/dhl_freight/services.csv | 6 + .../dhl_freight/shipment/__init__.py | 4 + .../providers/dhl_freight/shipment/create.py | 295 + .../karrio/providers/dhl_freight/tracking.py | 105 + .../karrio/providers/dhl_freight/units.py | 451 + .../karrio/providers/dhl_freight/utils.py | 104 + .../karrio/schemas/dhl_freight/__init__.py | 0 .../schemas/dhl_freight/error_response.py | 30 + .../schemas/dhl_freight/shipping_request.py | 148 + .../schemas/dhl_freight/shipping_response.py | 18 + .../schemas/dhl_freight/tracking_response.py | 102 + modules/connectors/dhl_freight/pyproject.toml | 39 + .../dhl_freight/schemas/error_response.json | 20 + .../dhl_freight/schemas/shipping_request.json | 164 + .../schemas/shipping_response.json | 9 + .../schemas/tracking_response.json | 78 + .../connectors/dhl_freight/tests/__init__.py | 0 .../dhl_freight/tests/dhl_freight/__init__.py | 0 .../dhl_freight/tests/dhl_freight/fixture.py | 30 + .../tests/dhl_freight/test_authentication.py | 96 + .../tests/dhl_freight/test_error.py | 115 + .../tests/dhl_freight/test_i18n.py | 33 + .../tests/dhl_freight/test_rate.py | 68 + .../tests/dhl_freight/test_shipment.py | 643 + .../tests/dhl_freight/test_tracking.py | 109 + .../tests/dhl_freight/test_units.py | 139 + ...g_SANDBOX_2026_R03.postman_collection.json | 101 + modules/connectors/dhl_parcel_de/SPECS.md | 721 + .../karrio/mappers/dhl_parcel_de/mapper.py | 43 +- .../karrio/mappers/dhl_parcel_de/proxy.py | 67 +- .../karrio/mappers/dhl_parcel_de/settings.py | 7 +- .../karrio/plugins/dhl_parcel_de/__init__.py | 1 - .../providers/dhl_parcel_de/__init__.py | 18 +- .../karrio/providers/dhl_parcel_de/error.py | 29 +- .../dhl_parcel_de/pickup/__init__.py | 8 +- .../providers/dhl_parcel_de/pickup/cancel.py | 9 +- .../providers/dhl_parcel_de/pickup/create.py | 62 +- .../providers/dhl_parcel_de/services.csv | 36 +- .../dhl_parcel_de/shipment/cancel.py | 6 +- .../dhl_parcel_de/shipment/return_shipment.py | 51 +- .../providers/dhl_parcel_de/tracking.py | 85 +- .../karrio/providers/dhl_parcel_de/units.py | 428 +- .../karrio/providers/dhl_parcel_de/utils.py | 91 +- .../schemas/dhl_parcel_de/shipping_request.py | 10 +- .../dhl_parcel_de/shipping_response.py | 1 + .../schemas/shipping_request.json | 16 +- .../schemas/shipping_response.json | 1 + .../dhl_parcel_de/tests/__init__.py | 4 +- .../tests/dhl_parcel_de/fixture.py | 3 +- .../dhl_parcel_de/test_authentication.py | 94 +- .../dhl_parcel_de/test_billing_resolution.py | 102 + .../tests/dhl_parcel_de/test_delivery_tier.py | 70 + .../tests/dhl_parcel_de/test_pickup.py | 30 +- .../tests/dhl_parcel_de/test_rate.py | 26 +- .../dhl_parcel_de/test_return_shipment.py | 41 +- .../tests/dhl_parcel_de/test_shipment.py | 688 +- .../tests/dhl_parcel_de/test_tracking.py | 24 +- .../vendors/test_tracking_live.py | 79 +- modules/connectors/dhl_poland/SPECS.md | 464 + .../karrio/mappers/dhl_poland/mapper.py | 33 +- .../karrio/mappers/dhl_poland/proxy.py | 9 +- .../karrio/mappers/dhl_poland/settings.py | 7 +- .../karrio/plugins/dhl_poland/__init__.py | 1 - .../karrio/providers/dhl_poland/__init__.py | 7 +- .../karrio/providers/dhl_poland/error.py | 9 +- .../karrio/providers/dhl_poland/services.csv | 60 +- .../providers/dhl_poland/shipment/cancel.py | 30 +- .../providers/dhl_poland/shipment/create.py | 44 +- .../dhl_poland/shipment/return_shipment.py | 5 +- .../karrio/providers/dhl_poland/tracking.py | 20 +- .../karrio/providers/dhl_poland/units.py | 24 +- .../karrio/providers/dhl_poland/utils.py | 17 +- .../connectors/dhl_poland/tests/__init__.py | 2 +- .../dhl_poland/tests/dhl_poland/test_rate.py | 44 +- .../tests/dhl_poland/test_shipment.py | 30 +- .../tests/dhl_poland/test_tracking.py | 16 +- modules/connectors/dhl_universal/SPECS.md | 312 + .../karrio/mappers/dhl_universal/mapper.py | 27 +- .../karrio/mappers/dhl_universal/proxy.py | 12 +- .../karrio/plugins/dhl_universal/__init__.py | 5 +- .../providers/dhl_universal/__init__.py | 2 +- .../karrio/providers/dhl_universal/error.py | 7 +- .../providers/dhl_universal/tracking.py | 58 +- .../karrio/providers/dhl_universal/units.py | 3 +- .../karrio/providers/dhl_universal/utils.py | 6 +- .../tests/dhl_universal/test_tracking.py | 16 +- modules/connectors/dpd/SPECS.md | 531 + .../dpd/karrio/mappers/dpd/mapper.py | 19 +- .../dpd/karrio/mappers/dpd/proxy.py | 7 +- .../dpd/karrio/mappers/dpd/settings.py | 10 +- .../dpd/karrio/plugins/dpd/__init__.py | 1 - .../dpd/karrio/providers/dpd/__init__.py | 2 +- .../dpd/karrio/providers/dpd/error.py | 17 +- .../karrio/providers/dpd/shipment/create.py | 97 +- .../dpd/karrio/providers/dpd/tracking.py | 39 +- .../dpd/karrio/providers/dpd/units.py | 67 +- .../dpd/karrio/providers/dpd/utils.py | 21 +- modules/connectors/dpd/tests/__init__.py | 2 +- modules/connectors/dpd/tests/dpd/fixture.py | 3 +- .../dpd/tests/dpd/test_authentication.py | 8 +- .../connectors/dpd/tests/dpd/test_rating.py | 24 +- .../connectors/dpd/tests/dpd/test_shipment.py | 17 +- .../connectors/dpd/tests/dpd/test_tracking.py | 17 +- modules/connectors/dpd_france/MANIFEST.in | 1 + modules/connectors/dpd_france/README.md | 69 + modules/connectors/dpd_france/SPECS.md | 501 + modules/connectors/dpd_france/generate | 9 + .../karrio/mappers/dpd_france/__init__.py | 3 + .../karrio/mappers/dpd_france/mapper.py | 69 + .../karrio/mappers/dpd_france/proxy.py | 116 + .../karrio/mappers/dpd_france/settings.py | 36 + .../karrio/plugins/dpd_france/__init__.py | 23 + .../karrio/providers/dpd_france/__init__.py | 24 + .../karrio/providers/dpd_france/error.py | 30 + .../providers/dpd_france/pickup/__init__.py | 8 + .../providers/dpd_france/pickup/cancel.py | 80 + .../providers/dpd_france/pickup/create.py | 88 + .../providers/dpd_france/return_shipment.py | 101 + .../karrio/providers/dpd_france/services.csv | 31 + .../providers/dpd_france/shipment/__init__.py | 8 + .../providers/dpd_france/shipment/cancel.py | 72 + .../providers/dpd_france/shipment/create.py | 158 + .../karrio/providers/dpd_france/tracking.py | 125 + .../karrio/providers/dpd_france/units.py | 148 + .../karrio/providers/dpd_france/utils.py | 57 + .../karrio/schemas/dpd_france/__init__.py | 0 .../schemas/dpd_france/eprintwebservice.py | 47096 ++++++++++++++++ .../karrio/schemas/dpd_france/error.py | 1331 + .../schemas/dpd_france/webtraceservice.py | 8471 +++ modules/connectors/dpd_france/pyproject.toml | 39 + .../dpd_france/schemas/EPrintWebservice.xsd | 3169 ++ .../dpd_france/schemas/Webtrace_Service.xsd | 481 + .../dpd_france/schemas/error_response.xsd | 13 + .../connectors/dpd_france/tests/__init__.py | 5 + .../dpd_france/tests/dpd_france/__init__.py | 1 + .../dpd_france/tests/dpd_france/fixture.py | 17 + .../tests/dpd_france/test_pickup.py | 208 + .../dpd_france/tests/dpd_france/test_rate.py | 142 + .../tests/dpd_france/test_return_shipment.py | 187 + .../tests/dpd_france/test_shipment.py | 397 + .../tests/dpd_france/test_tracking.py | 156 + .../connectors/dpd_france/vendor/README.md | 116 + modules/connectors/dpd_meta/README.md | 27 +- modules/connectors/dpd_meta/SPECS.md | 580 + modules/connectors/dpd_meta/generate | 3 + .../karrio/mappers/dpd_meta/__init__.py | 3 +- .../dpd_meta/karrio/mappers/dpd_meta/hooks.py | 15 + .../karrio/mappers/dpd_meta/mapper.py | 37 +- .../dpd_meta/karrio/mappers/dpd_meta/proxy.py | 275 +- .../karrio/mappers/dpd_meta/settings.py | 43 +- .../karrio/plugins/dpd_meta/__init__.py | 12 +- .../karrio/providers/dpd_meta/__init__.py | 19 +- .../karrio/providers/dpd_meta/error.py | 61 +- .../providers/dpd_meta/hooks/__init__.py | 5 + .../karrio/providers/dpd_meta/hooks/event.py | 79 + .../karrio/providers/dpd_meta/location.py | 118 + .../providers/dpd_meta/pickup/__init__.py | 2 +- .../providers/dpd_meta/pickup/create.py | 64 +- .../karrio/providers/dpd_meta/services.csv | 162 +- .../providers/dpd_meta/shipment/__init__.py | 1 - .../providers/dpd_meta/shipment/create.py | 353 +- .../dpd_meta/shipment/return_shipment.py | 5 +- .../karrio/providers/dpd_meta/units.py | 696 +- .../karrio/providers/dpd_meta/utils.py | 57 +- .../schemas/dpd_meta/shipment_request.py | 54 +- .../karrio/schemas/dpd_meta/webapi.py | 2797 + .../dpd_meta/schemas/shipment_request.json | 71 +- .../connectors/dpd_meta/schemas/webapi.xsd | 133 + modules/connectors/dpd_meta/tests/__init__.py | 2 +- .../dpd_meta/tests/dpd_meta/__init__.py | 2 +- .../dpd_meta/tests/dpd_meta/fixture.py | 25 +- .../tests/dpd_meta/test_authentication.py | 435 + .../dpd_meta/tests/dpd_meta/test_location.py | 374 + .../dpd_meta/tests/dpd_meta/test_pickup.py | 35 +- .../dpd_meta/tests/dpd_meta/test_shipment.py | 940 +- .../dpd_meta/tests/dpd_meta/test_units.py | 130 + .../dpd_meta/tests/dpd_meta/test_webhook.py | 112 + modules/connectors/fedex/SPECS.md | 623 + .../fedex/karrio/mappers/fedex/mapper.py | 53 +- .../fedex/karrio/mappers/fedex/proxy.py | 15 +- .../fedex/karrio/mappers/fedex/settings.py | 2 - .../fedex/karrio/plugins/fedex/__init__.py | 1 - .../fedex/karrio/providers/fedex/__init__.py | 34 +- .../fedex/karrio/providers/fedex/document.py | 18 +- .../fedex/karrio/providers/fedex/error.py | 31 +- .../karrio/providers/fedex/pickup/cancel.py | 11 +- .../karrio/providers/fedex/pickup/create.py | 18 +- .../karrio/providers/fedex/pickup/update.py | 14 +- .../fedex/karrio/providers/fedex/rate.py | 112 +- .../providers/fedex/shipment/__init__.py | 1 - .../karrio/providers/fedex/shipment/cancel.py | 8 +- .../fedex/shipment/return_shipment.py | 5 +- .../fedex/karrio/providers/fedex/tracking.py | 38 +- .../fedex/karrio/providers/fedex/units.py | 142 +- .../fedex/karrio/providers/fedex/utils.py | 28 +- .../connectors/fedex/tests/fedex/fixture.py | 3 +- .../fedex/tests/fedex/test_authentication.py | 2 +- .../fedex/tests/fedex/test_document.py | 29 +- .../fedex/tests/fedex/test_pickup.py | 22 +- .../connectors/fedex/tests/fedex/test_rate.py | 25 +- .../fedex/tests/fedex/test_return_shipment.py | 35 +- .../fedex/tests/fedex/test_tracking.py | 28 +- modules/connectors/generic/SPECS.md | 456 + .../generic/karrio/mappers/generic/mapper.py | 23 +- .../generic/karrio/mappers/generic/proxy.py | 3 +- .../karrio/mappers/generic/settings.py | 7 +- .../generic/karrio/providers/generic/units.py | 2 +- .../generic/tests/generic/test_errors.py | 16 +- .../generic/tests/generic/test_rate.py | 16 +- .../generic/tests/generic/test_shipment.py | 11 +- modules/connectors/gls/SPECS.md | 787 + modules/connectors/gls/generate | 3 + .../gls/karrio/mappers/gls/__init__.py | 1 + .../gls/karrio/mappers/gls/mapper.py | 57 +- .../gls/karrio/mappers/gls/proxy.py | 42 +- .../gls/karrio/mappers/gls/settings.py | 13 +- .../gls/karrio/plugins/gls/__init__.py | 2 +- .../gls/karrio/providers/gls/__init__.py | 25 +- .../gls/karrio/providers/gls/document.py | 151 + .../gls/karrio/providers/gls/error.py | 8 +- .../gls/karrio/providers/gls/location.py | 109 + .../karrio/providers/gls/pickup/__init__.py | 4 + .../gls/karrio/providers/gls/pickup/create.py | 65 + .../gls/karrio/providers/gls/services.csv | 45 +- .../karrio/providers/gls/shipment/__init__.py | 5 +- .../karrio/providers/gls/shipment/cancel.py | 38 + .../karrio/providers/gls/shipment/create.py | 396 +- .../providers/gls/shipment/return_shipment.py | 5 +- .../gls/karrio/providers/gls/tracking.py | 30 +- .../gls/karrio/providers/gls/units.py | 786 +- .../gls/karrio/providers/gls/utils.py | 239 +- .../gls/customs_consignment_request.py | 124 + .../gls/karrio/schemas/gls/pickup_request.py | 14 + .../gls/karrio/schemas/gls/pickup_response.py | 8 + .../karrio/schemas/gls/shipment_request.py | 41 +- .../karrio/schemas/gls/shipment_response.py | 104 +- .../schemas/customs_consignment_request.json | 135 + .../gls/schemas/pickup_request.json | 9 + .../gls/schemas/pickup_response.json | 3 + .../gls/schemas/shipment_request.json | 81 +- .../gls/schemas/shipment_response.json | 94 +- modules/connectors/gls/tests/gls/fixture.py | 29 +- .../connectors/gls/tests/gls/test_document.py | 106 + .../connectors/gls/tests/gls/test_pickup.py | 64 + modules/connectors/gls/tests/gls/test_rate.py | 136 +- .../connectors/gls/tests/gls/test_shipment.py | 922 +- .../connectors/gls/tests/gls/test_tracking.py | 38 +- modules/connectors/gls/vendor/STALE.md | 84 + .../gls/vendor/customs-consignments-v3.yaml | 1024 + .../gls/vendor/document-management-v1.yaml | 2 +- .../connectors/gls/vendor/doxygen/doxygen.css | 1596 + .../connectors/gls/vendor/doxygen/index.html | 133 + .../gls/vendor/doxygen/rest_parcel_shop.html | 259 + .../doxygen/rest_shipment_processing.html | 1166 + .../doxygen/rest_sporadic_collection.html | 153 + .../gls/vendor/doxygen/rest_tracking.html | 236 + .../rest_web_service_requests_samples.html | 707 + ...st_webservice_fault_responses_samples.html | 362 + .../connectors/gls/vendor/doxygen/tabs.css | 1 + .../vendor/gls-validated-samples/README.md | 31 + .../service_addresseeonly.json | 69 + .../service_flexdelivery.json | 69 + .../service_pickandreturn.json | 58 + .../service_shopreturn.json | 58 + .../service_signature.json | 78 + .../connectors/gls/vendor/shipit-farm.yaml | 1624 - modules/connectors/hermes/SPECS.md | 700 + modules/connectors/hermes/generate | 1 + .../hermes/karrio/mappers/hermes/__init__.py | 2 +- .../hermes/karrio/mappers/hermes/mapper.py | 46 +- .../hermes/karrio/mappers/hermes/proxy.py | 42 +- .../hermes/karrio/mappers/hermes/settings.py | 13 +- .../hermes/karrio/plugins/hermes/__init__.py | 4 +- .../karrio/providers/hermes/__init__.py | 18 +- .../hermes/karrio/providers/hermes/error.py | 19 +- .../karrio/providers/hermes/location.py | 103 + .../providers/hermes/pickup/__init__.py | 10 +- .../karrio/providers/hermes/pickup/cancel.py | 15 +- .../karrio/providers/hermes/pickup/create.py | 34 +- .../karrio/providers/hermes/services.csv | 48 +- .../providers/hermes/shipment/create.py | 131 +- .../hermes/shipment/return_shipment.py | 5 +- .../karrio/providers/hermes/tracking.py | 64 +- .../hermes/karrio/providers/hermes/units.py | 255 +- .../hermes/karrio/providers/hermes/utils.py | 35 +- .../karrio/schemas/hermes/parcelshop.py | 30 + .../connectors/hermes/schemas/parcelshop.json | 24 + modules/connectors/hermes/tests/__init__.py | 2 +- .../hermes/tests/hermes/__init__.py | 2 +- .../connectors/hermes/tests/hermes/fixture.py | 4 +- .../hermes/tests/hermes/test_location.py | 197 + .../hermes/tests/hermes/test_pickup.py | 79 +- .../hermes/tests/hermes/test_rate.py | 44 +- .../hermes/tests/hermes/test_shipment.py | 107 +- .../hermes/tests/hermes/test_tracking.py | 28 +- modules/connectors/hermes/vendor/openapi.yaml | 753 +- modules/connectors/landmark/SPECS.md | 567 + .../karrio/mappers/landmark/mapper.py | 31 +- .../landmark/karrio/mappers/landmark/proxy.py | 8 +- .../karrio/mappers/landmark/settings.py | 9 +- .../karrio/plugins/landmark/__init__.py | 6 +- .../karrio/providers/landmark/__init__.py | 8 +- .../karrio/providers/landmark/error.py | 7 +- .../karrio/providers/landmark/services.csv | 98 +- .../providers/landmark/shipment/__init__.py | 1 - .../providers/landmark/shipment/cancel.py | 21 +- .../providers/landmark/shipment/create.py | 64 +- .../landmark/shipment/return_shipment.py | 9 +- .../karrio/providers/landmark/tracking.py | 49 +- .../karrio/providers/landmark/units.py | 43 +- .../karrio/providers/landmark/utils.py | 3 +- modules/connectors/landmark/tests/__init__.py | 2 +- .../landmark/tests/landmark/__init__.py | 2 +- .../landmark/tests/landmark/fixture.py | 1 - .../landmark/tests/landmark/test_rate.py | 110 +- .../landmark/tests/landmark/test_shipment.py | 47 +- .../landmark/tests/landmark/test_tracking.py | 121 +- modules/connectors/laposte/SPECS.md | 389 + .../karrio/mappers/laposte/__init__.py | 2 +- .../laposte/karrio/mappers/laposte/mapper.py | 11 +- .../laposte/karrio/mappers/laposte/proxy.py | 2 +- .../karrio/plugins/laposte/__init__.py | 1 - .../karrio/providers/laposte/__init__.py | 3 +- .../laposte/karrio/providers/laposte/error.py | 14 +- .../karrio/providers/laposte/services.csv | 36 +- .../karrio/providers/laposte/tracking.py | 42 +- .../laposte/karrio/providers/laposte/units.py | 11 +- .../laposte/karrio/providers/laposte/utils.py | 3 +- modules/connectors/laposte/tests/__init__.py | 3 +- .../laposte/tests/laposte/test_tracking.py | 17 +- modules/connectors/mydhl/SPECS.md | 735 + .../mydhl/karrio/mappers/mydhl/__init__.py | 2 +- .../mydhl/karrio/mappers/mydhl/mapper.py | 86 +- .../mydhl/karrio/mappers/mydhl/proxy.py | 12 +- .../mydhl/karrio/plugins/mydhl/__init__.py | 6 +- .../mydhl/karrio/providers/mydhl/__init__.py | 40 +- .../mydhl/karrio/providers/mydhl/address.py | 19 +- .../mydhl/karrio/providers/mydhl/document.py | 23 +- .../mydhl/karrio/providers/mydhl/error.py | 5 +- .../karrio/providers/mydhl/pickup/__init__.py | 8 +- .../karrio/providers/mydhl/pickup/cancel.py | 7 +- .../karrio/providers/mydhl/pickup/create.py | 38 +- .../karrio/providers/mydhl/pickup/update.py | 19 +- .../mydhl/karrio/providers/mydhl/rate.py | 29 +- .../mydhl/karrio/providers/mydhl/services.csv | 150 +- .../karrio/providers/mydhl/shipment/create.py | 96 +- .../mydhl/shipment/return_shipment.py | 5 +- .../mydhl/karrio/providers/mydhl/tracking.py | 26 +- .../mydhl/karrio/providers/mydhl/units.py | 14 +- .../mydhl/karrio/providers/mydhl/utils.py | 13 +- modules/connectors/mydhl/tests/__init__.py | 3 +- .../connectors/mydhl/tests/mydhl/__init__.py | 2 +- .../connectors/mydhl/tests/mydhl/fixture.py | 3 +- .../mydhl/tests/mydhl/test_address.py | 39 +- .../mydhl/tests/mydhl/test_document.py | 34 +- .../mydhl/tests/mydhl/test_pickup.py | 76 +- .../connectors/mydhl/tests/mydhl/test_rate.py | 104 +- .../mydhl/tests/mydhl/test_shipment.py | 88 +- .../mydhl/tests/mydhl/test_tracking.py | 31 +- modules/connectors/parcelone/README.md | 11 +- modules/connectors/parcelone/SPECS.md | 254 + .../karrio/mappers/parcelone/mapper.py | 38 +- .../karrio/mappers/parcelone/proxy.py | 89 +- .../karrio/mappers/parcelone/settings.py | 15 +- .../karrio/plugins/parcelone/__init__.py | 15 +- .../karrio/providers/parcelone/__init__.py | 16 +- .../karrio/providers/parcelone/error.py | 16 +- .../karrio/providers/parcelone/rate.py | 162 - .../karrio/providers/parcelone/services.csv | 202 +- .../providers/parcelone/shipment/__init__.py | 12 +- .../providers/parcelone/shipment/cancel.py | 41 +- .../providers/parcelone/shipment/create.py | 450 +- .../parcelone/shipment/return_shipment.py | 30 + .../karrio/providers/parcelone/tracking.py | 150 +- .../karrio/providers/parcelone/units.py | 861 +- .../karrio/providers/parcelone/utils.py | 222 +- .../karrio/schemas/parcelone/__init__.py | 69 - .../schemas/parcelone/cancel_response.py | 5 +- .../karrio/schemas/parcelone/error.py | 2 +- .../schemas/parcelone/shipping_request.py | 14 +- .../schemas/parcelone/shipping_response.py | 32 +- .../schemas/parcelone/tracking_response.py | 40 +- .../parcelone/schemas/cancel_response.json | 9 +- .../parcelone/schemas/error_response.json | 10 +- .../parcelone/schemas/shipping_request.json | 78 +- .../parcelone/schemas/shipping_response.json | 67 +- .../parcelone/schemas/tracking_response.json | 61 +- .../parcelone/tests/parcelone/fixture.py | 1 + .../parcelone/tests/parcelone/test_profile.py | 278 + .../parcelone/tests/parcelone/test_rate.py | 237 +- .../tests/parcelone/test_shipment.py | 1335 +- .../tests/parcelone/test_tracking.py | 224 +- .../vendor/ParcelOneShippingRESTApiV1.json | 488 +- .../vendor/ParcelOneShippingRESTApiV1.yaml | 4025 ++ .../vendor/ParcelOneTrackingRESTApiV1.json | 335 + .../vendor/ParcelOneTracklmcRESTApiV1.json | 592 +- .../vendor/ParcelOneTracklmcRESTApiV1.yaml | 249 + modules/connectors/postat/SPECS.md | 495 + .../postat/karrio/mappers/postat/__init__.py | 2 +- .../postat/karrio/mappers/postat/mapper.py | 19 +- .../postat/karrio/mappers/postat/proxy.py | 2 +- .../postat/karrio/mappers/postat/settings.py | 7 +- .../postat/karrio/plugins/postat/__init__.py | 4 +- .../karrio/providers/postat/__init__.py | 3 +- .../postat/karrio/providers/postat/error.py | 9 +- .../karrio/providers/postat/services.csv | 40 +- .../providers/postat/shipment/__init__.py | 9 +- .../providers/postat/shipment/cancel.py | 14 +- .../providers/postat/shipment/create.py | 32 +- .../postat/karrio/providers/postat/units.py | 40 +- .../postat/karrio/providers/postat/utils.py | 17 +- modules/connectors/postat/tests/__init__.py | 3 +- .../postat/tests/postat/__init__.py | 2 +- .../connectors/postat/tests/postat/fixture.py | 3 +- .../postat/tests/postat/test_settings.py | 55 + .../postat/tests/postat/test_shipment.py | 46 +- modules/connectors/purolator/SPECS.md | 584 + .../karrio/mappers/purolator/__init__.py | 2 +- .../karrio/mappers/purolator/mapper.py | 101 +- .../karrio/mappers/purolator/proxy.py | 8 +- .../karrio/plugins/purolator/__init__.py | 1 - .../karrio/providers/purolator/__init__.py | 26 +- .../karrio/providers/purolator/address.py | 35 +- .../karrio/providers/purolator/error.py | 12 +- .../providers/purolator/pickup/cancel.py | 26 +- .../providers/purolator/pickup/create.py | 82 +- .../providers/purolator/pickup/update.py | 46 +- .../providers/purolator/pickup/validate.py | 28 +- .../karrio/providers/purolator/rate.py | 106 +- .../karrio/providers/purolator/services.csv | 144 +- .../providers/purolator/shipment/cancel.py | 29 +- .../providers/purolator/shipment/create.py | 186 +- .../providers/purolator/shipment/documents.py | 26 +- .../purolator/shipment/return_shipment.py | 5 +- .../karrio/providers/purolator/tracking.py | 27 +- .../karrio/providers/purolator/units.py | 50 +- .../karrio/providers/purolator/utils.py | 10 +- .../connectors/purolator/tests/__init__.py | 6 +- .../purolator/tests/purolator/test_address.py | 22 +- .../purolator/tests/purolator/test_pickup.py | 22 +- .../purolator/tests/purolator/test_rate.py | 8 +- .../tests/purolator/test_shipment.py | 37 +- .../tests/purolator/test_tracking.py | 20 +- modules/connectors/seko/SPECS.md | 560 + .../seko/karrio/mappers/seko/__init__.py | 2 +- .../seko/karrio/mappers/seko/mapper.py | 55 +- .../seko/karrio/mappers/seko/proxy.py | 4 +- .../seko/karrio/plugins/seko/__init__.py | 1 - .../seko/karrio/providers/seko/__init__.py | 11 +- .../seko/karrio/providers/seko/error.py | 30 +- .../seko/karrio/providers/seko/manifest.py | 21 +- .../seko/karrio/providers/seko/rate.py | 29 +- .../seko/karrio/providers/seko/services.csv | 62 +- .../providers/seko/shipment/__init__.py | 9 +- .../karrio/providers/seko/shipment/cancel.py | 10 +- .../karrio/providers/seko/shipment/create.py | 61 +- .../seko/karrio/providers/seko/tracking.py | 33 +- .../seko/karrio/providers/seko/units.py | 42 +- .../seko/karrio/providers/seko/utils.py | 15 +- modules/connectors/seko/tests/__init__.py | 5 +- .../seko/tests/seko/test_manifest.py | 14 +- .../connectors/seko/tests/seko/test_rate.py | 14 +- .../seko/tests/seko/test_shipment.py | 32 +- .../seko/tests/seko/test_tracking.py | 37 +- modules/connectors/sendle/SPECS.md | 489 + .../sendle/karrio/mappers/sendle/__init__.py | 2 +- .../sendle/karrio/mappers/sendle/mapper.py | 46 +- .../sendle/karrio/mappers/sendle/proxy.py | 10 +- .../sendle/karrio/plugins/sendle/__init__.py | 1 - .../karrio/providers/sendle/__init__.py | 3 +- .../sendle/karrio/providers/sendle/error.py | 17 +- .../sendle/karrio/providers/sendle/rate.py | 17 +- .../karrio/providers/sendle/services.csv | 36 +- .../providers/sendle/shipment/__init__.py | 9 +- .../providers/sendle/shipment/cancel.py | 17 +- .../providers/sendle/shipment/create.py | 48 +- .../karrio/providers/sendle/tracking.py | 33 +- .../sendle/karrio/providers/sendle/units.py | 10 +- .../sendle/karrio/providers/sendle/utils.py | 12 +- modules/connectors/sendle/tests/__init__.py | 3 +- .../sendle/tests/sendle/test_rate.py | 25 +- .../sendle/tests/sendle/test_shipment.py | 37 +- .../sendle/tests/sendle/test_tracking.py | 29 +- modules/connectors/smartkargo/SPECS.md | 559 + .../karrio/mappers/smartkargo/__init__.py | 2 +- .../karrio/mappers/smartkargo/mapper.py | 29 +- .../karrio/mappers/smartkargo/proxy.py | 15 +- .../karrio/plugins/smartkargo/__init__.py | 3 +- .../karrio/providers/smartkargo/__init__.py | 5 +- .../karrio/providers/smartkargo/error.py | 19 +- .../karrio/providers/smartkargo/rate.py | 104 +- .../providers/smartkargo/shipment/__init__.py | 9 +- .../providers/smartkargo/shipment/cancel.py | 20 +- .../providers/smartkargo/shipment/create.py | 48 +- .../karrio/providers/smartkargo/units.py | 2 +- .../karrio/providers/smartkargo/utils.py | 2 +- .../connectors/smartkargo/tests/__init__.py | 2 +- .../smartkargo/tests/smartkargo/__init__.py | 2 +- .../smartkargo/tests/smartkargo/fixture.py | 3 +- .../smartkargo/tests/smartkargo/test_rate.py | 20 +- .../tests/smartkargo/test_shipment.py | 34 +- .../tests/smartkargo/test_tracking.py | 24 +- modules/connectors/spring/SPECS.md | 542 + .../spring/karrio/mappers/spring/__init__.py | 2 +- .../spring/karrio/mappers/spring/mapper.py | 40 +- .../spring/karrio/mappers/spring/proxy.py | 2 +- .../spring/karrio/mappers/spring/settings.py | 7 +- .../spring/karrio/plugins/spring/__init__.py | 1 - .../karrio/providers/spring/__init__.py | 10 +- .../spring/karrio/providers/spring/error.py | 12 +- .../karrio/providers/spring/services.csv | 550 +- .../providers/spring/shipment/__init__.py | 1 - .../providers/spring/shipment/cancel.py | 9 +- .../providers/spring/shipment/create.py | 33 +- .../spring/shipment/return_shipment.py | 5 +- .../karrio/providers/spring/tracking.py | 20 +- .../spring/karrio/providers/spring/units.py | 85 +- .../spring/karrio/providers/spring/utils.py | 9 +- modules/connectors/spring/tests/__init__.py | 3 +- .../spring/tests/spring/__init__.py | 2 +- .../connectors/spring/tests/spring/fixture.py | 1 - .../spring/tests/spring/test_shipment.py | 47 +- .../spring/tests/spring/test_tracking.py | 23 +- modules/connectors/teleship/SPECS.md | 556 + .../karrio/mappers/teleship/__init__.py | 2 +- .../teleship/karrio/mappers/teleship/hooks.py | 14 +- .../karrio/mappers/teleship/mapper.py | 81 +- .../teleship/karrio/mappers/teleship/proxy.py | 15 +- .../karrio/plugins/teleship/__init__.py | 8 +- .../karrio/providers/teleship/__init__.py | 45 +- .../karrio/providers/teleship/duties.py | 13 +- .../karrio/providers/teleship/error.py | 8 +- .../karrio/providers/teleship/hooks/event.py | 13 +- .../karrio/providers/teleship/hooks/oauth.py | 14 +- .../karrio/providers/teleship/manifest.py | 9 +- .../providers/teleship/pickup/__init__.py | 8 +- .../providers/teleship/pickup/cancel.py | 9 +- .../providers/teleship/pickup/schedule.py | 9 +- .../karrio/providers/teleship/rate.py | 40 +- .../karrio/providers/teleship/services.csv | 60 +- .../providers/teleship/shipment/__init__.py | 9 +- .../providers/teleship/shipment/cancel.py | 9 +- .../providers/teleship/shipment/create.py | 40 +- .../karrio/providers/teleship/tracking.py | 24 +- .../karrio/providers/teleship/units.py | 11 +- .../karrio/providers/teleship/utils.py | 13 +- .../providers/teleship/webhook/__init__.py | 10 +- .../providers/teleship/webhook/deregister.py | 9 +- .../providers/teleship/webhook/register.py | 9 +- modules/connectors/teleship/tests/__init__.py | 3 +- .../teleship/tests/teleship/__init__.py | 2 +- .../teleship/tests/teleship/fixture.py | 5 +- .../tests/teleship/test_authentication.py | 2 +- .../teleship/tests/teleship/test_duties.py | 15 +- .../teleship/tests/teleship/test_hooks.py | 17 +- .../teleship/tests/teleship/test_manifest.py | 15 +- .../teleship/tests/teleship/test_pickup.py | 19 +- .../teleship/tests/teleship/test_rate.py | 15 +- .../teleship/tests/teleship/test_shipment.py | 15 +- .../teleship/tests/teleship/test_tracking.py | 15 +- .../teleship/tests/teleship/test_webhook.py | 49 +- modules/connectors/ups/SPECS.md | 826 + .../ups/karrio/mappers/ups/__init__.py | 2 +- .../ups/karrio/mappers/ups/mapper.py | 47 +- .../ups/karrio/mappers/ups/proxy.py | 31 +- .../ups/karrio/mappers/ups/settings.py | 10 +- .../ups/karrio/plugins/ups/__init__.py | 2 +- .../ups/karrio/providers/ups/__init__.py | 30 +- .../ups/karrio/providers/ups/document.py | 22 +- .../ups/karrio/providers/ups/error.py | 28 +- .../karrio/providers/ups/pickup/__init__.py | 8 +- .../ups/karrio/providers/ups/pickup/cancel.py | 6 +- .../ups/karrio/providers/ups/pickup/create.py | 40 +- .../ups/karrio/providers/ups/rate.py | 217 +- .../karrio/providers/ups/shipment/cancel.py | 13 +- .../karrio/providers/ups/shipment/create.py | 328 +- .../providers/ups/shipment/return_shipment.py | 10 +- .../ups/karrio/providers/ups/tracking.py | 63 +- .../ups/karrio/providers/ups/units.py | 435 +- .../ups/karrio/providers/ups/utils.py | 40 +- modules/connectors/ups/tests/ups/fixture.py | 3 +- .../ups/tests/ups/test_authentication.py | 5 +- .../connectors/ups/tests/ups/test_document.py | 34 +- .../connectors/ups/tests/ups/test_pickup.py | 83 +- modules/connectors/ups/tests/ups/test_rate.py | 124 +- .../ups/tests/ups/test_return_shipment.py | 35 +- .../ups/tests/ups/test_service_zone.py | 123 + .../connectors/ups/tests/ups/test_shipment.py | 447 +- .../connectors/ups/tests/ups/test_tracking.py | 214 +- modules/connectors/usps/SPECS.md | 654 + .../usps/karrio/mappers/usps/__init__.py | 2 +- .../usps/karrio/mappers/usps/mapper.py | 87 +- .../usps/karrio/mappers/usps/proxy.py | 9 +- .../usps/karrio/plugins/usps/__init__.py | 1 - .../usps/karrio/providers/usps/__init__.py | 30 +- .../usps/karrio/providers/usps/error.py | 20 +- .../usps/karrio/providers/usps/manifest.py | 26 +- .../karrio/providers/usps/pickup/__init__.py | 1 - .../karrio/providers/usps/pickup/cancel.py | 9 +- .../karrio/providers/usps/pickup/create.py | 34 +- .../karrio/providers/usps/pickup/update.py | 24 +- .../usps/karrio/providers/usps/rate.py | 85 +- .../usps/karrio/providers/usps/services.csv | 68 +- .../providers/usps/shipment/__init__.py | 1 - .../karrio/providers/usps/shipment/cancel.py | 15 +- .../karrio/providers/usps/shipment/create.py | 79 +- .../usps/shipment/return_shipment.py | 7 +- .../usps/karrio/providers/usps/tracking.py | 46 +- .../usps/karrio/providers/usps/units.py | 21 +- .../usps/karrio/providers/usps/utils.py | 16 +- modules/connectors/usps/tests/__init__.py | 9 +- modules/connectors/usps/tests/usps/fixture.py | 3 +- .../usps/tests/usps/test_authentication.py | 2 +- .../usps/tests/usps/test_manifest.py | 15 +- .../connectors/usps/tests/usps/test_pickup.py | 23 +- .../connectors/usps/tests/usps/test_rate.py | 20 +- .../usps/tests/usps/test_return_shipment.py | 31 +- .../usps/tests/usps/test_shipment.py | 55 +- .../usps/tests/usps/test_tracking.py | 23 +- .../connectors/usps_international/SPECS.md | 644 + .../mappers/usps_international/__init__.py | 2 +- .../mappers/usps_international/mapper.py | 51 +- .../mappers/usps_international/proxy.py | 9 +- .../plugins/usps_international/__init__.py | 1 - .../providers/usps_international/__init__.py | 31 +- .../providers/usps_international/error.py | 13 +- .../providers/usps_international/manifest.py | 26 +- .../usps_international/pickup/__init__.py | 8 +- .../usps_international/pickup/cancel.py | 9 +- .../usps_international/pickup/create.py | 34 +- .../usps_international/pickup/update.py | 24 +- .../providers/usps_international/rate.py | 68 +- .../providers/usps_international/services.csv | 44 +- .../usps_international/shipment/__init__.py | 8 +- .../usps_international/shipment/cancel.py | 15 +- .../usps_international/shipment/create.py | 54 +- .../shipment/return_shipment.py | 5 +- .../providers/usps_international/tracking.py | 44 +- .../providers/usps_international/units.py | 25 +- .../providers/usps_international/utils.py | 11 +- .../usps_international/tests/__init__.py | 8 +- .../tests/usps_international/fixture.py | 3 +- .../usps_international/test_authentication.py | 2 +- .../tests/usps_international/test_manifest.py | 15 +- .../tests/usps_international/test_pickup.py | 23 +- .../tests/usps_international/test_rate.py | 14 +- .../tests/usps_international/test_shipment.py | 33 +- .../tests/usps_international/test_tracking.py | 19 +- modules/core/karrio/server/conf.py | 6 +- modules/core/karrio/server/core/__init__.py | 4 +- modules/core/karrio/server/core/admin.py | 1 - modules/core/karrio/server/core/apps.py | 3 + .../core/karrio/server/core/authentication.py | 118 +- .../karrio/server/core/backends/__init__.py | 0 .../karrio/server/core/backends/immediate.py | 56 + modules/core/karrio/server/core/config.py | 1 + .../karrio/server/core/context_processors.py | 11 +- modules/core/karrio/server/core/datatypes.py | 176 +- modules/core/karrio/server/core/dataunits.py | 215 +- modules/core/karrio/server/core/dynamic.py | 193 + modules/core/karrio/server/core/exceptions.py | 104 +- modules/core/karrio/server/core/filters.py | 163 +- modules/core/karrio/server/core/gateway.py | 366 +- modules/core/karrio/server/core/logging.py | 21 +- .../karrio/server/core/management/__init__.py | 0 .../core/management/commands/__init__.py | 0 .../server/core/management/commands/cli.py | 5 +- .../commands/create_oauth_client.py | 35 +- .../core/management/commands/runserver.py | 3 +- modules/core/karrio/server/core/middleware.py | 96 +- .../server/core/migrations/0001_initial.py | 20 +- .../core/migrations/0002_apilogindex.py | 8 +- .../migrations/0003_apilogindex_test_mode.py | 20 +- .../server/core/migrations/0004_metafield.py | 13 +- ...er_metafield_type_alter_metafield_value.py | 27 +- .../0006_add_api_log_requested_at_index.py | 5 +- .../0007_add_generic_fk_to_metafield.py | 1 - .../migrations/0008_apilogindex_request_id.py | 1 - .../karrio/server/core/models/__init__.py | 38 +- .../core/karrio/server/core/models/base.py | 34 +- .../core/karrio/server/core/models/entity.py | 8 +- .../karrio/server/core/models/metafield.py | 52 +- .../karrio/server/core/models/third_party.py | 7 +- .../karrio/server/core/oauth_validators.py | 51 +- modules/core/karrio/server/core/paperless.py | 233 + .../core/karrio/server/core/permissions.py | 15 +- modules/core/karrio/server/core/public_ids.py | 357 + .../karrio/server/core/rate_dispatcher.py | 150 + modules/core/karrio/server/core/renderers.py | 4 +- modules/core/karrio/server/core/signals.py | 37 +- .../core/karrio/server/core/task_backend.py | 412 + modules/core/karrio/server/core/telemetry.py | 258 +- .../karrio/server/core/telemetry_scrubbing.py | 369 + .../core/karrio/server/core/tests/__init__.py | 92 +- .../server/core/tests/_logging_helpers.py | 39 + modules/core/karrio/server/core/tests/base.py | 14 +- .../core/tests/test_apply_rate_selection.py | 238 + .../tests/test_authentication_middleware.py | 137 + .../tests/test_carrier_plugin_visibility.py | 83 + .../server/core/tests/test_exception_level.py | 8 +- .../server/core/tests/test_health_status.py | 99 + .../karrio/server/core/tests/test_mfa_api.py | 389 + .../core/tests/test_password_hashers.py | 84 + .../server/core/tests/test_public_ids.py | 211 + .../server/core/tests/test_rate_dispatcher.py | 251 + .../server/core/tests/test_request_id.py | 20 +- .../server/core/tests/test_resource_token.py | 20 +- .../core/tests/test_shipment_documents.py | 4 +- .../server/core/tests/test_task_backend.py | 551 + .../core/tests/test_telemetry_scrubbing.py | 366 + .../tests/test_telemetry_scrubbing_hook.py | 154 + modules/core/karrio/server/core/urls.py | 8 +- modules/core/karrio/server/core/utils.py | 285 +- modules/core/karrio/server/core/validators.py | 205 +- .../core/karrio/server/core/views/__init__.py | 2 + modules/core/karrio/server/core/views/api.py | 42 +- .../core/karrio/server/core/views/health.py | 25 + .../core/karrio/server/core/views/metadata.py | 13 +- .../core/karrio/server/core/views/oauth.py | 45 +- .../karrio/server/core/views/references.py | 35 +- .../core/karrio/server/core/views/schema.py | 28 +- .../core/karrio/server/filters/abstract.py | 10 +- modules/core/karrio/server/iam/admin.py | 3 - .../server/iam/migrations/0001_initial.py | 60 +- .../0002_setup_carrier_permission_groups.py | 44 +- .../0003_remove_permission_groups.py | 1 - .../0004_dedupe_context_permissions.py | 60 + modules/core/karrio/server/iam/models.py | 24 +- modules/core/karrio/server/iam/serializers.py | 3 +- modules/core/karrio/server/iam/signals.py | 7 +- modules/core/karrio/server/iam/tests.py | 2 - modules/core/karrio/server/iam/views.py | 2 - modules/core/karrio/server/openapi.py | 3 +- .../core/karrio/server/providers/__init__.py | 2 +- modules/core/karrio/server/providers/admin.py | 40 +- .../backfill_rate_sheet_destination_flags.py | 65 + .../commands/migrate_rate_sheets.py | 82 +- .../providers/migrations/0001_initial.py | 233 +- .../migrations/0002_carrier_active.py | 7 +- .../migrations/0003_auto_20201230_0820.py | 15 +- .../migrations/0004_auto_20210212_0554.py | 291 +- .../migrations/0005_auto_20210212_0555.py | 7 +- .../migrations/0006_australiapostsettings.py | 31 +- .../migrations/0007_auto_20210213_0206.py | 11 +- .../migrations/0008_auto_20210214_0409.py | 25 +- .../migrations/0009_auto_20210308_0302.py | 9 +- .../migrations/0010_auto_20210409_0852.py | 21 +- .../migrations/0011_auto_20210409_0853.py | 11 +- .../migrations/0012_alter_carrier_options.py | 7 +- .../providers/migrations/0013_tntsettings.py | 33 +- .../migrations/0014_auto_20210612_1608.py | 47 +- .../migrations/0015_auto_20210615_1601.py | 17 +- ...0016_alter_purolatorsettings_user_token.py | 7 +- .../migrations/0017_auto_20210805_0359.py | 5 +- .../0018_alter_fedexsettings_user_key.py | 7 +- .../0019_dhlpolandsettings_servicelevel.py | 251 +- .../0020_genericsettings_labeltemplate.py | 315 +- .../migrations/0021_auto_20211231_2353.py | 29 +- .../migrations/0022_carrier_metadata.py | 7 +- .../migrations/0023_auto_20220124_1916.py | 26 +- ...ter_genericsettings_custom_carrier_name.py | 11 +- .../0025_alter_servicelevel_service_code.py | 9 +- .../migrations/0026_auto_20220208_0132.py | 9 +- .../migrations/0027_auto_20220304_1340.py | 23 +- .../migrations/0028_auto_20220323_1500.py | 23 +- .../migrations/0029_easypostsettings.py | 27 +- .../migrations/0030_amazonmwssettings.py | 31 +- .../0031_delete_amazonmwssettings.py | 1 - .../migrations/0032_alter_carrier_test.py | 9 +- .../migrations/0033_auto_20220708_1350.py | 13 +- .../0034_amazonmwssettings_dpdhlsettings.py | 65 +- .../migrations/0036_upsfreightsettings.py | 277 +- .../migrations/0037_chronopostsettings.py | 273 +- ...38_alter_genericsettings_label_template.py | 11 +- .../migrations/0039_auto_20220906_0612.py | 11 +- .../migrations/0040_dpdhlsettings_services.py | 9 +- .../migrations/0041_auto_20221105_0705.py | 1233 +- .../migrations/0042_auto_20221215_1642.py | 495 +- ...genericsettings_account_number_and_more.py | 5 +- .../0044_carrier_carrier_capabilities.py | 8 +- ...carrier_active_alter_carrier_carrier_id.py | 1 - ...ight_servicelevel_transit_days_and_more.py | 4 +- ..._system_alter_carrier_metadata_and_more.py | 20 +- .../providers/migrations/0059_ratesheet.py | 10 +- .../migrations/0063_servicelevel_metadata.py | 4 +- .../0064_alliedexpresslocalsettings.py | 1 - ...vicelevel_carrier_service_code_and_more.py | 1 - ..._fedexsettings_fedexwssettings_and_more.py | 1 - .../migrations/0067_fedexsettings.py | 1 - ...68_fedexsettings_track_api_key_and_more.py | 1 - ...canadapostsettings_contract_id_and_more.py | 1 - ..._tgesettings_alter_carrier_capabilities.py | 5 +- .../0071_alter_tgesettings_my_toll_token.py | 1 - ...ersettings_eshipperxmlsettings_and_more.py | 1 - .../0073_delete_eshipperxmlsettings.py | 1 - .../migrations/0074_eshippersettings.py | 1 - .../migrations/0075_haypostsettings.py | 1 - ...ationalsettings_account_number_and_more.py | 5 +- ...ationalsettings_uspswtsettings_and_more.py | 9 +- .../migrations/0078_auto_20240813_1552.py | 11 +- ...rier_options_alter_ratesheet_created_by.py | 1 - ...xsettings_account_country_code_and_more.py | 1 - ...iedexpresssettings_carrier_ptr_and_more.py | 1 - .../migrations/0082_add_zone_identifiers.py | 39 +- ...0083_add_optimized_rate_sheet_structure.py | 16 +- .../0084_alter_servicelevel_currency.py | 1 - ...opulate_dhl_parcel_de_oauth_credentials.py | 9 +- ...el_de_customer_number_to_billing_number.py | 9 +- .../0087_alter_carrier_capabilities.py | 5 +- .../0088_servicelevel_surcharges.py | 9 +- .../0089_servicelevel_cost_max_volume.py | 15 +- ...charges_servicelevel_zone_surcharge_ids.py | 15 +- .../0091_migrate_legacy_zones_surcharges.py | 96 +- ...okered_connection_models_update_carrier.py | 52 +- .../0093_migrate_system_carriers_data.py | 6 +- .../0094_remove_carrier_legacy_fields.py | 12 +- .../0096_ratesheet_origin_countries.py | 1 - ...7_servicelevel_volumetric_weight_fields.py | 1 - .../migrations/0098_servicelevel_features.py | 1 - .../providers/migrations/0099_cleanup.py | 9 +- ..._add_pickup_capability_to_dhl_parcel_de.py | 13 +- .../migrations/0102_add_pricing_config.py | 1 - .../0103_increase_service_field_lengths.py | 17 +- .../migrations/0106_system_rate_sheet.py | 118 +- .../0107_update_system_connection_fk.py | 20 +- .../0108_alter_secret_key_version_to_char.py | 17 + .../0109_cleanup_legacy_system_rate_sheets.py | 6 +- .../migrations/0110_merge_0108_branches.py | 10 + ..._carrier_options_frozen_connection_type.py | 24 + .../migrations/0112_service_level_tags.py | 27 + ...nection_capabilities_overrides_and_more.py | 79 + .../0114_backfill_paperless_capability.py | 88 + .../server/providers/models/__init__.py | 24 +- .../karrio/server/providers/models/carrier.py | 74 +- .../karrio/server/providers/models/service.py | 141 +- .../karrio/server/providers/models/sheet.py | 112 +- .../server/providers/models/template.py | 4 +- .../karrio/server/providers/models/utils.py | 44 +- .../server/providers/rate_sheet_datatypes.py | 348 + .../server/providers/serializers/__init__.py | 2 +- .../providers/service_level_matching.py | 478 + .../core/karrio/server/providers/signals.py | 61 +- modules/core/karrio/server/providers/tags.py | 250 + .../karrio/server/providers/tests/__init__.py | 3 + .../providers/tests/test_carrier_dynamic.py | 472 + .../providers/tests/test_connections.py | 452 +- .../tests/test_rate_sheet_datatypes.py | 139 + .../tests/test_rate_sheet_routing.py | 5 +- .../tests/test_service_level_matching.py | 507 + .../server/providers/tests/test_tags.py | 192 + .../karrio/server/providers/views/carriers.py | 133 +- .../server/providers/views/connections.py | 57 +- .../core/karrio/server/providers/webhooks.py | 159 + .../karrio/server/serializers/__init__.py | 1 + .../karrio/server/serializers/abstract.py | 137 +- .../karrio/server/serializers/json_utils.py | 152 +- modules/core/karrio/server/tracing/admin.py | 20 +- .../server/tracing/migrations/0001_initial.py | 61 +- .../migrations/0002_auto_20220710_1307.py | 19 +- .../migrations/0003_auto_20221105_0317.py | 33 +- .../0004_tracingrecord_carrier_account_idx.py | 5 +- ...06_alter_tracingrecord_options_and_more.py | 8 +- ...07_tracingrecord_tracing_created_at_idx.py | 1 - .../0008_tracingrecord_request_id_idx.py | 1 - modules/core/karrio/server/tracing/models.py | 2 +- modules/core/karrio/server/tracing/tests.py | 72 +- modules/core/karrio/server/tracing/utils.py | 42 +- modules/core/karrio/server/user/admin.py | 6 +- modules/core/karrio/server/user/forms.py | 14 +- .../server/user/migrations/0001_initial.py | 76 +- .../server/user/migrations/0002_token.py | 20 +- .../user/migrations/0003_token_test_mode.py | 11 +- .../server/user/migrations/0004_group.py | 23 +- .../user/migrations/0005_token_label.py | 4 +- .../user/migrations/0006_workspaceconfig.py | 11 +- .../user/migrations/0007_user_metadata.py | 5 +- modules/core/karrio/server/user/models.py | 69 +- .../core/karrio/server/user/serializers.py | 6 +- .../templates/two_factor/profile/profile.html | 31 + modules/core/karrio/server/user/tests.py | 2 - modules/core/karrio/server/user/urls.py | 1 + modules/core/karrio/server/user/utils.py | 15 +- modules/core/pyproject.toml | 2 +- modules/data/karrio/server/data/admin.py | 1 - modules/data/karrio/server/data/filters.py | 8 +- .../server/data/migrations/0001_initial.py | 137 +- ...r_batchoperation_resource_type_and_more.py | 48 +- ...metadata_alter_batchoperation_resources.py | 11 +- modules/data/karrio/server/data/models.py | 14 +- .../karrio/server/data/resources/__init__.py | 26 +- .../karrio/server/data/resources/orders.py | 103 +- .../karrio/server/data/resources/shipments.py | 59 +- .../karrio/server/data/resources/trackers.py | 42 +- .../server/data/serializers/__init__.py | 34 +- .../karrio/server/data/serializers/base.py | 2 +- .../karrio/server/data/serializers/batch.py | 2 +- .../server/data/serializers/batch_orders.py | 33 +- .../data/serializers/batch_shipments.py | 25 +- .../server/data/serializers/batch_trackers.py | 74 +- .../karrio/server/data/serializers/data.py | 26 +- modules/data/karrio/server/data/signals.py | 13 +- .../karrio/server/data/templates/__init__.py | 0 modules/data/karrio/server/data/tests.py | 3 - .../data/tests/test_rate_sheet_import_tags.py | 195 + modules/data/karrio/server/data/urls.py | 1 + .../data/karrio/server/data/views/batch.py | 27 +- .../karrio/server/data/views/batch_order.py | 17 +- .../server/data/views/batch_shipment.py | 17 +- .../server/data/views/batch_tracking.py | 17 +- modules/data/karrio/server/data/views/data.py | 124 +- .../events/task_definitions/data/__init__.py | 36 +- .../events/task_definitions/data/batch.py | 28 +- .../events/task_definitions/data/shipments.py | 9 +- .../karrio/server/graph/schemas/__init__.py | 2 +- .../server/graph/schemas/data/__init__.py | 27 +- .../server/graph/schemas/data/inputs.py | 23 +- .../server/graph/schemas/data/mutations.py | 28 +- .../karrio/server/graph/schemas/data/types.py | 32 +- modules/data/karrio/server/settings/data.py | 1 - modules/data/pyproject.toml | 3 + .../karrio/server/documents/admin.py | 33 +- .../karrio/server/documents/filters.py | 6 +- .../karrio/server/documents/generator.py | 147 +- .../documents/migrations/0001_initial.py | 18 +- ..._alter_documenttemplate_related_objects.py | 7 +- ...objects_documenttemplate_related_object.py | 9 +- .../0004_documenttemplate_active.py | 11 +- ...r_documenttemplate_description_and_more.py | 1 - .../0006_documenttemplate_metadata.py | 7 +- ...7_alter_documenttemplate_related_object.py | 1 - .../0008_documenttemplate_options.py | 8 +- .../karrio/server/documents/models.py | 17 +- .../server/documents/serializers/__init__.py | 2 +- .../server/documents/serializers/base.py | 29 +- .../server/documents/serializers/documents.py | 2 +- .../karrio/server/documents/signals.py | 12 +- .../karrio/server/documents/tests.py | 182 - .../karrio/server/documents/tests/__init__.py | 3 +- .../server/documents/tests/test_generation.py | 223 + .../server/documents/tests/test_generator.py | 25 +- .../server/documents/tests/test_templates.py | 5 +- .../karrio/server/documents/utils.py | 15 +- .../karrio/server/documents/views/printers.py | 44 +- .../server/documents/views/templates.py | 35 +- .../karrio/server/graph/schemas/__init__.py | 2 +- .../graph/schemas/documents/__init__.py | 21 +- .../server/graph/schemas/documents/inputs.py | 40 +- .../graph/schemas/documents/mutations.py | 28 +- .../server/graph/schemas/documents/types.py | 31 +- modules/events/karrio/server/events/admin.py | 2 - modules/events/karrio/server/events/apps.py | 5 +- .../events/karrio/server/events/filters.py | 20 +- .../server/events/management/__init__.py | 0 .../events/management/commands/__init__.py | 0 .../management/commands/archive_data.py | 43 + .../management/commands/close_pickups.py | 43 + .../commands/dispatch_scheduled_workflows.py | 60 + .../management/commands/update_trackers.py | 58 + .../server/events/migrations/0001_initial.py | 12 +- .../server/events/migrations/0002_event.py | 53 +- .../migrations/0003_auto_20220303_1210.py | 27 +- .../migrations/0005_event_event_object_idx.py | 13 +- .../0006_webhook_events_alter_event_data.py | 17 +- .../migrations/0007_auto_20221130_0255.py | 2 +- .../migrations/0008_alter_event_type.py | 1 - .../0009_alter_webhook_enabled_events.py | 7 +- .../0010_event_event_created_at_idx.py | 1 - .../0011_alter_webhook_enabled_events.py | 36 +- modules/events/karrio/server/events/models.py | 12 +- .../karrio/server/events/serializers/base.py | 8 +- .../karrio/server/events/serializers/event.py | 2 +- .../server/events/serializers/webhook.py | 14 +- .../events/karrio/server/events/signals.py | 122 +- .../events/task_definitions/base/__init__.py | 44 +- .../events/task_definitions/base/archiving.py | 17 +- .../events/task_definitions/base/pickup.py | 4 +- .../events/task_definitions/base/tracking.py | 140 +- .../events/task_definitions/base/webhook.py | 29 +- modules/events/karrio/server/events/tasks.py | 12 +- modules/events/karrio/server/events/tests.py | 2 - .../karrio/server/events/tests/__init__.py | 4 +- .../events/tests/test_batch_webhooks.py | 9 +- .../karrio/server/events/tests/test_events.py | 2 +- .../events/tests/test_tracking_tasks.py | 185 +- .../server/events/tests/test_webhooks.py | 66 +- modules/events/karrio/server/events/urls.py | 5 +- .../karrio/server/events/views/__init__.py | 2 +- .../server/events/views/batch_webhook.py | 53 +- .../karrio/server/events/views/webhooks.py | 52 +- .../karrio/server/graph/schemas/__init__.py | 2 +- .../server/graph/schemas/events/__init__.py | 35 +- .../server/graph/schemas/events/inputs.py | 40 +- .../server/graph/schemas/events/mutations.py | 24 +- .../server/graph/schemas/events/types.py | 62 +- modules/events/pyproject.toml | 1 - modules/graph/karrio/server/graph/__init__.py | 2 +- modules/graph/karrio/server/graph/admin.py | 2 - modules/graph/karrio/server/graph/apps.py | 2 +- modules/graph/karrio/server/graph/forms.py | 9 +- .../management/commands/export_schema.py | 7 +- .../server/graph/migrations/0001_initial.py | 58 +- .../migrations/0002_auto_20210512_1353.py | 11 +- .../0003_remove_template_customs.py | 1 - modules/graph/karrio/server/graph/models.py | 19 +- modules/graph/karrio/server/graph/schema.py | 14 +- .../server/graph/schemas/base/__init__.py | 279 +- .../server/graph/schemas/base/inputs.py | 881 +- .../server/graph/schemas/base/mutations.py | 518 +- .../karrio/server/graph/schemas/base/types.py | 1487 +- .../karrio/server/graph/tests/__init__.py | 7 +- .../graph/karrio/server/graph/tests/base.py | 24 +- .../graph/tests/test_carrier_connections.py | 267 +- .../tests/test_connection_message_hooks.py | 110 + .../server/graph/tests/test_metafield.py | 149 +- .../graph/tests/test_partial_shipments.py | 10 +- .../karrio/server/graph/tests/test_pickups.py | 4 +- .../server/graph/tests/test_rate_sheets.py | 282 +- .../graph/tests/test_rate_type_parse.py | 48 + .../server/graph/tests/test_registration.py | 12 +- .../server/graph/tests/test_templates.py | 3 +- .../graph/tests/test_tracing_redaction.py | 170 + .../server/graph/tests/test_user_info.py | 9 +- modules/graph/karrio/server/graph/urls.py | 2 +- modules/graph/karrio/server/graph/utils.py | 201 +- modules/graph/karrio/server/graph/views.py | 25 +- modules/graph/karrio/server/settings/graph.py | 1 + .../manager/karrio/server/manager/__init__.py | 2 +- modules/manager/karrio/server/manager/apps.py | 3 + .../server/manager/migrations/0001_initial.py | 52 +- .../migrations/0002_auto_20201127_0721.py | 67 +- .../migrations/0003_auto_20201230_0820.py | 21 +- .../migrations/0004_auto_20210125_2125.py | 14 +- .../migrations/0005_auto_20210216_0758.py | 31 +- .../migrations/0006_auto_20210307_0438.py | 13 +- .../migrations/0006_auto_20210308_0302.py | 51 +- .../migrations/0007_merge_20210311_1428.py | 8 +- .../0008_remove_shipment_doc_images.py | 7 +- .../migrations/0009_auto_20210326_1425.py | 427 +- .../migrations/0010_auto_20210403_1404.py | 21 +- .../migrations/0011_auto_20210426_1924.py | 2 +- .../migrations/0012_auto_20210427_1319.py | 13 +- .../migrations/0013_customs_invoice_date.py | 7 +- .../migrations/0014_auto_20210515_0928.py | 17 +- .../migrations/0015_auto_20210601_0340.py | 45 +- .../migrations/0016_shipment_archived.py | 7 +- .../migrations/0017_auto_20210629_1650.py | 24 +- .../migrations/0018_auto_20210705_1049.py | 11 +- .../migrations/0019_auto_20210722_1131.py | 44 +- .../migrations/0020_tracking_messages.py | 16 +- .../0021_tracking_estimated_delivery.py | 7 +- .../migrations/0022_auto_20211122_2100.py | 5 +- .../migrations/0023_auto_20211227_2141.py | 22 +- .../migrations/0024_alter_parcel_items.py | 9 +- .../migrations/0025_auto_20220113_1158.py | 26 +- .../0026_parcel_reference_number.py | 7 +- .../0027_custom_migration_2021_1.py | 7 +- .../migrations/0028_auto_20220303_1153.py | 65 +- .../migrations/0029_auto_20220303_1249.py | 78 +- .../migrations/0030_alter_shipment_status.py | 7 +- .../migrations/0031_shipment_invoice.py | 4 +- .../0032_custom_migration_2022_3.py | 7 +- .../migrations/0033_auto_20220504_1335.py | 20 +- .../migrations/0034_commodity_hs_code.py | 7 +- .../manager/migrations/0035_parcel_options.py | 8 +- .../0036_alter_tracking_shipment.py | 3 +- .../migrations/0037_auto_20220710_1350.py | 6 +- .../migrations/0038_alter_tracking_status.py | 19 +- .../migrations/0039_documentuploadrecord.py | 94 +- .../migrations/0040_parcel_freight_class.py | 7 +- ..._commodity_options_alter_parcel_options.py | 1 - ...t_shipment_tracking_number_idx_and_more.py | 30 +- ...3_customs_duty_billing_address_and_more.py | 14 +- ...044_address_address_line1_temp_and_more.py | 24 +- ...r_customs_duty_billing_address_and_more.py | 3 +- .../migrations/0046_auto_20230114_0930.py | 21 +- ...t_shipment_tracking_number_idx_and_more.py | 4 +- .../migrations/0049_auto_20230318_0708.py | 12 +- ...number_tracking_account_number_and_more.py | 7 +- .../migrations/0051_auto_20230330_0556.py | 6 +- .../migrations/0052_auto_20230520_0811.py | 12 +- .../0057_alter_customs_invoice_date.py | 1 - .../0058_manifest_shipment_manifest.py | 30 +- .../0059_shipment_return_address.py | 3 +- ...eta_alter_address_country_code_and_more.py | 8 +- .../migrations/0061_alter_customs_incoterm.py | 1 - .../migrations/0062_alter_tracking_status.py | 1 - .../0063_alter_commodity_value_currency.py | 1 - ...ipment_shipment_created_at_idx_and_more.py | 1 - ...ity_alter_address_company_name_and_more.py | 152 +- ...image_url_commodity_product_id_and_more.py | 1 - .../0067_rename_customs_commodities_table.py | 14 +- .../0068_shipment_extra_documents.py | 12 +- .../migrations/0069_alter_tracking_status.py | 1 - .../0070_add_meta_and_product_fields.py | 38 +- .../manager/migrations/0071_product_proxy.py | 1 - .../migrations/0072_populate_json_fields.py | 23 +- .../0073_make_shipment_fk_nullable.py | 1 - .../0074_clean_model_refactoring.py | 9 - .../migrations/0075_populate_template_meta.py | 1 - .../migrations/0076_remove_customs_model.py | 1 - .../0077_add_carrier_snapshot_fields.py | 1 + .../0080_add_carrier_json_indexes.py | 4 +- .../server/manager/migrations/0081_cleanup.py | 26 +- .../manager/migrations/0082_shipment_fees.py | 4 +- .../manager/migrations/0083_pickup_status.py | 1 - .../0084_shipment_return_shipment.py | 1 - ...085_fix_stale_tracker_carrier_snapshots.py | 6 +- .../migrations/0086_shipment_is_return.py | 1 - .../0087_shipment_is_return_nullable.py | 1 - .../migrations/0088_shipment_order_id.py | 1 - ...purchased_to_created_and_remove_choices.py | 1 - .../0090_redact_persisted_rate_meta.py | 135 + .../0091_backfill_purchase_snapshot.py | 283 + .../manager/karrio/server/manager/models.py | 263 +- .../server/manager/serializers/__init__.py | 111 +- .../server/manager/serializers/address.py | 23 +- .../server/manager/serializers/commodity.py | 19 +- .../server/manager/serializers/document.py | 25 +- .../server/manager/serializers/manifest.py | 15 +- .../server/manager/serializers/parcel.py | 20 +- .../server/manager/serializers/pickup.py | 200 +- .../karrio/server/manager/serializers/rate.py | 7 +- .../server/manager/serializers/shipment.py | 391 +- .../server/manager/serializers/tracking.py | 172 +- .../manager/karrio/server/manager/signals.py | 27 +- .../manager/karrio/server/manager/tasks.py | 161 + .../karrio/server/manager/tests/__init__.py | 5 +- .../server/manager/tests/test_addresses.py | 15 +- .../server/manager/tests/test_errors.py | 3 +- .../server/manager/tests/test_manifests.py | 9 +- .../server/manager/tests/test_paperless.py | 595 + .../server/manager/tests/test_parcels.py | 17 +- .../server/manager/tests/test_pickups.py | 225 +- .../server/manager/tests/test_products.py | 62 +- modules/manager/karrio/server/manager/urls.py | 1 + .../karrio/server/manager/views/__init__.py | 10 +- .../karrio/server/manager/views/addresses.py | 44 +- .../karrio/server/manager/views/documents.py | 34 +- .../karrio/server/manager/views/manifests.py | 48 +- .../karrio/server/manager/views/parcels.py | 38 +- .../karrio/server/manager/views/pickups.py | 57 +- .../karrio/server/manager/views/products.py | 45 +- .../karrio/server/manager/views/shipments.py | 112 +- .../karrio/server/manager/views/trackers.py | 93 +- .../karrio/server/graph/schemas/__init__.py | 2 +- .../server/graph/schemas/orders/__init__.py | 32 +- .../server/graph/schemas/orders/inputs.py | 58 +- .../server/graph/schemas/orders/mutations.py | 35 +- .../server/graph/schemas/orders/types.py | 56 +- modules/orders/karrio/server/orders/admin.py | 4 +- .../orders/karrio/server/orders/filters.py | 21 +- .../server/orders/migrations/0001_initial.py | 21 +- .../migrations/0002_auto_20211231_2353.py | 6 +- .../0003_alter_order_shipping_address.py | 15 +- .../migrations/0004_alter_order_status.py | 19 +- .../migrations/0005_auto_20220303_1153.py | 24 +- .../0006_alter_order_shipping_to.py | 15 +- .../migrations/0007_alter_order_line_items.py | 13 +- .../migrations/0008_alter_order_status.py | 1 - .../migrations/0009_auto_20220321_1535.py | 36 +- .../migrations/0010_auto_20220324_2031.py | 20 +- .../migrations/0011_order_billing_address.py | 18 +- .../migrations/0012_order_order_id_idx.py | 7 +- .../orders/migrations/0014_order_meta.py | 7 +- ...er_id_idx_alter_order_order_id_and_more.py | 1 - .../orders/migrations/0016_order_shipments.py | 4 +- .../0017_order_order_created_at_idx.py | 1 - .../orders/migrations/0018_ordercounter.py | 37 +- .../server/orders/migrations/0019_orderkey.py | 3 +- .../migrations/0020_alter_orderkey_scope.py | 5 +- .../orders/migrations/0021_add_json_fields.py | 18 +- .../migrations/0022_make_order_fk_nullable.py | 1 - .../0023_clean_model_refactoring.py | 6 +- .../migrations/0024_add_json_indexes.py | 2 +- .../server/orders/migrations/0025_cleanup.py | 10 +- modules/orders/karrio/server/orders/models.py | 46 +- .../server/orders/serializers/__init__.py | 24 +- .../karrio/server/orders/serializers/base.py | 38 +- .../karrio/server/orders/serializers/order.py | 99 +- .../orders/karrio/server/orders/signals.py | 45 +- .../karrio/server/orders/tests/__init__.py | 1 + .../karrio/server/orders/tests/test_orders.py | 16 +- modules/orders/karrio/server/orders/urls.py | 1 + modules/orders/karrio/server/orders/views.py | 66 +- .../orders/karrio/server/settings/orders.py | 1 + .../pricing/karrio/server/pricing/__init__.py | 2 +- .../pricing/karrio/server/pricing/admin.py | 131 +- modules/pricing/karrio/server/pricing/apps.py | 3 +- .../karrio/server/pricing/charge_breakdown.py | 371 + .../server/pricing/migrations/0001_initial.py | 5 +- .../migrations/0002_auto_20201127_0721.py | 3 +- .../migrations/0003_auto_20201230_0820.py | 3 +- .../migrations/0004_auto_20201231_1402.py | 3 +- .../migrations/0005_auto_20210204_1725.py | 3 +- .../migrations/0006_auto_20210217_1109.py | 9 +- .../migrations/0007_auto_20210218_1202.py | 3 +- .../migrations/0008_auto_20210418_0504.py | 3 +- .../migrations/0009_auto_20210603_2149.py | 1 - .../migrations/0010_auto_20210612_1608.py | 3 +- .../migrations/0011_auto_20210615_1601.py | 3 +- .../0012_surcharge_carrier_accounts.py | 1 - .../0013_alter_surcharge_services.py | 3 +- .../migrations/0014_auto_20211013_1520.py | 1 - .../migrations/0015_auto_20211204_1350.py | 3 +- .../migrations/0016_auto_20211220_1500.py | 3 +- .../0017_alter_surcharge_services.py | 3 +- .../0018_alter_surcharge_services.py | 3 +- .../0019_alter_surcharge_services.py | 3 +- .../migrations/0020_auto_20220412_1215.py | 3 +- .../migrations/0021_auto_20220413_0959.py | 3 +- .../0022_alter_surcharge_services.py | 3 +- .../migrations/0023_auto_20220504_1335.py | 3 +- .../migrations/0024_auto_20220808_0803.py | 3 +- .../0025_alter_surcharge_carriers.py | 3 +- .../migrations/0026_auto_20220828_0158.py | 3 +- .../0027_alter_surcharge_services.py | 3 +- ...rkup_carriers_surcharge_markup_services.py | 12 +- ...harge_carriers_alter_surcharge_services.py | 1 - .../0043_alter_surcharge_services.py | 1 - .../0044_alter_surcharge_carriers.py | 1 - .../0045_alter_surcharge_carriers.py | 1 - .../0046_alter_surcharge_services.py | 1 - .../0047_alter_surcharge_services.py | 1 - .../0048_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 1 - .../0050_alter_surcharge_carriers.py | 1 - ...harge_carriers_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 1 - .../0054_alter_surcharge_services.py | 1 - .../0055_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 1 - .../0058_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 1 - .../0060_alter_surcharge_services.py | 1 - .../0061_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 1 - .../0065_fix_surcharge_type_enum.py | 15 +- ...riers_alter_surcharge_services_and_more.py | 1 - ...harge_carriers_alter_surcharge_services.py | 2578 +- ...harge_carriers_alter_surcharge_services.py | 2560 +- ...harge_carriers_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 341 +- ...harge_carriers_alter_surcharge_services.py | 1 - ...harge_carriers_alter_surcharge_services.py | 227 +- ...harge_carriers_alter_surcharge_services.py | 1 - .../0076_create_markup_and_fee_models.py | 32 +- .../0077_migrate_surcharge_to_markup_data.py | 4 +- .../server/pricing/migrations/0078_cleanup.py | 1 - .../migrations/0079_fee_snapshot_model.py | 57 +- .../migrations/0080_markup_meta_field.py | 3 +- .../migrations/0081_alter_markup_meta.py | 13 +- ...l_fee_account_and_capture_platform_fees.py | 194 + ...3_normalize_selected_rate_extra_charges.py | 409 + ...ranslate_markup_brokered_connection_ids.py | 77 + .../pricing/karrio/server/pricing/models.py | 120 +- .../karrio/server/pricing/rate_enrichment.py | 295 + .../karrio/server/pricing/serializers.py | 1 + .../pricing/karrio/server/pricing/signals.py | 183 +- .../pricing/karrio/server/pricing/tests.py | 1163 +- .../pricing/karrio/server/pricing/views.py | 2 - modules/proxy/karrio/server/proxy/admin.py | 2 - modules/proxy/karrio/server/proxy/apps.py | 2 +- modules/proxy/karrio/server/proxy/models.py | 2 - .../karrio/server/proxy/tests/__init__.py | 2 +- .../karrio/server/proxy/tests/test_pickup.py | 7 +- .../karrio/server/proxy/tests/test_rating.py | 8 +- .../server/proxy/tests/test_shipping.py | 9 +- .../server/proxy/tests/test_tracking.py | 5 +- modules/proxy/karrio/server/proxy/urls.py | 5 +- .../karrio/server/proxy/views/__init__.py | 15 +- .../karrio/server/proxy/views/manifest.py | 17 +- .../proxy/karrio/server/proxy/views/pickup.py | 35 +- .../proxy/karrio/server/proxy/views/rating.py | 32 +- .../karrio/server/proxy/views/shipping.py | 61 +- .../karrio/server/proxy/views/tracking.py | 47 +- modules/sdk/karrio/addons/label.py | 7 +- modules/sdk/karrio/addons/renderer.py | 48 +- modules/sdk/karrio/api/__init__.py | 1 + modules/sdk/karrio/api/gateway.py | 51 +- modules/sdk/karrio/api/hooks.py | 13 +- modules/sdk/karrio/api/interface.py | 225 +- modules/sdk/karrio/api/mapper.py | 144 +- modules/sdk/karrio/api/proxy.py | 78 +- modules/sdk/karrio/core/__init__.py | 2 + modules/sdk/karrio/core/dynamic.py | 242 + modules/sdk/karrio/core/errors.py | 14 +- modules/sdk/karrio/core/metadata.py | 30 +- modules/sdk/karrio/core/models.py | 190 +- modules/sdk/karrio/core/plugins.py | 65 +- modules/sdk/karrio/core/settings.py | 30 +- .../sdk/karrio/core/tests/test_redaction.py | 33 +- modules/sdk/karrio/core/units.py | 421 +- modules/sdk/karrio/core/utils/__init__.py | 81 +- modules/sdk/karrio/core/utils/caching.py | 48 +- modules/sdk/karrio/core/utils/config.py | 4 +- modules/sdk/karrio/core/utils/datetime.py | 53 +- modules/sdk/karrio/core/utils/dict.py | 26 +- modules/sdk/karrio/core/utils/enum.py | 53 +- modules/sdk/karrio/core/utils/functional.py | 14 +- modules/sdk/karrio/core/utils/helpers.py | 262 +- modules/sdk/karrio/core/utils/log.py | 2 + modules/sdk/karrio/core/utils/logger.py | 17 +- modules/sdk/karrio/core/utils/number.py | 23 +- modules/sdk/karrio/core/utils/pipeline.py | 13 +- modules/sdk/karrio/core/utils/redaction.py | 23 +- modules/sdk/karrio/core/utils/serializable.py | 3 +- modules/sdk/karrio/core/utils/soap.py | 34 +- modules/sdk/karrio/core/utils/string.py | 17 +- modules/sdk/karrio/core/utils/tracing.py | 99 +- modules/sdk/karrio/core/utils/transformer.py | 106 +- modules/sdk/karrio/core/utils/xml.py | 51 +- modules/sdk/karrio/lib.py | 349 +- modules/sdk/karrio/mappers/__init__.py | 1 + modules/sdk/karrio/plugins/__init__.py | 1 + modules/sdk/karrio/providers/__init__.py | 1 + modules/sdk/karrio/references.py | 519 +- modules/sdk/karrio/schemas/__init__.py | 1 + modules/sdk/karrio/sdk.py | 4 +- .../karrio/universal/mappers/rating_proxy.py | 244 +- .../universal/mappers/shipping_proxy.py | 53 +- .../universal/providers/rating/__init__.py | 6 +- .../karrio/universal/providers/rating/rate.py | 11 +- .../universal/providers/rating/utils.py | 10 +- .../universal/providers/shipping/__init__.py | 9 +- .../universal/providers/shipping/shipment.py | 26 +- .../universal/providers/shipping/utils.py | 8 +- modules/sdk/tests/__init__.py | 11 +- modules/sdk/tests/core/__init__.py | 2 + .../tests/core/test_authenticated_request.py | 254 + modules/sdk/tests/core/test_caching.py | 99 +- modules/sdk/tests/core/test_dynamic.py | 163 + modules/sdk/tests/core/test_errors.py | 51 +- .../sdk/tests/core/test_multi_piece_rates.py | 142 + .../sdk/tests/core/test_return_shipment.py | 28 +- .../core/test_suppress_recipient_contact.py | 203 + modules/sdk/tests/core/test_universal_rate.py | 239 +- .../sdk/tests/core/test_universal_shipment.py | 8 +- modules/sdk/tests/test_helpers_span.py | 161 + modules/soap/pysoap/__init__.py | 4 +- packages/core/modules/Connections/index.tsx | 45 +- .../components/views/system-health-view.tsx | 75 +- packages/types/graphql/queries.ts | 8 + packages/ui/components/rate-sheet-editor.tsx | 29 +- .../ui/components/schedule-pickup-dialog.tsx | 10 +- schemas/openapi.yml | 4 +- 1613 files changed, 179862 insertions(+), 28332 deletions(-) delete mode 100644 apps/api/karrio/server/lib/otel_huey.py create mode 100644 apps/api/karrio/server/urls/mfa.py create mode 100755 bin/regenerate-rate-sheets create mode 100644 modules/admin/karrio/server/admin/tests/test_admin_mfa.py create mode 100644 modules/admin/karrio/server/admin/tests/test_admin_route_gate.py create mode 100644 modules/admin/karrio/server/admin/tests/test_create_user.py create mode 100644 modules/cli/karrio_cli/templates/location.py create mode 100644 modules/connectors/amazon_shipping/README.md create mode 100644 modules/connectors/amazon_shipping/SPECS.md create mode 100755 modules/connectors/amazon_shipping/generate create mode 100644 modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/__init__.py create mode 100644 modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/mapper.py create mode 100644 modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/proxy.py create mode 100644 modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/settings.py create mode 100644 modules/connectors/amazon_shipping/karrio/plugins/amazon_shipping/__init__.py create mode 100644 modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/__init__.py create mode 100644 modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/error.py create mode 100644 modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/rate.py create mode 100644 modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/shipment/__init__.py create mode 100644 modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/shipment/cancel.py create mode 100644 modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/shipment/create.py create mode 100644 modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/tracking.py create mode 100644 modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/units.py create mode 100644 modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/utils.py create mode 100644 modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/__init__.py create mode 100644 modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/cancel_shipment_response.py create mode 100644 modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/error_response.py create mode 100644 modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/one_click_shipment_request.py create mode 100644 modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/one_click_shipment_response.py create mode 100644 modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/purchase_shipment_request.py create mode 100644 modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/purchase_shipment_response.py create mode 100644 modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/rate_request.py create mode 100644 modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/rate_response.py create mode 100644 modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/tracking_response.py create mode 100644 modules/connectors/amazon_shipping/pyproject.toml create mode 100644 modules/connectors/amazon_shipping/schemas/cancel_shipment_response.json create mode 100644 modules/connectors/amazon_shipping/schemas/create_shipment_request.json create mode 100644 modules/connectors/amazon_shipping/schemas/create_shipment_response.json create mode 100644 modules/connectors/amazon_shipping/schemas/error_response.json create mode 100644 modules/connectors/amazon_shipping/schemas/one_click_shipment_request.json create mode 100644 modules/connectors/amazon_shipping/schemas/one_click_shipment_response.json create mode 100644 modules/connectors/amazon_shipping/schemas/purchase_label_request.json create mode 100644 modules/connectors/amazon_shipping/schemas/purchase_label_response.json create mode 100644 modules/connectors/amazon_shipping/schemas/purchase_shipment_request.json create mode 100644 modules/connectors/amazon_shipping/schemas/purchase_shipment_response.json create mode 100644 modules/connectors/amazon_shipping/schemas/rate_request.json create mode 100644 modules/connectors/amazon_shipping/schemas/rate_response.json create mode 100644 modules/connectors/amazon_shipping/schemas/shipping_label.json create mode 100644 modules/connectors/amazon_shipping/schemas/tracking_response.json create mode 100644 modules/connectors/amazon_shipping/tests/__init__.py create mode 100644 modules/connectors/amazon_shipping/tests/amazon_shipping/__init__.py create mode 100644 modules/connectors/amazon_shipping/tests/amazon_shipping/fixture.py create mode 100644 modules/connectors/amazon_shipping/tests/amazon_shipping/test_authentication.py create mode 100644 modules/connectors/amazon_shipping/tests/amazon_shipping/test_rate.py create mode 100644 modules/connectors/amazon_shipping/tests/amazon_shipping/test_shipment.py create mode 100644 modules/connectors/amazon_shipping/tests/amazon_shipping/test_tracking.py create mode 100644 modules/connectors/amazon_shipping/vendor/openapi.yaml create mode 100644 modules/connectors/amazon_shipping/vendor/postman.json create mode 100644 modules/connectors/amazon_shipping/vendor/shippingV2.json create mode 100644 modules/connectors/asendia/SPECS.md create mode 100644 modules/connectors/asendia/tests/asendia/test_rate.py create mode 100644 modules/connectors/asendia/vendor/README.md create mode 100644 modules/connectors/asendia/vendor/openapi.json create mode 100644 modules/connectors/asendia/vendor/openapi.yaml create mode 100644 modules/connectors/australiapost/SPECS.md create mode 100644 modules/connectors/bpost/SPECS.md create mode 100644 modules/connectors/canadapost/SPECS.md create mode 100644 modules/connectors/chronopost/SPECS.md create mode 100644 modules/connectors/dhl_express/SPECS.md create mode 100644 modules/connectors/dhl_freight/MANIFEST.in create mode 100644 modules/connectors/dhl_freight/PRD.md create mode 100644 modules/connectors/dhl_freight/README.md create mode 100644 modules/connectors/dhl_freight/SPECS.md create mode 100755 modules/connectors/dhl_freight/generate create mode 100644 modules/connectors/dhl_freight/karrio/mappers/dhl_freight/__init__.py create mode 100644 modules/connectors/dhl_freight/karrio/mappers/dhl_freight/mapper.py create mode 100644 modules/connectors/dhl_freight/karrio/mappers/dhl_freight/proxy.py create mode 100644 modules/connectors/dhl_freight/karrio/mappers/dhl_freight/settings.py create mode 100644 modules/connectors/dhl_freight/karrio/plugins/dhl_freight/__init__.py create mode 100644 modules/connectors/dhl_freight/karrio/providers/dhl_freight/__init__.py create mode 100644 modules/connectors/dhl_freight/karrio/providers/dhl_freight/error.py create mode 100644 modules/connectors/dhl_freight/karrio/providers/dhl_freight/i18n.py create mode 100644 modules/connectors/dhl_freight/karrio/providers/dhl_freight/services.csv create mode 100644 modules/connectors/dhl_freight/karrio/providers/dhl_freight/shipment/__init__.py create mode 100644 modules/connectors/dhl_freight/karrio/providers/dhl_freight/shipment/create.py create mode 100644 modules/connectors/dhl_freight/karrio/providers/dhl_freight/tracking.py create mode 100644 modules/connectors/dhl_freight/karrio/providers/dhl_freight/units.py create mode 100644 modules/connectors/dhl_freight/karrio/providers/dhl_freight/utils.py create mode 100644 modules/connectors/dhl_freight/karrio/schemas/dhl_freight/__init__.py create mode 100644 modules/connectors/dhl_freight/karrio/schemas/dhl_freight/error_response.py create mode 100644 modules/connectors/dhl_freight/karrio/schemas/dhl_freight/shipping_request.py create mode 100644 modules/connectors/dhl_freight/karrio/schemas/dhl_freight/shipping_response.py create mode 100644 modules/connectors/dhl_freight/karrio/schemas/dhl_freight/tracking_response.py create mode 100644 modules/connectors/dhl_freight/pyproject.toml create mode 100644 modules/connectors/dhl_freight/schemas/error_response.json create mode 100644 modules/connectors/dhl_freight/schemas/shipping_request.json create mode 100644 modules/connectors/dhl_freight/schemas/shipping_response.json create mode 100644 modules/connectors/dhl_freight/schemas/tracking_response.json create mode 100644 modules/connectors/dhl_freight/tests/__init__.py create mode 100644 modules/connectors/dhl_freight/tests/dhl_freight/__init__.py create mode 100644 modules/connectors/dhl_freight/tests/dhl_freight/fixture.py create mode 100644 modules/connectors/dhl_freight/tests/dhl_freight/test_authentication.py create mode 100644 modules/connectors/dhl_freight/tests/dhl_freight/test_error.py create mode 100644 modules/connectors/dhl_freight/tests/dhl_freight/test_i18n.py create mode 100644 modules/connectors/dhl_freight/tests/dhl_freight/test_rate.py create mode 100644 modules/connectors/dhl_freight/tests/dhl_freight/test_shipment.py create mode 100644 modules/connectors/dhl_freight/tests/dhl_freight/test_tracking.py create mode 100644 modules/connectors/dhl_freight/tests/dhl_freight/test_units.py create mode 100644 modules/connectors/dhl_freight/vendors/DHL_Freight_Shipment_Booking_SANDBOX_2026_R03.postman_collection.json create mode 100644 modules/connectors/dhl_parcel_de/SPECS.md create mode 100644 modules/connectors/dhl_parcel_de/tests/dhl_parcel_de/test_billing_resolution.py create mode 100644 modules/connectors/dhl_parcel_de/tests/dhl_parcel_de/test_delivery_tier.py create mode 100644 modules/connectors/dhl_poland/SPECS.md create mode 100644 modules/connectors/dhl_universal/SPECS.md create mode 100644 modules/connectors/dpd/SPECS.md create mode 100644 modules/connectors/dpd_france/MANIFEST.in create mode 100644 modules/connectors/dpd_france/README.md create mode 100644 modules/connectors/dpd_france/SPECS.md create mode 100755 modules/connectors/dpd_france/generate create mode 100644 modules/connectors/dpd_france/karrio/mappers/dpd_france/__init__.py create mode 100644 modules/connectors/dpd_france/karrio/mappers/dpd_france/mapper.py create mode 100644 modules/connectors/dpd_france/karrio/mappers/dpd_france/proxy.py create mode 100644 modules/connectors/dpd_france/karrio/mappers/dpd_france/settings.py create mode 100644 modules/connectors/dpd_france/karrio/plugins/dpd_france/__init__.py create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/__init__.py create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/error.py create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/pickup/__init__.py create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/pickup/cancel.py create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/pickup/create.py create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/return_shipment.py create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/services.csv create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/shipment/__init__.py create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/shipment/cancel.py create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/shipment/create.py create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/tracking.py create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/units.py create mode 100644 modules/connectors/dpd_france/karrio/providers/dpd_france/utils.py create mode 100644 modules/connectors/dpd_france/karrio/schemas/dpd_france/__init__.py create mode 100644 modules/connectors/dpd_france/karrio/schemas/dpd_france/eprintwebservice.py create mode 100644 modules/connectors/dpd_france/karrio/schemas/dpd_france/error.py create mode 100644 modules/connectors/dpd_france/karrio/schemas/dpd_france/webtraceservice.py create mode 100644 modules/connectors/dpd_france/pyproject.toml create mode 100644 modules/connectors/dpd_france/schemas/EPrintWebservice.xsd create mode 100644 modules/connectors/dpd_france/schemas/Webtrace_Service.xsd create mode 100644 modules/connectors/dpd_france/schemas/error_response.xsd create mode 100644 modules/connectors/dpd_france/tests/__init__.py create mode 100644 modules/connectors/dpd_france/tests/dpd_france/__init__.py create mode 100644 modules/connectors/dpd_france/tests/dpd_france/fixture.py create mode 100644 modules/connectors/dpd_france/tests/dpd_france/test_pickup.py create mode 100644 modules/connectors/dpd_france/tests/dpd_france/test_rate.py create mode 100644 modules/connectors/dpd_france/tests/dpd_france/test_return_shipment.py create mode 100644 modules/connectors/dpd_france/tests/dpd_france/test_shipment.py create mode 100644 modules/connectors/dpd_france/tests/dpd_france/test_tracking.py create mode 100644 modules/connectors/dpd_france/vendor/README.md create mode 100644 modules/connectors/dpd_meta/SPECS.md create mode 100644 modules/connectors/dpd_meta/karrio/mappers/dpd_meta/hooks.py create mode 100644 modules/connectors/dpd_meta/karrio/providers/dpd_meta/hooks/__init__.py create mode 100644 modules/connectors/dpd_meta/karrio/providers/dpd_meta/hooks/event.py create mode 100644 modules/connectors/dpd_meta/karrio/providers/dpd_meta/location.py create mode 100644 modules/connectors/dpd_meta/karrio/schemas/dpd_meta/webapi.py create mode 100644 modules/connectors/dpd_meta/schemas/webapi.xsd create mode 100644 modules/connectors/dpd_meta/tests/dpd_meta/test_authentication.py create mode 100644 modules/connectors/dpd_meta/tests/dpd_meta/test_location.py create mode 100644 modules/connectors/dpd_meta/tests/dpd_meta/test_units.py create mode 100644 modules/connectors/dpd_meta/tests/dpd_meta/test_webhook.py create mode 100644 modules/connectors/fedex/SPECS.md create mode 100644 modules/connectors/generic/SPECS.md create mode 100644 modules/connectors/gls/SPECS.md create mode 100644 modules/connectors/gls/karrio/providers/gls/document.py create mode 100644 modules/connectors/gls/karrio/providers/gls/location.py create mode 100644 modules/connectors/gls/karrio/providers/gls/pickup/__init__.py create mode 100644 modules/connectors/gls/karrio/providers/gls/pickup/create.py create mode 100644 modules/connectors/gls/karrio/providers/gls/shipment/cancel.py create mode 100644 modules/connectors/gls/karrio/schemas/gls/customs_consignment_request.py create mode 100644 modules/connectors/gls/karrio/schemas/gls/pickup_request.py create mode 100644 modules/connectors/gls/karrio/schemas/gls/pickup_response.py create mode 100644 modules/connectors/gls/schemas/customs_consignment_request.json create mode 100644 modules/connectors/gls/schemas/pickup_request.json create mode 100644 modules/connectors/gls/schemas/pickup_response.json create mode 100644 modules/connectors/gls/tests/gls/test_document.py create mode 100644 modules/connectors/gls/tests/gls/test_pickup.py create mode 100644 modules/connectors/gls/vendor/STALE.md create mode 100644 modules/connectors/gls/vendor/customs-consignments-v3.yaml create mode 100644 modules/connectors/gls/vendor/doxygen/doxygen.css create mode 100644 modules/connectors/gls/vendor/doxygen/index.html create mode 100644 modules/connectors/gls/vendor/doxygen/rest_parcel_shop.html create mode 100644 modules/connectors/gls/vendor/doxygen/rest_shipment_processing.html create mode 100644 modules/connectors/gls/vendor/doxygen/rest_sporadic_collection.html create mode 100644 modules/connectors/gls/vendor/doxygen/rest_tracking.html create mode 100644 modules/connectors/gls/vendor/doxygen/rest_web_service_requests_samples.html create mode 100644 modules/connectors/gls/vendor/doxygen/rest_webservice_fault_responses_samples.html create mode 100644 modules/connectors/gls/vendor/doxygen/tabs.css create mode 100644 modules/connectors/gls/vendor/gls-validated-samples/README.md create mode 100644 modules/connectors/gls/vendor/gls-validated-samples/service_addresseeonly.json create mode 100644 modules/connectors/gls/vendor/gls-validated-samples/service_flexdelivery.json create mode 100644 modules/connectors/gls/vendor/gls-validated-samples/service_pickandreturn.json create mode 100644 modules/connectors/gls/vendor/gls-validated-samples/service_shopreturn.json create mode 100644 modules/connectors/gls/vendor/gls-validated-samples/service_signature.json delete mode 100644 modules/connectors/gls/vendor/shipit-farm.yaml create mode 100644 modules/connectors/hermes/SPECS.md create mode 100644 modules/connectors/hermes/karrio/providers/hermes/location.py create mode 100644 modules/connectors/hermes/karrio/schemas/hermes/parcelshop.py create mode 100644 modules/connectors/hermes/schemas/parcelshop.json create mode 100644 modules/connectors/hermes/tests/hermes/test_location.py create mode 100644 modules/connectors/landmark/SPECS.md create mode 100644 modules/connectors/laposte/SPECS.md create mode 100644 modules/connectors/mydhl/SPECS.md create mode 100644 modules/connectors/parcelone/SPECS.md delete mode 100644 modules/connectors/parcelone/karrio/providers/parcelone/rate.py create mode 100644 modules/connectors/parcelone/karrio/providers/parcelone/shipment/return_shipment.py create mode 100644 modules/connectors/parcelone/tests/parcelone/test_profile.py create mode 100644 modules/connectors/parcelone/vendor/ParcelOneShippingRESTApiV1.yaml create mode 100644 modules/connectors/parcelone/vendor/ParcelOneTrackingRESTApiV1.json create mode 100644 modules/connectors/parcelone/vendor/ParcelOneTracklmcRESTApiV1.yaml create mode 100644 modules/connectors/postat/SPECS.md create mode 100644 modules/connectors/postat/tests/postat/test_settings.py create mode 100644 modules/connectors/purolator/SPECS.md create mode 100644 modules/connectors/seko/SPECS.md create mode 100644 modules/connectors/sendle/SPECS.md create mode 100644 modules/connectors/smartkargo/SPECS.md create mode 100644 modules/connectors/spring/SPECS.md create mode 100644 modules/connectors/teleship/SPECS.md create mode 100644 modules/connectors/ups/SPECS.md create mode 100644 modules/connectors/ups/tests/ups/test_service_zone.py create mode 100644 modules/connectors/usps/SPECS.md create mode 100644 modules/connectors/usps_international/SPECS.md create mode 100644 modules/core/karrio/server/core/backends/__init__.py create mode 100644 modules/core/karrio/server/core/backends/immediate.py create mode 100644 modules/core/karrio/server/core/dynamic.py create mode 100644 modules/core/karrio/server/core/management/__init__.py create mode 100644 modules/core/karrio/server/core/management/commands/__init__.py create mode 100644 modules/core/karrio/server/core/paperless.py create mode 100644 modules/core/karrio/server/core/public_ids.py create mode 100644 modules/core/karrio/server/core/rate_dispatcher.py create mode 100644 modules/core/karrio/server/core/task_backend.py create mode 100644 modules/core/karrio/server/core/telemetry_scrubbing.py create mode 100644 modules/core/karrio/server/core/tests/_logging_helpers.py create mode 100644 modules/core/karrio/server/core/tests/test_apply_rate_selection.py create mode 100644 modules/core/karrio/server/core/tests/test_authentication_middleware.py create mode 100644 modules/core/karrio/server/core/tests/test_carrier_plugin_visibility.py create mode 100644 modules/core/karrio/server/core/tests/test_health_status.py create mode 100644 modules/core/karrio/server/core/tests/test_mfa_api.py create mode 100644 modules/core/karrio/server/core/tests/test_password_hashers.py create mode 100644 modules/core/karrio/server/core/tests/test_public_ids.py create mode 100644 modules/core/karrio/server/core/tests/test_rate_dispatcher.py create mode 100644 modules/core/karrio/server/core/tests/test_task_backend.py create mode 100644 modules/core/karrio/server/core/tests/test_telemetry_scrubbing.py create mode 100644 modules/core/karrio/server/core/tests/test_telemetry_scrubbing_hook.py create mode 100644 modules/core/karrio/server/core/views/health.py create mode 100644 modules/core/karrio/server/iam/migrations/0004_dedupe_context_permissions.py create mode 100644 modules/core/karrio/server/providers/management/commands/backfill_rate_sheet_destination_flags.py create mode 100644 modules/core/karrio/server/providers/migrations/0108_alter_secret_key_version_to_char.py create mode 100644 modules/core/karrio/server/providers/migrations/0110_merge_0108_branches.py create mode 100644 modules/core/karrio/server/providers/migrations/0111_carrier_options_frozen_connection_type.py create mode 100644 modules/core/karrio/server/providers/migrations/0112_service_level_tags.py create mode 100644 modules/core/karrio/server/providers/migrations/0113_alter_brokeredconnection_capabilities_overrides_and_more.py create mode 100644 modules/core/karrio/server/providers/migrations/0114_backfill_paperless_capability.py create mode 100644 modules/core/karrio/server/providers/rate_sheet_datatypes.py create mode 100644 modules/core/karrio/server/providers/service_level_matching.py create mode 100644 modules/core/karrio/server/providers/tags.py create mode 100644 modules/core/karrio/server/providers/tests/test_carrier_dynamic.py create mode 100644 modules/core/karrio/server/providers/tests/test_rate_sheet_datatypes.py create mode 100644 modules/core/karrio/server/providers/tests/test_service_level_matching.py create mode 100644 modules/core/karrio/server/providers/tests/test_tags.py create mode 100644 modules/core/karrio/server/providers/webhooks.py create mode 100644 modules/core/karrio/server/user/templates/two_factor/profile/profile.html create mode 100644 modules/data/karrio/server/data/templates/__init__.py delete mode 100644 modules/data/karrio/server/data/tests.py create mode 100644 modules/data/karrio/server/data/tests/test_rate_sheet_import_tags.py delete mode 100644 modules/documents/karrio/server/documents/tests.py create mode 100644 modules/documents/karrio/server/documents/tests/test_generation.py create mode 100644 modules/events/karrio/server/events/management/__init__.py create mode 100644 modules/events/karrio/server/events/management/commands/__init__.py create mode 100644 modules/events/karrio/server/events/management/commands/archive_data.py create mode 100644 modules/events/karrio/server/events/management/commands/close_pickups.py create mode 100644 modules/events/karrio/server/events/management/commands/dispatch_scheduled_workflows.py create mode 100644 modules/events/karrio/server/events/management/commands/update_trackers.py create mode 100644 modules/graph/karrio/server/graph/tests/test_connection_message_hooks.py create mode 100644 modules/graph/karrio/server/graph/tests/test_rate_type_parse.py create mode 100644 modules/graph/karrio/server/graph/tests/test_tracing_redaction.py create mode 100644 modules/manager/karrio/server/manager/migrations/0090_redact_persisted_rate_meta.py create mode 100644 modules/manager/karrio/server/manager/migrations/0091_backfill_purchase_snapshot.py create mode 100644 modules/manager/karrio/server/manager/tasks.py create mode 100644 modules/manager/karrio/server/manager/tests/test_paperless.py create mode 100644 modules/pricing/karrio/server/pricing/charge_breakdown.py create mode 100644 modules/pricing/karrio/server/pricing/migrations/0082_backfill_fee_account_and_capture_platform_fees.py create mode 100644 modules/pricing/karrio/server/pricing/migrations/0083_normalize_selected_rate_extra_charges.py create mode 100644 modules/pricing/karrio/server/pricing/migrations/0084_translate_markup_brokered_connection_ids.py create mode 100644 modules/pricing/karrio/server/pricing/rate_enrichment.py create mode 100644 modules/sdk/karrio/core/dynamic.py create mode 100644 modules/sdk/tests/core/test_authenticated_request.py create mode 100644 modules/sdk/tests/core/test_dynamic.py create mode 100644 modules/sdk/tests/core/test_multi_piece_rates.py create mode 100644 modules/sdk/tests/core/test_suppress_recipient_contact.py create mode 100644 modules/sdk/tests/test_helpers_span.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d7e18518bc..b0c07e6fd6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -92,6 +92,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: "20" + cache: "npm" - id: get_tag run: | @@ -111,14 +112,14 @@ jobs: # Build browser JS bundle cd packages/karriojs - npm install + npm ci npx gulp build --output "${GITHUB_WORKSPACE}/apps/api/karrio/server/static/karrio/js/karrio.js" cd - - name: Build embeddable elements run: | cd packages/elements - npm install + npm ci npm run build # Copy built elements to Django static directory @@ -163,15 +164,28 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - id: get_tag run: | cat ./apps/api/karrio/server/VERSION echo "tag=$(cat ./apps/api/karrio/server/VERSION)" >> "$GITHUB_ENV" - name: Build karrio dashboard image - run: | - echo 'Building karrio dashboard:${{ env.tag }}...' - ./bin/build-dashboard-image ${{ env.tag }} + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/dashboard/Dockerfile + push: false + load: true + tags: karrio/dashboard:${{ env.tag }} + build-args: | + VERSION=${{ env.tag }} + NEXT_PUBLIC_DASHBOARD_VERSION=${{ env.tag }} + SOURCE=https://github.com/karrioapi/karrio + cache-from: type=gha,scope=dashboard + cache-to: type=gha,mode=max,scope=dashboard - name: Push karrio dashboard image run: | diff --git a/.github/workflows/insiders-build.yml b/.github/workflows/insiders-build.yml index e4a5b8f088..021bdca73a 100644 --- a/.github/workflows/insiders-build.yml +++ b/.github/workflows/insiders-build.yml @@ -95,6 +95,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: "20" + cache: "npm" - id: get_tag run: | @@ -112,14 +113,14 @@ jobs: --additional-properties=useSingleRequestParameter=true cd packages/karriojs - npm install + npm ci npx gulp build --output "${GITHUB_WORKSPACE}/apps/api/karrio/server/static/karrio/js/karrio.js" cd - - name: Build embeddable elements run: | cd packages/elements - npm install + npm ci npm run build ELEMENTS_STATIC="${GITHUB_WORKSPACE}/apps/api/karrio/server/static/karrio/elements" @@ -156,14 +157,27 @@ jobs: submodules: recursive token: ${{ secrets.GH_PAT }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - id: get_tag run: | cat ./apps/api/karrio/server/VERSION echo "tag=$(cat ./apps/api/karrio/server/VERSION)" >> "$GITHUB_ENV" + - name: Login to GHCR + run: echo ${{ secrets.GH_PAT }} | docker login ghcr.io -u USERNAME --password-stdin + - name: Build insider dashboard image - run: | - echo 'Build and push karrio-insiders dashboard:${{ env.tag }}...' - echo ${{ secrets.GH_PAT }} | docker login ghcr.io -u USERNAME --password-stdin - KARRIO_IMAGE=ghcr.io/karrioapi/dashboard SOURCE=https://github.com/karrioapi/karrio-insiders ./bin/build-dashboard-image ${{ env.tag }} && - docker push ghcr.io/karrioapi/dashboard:${{ env.tag }} || exit 1 + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/dashboard/Dockerfile + push: true + tags: ghcr.io/karrioapi/dashboard:${{ env.tag }} + build-args: | + VERSION=${{ env.tag }} + NEXT_PUBLIC_DASHBOARD_VERSION=${{ env.tag }} + SOURCE=https://github.com/karrioapi/karrio-insiders + cache-from: type=gha,scope=insiders-dashboard + cache-to: type=gha,mode=max,scope=insiders-dashboard diff --git a/.github/workflows/platform-build.yml b/.github/workflows/platform-build.yml index 7628bd185a..3275549b8f 100644 --- a/.github/workflows/platform-build.yml +++ b/.github/workflows/platform-build.yml @@ -41,6 +41,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: "20" + cache: "npm" - id: get_tag run: | @@ -57,14 +58,14 @@ jobs: --additional-properties=useSingleRequestParameter=true cd packages/karriojs - npm install + npm ci npx gulp build --output "${GITHUB_WORKSPACE}/apps/api/karrio/server/static/karrio/js/karrio.js" cd - - name: Build embeddable elements run: | cd packages/elements - npm install + npm ci npm run build ELEMENTS_STATIC="${GITHUB_WORKSPACE}/apps/api/karrio/server/static/karrio/elements" @@ -126,6 +127,12 @@ jobs: submodules: recursive token: ${{ secrets.GH_PAT }} + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: diff --git a/apps/api/gunicorn-cfg.py b/apps/api/gunicorn-cfg.py index 9d1a2fe7e8..0014fb2e94 100644 --- a/apps/api/gunicorn-cfg.py +++ b/apps/api/gunicorn-cfg.py @@ -1,7 +1,6 @@ -# -*- encoding: utf-8 -*- import decouple -KARRIO_HOST = decouple.config("KARRIO_HTTP_HOST", default="0.0.0.0") +KARRIO_HOST = decouple.config("KARRIO_HTTP_HOST", default="0.0.0.0") # noqa: S104 KARRIO_PORT = decouple.config("KARRIO_HTTP_PORT", default=5002) bind = f"{KARRIO_HOST}:{KARRIO_PORT}" @@ -10,3 +9,12 @@ capture_output = True enable_stdio_inheritance = True workers = decouple.config("KARRIO_WORKERS", default=2, cast=int) + +# NOTE: preload_app is intentionally NOT set here. +# With UvicornWorker (ASGI), preload_app=True causes: +# 1. asyncio.CancelledError in django-health-check (stale event loop from master) +# 2. psycopg "BAD" connections (DB pool forked from master) +# UvicornWorker manages its own lifecycle and ignores gunicorn's post_fork, +# so these issues cannot be fixed via post_fork hooks. +# Cold-start CPU optimization should be addressed at the k8s level +# (resource requests/limits, startup probes) instead. diff --git a/apps/api/karrio/server/__init__.py b/apps/api/karrio/server/__init__.py index 0d1f7edf5d..d55ccad1f5 100644 --- a/apps/api/karrio/server/__init__.py +++ b/apps/api/karrio/server/__init__.py @@ -1 +1 @@ -__path__ = __import__('pkgutil').extend_path(__path__, __name__) # type: ignore +__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore diff --git a/apps/api/karrio/server/__main__.py b/apps/api/karrio/server/__main__.py index 4bb823c45c..e139181b68 100644 --- a/apps/api/karrio/server/__main__.py +++ b/apps/api/karrio/server/__main__.py @@ -1,11 +1,12 @@ #!/usr/bin/env python """Karrio's command-line utility for administrative tasks.""" + import os import sys def main(): - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'karrio.server.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "karrio.server.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -17,5 +18,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/apps/api/karrio/server/asgi.py b/apps/api/karrio/server/asgi.py index 41ddbc5218..106a2e83a6 100644 --- a/apps/api/karrio/server/asgi.py +++ b/apps/api/karrio/server/asgi.py @@ -1,17 +1,61 @@ """ ASGI config for karrio.server project. -It exposes the ASGI callable as a module-level variable named ``application``. +Exposes the ASGI callable as a module-level variable named ``application``. + +The callable is a thin async wrapper around Django's ASGI app that +captures the worker's running event loop on the first http/websocket +request. The captured loop is handed to the ``karrio.server.servicebus`` +publisher (if installed) so its async producer can schedule publishes +on the worker's event loop via ``asyncio.run_coroutine_threadsafe``. + +Why first-request capture and not the ASGI lifespan protocol: our +custom ``UvicornWorker`` (karrio/apps/api/karrio/server/workers.py) sets +``"lifespan": "off"`` explicitly because Django's stock ASGI app does +not handle lifespan messages. First-request capture is one global + +a handful of lines, no worker config change needed. For more information on this file, see https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ """ +import asyncio +import logging import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'karrio.server.settings') -os.environ.setdefault('DJANGO_ALLOW_ASYNC_UNSAFE', 'true') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "karrio.server.settings") +os.environ.setdefault("DJANGO_ALLOW_ASYNC_UNSAFE", "true") + +logger = logging.getLogger(__name__) + +_django_app = get_asgi_application() +_loop_captured = False + + +async def application(scope, receive, send): + """ASGI application with first-request event-loop capture. + + Non-http/websocket scopes pass straight through to Django. The + capture happens once per worker; subsequent requests are zero-cost + fast-path (a single boolean check). + """ + global _loop_captured + if not _loop_captured and scope.get("type") in ("http", "websocket"): + _loop_captured = True + try: + from karrio.server.servicebus import set_async_loop -application = get_asgi_application() + set_async_loop(asyncio.get_running_loop()) + logger.debug("ASGI: captured event loop for servicebus async publisher") + except ImportError: + # servicebus extension not installed in this deployment; that's + # fine — bridge dual-publish will use the sync path. + pass + except Exception: + logger.warning( + "ASGI: failed to capture event loop for servicebus async publisher", + exc_info=True, + ) + await _django_app(scope, receive, send) diff --git a/apps/api/karrio/server/lib/otel_huey.py b/apps/api/karrio/server/lib/otel_huey.py deleted file mode 100644 index 0baad44df1..0000000000 --- a/apps/api/karrio/server/lib/otel_huey.py +++ /dev/null @@ -1,146 +0,0 @@ -""" -OpenTelemetry instrumentation for Huey task queue. - -This module provides tracing support for Huey tasks, enabling distributed tracing -across API requests and background tasks. -""" -import functools -import logging -from typing import Any, Callable, Dict, Optional - -from opentelemetry import trace, context, propagate -from opentelemetry.trace import Status, StatusCode -from opentelemetry.semconv.trace import SpanAttributes - -logger = logging.getLogger(__name__) - - -class HueyInstrumentor: - """Instrumentation for Huey task queue.""" - - _instance = None - _instrumented = False - - def __new__(cls): - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def instrument(self, huey_instance=None): - """ - Instrument Huey for OpenTelemetry tracing. - - Args: - huey_instance: The Huey instance to instrument. If None, will try to - import from Django settings. - """ - if self._instrumented: - logger.debug("Huey already instrumented") - return - - try: - if huey_instance is None: - from django.conf import settings - huey_instance = settings.HUEY - - # Wrap the task decorator - original_task = huey_instance.task - huey_instance.task = self._wrap_task_decorator(original_task, huey_instance) - - # Wrap periodic tasks - if hasattr(huey_instance, 'periodic_task'): - original_periodic = huey_instance.periodic_task - huey_instance.periodic_task = self._wrap_task_decorator(original_periodic, huey_instance) - - self._instrumented = True - logger.info("Huey instrumented for OpenTelemetry") - - except Exception as e: - logger.warning(f"Failed to instrument Huey: {e}") - - def _wrap_task_decorator(self, original_decorator: Callable, huey_instance) -> Callable: - """Wrap the Huey task decorator to add tracing.""" - - @functools.wraps(original_decorator) - def wrapped_decorator(*args, **kwargs): - decorated = original_decorator(*args, **kwargs) - - def task_wrapper(fn): - task_fn = decorated(fn) - - @functools.wraps(task_fn) - def traced_task(*task_args, **task_kwargs): - tracer = trace.get_tracer(__name__) - - # Extract trace context from task kwargs if present - trace_context = task_kwargs.pop('_otel_context', None) - if trace_context: - ctx = propagate.extract(trace_context) - token = context.attach(ctx) - else: - token = None - - # Start span for the task - task_name = fn.__name__ - with tracer.start_as_current_span( - f"huey.task.{task_name}", - kind=trace.SpanKind.CONSUMER, - ) as span: - try: - # Set span attributes - span.set_attribute("messaging.system", "huey") - span.set_attribute("messaging.destination", task_name) - span.set_attribute("messaging.operation", "process") - span.set_attribute("task.name", task_name) - - # Execute the task - result = task_fn(*task_args, **task_kwargs) - span.set_status(Status(StatusCode.OK)) - return result - - except Exception as e: - span.set_status(Status(StatusCode.ERROR, str(e))) - span.record_exception(e) - raise - finally: - if token: - context.detach(token) - - # Preserve original attributes - traced_task.task = task_fn.task if hasattr(task_fn, 'task') else task_fn - if hasattr(task_fn, '__name__'): - traced_task.__name__ = task_fn.__name__ - if hasattr(task_fn, '__module__'): - traced_task.__module__ = task_fn.__module__ - - return traced_task - - return task_wrapper - - return wrapped_decorator - - -def inject_trace_context(task_kwargs: Dict[str, Any]) -> Dict[str, Any]: - """ - Inject current trace context into task kwargs for propagation. - - This should be called when enqueuing a task to propagate the trace context - to the background worker. - - Args: - task_kwargs: The kwargs to be passed to the task - - Returns: - Updated kwargs with trace context - """ - carrier = {} - propagate.inject(carrier) - if carrier: - task_kwargs['_otel_context'] = carrier - return task_kwargs - - -def instrument_huey(): - """Convenience function to instrument Huey.""" - instrumentor = HueyInstrumentor() - instrumentor.instrument() \ No newline at end of file diff --git a/apps/api/karrio/server/settings/__init__.py b/apps/api/karrio/server/settings/__init__.py index 475f01cdad..9c91e6742d 100644 --- a/apps/api/karrio/server/settings/__init__.py +++ b/apps/api/karrio/server/settings/__init__.py @@ -1,4 +1,5 @@ # type: ignore +# ruff: noqa: F403, F405 __path__ = __import__("pkgutil").extend_path(__path__, __name__) import importlib.util @@ -34,5 +35,13 @@ from karrio.server.settings.admin import * +if importlib.util.find_spec("karrio.server.settings.huey") is not None: + from karrio.server.settings.huey import * + + +if importlib.util.find_spec("karrio.server.settings.servicebus") is not None: + from karrio.server.settings.servicebus import * + + if importlib.util.find_spec("karrio.server.settings.main") is not None: from karrio.server.settings.main import * diff --git a/apps/api/karrio/server/settings/apm.py b/apps/api/karrio/server/settings/apm.py index 0535b9f92c..18f97ddd24 100644 --- a/apps/api/karrio/server/settings/apm.py +++ b/apps/api/karrio/server/settings/apm.py @@ -1,8 +1,15 @@ # type: ignore +# ruff: noqa: F403, F405 +import asyncio +import logging +import time +from contextlib import suppress + +import karrio.server.core.telemetry_scrubbing as telemetry_scrubbing import posthog import sentry_sdk -from sentry_sdk.integrations.django import DjangoIntegration from karrio.server.settings.base import * +from sentry_sdk.integrations.django import DjangoIntegration # Try to import PostHog integration, fallback if not available try: @@ -50,25 +57,36 @@ # 5. Logging Integration: ERROR/CRITICAL logs are sent to Sentry # # Environment Variables: -# SENTRY_DSN - Sentry Data Source Name (required to enable) -# SENTRY_ENVIRONMENT - Environment name (default: from ENV or "production") -# SENTRY_RELEASE - Release version (default: VERSION) -# SENTRY_TRACES_SAMPLE_RATE - Transaction sampling rate 0.0-1.0 (default: 1.0) -# SENTRY_PROFILES_SAMPLE_RATE - Profile sampling rate 0.0-1.0 (default: 1.0) -# SENTRY_SEND_PII - Send personally identifiable information (default: true) -# SENTRY_DEBUG - Enable Sentry debug mode (default: false) +# SENTRY_DSN - Sentry Data Source Name (required to enable) +# SENTRY_ENVIRONMENT - Environment name (default: from ENV or "production") +# SENTRY_RELEASE - Release version (default: VERSION) +# SENTRY_TRACES_SAMPLE_RATE - Transaction sampling rate 0.0-1.0 (default: 1.0) +# SENTRY_PROFILES_SAMPLE_RATE - Profile sampling rate 0.0-1.0 (default: 1.0) +# SENTRY_SEND_PII - Send personally identifiable information (default: false) +# SENTRY_DEBUG - Enable Sentry debug mode (default: false) # SENTRY_TRACE_PROPAGATION_TARGETS - Regex patterns for distributed tracing (default: localhost) # +# Worker Uptime Monitor (set on the worker deployment only): +# SENTRY_WORKER_MONITOR_SLUG - Sentry Cron Monitor slug for the worker heartbeat. +# Create the monitor in Sentry UI (type: Heartbeat, +# check-in margin: 2 min, max runtime: 1 min). +# When set, the worker sends an IN_PROGRESS check-in +# every SENTRY_WORKER_HEARTBEAT_INTERVAL seconds. +# SENTRY_WORKER_HEARTBEAT_INTERVAL - Heartbeat interval in seconds (default: 60) +# # ============================================================================= -sentry_sdk.utils.MAX_STRING_LENGTH = 4096 +# Sentry truncates string values (including HTTP request bodies) to this +# limit. The default (1024) is too short for wawi payloads. 16 KB is +# enough for most shipment requests while avoiding excessive event size. +sentry_sdk.utils.MAX_STRING_LENGTH = 16384 SENTRY_DSN = config("SENTRY_DSN", default=None) SENTRY_ENVIRONMENT = config("SENTRY_ENVIRONMENT", default=config("ENV", default="production")) SENTRY_RELEASE = config("SENTRY_RELEASE", default=config("VERSION", default=None)) # Lower default sample rates for better performance (was 1.0/100%) SENTRY_TRACES_SAMPLE_RATE = config("SENTRY_TRACES_SAMPLE_RATE", default=1.0, cast=float) # 100% - capture all traces SENTRY_PROFILES_SAMPLE_RATE = config("SENTRY_PROFILES_SAMPLE_RATE", default=0.0, cast=float) # Disabled by default -SENTRY_SEND_PII = config("SENTRY_SEND_PII", default=True, cast=bool) +SENTRY_SEND_PII = config("SENTRY_SEND_PII", default=False, cast=bool) SENTRY_DEBUG = config("SENTRY_DEBUG", default=False, cast=bool) # Trace propagation targets for distributed tracing (internal services only) # Comma-separated list of regex patterns; headers are NOT sent to carrier APIs @@ -78,6 +96,22 @@ default=r"localhost", ) +_SENTRY_NOISY_ENDPOINTS = ( + "/health", + "/ready", + "/live", + "/status", + "/_health", + "/favicon.ico", + "/static/", + "/robots.txt", +) + +# Harmless exceptions filtered out before reaching Sentry. +_SENTRY_FILTERED_EXCEPTIONS = ( + asyncio.CancelledError, # ASGI probe disconnects before response completes +) + def _sentry_traces_sampler(sampling_context): """Custom traces sampler that ensures requests with x-request-id are always traced.""" @@ -87,7 +121,7 @@ def _sentry_traces_sampler(sampling_context): return 1.0 # Health checks - never trace path = wsgi_env.get("PATH_INFO", "") - if any(path.startswith(ep) for ep in ["/health", "/ready", "/live", "/status"]): + if any(path.startswith(endpoint) for endpoint in _SENTRY_NOISY_ENDPOINTS): return 0.0 return SENTRY_TRACES_SAMPLE_RATE @@ -100,6 +134,10 @@ def _sentry_before_send(event, hint): - Add custom tags - Filter out certain events """ + exc_info = hint.get("exc_info") + if exc_info and isinstance(exc_info[1], _SENTRY_FILTERED_EXCEPTIONS): + return None + # Scrub sensitive data from request bodies if "request" in event: request_data = event["request"] @@ -114,9 +152,17 @@ def _sentry_before_send(event, hint): # Scrub POST data if "data" in request_data and isinstance(request_data["data"], dict): sensitive_fields = [ - "password", "secret", "token", "api_key", "apikey", - "access_token", "refresh_token", "client_secret", - "account_number", "meter_number", "license_key", + "password", + "secret", + "token", + "api_key", + "apikey", + "access_token", + "refresh_token", + "client_secret", + "account_number", + "meter_number", + "license_key", ] for field in sensitive_fields: for key in list(request_data["data"].keys()): @@ -135,21 +181,59 @@ def _sentry_before_send_transaction(event, hint): """ transaction_name = event.get("transaction", "") - # Filter out health check and monitoring endpoints - noisy_endpoints = [ - "/health", - "/ready", - "/live", - "/_health", - "/favicon.ico", - "/static/", - "/robots.txt", - ] - - for endpoint in noisy_endpoints: + for endpoint in _SENTRY_NOISY_ENDPOINTS: if transaction_name.startswith(endpoint): return None # Drop this transaction + # SHIP2-1185 — surface per-transaction scrub timing in pod logs so we can + # correlate /v1/wawi/* latency against missing http.carrier spans. Bounded + # to the wawi prefix to avoid log flood on other endpoints. + _diag_log = transaction_name.startswith("/v1/wawi/") + _t0 = time.monotonic() if _diag_log else None + _span_count = 0 + _carrier_count = 0 + _scrubbed_bytes = 0 + + try: + for span in event.get("spans", []): + _span_count += 1 + if _diag_log and span.get("op") in ("http.carrier", "http.client"): + _carrier_count += 1 + _data = span.get("data") or {} + for _k in ( + "http.request.body", + "http.response.body", + "request.body", + "response.body", + "http.url", + ): + _v = _data.get(_k) + if isinstance(_v, str): + _scrubbed_bytes += len(_v) + span["data"] = telemetry_scrubbing.scrub_span_data(span.get("data")) + except Exception: + if _diag_log: + logging.getLogger(__name__).warning( + "sentry.before_send_transaction.raised txn=%s spans=%d carrier=%d bytes=%d", + transaction_name, + _span_count, + _carrier_count, + _scrubbed_bytes, + exc_info=True, + ) + return event + + if _diag_log: + _dt_ms = (time.monotonic() - _t0) * 1000 + logging.getLogger(__name__).info( + "sentry.before_send_transaction.ok txn=%s spans=%d carrier=%d bytes=%d dur_ms=%.1f", + transaction_name, + _span_count, + _carrier_count, + _scrubbed_bytes, + _dt_ms, + ) + return event @@ -158,8 +242,8 @@ def _sentry_before_send_transaction(event, hint): integrations = [ DjangoIntegration( transaction_style="url", # Use URL patterns for transaction names - middleware_spans=False, # Disabled for performance (request_id is tagged via set_tag, not spans) - signals_spans=False, # Disabled for performance + middleware_spans=False, # Disabled for performance (request_id is tagged via set_tag, not spans) + signals_spans=False, # Disabled for performance ), ] @@ -170,79 +254,75 @@ def _sentry_before_send_transaction(event, hint): # Try to add Redis integration if Redis is configured try: from sentry_sdk.integrations.redis import RedisIntegration + if config("REDIS_URL", default=None) or config("REDIS_HOST", default=None): integrations.append(RedisIntegration()) except ImportError: pass - # Try to add Huey integration for background tasks - try: + # Try to add Huey integration for background tasks (dev only; not installed in servicebus images) + with suppress(Exception): from sentry_sdk.integrations.huey import HueyIntegration + integrations.append(HueyIntegration()) - except ImportError: - pass # Note: karrio SDK uses urllib (not requests), so RequestsIntegration is not used here. # Carrier HTTP calls are instrumented via _urlopen_with_span() in karrio/core/utils/helpers.py # which wraps urlopen with sentry_sdk.start_span() directly. # Try to add httpx integration for async HTTP clients - try: + with suppress(Exception): from sentry_sdk.integrations.httpx import HttpxIntegration + integrations.append(HttpxIntegration()) - except Exception: - pass # httpx may not be installed # Try to add Strawberry GraphQL integration - try: + with suppress(Exception): from sentry_sdk.integrations.strawberry import StrawberryIntegration + integrations.append(StrawberryIntegration(async_execution=False)) - except Exception: - pass # strawberry integration may not be available # Parse trace propagation targets (comma-separated regex patterns) - trace_targets = [ - pattern.strip() - for pattern in SENTRY_TRACE_PROPAGATION_TARGETS.split(",") - if pattern.strip() - ] + trace_targets = [pattern.strip() for pattern in SENTRY_TRACE_PROPAGATION_TARGETS.split(",") if pattern.strip()] sentry_sdk.init( dsn=SENTRY_DSN, integrations=integrations, - + # In-app frame classification — karrio.* code is "in app", everything else is library code. + # Drives which frames are expanded by default in the Sentry UI. + in_app_include=["karrio"], # Environment and release tracking environment=SENTRY_ENVIRONMENT, release=SENTRY_RELEASE, - # Performance monitoring — use sampler for fine-grained control # Always trace requests with explicit x-request-id; skip health checks traces_sampler=_sentry_traces_sampler, # Only enable profiling if explicitly configured (disabled by default) - **({"profile_session_sample_rate": SENTRY_PROFILES_SAMPLE_RATE, "profile_lifecycle": "trace"} if SENTRY_PROFILES_SAMPLE_RATE > 0 else {}), - + **( + {"profile_session_sample_rate": SENTRY_PROFILES_SAMPLE_RATE, "profile_lifecycle": "trace"} + if SENTRY_PROFILES_SAMPLE_RATE > 0 + else {} + ), # Distributed tracing - propagate trace headers to internal services # Note: Headers are NOT sent to carrier APIs (they wouldn't understand them) trace_propagation_targets=trace_targets, - # Privacy settings send_default_pii=SENTRY_SEND_PII, - # Logging integration enable_logs=True, - # Debug mode debug=SENTRY_DEBUG, - # Event processing hooks before_send=_sentry_before_send, before_send_transaction=_sentry_before_send_transaction, - + # Request body capture — "always" ensures the full POST body is + # attached to Sentry events (needed for debugging wawi payloads). + max_request_body_size="always", # Additional options (reduced for performance) - max_breadcrumbs=50, # Enough for carrier call traces without excessive memory - attach_stacktrace=True, # Attach stack traces to messages - include_source_context=False, # Disabled for performance (avoids file I/O on capture) - include_local_variables=False, # Disabled for performance (avoids capturing all stack locals) + max_breadcrumbs=50, # Enough for carrier call traces without excessive memory + attach_stacktrace=True, # Attach stack traces to messages + include_source_context=True, # Read .py files on capture so frames show inline code context + include_local_variables=False, # Disabled for performance (avoids capturing all stack locals) ) # Set default tags that will be applied to all events @@ -250,11 +330,9 @@ def _sentry_before_send_transaction(event, hint): sentry_sdk.set_tag("framework", "django") import logging + logger = logging.getLogger(__name__) - logger.info( - f"Sentry initialized: env={SENTRY_ENVIRONMENT}, " - f"traces_sample_rate={SENTRY_TRACES_SAMPLE_RATE}" - ) + logger.info(f"Sentry initialized: env={SENTRY_ENVIRONMENT}, traces_sample_rate={SENTRY_TRACES_SAMPLE_RATE}") # OpenTelemetry Configuration @@ -272,26 +350,27 @@ def _sentry_before_send_transaction(event, hint): # Only initialize OpenTelemetry if enabled and endpoint is configured if OTEL_ENABLED and OTEL_EXPORTER_OTLP_ENDPOINT: import logging - from opentelemetry import trace, metrics - from opentelemetry.sdk.trace import TracerProvider - from opentelemetry.sdk.trace.export import BatchSpanProcessor - from opentelemetry.sdk.metrics import MeterProvider - from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader - from opentelemetry.sdk.resources import Resource, SERVICE_NAME, SERVICE_VERSION + + from opentelemetry import metrics, trace from opentelemetry.instrumentation.django import DjangoInstrumentor - from opentelemetry.instrumentation.requests import RequestsInstrumentor from opentelemetry.instrumentation.logging import LoggingInstrumentor - from opentelemetry.instrumentation.psycopg2 import Psycopg2Instrumentor + from opentelemetry.instrumentation.psycopg import PsycopgInstrumentor from opentelemetry.instrumentation.redis import RedisInstrumentor - + from opentelemetry.instrumentation.requests import RequestsInstrumentor + from opentelemetry.sdk.metrics import MeterProvider + from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader + from opentelemetry.sdk.resources import SERVICE_NAME, SERVICE_VERSION, Resource + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import BatchSpanProcessor + # Import appropriate exporter based on protocol if OTEL_EXPORTER_OTLP_PROTOCOL == "grpc": - from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter + from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter else: # http/protobuf - from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter - + from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter + # Parse headers if provided headers = {} if OTEL_EXPORTER_OTLP_HEADERS: @@ -299,7 +378,7 @@ def _sentry_before_send_transaction(event, hint): if "=" in header_pair: key, value = header_pair.split("=", 1) headers[key.strip()] = value.strip() - + # Parse resource attributes resource_attributes = { SERVICE_NAME: OTEL_SERVICE_NAME, @@ -307,20 +386,20 @@ def _sentry_before_send_transaction(event, hint): "environment": OTEL_ENVIRONMENT, "deployment.environment": OTEL_ENVIRONMENT, } - + if OTEL_RESOURCE_ATTRIBUTES: for attr_pair in OTEL_RESOURCE_ATTRIBUTES.split(","): if "=" in attr_pair: key, value = attr_pair.split("=", 1) resource_attributes[key.strip()] = value.strip() - + # Create resource resource = Resource(attributes=resource_attributes) - + # Configure Trace Provider trace_provider = TracerProvider(resource=resource) trace.set_tracer_provider(trace_provider) - + # Configure span exporter span_exporter = OTLPSpanExporter( endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, @@ -328,7 +407,7 @@ def _sentry_before_send_transaction(event, hint): ) span_processor = BatchSpanProcessor(span_exporter) trace_provider.add_span_processor(span_processor) - + # Configure Metrics Provider metric_exporter = OTLPMetricExporter( endpoint=OTEL_EXPORTER_OTLP_ENDPOINT, @@ -343,42 +422,31 @@ def _sentry_before_send_transaction(event, hint): metric_readers=[metric_reader], ) metrics.set_meter_provider(meter_provider) - + # Instrument Django DjangoInstrumentor().instrument( is_sql_commentor_enabled=True, # Add trace context to SQL queries request_hook=lambda span, request: span.set_attribute("http.client_ip", request.META.get("REMOTE_ADDR", "")), - response_hook=lambda span, request, response: span.set_attribute("http.response.size", len(response.content) if hasattr(response, 'content') else 0), + response_hook=lambda span, request, response: span.set_attribute( + "http.response.size", len(response.content) if hasattr(response, "content") else 0 + ), ) - + # Instrument other libraries RequestsInstrumentor().instrument() # HTTP client requests LoggingInstrumentor().instrument(set_logging_format=True) # Add trace context to logs - + # Instrument database if PostgreSQL is used if config("DATABASE_ENGINE", default="").endswith("postgresql"): - try: - Psycopg2Instrumentor().instrument() - except Exception: - pass # Psycopg2 might not be installed - + with suppress(Exception): + PsycopgInstrumentor().instrument() + # Instrument Redis if configured if config("REDIS_URL", default=None) or config("REDIS_HOST", default=None): - try: + with suppress(Exception): RedisInstrumentor().instrument() - except Exception: - pass # Redis might not be installed - - # Instrument Huey task queue - try: - from huey.contrib.djhuey import HUEY as huey_instance - from karrio.server.lib.otel_huey import HueyInstrumentor - instrumentor = HueyInstrumentor() - instrumentor.instrument(huey_instance) - except Exception as e: - logger = logging.getLogger(__name__) - logger.warning(f"Failed to instrument Huey: {e}") + # Huey OTel instrumentation is handled by karrio.server.huey.apps.HueyConfig.ready() # Log that OpenTelemetry is enabled logger = logging.getLogger(__name__) @@ -432,7 +500,8 @@ def _sentry_before_send_transaction(event, hint): try: import ddtrace - from ddtrace import config as dd_config, tracer, patch_all + from ddtrace import config as dd_config + from ddtrace import patch_all, tracer # Configure tracer ddtrace.config.service = DD_SERVICE @@ -454,6 +523,7 @@ def _sentry_before_send_transaction(event, hint): # Set global sample rate from ddtrace.sampler import DatadogSampler + tracer.configure(sampler=DatadogSampler(default_sample_rate=DD_TRACE_SAMPLE_RATE)) # Enable log injection @@ -471,18 +541,15 @@ def _sentry_before_send_transaction(event, hint): ) # Patch Huey for background task tracing - try: + with suppress(Exception): from ddtrace import patch + patch(huey=True) - except Exception: - pass # Huey integration may not be available in all ddtrace versions # Enable profiling if configured if DD_PROFILING_ENABLED: - try: + with suppress(ImportError): import ddtrace.profiling.auto # noqa: F401 - except ImportError: - pass # Configure DogStatsD for metrics try: @@ -504,20 +571,19 @@ def _sentry_before_send_transaction(event, hint): pass # datadog package not installed, metrics won't work import logging + logger = logging.getLogger(__name__) logger.info( - f"Datadog APM initialized: service={DD_SERVICE}, env={DD_ENV}, " - f"agent={DD_AGENT_HOST}:{DD_TRACE_AGENT_PORT}" + f"Datadog APM initialized: service={DD_SERVICE}, env={DD_ENV}, agent={DD_AGENT_HOST}:{DD_TRACE_AGENT_PORT}" ) except ImportError: import logging + logger = logging.getLogger(__name__) - logger.warning( - "Datadog tracing enabled but ddtrace package not installed. " - "Install with: pip install ddtrace" - ) + logger.warning("Datadog tracing enabled but ddtrace package not installed. Install with: pip install ddtrace") except Exception as e: import logging + logger = logging.getLogger(__name__) logger.warning(f"Failed to initialize Datadog APM: {e}") diff --git a/apps/api/karrio/server/settings/base.py b/apps/api/karrio/server/settings/base.py index 84377e0e05..4ab373e9f1 100644 --- a/apps/api/karrio/server/settings/base.py +++ b/apps/api/karrio/server/settings/base.py @@ -10,23 +10,23 @@ https://docs.djangoproject.com/en/3.0/ref/settings/ """ +import importlib import os +import sys as _sys +from datetime import timedelta +from pathlib import Path + import decouple -import importlib import dj_database_url - -from pathlib import Path -from datetime import timedelta -from django.urls import reverse_lazy from corsheaders.defaults import default_headers from django.core.management.utils import get_random_secret_key +from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ - # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = Path(__file__).resolve().parent.parent.parent -with open(BASE_DIR / "server" / "VERSION", "r") as v: +with open(BASE_DIR / "server" / "VERSION") as v: VERSION = v.read().strip() @@ -35,11 +35,11 @@ if not config("SECRET_KEY", default=None): try: # Note: Using print here intentionally as logging isn't configured yet - print("> fallback .env.sample...") + print("> fallback .env.sample...") # noqa: T201 config = decouple.Config(decouple.RepositoryEnv(".env.sample")) except Exception as e: # Note: Using print here intentionally as logging isn't configured yet - print(f"> error: {e}") + print(f"> error: {e}") # noqa: T201 # Quick-start development settings - unsuitable for production @@ -48,7 +48,7 @@ SECRET_KEY = config("SECRET_KEY", default=get_random_secret_key()) # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = config("DEBUG_MODE", default=True, cast=bool) +DEBUG = config("DEBUG_MODE", default=False, cast=bool) # custom env WORK_DIR = config("WORK_DIR", default="") @@ -88,16 +88,14 @@ SECURE_SSL_REDIRECT = True # Exempt health check endpoint from HTTPS redirect for Kubernetes probes - SECURE_REDIRECT_EXEMPT = [r'^status/$'] + SECURE_REDIRECT_EXEMPT = [r"^status/$"] SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") SESSION_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 1 SECURE_HSTS_INCLUDE_SUBDOMAINS = True CSRF_COOKIE_SECURE = True SECURE_HSTS_PRELOAD = True - CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default="https://*").split( - "," - ) + CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default="https://*").split(",") # karrio packages settings @@ -171,18 +169,12 @@ KARRIO_APPS = [cfg["app"] for cfg in KARRIO_CONF] KARRIO_URLS = [cfg["urls"] for cfg in KARRIO_CONF if "urls" in cfg] -ALLOW_ADMIN_APPROVED_SIGNUP = config( - "ALLOW_ADMIN_APPROVED_SIGNUP", default=False, cast=bool -) -ALLOW_SIGNUP = ( - config("ALLOW_SIGNUP", default=False, cast=bool) or ALLOW_ADMIN_APPROVED_SIGNUP -) +ALLOW_ADMIN_APPROVED_SIGNUP = config("ALLOW_ADMIN_APPROVED_SIGNUP", default=False, cast=bool) +ALLOW_SIGNUP = config("ALLOW_SIGNUP", default=False, cast=bool) or ALLOW_ADMIN_APPROVED_SIGNUP MULTI_ORGANIZATIONS = ( importlib.util.find_spec("karrio.server.orgs") is not None # type:ignore ) -ALLOW_MULTI_ACCOUNT = config( - "ALLOW_MULTI_ACCOUNT", default=MULTI_ORGANIZATIONS, cast=bool -) +ALLOW_MULTI_ACCOUNT = config("ALLOW_MULTI_ACCOUNT", default=MULTI_ORGANIZATIONS, cast=bool) ADMIN_DASHBOARD = importlib.util.find_spec( # type:ignore "karrio.server.admin" ) is not None and config("ADMIN_DASHBOARD", default=True, cast=bool) @@ -191,7 +183,7 @@ ) APPS_MANAGEMENT = ( importlib.util.find_spec("karrio.server.apps") is not None # type:ignore -) and config("APPS_MANAGEMENT", default=(True if DEBUG else False), cast=bool) +) and config("APPS_MANAGEMENT", default=bool(DEBUG), cast=bool) DOCUMENTS_MANAGEMENT = ( importlib.util.find_spec("karrio.server.documents") is not None # type:ignore ) @@ -208,9 +200,13 @@ "karrio.server.audit" ) is not None and config("AUDIT_LOGGING", default=True, cast=bool) PERSIST_SDK_TRACING = config("PERSIST_SDK_TRACING", default=True, cast=bool) +# Emit the built-in shipment.purchased webhook from the core events signal. +# Default on; set False once the bridge fan-out owns the purchased webhook +# (see modules/bridge consumer) to avoid double-delivery. +BUILT_IN_WEBHOOKS = config("BUILT_IN_WEBHOOKS", default=True, cast=bool) WORKFLOW_MANAGEMENT = ( importlib.util.find_spec("karrio.server.automation") is not None # type:ignore -) and config("WORKFLOW_MANAGEMENT", default=(True if DEBUG else False), cast=bool) +) and config("WORKFLOW_MANAGEMENT", default=bool(DEBUG), cast=bool) SHIPPING_RULES = ( importlib.util.find_spec("karrio.server.automation") is not None # type:ignore ) @@ -219,7 +215,7 @@ ) ADVANCED_ANALYTICS = ( importlib.util.find_spec("karrio.server.analytics") is not None # type:ignore -) and config("ADVANCED_ANALYTICS", default=(True if DEBUG else False), cast=bool) +) and config("ADVANCED_ANALYTICS", default=bool(DEBUG), cast=bool) # Feature flags @@ -288,11 +284,12 @@ *KARRIO_APPS, *BASE_APPS, "rest_framework", + "rest_framework_simplejwt.token_blacklist", "django_email_verification", "rest_framework_tracking", "drf_spectacular", "constance.backends.database", - "huey.contrib.djhuey", + # "huey.contrib.djhuey" — moved to karrio.server.settings.huey (auto-discovered) "corsheaders", "oauth2_provider", *OTP_APPS, @@ -314,6 +311,10 @@ "karrio.server.core.middleware.SessionContext", ] +# Allow large GraphQL mutations (rate sheet upsert sends all zones + services +# + service_rates in one request; UPS sheets can have 5000+ rows). +DATA_UPLOAD_MAX_MEMORY_SIZE = 20 * 1024 * 1024 # 20 MB (Django default: 2.5 MB) + TEMPLATES = [ { @@ -348,21 +349,38 @@ _DB_NAME = config("DATABASE_NAME", default="db") _DB_ENGINE = "sqlite3" if "sqlite3" in _DB_NAME else "postgresql" -CONN_MAX_AGE = config("DATABASE_CONN_MAX_AGE", default=0, cast=int) -DB_ENGINE = "django.db.backends.{}".format( - config("DATABASE_ENGINE", default=_DB_ENGINE) -) +CONN_MAX_AGE = config("DATABASE_CONN_MAX_AGE", default=60, cast=int) +# Validate connection health before reuse; transparently reconnects if the DB +# dropped an idle connection (e.g. Azure PostgreSQL load-balancer idle timeout). +# Requires CONN_MAX_AGE > 0 to have effect. Added in Django 4.1. +CONN_HEALTH_CHECKS = config("DATABASE_CONN_HEALTH_CHECKS", default=True, cast=bool) +DB_ENGINE = "django.db.backends.{}".format(config("DATABASE_ENGINE", default=_DB_ENGINE)) DB_NAME = ( os.path.join(WORK_DIR, f"{_DB_NAME}{'' if '.sqlite3' in _DB_NAME else '.sqlite3'}") if "sqlite3" in DB_ENGINE else _DB_NAME ) +# Connection pool (psycopg 3) — required under ASGI. Django's CONN_MAX_AGE and +# CONN_HEALTH_CHECKS fire on the request_started/request_finished signals on the +# main async coroutine, but sync views run under sync_to_async in a +# ThreadPoolExecutor whose thread-local connection cache is never aged out or +# health-checked. The process-wide pool serves both paths and uses pool-level +# health checks on checkout to fail fast on sockets the network silently dropped. +# See GH #508. +DB_POOL_ENABLED = config("DATABASE_POOL_ENABLED", default="postgresql" in DB_ENGINE, cast=bool) +DB_POOL_MIN_SIZE = config("DATABASE_POOL_MIN_SIZE", default=4, cast=int) +DB_POOL_MAX_SIZE = config("DATABASE_POOL_MAX_SIZE", default=20, cast=int) +DB_POOL_MAX_LIFETIME = config("DATABASE_POOL_MAX_LIFETIME", default=3600, cast=int) +DB_POOL_MAX_IDLE = config("DATABASE_POOL_MAX_IDLE", default=600, cast=int) +DB_POOL_TIMEOUT = config("DATABASE_POOL_TIMEOUT", default=10, cast=int) + DATABASES = { "default": { "NAME": DB_NAME, "ENGINE": DB_ENGINE, "CONN_MAX_AGE": CONN_MAX_AGE, + "CONN_HEALTH_CHECKS": CONN_HEALTH_CHECKS, "PORT": config("DATABASE_PORT", default="5432"), "HOST": config("DATABASE_HOST", default="localhost"), "USER": config("DATABASE_USERNAME", default="postgres"), @@ -376,12 +394,68 @@ TEST_RUNNER = "karrio.server.test_runner.KarrioTestRunner" +# Password hashing. PBKDF2 preferred; Django auto-upgrades legacy hashes to it +# on successful login. MD5 kept only as a trailing fallback so accounts hashed +# while prod ran MD5-only (#926) still verify and migrate forward — drop it once +# they have. +PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.PBKDF2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", + "django.contrib.auth.hashers.Argon2PasswordHasher", + "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", + "django.contrib.auth.hashers.ScryptPasswordHasher", + "django.contrib.auth.hashers.MD5PasswordHasher", # legacy fallback — remove after #926 migration window +] + +# Test suite only: MD5 first for speed. Gated on an exact argv token so it can +# never match a prod process — the old `"karrio" in argv[0]` matched everything +# under /karrio/venv, which was the #926 root cause. +if "test" in _sys.argv: + PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher", *PASSWORD_HASHERS] + if config("DATABASE_URL", default=None): db_from_env = dj_database_url.config( - conn_max_age=config("DATABASE_CONN_MAX_AGE", default=0, cast=int) + conn_max_age=config("DATABASE_CONN_MAX_AGE", default=60, cast=int), + conn_health_checks=config("DATABASE_CONN_HEALTH_CHECKS", default=True, cast=bool), ) DATABASES["default"].update(db_from_env) +# Postgres-only connection hardening (libpq TCP keepalives + psycopg3 pool). +# Keepalives make the OS fail stale sockets fast when the network path silently +# drops TCP (e.g. Azure Private Endpoint ~4 min TCP idle timeout) so psycopg +# reconnects instead of reusing a dead socket. The pool bounds backend fan-out +# per process and health-checks connections on checkout. See GH #508. +if "postgresql" in DATABASES["default"]["ENGINE"]: + _db_options = DATABASES["default"].setdefault("OPTIONS", {}) + _db_options.setdefault("keepalives", 1) + _db_options.setdefault("keepalives_idle", 60) + _db_options.setdefault("keepalives_interval", 10) + _db_options.setdefault("keepalives_count", 3) + + if DB_POOL_ENABLED: + try: + import psycopg_pool # noqa: F401 + + # Django's postgresql backend already passes + # check=ConnectionPool.check_connection when constructing the pool; + # supplying it again here raises TypeError (multiple values for 'check'). + _db_options["pool"] = { + "min_size": DB_POOL_MIN_SIZE, + "max_size": DB_POOL_MAX_SIZE, + "max_lifetime": DB_POOL_MAX_LIFETIME, + "max_idle": DB_POOL_MAX_IDLE, + "timeout": DB_POOL_TIMEOUT, + } + # Django requires persistent-connection cache disabled when a pool + # is configured; the pool owns connection lifetime now. + DATABASES["default"]["CONN_MAX_AGE"] = 0 + DATABASES["default"]["CONN_HEALTH_CHECKS"] = False + except ImportError: + # psycopg3 / psycopg-pool not installed (e.g. psycopg2 fallback). + # Keepalives alone still cover the ASGI stale-socket case, just + # without per-process pool bounds. + pass + # Configure workers database for SQLite storage when Redis is not available if not config("REDIS_URL", default=None) and not config("REDIS_HOST", default=None): _WORKER_DB_DIR = config("WORKER_DB_DIR", default=WORK_DIR) @@ -448,9 +522,7 @@ # https://docs.djangoproject.com/en/3.0/howto/static-files/ STATIC_HOST = config("CDN_STATIC_HOST", default="") if not DEBUG else "" -STATIC_URL = STATIC_HOST + config( - "STATIC_URL", default=f"{BASE_PATH}/static/".replace("//", "/") -) +STATIC_URL = STATIC_HOST + config("STATIC_URL", default=f"{BASE_PATH}/static/".replace("//", "/")) STATIC_ROOT = config("STATIC_ROOT_DIR", default=(BASE_DIR / "server" / "staticfiles")) STATICFILES_DIRS = [ @@ -512,14 +584,10 @@ # JWT config SIMPLE_JWT = { - "ACCESS_TOKEN_LIFETIME": timedelta( - config("JWT_ACCESS_EXPIRY", default="30", cast=int) - ), - "REFRESH_TOKEN_LIFETIME": timedelta( - config("JWT_REFRESH_EXPIRY", default="3", cast=int) - ), + "ACCESS_TOKEN_LIFETIME": timedelta(config("JWT_ACCESS_EXPIRY", default="30", cast=int)), + "REFRESH_TOKEN_LIFETIME": timedelta(config("JWT_REFRESH_EXPIRY", default="3", cast=int)), "ROTATE_REFRESH_TOKENS": False, - "BLACKLIST_AFTER_ROTATION": False, + "BLACKLIST_AFTER_ROTATION": True, "UPDATE_LAST_LOGIN": True, "ALGORITHM": "HS256", "SIGNING_KEY": SECRET_KEY, @@ -541,10 +609,9 @@ # JWT Cookie settings for HTTP-only cookie authentication JWT_AUTH_COOKIE = config("JWT_AUTH_COOKIE", default="karrio_access_token") JWT_REFRESH_COOKIE = config("JWT_REFRESH_COOKIE", default="karrio_refresh_token") -JWT_AUTH_COOKIE_SECURE = config( - "JWT_AUTH_COOKIE_SECURE", default=USE_HTTPS, cast=bool -) +JWT_AUTH_COOKIE_SECURE = config("JWT_AUTH_COOKIE_SECURE", default=USE_HTTPS, cast=bool) JWT_AUTH_COOKIE_SAMESITE = config("JWT_AUTH_COOKIE_SAMESITE", default="Lax") +JWT_AUTH_COOKIE_DOMAIN = config("JWT_AUTH_COOKIE_DOMAIN", default=None) JWT_AUTH_COOKIE_PATH = config("JWT_AUTH_COOKIE_PATH", default="/") # OAuth2 config @@ -582,9 +649,7 @@ "OAUTH2_TOKEN_URL": "/oauth/token/", "OAUTH2_REFRESH_URL": None, "OAUTH2_SCOPES": OAUTH2_PROVIDER["SCOPES"], - "AUTHENTICATION_WHITELIST": [ - _ for _ in AUTHENTICATION_CLASSES if "Session" not in _ - ], + "AUTHENTICATION_WHITELIST": [_ for _ in AUTHENTICATION_CLASSES if "Session" not in _], "POSTPROCESSING_HOOKS": [ "karrio.server.openapi.custom_postprocessing_hook", ], @@ -656,6 +721,9 @@ "handlers": ["console"], "level": "INFO", }, + "loggers": { + "azure.servicebus._pyamqp": {"level": "WARNING"}, + }, } else: # Traditional Django logging configuration @@ -711,11 +779,10 @@ }, } - # Initialize Loguru if enabled if USE_LOGURU: try: - from karrio.server.core.logging import setup_django_loguru, logger + from karrio.server.core.logging import setup_django_loguru setup_django_loguru( level=LOG_LEVEL, @@ -725,6 +792,6 @@ ) except ImportError as e: # Note: Using print here as Loguru failed to load - print(f"Warning: Failed to initialize Loguru: {e}") - print("Falling back to standard Django logging") + print(f"Warning: Failed to initialize Loguru: {e}") # noqa: T201 + print("Falling back to standard Django logging") # noqa: T201 USE_LOGURU = False diff --git a/apps/api/karrio/server/settings/cache.py b/apps/api/karrio/server/settings/cache.py index c593f7b2ab..8809e79d95 100644 --- a/apps/api/karrio/server/settings/cache.py +++ b/apps/api/karrio/server/settings/cache.py @@ -1,11 +1,11 @@ # type: ignore -import sys +# ruff: noqa: F403, F405 import socket +import sys + from decouple import config +from karrio.server.settings.apm import HEALTH_CHECK_CHECKS from karrio.server.settings.base import * -from karrio.server.settings.apm import HEALTH_CHECK_APPS, HEALTH_CHECK_CHECKS -from karrio.server.core.logging import logger - CACHE_TTL = 60 * 15 @@ -30,8 +30,7 @@ # Parse REDIS_URL or construct from individual parameters if REDIS_URL is not None: - from urllib.parse import urlparse, urlunparse - import re + from urllib.parse import urlparse parsed = urlparse(REDIS_URL) @@ -61,18 +60,14 @@ REDIS_DB = config("REDIS_CACHE_DB", default="0") REDIS_AUTH = f"{REDIS_USERNAME}:{REDIS_PASSWORD}@" if REDIS_PASSWORD else "" REDIS_SCHEME = "rediss" if REDIS_SSL else "redis" - REDIS_CONNECTION_URL = ( - f'{REDIS_SCHEME}://{REDIS_AUTH}{REDIS_HOST}:{REDIS_PORT or "6379"}/{REDIS_DB}' - ) + REDIS_CONNECTION_URL = f"{REDIS_SCHEME}://{REDIS_AUTH}{REDIS_HOST}:{REDIS_PORT or '6379'}/{REDIS_DB}" # Configure Django cache if Redis is available and not in worker mode if REDIS_HOST is not None and not SKIP_DEFAULT_CACHE: # Configure connection pool with max_connections to prevent exhaustion # Default: 50 connections per process (2 Gunicorn workers = 100 total) # Azure Redis Basic: 256 max connections total - REDIS_CACHE_MAX_CONNECTIONS = config( - "REDIS_CACHE_MAX_CONNECTIONS", default=50, cast=int - ) + REDIS_CACHE_MAX_CONNECTIONS = config("REDIS_CACHE_MAX_CONNECTIONS", default=50, cast=int) pool_kwargs = {"max_connections": REDIS_CACHE_MAX_CONNECTIONS} if REDIS_SSL: @@ -84,11 +79,7 @@ "LOCATION": REDIS_CONNECTION_URL, "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", - **( - {"CONNECTION_POOL_KWARGS": {"ssl_cert_reqs": None}} - if REDIS_SSL - else {} - ), + **({"CONNECTION_POOL_KWARGS": {"ssl_cert_reqs": None}} if REDIS_SSL else {}), }, "KEY_PREFIX": REDIS_PREFIX, } @@ -103,14 +94,12 @@ import redis.asyncio as aioredis _redis_check_client = aioredis.Redis.from_url(REDIS_CONNECTION_URL) - HEALTH_CHECK_CHECKS.append( - ("health_check.contrib.redis.Redis", {"client": _redis_check_client}) - ) + HEALTH_CHECK_CHECKS.append(("health_check.contrib.redis.Redis", {"client": _redis_check_client})) - print(f"Redis cache connection initialized") + print("Redis cache connection initialized") # noqa: T201 elif SKIP_DEFAULT_CACHE: - print( + print( # noqa: T201 "Skipping default Redis cache configuration (worker mode - only HUEY Redis needed)" ) else: diff --git a/apps/api/karrio/server/settings/constance.py b/apps/api/karrio/server/settings/constance.py index bb2af7e5f9..7f7f7123dd 100644 --- a/apps/api/karrio/server/settings/constance.py +++ b/apps/api/karrio/server/settings/constance.py @@ -1,17 +1,18 @@ """Dynamic configuration editable on runtime powered by django-constance.""" -from decouple import config +import importlib.util + import karrio.references as ref import karrio.server.settings.base as base +from decouple import config from karrio.server.settings.email import ( - EMAIL_USE_TLS, - EMAIL_HOST_USER, - EMAIL_HOST_PASSWORD, + EMAIL_FROM_ADDRESS, EMAIL_HOST, + EMAIL_HOST_PASSWORD, + EMAIL_HOST_USER, EMAIL_PORT, - EMAIL_FROM_ADDRESS, + EMAIL_USE_TLS, ) -import importlib.util CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend" CONSTANCE_DATABASE_PREFIX = "constance:core:" @@ -19,9 +20,7 @@ DATA_ARCHIVING_SCHEDULE = config("DATA_ARCHIVING_SCHEDULE", default=168, cast=int) GOOGLE_CLOUD_API_KEY = config("GOOGLE_CLOUD_API_KEY", default="") -CANADAPOST_ADDRESS_COMPLETE_API_KEY = config( - "CANADAPOST_ADDRESS_COMPLETE_API_KEY", default="" -) +CANADAPOST_ADDRESS_COMPLETE_API_KEY = config("CANADAPOST_ADDRESS_COMPLETE_API_KEY", default="") # data retention env in days ORDER_DATA_RETENTION = config("ORDER_DATA_RETENTION", default=183, cast=int) @@ -32,11 +31,171 @@ # background tracking max active age TRACKER_MAX_ACTIVE_DAYS = config("TRACKER_MAX_ACTIVE_DAYS", default=90, cast=int) -# registry config -ENABLE_ALL_PLUGINS_BY_DEFAULT = config( - "ENABLE_ALL_PLUGINS_BY_DEFAULT", default=True if base.DEBUG else False, cast=bool +# paperless trade (ETD): the system-wide default commercial-invoice template +# BODY (HTML/Jinja), rendered inline when a merchant enables paperless_trade +# without supplying their own document or per-shipment invoice_template (e.g. the +# post_upload flow on GLS). It is the template *body*, not a slug — so a default +# always resolves without depending on a tenant-owned DocumentTemplate row. The +# render context exposes `shipment`, `line_items`, `carrier`, `orders`, plus +# `units`/`utils`/`lib`. Editable at runtime via Constance; clear it to opt out +# (the post_upload job then records an actionable `skipped` reason — D15). +DEFAULT_COMMERCIAL_INVOICE_TEMPLATE = """ + + + + Commercial Invoice + + + + {% set c = shipment.customs or {} %} + {% set co = c.options or {} %} + {% set items = c.commodities or [] %} + {% set ns = namespace(sub=0.0, cur='', wt=0.0, wu='') %} + {% for it in items %} + {% set q = (it.quantity or 1)|float %} + {% set u = (it.value_amount or 0)|float %} + {% set ns.sub = ns.sub + (q * u) %} + {% set ns.cur = it.value_currency or ns.cur %} + {% endfor %} + {% for p in (shipment.parcels or []) %} + {% set ns.wt = ns.wt + (p.weight or 0)|float %} + {% set ns.wu = p.weight_unit or ns.wu %} + {% endfor %} + {% set currency = ns.cur or (c.duty.currency if c.duty else '') or (shipment.options.currency if shipment.options else '') %} + +
+

COMMERCIAL INVOICE

+
+ {% if shipment.tracking_number %}Shipment ID: {{ shipment.tracking_number }}
{% endif %} + {% if shipment.created_at %}{{ shipment.created_at[:10] }}{% endif %} +
+
+ + + + +
+
Exporter / From
+
{{ shipment.shipper.company_name or shipment.shipper.person_name or '-' }}
+ {% if shipment.shipper.company_name and shipment.shipper.person_name %}
{{ shipment.shipper.person_name }}
{% endif %} +
{{ shipment.shipper.address_line1 }}{% if shipment.shipper.address_line2 %}, {{ shipment.shipper.address_line2 }}{% endif %}
+
{{ shipment.shipper.postal_code }} {{ shipment.shipper.city }}{% if shipment.shipper.state_code %}, {{ shipment.shipper.state_code }}{% endif %}
+
{{ shipment.shipper.country_code }}
+ {% if shipment.shipper.phone_number %}
Tel: {{ shipment.shipper.phone_number }}
{% endif %} + {% if shipment.shipper.email %}
Email: {{ shipment.shipper.email }}
{% endif %} + {% if shipment.shipper.federal_tax_id %}
Tax ID: {{ shipment.shipper.federal_tax_id }}
{% endif %} + {% if co.vat_registration_number %}
VAT No: {{ co.vat_registration_number }}
{% endif %} + {% if co.eori_number %}
EORI No: {{ co.eori_number }}
{% endif %} +
+
Consignee / Ship To
+
{{ shipment.recipient.company_name or shipment.recipient.person_name or '-' }}
+ {% if shipment.recipient.company_name and shipment.recipient.person_name %}
{{ shipment.recipient.person_name }}
{% endif %} +
{{ shipment.recipient.address_line1 }}{% if shipment.recipient.address_line2 %}, {{ shipment.recipient.address_line2 }}{% endif %}
+
{{ shipment.recipient.postal_code }} {{ shipment.recipient.city }}{% if shipment.recipient.state_code %}, {{ shipment.recipient.state_code }}{% endif %}
+
{{ shipment.recipient.country_code }}
+ {% if shipment.recipient.phone_number %}
Tel: {{ shipment.recipient.phone_number }}
{% endif %} + {% if shipment.recipient.email %}
Email: {{ shipment.recipient.email }}
{% endif %} + {% if shipment.recipient.federal_tax_id %}
Tax ID / VAT No: {{ shipment.recipient.federal_tax_id }}
{% endif %} +
+ +
+ {% if c.invoice %}Invoice No: {{ c.invoice }}{% endif %} + {% if c.invoice_date %}Invoice Date: {{ c.invoice_date }}{% endif %} + {% if shipment.reference %}Reference: {{ shipment.reference }}{% endif %} + {% if c.incoterm %}Incoterm: {{ c.incoterm }}{% endif %} + {% if c.content_type %}Reason for Export: {{ c.content_type|replace('_', ' ')|title }}{% endif %} + {% if shipment.service %}Service: {{ shipment.service|replace('_', ' ')|title }}{% endif %} +
+ + + + + + + + + + + + + + + {% for it in items %} + {% set q = (it.quantity or 1)|float %} + {% set u = (it.value_amount or 0)|float %} + + + + + + + + + + {% else %} + + {% endfor %} + +
#Description of GoodsHS CodeOriginQtyUnit ValueTotal Value
{{ loop.index }}{{ it.description or it.title or '-' }}{% if it.sku %}
SKU: {{ it.sku }}
{% endif %}
{{ it.hs_code or '-' }}{{ it.origin_country or shipment.shipper.country_code or '-' }}{{ q|int }}{{ "%.2f"|format(u) }} {{ it.value_currency or currency }}{{ "%.2f"|format(q * u) }} {{ it.value_currency or currency }}
No commodities declared
+ +
+
+ Declaration: I/We hereby certify that the information on this invoice is true and correct and that the contents of this shipment are as stated above. + {% if c.signer %}
Signed by: {{ c.signer }}
{% endif %} +
Signature & Date
+
+ + + {% if c.duty and c.duty.declared_value %}{% endif %} + + + + {% if ns.wt %}{% endif %} +
Subtotal{{ "%.2f"|format(ns.sub) }} {{ currency }}
Declared Value{{ "%.2f"|format(c.duty.declared_value|float) }} {{ c.duty.currency or currency }}
Total{{ "%.2f"|format(ns.sub) }} {{ currency }}
Currency{{ currency or '-' }}
Total Packages{{ (shipment.parcels or [])|length or 1 }}
Total Weight{{ "%.2f"|format(ns.wt) }} {{ ns.wu }}
+
+ + +""" + +PAPERLESS_DEFAULT_INVOICE_TEMPLATE = config( + "PAPERLESS_DEFAULT_INVOICE_TEMPLATE", default=DEFAULT_COMMERCIAL_INVOICE_TEMPLATE ) +# registry config +ENABLE_ALL_PLUGINS_BY_DEFAULT = config("ENABLE_ALL_PLUGINS_BY_DEFAULT", default=bool(base.DEBUG), cast=bool) + # Feature flags config — always present with False default when module is absent FEATURE_FLAGS_CONFIG = { "AUDIT_LOGGING": ( @@ -114,6 +273,11 @@ "Persist SDK tracing", bool, ), + "CLOSED_BETA_ENABLED": ( + config("CLOSED_BETA_ENABLED", default=False, cast=bool), + "Enable closed beta mode — only invited KAccounts can onboard", + bool, + ), } # Update fieldsets to only include existing feature flags @@ -205,6 +369,14 @@ "Maximum age (in days) for active background tracking. Trackers created more than this many days ago will be retired from polling and their status set to 'unknown'.", int, ), + "PAPERLESS_DEFAULT_INVOICE_TEMPLATE": ( + PAPERLESS_DEFAULT_INVOICE_TEMPLATE, + "Default commercial-invoice template BODY (HTML/Jinja) rendered for " + "paperless-trade (ETD) shipments when the merchant supplies neither a " + "document nor a per-shipment invoice_template. Render context exposes " + "`shipment`, `line_items`, `carrier`, `orders`. Clear to opt out.", + str, + ), **FEATURE_FLAGS_CONFIG, **PLUGIN_REGISTRY, **PLUGIN_SYSTEM_CONFIG, @@ -230,14 +402,9 @@ "API_LOGS_DATA_RETENTION", "TRACKER_MAX_ACTIVE_DAYS", ), + "Paperless Trade": ("PAPERLESS_DEFAULT_INVOICE_TEMPLATE",), "Feature Flags": tuple(FEATURE_FLAGS_FIELDSET), "Registry Config": ("ENABLE_ALL_PLUGINS_BY_DEFAULT",), - "Registry Plugins": tuple( - [ - k - for k in PLUGIN_REGISTRY.keys() - if not k in ("ENABLE_ALL_PLUGINS_BY_DEFAULT",) - ] - ), + "Registry Plugins": tuple([k for k in PLUGIN_REGISTRY if k not in ("ENABLE_ALL_PLUGINS_BY_DEFAULT",)]), **PLUGIN_SYSTEM_CONFIG_FIELDSETS, } diff --git a/apps/api/karrio/server/settings/debug.py b/apps/api/karrio/server/settings/debug.py index 98cb90d4b2..2ada8bfa8c 100644 --- a/apps/api/karrio/server/settings/debug.py +++ b/apps/api/karrio/server/settings/debug.py @@ -1,14 +1,13 @@ # type: ignore +# ruff: noqa: F403, F405, S104 +import importlib import sys + from karrio.server.settings.base import * TESTING = sys.argv[1:2] == ["test"] -if ( - DEBUG is True - and TESTING is False - and importlib.util.find_spec("debug_toolbar") is not None -): +if DEBUG is True and TESTING is False and importlib.util.find_spec("debug_toolbar") is not None: INTERNAL_IPS = [ "127.0.0.1", "0.0.0.0", diff --git a/apps/api/karrio/server/settings/email.py b/apps/api/karrio/server/settings/email.py index 266b749687..79aea8d224 100644 --- a/apps/api/karrio/server/settings/email.py +++ b/apps/api/karrio/server/settings/email.py @@ -9,6 +9,15 @@ EMAIL_PORT = config("EMAIL_PORT", default=587, cast=int) EMAIL_FROM_ADDRESS = config("EMAIL_FROM_ADDRESS", default="noreply@example.com") +# django-otp's email plugin reads `settings.OTP_EMAIL_SENDER` for the From +# header, NOT `EMAIL_FROM_ADDRESS`. Without this, OTP challenge emails go out +# as `webmaster@localhost` (Django's DEFAULT_FROM_EMAIL fallback) and get +# rejected by strict SMTP relays (e.g. SendGrid 550 — sender not verified). +# This holds the import-time value; the constance signal handler in +# `karrio.server.core.signals.update_settings` keeps it in sync with +# `EMAIL_FROM_ADDRESS` whenever an admin updates the Email Config page. +OTP_EMAIL_SENDER = config("OTP_EMAIL_SENDER", default="") or EMAIL_FROM_ADDRESS + EMAIL_SERVER = EMAIL_HOST EMAIL_ADDRESS = EMAIL_HOST_USER EMAIL_PASSWORD = EMAIL_HOST_PASSWORD @@ -26,7 +35,7 @@ def user_verified_callback(user): from karrio.server.conf import settings - if settings.ALLOW_ADMIN_APPROVED_SIGNUP == False: + if not settings.ALLOW_ADMIN_APPROVED_SIGNUP: user.is_active = True user.save() diff --git a/apps/api/karrio/server/settings/workers.py b/apps/api/karrio/server/settings/workers.py index 9e84f0913a..ca81baf1c5 100644 --- a/apps/api/karrio/server/settings/workers.py +++ b/apps/api/karrio/server/settings/workers.py @@ -1,12 +1,7 @@ # type: ignore -import os import sys -import socket -import huey -import redis -import decouple -from karrio.server.settings import base as settings +import decouple # Karrio Server Background jobs interval config DEFAULT_SCHEDULER_RUN_INTERVAL = 3600 # value is seconds. so 3600 seconds = 1 Hour @@ -23,129 +18,26 @@ # Check if worker is running in detached mode (separate from API server) DETACHED_WORKER = decouple.config("DETACHED_WORKER", default=False, cast=bool) -WORKER_IMMEDIATE_MODE = decouple.config( - "WORKER_IMMEDIATE_MODE", default=False, cast=bool -) - -# Detect if running as a worker process (via run_huey command) -IS_WORKER_PROCESS = any("run_huey" in arg for arg in sys.argv) - -# Always configure Huey for both API servers and workers -# API servers need to enqueue tasks even when DETACHED_WORKER=True -# Only the worker pods will consume tasks -# Redis configuration - REDIS_URL takes precedence and supersedes granular env vars -REDIS_URL = decouple.config("REDIS_URL", default=None) - -# Parse REDIS_URL or construct from individual parameters -if REDIS_URL is not None: - from urllib.parse import urlparse - - parsed = urlparse(REDIS_URL) - - # Extract values from REDIS_URL (these supersede granular env vars) - REDIS_HOST = parsed.hostname - REDIS_PORT = parsed.port or 10000 # Azure Managed Redis default port - REDIS_USERNAME = parsed.username or "default" - REDIS_PASSWORD = parsed.password - - # Determine SSL from URL scheme (rediss:// means SSL is enabled) - REDIS_SCHEME = ( - parsed.scheme if parsed.scheme in ("redis", "rediss") else "redis" - ) - REDIS_SSL = REDIS_SCHEME == "rediss" - -else: - # Fall back to individual parameters - REDIS_HOST = decouple.config("REDIS_HOST", default=None) - REDIS_PORT = decouple.config("REDIS_PORT", default=None) - REDIS_PASSWORD = decouple.config("REDIS_PASSWORD", default=None) - REDIS_USERNAME = decouple.config("REDIS_USERNAME", default="default") - REDIS_SSL = decouple.config("REDIS_SSL", default=False, cast=bool) - -# Configure HUEY based on available Redis configuration -if REDIS_HOST is not None: - # Calculate max connections based on environment - # Each worker replica needs: (workers_per_replica + 1 scheduler) connections - # Formula: (worker_replicas * (threads_per_worker + 1)) + api_connections + buffer - # Example: 100 connections = (5 replicas * (8 workers + 1 scheduler)) + 40 API + 15 buffer - REDIS_MAX_CONNECTIONS = decouple.config( - "REDIS_MAX_CONNECTIONS", default=100, cast=int - ) - - # Connection pool configuration with timeouts - # Use BlockingConnectionPool to wait for available connections instead of failing immediately - pool_kwargs = { - "host": REDIS_HOST, - "port": REDIS_PORT, - "max_connections": REDIS_MAX_CONNECTIONS, - "timeout": 20, # Wait up to 20 seconds for an available connection - # Timeout settings to prevent hung connections - "socket_timeout": 10, # Command execution timeout (seconds) - "socket_connect_timeout": 10, # Connection establishment timeout (seconds) - # Keep connections alive to prevent closure by firewalls/load balancers - "socket_keepalive": True, - # Retry on transient failures - "retry_on_timeout": True, - } - - # Add TCP keepalive options if available (Linux/Unix only) - try: - pool_kwargs["socket_keepalive_options"] = { - socket.TCP_KEEPIDLE: 60, # Start keepalive after 60s idle - socket.TCP_KEEPINTVL: 10, # Keepalive interval - socket.TCP_KEEPCNT: 3, # Keepalive probes before timeout - } - except AttributeError: - # TCP keepalive constants not available on this platform - pass - - # Add authentication if provided - if REDIS_PASSWORD: - pool_kwargs["password"] = REDIS_PASSWORD - if REDIS_USERNAME: - pool_kwargs["username"] = REDIS_USERNAME - - # Add SSL/TLS configuration if enabled - if REDIS_SSL: - # Use SSLConnection class for SSL/TLS connections - pool_kwargs["connection_class"] = redis.SSLConnection - pool_kwargs["ssl_cert_reqs"] = None # For Azure Redis compatibility +WORKER_IMMEDIATE_MODE = decouple.config("WORKER_IMMEDIATE_MODE", default=False, cast=bool) - # Use BlockingConnectionPool to wait for connections instead of raising errors immediately - pool = redis.BlockingConnectionPool(**pool_kwargs) +# Detect if running as a worker process (via run_huey or run-servicebus-worker) +IS_WORKER_PROCESS = any(cmd in arg for arg in sys.argv for cmd in ("run_huey", "run-servicebus-worker")) - HUEY = huey.RedisHuey( - "default", - connection_pool=pool, - **({"immediate": WORKER_IMMEDIATE_MODE} if WORKER_IMMEDIATE_MODE else {}), - ) +# Task backend selection: "huey" (default), "servicebus", or "immediate" +# - "huey": Redis-backed task queue (existing behavior, backward-compatible) +# - "servicebus": Azure Service Bus with KEDA autoscaling +# - "immediate": synchronous execution (for local dev without Redis) +TASK_BACKEND = decouple.config("TASK_BACKEND", default="huey") -else: - # No Redis configured, use SQLite - WORKER_DB_DIR = decouple.config("WORKER_DB_DIR", default=settings.WORK_DIR) - WORKER_DB_FILE_NAME = os.path.join(WORKER_DB_DIR, "tasks.sqlite3") - settings.DATABASES["workers"] = { - "NAME": WORKER_DB_FILE_NAME, - "ENGINE": "django.db.backends.sqlite3", - } +# ───────────────────────────────────────────────────────────────────────────── +# Task Backend Initialization +# ───────────────────────────────────────────────────────────────────────────── +# ImmediateBackend is set eagerly as a safe fallback. When TASK_BACKEND=huey, +# HueyConfig.ready() overrides it with HueyBackend after Django is fully loaded. +# This ensures tasks work even if the huey module isn't installed. - # SQLite-specific Huey configuration - # WAL mode (Write-Ahead Logging) enables better concurrency for multiple workers - # Increased timeout helps handle lock contention under concurrent access - HUEY = huey.SqliteHuey( - name="default", - filename=WORKER_DB_FILE_NAME, - # Storage-specific kwargs for better concurrent access handling - journal_mode="wal", # Enable Write-Ahead Logging for better concurrency - timeout=30, # Increase timeout to 30s to handle lock contention with multiple workers - cache_mb=16, # Increase cache size for better performance (default: 8MB) - fsync=False, # Disable forced fsync for better performance (default: False) - **({"immediate": WORKER_IMMEDIATE_MODE} if WORKER_IMMEDIATE_MODE else {}), - ) +from karrio.server.core.backends.immediate import ImmediateBackend +from karrio.server.core.task_backend import set_backend - # When DETACHED_WORKER is True, the entrypoint only runs Gunicorn — no worker - # process will ever consume from the SQLite queue. Auto-enable immediate mode - # so tasks execute synchronously in the web process. - if DETACHED_WORKER and not IS_WORKER_PROCESS and not WORKER_IMMEDIATE_MODE: - HUEY.immediate = True +set_backend(ImmediateBackend()) diff --git a/apps/api/karrio/server/urls/__init__.py b/apps/api/karrio/server/urls/__init__.py index 1c999a986b..466480994b 100644 --- a/apps/api/karrio/server/urls/__init__.py +++ b/apps/api/karrio/server/urls/__init__.py @@ -14,26 +14,29 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ +from contextlib import suppress + +from constance.admin import Config from django.conf import settings -from django.urls import include, path from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns -from constance.admin import Config - +from django.urls import include, path +from two_factor.admin import AdminSiteOTPRequired +from two_factor.urls import urlpatterns as two_factor_urlpatterns BASE_PATH = getattr(settings, "BASE_PATH", "") +admin.site.__class__ = AdminSiteOTPRequired + admin.site.site_header = "Administration" admin.site.index_title = "Administration" admin.site.site_url = f"/{BASE_PATH}" -try: +with suppress(Exception): if getattr(settings, "MULTI_TENANTS", False): admin.site.unregister([Config]) admin.autodiscover() -except: - pass urlpatterns = [ path( @@ -46,8 +49,10 @@ for (subpath, urls, namespace) in settings.NAMESPACED_URLS ], path("", include("karrio.server.urls.jwt")), + path("", include("karrio.server.urls.mfa")), path("", include("karrio.server.urls.tokens")), path("", include("karrio.server.user.urls")), + path("", include(two_factor_urlpatterns)), *[path("", include(urls)) for urls in settings.KARRIO_URLS], path("admin/", admin.site.urls, name="app_admin"), *staticfiles_urlpatterns(), diff --git a/apps/api/karrio/server/urls/jwt.py b/apps/api/karrio/server/urls/jwt.py index 361694d93a..14013cff24 100644 --- a/apps/api/karrio/server/urls/jwt.py +++ b/apps/api/karrio/server/urls/jwt.py @@ -1,30 +1,42 @@ -from django.urls import path +from contextlib import suppress + +import karrio.server.openapi as openapi +from django.conf import settings from django.contrib.auth import get_user_model +from django.urls import path from django.utils.translation import gettext_lazy as _ -from django.conf import settings -from rest_framework import serializers, exceptions, status -from rest_framework.response import Response +from rest_framework import exceptions, serializers, status from rest_framework.permissions import AllowAny -from rest_framework_simplejwt import views as jwt_views, serializers as jwt +from rest_framework.response import Response +from rest_framework_simplejwt import serializers as jwt +from rest_framework_simplejwt import views as jwt_views from two_factor.utils import default_device -import karrio.server.openapi as openapi - ENDPOINT_ID = "&&" # This endpoint id is used to make operation ids unique make sure not to duplicate User = get_user_model() # --- Cookie helpers (shared by all JWT views) --- + def get_cookie_config(include_max_age=True): """Build cookie configuration from Django settings.""" - config = dict( - access_cookie_name=getattr(settings, "JWT_AUTH_COOKIE", "karrio_access_token"), - refresh_cookie_name=getattr(settings, "JWT_REFRESH_COOKIE", "karrio_refresh_token"), + domain = getattr(settings, "JWT_AUTH_COOKIE_DOMAIN", None) + + cookie_kwargs = dict( + httponly=True, secure=getattr(settings, "JWT_AUTH_COOKIE_SECURE", getattr(settings, "USE_HTTPS", False)), samesite=getattr(settings, "JWT_AUTH_COOKIE_SAMESITE", "Lax"), path=getattr(settings, "JWT_AUTH_COOKIE_PATH", "/"), ) + if domain: + cookie_kwargs["domain"] = domain + + config = dict( + access_cookie_name=getattr(settings, "JWT_AUTH_COOKIE", "karrio_access_token"), + refresh_cookie_name=getattr(settings, "JWT_REFRESH_COOKIE", "karrio_refresh_token"), + cookie_kwargs=cookie_kwargs, + ) if include_max_age: jwt_config = getattr(settings, "SIMPLE_JWT", {}) @@ -46,19 +58,13 @@ def set_auth_cookies(response, access_token, refresh_token): config["access_cookie_name"], access_token, max_age=config["access_max_age"], - httponly=True, - secure=config["secure"], - samesite=config["samesite"], - path=config["path"], + **config["cookie_kwargs"], ) response.set_cookie( config["refresh_cookie_name"], refresh_token, max_age=config["refresh_max_age"], - httponly=True, - secure=config["secure"], - samesite=config["samesite"], - path=config["path"], + **config["cookie_kwargs"], ) @@ -71,33 +77,32 @@ def clear_auth_cookies(response): cookie_name, "", max_age=0, - httponly=True, - secure=config["secure"], - samesite=config["samesite"], - path=config["path"], + **config["cookie_kwargs"], ) def get_refresh_token(request): """Get refresh token from cookie, falling back to request body.""" cookie_name = getattr(settings, "JWT_REFRESH_COOKIE", "karrio_refresh_token") - refresh_token = ( - request.COOKIES.get(cookie_name) - or request.data.get("refresh") - ) + refresh_token = request.COOKIES.get(cookie_name) or request.data.get("refresh") if not refresh_token: - raise exceptions.ValidationError( - {"refresh": _("Refresh token is required.")} - ) + raise exceptions.ValidationError({"refresh": _("Refresh token is required.")}) return refresh_token -def _build_token_response(access_token, refresh_token): - """Build a 201 token pair response with no-cache headers.""" +def _build_token_response(access_token, refresh_token, is_verified: bool = True): + """Build a 201 token pair response with no-cache headers. + + `is_verified` exposes the JWT's two-factor claim in the response body so + clients (e.g. the admin dashboard) can decide whether to prompt for OTP + without having to decode the JWT themselves. Defaults to True so refresh / + verified endpoints — which always issue verified tokens — keep their + existing response shape. + """ response = Response( - {"access": access_token, "refresh": refresh_token}, + {"access": access_token, "refresh": refresh_token, "is_verified": is_verified}, status=status.HTTP_201_CREATED, ) response["Cache-Control"] = "no-store" @@ -107,6 +112,7 @@ def _build_token_response(access_token, refresh_token): # --- Serializers --- + class AccessToken(serializers.Serializer): access = serializers.CharField() @@ -121,7 +127,7 @@ def get_token(cls, user): token = super().get_token(user) # Set is_verified to False if the user has Two Factor enabled and confirmed - token["is_verified"] = False if default_device(user) else True + token["is_verified"] = not default_device(user) return token @@ -145,13 +151,17 @@ def validate(self, attrs): class TokenRefreshSerializer(jwt.TokenRefreshSerializer): def validate(self, attrs: dict): + from rest_framework_simplejwt.exceptions import TokenError + refresh_token = attrs.get("refresh") if not refresh_token: - raise exceptions.ValidationError( - {"refresh": _("Refresh token is required.")} - ) + raise exceptions.ValidationError({"refresh": _("Refresh token is required.")}) - refresh = jwt.RefreshToken(refresh_token) + try: + refresh = jwt.RefreshToken(refresh_token) + except TokenError as e: + # Blacklisted / expired / malformed → 401, not 500. + raise exceptions.AuthenticationFailed(str(e), code="token_not_valid") from e if not refresh["is_verified"]: raise exceptions.AuthenticationFailed( @@ -163,13 +173,9 @@ def validate(self, attrs: dict): if jwt.api_settings.ROTATE_REFRESH_TOKENS: if jwt.api_settings.BLACKLIST_AFTER_ROTATION: - try: + with suppress(AttributeError): # Attempt to blacklist the given refresh token refresh.blacklist() - except AttributeError: - # If blacklist app not installed, `blacklist` method will - # not be present - pass refresh.set_jti() refresh.set_exp() @@ -190,9 +196,7 @@ class VerifiedTokenObtainPairSerializer(jwt.TokenRefreshSerializer): def validate(self, attrs): refresh_token = attrs.get("refresh") if not refresh_token: - raise exceptions.ValidationError( - {"refresh": _("Refresh token is required.")} - ) + raise exceptions.ValidationError({"refresh": _("Refresh token is required.")}) refresh = self.token_class(refresh_token) user = User.objects.get(id=refresh["user_id"]) @@ -202,13 +206,9 @@ def validate(self, attrs): if jwt.api_settings.ROTATE_REFRESH_TOKENS: if jwt.api_settings.BLACKLIST_AFTER_ROTATION: - try: + with suppress(AttributeError): # Attempt to blacklist the given refresh token refresh.blacklist() - except AttributeError: - # If blacklist app not installed, `blacklist` method will - # not be present - pass refresh.set_jti() refresh.set_exp() @@ -229,13 +229,12 @@ def _validate_otp(self, otp_token, user) -> bool: if device.verify_token(otp_token): return True - raise exceptions.ValidationError( - {"otp_token": _("Invalid or Expired OTP token")}, code="otp_invalid" - ) + raise exceptions.ValidationError({"otp_token": _("Invalid or Expired OTP token")}, code="otp_invalid") # --- Views --- + class TokenObtainPair(jwt_views.TokenObtainPairView): serializer_class = TokenObtainPairSerializer @@ -252,7 +251,10 @@ def post(self, *args, **kwargs): serializer.is_valid(raise_exception=True) data = serializer.validated_data - response = _build_token_response(data["access"], data["refresh"]) + # `is_verified` is sourced from the refresh token claim — clients use it + # to decide whether to prompt for an OTP and call /api/token/verified. + is_verified = bool(jwt.RefreshToken(data["refresh"])["is_verified"]) + response = _build_token_response(data["access"], data["refresh"], is_verified) set_auth_cookies(response, data["access"], data["refresh"]) return response @@ -287,7 +289,6 @@ def post(self, *args, **kwargs): class TokenVerify(jwt_views.TokenVerifyView): - @openapi.extend_schema( auth=[], tags=["Auth"], @@ -317,10 +318,12 @@ class VerifiedTokenPair(jwt_views.TokenVerifyView): def post(self, *args, **kwargs): refresh_token = get_refresh_token(self.request) - serializer = self.get_serializer(data={ - "refresh": refresh_token, - "otp_token": self.request.data.get("otp_token"), - }) + serializer = self.get_serializer( + data={ + "refresh": refresh_token, + "otp_token": self.request.data.get("otp_token"), + } + ) serializer.is_valid(raise_exception=True) data = serializer.validated_data @@ -336,6 +339,7 @@ def post(self, *args, **kwargs): class LogoutView(jwt_views.TokenVerifyView): """Logout view that clears HTTP-only auth cookies.""" + permission_classes = [AllowAny] @openapi.extend_schema( @@ -346,7 +350,20 @@ class LogoutView(jwt_views.TokenVerifyView): description="Clear authentication cookies and logout the user. Accessible without authentication.", responses={200: openapi.OpenApiTypes.OBJECT}, ) - def post(self, *args, **kwargs): + def post(self, request, *args, **kwargs): + # Blacklist the refresh token server-side so a stale cookie or replay + # cannot mint new access tokens. Accept token from body (API clients) + # or cookie (browser). Fail open on token errors — logout should always + # succeed from the user's perspective. + from rest_framework_simplejwt.exceptions import TokenError + from rest_framework_simplejwt.tokens import RefreshToken + + refresh_cookie = getattr(settings, "JWT_REFRESH_COOKIE", "karrio_refresh_token") + raw = (request.data.get("refresh") if hasattr(request, "data") else None) or request.COOKIES.get(refresh_cookie) + if raw: + with suppress(TokenError, Exception): + RefreshToken(raw).blacklist() + response = Response( {"detail": "Successfully logged out."}, status=status.HTTP_200_OK, diff --git a/apps/api/karrio/server/urls/mfa.py b/apps/api/karrio/server/urls/mfa.py new file mode 100644 index 0000000000..e1f3faf145 --- /dev/null +++ b/apps/api/karrio/server/urls/mfa.py @@ -0,0 +1,246 @@ +""" +MFA (TOTP) enrollment REST API. + +Login + OTP-challenge already exists at /api/token + /api/token/verified +(see urls/jwt.py). This module only exposes the *enrollment* lifecycle so +non-Django-admin clients (e.g. the JTL admin dashboard) can let staff add / +confirm / remove a TOTP device without going through Django admin. + +Only TOTP is exposed here. Email-based MFA (django_otp.plugins.otp_email) +isn't surfaced on these endpoints by design — the JTL admin flow uses +authenticator apps. The tenant /graphql schema has separate EmailDevice +mutations for end-customer apps that need email MFA; this REST surface +does not. + +Endpoints +--------- + POST /api/mfa/totp/init Get-or-create unconfirmed TOTPDevice; returns + provisioning_uri + secret for QR rendering. + POST /api/mfa/totp/confirm Verify OTP token, mark device confirmed. + POST /api/mfa/totp/disable Delete the user's TOTP device(s). + GET /api/mfa/status Whether the current user has TOTP enrolled. + +Authentication +-------------- +Restricted to JWT (cookie or bearer). Token / Basic / OAuth2 are intentionally +excluded — those credential types don't represent an interactive session and +shouldn't be used to enroll or revoke a second factor. +""" + +import base64 + +import karrio.server.openapi as openapi +from django.utils.translation import gettext_lazy as _ +from django_otp.plugins.otp_totp.models import TOTPDevice +from karrio.server.core.authentication import JWTAuthentication +from rest_framework import exceptions, serializers, status +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +ENDPOINT_ID = "##" # Per-file unique prefix for operation_id (matches jwt.py "&&", tokens.py etc.) + + +# --- Response / request serializers (for OpenAPI) --- + + +class TOTPInitResponse(serializers.Serializer): + provisioning_uri = serializers.CharField( + help_text="otpauth:// URI for QR code rendering or 'Scan with authenticator app' deep link.", + ) + secret = serializers.CharField( + help_text="Base32-encoded shared secret for manual entry into an authenticator app.", + ) + + +class TOTPConfirmInput(serializers.Serializer): + otp_token = serializers.CharField( + required=True, + min_length=6, + max_length=8, + help_text="The 6-digit (or 8-digit) code currently displayed by the authenticator app.", + ) + + +class TOTPDisableInput(serializers.Serializer): + otp_token = serializers.CharField( + required=True, + min_length=6, + max_length=8, + help_text=( + "Fresh OTP code from the authenticator app. Required to disable MFA — " + "an OTP-verified session alone is not sufficient (step-up auth)." + ), + ) + + +class MFAStatusResponse(serializers.Serializer): + enrolled = serializers.BooleanField(help_text="True if any TOTPDevice exists for the user (confirmed or not).") + confirmed = serializers.BooleanField(help_text="True if a confirmed TOTPDevice exists.") + + +class SimpleResultResponse(serializers.Serializer): + confirmed = serializers.BooleanField(required=False) + disabled = serializers.BooleanField(required=False) + + +# --- Views --- + + +class _BaseMFAView(APIView): + """Cookie/JWT auth only — see module docstring rationale.""" + + authentication_classes = [JWTAuthentication] + permission_classes = [IsAuthenticated] + + +class TOTPInitView(_BaseMFAView): + @openapi.extend_schema( + tags=["Auth"], + operation_id=f"{ENDPOINT_ID}init_totp", + summary="Begin TOTP enrollment", + description=( + "Create (or fetch) an unconfirmed TOTP device for the current user and return " + "its provisioning URI. The client renders this URI as a QR code; the user scans " + "it with an authenticator app and submits the first generated code to " + "`/api/mfa/totp/confirm` to complete enrollment." + ), + request=None, + responses={200: TOTPInitResponse()}, + ) + def post(self, request): + user = request.user + + # Refuse re-enrollment if there is already a confirmed device — the + # caller must explicitly disable first. This stops a malicious /init + # call from silently rotating someone's TOTP secret. + if TOTPDevice.objects.filter(user=user, confirmed=True).exists(): + raise exceptions.ValidationError( + _("TOTP is already enrolled. Disable it before re-enrolling."), + code="totp_already_enrolled", + ) + + # `get_or_create` would raise MultipleObjectsReturned if a race or an + # aborted prior enrollment left more than one unconfirmed device for + # this user. Pick the oldest existing one (so retries return the same + # secret) and prune the rest so future init calls stay clean. + unconfirmed = TOTPDevice.objects.filter(user=user, confirmed=False).order_by("pk") + device = unconfirmed.first() + if device is None: + device = TOTPDevice.objects.create(user=user, name="default", confirmed=False) + else: + unconfirmed.exclude(pk=device.pk).delete() + + return Response( + { + "provisioning_uri": device.config_url, + "secret": base64.b32encode(device.bin_key).decode("ascii"), + }, + status=status.HTTP_200_OK, + ) + + +class TOTPConfirmView(_BaseMFAView): + @openapi.extend_schema( + tags=["Auth"], + operation_id=f"{ENDPOINT_ID}confirm_totp", + summary="Confirm TOTP enrollment", + description=( + "Submit the 6-digit code currently shown by the authenticator app. If valid, " + "the device is marked `confirmed=True` and the user is enrolled." + ), + request=TOTPConfirmInput, + responses={200: SimpleResultResponse()}, + ) + def post(self, request): + serializer = TOTPConfirmInput(data=request.data) + serializer.is_valid(raise_exception=True) + otp_token = serializer.validated_data["otp_token"] + + device = TOTPDevice.objects.filter(user=request.user, confirmed=False).first() + if device is None: + raise exceptions.ValidationError( + _("No pending TOTP enrollment. Call /api/mfa/totp/init first."), + code="totp_not_initialized", + ) + + if not device.verify_token(otp_token): + raise exceptions.ValidationError( + {"otp_token": _("Invalid or expired OTP token")}, + code="otp_invalid", + ) + + device.confirmed = True + device.save() + + return Response({"confirmed": True}, status=status.HTTP_200_OK) + + +class TOTPDisableView(_BaseMFAView): + @openapi.extend_schema( + tags=["Auth"], + operation_id=f"{ENDPOINT_ID}disable_totp", + summary="Disable TOTP", + description=( + "Remove the user's TOTP device(s). Requires **both** an OTP-verified JWT *and* " + "a fresh OTP code in the request body. The session-verified check happens at " + "auth time (`JWTAuthentication.authenticate` rejects tokens with " + "`is_verified=False`); the fresh OTP check is step-up auth — a session " + "verified 25 minutes ago shouldn't be able to disable MFA without re-proving " + "the second factor right now." + ), + request=TOTPDisableInput, + responses={200: SimpleResultResponse()}, + ) + def post(self, request): + # 1. JWT is_verified=False is rejected upstream in JWTAuthentication. + # 2. Step-up: require a fresh OTP in the body — matches the pattern + # that /api/token/verified uses for token upgrades. + serializer = TOTPDisableInput(data=request.data) + serializer.is_valid(raise_exception=True) + otp_token = serializer.validated_data["otp_token"] + + device = TOTPDevice.objects.filter(user=request.user, confirmed=True).first() + if device is None: + raise exceptions.ValidationError( + _("No confirmed TOTP device to disable."), + code="totp_not_enrolled", + ) + + if not device.verify_token(otp_token): + raise exceptions.ValidationError( + {"otp_token": _("Invalid or expired OTP token")}, + code="otp_invalid", + ) + + TOTPDevice.objects.filter(user=request.user).delete() + return Response({"disabled": True}, status=status.HTTP_200_OK) + + +class MFAStatusView(_BaseMFAView): + @openapi.extend_schema( + tags=["Auth"], + operation_id=f"{ENDPOINT_ID}mfa_status", + summary="MFA status", + description="Whether the current user has TOTP enrolled and confirmed.", + responses={200: MFAStatusResponse()}, + ) + def get(self, request): + qs = TOTPDevice.objects.filter(user=request.user) + return Response( + { + "enrolled": qs.exists(), + "confirmed": qs.filter(confirmed=True).exists(), + }, + status=status.HTTP_200_OK, + ) + + +from django.urls import path # noqa: E402 (kept with urlpatterns below for locality) + +urlpatterns = [ + path("api/mfa/totp/init", TOTPInitView.as_view(), name="mfa-totp-init"), + path("api/mfa/totp/confirm", TOTPConfirmView.as_view(), name="mfa-totp-confirm"), + path("api/mfa/totp/disable", TOTPDisableView.as_view(), name="mfa-totp-disable"), + path("api/mfa/status", MFAStatusView.as_view(), name="mfa-status"), +] diff --git a/apps/api/karrio/server/urls/tokens.py b/apps/api/karrio/server/urls/tokens.py index 31e0378a6c..c5da69ed9b 100644 --- a/apps/api/karrio/server/urls/tokens.py +++ b/apps/api/karrio/server/urls/tokens.py @@ -5,15 +5,15 @@ """ from datetime import datetime, timezone + +import karrio.server.openapi as openapi from django.urls import path +from karrio.server.core.utils import ResourceAccessToken from rest_framework import serializers, status -from rest_framework.views import APIView +from rest_framework.permissions import IsAuthenticated from rest_framework.request import Request from rest_framework.response import Response -from rest_framework.permissions import IsAuthenticated - -import karrio.server.openapi as openapi -from karrio.server.core.utils import ResourceAccessToken +from rest_framework.views import APIView ENDPOINT_ID = "&&" # Unique endpoint ID for OpenAPI operation IDs @@ -103,10 +103,7 @@ def _build_template_urls(resource_ids: list, token: str) -> dict: templates = DocumentTemplate.objects.filter(pk__in=resource_ids).values("pk", "slug") template_map = {t["pk"]: t["slug"] for t in templates} - return { - rid: f"/documents/templates/{rid}.{template_map.get(rid, 'doc')}?token={token}" - for rid in resource_ids - } + return {rid: f"/documents/templates/{rid}.{template_map.get(rid, 'doc')}?token={token}" for rid in resource_ids} def _build_document_urls(resource_ids: list, access: list, format_ext: str, token: str) -> dict: @@ -141,7 +138,6 @@ def build_resource_urls( return builders.get(resource_type, lambda: {})() - class ResourceTokenView(APIView): """Generate limited-access tokens for resources.""" diff --git a/apps/api/karrio/server/wsgi.py b/apps/api/karrio/server/wsgi.py index 494145a67f..f815088ced 100644 --- a/apps/api/karrio/server/wsgi.py +++ b/apps/api/karrio/server/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'karrio.server.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "karrio.server.settings") application = get_wsgi_application() diff --git a/apps/api/pyproject.toml b/apps/api/pyproject.toml index 45f4a49fff..e2f213a971 100644 --- a/apps/api/pyproject.toml +++ b/apps/api/pyproject.toml @@ -47,7 +47,7 @@ dependencies = [ "opentelemetry-exporter-otlp", "opentelemetry-instrumentation-requests", "opentelemetry-instrumentation-logging", - "opentelemetry-instrumentation-psycopg2", + "opentelemetry-instrumentation-psycopg", "opentelemetry-instrumentation-redis", "opentelemetry-semantic-conventions", "loguru", diff --git a/apps/web/public/carrier-integrations.json b/apps/web/public/carrier-integrations.json index cc926a6ce7..27e1775d90 100644 --- a/apps/web/public/carrier-integrations.json +++ b/apps/web/public/carrier-integrations.json @@ -1 +1 @@ -[{"id":"aramex","carrier_name":"aramex","display_name":"Aramex","integration_status":"beta","website":"https://www.aramex.com/ae/en","description":"Aramex is the leading global logistics provider.","documentation":"https://www.aramex.com/us/en/developers-solution-center/aramex-apis","is_enabled":true,"capabilities":["Tracking"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_pin":{"name":"account_pin","required":true,"sensitive":false,"type":"string","label":"Account PIN"},"account_entity":{"name":"account_entity","required":true,"sensitive":false,"type":"string","label":"Account Entity"},"account_number":{"name":"account_number","required":true,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":true,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"asendia","carrier_name":"asendia","display_name":"Asendia","integration_status":"beta","website":"https://www.asendia.com","description":"Asendia international e-commerce shipping services","documentation":"https://www.asendia-sync.com/swagger-ui/index.html","is_enabled":true,"capabilities":["Manifest","Tracking","Paperless","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"customer_id":{"name":"customer_id","required":false,"sensitive":false,"type":"string","label":"Customer Id"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_type":{"code":"label_type","name":"label_type","required":false,"type":"string","label":"Label Type"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"asendia_epaq_standard":"EPAQSTD","asendia_epaq_standard_cup":"EPAQSTD_CUP","asendia_epaq_plus":"EPAQPLUS","asendia_epaq_plus_cup":"EPAQPLUS_CUP","asendia_epaq_elite":"EPAQELITE","asendia_epaq_elite_cup":"EPAQELITE_CUP","asendia_epaq_returns":"EPAQRET","asendia_epaq_returns_domestic":"EPAQRETDOM","asendia_country_road":"CROAD","asendia_country_road_plus":"CROADPLUS","asendia_priority":"PRIORITY","asendia_priority_tracked":"PRIORITYTRK"},"shipping_options":{"asendia_insurance":{"code":"insurance","meta":{"configurable":true},"type":"string","label":"Asendia Insurance"},"asendia_return_label":{"code":"return_label","meta":{"configurable":true},"type":"boolean","label":"Asendia Return Label"},"asendia_return_label_type":{"code":"return_label_type","meta":{"configurable":true},"type":"string","label":"Asendia Return Label Type"},"asendia_return_label_payment":{"code":"return_label_payment","meta":{"configurable":true},"type":"string","label":"Asendia Return Label Payment"},"asendia_sender_eori":{"code":"sender_eori","meta":{"configurable":true},"type":"string","label":"Asendia Sender Eori"},"asendia_seller_eori":{"code":"seller_eori","meta":{"configurable":true},"type":"string","label":"Asendia Seller Eori"},"asendia_sender_tax_id":{"code":"sender_tax_id","meta":{"configurable":true},"type":"string","label":"Asendia Sender Tax ID"},"asendia_receiver_tax_id":{"code":"receiver_tax_id","meta":{"configurable":true},"type":"string","label":"Asendia Receiver Tax ID"}},"readme":null},{"id":"asendia_us","carrier_name":"asendia_us","display_name":"Asendia US","integration_status":"beta","website":"https://www.asendia.com/","description":"deliver cross-border e-commerce solutions that are loved by your shoppers worldwide.","documentation":"https://a1api.asendiausa.com/swagger/index.html","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"default":"US","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"sub_account":{"code":"sub_account","name":"sub_account","required":false,"type":"string","label":"Sub Account"},"processing_location":{"code":"processing_location","name":"processing_location","required":false,"type":"string","label":"Processing Location"}},"shipping_services":{"asendia_us_e_com_tracked_ddp":"19","asendia_us_fully_tracked":"65","asendia_us_country_tracked":"66"},"shipping_options":{"asendia_us_processing_location":{"code":"asendia_us_processing_location","meta":{"configurable":true},"type":"string","label":"Processing Location"}},"readme":null},{"id":"australiapost","carrier_name":"australiapost","display_name":"Australia Post","integration_status":"beta","website":"https://auspost.com.au/","description":"Australia Post, formally known as the Australian Postal Corporation, is a Commonwealth government-owned corporation that provides postal services throughout Australia.","documentation":"https://developers.auspost.com.au/apis/shipping-and-tracking/reference","is_enabled":true,"capabilities":["Manifest","Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_number":{"name":"account_number","required":true,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"default":"AU","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"australiapost_parcel_post":"T28","australiapost_express_post":"E34","australiapost_parcel_post_signature":"3D55","australiapost_express_post_signature":"3J55","australiapost_intl_standard_pack_track":"PTI8","australiapost_intl_standard_with_signature":"PTI7","australiapost_intl_express_merch":"ECM8","australiapost_intl_express_docs":"ECD8","australiapost_eparcel_post_returns":"PR","australiapost_express_eparcel_post_returns":"XPR"},"shipping_options":{"australiapost_delivery_date":{"code":"DELIVERY_DATE","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Australia Post Delivery Date"},"australiapost_delivery_time_start":{"code":"DELIVERY_TIMES","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Australia Post Delivery Time Start"},"australiapost_pickup_date":{"code":"PICKUP_DATE","meta":{"configurable":true},"type":"string","label":"Australia Post Pickup Date"},"australiapost_pickup_time":{"code":"PICKUP_TIME","meta":{"configurable":true},"type":"string","label":"Australia Post Pickup Time"},"australiapost_identity_on_delivery":{"code":"IDENTITY_ON_DELIVERY","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Australia Post Identity On Delivery"},"australiapost_print_at_depot":{"code":"PRINT_AT_DEPOT","meta":{"configurable":true},"type":"boolean","label":"Australia Post Print At Depot"},"australiapost_transit_cover":{"code":"TRANSIT_COVER","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Australia Post Transit Cover"},"australiapost_sameday_identity_on_delivery":{"code":"SAMEDAY_IDENTITY_ON_DELIVERY","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Australia Post Sameday Identity On Delivery"},"australiapost_authority_to_leave":{"code":"authority_to_leave","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Australia Post Authority To Leave"},"australiapost_allow_partial_delivery":{"code":"allow_partial_delivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Australia Post Allow Partial Delivery"},"australiapost_contains_dangerous_goods":{"code":"contains_dangerous_goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Australia Post Contains Dangerous Goods"}},"readme":null},{"id":"boxknight","carrier_name":"boxknight","display_name":"BoxKnight","integration_status":"beta","website":"https://www.boxknight.com/","description":"Specializes in same-day delivery at affordable prices for e-commerce retailers. Our mission is to get packages to your customers when they are actually home and as quickly as possible.","documentation":"https://www.docs.boxknight.com/","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_country_code":{"default":"CA","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"boxknight_sameday":"SAMEDAY","boxknight_nextday":"NEXTDAY","boxknight_scheduled":"SCHEDULED"},"shipping_options":{"boxknight_signature_required":{"code":"signatureRequired","meta":{"configurable":true},"type":"boolean","label":"Signature Required"},"boxknight_merchant_display_name":{"code":"merchantDisplayName","meta":{"configurable":true},"type":"string","label":"Merchant Display Name"},"boxknight_notes":{"code":"notes","meta":{"configurable":true},"type":"string","label":"Notes"}},"readme":null},{"id":"bpost","carrier_name":"bpost","display_name":"Belgian Post","integration_status":"beta","website":"https://bpostgroup.com/","description":"The Belgian company responsible for the delivery of national and international mail.","documentation":"https://bpost.freshdesk.com/support/solutions/articles/4000037653-where-can-i-find-the-bpack-integration-manual-examples-and-xsd-s-","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"account_id":{"name":"account_id","required":true,"sensitive":false,"type":"string","label":"Account Id"},"passphrase":{"name":"passphrase","required":true,"sensitive":false,"type":"string","label":"Passphrase"},"account_country_code":{"default":"BE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"cost_center":{"code":"cost_center","name":"cost_center","required":false,"type":"string","label":"Cost Center"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"lang":{"code":"lang","enum":["FR","EN"],"name":"lang","required":false,"type":"string","label":"Lang"}},"shipping_services":{"bpack_24h_pro":"bpack 24h Pro","bpack_24h_business":"bpack 24h business","bpack_bus":"bpack Bus","bpack_pallet":"bpack Pallet","bpack_easy_retour":"bpack Easy Retour","bpack_xl":"bpack XL","bpack_bpost":"bpack@bpost","bpack_24_7":"bpack 24/7","bpack_world_business":"bpack World Business","bpack_world_express_pro":"bpack World Express Pro","bpack_europe_business":"bpack Europe Business","bpack_world_easy_return":"bpack World Easy Return","bpack_bpost_international":"bpack@bpost international","bpack_24_7_international":"bpack 24/7 international"},"shipping_options":{"bpost_info_distributed":{"code":"infoDistributed","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"bpost Info Distributed"},"bpost_info_next_day":{"code":"infoNextDay","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"bpost Info Next Day"},"bpost_info_reminder":{"code":"infoReminder","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"bpost Info Reminder"},"bpost_keep_me_informed":{"code":"keepMeInformed","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"bpost Keep Me Informed"},"bpost_automatic_second_presentation":{"code":"automaticSecondPresentation","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"bpost Automatic Second Presentation"},"bpost_fragile":{"code":"fragile","meta":{"configurable":true},"type":"boolean","label":"bpost Fragile"},"bpost_insured":{"code":"insured","meta":{"category":"INSURANCE","configurable":true},"type":"string","label":"bpost Insured"},"bpost_signed":{"code":"signed","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"bpost Signed"},"bpost_time_slot_delivery":{"code":"timeSlotDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"bpost Time Slot Delivery"},"bpost_saturday_delivery":{"code":"saturdayDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"bpost Saturday Delivery"},"bpost_sunday_delivery":{"code":"sundayDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"bpost Sunday Delivery"},"bpost_same_day_delivery":{"code":"sameDayDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"bpost Same Day Delivery"},"bpost_cod":{"code":"cod","meta":{"category":"COD","configurable":true},"type":"float","label":"bpost COD"},"bpost_preferred_delivery_window":{"code":"preferredDeliveryWindow","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"bpost Preferred Delivery Window"},"bpost_full_service":{"code":"fullService","meta":{"configurable":true},"type":"boolean","label":"bpost Full Service"},"bpost_door_step_plus_service":{"code":"doorStepPlusService","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"bpost Door Step Plus Service"},"bpost_ultra_late_in_evening_delivery":{"code":"ultraLateInEveningDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"bpost Ultra Late In Evening Delivery"},"bpost_pugo_id":{"code":"pugoId","meta":{"category":"PUDO","configurable":true},"type":"string","label":"bpost Pugo ID"},"bpost_pugo_name":{"code":"pugoName","meta":{"category":"PUDO","configurable":true},"type":"string","label":"bpost Pugo Name"},"bpost_pugo_address":{"code":"pugoAddress","meta":{"category":"PUDO","configurable":true},"type":"object","label":"bpost Pugo Address"},"bpost_parcels_depot_id":{"code":"parcelsDepotId","meta":{"category":"PUDO","configurable":true},"type":"string","label":"bpost Parcels Depot ID"},"bpost_parcels_depot_name":{"code":"parcelsDepotName","meta":{"category":"PUDO","configurable":true},"type":"string","label":"bpost Parcels Depot Name"},"bpost_parcels_depot_address":{"code":"parcelsDepotAddress","meta":{"category":"PUDO","configurable":true},"type":"object","label":"bpost Parcels Depot Address"},"bpost_parcel_return_instructions":{"code":"parcelReturnInstructions","meta":{"category":"RETURN","configurable":true},"type":"string","label":"bpost Parcel Return Instructions"}},"readme":null},{"id":"canadapost","carrier_name":"canadapost","display_name":"Canada Post","integration_status":"production-ready","website":"https://www.canadapost-postescanada.ca/cpc/en/home.page","description":"Mailing and shipping for Personal and Business.","documentation":"https://www.canadapost-postescanada.ca/information/app/drc/home","is_enabled":true,"capabilities":["Manifest","Rating","Pickup","Shipping","Tracking"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":true,"type":"string","label":"Password"},"customer_number":{"name":"customer_number","required":false,"sensitive":false,"type":"string","label":"Customer Number"},"contract_id":{"name":"contract_id","required":false,"sensitive":false,"type":"string","label":"Contract ID"},"language":{"default":"en","enum":["en","fr"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"CA","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"cost_center":{"code":"cost_center","name":"cost_center","required":false,"type":"string","label":"Cost Center"},"label_type":{"code":"label_type","enum":["PDF_4x6","PDF_8_5x11","ZPL_4x6"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"transmit_shipment_by_default":{"code":"transmit_shipment_by_default","name":"transmit_shipment_by_default","required":false,"type":"boolean","label":"Transmit Shipment By Default"}},"shipping_services":{"canadapost_regular_parcel":"DOM.RP","canadapost_expedited_parcel":"DOM.EP","canadapost_xpresspost":"DOM.XP","canadapost_xpresspost_certified":"DOM.XP.CERT","canadapost_priority":"DOM.PC","canadapost_library_books":"DOM.LIB","canadapost_expedited_parcel_usa":"USA.EP","canadapost_priority_worldwide_envelope_usa":"USA.PW.ENV","canadapost_priority_worldwide_pak_usa":"USA.PW.PAK","canadapost_priority_worldwide_parcel_usa":"USA.PW.PARCEL","canadapost_small_packet_usa_air":"USA.SP.AIR","canadapost_tracked_packet_usa":"USA.TP","canadapost_tracked_packet_usa_lvm":"USA.TP.LVM","canadapost_xpresspost_usa":"USA.XP","canadapost_xpresspost_international":"INT.XP","canadapost_international_parcel_air":"INT.IP.AIR","canadapost_international_parcel_surface":"INT.IP.SURF","canadapost_priority_worldwide_envelope_intl":"INT.PW.ENV","canadapost_priority_worldwide_pak_intl":"INT.PW.PAK","canadapost_priority_worldwide_parcel_intl":"INT.PW.PARCEL","canadapost_small_packet_international_air":"INT.SP.AIR","canadapost_small_packet_international_surface":"INT.SP.SURF","canadapost_tracked_packet_international":"INT.TP"},"shipping_options":{"canadapost_signature":{"code":"SO","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Canada Post Signature"},"canadapost_coverage":{"code":"COV","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Canada Post Coverage"},"canadapost_collect_on_delivery":{"code":"COD","meta":{"category":"COD","configurable":true},"type":"float","label":"Canada Post Collect On Delivery"},"canadapost_proof_of_age_required_18":{"code":"PA18","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Canada Post Proof Of Age Required 18"},"canadapost_proof_of_age_required_19":{"code":"PA19","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Canada Post Proof Of Age Required 19"},"canadapost_card_for_pickup":{"code":"HFP","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Canada Post Card For Pickup"},"canadapost_do_not_safe_drop":{"code":"DNS","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Canada Post Do Not Safe Drop"},"canadapost_leave_at_door":{"code":"LAD","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Canada Post Leave At Door"},"canadapost_deliver_to_post_office":{"code":"D2PO","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Canada Post Deliver To Post Office"},"canadapost_return_at_senders_expense":{"code":"RASE","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Canada Post Return At Senders Expense"},"canadapost_return_to_sender":{"code":"RTS","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Canada Post Return To Sender"},"canadapost_abandon":{"code":"ABAN","meta":{"configurable":true},"type":"boolean","label":"Canada Post Abandon"},"canadapost_cost_center":{"code":"cost-centre","meta":{"configurable":true},"type":"string","label":"Canada Post Cost Center"},"canadapost_submit_shipment":{"code":"transmit-shipment","meta":{"configurable":true},"type":"boolean","label":"Canada Post Submit Shipment"}},"readme":null},{"id":"canpar","carrier_name":"canpar","display_name":"Canpar","integration_status":"beta","website":"https://www.canpar.com/","description":"Everything Canpar Express does-product development, technological upgrades, customer service-is shaped and tailored to transporting our customers' parcels efficiently and cost-effectively.","documentation":"https://www.canpar.com/en/solutions/ecommerce_tools.htm","is_enabled":true,"capabilities":["Tracking","Pickup","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"language":{"default":"en","enum":["en","fr"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"CA","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"chronopost","carrier_name":"chronopost","display_name":"Chronopost","integration_status":"beta","website":"https://www.chronopost.fr/en","description":"Provides express shipping and delivery service both domestically and internationally.","documentation":"https://www.chrono-api.fr/docs/api/","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"account_number":{"name":"account_number","required":true,"sensitive":false,"type":"string","label":"Account Number"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"id_emit":{"default":"CHRFR","name":"id_emit","required":false,"sensitive":false,"type":"string","label":"Id Emit"},"language":{"default":"en_GB","enum":["en_GB","fr_FR"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"FR","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"chronopost_retrait_bureau":"0","chronopost_13":"1","chronopost_10":"2","chronopost_18":"16","chronopost_relais":"86","chronopost_express_international":"17","chronopost_premium_international":"37","chronopost_classic_international":"44"},"shipping_options":{"chronopost_delivery_on_monday":{"code":"1","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Chronopost Delivery On Monday"},"chronopost_delivery_on_saturday":{"code":"6","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Chronopost Delivery On Saturday"},"chronopost_delivery_normal":{"code":"0","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Chronopost Delivery Normal"}},"readme":null},{"id":"colissimo","carrier_name":"colissimo","display_name":"Colissimo","integration_status":"beta","website":"https://www.colissimo.entreprise.laposte.fr/en","description":"Envoi de colis en France et dans le monde entier, livraison à domicile ou en point de retrait, Colissimo vous offre un choix de services qui facilitent votre quotidien.","documentation":"https://www.colissimo.entreprise.laposte.fr/en/tools-and-services","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"contract_number":{"name":"contract_number","required":true,"sensitive":false,"type":"string","label":"Contract Number"},"laposte_api_key":{"name":"laposte_api_key","required":false,"sensitive":false,"type":"string","label":"Laposte Api Key"},"account_country_code":{"default":"FR","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"lang":{"code":"lang","enum":["FR","EN"],"name":"lang","required":false,"type":"string","label":"Lang"}},"shipping_services":{"colissimo_home_without_signature":"DOM","colissimo_home_with_signature":"DOS","colissimo_eco_france":"CECO","colissimo_return_france":"CORE","colissimo_flash_without_signature":"COLR","colissimo_flash_with_signature":"J+1","colissimo_oversea_home_without_signature":"COM","colissimo_oversea_home_with_signature":"CDS","colissimo_eco_om_without_signature":"ECO","colissimo_eco_om_with_signature":"ECOS","colissimo_retour_om":"CORI","colissimo_return_international_from_france":"CORF","colissimo_economical_big_export_offer":"ACCI","colissimo_out_of_home_national_international":"HD"},"shipping_options":{"colissimo_insurance_value":{"code":"insuranceValue","meta":{"configurable":true},"type":"float","label":"Insurance Value"},"colissimo_cod_amount":{"code":"CODAmount","meta":{"configurable":true},"type":"float","label":"Cod Amount"},"colissimo_return_receipt":{"code":"returnReceipt","meta":{"configurable":true},"type":"boolean","label":"Return Receipt"},"colissimo_ftd":{"code":"ftd","meta":{"configurable":true},"type":"boolean","label":"Ftd"},"colissimo_non_machinable":{"code":"nonMachinable","meta":{"configurable":true},"type":"boolean","label":"Non Machinable"},"colissimo_ddp":{"code":"ddp","meta":{"configurable":true},"type":"boolean","label":"Ddp"},"colissimo_instructions":{"code":"instructions","meta":{"configurable":true},"type":"string","label":"Instructions"}},"readme":null},{"id":"dhl_express","carrier_name":"dhl_express","display_name":"DHL Express","integration_status":"production-ready","website":"https://www.dhl.com/ca-en/home/express.html","description":"When your shipment needs to be there fast, choose the International Specialists for quick, reliable expedited shipments to and from more than 220 countries and territories.","documentation":"https://developer.dhl.com/api-reference/dhl-express-xml","is_enabled":true,"capabilities":["Rating","Pickup","Paperless","Shipping","Tracking"],"connection_fields":{"site_id":{"name":"site_id","required":true,"sensitive":false,"type":"string","label":"Site ID"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_type":{"code":"label_type","enum":["PDF_6x4","PDF_8x4","PDF_8x4_A4","PDF_6x4_A4","PDF_8x4_CI","PDF_8x4_RU_A4","ZPL_8x4","ZPL_6x4","ZPL_8x4_CI"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"software_name":{"code":"software_name","name":"software_name","required":false,"type":"string","label":"Software Name"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"skip_service_filter":{"code":"skip_service_filter","name":"skip_service_filter","required":false,"type":"boolean","label":"Skip Service Filter"}},"shipping_services":{"dhl_logistics_services":"0","dhl_domestic_express_12_00":"1","dhl_express_choice":"2","dhl_express_choice_nondoc":"3","dhl_jetline":"4","dhl_sprintline":"5","dhl_air_capacity_sales":"6","dhl_express_easy":"7","dhl_express_easy_nondoc":"8","dhl_parcel_product":"9","dhl_accounting":"A","dhl_breakbulk_express":"B","dhl_medical_express":"C","dhl_express_worldwide_doc":"D","dhl_express_9_00_nondoc":"E","dhl_freight_worldwide_nondoc":"F","dhl_economy_select_domestic":"G","dhl_economy_select_nondoc":"H","dhl_express_domestic_9_00":"I","dhl_jumbo_box_nondoc":"J","dhl_express_9_00":"K","dhl_express_10_30":"L","dhl_express_10_30_nondoc":"M","dhl_express_domestic":"N","dhl_express_domestic_10_30":"O","dhl_express_worldwide_nondoc":"P","dhl_medical_express_nondoc":"Q","dhl_globalmail":"R","dhl_same_day":"S","dhl_express_12_00":"T","dhl_express_worldwide":"U","dhl_parcel_product_nondoc":"V","dhl_economy_select":"W","dhl_express_envelope":"X","dhl_express_12_00_nondoc":"Y","dhl_destination_charges":"Z","dhl_express_all":null},"shipping_options":{"dhl_logistics_services":{"code":"0A","meta":{"configurable":true},"type":"boolean","label":"Dhl Logistics Services"},"dhl_mailroom_management":{"code":"0B","meta":{"configurable":true},"type":"boolean","label":"Dhl Mailroom Management"},"dhl_pallet_administration":{"code":"0C","meta":{"configurable":true},"type":"boolean","label":"Dhl Pallet Administration"},"dhl_warehousing":{"code":"0D","meta":{"configurable":true},"type":"boolean","label":"Dhl Warehousing"},"dhl_express_logistics_centre":{"code":"0E","meta":{"configurable":true},"type":"boolean","label":"Logistics Centre"},"dhl_strategic_parts_centre":{"code":"0F","meta":{"configurable":true},"type":"boolean","label":"Dhl Strategic Parts Centre"},"dhl_local_distribution_centre":{"code":"0G","meta":{"configurable":true},"type":"boolean","label":"Dhl Local Distribution Centre"},"dhl_terminal_handling":{"code":"0H","meta":{"configurable":true},"type":"boolean","label":"Dhl Terminal Handling"},"dhl_cross_docking":{"code":"0I","meta":{"configurable":true},"type":"boolean","label":"Dhl Cross Docking"},"dhl_inventory_management":{"code":"0J","meta":{"configurable":true},"type":"boolean","label":"Dhl Inventory Management"},"dhl_loading_unloading":{"code":"0K","meta":{"configurable":true},"type":"boolean","label":"Dhl Loading Unloading"},"dhl_product_kitting":{"code":"0L","meta":{"configurable":true},"type":"boolean","label":"Dhl Product Kitting"},"dhl_priority_account_desk":{"code":"0M","meta":{"configurable":true},"type":"boolean","label":"Dhl Priority Account Desk"},"dhl_document_archiving":{"code":"0N","meta":{"configurable":true},"type":"boolean","label":"Dhl Document Archiving"},"dhl_saturday_delivery":{"code":"AA","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Dhl Saturday Delivery"},"dhl_saturday_pickup":{"code":"AB","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Dhl Saturday Pickup"},"dhl_holiday_delivery":{"code":"AC","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Dhl Holiday Delivery"},"dhl_holiday_pickup":{"code":"AD","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Dhl Holiday Pickup"},"dhl_domestic_saturday_delivery":{"code":"AG","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Dhl Domestic Saturday Delivery"},"dhl_standard":{"code":"BA","meta":{"configurable":true},"type":"boolean","label":"Dhl Standard"},"dhl_globalmail_item":{"code":"BB","meta":{"configurable":true},"type":"boolean","label":"Dhl Globalmail Item"},"dhl_letter":{"code":"BC","meta":{"configurable":true},"type":"boolean","label":"Dhl Letter"},"dhl_packet":{"code":"BD","meta":{"configurable":true},"type":"boolean","label":"Dhl Packet"},"dhl_letter_plus":{"code":"BE","meta":{"configurable":true},"type":"boolean","label":"Dhl Letter Plus"},"dhl_packet_plus":{"code":"BF","meta":{"configurable":true},"type":"boolean","label":"Dhl Packet Plus"},"dhl_elevated_risk":{"code":"CA","meta":{"configurable":true},"type":"boolean","label":"Dhl Elevated Risk"},"dhl_restricted_destination":{"code":"CB","meta":{"configurable":true},"type":"boolean","label":"Dhl Restricted Destination"},"dhl_security_validation":{"code":"CC","meta":{"configurable":true},"type":"boolean","label":"Dhl Security Validation"},"dhl_secure_protection":{"code":"CD","meta":{"configurable":true},"type":"boolean","label":"Dhl Secure Protection"},"dhl_proof_of_identity":{"code":"CE","meta":{"configurable":true},"type":"boolean","label":"Dhl Proof Of Identity"},"dhl_secure_storage":{"code":"CF","meta":{"configurable":true},"type":"boolean","label":"Dhl Secure Storage"},"dhl_diplomatic_material":{"code":"CG","meta":{"configurable":true},"type":"boolean","label":"Dhl Diplomatic Material"},"dhl_smart_sensor":{"code":"CH","meta":{"configurable":true},"type":"boolean","label":"Dhl Smart Sensor"},"dhl_visa_program":{"code":"CI","meta":{"configurable":true},"type":"boolean","label":"Dhl Visa Program"},"dhl_onboard_courier":{"code":"CJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Onboard Courier"},"dhl_secure_safebox":{"code":"CK","meta":{"configurable":true},"type":"boolean","label":"Dhl Secure Safebox"},"dhl_smart_sentry":{"code":"CL","meta":{"configurable":true},"type":"boolean","label":"Dhl Smart Sentry"},"dhl_split_duties_and_tax":{"code":"DC","meta":{"configurable":true},"type":"boolean","label":"Dhl Split Duties And Tax"},"dhl_duties_and_taxes_paid":{"code":"DD","meta":{"configurable":true},"type":"boolean","label":"Dhl Duties And Taxes Paid"},"dhl_receiver_paid":{"code":"DE","meta":{"configurable":true},"type":"boolean","label":"Dhl Receiver Paid"},"dhl_duties_and_taxes_unpaid":{"code":"DS","meta":{"configurable":true},"type":"boolean","label":"Dhl Duties And Taxes Unpaid"},"dhl_import_billing":{"code":"DT","meta":{"configurable":true},"type":"boolean","label":"Dhl Import Billing"},"dhl_importer_of_record":{"code":"DU","meta":{"configurable":true},"type":"boolean","label":"Dhl Importer Of Record"},"dhl_go_green_carbon_neutral":{"code":"EA","meta":{"configurable":true},"type":"boolean","label":"Dhl Go Green Carbon Neutral"},"dhl_go_green_carbon_footprint":{"code":"EB","meta":{"configurable":true},"type":"boolean","label":"Dhl Go Green Carbon Footprint"},"dhl_go_green_carbon_estimate":{"code":"EC","meta":{"configurable":true},"type":"boolean","label":"Dhl Go Green Carbon Estimate"},"dhl_fuel_surcharge_b":{"code":"FB","meta":{"configurable":true},"type":"boolean","label":"Dhl Fuel Surcharge B"},"dhl_fuel_surcharge_c":{"code":"FC","meta":{"configurable":true},"type":"boolean","label":"Dhl Fuel Surcharge C"},"dhl_fuel_surcharge_f":{"code":"FF","meta":{"configurable":true},"type":"boolean","label":"Dhl Fuel Surcharge F"},"dhl_smartphone_box":{"code":"GA","meta":{"configurable":true},"type":"boolean","label":"Dhl Smartphone Box"},"dhl_laptop_box":{"code":"GB","meta":{"configurable":true},"type":"boolean","label":"Dhl Laptop Box"},"dhl_bottle_box":{"code":"GC","meta":{"configurable":true},"type":"boolean","label":"Dhl Bottle Box"},"dhl_repacking":{"code":"GD","meta":{"configurable":true},"type":"boolean","label":"Dhl Repacking"},"dhl_tablet_box":{"code":"GE","meta":{"configurable":true},"type":"boolean","label":"Dhl Tablet Box"},"dhl_filler_material":{"code":"GF","meta":{"configurable":true},"type":"boolean","label":"Dhl Filler Material"},"dhl_packaging":{"code":"GG","meta":{"configurable":true},"type":"boolean","label":"Dhl Packaging"},"dhl_diplomatic_bag":{"code":"GH","meta":{"configurable":true},"type":"boolean","label":"Dhl Diplomatic Bag"},"dhl_pallet_box":{"code":"GI","meta":{"configurable":true},"type":"boolean","label":"Dhl Pallet Box"},"dhl_lock_box":{"code":"GJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Lock Box"},"dhl_lithium_ion_pi965_section_ii":{"code":"HB","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Lithium Ion Pi965 Section Ii"},"dhl_dry_ice_un1845":{"code":"HC","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Dry Ice Un1845"},"dhl_lithium_ion_pi965_966_section_ii":{"code":"HD","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Lithium Ion Pi965 966 Section Ii"},"dhl_dangerous_goods":{"code":"HE","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Dangerous Goods"},"dhl_perishable_cargo":{"code":"HG","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Perishable Cargo"},"dhl_excepted_quantity":{"code":"HH","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Excepted Quantity"},"dhl_spill_cleaning":{"code":"HI","meta":{"configurable":true},"type":"boolean","label":"Dhl Spill Cleaning"},"dhl_consumer_commodities":{"code":"HK","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Consumer Commodities"},"dhl_limited_quantities_adr":{"code":"HL","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Limited Quantities Adr"},"dhl_lithium_metal_pi969_section_ii":{"code":"HM","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Lithium Metal Pi969 Section Ii"},"dhl_adr_load_exemption":{"code":"HN","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Adr Load Exemption"},"dhl_lithium_ion_pi967_section_ii":{"code":"HV","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Lithium Ion Pi967 Section Ii"},"dhl_lithium_metal_pi970_section_ii":{"code":"HW","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Lithium Metal Pi970 Section Ii"},"dhl_biological_un3373":{"code":"HY","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Biological Un3373"},"dhl_extended_liability":{"code":"IB","meta":{"category":"INSURANCE","configurable":true},"type":"boolean","label":"Dhl Extended Liability"},"dhl_contract_insurance":{"code":"IC","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Dhl Contract Insurance"},"dhl_shipment_insurance":{"code":"II","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Dhl Shipment Insurance"},"dhl_delivery_notification":{"code":"JA","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"Dhl Delivery Notification"},"dhl_pickup_notification":{"code":"JC","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"Dhl Pickup Notification"},"dhl_proactive_tracking":{"code":"JD","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"Dhl Proactive Tracking"},"dhl_performance_reporting":{"code":"JE","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"Dhl Performance Reporting"},"dhl_prealert_notification":{"code":"JY","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"Dhl Prealert Notification"},"dhl_change_of_billing":{"code":"KA","meta":{"configurable":true},"type":"boolean","label":"Dhl Change Of Billing"},"dhl_cash_on_delivery":{"code":"KB","meta":{"category":"COD","configurable":true},"type":"float","label":"Dhl Cash On Delivery"},"dhl_printed_invoice":{"code":"KD","meta":{"configurable":true},"type":"boolean","label":"Dhl Printed Invoice"},"dhl_waybill_copy":{"code":"KE","meta":{"configurable":true},"type":"boolean","label":"Dhl Waybill Copy"},"dhl_import_paperwork":{"code":"KF","meta":{"configurable":true},"type":"boolean","label":"Dhl Import Paperwork"},"dhl_payment_on_pickup":{"code":"KY","meta":{"configurable":true},"type":"boolean","label":"Dhl Payment On Pickup"},"dhl_shipment_intercept":{"code":"LA","meta":{"configurable":true},"type":"boolean","label":"Dhl Shipment Intercept"},"dhl_shipment_redirect":{"code":"LC","meta":{"configurable":true},"type":"boolean","label":"Dhl Shipment Redirect"},"dhl_storage_at_facility":{"code":"LE","meta":{"configurable":true},"type":"boolean","label":"Dhl Storage At Facility"},"dhl_cold_storage":{"code":"LG","meta":{"configurable":true},"type":"boolean","label":"Dhl Cold Storage"},"dhl_specific_routing":{"code":"LH","meta":{"configurable":true},"type":"boolean","label":"Dhl Specific Routing"},"dhl_service_recovery":{"code":"LV","meta":{"configurable":true},"type":"boolean","label":"Dhl Service Recovery"},"dhl_alternative_address":{"code":"LW","meta":{"configurable":true},"type":"boolean","label":"Dhl Alternative Address"},"dhl_hold_for_collection":{"code":"LX","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Dhl Hold For Collection"},"dhl_address_correction_a":{"code":"MA","meta":{"configurable":true},"type":"boolean","label":"Dhl Address Correction A"},"dhl_address_correction_b":{"code":"MB","meta":{"configurable":true},"type":"boolean","label":"Dhl Address Correction B"},"dhl_neutral_delivery":{"code":"NN","meta":{"configurable":true},"type":"boolean","label":"Dhl Neutral Delivery"},"dhl_remote_area_pickup":{"code":"OB","meta":{"configurable":true},"type":"boolean","label":"Dhl Remote Area Pickup"},"dhl_remote_area_delivery_c":{"code":"OC","meta":{"configurable":true},"type":"boolean","label":"Dhl Remote Area Delivery C"},"dhl_out_of_service_area":{"code":"OE","meta":{"configurable":true},"type":"boolean","label":"Dhl Out Of Service Area"},"dhl_remote_area_delivery_o":{"code":"OO","meta":{"configurable":true},"type":"boolean","label":"Dhl Remote Area Delivery O"},"dhl_shipment_preparation":{"code":"PA","meta":{"configurable":true},"type":"boolean","label":"Dhl Shipment Preparation"},"dhl_shipment_labeling":{"code":"PB","meta":{"configurable":true},"type":"boolean","label":"Dhl Shipment Labeling"},"dhl_shipment_consolidation":{"code":"PC","meta":{"configurable":true},"type":"boolean","label":"Dhl Shipment Consolidation"},"dhl_relabeling_data_entry":{"code":"PD","meta":{"configurable":true},"type":"boolean","label":"Dhl Relabeling Data Entry"},"dhl_preprinted_waybill":{"code":"PE","meta":{"configurable":true},"type":"boolean","label":"Dhl Preprinted Waybill"},"dhl_piece_labelling":{"code":"PS","meta":{"configurable":true},"type":"boolean","label":"Dhl Piece Labelling"},"dhl_data_staging_03":{"code":"PT","meta":{"configurable":true},"type":"boolean","label":"Dhl Data Staging 03"},"dhl_data_staging_06":{"code":"PU","meta":{"configurable":true},"type":"boolean","label":"Dhl Data Staging 06"},"dhl_data_staging_12":{"code":"PV","meta":{"configurable":true},"type":"boolean","label":"Dhl Data Staging 12"},"dhl_data_staging_24":{"code":"PW","meta":{"configurable":true},"type":"boolean","label":"Dhl Data Staging 24"},"dhl_standard_pickup":{"code":"PX","meta":{"configurable":true},"type":"boolean","label":"Dhl Standard Pickup"},"dhl_scheduled_pickup":{"code":"PY","meta":{"configurable":true},"type":"boolean","label":"Dhl Scheduled Pickup"},"dhl_dedicated_pickup":{"code":"QA","meta":{"configurable":true},"type":"boolean","label":"Dhl Dedicated Pickup"},"dhl_early_pickup":{"code":"QB","meta":{"configurable":true},"type":"boolean","label":"Dhl Early Pickup"},"dhl_late_pickup":{"code":"QD","meta":{"configurable":true},"type":"boolean","label":"Dhl Late Pickup"},"dhl_residential_pickup":{"code":"QE","meta":{"configurable":true},"type":"boolean","label":"Dhl Residential Pickup"},"dhl_loading_waiting":{"code":"QF","meta":{"configurable":true},"type":"boolean","label":"Dhl Loading Waiting"},"dhl_bypass_injection":{"code":"QH","meta":{"configurable":true},"type":"boolean","label":"Dhl Bypass Injection"},"dhl_direct_injection":{"code":"QI","meta":{"configurable":true},"type":"boolean","label":"Dhl Direct Injection"},"dhl_drop_off_at_facility":{"code":"QY","meta":{"configurable":true},"type":"boolean","label":"Dhl Drop Off At Facility"},"dhl_delivery_signature":{"code":"SA","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl Delivery Signature"},"dhl_content_signature":{"code":"SB","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl Content Signature"},"dhl_named_signature":{"code":"SC","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl Named Signature"},"dhl_adult_signature":{"code":"SD","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl Adult Signature"},"dhl_contract_signature":{"code":"SE","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl Contract Signature"},"dhl_alternative_signature":{"code":"SW","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl Alternative Signature"},"dhl_no_signature_required":{"code":"SX","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl No Signature Required"},"dhl_dedicated_delivery":{"code":"TA","meta":{"configurable":true},"type":"boolean","label":"Dhl Dedicated Delivery"},"dhl_early_delivery":{"code":"TB","meta":{"configurable":true},"type":"boolean","label":"Dhl Early Delivery"},"dhl_time_window_delivery":{"code":"TC","meta":{"configurable":true},"type":"boolean","label":"Dhl Time Window Delivery"},"dhl_evening_delivery":{"code":"TD","meta":{"configurable":true},"type":"boolean","label":"Dhl Evening Delivery"},"dhl_delivery_on_appointment":{"code":"TE","meta":{"configurable":true},"type":"boolean","label":"Dhl Delivery On Appointment"},"dhl_return_undeliverable":{"code":"TG","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Dhl Return Undeliverable"},"dhl_swap_delivery":{"code":"TH","meta":{"configurable":true},"type":"boolean","label":"Dhl Swap Delivery"},"dhl_unloading_waiting":{"code":"TJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Unloading Waiting"},"dhl_residential_delivery":{"code":"TK","meta":{"configurable":true},"type":"boolean","label":"Dhl Residential Delivery"},"dhl_repeat_delivery":{"code":"TN","meta":{"configurable":true},"type":"boolean","label":"Dhl Repeat Delivery"},"dhl_alternative_date":{"code":"TT","meta":{"configurable":true},"type":"boolean","label":"Dhl Alternative Date"},"dhl_no_partial_delivery":{"code":"TU","meta":{"configurable":true},"type":"boolean","label":"Dhl No Partial Delivery"},"dhl_service_point_24_7":{"code":"TV","meta":{"configurable":true},"type":"boolean","label":"Dhl Service Point 24 7"},"dhl_pre_9_00":{"code":"TW","meta":{"configurable":true},"type":"boolean","label":"Dhl Pre 9 00"},"dhl_pre_10_30":{"code":"TX","meta":{"configurable":true},"type":"boolean","label":"Dhl Pre 10 30"},"dhl_pre_12_00":{"code":"TY","meta":{"configurable":true},"type":"boolean","label":"Dhl Pre 12 00"},"dhl_thermo_packaging":{"code":"UA","meta":{"configurable":true},"type":"boolean","label":"Dhl Thermo Packaging"},"dhl_ambient_vialsafe":{"code":"UB","meta":{"configurable":true},"type":"boolean","label":"Dhl Ambient Vialsafe"},"dhl_ambient_non_insulated":{"code":"UC","meta":{"configurable":true},"type":"boolean","label":"Dhl Ambient Non Insulated"},"dhl_ambient_insulated":{"code":"UD","meta":{"configurable":true},"type":"boolean","label":"Dhl Ambient Insulated"},"dhl_ambient_extreme":{"code":"UE","meta":{"configurable":true},"type":"boolean","label":"Dhl Ambient Extreme"},"dhl_chilled_box_s":{"code":"UF","meta":{"configurable":true},"type":"boolean","label":"Dhl Chilled Box S"},"dhl_chilled_box_m":{"code":"UG","meta":{"configurable":true},"type":"boolean","label":"Dhl Chilled Box M"},"dhl_chilled_box_l":{"code":"UH","meta":{"configurable":true},"type":"boolean","label":"Dhl Chilled Box L"},"dhl_frozen_no_ice_s":{"code":"UI","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen No Ice S"},"dhl_frozen_no_ice_m":{"code":"UJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen No Ice M"},"dhl_frozen_no_ice_l":{"code":"UK","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen No Ice L"},"dhl_frozen_ice_sticks_s":{"code":"UL","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Sticks S"},"dhl_frozen_ice_sticks_m":{"code":"UM","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Sticks M"},"dhl_frozen_ice_sticks_l":{"code":"UN","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Sticks L"},"dhl_frozen_ice_plates_s":{"code":"UO","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Plates S"},"dhl_frozen_ice_plates_m":{"code":"UP","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Plates M"},"dhl_frozen_ice_plates_l":{"code":"UQ","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Plates L"},"dhl_combination_no_ice":{"code":"UR","meta":{"configurable":true},"type":"boolean","label":"Dhl Combination No Ice"},"dhl_combination_dry_ice":{"code":"US","meta":{"configurable":true},"type":"boolean","label":"Dhl Combination Dry Ice"},"dhl_frozen_ice_sticks_e":{"code":"UT","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Sticks E"},"dhl_frozen_ice_plates_e":{"code":"UV","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Plates E"},"dhl_customer_tcp_1":{"code":"UW","meta":{"configurable":true},"type":"boolean","label":"Dhl Customer Tcp 1"},"dhl_thermo_accessories":{"code":"VA","meta":{"configurable":true},"type":"boolean","label":"Dhl Thermo Accessories"},"dhl_absorbent_sleeve":{"code":"VB","meta":{"configurable":true},"type":"boolean","label":"Dhl Absorbent Sleeve"},"dhl_cooland_wrap":{"code":"VC","meta":{"configurable":true},"type":"boolean","label":"Dhl Cooland Wrap"},"dhl_dry_ice_supplies":{"code":"VD","meta":{"configurable":true},"type":"boolean","label":"Dhl Dry Ice Supplies"},"dhl_pressure_bag_s":{"code":"VE","meta":{"configurable":true},"type":"boolean","label":"Dhl Pressure Bag S"},"dhl_pressure_bag_m":{"code":"VF","meta":{"configurable":true},"type":"boolean","label":"Dhl Pressure Bag M"},"dhl_pressure_bag_l":{"code":"VG","meta":{"configurable":true},"type":"boolean","label":"Dhl Pressure Bag L"},"dhl_informal_clearance":{"code":"WA","meta":{"configurable":true},"type":"boolean","label":"Dhl Informal Clearance"},"dhl_formal_clearance":{"code":"WB","meta":{"configurable":true},"type":"boolean","label":"Dhl Formal Clearance"},"dhl_payment_deferment":{"code":"WC","meta":{"configurable":true},"type":"boolean","label":"Dhl Payment Deferment"},"dhl_clearance_authorization":{"code":"WD","meta":{"configurable":true},"type":"boolean","label":"Dhl Clearance Authorization"},"dhl_multiline_entry":{"code":"WE","meta":{"configurable":true},"type":"boolean","label":"Dhl Multiline Entry"},"dhl_post_clearance_modification":{"code":"WF","meta":{"configurable":true},"type":"boolean","label":"Dhl Post Clearance Modification"},"dhl_handover_to_broker":{"code":"WG","meta":{"configurable":true},"type":"boolean","label":"Dhl Handover To Broker"},"dhl_physical_intervention":{"code":"WH","meta":{"configurable":true},"type":"boolean","label":"Dhl Physical Intervention"},"dhl_bio_phyto_veterinary_controls":{"code":"WI","meta":{"configurable":true},"type":"boolean","label":"Dhl Bio Phyto Veterinary Controls"},"dhl_obtaining_permits_and_licences":{"code":"WJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Obtaining Permits And Licences"},"dhl_bonded_storage":{"code":"WK","meta":{"configurable":true},"type":"boolean","label":"Dhl Bonded Storage"},"dhl_bonded_transit_documents":{"code":"WL","meta":{"configurable":true},"type":"boolean","label":"Dhl Bonded Transit Documents"},"dhl_temporary_import_export":{"code":"WM","meta":{"configurable":true},"type":"boolean","label":"Dhl Temporary Import Export"},"dhl_under_bond_guarantee":{"code":"WN","meta":{"configurable":true},"type":"boolean","label":"Dhl Under Bond Guarantee"},"dhl_export_declaration":{"code":"WO","meta":{"configurable":true},"type":"boolean","label":"Dhl Export Declaration"},"dhl_exporter_validation":{"code":"WP","meta":{"configurable":true},"type":"boolean","label":"Dhl Exporter Validation"},"dhl_certificate_of_origin":{"code":"WQ","meta":{"configurable":true},"type":"boolean","label":"Dhl Certificate Of Origin"},"dhl_document_translation":{"code":"WR","meta":{"configurable":true},"type":"boolean","label":"Dhl Document Translation"},"dhl_personal_effects":{"code":"WS","meta":{"configurable":true},"type":"boolean","label":"Dhl Personal Effects"},"dhl_paperless_trade":{"code":"WY","meta":{"category":"PAPERLESS","configurable":true},"type":"boolean","label":"Dhl Paperless Trade"},"dhl_import_export_taxes":{"code":"XB","meta":{"configurable":true},"type":"boolean","label":"Dhl Import Export Taxes"},"dhl_unrecoverable_origin_tax":{"code":"XC","meta":{"configurable":true},"type":"boolean","label":"Dhl Unrecoverable Origin Tax"},"dhl_quarantine_inspection":{"code":"XD","meta":{"configurable":true},"type":"boolean","label":"Dhl Quarantine Inspection"},"dhl_merchandise_process":{"code":"XE","meta":{"configurable":true},"type":"boolean","label":"Dhl Merchandise Process"},"dhl_domestic_postal_tax":{"code":"XF","meta":{"configurable":true},"type":"boolean","label":"Dhl Domestic Postal Tax"},"dhl_tier_two_tax":{"code":"XG","meta":{"configurable":true},"type":"boolean","label":"Dhl Tier Two Tax"},"dhl_tier_three_tax":{"code":"XH","meta":{"configurable":true},"type":"boolean","label":"Dhl Tier Three Tax"},"dhl_import_penalty":{"code":"XI","meta":{"configurable":true},"type":"boolean","label":"Dhl Import Penalty"},"dhl_cargo_zone_process":{"code":"XJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Cargo Zone Process"},"dhl_import_export_duties":{"code":"XX","meta":{"configurable":true},"type":"boolean","label":"Dhl Import Export Duties"},"dhl_premium_09_00":{"code":"Y1","meta":{"configurable":true},"type":"boolean","label":"Dhl Premium 09 00"},"dhl_premium_10_30":{"code":"Y2","meta":{"configurable":true},"type":"boolean","label":"Dhl Premium 10 30"},"dhl_premium_12_00":{"code":"Y3","meta":{"configurable":true},"type":"boolean","label":"Dhl Premium 12 00"},"dhl_over_sized_piece_b":{"code":"YB","meta":{"configurable":true},"type":"boolean","label":"Dhl Over Sized Piece B"},"dhl_over_handled_piece_c":{"code":"YC","meta":{"configurable":true},"type":"boolean","label":"Dhl Over Handled Piece C"},"dhl_multipiece_shipment":{"code":"YE","meta":{"configurable":true},"type":"boolean","label":"Dhl Multipiece Shipment"},"dhl_over_weight_piece_f":{"code":"YF","meta":{"configurable":true},"type":"boolean","label":"Dhl Over Weight Piece F"},"dhl_over_sized_piece_g":{"code":"YG","meta":{"configurable":true},"type":"boolean","label":"Dhl Over Sized Piece G"},"dhl_over_handled_piece_h":{"code":"YH","meta":{"configurable":true},"type":"boolean","label":"Dhl Over Handled Piece H"},"dhl_premium_9_00_i":{"code":"YI","meta":{"configurable":true},"type":"boolean","label":"Dhl Premium 9 00 I"},"dhl_premium_10_30_j":{"code":"YJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Premium 10 30 J"},"dhl_premium_12_00_k":{"code":"YK","meta":{"configurable":true},"type":"boolean","label":"Dhl Premium 12 00 K"},"dhl_paket_shipment":{"code":"YV","meta":{"configurable":true},"type":"boolean","label":"Dhl Paket Shipment"},"dhl_breakbulk_mother":{"code":"YW","meta":{"configurable":true},"type":"boolean","label":"Dhl Breakbulk Mother"},"dhl_breakbulk_baby":{"code":"YX","meta":{"configurable":true},"type":"boolean","label":"Dhl Breakbulk Baby"},"dhl_over_weight_piece_y":{"code":"YY","meta":{"configurable":true},"type":"boolean","label":"Dhl Over Weight Piece Y"},"dhl_customer_claim":{"code":"ZA","meta":{"configurable":true},"type":"boolean","label":"Dhl Customer Claim"},"dhl_damage_compensation":{"code":"ZB","meta":{"configurable":true},"type":"boolean","label":"Dhl Damage Compensation"},"dhl_loss_compensation":{"code":"ZC","meta":{"configurable":true},"type":"boolean","label":"Dhl Loss Compensation"},"dhl_customer_rebate":{"code":"ZD","meta":{"configurable":true},"type":"boolean","label":"Dhl Customer Rebate"},"dhl_e_com_discount":{"code":"ZE","meta":{"configurable":true},"type":"boolean","label":"Dhl E Com Discount"},"dhl_shipment_content":{"code":"content","meta":{"configurable":true},"type":"string","label":"Dhl Shipment Content"}},"readme":null},{"id":"dhl_parcel_de","carrier_name":"dhl_parcel_de","display_name":"DHL Germany","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Pickup","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":false,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":false,"sensitive":false,"type":"string","label":"Password"},"client_id":{"name":"client_id","required":false,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":false,"sensitive":false,"type":"string","label":"Client Secret"},"account_country_code":{"default":"DE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_type":{"code":"label_type","enum":["PDF_A4","ZPL2_A4","PDF_910_300_700","ZPL2_910_300_700","PDF_910_300_700_oz","ZPL2_910_300_700_oz","PDF_910_300_710","ZPL2_910_300_710","PDF_910_300_600","ZPL2_910_300_600","PDF_910_300_610","ZPL2_910_300_610","PDF_910_300_400","ZPL2_910_300_400","PDF_910_300_410","ZPL2_910_300_410","PDF_910_300_300","ZPL2_910_300_300","PDF_910_300_300_oz","ZPL2_910_300_300_oz"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"language":{"code":"language","default":"en","enum":["de","en"],"name":"language","required":false,"type":"string","label":"Language"},"default_billing_number":{"code":"default_billing_number","default":"33333333330102","name":"default_billing_number","required":false,"type":"string","label":"Default Billing Number"},"service_billing_numbers":{"code":"service_billing_numbers","default":[{"billing_number":"33333333330102","service":"dhl_parcel_de_paket"},{"billing_number":"33333333335301","service":"dhl_parcel_de_paket_international"},{"billing_number":"33333333335401","service":"dhl_parcel_de_europaket"},{"billing_number":"33333333336201","service":"dhl_parcel_de_kleinpaket"},{"billing_number":"33333333336601","service":"dhl_parcel_de_warenpost_international"},{"billing_number":"33333333330701","service":"dhl_parcel_de_retoure"}],"fields":{"billing_number":{"name":"billing_number","required":true,"type":"string"},"name":{"name":"name","required":false,"type":"string"},"service":{"enum":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket","dhl_parcel_de_europaket","dhl_parcel_de_paket_international","dhl_parcel_de_warenpost_international","dhl_parcel_de_retoure"],"name":"service","required":true,"type":"string"}},"item_type":"ServiceBillingNumberType","name":"service_billing_numbers","required":false,"type":"list","label":"Service Billing Numbers"},"pickup_billing_number":{"code":"pickup_billing_number","default":"22222222220801","name":"pickup_billing_number","required":false,"type":"string","label":"Pickup Billing Number"},"return_billing_number":{"code":"return_billing_number","default":"33333333330701","name":"return_billing_number","required":false,"type":"string","label":"Return Billing Number"},"profile":{"code":"profile","name":"profile","required":false,"type":"string","label":"Profile"},"cost_center":{"code":"cost_center","name":"cost_center","required":false,"type":"string","label":"Cost Center"},"creation_software":{"code":"creation_software","name":"creation_software","required":false,"type":"string","label":"Creation Software"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"dhl_parcel_de_paket":"V01PAK","dhl_parcel_de_kleinpaket":"V62KP","dhl_parcel_de_europaket":"V54EPAK","dhl_parcel_de_paket_international":"V53WPAK","dhl_parcel_de_warenpost_international":"V66WPI","dhl_parcel_de_retoure":"V07PAK"},"shipping_options":{"dhl_parcel_de_preferred_neighbour":{"code":"preferredNeighbour","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Preferred neighbour for delivery if recipient not home"},"type":"string","label":"Preferred Neighbour"},"dhl_parcel_de_preferred_location":{"code":"preferredLocation","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Preferred drop-off location (e.g., garage, shed)"},"type":"string","label":"Preferred Location"},"dhl_parcel_de_named_person_only":{"code":"namedPersonOnly","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Delivery only to the named recipient"},"type":"boolean","label":"Named Person Only"},"dhl_parcel_de_signed_for_by_recipient":{"code":"signedForByRecipient","meta":{"category":"SIGNATURE","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Require signature from recipient"},"type":"boolean","label":"Signed For By Recipient"},"dhl_parcel_de_preferred_day":{"code":"preferredDay","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Preferred delivery day (format: YYYY-MM-DD)"},"type":"string","label":"Preferred Day"},"dhl_parcel_de_no_neighbour_delivery":{"code":"noNeighbourDelivery","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Do not deliver to neighbours"},"type":"boolean","label":"No Neighbour Delivery"},"dhl_parcel_de_additional_insurance":{"code":"additionalInsurance","meta":{"category":"INSURANCE","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_kleinpaket","dhl_parcel_de_warenpost_international"],"configurable":false,"help":"Additional insurance value in EUR (0-2500, 0-25000, or 0-50000)"},"type":"float","label":"Additional Insurance"},"dhl_parcel_de_bulky_goods":{"code":"bulkyGoods","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Mark shipment as bulky goods (Sperrgut)"},"type":"boolean","label":"Bulky Goods"},"dhl_parcel_de_cash_on_delivery":{"code":"cashOnDelivery","meta":{"category":"COD","compatible_services":["dhl_parcel_de_paket"],"configurable":false,"help":"Cash on delivery amount in EUR"},"type":"float","label":"Cash On Delivery"},"dhl_parcel_de_individual_sender_requirement":{"code":"individualSenderRequirement","meta":{"category":"INSTRUCTIONS","configurable":true,"help":"Custom sender note for the label"},"type":"string","label":"Individual Sender Requirement"},"dhl_parcel_de_premium":{"code":"premium","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Premium shipping service"},"type":"boolean","label":"Premium"},"dhl_parcel_de_closest_drop_point":{"code":"closestDropPoint","meta":{"category":"PUDO","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Deliver to closest drop point (CDP)"},"type":"boolean","label":"Closest Drop Point"},"dhl_parcel_de_parcel_outlet_routing":{"code":"parcelOutletRouting","meta":{"category":"PUDO","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":true,"help":"Filial routing - deliver to retail outlet"},"type":"string","label":"Parcel Outlet Routing"},"dhl_parcel_de_postal_delivery_duty_paid":{"code":"postalDeliveryDutyPaid","meta":{"category":"PAPERLESS","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket"],"configurable":true,"help":"Postal Delivered Duty Paid (pDDP) - sender pays customs duties"},"type":"boolean","label":"Postal Delivery Duty Paid"},"dhl_parcel_de_postal_charges":{"code":"postalCharges","meta":{"configurable":false},"type":"float","label":"Postal Charges"},"dhl_parcel_de_post_number":{"code":"postNumber","meta":{"category":"PUDO","configurable":false,"help":"Postnummer for Packstation delivery"},"type":"string","label":"Post Number"},"dhl_parcel_de_retail_id":{"code":"retailID","meta":{"category":"PUDO","configurable":false,"help":"Retail outlet ID"},"type":"string","label":"Retail Id"},"dhl_parcel_de_po_box_id":{"code":"poBoxID","meta":{"category":"PUDO","configurable":false,"help":"PO Box ID"},"type":"string","label":"Po Box Id"},"dhl_parcel_de_shipper_customs_ref":{"code":"shipperCustomsRef","meta":{"category":"INVOICE","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Sender EORI number for customs"},"type":"string","label":"Shipper Customs Ref"},"dhl_parcel_de_consignee_customs_ref":{"code":"consigneeCustomsRef","meta":{"category":"INVOICE","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Recipient ID for customs"},"type":"string","label":"Consignee Customs Ref"},"dhl_parcel_de_permit_no":{"code":"permitNo","meta":{"category":"INVOICE","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Export permit number"},"type":"string","label":"Permit No"},"dhl_parcel_de_attestation_no":{"code":"attestationNo","meta":{"category":"INVOICE","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Attestation number"},"type":"string","label":"Attestation No"},"dhl_parcel_de_has_electronic_export_notification":{"code":"hasElectronicExportNotification","meta":{"category":"PAPERLESS","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Electronic export notification (EEN)"},"type":"boolean","label":"Has Electronic Export Notification"},"dhl_parcel_de_MRN":{"code":"MRN","meta":{"category":"INVOICE","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Movement Reference Number for customs declaration"},"type":"string","label":"Mrn"},"dhl_parcel_de_locker_id":{"code":"lockerID","meta":{"category":"LOCKER","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":false,"help":"Packstation locker ID"},"type":"integer","label":"Locker Id"},"dhl_parcel_de_ident_check":{"code":"identCheck","fields":{"dateOfBirth":{"name":"dateOfBirth","required":false,"type":"string"},"firstName":{"name":"firstName","required":false,"type":"string"},"lastName":{"name":"lastName","required":false,"type":"string"},"minimumAge":{"name":"minimumAge","required":false,"type":"string"}},"meta":{"compatible_services":["dhl_parcel_de_paket"],"configurable":false},"type":"object","label":"Ident Check"},"dhl_parcel_de_return_enabled":{"code":"returnEnabled","meta":{"category":"RETURN","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":true,"help":"Enable DHL Retoure - include return label with shipment"},"type":"boolean","label":"Return Enabled"},"dhl_parcel_de_return_receiver_id":{"code":"returnReceiverId","meta":{"category":"RETURN","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":true,"help":"DHL Receiver ID for returns (configured in DHL Business Portal)"},"type":"string","label":"Return Receiver Id"},"dhl_parcel_de_return_billing_number":{"code":"returnBillingNumber","meta":{"category":"RETURN","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":true,"help":"Billing number for returns (if different from main billing number)"},"type":"string","label":"Return Billing Number"},"dhl_parcel_de_return_reference":{"code":"returnReference","meta":{"category":"RETURN","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":true,"help":"Reference text for the return label"},"type":"string","label":"Return Reference"},"dhl_parcel_de_dhl_retoure":{"code":"dhlRetoure","fields":{"billingNumber":{"name":"billingNumber","required":false,"type":"string"},"refNo":{"name":"refNo","required":false,"type":"string"},"returnAddress":{"fields":{"additionalAddressInformation1":{"name":"additionalAddressInformation1","required":false,"type":"string"},"additionalAddressInformation2":{"name":"additionalAddressInformation2","required":false,"type":"string"},"addressHouse":{"name":"addressHouse","required":false,"type":"string"},"addressStreet":{"name":"addressStreet","required":false,"type":"string"},"city":{"name":"city","required":false,"type":"string"},"contactName":{"name":"contactName","required":false,"type":"string"},"country":{"name":"country","required":false,"type":"string"},"dispatchingInformation":{"name":"dispatchingInformation","required":false,"type":"string"},"email":{"name":"email","required":false,"type":"string"},"lockerID":{"name":"lockerID","required":false,"type":"integer"},"name":{"name":"name","required":false,"type":"string"},"name1":{"name":"name1","required":false,"type":"string"},"name2":{"name":"name2","required":false,"type":"string"},"name3":{"name":"name3","required":false,"type":"string"},"phone":{"name":"phone","required":false,"type":"string"},"poBoxID":{"name":"poBoxID","required":false,"type":"integer"},"postNumber":{"name":"postNumber","required":false,"type":"integer"},"postalCode":{"name":"postalCode","required":false,"type":"string"},"retailID":{"name":"retailID","required":false,"type":"integer"},"state":{"name":"state","required":false,"type":"string"}},"name":"returnAddress","required":false,"type":"object"}},"meta":{"category":"RETURN","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":false,"help":"DHL Retoure configuration for return labels"},"type":"object","label":"Dhl Retoure"},"dhl_parcel_de_visual_check_of_age":{"code":"visualCheckOfAge","enum":["A16","A18"],"meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Visual age check at delivery (A16=16+, A18=18+)"},"type":"string","label":"Visual Check Of Age"},"dhl_parcel_de_endorsement":{"code":"endorsement","default":"RETURN","enum":["RETURN","ABANDON"],"meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Action if delivery fails (RETURN or ABANDON)"},"type":"string","label":"Endorsement"},"dhl_parcel_de_economy":{"code":"economy","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Economy shipping service"},"type":"boolean","label":"Economy"},"dhl_parcel_de_gogreen_plus":{"code":"goGreenPlus","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket","dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"GoGreen Plus climate-neutral shipping"},"type":"boolean","label":"Gogreen Plus"},"dhl_parcel_de_label_type":{"code":"label_type","enum":["PDF_A4","ZPL2_A4","PDF_910_300_700","ZPL2_910_300_700","PDF_910_300_700_oz","ZPL2_910_300_700_oz","PDF_910_300_710","ZPL2_910_300_710","PDF_910_300_600","ZPL2_910_300_600","PDF_910_300_610","ZPL2_910_300_610","PDF_910_300_400","ZPL2_910_300_400","PDF_910_300_410","ZPL2_910_300_410","PDF_910_300_300","ZPL2_910_300_300","PDF_910_300_300_oz","ZPL2_910_300_300_oz"],"meta":{"category":"DELIVERY_OPTIONS","configurable":true,"help":"Label format for this shipping method (PDF or ZPL variant). Overrides carrier connection setting."},"type":"string","label":"Label Type"},"dhl_parcel_de_cost_center":{"code":"cost_center","meta":{"category":"INVOICE","configurable":true,"help":"Cost center code for billing allocation. Overrides carrier connection setting."},"type":"string","label":"Cost Center"},"dhl_parcel_de_return_service_code":{"code":"return_service_code","enum":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket","dhl_parcel_de_europaket","dhl_parcel_de_paket_international","dhl_parcel_de_warenpost_international","dhl_parcel_de_retoure"],"meta":{"category":"RETURN","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":true,"help":"Return service for this shipping method (determines return billing number). Overrides carrier connection setting."},"type":"string","label":"Return Service Code"},"dhl_parcel_de_profile":{"code":"profile","meta":{"category":"OTHER","configurable":true,"help":"DHL shipping profile (Gruppenprofil). Overrides carrier connection setting."},"type":"string","label":"Profile"}},"readme":null},{"id":"dhl_poland","carrier_name":"dhl_poland","display_name":"DHL Parcel Poland","integration_status":"production-ready","website":"https://dhl24.com.pl/en","description":"Global Logistics and International Shipping Poland.","documentation":"https://dhl24.com.pl/en/webapi2/doc.html","is_enabled":true,"capabilities":["Shipping","Rating","Tracking"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"default":"PL","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"dhl_poland_premium":"PR","dhl_poland_polska":"AH","dhl_poland_09":"09","dhl_poland_12":"12","dhl_poland_connect":"EK","dhl_poland_international":"PI"},"shipping_options":{"dhl_poland_delivery_in_18_22_hours":{"code":"1722","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DHL Poland Delivery In 18 22 Hours"},"dhl_poland_delivery_on_saturday":{"code":"SATURDAY","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DHL Poland Delivery On Saturday"},"dhl_poland_pickup_on_staturday":{"code":"NAD_SOBOTA","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DHL Poland Pickup On Staturday"},"dhl_poland_insuration":{"code":"UBEZP","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"DHL Poland Insuration"},"dhl_poland_collect_on_delivery":{"code":"COD","meta":{"category":"COD","configurable":true},"type":"float","label":"DHL Poland Collect On Delivery"},"dhl_poland_information_to_receiver":{"code":"PDI","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"DHL Poland Information To Receiver"},"dhl_poland_return_of_document":{"code":"ROD","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"DHL Poland Return Of Document"},"dhl_poland_proof_of_delivery":{"code":"POD","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DHL Poland Proof Of Delivery"},"dhl_poland_delivery_to_neighbour":{"code":"SAS","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DHL Poland Delivery To Neighbour"},"dhl_poland_self_collect":{"code":"ODB","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"DHL Poland Self Collect"}},"readme":null},{"id":"dhl_universal","carrier_name":"dhl_universal","display_name":"DHL Universal","integration_status":"production-ready","website":"https://www.dhl.com/","description":"DHL is a German logistics company providing courier, package delivery and express mail service, delivering over 1.8 billion parcels per year.","documentation":"https://developer.dhl.com/api-reference/shipment-tracking","is_enabled":true,"capabilities":["Tracking"],"connection_fields":{"consumer_key":{"name":"consumer_key","required":true,"sensitive":false,"type":"string","label":"Consumer Key"},"consumer_secret":{"name":"consumer_secret","required":true,"sensitive":false,"type":"string","label":"Consumer Secret"},"language":{"default":"en","enum":["en","de"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"DE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"dicom","carrier_name":"dicom","display_name":"Dicom","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"billing_account":{"name":"billing_account","required":false,"sensitive":false,"type":"string","label":"Billing Account"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"dpd","carrier_name":"dpd","display_name":"DPD","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"delis_id":{"name":"delis_id","required":true,"sensitive":false,"type":"string","label":"Delis Id"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"depot":{"name":"depot","required":false,"sensitive":false,"type":"string","label":"Depot"},"message_language":{"default":"en_EN","name":"message_language","required":false,"sensitive":false,"type":"string","label":"Message Language"},"account_country_code":{"default":"BE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"dpd_cl":"CL","dpd_express_10h":"E10","dpd_express_12h":"E12","dpd_express_18h_guarantee":"E18","dpd_express_b2b_predict":"B2B MSG option"},"shipping_options":{"dpd_order_type":{"code":"orderType","meta":{"configurable":true},"type":"string","label":"DPD Order Type"},"dpd_saturday_delivery":{"code":"saturdayDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DPD Saturday Delivery"},"dpd_ex_works_delivery":{"code":"exWorksDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DPD Ex Works Delivery"},"dpd_tyres":{"code":"tyres","meta":{"configurable":true},"type":"boolean","label":"DPD Tyres"},"dpd_parcel_shop_delivery":{"code":"parcelShopDelivery","meta":{"category":"PUDO","configurable":true},"type":"string","label":"DPD Parcel Shop Delivery"}},"readme":null},{"id":"dpd_meta","carrier_name":"dpd_meta","display_name":"DPD Global","integration_status":"beta","website":"https://www.dpdgroup.com","description":"DPD Global shipping integration for Karrio","documentation":"https://api-preprod.dpsin.dpdgroup.com:8443/shipping/v1/meta-api-docs","is_enabled":true,"capabilities":["Pickup","Rating","Shipping"],"connection_fields":{"dpd_login":{"name":"dpd_login","required":false,"sensitive":false,"type":"string","label":"Dpd Login"},"dpd_password":{"name":"dpd_password","required":false,"sensitive":false,"type":"string","label":"Dpd Password"},"dpd_client_id":{"name":"dpd_client_id","required":false,"sensitive":false,"type":"string","label":"Dpd Client Id"},"dpd_client_secret":{"name":"dpd_client_secret","required":false,"sensitive":false,"type":"string","label":"Dpd Client Secret"},"dpd_bucode":{"name":"dpd_bucode","required":false,"sensitive":false,"type":"string","label":"Dpd Bucode"},"customer_id":{"name":"customer_id","required":false,"sensitive":false,"type":"string","label":"Customer Id"},"customer_account_number":{"name":"customer_account_number","required":false,"sensitive":false,"type":"string","label":"Customer Account Number"},"customer_sub_account_number":{"name":"customer_sub_account_number","required":false,"sensitive":false,"type":"string","label":"Customer Sub Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"label_type":{"code":"label_type","enum":["A4","A5","A6","A7"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"label_format":{"code":"label_format","enum":["PDF","EPL","ZPL","TIFF","PNG","PPR","SPD","Z2D","THE","XML","XML2D","THEPSG","ZPLPSG","ZPL300","JSON","PS","DATA","CLP","HTML"],"name":"label_format","required":false,"type":"string","label":"Label Format"},"label_paper_format":{"code":"label_paper_format","enum":["A4","A5","A6","A7"],"name":"label_paper_format","required":false,"type":"string","label":"Label Paper Format"},"label_printer_position":{"code":"label_printer_position","enum":["UPPER_LEFT","UPPER_RIGHT","LOWER_LEFT","LOWER_RIGHT"],"name":"label_printer_position","required":false,"type":"string","label":"Label Printer Position"},"dropoff_type":{"code":"dropoff_type","enum":["FULL_LABEL","QR_CODE","BOTH"],"name":"dropoff_type","required":false,"type":"string","label":"Dropoff Type"},"sending_depot":{"code":"sending_depot","name":"sending_depot","required":false,"type":"string","label":"Sending Depot"},"simulate":{"code":"simulate","name":"simulate","required":false,"type":"boolean","label":"Simulate"},"extra_barcode":{"code":"extra_barcode","name":"extra_barcode","required":false,"type":"boolean","label":"Extra Barcode"},"with_document":{"code":"with_document","name":"with_document","required":false,"type":"boolean","label":"With Document"}},"shipping_services":{"dpd_meta_classic":"CL","dpd_meta_express_830":"E830","dpd_meta_express_12":"E12","dpd_meta_express_18":"E18","dpd_meta_international_express":"IE2","dpd_meta_parcel_letter":"PL","dpd_meta_mail":"MAIL","dpd_meta_max":"MAX"},"shipping_options":{"dpd_meta_saturday_delivery":{"code":"saturday_delivery","help":"Enable Saturday delivery service","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DPD Meta Saturday Delivery"},"dpd_meta_small_parcel":{"code":"small_parcel","help":"Mark shipment as small package (Kleinpaket) for reduced rates","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DPD Meta Small Parcel"},"dpd_meta_exchange_service":{"code":"exchange_service","help":"Enable exchange service (Austauschservice) — simultaneous delivery and pickup","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DPD Meta Exchange Service"},"dpd_meta_ex_works":{"code":"ex_works","help":"Enable EX Works delivery mode","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DPD Meta Ex Works"},"dpd_meta_delivery_date_from":{"code":"delivery_date_from","help":"Earliest delivery date (YYYY-MM-DD)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"DPD Meta Delivery Date From"},"dpd_meta_delivery_date_to":{"code":"delivery_date_to","help":"Latest delivery date (YYYY-MM-DD)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"DPD Meta Delivery Date To"},"dpd_meta_delivery_time_from":{"code":"delivery_time_from","help":"Earliest delivery time (HH:MM)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"DPD Meta Delivery Time From"},"dpd_meta_delivery_time_to":{"code":"delivery_time_to","help":"Latest delivery time (HH:MM)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"DPD Meta Delivery Time To"},"dpd_meta_dropoff_type":{"code":"dropoff_type","help":"Drop-off type for parcel shop delivery (FULL_LABEL, QR_CODE, BOTH)","meta":{"category":"PUDO","configurable":true},"type":"string","label":"DPD Meta Dropoff Type"},"dpd_meta_parcel_shop_id":{"code":"parcel_shop_id","help":"DPD ParcelShop ID for shop delivery","meta":{"category":"PUDO","configurable":true},"type":"string","label":"DPD Meta Parcel Shop ID"},"dpd_meta_notification_email":{"code":"notification_email","help":"Email for delivery notifications (Predict service)","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"DPD Meta Notification Email"},"dpd_meta_notification_sms":{"code":"notification_sms","help":"SMS number for delivery notifications","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"DPD Meta Notification Sms"},"dpd_meta_insurance_description":{"code":"insurance_description","help":"Free text description for additional insurance purpose","meta":{"category":"INSURANCE","configurable":true},"type":"string","label":"DPD Meta Insurance Description"},"dpd_meta_cod_collect_type":{"code":"cod_collect_type","help":"COD collection type (cash, cheque, etc.)","meta":{"category":"COD","configurable":true},"type":"string","label":"DPD Meta COD Collect Type"},"dpd_meta_cod_bank_code":{"code":"cod_bank_code","help":"Bank code for COD payment","meta":{"category":"COD","configurable":false},"type":"string","label":"DPD Meta COD Bank Code"},"dpd_meta_cod_bank_name":{"code":"cod_bank_name","help":"Bank name for COD payment","meta":{"category":"COD","configurable":false},"type":"string","label":"DPD Meta COD Bank Name"},"dpd_meta_cod_bank_account_number":{"code":"cod_bank_account_number","help":"Bank account number for COD payment","meta":{"category":"COD","configurable":false},"type":"string","label":"DPD Meta COD Bank Account Number"},"dpd_meta_cod_bank_account_name":{"code":"cod_bank_account_name","help":"Account holder name for COD payment","meta":{"category":"COD","configurable":false},"type":"string","label":"DPD Meta COD Bank Account Name"},"dpd_meta_cod_iban":{"code":"cod_iban","help":"IBAN for COD payment","meta":{"category":"COD","configurable":false},"type":"string","label":"DPD Meta COD Iban"},"dpd_meta_cod_bic":{"code":"cod_bic","help":"BIC/SWIFT code for COD payment","meta":{"category":"COD","configurable":false},"type":"string","label":"DPD Meta COD Bic"},"dpd_meta_cod_purpose":{"code":"cod_purpose","help":"Purpose text for COD payment","meta":{"category":"COD","configurable":true},"type":"string","label":"DPD Meta COD Purpose"},"dpd_meta_dangerous_goods":{"code":"dangerous_goods","help":"Enable shipment of dangerous goods (Gefahrgut)","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"DPD Meta Dangerous Goods"},"dpd_meta_dg_identification_class":{"code":"dg_identification_class","help":"Hazard identification number (Identifikationsklasse)","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Identification Class"},"dpd_meta_dg_un_number":{"code":"dg_un_number","help":"UN number for the hazardous material","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Un Number"},"dpd_meta_dg_weight":{"code":"dg_weight","help":"Weight of dangerous goods package in kg","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"float","label":"Dg Weight"},"dpd_meta_dg_description":{"code":"dg_description","help":"Detailed description of the dangerous goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Description"},"dpd_meta_dg_hazard_factor":{"code":"dg_hazard_factor","help":"Factor defining the risk level of the hazardous goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Hazard Factor"},"dpd_meta_dg_hazard_class":{"code":"dg_hazard_class","help":"Official hazard classification (e.g. 3 for flammable liquids)","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Hazard Class"},"dpd_meta_dg_nag_entry":{"code":"dg_nag_entry","help":"Not otherwise specified (N.A.G.) entry for hazardous goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Nag Entry"},"dpd_meta_dg_packing_group":{"code":"dg_packing_group","help":"Packing group (I, II, III) indicating packaging strength","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Packing Group"},"dpd_meta_dg_packing_code":{"code":"dg_packing_code","help":"DPD-specific packaging code for dangerous goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Packing Code"},"dpd_meta_dg_subsidiary_risks":{"code":"dg_subsidiary_risks","help":"Additional hazard classifications (subsidiary risks)","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Subsidiary Risks"},"dpd_meta_dg_tunnel_restriction_code":{"code":"dg_tunnel_restriction_code","help":"Code defining tunnel access restrictions for hazardous goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Tunnel Restriction Code"},"dpd_meta_return_enabled":{"code":"return_enabled","help":"Enable DPD return label creation","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Return Enabled"},"dpd_meta_return_description":{"code":"return_description","help":"Description or reason for the return shipment","meta":{"category":"RETURN","configurable":true},"type":"string","label":"Return Description"},"dpd_meta_include_return_label":{"code":"include_return_label","help":"Add a printed return label with the outbound shipment (Beilegeretoure)","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Include Return Label"},"dpd_meta_label_format":{"code":"label_format","meta":{"configurable":false},"type":"string","label":"Label Format"},"dpd_meta_label_paper_format":{"code":"label_paper_format","meta":{"configurable":false},"type":"string","label":"Label Paper Format"},"dpd_meta_label_printer_position":{"code":"label_printer_position","meta":{"configurable":false},"type":"string","label":"Label Printer Position"},"dpd_meta_simulate":{"code":"simulate","meta":{"configurable":false},"type":"boolean","label":"Simulate"},"dpd_meta_extra_barcode":{"code":"extra_barcode","meta":{"configurable":false},"type":"boolean","label":"Extra Barcode"},"dpd_meta_with_document":{"code":"with_document","meta":{"configurable":false},"type":"boolean","label":"With Document"},"cash_on_delivery":{"code":"cash_on_delivery","help":"Cash on delivery amount","meta":{"category":"COD","configurable":true},"type":"float","label":"Cash On Delivery"},"insurance":{"code":"insurance","help":"Additional insurance value","meta":{"category":"INSURANCE","configurable":false},"type":"float","label":"Insurance"},"declared_value":{"code":"declared_value","help":"Declared value for customs","meta":{"category":"INVOICE","configurable":true},"type":"float","label":"Declared Value"},"currency":{"code":"currency","help":"Currency code for values","meta":{"configurable":false},"type":"string","label":"Currency"}},"readme":null},{"id":"dtdc","carrier_name":"dtdc","display_name":"DTDC","integration_status":"beta","website":"https://dtdc.in","description":"DTDC is India's leading express distribution and logistics solutions provider, offering domestic and international courier services.","documentation":"https://www.dtdc.in/dtdcapi/trackCnno.do","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"customer_code":{"name":"customer_code","required":true,"sensitive":false,"type":"string","label":"Customer Code"},"username":{"name":"username","required":false,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":false,"sensitive":false,"type":"string","label":"Password"},"account_country_code":{"default":"IN","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_type":{"code":"label_type","enum":["SHIP_LABEL_A4","SHIP_LABEL_A6","SHIP_LABEL_POD","SHIP_LABEL_4X6","ROUTE_LABEL_A4","ROUTE_LABEL_4X4","ADDR_LABEL_A4","ADDR_LABEL_4X2"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"dtdc_b2c_priority":"B2C PRIORITY","dtdc_b2c_economy":"B2C SMART EXPRESS","dtdc_b2c_express":"B2C PREMIUM","dtdc_b2c_ground":"B2C GROUND ECONOMY","dtdc_priority":"PRIORITY","dtdc_ground_express":"GROUND EXPRESS","dtdc_premium":"PREMIUM","dtdc_economy_ground":"GEC","dtdc_standard_express":"STD EXP-A"},"shipping_options":{"dtdc_is_risk_surcharge_applicable":{"code":"is_risk_surcharge_applicable","meta":{"configurable":true},"type":"boolean","label":"Is Risk Surcharge Applicable"},"dtdc_invoice_number":{"code":"invoice_number","meta":{"configurable":true},"type":"string","label":"Invoice Number"},"dtdc_invoice_date":{"code":"invoice_date","meta":{"configurable":true},"type":"string","label":"Invoice Date"},"dtdc_commodity_id":{"code":"commodity_id","meta":{"configurable":true},"type":"string","label":"Commodity Id"},"dtdc_cod_amount":{"code":"cod_amount","meta":{"configurable":true},"type":"float","label":"Cod Amount"},"dtdc_eway_bill":{"code":"eway_bill","meta":{"configurable":true},"type":"string","label":"Eway Bill"},"dtdc_cod_collection_mode":{"code":"cod_collection_mode","enum":["CASH","CHEQUE"],"meta":{"configurable":true},"type":"string","label":"Cod Collection Mode"}},"readme":null},{"id":"easypost","carrier_name":"easypost","display_name":"EasyPost","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"easypost_amazonmws_ups_rates":"UPS Rates","easypost_amazonmws_usps_rates":"USPS Rates","easypost_amazonmws_fedex_rates":"FedEx Rates","easypost_amazonmws_ups_labels":"UPS Labels","easypost_amazonmws_usps_labels":"USPS Labels","easypost_amazonmws_fedex_labels":"FedEx Labels","easypost_amazonmws_ups_tracking":"UPS Tracking","easypost_amazonmws_usps_tracking":"USPS Tracking","easypost_amazonmws_fedex_tracking":"FedEx Tracking","easypost_apc_parcel_connect_book_service":"parcelConnectBookService","easypost_apc_parcel_connect_expedited_ddp":"parcelConnectExpeditedDDP","easypost_apc_parcel_connect_expedited_ddu":"parcelConnectExpeditedDDU","easypost_apc_parcel_connect_priority_ddp":"parcelConnectPriorityDDP","easypost_apc_parcel_connect_priority_ddp_delcon":"parcelConnectPriorityDDPDelcon","easypost_apc_parcel_connect_priority_ddu":"parcelConnectPriorityDDU","easypost_apc_parcel_connect_priority_ddu_delcon":"parcelConnectPriorityDDUDelcon","easypost_apc_parcel_connect_priority_ddupqw":"parcelConnectPriorityDDUPQW","easypost_apc_parcel_connect_standard_ddu":"parcelConnectStandardDDU","easypost_apc_parcel_connect_standard_ddupqw":"parcelConnectStandardDDUPQW","easypost_apc_parcel_connect_packet_ddu":"parcelConnectPacketDDU","easypost_asendia_pmi":"PMI","easypost_asendia_e_packet":"ePacket","easypost_asendia_ipa":"IPA","easypost_asendia_isal":"ISAL","easypost_asendia_us_ads":"ADS","easypost_asendia_us_air_freight_inbound":"AirFreightInbound","easypost_asendia_us_air_freight_outbound":"AirFreightOutbound","easypost_asendia_us_domestic_bound_printer_matter_expedited":"AsendiaDomesticBoundPrinterMatterExpedited","easypost_asendia_us_domestic_bound_printer_matter_ground":"AsendiaDomesticBoundPrinterMatterGround","easypost_asendia_us_domestic_flats_expedited":"AsendiaDomesticFlatsExpedited","easypost_asendia_us_domestic_flats_ground":"AsendiaDomesticFlatsGround","easypost_asendia_us_domestic_parcel_ground_over1lb":"AsendiaDomesticParcelGroundOver1lb","easypost_asendia_us_domestic_parcel_ground_under1lb":"AsendiaDomesticParcelGroundUnder1lb","easypost_asendia_us_domestic_parcel_max_over1lb":"AsendiaDomesticParcelMAXOver1lb","easypost_asendia_us_domestic_parcel_max_under1lb":"AsendiaDomesticParcelMAXUnder1lb","easypost_asendia_us_domestic_parcel_over1lb_expedited":"AsendiaDomesticParcelOver1lbExpedited","easypost_asendia_us_domestic_parcel_under1lb_expedited":"AsendiaDomesticParcelUnder1lbExpedited","easypost_asendia_us_domestic_promo_parcel_expedited":"AsendiaDomesticPromoParcelExpedited","easypost_asendia_us_domestic_promo_parcel_ground":"AsendiaDomesticPromoParcelGround","easypost_asendia_us_bulk_freight":"BulkFreight","easypost_asendia_us_business_mail_canada_lettermail":"BusinessMailCanadaLettermail","easypost_asendia_us_business_mail_canada_lettermail_machineable":"BusinessMailCanadaLettermailMachineable","easypost_asendia_us_business_mail_economy":"BusinessMailEconomy","easypost_asendia_us_business_mail_economy_lp_wholesale":"BusinessMailEconomyLPWholesale","easypost_asendia_us_business_mail_economy_sp_wholesale":"BusinessMailEconomySPWholesale","easypost_asendia_us_business_mail_ipa":"BusinessMailIPA","easypost_asendia_us_business_mail_isal":"BusinessMailISAL","easypost_asendia_us_business_mail_priority":"BusinessMailPriority","easypost_asendia_us_business_mail_priority_lp_wholesale":"BusinessMailPriorityLPWholesale","easypost_asendia_us_business_mail_priority_sp_wholesale":"BusinessMailPrioritySPWholesale","easypost_asendia_us_marketing_mail_canada_personalized_lcp":"MarketingMailCanadaPersonalizedLCP","easypost_asendia_us_marketing_mail_canada_personalized_machineable":"MarketingMailCanadaPersonalizedMachineable","easypost_asendia_us_marketing_mail_canada_personalized_ndg":"MarketingMailCanadaPersonalizedNDG","easypost_asendia_us_marketing_mail_economy":"MarketingMailEconomy","easypost_asendia_us_marketing_mail_ipa":"MarketingMailIPA","easypost_asendia_us_marketing_mail_isal":"MarketingMailISAL","easypost_asendia_us_marketing_mail_priority":"MarketingMailPriority","easypost_asendia_us_publications_canada_lcp":"PublicationsCanadaLCP","easypost_asendia_us_publications_canada_ndg":"PublicationsCanadaNDG","easypost_asendia_us_publications_economy":"PublicationsEconomy","easypost_asendia_us_publications_ipa":"PublicationsIPA","easypost_asendia_us_publications_isal":"PublicationsISAL","easypost_asendia_us_publications_priority":"PublicationsPriority","easypost_asendia_us_epaq_elite":"ePAQElite","easypost_asendia_us_epaq_elite_custom":"ePAQEliteCustom","easypost_asendia_us_epaq_elite_dap":"ePAQEliteDAP","easypost_asendia_us_epaq_elite_ddp":"ePAQEliteDDP","easypost_asendia_us_epaq_elite_ddp_oversized":"ePAQEliteDDPOversized","easypost_asendia_us_epaq_elite_dpd":"ePAQEliteDPD","easypost_asendia_us_epaq_elite_direct_access_canada_ddp":"ePAQEliteDirectAccessCanadaDDP","easypost_asendia_us_epaq_elite_oversized":"ePAQEliteOversized","easypost_asendia_us_epaq_plus":"ePAQPlus","easypost_asendia_us_epaq_plus_custom":"ePAQPlusCustom","easypost_asendia_us_epaq_plus_customs_prepaid":"ePAQPlusCustomsPrepaid","easypost_asendia_us_epaq_plus_dap":"ePAQPlusDAP","easypost_asendia_us_epaq_plus_ddp":"ePAQPlusDDP","easypost_asendia_us_epaq_plus_economy":"ePAQPlusEconomy","easypost_asendia_us_epaq_plus_wholesale":"ePAQPlusWholesale","easypost_asendia_us_epaq_pluse_packet":"ePAQPlusePacket","easypost_asendia_us_epaq_pluse_packet_canada_customs_pre_paid":"ePAQPlusePacketCanadaCustomsPrePaid","easypost_asendia_us_epaq_pluse_packet_canada_ddp":"ePAQPlusePacketCanadaDDP","easypost_asendia_us_epaq_returns_domestic":"ePAQReturnsDomestic","easypost_asendia_us_epaq_returns_international":"ePAQReturnsInternational","easypost_asendia_us_epaq_select":"ePAQSelect","easypost_asendia_us_epaq_select_custom":"ePAQSelectCustom","easypost_asendia_us_epaq_select_customs_prepaid_by_shopper":"ePAQSelectCustomsPrepaidByShopper","easypost_asendia_us_epaq_select_dap":"ePAQSelectDAP","easypost_asendia_us_epaq_select_ddp":"ePAQSelectDDP","easypost_asendia_us_epaq_select_ddp_direct_access":"ePAQSelectDDPDirectAccess","easypost_asendia_us_epaq_select_direct_access":"ePAQSelectDirectAccess","easypost_asendia_us_epaq_select_direct_access_canada_ddp":"ePAQSelectDirectAccessCanadaDDP","easypost_asendia_us_epaq_select_economy":"ePAQSelectEconomy","easypost_asendia_us_epaq_select_oversized":"ePAQSelectOversized","easypost_asendia_us_epaq_select_oversized_ddp":"ePAQSelectOversizedDDP","easypost_asendia_us_epaq_select_pmei":"ePAQSelectPMEI","easypost_asendia_us_epaq_select_pmei_canada_customs_pre_paid":"ePAQSelectPMEICanadaCustomsPrePaid","easypost_asendia_us_epaq_select_pmeipc_postage":"ePAQSelectPMEIPCPostage","easypost_asendia_us_epaq_select_pmi":"ePAQSelectPMI","easypost_asendia_us_epaq_select_pmi_canada_customs_prepaid":"ePAQSelectPMICanadaCustomsPrepaid","easypost_asendia_us_epaq_select_pmi_canada_ddp":"ePAQSelectPMICanadaDDP","easypost_asendia_us_epaq_select_pmi_non_presort":"ePAQSelectPMINonPresort","easypost_asendia_us_epaq_select_pmipc_postage":"ePAQSelectPMIPCPostage","easypost_asendia_us_epaq_standard":"ePAQStandard","easypost_asendia_us_epaq_standard_custom":"ePAQStandardCustom","easypost_asendia_us_epaq_standard_economy":"ePAQStandardEconomy","easypost_asendia_us_epaq_standard_ipa":"ePAQStandardIPA","easypost_asendia_us_epaq_standard_isal":"ePAQStandardISAL","easypost_asendia_us_epaq_select_pmei_non_presort":"ePaqSelectPMEINonPresort","easypost_australiapost_express_post":"ExpressPost","easypost_australiapost_express_post_signature":"ExpressPostSignature","easypost_australiapost_parcel_post":"ParcelPost","easypost_australiapost_parcel_post_signature":"ParcelPostSignature","easypost_australiapost_parcel_post_extra":"ParcelPostExtra","easypost_australiapost_parcel_post_wine_plus_signature":"ParcelPostWinePlusSignature","easypost_axlehire_delivery":"AxleHireDelivery","easypost_better_trucks_next_day":"NEXT_DAY","easypost_bond_standard":"Standard","easypost_canadapost_regular_parcel":"RegularParcel","easypost_canadapost_expedited_parcel":"ExpeditedParcel","easypost_canadapost_xpresspost":"Xpresspost","easypost_canadapost_xpresspost_certified":"XpresspostCertified","easypost_canadapost_priority":"Priority","easypost_canadapost_library_books":"LibraryBooks","easypost_canadapost_expedited_parcel_usa":"ExpeditedParcelUSA","easypost_canadapost_priority_worldwide_envelope_usa":"PriorityWorldwideEnvelopeUSA","easypost_canadapost_priority_worldwide_pak_usa":"PriorityWorldwidePakUSA","easypost_canadapost_priority_worldwide_parcel_usa":"PriorityWorldwideParcelUSA","easypost_canadapost_small_packet_usa_air":"SmallPacketUSAAir","easypost_canadapost_tracked_packet_usa":"TrackedPacketUSA","easypost_canadapost_tracked_packet_usalvm":"TrackedPacketUSALVM","easypost_canadapost_xpresspost_usa":"XpresspostUSA","easypost_canadapost_xpresspost_international":"XpresspostInternational","easypost_canadapost_international_parcel_air":"InternationalParcelAir","easypost_canadapost_international_parcel_surface":"InternationalParcelSurface","easypost_canadapost_priority_worldwide_envelope_intl":"PriorityWorldwideEnvelopeIntl","easypost_canadapost_priority_worldwide_pak_intl":"PriorityWorldwidePakIntl","easypost_canadapost_priority_worldwide_parcel_intl":"PriorityWorldwideParcelIntl","easypost_canadapost_small_packet_international_air":"SmallPacketInternationalAir","easypost_canadapost_small_packet_international_surface":"SmallPacketInternationalSurface","easypost_canadapost_tracked_packet_international":"TrackedPacketInternational","easypost_canpar_ground":"Ground","easypost_canpar_select_letter":"SelectLetter","easypost_canpar_select_pak":"SelectPak","easypost_canpar_select":"Select","easypost_canpar_overnight_letter":"OvernightLetter","easypost_canpar_overnight_pak":"OvernightPak","easypost_canpar_overnight":"Overnight","easypost_canpar_select_usa":"SelectUSA","easypost_canpar_usa_pak":"USAPak","easypost_canpar_usa_letter":"USALetter","easypost_canpar_usa":"USA","easypost_canpar_international":"International","easypost_cdl_distribution":"DISTRIBUTION","easypost_cdl_same_day":"Same Day","easypost_courier_express_basic_parcel":"BASIC_PARCEL","easypost_couriersplease_domestic_priority_signature":"DomesticPrioritySignature","easypost_couriersplease_domestic_priority":"DomesticPriority","easypost_couriersplease_domestic_off_peak_signature":"DomesticOffPeakSignature","easypost_couriersplease_domestic_off_peak":"DomesticOffPeak","easypost_couriersplease_gold_domestic_signature":"GoldDomesticSignature","easypost_couriersplease_gold_domestic":"GoldDomestic","easypost_couriersplease_australian_city_express_signature":"AustralianCityExpressSignature","easypost_couriersplease_australian_city_express":"AustralianCityExpress","easypost_couriersplease_domestic_saver_signature":"DomesticSaverSignature","easypost_couriersplease_domestic_saver":"DomesticSaver","easypost_couriersplease_road_express":"RoadExpress","easypost_couriersplease_5_kg_satchel":"5KgSatchel","easypost_couriersplease_3_kg_satchel":"3KgSatchel","easypost_couriersplease_1_kg_satchel":"1KgSatchel","easypost_couriersplease_5_kg_satchel_atl":"5KgSatchelATL","easypost_couriersplease_3_kg_satchel_atl":"3KgSatchelATL","easypost_couriersplease_1_kg_satchel_atl":"1KgSatchelATL","easypost_couriersplease_500_gram_satchel":"500GramSatchel","easypost_couriersplease_500_gram_satchel_atl":"500GramSatchelATL","easypost_couriersplease_25_kg_parcel":"25KgParcel","easypost_couriersplease_10_kg_parcel":"10KgParcel","easypost_couriersplease_5_kg_parcel":"5KgParcel","easypost_couriersplease_3_kg_parcel":"3KgParcel","easypost_couriersplease_1_kg_parcel":"1KgParcel","easypost_couriersplease_500_gram_parcel":"500GramParcel","easypost_couriersplease_500_gram_parcel_atl":"500GramParcelATL","easypost_couriersplease_express_international_priority":"ExpressInternationalPriority","easypost_couriersplease_international_saver":"InternationalSaver","easypost_couriersplease_international_express_import":"InternationalExpressImport","easypost_couriersplease_domestic_tracked":"DomesticTracked","easypost_couriersplease_international_economy":"InternationalEconomy","easypost_couriersplease_international_standard":"InternationalStandard","easypost_couriersplease_international_express":"InternationalExpress","easypost_deutschepost_packet_plus":"PacketPlus","easypost_deutschepost_uk_priority_packet_plus":"PriorityPacketPlus","easypost_deutschepost_uk_priority_packet":"PriorityPacket","easypost_deutschepost_uk_priority_packet_tracked":"PriorityPacketTracked","easypost_deutschepost_uk_business_mail_registered":"BusinessMailRegistered","easypost_deutschepost_uk_standard_packet":"StandardPacket","easypost_deutschepost_uk_business_mail_standard":"BusinessMailStandard","easypost_dhl_ecom_asia_packet":"Packet","easypost_dhl_ecom_asia_parcel_direct":"ParcelDirect","easypost_dhl_ecom_asia_parcel_direct_expedited":"ParcelDirectExpedited","easypost_dhl_ecom_parcel_expedited":"DHLParcelExpedited","easypost_dhl_ecom_parcel_expedited_max":"DHLParcelExpeditedMax","easypost_dhl_ecom_parcel_ground":"DHLParcelGround","easypost_dhl_ecom_bpm_expedited":"DHLBPMExpedited","easypost_dhl_ecom_bpm_ground":"DHLBPMGround","easypost_dhl_ecom_parcel_international_direct":"DHLParcelInternationalDirect","easypost_dhl_ecom_parcel_international_standard":"DHLParcelInternationalStandard","easypost_dhl_ecom_packet_international":"DHLPacketInternational","easypost_dhl_ecom_parcel_international_direct_priority":"DHLParcelInternationalDirectPriority","easypost_dhl_ecom_parcel_international_direct_standard":"DHLParcelInternationalDirectStandard","easypost_dhl_express_break_bulk_economy":"BreakBulkEconomy","easypost_dhl_express_break_bulk_express":"BreakBulkExpress","easypost_dhl_express_domestic_economy_select":"DomesticEconomySelect","easypost_dhl_express_domestic_express":"DomesticExpress","easypost_dhl_express_domestic_express1030":"DomesticExpress1030","easypost_dhl_express_domestic_express1200":"DomesticExpress1200","easypost_dhl_express_economy_select":"EconomySelect","easypost_dhl_express_economy_select_non_doc":"EconomySelectNonDoc","easypost_dhl_express_euro_pack":"EuroPack","easypost_dhl_express_europack_non_doc":"EuropackNonDoc","easypost_dhl_express_express1030":"Express1030","easypost_dhl_express_express1030_non_doc":"Express1030NonDoc","easypost_dhl_express_express1200_non_doc":"Express1200NonDoc","easypost_dhl_express_express1200":"Express1200","easypost_dhl_express_express900":"Express900","easypost_dhl_express_express900_non_doc":"Express900NonDoc","easypost_dhl_express_express_easy":"ExpressEasy","easypost_dhl_express_express_easy_non_doc":"ExpressEasyNonDoc","easypost_dhl_express_express_envelope":"ExpressEnvelope","easypost_dhl_express_express_worldwide":"ExpressWorldwide","easypost_dhl_express_express_worldwide_b2_c":"ExpressWorldwideB2C","easypost_dhl_express_express_worldwide_b2_c_non_doc":"ExpressWorldwideB2CNonDoc","easypost_dhl_express_express_worldwide_ecx":"ExpressWorldwideECX","easypost_dhl_express_express_worldwide_non_doc":"ExpressWorldwideNonDoc","easypost_dhl_express_freight_worldwide":"FreightWorldwide","easypost_dhl_express_globalmail_business":"GlobalmailBusiness","easypost_dhl_express_jet_line":"JetLine","easypost_dhl_express_jumbo_box":"JumboBox","easypost_dhl_express_logistics_services":"LogisticsServices","easypost_dhl_express_same_day":"SameDay","easypost_dhl_express_secure_line":"SecureLine","easypost_dhl_express_sprint_line":"SprintLine","easypost_dpd_classic":"DPDCLASSIC","easypost_dpd_8_30":"DPD8:30","easypost_dpd_10_00":"DPD10:00","easypost_dpd_12_00":"DPD12:00","easypost_dpd_18_00":"DPD18:00","easypost_dpd_express":"DPDEXPRESS","easypost_dpd_parcelletter":"DPDPARCELLETTER","easypost_dpd_parcelletterplus":"DPDPARCELLETTERPLUS","easypost_dpd_internationalmail":"DPDINTERNATIONALMAIL","easypost_dpd_uk_air_express_international_air":"AirExpressInternationalAir","easypost_dpd_uk_air_classic_international_air":"AirClassicInternationalAir","easypost_dpd_uk_parcel_sunday":"ParcelSunday","easypost_dpd_uk_freight_parcel_sunday":"FreightParcelSunday","easypost_dpd_uk_pallet_sunday":"PalletSunday","easypost_dpd_uk_pallet_dpd_classic":"PalletDpdClassic","easypost_dpd_uk_expresspak_dpd_classic":"ExpresspakDpdClassic","easypost_dpd_uk_expresspak_sunday":"ExpresspakSunday","easypost_dpd_uk_parcel_dpd_classic":"ParcelDpdClassic","easypost_dpd_uk_parcel_dpd_two_day":"ParcelDpdTwoDay","easypost_dpd_uk_parcel_dpd_next_day":"ParcelDpdNextDay","easypost_dpd_uk_parcel_dpd12":"ParcelDpd12","easypost_dpd_uk_parcel_dpd10":"ParcelDpd10","easypost_dpd_uk_parcel_return_to_shop":"ParcelReturnToShop","easypost_dpd_uk_parcel_saturday":"ParcelSaturday","easypost_dpd_uk_parcel_saturday12":"ParcelSaturday12","easypost_dpd_uk_parcel_saturday10":"ParcelSaturday10","easypost_dpd_uk_parcel_sunday12":"ParcelSunday12","easypost_dpd_uk_freight_parcel_dpd_classic":"FreightParcelDpdClassic","easypost_dpd_uk_freight_parcel_sunday12":"FreightParcelSunday12","easypost_dpd_uk_expresspak_dpd_next_day":"ExpresspakDpdNextDay","easypost_dpd_uk_expresspak_dpd12":"ExpresspakDpd12","easypost_dpd_uk_expresspak_dpd10":"ExpresspakDpd10","easypost_dpd_uk_expresspak_saturday":"ExpresspakSaturday","easypost_dpd_uk_expresspak_saturday12":"ExpresspakSaturday12","easypost_dpd_uk_expresspak_saturday10":"ExpresspakSaturday10","easypost_dpd_uk_expresspak_sunday12":"ExpresspakSunday12","easypost_dpd_uk_pallet_sunday12":"PalletSunday12","easypost_dpd_uk_pallet_dpd_two_day":"PalletDpdTwoDay","easypost_dpd_uk_pallet_dpd_next_day":"PalletDpdNextDay","easypost_dpd_uk_pallet_dpd12":"PalletDpd12","easypost_dpd_uk_pallet_dpd10":"PalletDpd10","easypost_dpd_uk_pallet_saturday":"PalletSaturday","easypost_dpd_uk_pallet_saturday12":"PalletSaturday12","easypost_dpd_uk_pallet_saturday10":"PalletSaturday10","easypost_dpd_uk_freight_parcel_dpd_two_day":"FreightParcelDpdTwoDay","easypost_dpd_uk_freight_parcel_dpd_next_day":"FreightParcelDpdNextDay","easypost_dpd_uk_freight_parcel_dpd12":"FreightParcelDpd12","easypost_dpd_uk_freight_parcel_dpd10":"FreightParcelDpd10","easypost_dpd_uk_freight_parcel_saturday":"FreightParcelSaturday","easypost_dpd_uk_freight_parcel_saturday12":"FreightParcelSaturday12","easypost_dpd_uk_freight_parcel_saturday10":"FreightParcelSaturday10","easypost_epost_courier_service_ddp":"CourierServiceDDP","easypost_epost_courier_service_ddu":"CourierServiceDDU","easypost_epost_domestic_economy_parcel":"DomesticEconomyParcel","easypost_epost_domestic_parcel_bpm":"DomesticParcelBPM","easypost_epost_domestic_priority_parcel":"DomesticPriorityParcel","easypost_epost_domestic_priority_parcel_bpm":"DomesticPriorityParcelBPM","easypost_epost_emi_service":"EMIService","easypost_epost_economy_parcel_service":"EconomyParcelService","easypost_epost_ipa_service":"IPAService","easypost_epost_isal_service":"ISALService","easypost_epost_pmi_service":"PMIService","easypost_epost_priority_parcel_ddp":"PriorityParcelDDP","easypost_epost_priority_parcel_ddu":"PriorityParcelDDU","easypost_epost_priority_parcel_delivery_confirmation_ddp":"PriorityParcelDeliveryConfirmationDDP","easypost_epost_priority_parcel_delivery_confirmation_ddu":"PriorityParcelDeliveryConfirmationDDU","easypost_epost_epacket_service":"ePacketService","easypost_estafeta_next_day_by930":"NextDayBy930","easypost_estafeta_next_day_by1130":"NextDayBy1130","easypost_estafeta_next_day":"NextDay","easypost_estafeta_two_day":"TwoDay","easypost_estafeta_ltl":"LTL","easypost_fastway_parcel":"Parcel","easypost_fastway_satchel":"Satchel","easypost_fedex_ground":"FEDEX_GROUND","easypost_fedex_2_day":"FEDEX_2_DAY","easypost_fedex_2_day_am":"FEDEX_2_DAY_AM","easypost_fedex_express_saver":"FEDEX_EXPRESS_SAVER","easypost_fedex_standard_overnight":"STANDARD_OVERNIGHT","easypost_fedex_first_overnight":"FIRST_OVERNIGHT","easypost_fedex_priority_overnight":"PRIORITY_OVERNIGHT","easypost_fedex_international_economy":"INTERNATIONAL_ECONOMY","easypost_fedex_international_first":"INTERNATIONAL_FIRST","easypost_fedex_international_priority":"INTERNATIONAL_PRIORITY","easypost_fedex_ground_home_delivery":"GROUND_HOME_DELIVERY","easypost_fedex_crossborder_cbec":"CBEC","easypost_fedex_crossborder_cbecl":"CBECL","easypost_fedex_crossborder_cbecp":"CBECP","easypost_fedex_sameday_city_economy_service":"EconomyService","easypost_fedex_sameday_city_standard_service":"StandardService","easypost_fedex_sameday_city_priority_service":"PriorityService","easypost_fedex_sameday_city_last_mile":"LastMile","easypost_fedex_smart_post":"SMART_POST","easypost_globegistics_pmei":"PMEI","easypost_globegistics_ecom_domestic":"eComDomestic","easypost_globegistics_ecom_europe":"eComEurope","easypost_globegistics_ecom_express":"eComExpress","easypost_globegistics_ecom_extra":"eComExtra","easypost_globegistics_ecom_ipa":"eComIPA","easypost_globegistics_ecom_isal":"eComISAL","easypost_globegistics_ecom_pmei_duty_paid":"eComPMEIDutyPaid","easypost_globegistics_ecom_pmi_duty_paid":"eComPMIDutyPaid","easypost_globegistics_ecom_packet":"eComPacket","easypost_globegistics_ecom_packet_ddp":"eComPacketDDP","easypost_globegistics_ecom_priority":"eComPriority","easypost_globegistics_ecom_standard":"eComStandard","easypost_globegistics_ecom_tracked_ddp":"eComTrackedDDP","easypost_globegistics_ecom_tracked_ddu":"eComTrackedDDU","easypost_gso_early_priority_overnight":"EarlyPriorityOvernight","easypost_gso_priority_overnight":"PriorityOvernight","easypost_gso_california_parcel_service":"CaliforniaParcelService","easypost_gso_saturday_delivery_service":"SaturdayDeliveryService","easypost_gso_early_saturday_service":"EarlySaturdayService","easypost_hermes_domestic_delivery":"DomesticDelivery","easypost_hermes_domestic_delivery_signed":"DomesticDeliverySigned","easypost_hermes_international_delivery":"InternationalDelivery","easypost_hermes_international_delivery_signed":"InternationalDeliverySigned","easypost_interlink_air_classic_international_air":"InterlinkAirClassicInternationalAir","easypost_interlink_air_express_international_air":"InterlinkAirExpressInternationalAir","easypost_interlink_expresspak1_by10_30":"InterlinkExpresspak1By10:30","easypost_interlink_expresspak1_by12":"InterlinkExpresspak1By12","easypost_interlink_expresspak1_next_day":"InterlinkExpresspak1NextDay","easypost_interlink_expresspak1_saturday":"InterlinkExpresspak1Saturday","easypost_interlink_expresspak1_saturday_by10_30":"InterlinkExpresspak1SaturdayBy10:30","easypost_interlink_expresspak1_saturday_by12":"InterlinkExpresspak1SaturdayBy12","easypost_interlink_expresspak1_sunday":"InterlinkExpresspak1Sunday","easypost_interlink_expresspak1_sunday_by12":"InterlinkExpresspak1SundayBy12","easypost_interlink_expresspak5_by10":"InterlinkExpresspak5By10","easypost_interlink_expresspak5_by10_30":"InterlinkExpresspak5By10:30","easypost_interlink_expresspak5_by12":"InterlinkExpresspak5By12","easypost_interlink_expresspak5_next_day":"InterlinkExpresspak5NextDay","easypost_interlink_expresspak5_saturday":"InterlinkExpresspak5Saturday","easypost_interlink_expresspak5_saturday_by10":"InterlinkExpresspak5SaturdayBy10","easypost_interlink_expresspak5_saturday_by10_30":"InterlinkExpresspak5SaturdayBy10:30","easypost_interlink_expresspak5_saturday_by12":"InterlinkExpresspak5SaturdayBy12","easypost_interlink_expresspak5_sunday":"InterlinkExpresspak5Sunday","easypost_interlink_expresspak5_sunday_by12":"InterlinkExpresspak5SundayBy12","easypost_interlink_freight_by10":"InterlinkFreightBy10","easypost_interlink_freight_by12":"InterlinkFreightBy12","easypost_interlink_freight_next_day":"InterlinkFreightNextDay","easypost_interlink_freight_saturday":"InterlinkFreightSaturday","easypost_interlink_freight_saturday_by10":"InterlinkFreightSaturdayBy10","easypost_interlink_freight_saturday_by12":"InterlinkFreightSaturdayBy12","easypost_interlink_freight_sunday":"InterlinkFreightSunday","easypost_interlink_freight_sunday_by12":"InterlinkFreightSundayBy12","easypost_interlink_parcel_by10":"InterlinkParcelBy10","easypost_interlink_parcel_by10_30":"InterlinkParcelBy10:30","easypost_interlink_parcel_by12":"InterlinkParcelBy12","easypost_interlink_parcel_dpd_europe_by_road":"InterlinkParcelDpdEuropeByRoad","easypost_interlink_parcel_next_day":"InterlinkParcelNextDay","easypost_interlink_parcel_return":"InterlinkParcelReturn","easypost_interlink_parcel_return_to_shop":"InterlinkParcelReturnToShop","easypost_interlink_parcel_saturday":"InterlinkParcelSaturday","easypost_interlink_parcel_saturday_by10":"InterlinkParcelSaturdayBy10","easypost_interlink_parcel_saturday_by10_30":"InterlinkParcelSaturdayBy10:30","easypost_interlink_parcel_saturday_by12":"InterlinkParcelSaturdayBy12","easypost_interlink_parcel_ship_to_shop":"InterlinkParcelShipToShop","easypost_interlink_parcel_sunday":"InterlinkParcelSunday","easypost_interlink_parcel_sunday_by12":"InterlinkParcelSundayBy12","easypost_interlink_parcel_two_day":"InterlinkParcelTwoDay","easypost_interlink_pickup_parcel_dpd_europe_by_road":"InterlinkPickupParcelDpdEuropeByRoad","easypost_lasership_weekend":"Weekend","easypost_loomis_ground":"LoomisGround","easypost_loomis_express1800":"LoomisExpress1800","easypost_loomis_express1200":"LoomisExpress1200","easypost_loomis_express900":"LoomisExpress900","easypost_lso_ground_early":"GroundEarly","easypost_lso_ground_basic":"GroundBasic","easypost_lso_priority_basic":"PriorityBasic","easypost_lso_priority_early":"PriorityEarly","easypost_lso_priority_saturday":"PrioritySaturday","easypost_lso_priority2nd_day":"Priority2ndDay","easypost_newgistics_parcel_select":"ParcelSelect","easypost_newgistics_parcel_select_lightweight":"ParcelSelectLightweight","easypost_newgistics_express":"Express","easypost_newgistics_first_class_mail":"FirstClassMail","easypost_newgistics_priority_mail":"PriorityMail","easypost_newgistics_bound_printed_matter":"BoundPrintedMatter","easypost_ontrac_sunrise":"Sunrise","easypost_ontrac_gold":"Gold","easypost_ontrac_on_trac_ground":"OnTracGround","easypost_ontrac_palletized_freight":"PalletizedFreight","easypost_osm_first":"First","easypost_osm_expedited":"Expedited","easypost_osm_bpm":"BPM","easypost_osm_media_mail":"MediaMail","easypost_osm_marketing_parcel":"MarketingParcel","easypost_osm_marketing_parcel_tracked":"MarketingParcelTracked","easypost_parcll_economy_west":"Economy West","easypost_parcll_economy_east":"Economy East","easypost_parcll_economy_central":"Economy Central","easypost_parcll_economy_northeast":"Economy Northeast","easypost_parcll_economy_south":"Economy South","easypost_parcll_expedited_west":"Expedited West","easypost_parcll_expedited_northeast":"Expedited Northeast","easypost_parcll_regional_west":"Regional West","easypost_parcll_regional_east":"Regional East","easypost_parcll_regional_central":"Regional Central","easypost_parcll_regional_northeast":"Regional Northeast","easypost_parcll_regional_south":"Regional South","easypost_parcll_us_to_canada_economy_west":"US to Canada Economy West","easypost_parcll_us_to_canada_economy_central":"US to Canada Economy Central","easypost_parcll_us_to_canada_economy_northeast":"US to Canada Economy Northeast","easypost_parcll_us_to_europe_economy_west":"US to Europe Economy West","easypost_parcll_us_to_europe_economy_northeast":"US to Europe Economy Northeast","easypost_purolator_express":"PurolatorExpress","easypost_purolator_express12_pm":"PurolatorExpress12PM","easypost_purolator_express_pack12_pm":"PurolatorExpressPack12PM","easypost_purolator_express_box12_pm":"PurolatorExpressBox12PM","easypost_purolator_express_envelope12_pm":"PurolatorExpressEnvelope12PM","easypost_purolator_express1030_am":"PurolatorExpress1030AM","easypost_purolator_express9_am":"PurolatorExpress9AM","easypost_purolator_express_box":"PurolatorExpressBox","easypost_purolator_express_box1030_am":"PurolatorExpressBox1030AM","easypost_purolator_express_box9_am":"PurolatorExpressBox9AM","easypost_purolator_express_box_evening":"PurolatorExpressBoxEvening","easypost_purolator_express_box_international":"PurolatorExpressBoxInternational","easypost_purolator_express_box_international1030_am":"PurolatorExpressBoxInternational1030AM","easypost_purolator_express_box_international1200":"PurolatorExpressBoxInternational1200","easypost_purolator_express_box_international9_am":"PurolatorExpressBoxInternational9AM","easypost_purolator_express_box_us":"PurolatorExpressBoxUS","easypost_purolator_express_box_us1030_am":"PurolatorExpressBoxUS1030AM","easypost_purolator_express_box_us1200":"PurolatorExpressBoxUS1200","easypost_purolator_express_box_us9_am":"PurolatorExpressBoxUS9AM","easypost_purolator_express_envelope":"PurolatorExpressEnvelope","easypost_purolator_express_envelope1030_am":"PurolatorExpressEnvelope1030AM","easypost_purolator_express_envelope9_am":"PurolatorExpressEnvelope9AM","easypost_purolator_express_envelope_evening":"PurolatorExpressEnvelopeEvening","easypost_purolator_express_envelope_international":"PurolatorExpressEnvelopeInternational","easypost_purolator_express_envelope_international1030_am":"PurolatorExpressEnvelopeInternational1030AM","easypost_purolator_express_envelope_international1200":"PurolatorExpressEnvelopeInternational1200","easypost_purolator_express_envelope_international9_am":"PurolatorExpressEnvelopeInternational9AM","easypost_purolator_express_envelope_us":"PurolatorExpressEnvelopeUS","easypost_purolator_express_envelope_us1030_am":"PurolatorExpressEnvelopeUS1030AM","easypost_purolator_express_envelope_us1200":"PurolatorExpressEnvelopeUS1200","easypost_purolator_express_envelope_us9_am":"PurolatorExpressEnvelopeUS9AM","easypost_purolator_express_evening":"PurolatorExpressEvening","easypost_purolator_express_international":"PurolatorExpressInternational","easypost_purolator_express_international1030_am":"PurolatorExpressInternational1030AM","easypost_purolator_express_international1200":"PurolatorExpressInternational1200","easypost_purolator_express_international9_am":"PurolatorExpressInternational9AM","easypost_purolator_express_pack":"PurolatorExpressPack","easypost_purolator_express_pack1030_am":"PurolatorExpressPack1030AM","easypost_purolator_express_pack9_am":"PurolatorExpressPack9AM","easypost_purolator_express_pack_evening":"PurolatorExpressPackEvening","easypost_purolator_express_pack_international":"PurolatorExpressPackInternational","easypost_purolator_express_pack_international1030_am":"PurolatorExpressPackInternational1030AM","easypost_purolator_express_pack_international1200":"PurolatorExpressPackInternational1200","easypost_purolator_express_pack_international9_am":"PurolatorExpressPackInternational9AM","easypost_purolator_express_pack_us":"PurolatorExpressPackUS","easypost_purolator_express_pack_us1030_am":"PurolatorExpressPackUS1030AM","easypost_purolator_express_pack_us1200":"PurolatorExpressPackUS1200","easypost_purolator_express_pack_us9_am":"PurolatorExpressPackUS9AM","easypost_purolator_express_us":"PurolatorExpressUS","easypost_purolator_express_us1030_am":"PurolatorExpressUS1030AM","easypost_purolator_express_us1200":"PurolatorExpressUS1200","easypost_purolator_express_us9_am":"PurolatorExpressUS9AM","easypost_purolator_ground":"PurolatorGround","easypost_purolator_ground1030_am":"PurolatorGround1030AM","easypost_purolator_ground9_am":"PurolatorGround9AM","easypost_purolator_ground_distribution":"PurolatorGroundDistribution","easypost_purolator_ground_evening":"PurolatorGroundEvening","easypost_purolator_ground_regional":"PurolatorGroundRegional","easypost_purolator_ground_us":"PurolatorGroundUS","easypost_royalmail_international_signed":"InternationalSigned","easypost_royalmail_international_tracked":"InternationalTracked","easypost_royalmail_international_tracked_and_signed":"InternationalTrackedAndSigned","easypost_royalmail_1st_class":"1stClass","easypost_royalmail_1st_class_signed_for":"1stClassSignedFor","easypost_royalmail_2nd_class":"2ndClass","easypost_royalmail_2nd_class_signed_for":"2ndClassSignedFor","easypost_royalmail_royal_mail24":"RoyalMail24","easypost_royalmail_royal_mail24_signed_for":"RoyalMail24SignedFor","easypost_royalmail_royal_mail48":"RoyalMail48","easypost_royalmail_royal_mail48_signed_for":"RoyalMail48SignedFor","easypost_royalmail_special_delivery_guaranteed1pm":"SpecialDeliveryGuaranteed1pm","easypost_royalmail_special_delivery_guaranteed9am":"SpecialDeliveryGuaranteed9am","easypost_royalmail_standard_letter1st_class":"StandardLetter1stClass","easypost_royalmail_standard_letter1st_class_signed_for":"StandardLetter1stClassSignedFor","easypost_royalmail_standard_letter2nd_class":"StandardLetter2ndClass","easypost_royalmail_standard_letter2nd_class_signed_for":"StandardLetter2ndClassSignedFor","easypost_royalmail_tracked24":"Tracked24","easypost_royalmail_tracked24_high_volume":"Tracked24HighVolume","easypost_royalmail_tracked24_high_volume_signature":"Tracked24HighVolumeSignature","easypost_royalmail_tracked24_signature":"Tracked24Signature","easypost_royalmail_tracked48":"Tracked48","easypost_royalmail_tracked48_high_volume":"Tracked48HighVolume","easypost_royalmail_tracked48_high_volume_signature":"Tracked48HighVolumeSignature","easypost_royalmail_tracked48_signature":"Tracked48Signature","easypost_seko_ecommerce_standard_tracked":"eCommerce Standard Tracked","easypost_seko_ecommerce_express_tracked":"eCommerce Express Tracked","easypost_seko_domestic_express":"Domestic Express","easypost_seko_domestic_standard":"Domestic Standard","easypost_sendle_easy":"Easy","easypost_sendle_pro":"Pro","easypost_sendle_plus":"Plus","easypost_sfexpress_international_standard_express_doc":"International Standard Express - Doc","easypost_sfexpress_international_standard_express_parcel":"International Standard Express - Parcel","easypost_sfexpress_international_economy_express_pilot":"International Economy Express - Pilot","easypost_sfexpress_international_economy_express_doc":"International Economy Express - Doc","easypost_speedee_delivery":"SpeeDeeDelivery","easypost_startrack_express":"StartrackExpress","easypost_startrack_premium":"StartrackPremium","easypost_startrack_fixed_price_premium":"StartrackFixedPricePremium","easypost_tforce_same_day_white_glove":"SameDayWhiteGlove","easypost_tforce_next_day_white_glove":"NextDayWhiteGlove","easypost_uds_delivery_service":"DeliveryService","easypost_ups_standard":"UPSStandard","easypost_ups_saver":"UPSSaver","easypost_ups_express_plus":"ExpressPlus","easypost_ups_next_day_air":"NextDayAir","easypost_ups_next_day_air_saver":"NextDayAirSaver","easypost_ups_next_day_air_early_am":"NextDayAirEarlyAM","easypost_ups_2nd_day_air":"2ndDayAir","easypost_ups_2nd_day_air_am":"2ndDayAirAM","easypost_ups_3_day_select":"3DaySelect","easypost_ups_mail_expedited_mail_innovations":"ExpeditedMailInnovations","easypost_ups_mail_priority_mail_innovations":"PriorityMailInnovations","easypost_ups_mail_economy_mail_innovations":"EconomyMailInnovations","easypost_usps_library_mail":"LibraryMail","easypost_usps_first_class_mail_international":"FirstClassMailInternational","easypost_usps_first_class_package_international_service":"FirstClassPackageInternationalService","easypost_usps_priority_mail_international":"PriorityMailInternational","easypost_usps_express_mail_international":"ExpressMailInternational","easypost_veho_next_day":"nextDay","easypost_veho_same_day":"sameDay"},"shipping_options":{"easypost_additional_handling":{"code":"additional_handling","meta":{"configurable":true},"type":"boolean","label":"Additional Handling"},"easypost_address_validation_level":{"code":"address_validation_level","meta":{"configurable":true},"type":"string","label":"Address Validation Level"},"easypost_alcohol":{"code":"alcohol","meta":{"configurable":true},"type":"boolean","label":"Alcohol"},"easypost_by_drone":{"code":"by_drone","meta":{"configurable":true},"type":"boolean","label":"By Drone"},"easypost_carbon_neutral":{"code":"carbon_neutral","meta":{"configurable":true},"type":"boolean","label":"Carbon Neutral"},"easypost_cod_amount":{"code":"cod_amount","meta":{"configurable":true},"type":"string","label":"Cod Amount"},"easypost_cod_method":{"code":"cod_method","meta":{"configurable":true},"type":"string","label":"Cod Method"},"easypost_cod_address_id":{"code":"cod_address_id","meta":{"configurable":true},"type":"string","label":"Cod Address Id"},"easypost_currency":{"code":"currency","meta":{"configurable":true},"type":"string","label":"Currency"},"easypost_delivery_confirmation":{"code":"delivery_confirmation","meta":{"configurable":true},"type":"string","label":"Delivery Confirmation"},"easypost_dropoff_type":{"code":"dropoff_type","meta":{"configurable":true},"type":"string","label":"Dropoff Type"},"easypost_dry_ice":{"code":"dry_ice","meta":{"configurable":true},"type":"boolean","label":"Dry Ice"},"easypost_dry_ice_medical":{"code":"dry_ice_medical","meta":{"configurable":true},"type":"boolean","label":"Dry Ice Medical"},"easypost_dry_ice_weight":{"code":"dry_ice_weight","meta":{"configurable":true},"type":"string","label":"Dry Ice Weight"},"easypost_endorsement":{"code":"endorsement","meta":{"configurable":true},"type":"string","label":"Endorsement"},"easypost_freight_charge":{"code":"freight_charge","meta":{"configurable":true},"type":"float","label":"Freight Charge"},"easypost_handling_instructions":{"code":"handling_instructions","meta":{"configurable":true},"type":"string","label":"Handling Instructions"},"easypost_hazmat":{"code":"hazmat","meta":{"configurable":true},"type":"string","label":"Hazmat"},"easypost_hold_for_pickup":{"code":"hold_for_pickup","meta":{"configurable":true},"type":"boolean","label":"Hold For Pickup"},"easypost_incoterm":{"code":"incoterm","meta":{"configurable":true},"type":"string","label":"Incoterm"},"easypost_invoice_number":{"code":"invoice_number","meta":{"configurable":true},"type":"string","label":"Invoice Number"},"easypost_label_date":{"code":"label_date","meta":{"configurable":true},"type":"string","label":"Label Date"},"easypost_label_format":{"code":"label_format","meta":{"configurable":true},"type":"string","label":"Label Format"},"easypost_machinable":{"code":"machinable","meta":{"configurable":true},"type":"boolean","label":"Machinable"},"easypost_payment":{"code":"payment","meta":{"configurable":true},"type":"object","label":"Payment"},"easypost_print_custom_1":{"code":"print_custom_1","meta":{"configurable":true},"type":"string","label":"Print Custom 1"},"easypost_print_custom_2":{"code":"print_custom_2","meta":{"configurable":true},"type":"string","label":"Print Custom 2"},"easypost_print_custom_3":{"code":"print_custom_3","meta":{"configurable":true},"type":"string","label":"Print Custom 3"},"easypost_print_custom_1_barcode":{"code":"print_custom_1_barcode","meta":{"configurable":true},"type":"string","label":"Print Custom 1 Barcode"},"easypost_print_custom_2_barcode":{"code":"print_custom_2_barcode","meta":{"configurable":true},"type":"string","label":"Print Custom 2 Barcode"},"easypost_print_custom_3_barcode":{"code":"print_custom_3_barcode","meta":{"configurable":true},"type":"string","label":"Print Custom 3 Barcode"},"easypost_print_custom_1_code":{"code":"print_custom_1_code","meta":{"configurable":true},"type":"string","label":"Print Custom 1 Code"},"easypost_print_custom_2_code":{"code":"print_custom_2_code","meta":{"configurable":true},"type":"string","label":"Print Custom 2 Code"},"easypost_print_custom_3_code":{"code":"print_custom_3_code","meta":{"configurable":true},"type":"string","label":"Print Custom 3 Code"},"easypost_saturday_delivery":{"code":"saturday_delivery","meta":{"configurable":true},"type":"boolean","label":"Saturday Delivery"},"easypost_special_rates_eligibility":{"code":"special_rates_eligibility","meta":{"configurable":true},"type":"string","label":"Special Rates Eligibility"},"easypost_smartpost_hub":{"code":"smartpost_hub","meta":{"configurable":true},"type":"string","label":"Smartpost Hub"},"easypost_smartpost_manifest":{"code":"smartpost_manifest","meta":{"configurable":true},"type":"string","label":"Smartpost Manifest"},"easypost_billing_ref":{"code":"billing_ref","meta":{"configurable":true},"type":"string","label":"Billing Ref"},"easypost_certified_mail":{"code":"certified_mail","meta":{"configurable":true},"type":"boolean","label":"Certified Mail"},"easypost_registered_mail":{"code":"registered_mail","meta":{"configurable":true},"type":"boolean","label":"Registered Mail"},"easypost_registered_mail_amount":{"code":"registered_mail_amount","meta":{"configurable":true},"type":"float","label":"Registered Mail Amount"},"easypost_return_receipt":{"code":"return_receipt","meta":{"configurable":true},"type":"boolean","label":"Return Receipt"}},"readme":null},{"id":"easyship","carrier_name":"easyship","display_name":"Easyship","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Manifest","Rating","Pickup","Shipping","Tracking"],"connection_fields":{"access_token":{"name":"access_token","required":true,"sensitive":false,"type":"string","label":"Access Token"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"platform_name":{"code":"platform_name","name":"platform_name","required":false,"type":"string","label":"Platform Name"},"apply_shipping_rules":{"code":"apply_shipping_rules","name":"apply_shipping_rules","required":false,"type":"boolean","label":"Apply Shipping Rules"},"allow_courier_fallback":{"code":"allow_courier_fallback","name":"allow_courier_fallback","required":false,"type":"boolean","label":"Allow Courier Fallback"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"easyship_aramex_parcel":"Parcel","easyship_sfexpress_domestic":"Domestic","easyship_hkpost_speedpost":"Speedpost","easyship_hkpost_air_mail_tracking":"Air Mail Tracking","easyship_hkpost_eexpress":"EExpress","easyship_hkpost_air_parcel":"Air Parcel ","easyship_sfexpress_mail":"Mail ","easyship_hkpost_local_parcel":"Local Parcel","easyship_ups_saver_net_battery":"SaverNet Battery","easyship_ups_worldwide_saver":"Worldwide Saver®","easyship_hkpost_air_parcel_xp":"Air Parcel XP","easyship_singpost_airmail":"Airmail","easyship_simplypost_express":"Express","easyship_singpost_e_pack":"ePack","easyship_usps_priority_mail_express":"Priority Mail Express","easyship_usps_first_class_international":"First Class International","easyship_usps_priority_mail_international_express":"Priority Mail International Express","easyship_usps_priority_mail_international":"Priority Mail International","easyship_fedex_international_priority":"InternationalPriority","easyship_usps_ground_advantage":"GroundAdvantage","easyship_usps_priority_mail":"PriorityMail","easyship_ups_worldwide_express":"Worldwide Express®","easyship_ups_ground":"Ground","easyship_ups_worldwide_expedited":"Worldwide Expedited®","easyship_fedex_international_economy":"International Economy®","easyship_fedex_priority_overnight":"Priority Overnight®","easyship_fedex_standard_overnight":"Standard Overnight®","easyship_fedex_2_day_a_m":"2Day® A.M.","easyship_fedex_2_day":"2Day®","easyship_fedex_express_saver":"Express Saver®","easyship_ups_next_day_air":"Next Day Air®","easyship_ups_2nd_day_air":"2nd Day Air®","easyship_ups_3_day_select":"3DaySelect","easyship_ups_standard":"Standard","easyship_usps_media":"Media","easyship_sfexpress_standard_express":"Standard Express","easyship_sfexpress_economy_express":"Economy Express","easyship_global_post_global_post_economy":"GlobalPost Economy","easyship_global_post_global_post_priority":"GlobalPost Priority","easyship_singpost_speed_post_priority":"SpeedPost Priority","easyship_skypostal_standard_private_delivery":"Standard Private Delivery","easyship_tnt_1000_express":"1000Express","easyship_toll_express_parcel":"Express Parcel","easyship_sendle_premium_international":"Premium International","easyship_sendle_premium_domestic":"PremiumDomestic","easyship_sendle_pro_domestic":"Pro Domestic","easyship_quantium_e_pac":"ePac","easyship_usps_pm_flat_rate":"PM Flat Rate","easyship_usps_pmi_flat_rate":"PMI Flat Rate","easyship_quantium_mail":"Mail","easyship_quantium_international_mail":"International Mail","easyship_apc_parcel_connect_expedited":"ParcelConnect Expedited","easyship_aramex_epx":"EPX","easyship_tnt_road_express":"Road Express","easyship_tnt_overnight":"Overnight","easyship_usps_pme_flat_rate":"PME Flat Rate","easyship_usps_pmei_flat_rate":"PMEI Flat Rate","easyship_easyship_cdek_russia":"CDEK Russia","easyship_usps_pmei_flat_rate_padded_envelope":"PMEI Flat Rate Padded Envelope","easyship_easyship_mate_bike_shipping_services":"Mate Bike Shipping Services","easyship_dhl_express_documents":"Documents","easyship_evri_uk_home_delivery":"UK_HomeDelivery","easyship_evri_home_delivery":"HomeDelivery","easyship_dpd_next_day":"NextDay","easyship_dpd_classic_parcel":"ClassicParcel","easyship_dpd_classic_expresspak":"ClassicExpresspak","easyship_dpd_air_classic":"AirClassic","easyship_singpost_speed_post_express":"SpeedPostExpress","easyship_ups_expedited":"Expedited","easyship_tnt_0900_express":"0900Express","easyship_tnt_1200_express":"1200Express","easyship_canadapost_domestic_regular_parcel":"Domestic Regular Parcel","easyship_canadapost_domestic_expedited_parcel":"Domestic Expedited Parcel","easyship_canadapost_domestic_xpresspost_domestic":"Domestic Xpresspost Domestic","easyship_canadapost_domestic_priority":"Domestic Priority","easyship_canadapost_usa_small_packet_air":"USA Small Packet Air","easyship_canadapost_usa_expedited_parcel":"USA Expedited Parcel","easyship_canadapost_usa_tracked_parcel":"USA Tracked Parcel","easyship_canadapost_usa_xpresspost":"USA Xpresspost","easyship_canadapost_international_xpresspost":"International Xpresspost","easyship_canadapost_international_small_packet_air":"International Small Packet Air","easyship_canadapost_international_tracked_packet":"International Tracked Packet","easyship_canadapost_international_small_packet_surface":"International Small Packet Surface","easyship_canadapost_international_parcel_surface":"International Parcel Surface","easyship_canadapost_international_parcel_air":"International Parcel Air","easyship_couriersplease_atl":"ATL","easyship_couriersplease_signature":"Signature","easyship_canpar_international":"International","easyship_canpar_usa":"USA","easyship_canpar_select_usa":"Select USA","easyship_canpar_usa_pak":"USA Pak","easyship_canpar_overnight_pak":"Overnight Pak","easyship_canpar_select_pak":"Select Pak","easyship_canpar_select":"Select","easyship_ups_express_saver":"ExpressSaver","easyship_ebay_send_sf_express_economy_express":"SF Express Economy Express","easyship_ups_worldwide_express_plus":"Worldwide Express Plus®","easyship_quantium_intl_priority":"IntlPriority","easyship_ups_next_day_air_early":"Next Day Air® Early","easyship_ups_next_day_air_saver":"Next Day Air Saver®","easyship_ups_2nd_day_air_a_m":"2nd Day Air® A.M.","easyship_fedex_home_delivery":"Home Delivery®","easyship_asendia_country_tracked":"CountryTracked","easyship_asendia_fully_tracked":"FullyTracked","easyship_dhl_express_express_dg":"ExpressDG","easyship_fedex_international_priority_dg":"InternationalPriorityDG","easyship_colissimo_expert":"Expert","easyship_colissimo_access":"Access","easyship_mondialrelay_international_home_delivery":"InternationalHomeDelivery","easyship_fedex_economy":"Economy","easyship_dhl_express_express1200":"Express1200","easyship_dhl_express_express0900":"Express0900","easyship_dhl_express_express1800":"Express1800","easyship_dhl_express_express_worldwide":"ExpressWorldwide","easyship_dhl_express_economy_select":"EconomySelect","easyship_dhl_express_express1030_international":"Express1030International","easyship_dhl_express_domestic_express0900":"DomesticExpress0900","easyship_dhl_express_domestic_express1200":"DomesticExpress1200","easyship_evri_lightand_large":"LightandLarge","easyship_ninjavan_standard_deliveries":"Standard Deliveries","easyship_couriersplease_parcel_tier2":"ParcelTier2","easyship_skypostal_postal_packet_standard":"Postal Packet Standard","easyship_easyshipdemo_basic":"Basic","easyship_easyshipdemo_tracked":"Tracked","easyship_easyshipdemo_battery":"Battery","easyship_dhl_express_domestic_express":"DomesticExpress","easyship_fedex_smart_post":"SmartPost","easyship_fedex_international_connect_plus":"InternationalConnectPlus","easyship_ups_saver_net":"SaverNet","easyship_chronopost_chrono_classic":"ChronoClassic","easyship_chronopost_chrono_express":"ChronoExpress","easyship_chronopost_chrono10":"Chrono10","easyship_chronopost_chrono13":"Chrono13","easyship_chronopost_chrono18":"Chrono18","easyship_omniparcel_parcel_expedited":"Parcel Expedited","easyship_omniparcel_parcel_expedited_plus":"Parcel Expedited Plus","easyship_evri_home_delivery_domestic":"HomeDeliveryDomestic","easyship_evri_home_domestic_postable":"HomeDomesticPostable","easyship_skypostal_packet_express":"PacketExpress","easyship_parcelforce_express48_large":"Express48Large","easyship_parcelforce_express24":"Express24","easyship_parcelforce_express1000":"Express1000","easyship_parcelforce_express_am":"ExpressAM","easyship_parcelforce_express48":"Express48","easyship_parcelforce_euro_economy":"EuroEconomy","easyship_parcelforce_global_priority":"GlobalPriority","easyship_fedex_cross_border_trakpak_worldwide_hermes":"TrakpakWorldwideHermes","easyship_fedex_cross_border_trakpak_worldwide":"TrakpakWorldwide","easyship_evri_home_domestic_postable_next_day":"HomeDomesticPostableNextDay","easyship_dpd_express_pak_next_day":"ExpressPakNextDay","easyship_dpd_classic_express_pak":"ClassicExpressPak","easyship_evri_light_and_large":"LightAndLarge","easyship_evri_home_delivery_domestic_next_day":"Home Delivery Domestic NextDay","easyship_evri_home_delivery_eu":"HomeDeliveryEU","easyship_asendia_epaq_plus":"EpaqPlus","easyship_asendia_epaq_select":"EpaqSelect","easyship_usps_lightweight_standard":"LightweightStandard","easyship_usps_lightweight_economy":"LightweightEconomy","easyship_ups_domestic_express_saver":"DomesticExpressSaver","easyship_apg_e_packet":"ePacket","easyship_apg_e_packet_plus":"ePacketPlus","easyship_couriersplease_ecom_base_kilo":"EComBaseKilo","easyship_couriersplease_stdatlbase_kilo":"STDATLBaseKilo","easyship_nz_post_international_courier":"InternationalCourier","easyship_nz_post_air_small_parcel":"AirSmallParcel","easyship_nz_post_tracked_air_satchel":"TrackedAirSatchel","easyship_nz_post_economy_parcel":"Economy Parcel","easyship_nz_post_parcel_local":"ParcelLocal","easyship_dhl_express_express_domestic":"ExpressDomestic","easyship_alliedexpress_roadexpress":"Roadexpress","easyship_flatexportrate_asendiae_paqselect":"AsendiaePAQSelect","easyship_flatexportrate_asendia_country_tracked":"AsendiaCountryTracked","easyship_singpost_nsaver":"NSaver","easyship_colisprive_home":"Home","easyship_osm_domestic_parcel":"Domestic Parcel","easyship_malca_amit_door_to_door":"Door To Door","easyship_ninjavan_next_day_deliveries":"Next Day Deliveries","easyship_asendia_e_paqselect":"ePAQSelect","easyship_dpd_classic":"Classic","easyship_usps_priority_mail_signature":"PriorityMailSignature","easyship_bringer_packet_standard":"PacketStandard","easyship_bringer_prime":"Prime","easyship_orangeds_expedited_ddp":"ExpeditedDDP","easyship_orangeds_expedited_ddu":"ExpeditedDDU","easyship_sendle_preferred":"Preferred","easyship_ups_ground_saver":"GroundSaver","easyship_ups_upsground_saver_us":"UPSGroundSaverUS","easyship_passport_priority_delcon_dduewr":"PriorityDelconDDUEWR","easyship_passport_priority_delcon_ddpewr":"PriorityDelconDDPEWR","easyship_bringer_tracked_parcel":"TrackedParcel","easyship_ups_express_early":"ExpressEarly","easyship_ups_wolrdwide_express":"WolrdwideExpress"},"shipping_options":{"easyship_box_slug":{"code":"box_slug","meta":{"configurable":true},"type":"string","label":"Box Slug"},"easyship_courier_id":{"code":"courier_id","meta":{"configurable":true},"type":"string","label":"Courier Id"},"easyship_eei_reference":{"code":"eei_reference","meta":{"configurable":true},"type":"string","label":"Eei Reference"},"easyship_incoterms":{"code":"incoterms","enum":["DDU","DDP"],"meta":{"configurable":true},"type":"string","label":"Incoterms"},"easyship_apply_shipping_rules":{"code":"apply_shipping_rules","meta":{"configurable":true},"type":"boolean","label":"Apply Shipping Rules"},"easyship_show_courier_logo_url":{"code":"show_courier_logo_url","meta":{"configurable":true},"type":"boolean","label":"Show Courier Logo Url"},"easyship_allow_courier_fallback":{"code":"allow_courier_fallback","meta":{"configurable":true},"type":"boolean","label":"Allow Courier Fallback"},"easyship_list_unavailable_couriers":{"code":"list_unavailable_couriers","meta":{"configurable":true},"type":"boolean","label":"List Unavailable Couriers"},"easyship_buyer_notes":{"code":"buyer_notes","meta":{"configurable":true},"type":"string","label":"Buyer Notes"},"easyship_seller_notes":{"code":"seller_notes","meta":{"configurable":true},"type":"string","label":"Seller Notes"},"easyship_sender_address_id":{"code":"sender_address_id","meta":{"configurable":true},"type":"string","label":"Sender Address Id"},"easyship_return_address_id":{"code":"return_address_id","meta":{"configurable":true},"type":"string","label":"Return Address Id"}},"readme":null},{"id":"eshipper","carrier_name":"eshipper","display_name":"eShipper","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"principal":{"name":"principal","required":true,"sensitive":false,"type":"string","label":"Principal"},"credential":{"name":"credential","required":true,"sensitive":false,"type":"string","label":"Credential"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"eshipper_aramex_economy_document_express":"eshipper_aramex_economy_document_express","eshipper_aramex_economy_parcel_express":"eshipper_aramex_economy_parcel_express","eshipper_aramex_priority_letter_express":"eshipper_aramex_priority_letter_express","eshipper_aramex_priority_parcel_express":"eshipper_aramex_priority_parcel_express","eshipper_canadapost_air_parcel_intl":"eshipper_canadapost_air_parcel_intl","eshipper_canadapost_expedited":"eshipper_canadapost_expedited","eshipper_canadapost_expedited_parcel_usa":"eshipper_canadapost_expedited_parcel_usa","eshipper_canadapost_priority_courier":"eshipper_canadapost_priority_courier","eshipper_canadapost_regular":"eshipper_canadapost_regular","eshipper_canadapost_small_packet":"eshipper_canadapost_small_packet","eshipper_canadapost_small_packet_international_air":"eshipper_canadapost_small_packet_international_air","eshipper_canadapost_small_packet_international_surface":"eshipper_canadapost_small_packet_international_surface","eshipper_canadapost_surface_parcel_intl":"eshipper_canadapost_surface_parcel_intl","eshipper_canadapost_xpress_post":"eshipper_canadapost_xpress_post","eshipper_canadapost_xpress_post_intl":"eshipper_canadapost_xpress_post_intl","eshipper_canadapost_xpress_post_usa":"eshipper_canadapost_xpress_post_usa","eshipper_canpar_express_letter":"eshipper_canpar_express_letter","eshipper_canpar_express_pak":"eshipper_canpar_express_pak","eshipper_canpar_express_parcel":"eshipper_canpar_express_parcel","eshipper_canpar_ground":"eshipper_canpar_ground","eshipper_canpar_international":"eshipper_canpar_international","eshipper_canpar_select_letter":"eshipper_canpar_select_letter","eshipper_canpar_select_pak":"eshipper_canpar_select_pak","eshipper_canpar_select_parcel":"eshipper_canpar_select_parcel","eshipper_canpar_usa":"eshipper_canpar_usa","eshipper_canpar_usa_select_letter":"eshipper_canpar_usa_select_letter","eshipper_canpar_usa_select_pak":"eshipper_canpar_usa_select_pak","eshipper_canpar_usa_select_parcel":"eshipper_canpar_usa_select_parcel","eshipper_cpx_canada_post":"eshipper_cpx_canada_post","eshipper_day_ross_ltl":"eshipper_day_ross_ltl","eshipper_dhl_ground":"eshipper_dhl_ground","eshipper_dhl_economy_select":"eshipper_dhl_economy_select","eshipper_dhl_esi_export":"eshipper_dhl_esi_export","eshipper_dhl_express_1030am":"eshipper_dhl_express_1030am","eshipper_dhl_express_12pm":"eshipper_dhl_express_12pm","eshipper_dhl_express_9am":"eshipper_dhl_express_9am","eshipper_dhl_express_envelope":"eshipper_dhl_express_envelope","eshipper_dhl_express_worldwide":"eshipper_dhl_express_worldwide","eshipper_dhl_import_express":"eshipper_dhl_import_express","eshipper_dhl_import_express_12pm":"eshipper_dhl_import_express_12pm","eshipper_dhl_import_express_9am":"eshipper_dhl_import_express_9am","eshipper_apex_v":"eshipper_apex_v","eshipper_apex_trucking":"eshipper_apex_trucking","eshipper_apex_trucking_v":"eshipper_apex_trucking_v","eshipper_fastfrate_rail":"eshipper_fastfrate_rail","eshipper_kindersley_expedited":"eshipper_kindersley_expedited","eshipper_kindersley_rail":"eshipper_kindersley_rail","eshipper_kindersley_regular":"eshipper_kindersley_regular","eshipper_kindersley_road":"eshipper_kindersley_road","eshipper_kingsway_road":"eshipper_kingsway_road","eshipper_m_o_eastbound":"eshipper_m_o_eastbound","eshipper_mo_rail":"eshipper_mo_rail","eshipper_national_fastfreight_rail":"eshipper_national_fastfreight_rail","eshipper_national_fastfreight_road":"eshipper_national_fastfreight_road","eshipper_vitran_rail":"eshipper_vitran_rail","eshipper_vitran_road":"eshipper_vitran_road","eshipper_western_canada_rail":"eshipper_western_canada_rail","eshipper_fedex_2day_freight":"eshipper_fedex_2day_freight","eshipper_fedex_3day_freight":"eshipper_fedex_3day_freight","eshipper_fedex_2nd_day":"eshipper_fedex_2nd_day","eshipper_fedex_economy":"eshipper_fedex_economy","eshipper_fedex_first_overnight":"eshipper_fedex_first_overnight","eshipper_fedex_ground":"eshipper_fedex_ground","eshipper_fedex_ground_us":"eshipper_fedex_ground_us","eshipper_fedex_international_priority":"eshipper_fedex_international_priority","eshipper_fedex_international_priority_express":"eshipper_fedex_international_priority_express","eshipper_fedex_intl_economy":"eshipper_fedex_intl_economy","eshipper_fedex_intl_economy_freight":"eshipper_fedex_intl_economy_freight","eshipper_fedex_intl_priority":"eshipper_fedex_intl_priority","eshipper_fedex_intl_priority_express":"eshipper_fedex_intl_priority_express","eshipper_fedex_intl_priority_freight":"eshipper_fedex_intl_priority_freight","eshipper_fedex_priority":"eshipper_fedex_priority","eshipper_fedex_standard_overnight":"eshipper_fedex_standard_overnight","eshipper_flash_bird_ground":"eshipper_flash_bird_ground","eshipper_fleet_optics_ground":"eshipper_fleet_optics_ground","eshipper_project44_a_duie_pyle":"eshipper_project44_a_duie_pyle","eshipper_project44_aaa_cooper_transportation":"eshipper_project44_aaa_cooper_transportation","eshipper_project44_aberdeen_express":"eshipper_project44_aberdeen_express","eshipper_project44_abfs":"eshipper_project44_abfs","eshipper_project44_averitt_express":"eshipper_project44_averitt_express","eshipper_project44_brown_transfer_company":"eshipper_project44_brown_transfer_company","eshipper_project44_central_freight_lines":"eshipper_project44_central_freight_lines","eshipper_project44_central_transport":"eshipper_project44_central_transport","eshipper_project44_chicago_suburban_express":"eshipper_project44_chicago_suburban_express","eshipper_project44_clear_lane_freight":"eshipper_project44_clear_lane_freight","eshipper_project44_con_way_freight":"eshipper_project44_con_way_freight","eshipper_project44_crosscountry_courier":"eshipper_project44_crosscountry_courier","eshipper_project44_day_ross":"eshipper_project44_day_ross","eshipper_project44_day_ross_v":"eshipper_project44_day_ross_v","eshipper_project44_dayton_freight_lines":"eshipper_project44_dayton_freight_lines","eshipper_project44_dependable_highway_express":"eshipper_project44_dependable_highway_express","eshipper_project44_dohrn_transfer_company":"eshipper_project44_dohrn_transfer_company","eshipper_project44_dugan_truck_line":"eshipper_project44_dugan_truck_line","eshipper_project44_estes_express_lines":"eshipper_project44_estes_express_lines","eshipper_project44_expedited_freight_systems":"eshipper_project44_expedited_freight_systems","eshipper_project44_fedex_freight_canada":"eshipper_project44_fedex_freight_canada","eshipper_project44_fedex_freight_east":"eshipper_project44_fedex_freight_east","eshipper_project44_fedex_freight_national_canada":"eshipper_project44_fedex_freight_national_canada","eshipper_project44_fedex_freight_national_usa":"eshipper_project44_fedex_freight_national_usa","eshipper_project44_fedex_freight_usa":"eshipper_project44_fedex_freight_usa","eshipper_project44_fedex_national":"eshipper_project44_fedex_national","eshipper_project44_forwardair":"eshipper_project44_forwardair","eshipper_project44_frontline_freight":"eshipper_project44_frontline_freight","eshipper_project44_holland_motor_express":"eshipper_project44_holland_motor_express","eshipper_project44_lakeville_motor_express":"eshipper_project44_lakeville_motor_express","eshipper_project44_manitoulin_tlx_inc":"eshipper_project44_manitoulin_tlx_inc","eshipper_project44_midwest_motor_express":"eshipper_project44_midwest_motor_express","eshipper_project44_monroe_transportation_services":"eshipper_project44_monroe_transportation_services","eshipper_project44_n_m_transfer":"eshipper_project44_n_m_transfer","eshipper_project44_new_england_motor_freight":"eshipper_project44_new_england_motor_freight","eshipper_project44_new_penn_motor_express":"eshipper_project44_new_penn_motor_express","eshipper_project44_pitt_ohio":"eshipper_project44_pitt_ohio","eshipper_project44_polaris":"eshipper_project44_polaris","eshipper_project44_purolator_freight":"eshipper_project44_purolator_freight","eshipper_project44_r_l_carriers":"eshipper_project44_r_l_carriers","eshipper_project44_roadrunner_transportation_services":"eshipper_project44_roadrunner_transportation_services","eshipper_project44_saia_motor_freight":"eshipper_project44_saia_motor_freight","eshipper_project44_southeastern_freight_lines":"eshipper_project44_southeastern_freight_lines","eshipper_project44_southwestern_motor_transport":"eshipper_project44_southwestern_motor_transport","eshipper_project44_standard_forwarding":"eshipper_project44_standard_forwarding","eshipper_project44_total_transportation_distribution":"eshipper_project44_total_transportation_distribution","eshipper_project44_tst_overland_express":"eshipper_project44_tst_overland_express","eshipper_project44_ups":"eshipper_project44_ups","eshipper_project44_usf_reddaway":"eshipper_project44_usf_reddaway","eshipper_project44_valley_cartage":"eshipper_project44_valley_cartage","eshipper_project44_vision_express_ltl":"eshipper_project44_vision_express_ltl","eshipper_project44_ward_trucking":"eshipper_project44_ward_trucking","eshipper_project44_xpo_logistics":"eshipper_project44_xpo_logistics","eshipper_project44_xpress_global_systems":"eshipper_project44_xpress_global_systems","eshipper_project44_yrc":"eshipper_project44_yrc","eshipper_purolator_express":"eshipper_purolator_express","eshipper_purolator_express_1030":"eshipper_purolator_express_1030","eshipper_purolator_express_9am":"eshipper_purolator_express_9am","eshipper_purolator_expresscheque":"eshipper_purolator_expresscheque","eshipper_purolator_ground":"eshipper_purolator_ground","eshipper_purolator_ground_1030":"eshipper_purolator_ground_1030","eshipper_purolator_ground_9am":"eshipper_purolator_ground_9am","eshipper_purolator":"eshipper_purolator","eshipper_purolator_10_30":"eshipper_purolator_10_30","eshipper_purolator_9am":"eshipper_purolator_9am","eshipper_purolator_puropak":"eshipper_purolator_puropak","eshipper_purolator_puropak_10_30":"eshipper_purolator_puropak_10_30","eshipper_purolator_puropak_9am":"eshipper_purolator_puropak_9am","eshipper_pyk_ground_advantage":"eshipper_pyk_ground_advantage","eshipper_pyk_priority_mail":"eshipper_pyk_priority_mail","eshipper_sameday_9_am_guaranteed":"eshipper_sameday_9_am_guaranteed","eshipper_sameday_am_service":"eshipper_sameday_am_service","eshipper_sameday_ground_service":"eshipper_sameday_ground_service","eshipper_sameday_h1_deliver_to_curbside":"eshipper_sameday_h1_deliver_to_curbside","eshipper_sameday_h4_delivery_to_curbside":"eshipper_sameday_h4_delivery_to_curbside","eshipper_sameday_h5_delivery_to_room_of_choice_2_man":"eshipper_sameday_h5_delivery_to_room_of_choice_2_man","eshipper_sameday_h6_delivery_packaging_removal_2_man":"eshipper_sameday_h6_delivery_packaging_removal_2_man","eshipper_sameday_ltl_service":"eshipper_sameday_ltl_service","eshipper_sameday_pm_service":"eshipper_sameday_pm_service","eshipper_sameday_urgent_letter":"eshipper_sameday_urgent_letter","eshipper_sameday_urgent_pac":"eshipper_sameday_urgent_pac","eshipper_skip":"eshipper_skip","eshipper_smarte_post_intl_dhl_parcel_international_direct_ngr":"eshipper_smarte_post_intl_dhl_parcel_international_direct_ngr","eshipper_smarte_post_intl_global_mail_business_priority":"eshipper_smarte_post_intl_global_mail_business_priority","eshipper_smarte_post_intl_global_mail_business_standard":"eshipper_smarte_post_intl_global_mail_business_standard","eshipper_smarte_post_intl_global_mail_packet_plus_priority":"eshipper_smarte_post_intl_global_mail_packet_plus_priority","eshipper_smarte_post_intl_global_mail_packet_priority":"eshipper_smarte_post_intl_global_mail_packet_priority","eshipper_smarte_post_intl_global_mail_packet_standard":"eshipper_smarte_post_intl_global_mail_packet_standard","eshipper_smarte_post_intl_global_mail_parcel_direct_priority_yyz":"eshipper_smarte_post_intl_global_mail_parcel_direct_priority_yyz","eshipper_smarte_post_intl_global_mail_parcel_direct_standard_yyz":"eshipper_smarte_post_intl_global_mail_parcel_direct_standard_yyz","eshipper_smarte_post_intl_global_mail_parcel_priority":"eshipper_smarte_post_intl_global_mail_parcel_priority","eshipper_smarte_post_intl_global_mail_parcel_standard":"eshipper_smarte_post_intl_global_mail_parcel_standard","eshipper_ups_expedited":"eshipper_ups_expedited","eshipper_ups_express":"eshipper_ups_express","eshipper_ups_express_early_am":"eshipper_ups_express_early_am","eshipper_ups_ground":"eshipper_ups_ground","eshipper_ups_second_day_air_a_m":"eshipper_ups_second_day_air_a_m","eshipper_ups_standard":"eshipper_ups_standard","eshipper_ups_three_day_select":"eshipper_ups_three_day_select","eshipper_ups_saver":"eshipper_ups_saver","eshipper_ups_worldwide_expedited":"eshipper_ups_worldwide_expedited","eshipper_ups_worldwide_express":"eshipper_ups_worldwide_express","eshipper_ups_worldwide_express_plus":"eshipper_ups_worldwide_express_plus","eshipper_usps_first_class_package_return_service":"eshipper_usps_first_class_package_return_service","eshipper_usps_library_mail":"eshipper_usps_library_mail","eshipper_usps_media_mail":"eshipper_usps_media_mail","eshipper_usps_parcel_select":"eshipper_usps_parcel_select","eshipper_usps_pbx":"eshipper_usps_pbx","eshipper_usps_pbx_lightweight":"eshipper_usps_pbx_lightweight","eshipper_usps_priority_mail":"eshipper_usps_priority_mail","eshipper_usps_priority_mail_express":"eshipper_usps_priority_mail_express","eshipper_usps_priority_mail_open_and_distribute":"eshipper_usps_priority_mail_open_and_distribute","eshipper_usps_priority_mail_return_service":"eshipper_usps_priority_mail_return_service","eshipper_usps_retail_ground_formerly_standard_post":"eshipper_usps_retail_ground_formerly_standard_post","eshipper_project44_abf_freight":"eshipper_project44_abf_freight","eshipper_canada_worldwide_next_flight_out":"eshipper_canada_worldwide_next_flight_out","eshipper_project44_conway_freight":"eshipper_project44_conway_freight","eshipper_day_and_ross":"eshipper_day_and_ross","eshipper_day_ross_r_and_l":"eshipper_day_ross_r_and_l","eshipper_project44_daylight_transport":"eshipper_project44_daylight_transport","eshipper_smarte_post_int_l_dhl_packet_international":"eshipper_smarte_post_int_l_dhl_packet_international","eshipper_smarte_post_int_l_dhl_parcel_international_direct":"eshipper_smarte_post_int_l_dhl_parcel_international_direct","eshipper_smarte_post_int_l_dhl_parcel_international_standard":"eshipper_smarte_post_int_l_dhl_parcel_international_standard","eshipper_envoi_same_day_delivery":"eshipper_envoi_same_day_delivery","eshipper_smarte_post_dom_expedited":"eshipper_smarte_post_dom_expedited","eshipper_fedex_freight_economy":"eshipper_fedex_freight_economy","eshipper_fedex_freight_priority":"eshipper_fedex_freight_priority","eshipper_fedex_international_connect_plus":"eshipper_fedex_international_connect_plus","eshipper_project44_forward_air":"eshipper_project44_forward_air","eshipper_sameday_h3_delivery_packaging_removal":"eshipper_sameday_h3_delivery_packaging_removal","eshipper_project44_jp_express":"eshipper_project44_jp_express","eshipper_mainliner_road":"eshipper_mainliner_road","eshipper_project44_mountain_valley_express":"eshipper_project44_mountain_valley_express","eshipper_project44_oak_harbor_freight":"eshipper_project44_oak_harbor_freight","eshipper_project44_old_dominion_freight":"eshipper_project44_old_dominion_freight","eshipper_purolator_puroletter":"eshipper_purolator_puroletter","eshipper_purolator_puroletter_10_30":"eshipper_purolator_puroletter_10_30","eshipper_purolator_puroletter_9am":"eshipper_purolator_puroletter_9am","eshipper_project44_rl_carriers":"eshipper_project44_rl_carriers","eshipper_project44_saia_ltl_freight":"eshipper_project44_saia_ltl_freight","eshipper_speedy":"eshipper_speedy","eshipper_tforce_freight_ltl":"eshipper_tforce_freight_ltl","eshipper_tforce_freight_ltl_guaranteed":"eshipper_tforce_freight_ltl_guaranteed","eshipper_tforce_freight_ltl_guaranteed_a_m":"eshipper_tforce_freight_ltl_guaranteed_a_m","eshipper_tforce_standard_ltl":"eshipper_tforce_standard_ltl","eshipper_ups":"eshipper_ups","eshipper_ups_freight":"eshipper_ups_freight","eshipper_ups_freight_canada":"eshipper_ups_freight_canada","eshipper_ods_usps_light_weight_parcel_budget":"eshipper_ods_usps_light_weight_parcel_budget","eshipper_ods_usps_light_weight_parcel_expedited":"eshipper_ods_usps_light_weight_parcel_expedited","eshipper_ods_usps_parcel_select_budget":"eshipper_ods_usps_parcel_select_budget","eshipper_ods_usps_parcel_select_expedited":"eshipper_ods_usps_parcel_select_expedited","eshipper_smarte_post_dom_xpresspost":"eshipper_smarte_post_dom_xpresspost"},"shipping_options":{"eshipper_signature_required":{"code":"signatureRequired","meta":{"configurable":true},"type":"boolean","label":"Signature Required"},"eshipper_insurance_type":{"code":"insuranceType","meta":{"configurable":true},"type":"string","label":"Insurance Type"},"eshipper_dangerous_goods_type":{"code":"dangerousGoodsType","meta":{"configurable":true},"type":"boolean","label":"Dangerous Goods Type"},"eshipper_cod":{"code":"cod","meta":{"configurable":true},"type":"float","label":"Cod"},"eshipper_is_saturday_service":{"code":"isSaturdayService","meta":{"configurable":true},"type":"boolean","label":"Is Saturday Service"},"eshipper_hold_for_pickup_required":{"code":"holdForPickupRequired","meta":{"configurable":true},"type":"boolean","label":"Hold For Pickup Required"},"eshipper_special_equipment":{"code":"specialEquipment","meta":{"configurable":true},"type":"boolean","label":"Special Equipment"},"eshipper_inside_delivery":{"code":"insideDelivery","meta":{"configurable":true},"type":"boolean","label":"Inside Delivery"},"eshipper_delivery_appointment":{"code":"deliveryAppointment","meta":{"configurable":true},"type":"boolean","label":"Delivery Appointment"},"eshipper_inside_pickup":{"code":"insidePickup","meta":{"configurable":true},"type":"boolean","label":"Inside Pickup"},"eshipper_saturday_pickup_required":{"code":"saturdayPickupRequired","meta":{"configurable":true},"type":"boolean","label":"Saturday Pickup Required"},"eshipper_stackable":{"code":"stackable","meta":{"configurable":true},"type":"boolean","label":"Stackable"}},"readme":null},{"id":"fedex","carrier_name":"fedex","display_name":"FedEx","integration_status":"production-ready","website":"https://www.fedex.com","description":"FedEx Corporation is an American multinational conglomerate holding company which focuses on transportation, e-commerce and business services.","documentation":null,"is_enabled":true,"capabilities":["Rating","Pickup","Paperless","Shipping","Tracking"],"connection_fields":{"api_key":{"name":"api_key","required":false,"sensitive":false,"type":"string","label":"API Key"},"secret_key":{"name":"secret_key","required":false,"sensitive":false,"type":"string","label":"Secret Key"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"track_api_key":{"name":"track_api_key","required":false,"sensitive":false,"type":"string","label":"Track Api Key"},"track_secret_key":{"name":"track_secret_key","required":false,"sensitive":false,"type":"string","label":"Track Secret Key"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_type":{"code":"label_type","enum":["PDF_4x6","PDF_4x6_75","PDF_4x8","PDF_4x9","ZPL_4x6","ZPL_4x6_75","ZPL_4x8","ZPL_4x9"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"smart_post_hub_id":{"code":"smart_post_hub_id","name":"smart_post_hub_id","required":false,"type":"string","label":"Smart Post Hub Id"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"locale":{"code":"locale","enum":["en_US","fr_CA"],"name":"locale","required":false,"type":"string","label":"Locale"}},"shipping_services":{"fedex_international_priority_express":"FEDEX_INTERNATIONAL_PRIORITY_EXPRESS","fedex_international_first":"INTERNATIONAL_FIRST","fedex_international_priority":"FEDEX_INTERNATIONAL_PRIORITY","fedex_international_economy":"INTERNATIONAL_ECONOMY","fedex_ground":"FEDEX_GROUND","fedex_cargo_mail":"FEDEX_CARGO_MAIL","fedex_cargo_international_premium":"FEDEX_CARGO_INTERNATIONAL_PREMIUM","fedex_first_overnight":"FIRST_OVERNIGHT","fedex_first_overnight_freight":"FIRST_OVERNIGHT_FREIGHT","fedex_1_day_freight":"FEDEX_1_DAY_FREIGHT","fedex_2_day_freight":"FEDEX_2_DAY_FREIGHT","fedex_3_day_freight":"FEDEX_3_DAY_FREIGHT","fedex_international_priority_freight":"INTERNATIONAL_PRIORITY_FREIGHT","fedex_international_economy_freight":"INTERNATIONAL_ECONOMY_FREIGHT","fedex_cargo_airport_to_airport":"FEDEX_CARGO_AIRPORT_TO_AIRPORT","fedex_international_priority_distribution":"INTERNATIONAL_PRIORITY_DISTRIBUTION","fedex_ip_direct_distribution_freight":"FEDEX_IP_DIRECT_DISTRIBUTION_FREIGHT","fedex_intl_ground_distribution":"INTL_GROUND_DISTRIBUTION","fedex_ground_home_delivery":"GROUND_HOME_DELIVERY","fedex_smart_post":"SMART_POST","fedex_priority_overnight":"PRIORITY_OVERNIGHT","fedex_standard_overnight":"STANDARD_OVERNIGHT","fedex_2_day":"FEDEX_2_DAY","fedex_2_day_am":"FEDEX_2_DAY_AM","fedex_express_saver":"FEDEX_EXPRESS_SAVER","fedex_same_day":"SAME_DAY","fedex_same_day_city":"SAME_DAY_CITY","fedex_one_day_freight":"FEDEX_ONE_DAY_FREIGHT","fedex_international_economy_distribution":"INTERNATIONAL_ECONOMY_DISTRIBUTION","fedex_international_connect_plus":"FEDEX_INTERNATIONAL_CONNECT_PLUS","fedex_international_distribution_freight":"INTERNATIONAL_DISTRIBUTION_FREIGHT","fedex_regional_economy":"FEDEX_REGIONAL_ECONOMY","fedex_next_day_freight":"FEDEX_NEXT_DAY_FREIGHT","fedex_next_day":"FEDEX_NEXT_DAY","fedex_next_day_10am":"FEDEX_NEXT_DAY_10AM","fedex_next_day_12pm":"FEDEX_NEXT_DAY_12PM","fedex_next_day_end_of_day":"FEDEX_NEXT_DAY_END_OF_DAY","fedex_distance_deferred":"FEDEX_DISTANCE_DEFERRED"},"shipping_options":{"fedex_appointment":{"code":"APPOINTMENT","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Appointment"},"fedex_broker_select_option":{"code":"BROKER_SELECT_OPTION","meta":{"configurable":true},"type":"boolean","label":"FedEx Broker Select Option"},"fedex_call_before_delivery":{"code":"CALL_BEFORE_DELIVERY","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"FedEx Call Before Delivery"},"fedex_cod":{"code":"COD","meta":{"category":"COD","configurable":true},"type":"float","label":"FedEx COD"},"fedex_custom_delivery_window":{"code":"CUSTOM_DELIVERY_WINDOW","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Custom Delivery Window"},"fedex_cut_flowers":{"code":"CUT_FLOWERS","meta":{"configurable":true},"type":"boolean","label":"FedEx Cut Flowers"},"fedex_do_not_break_down_pallets":{"code":"DO_NOT_BREAK_DOWN_PALLETS","meta":{"configurable":true},"type":"boolean","label":"FedEx Do Not Break Down Pallets"},"fedex_do_not_stack_pallets":{"code":"DO_NOT_STACK_PALLETS","meta":{"configurable":true},"type":"boolean","label":"FedEx Do Not Stack Pallets"},"fedex_dry_ice":{"code":"DRY_ICE","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"FedEx Dry Ice"},"fedex_east_coast_special":{"code":"EAST_COAST_SPECIAL","meta":{"configurable":true},"type":"boolean","label":"FedEx East Coast Special"},"fedex_exclude_from_consolidation":{"code":"EXCLUDE_FROM_CONSOLIDATION","meta":{"configurable":true},"type":"boolean","label":"FedEx Exclude From Consolidation"},"fedex_extreme_length":{"code":"EXTREME_LENGTH","meta":{"configurable":true},"type":"boolean","label":"FedEx Extreme Length"},"fedex_inside_delivery":{"code":"INSIDE_DELIVERY","meta":{"configurable":true},"type":"boolean","label":"FedEx Inside Delivery"},"fedex_inside_pickup":{"code":"INSIDE_PICKUP","meta":{"configurable":true},"type":"boolean","label":"FedEx Inside Pickup"},"fedex_international_controlled_export_service":{"code":"INTERNATIONAL_CONTROLLED_EXPORT_SERVICE","meta":{"configurable":true},"type":"boolean","label":"FedEx International Controlled Export Service"},"fedex_third_party_consignee":{"code":"THIRD_PARTY_CONSIGNEE","meta":{"configurable":true},"type":"boolean","label":"FedEx Third Party Consignee"},"fedex_electronic_trade_documents":{"code":"ELECTRONIC_TRADE_DOCUMENTS","meta":{"category":"PAPERLESS","configurable":true},"type":"boolean","label":"FedEx Electronic Trade Documents"},"fedex_food":{"code":"FOOD","meta":{"configurable":true},"type":"boolean","label":"FedEx Food"},"fedex_future_day_shipment":{"code":"FUTURE_DAY_SHIPMENT","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Future Day Shipment"},"fedex_hold_at_location":{"code":"HOLD_AT_LOCATION","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"FedEx Hold At Location"},"fedex_international_traffic_in_arms_regulations":{"code":"INTERNATIONAL_TRAFFIC_IN_ARMS_REGULATIONS","meta":{"configurable":true},"type":"boolean","label":"FedEx International Traffic In Arms Regulations"},"fedex_liftgate_delivery":{"code":"LIFTGATE_DELIVERY","meta":{"configurable":true},"type":"boolean","label":"FedEx Liftgate Delivery"},"fedex_liftgate_pickup":{"code":"LIFTGATE_PICKUP","meta":{"configurable":true},"type":"boolean","label":"FedEx Liftgate Pickup"},"fedex_limited_access_delivery":{"code":"LIMITED_ACCESS_DELIVERY","meta":{"configurable":true},"type":"boolean","label":"FedEx Limited Access Delivery"},"fedex_limited_access_pickup":{"code":"LIMITED_ACCESS_PICKUP","meta":{"configurable":true},"type":"boolean","label":"FedEx Limited Access Pickup"},"fedex_over_length":{"code":"OVER_LENGTH","meta":{"configurable":true},"type":"boolean","label":"FedEx Over Length"},"fedex_pending_shipment":{"code":"PENDING_SHIPMENT","meta":{"configurable":true},"type":"boolean","label":"FedEx Pending Shipment"},"fedex_pharmacy_delivery":{"code":"PHARMACY_DELIVERY","meta":{"configurable":true},"type":"boolean","label":"FedEx Pharmacy Delivery"},"fedex_poison":{"code":"POISON","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"FedEx Poison"},"fedex_home_delivery_premium":{"code":"HOME_DELIVERY_PREMIUM","meta":{"configurable":true},"type":"boolean","label":"FedEx Home Delivery Premium"},"fedex_protection_from_freezing":{"code":"PROTECTION_FROM_FREEZING","meta":{"configurable":true},"type":"boolean","label":"FedEx Protection From Freezing"},"fedex_returns_clearance":{"code":"RETURNS_CLEARANCE","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"FedEx Returns Clearance"},"fedex_return_shipment":{"code":"RETURN_SHIPMENT","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"FedEx Return Shipment"},"fedex_saturday_pickup":{"code":"SATURDAY_PICKUP","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Saturday Pickup"},"fedex_event_notification":{"code":"EVENT_NOTIFICATION","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"FedEx Event Notification"},"fedex_delivery_on_invoice_acceptance":{"code":"DELIVERY_ON_INVOICE_ACCEPTANCE","meta":{"configurable":true},"type":"boolean","label":"FedEx Delivery On Invoice Acceptance"},"fedex_top_load":{"code":"TOP_LOAD","meta":{"configurable":true},"type":"boolean","label":"FedEx Top Load"},"fedex_one_rate":{"code":"FEDEX_ONE_RATE","meta":{"configurable":true},"type":"boolean","label":"FedEx One Rate"},"fedex_freight_guarantee":{"code":"FREIGHT_GUARANTEE","meta":{"configurable":true},"type":"boolean","label":"FedEx Freight Guarantee"},"fedex_saturday_delivery":{"code":"SATURDAY_DELIVERY","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Saturday Delivery"},"fedex_pickup_type":{"code":"fedex_pickup_type","enum":["DROPOFF_AT_FEDEX_LOCATION","CONTACT_FEDEX_TO_SCHEDULE","USE_SCHEDULED_PICKUP"],"help":"How the shipper will tender the package to FedEx. Valid values: DROPOFF_AT_FEDEX_LOCATION, CONTACT_FEDEX_TO_SCHEDULE, USE_SCHEDULED_PICKUP. Defaults to DROPOFF_AT_FEDEX_LOCATION.","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"FedEx Pickup Type"},"fedex_department_number":{"code":"fedex_department_number","meta":{"configurable":true},"type":"string","label":"FedEx Department Number"},"fedex_po_number":{"code":"fedex_po_number","meta":{"configurable":true},"type":"string","label":"FedEx PO Number"},"fedex_rma_association":{"code":"fedex_rma_association","meta":{"configurable":true},"type":"string","label":"FedEx RMA Association"},"fedex_smart_post_hub_id":{"code":"SMART_POST_HUB_ID","meta":{"configurable":true},"type":"string","label":"FedEx Smart Post Hub ID"},"fedex_smart_post_allowed_indicia":{"code":"SMART_POST_ALLOWED_INDICIA","meta":{"configurable":true},"type":"string","label":"FedEx Smart Post Allowed Indicia"},"fedex_alcohol":{"code":"ALCOHOL","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"FedEx Alcohol"},"fedex_battery":{"code":"BATTERY","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"FedEx Battery"},"fedex_dangerous_goods":{"code":"DANGEROUS_GOODS","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"FedEx Dangerous Goods"},"fedex_priority_alert":{"code":"PRIORITY_ALERT","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"FedEx Priority Alert"},"fedex_priority_alert_plus":{"code":"PRIORITY_ALERT_PLUS","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"FedEx Priority Alert Plus"},"fedex_non_standard_container":{"code":"NON_STANDARD_CONTAINER","meta":{"configurable":true},"type":"boolean","label":"FedEx Non Standard Container"},"fedex_piece_count_verification":{"code":"PIECE_COUNT_VERIFICATION","meta":{"configurable":true},"type":"boolean","label":"FedEx Piece Count Verification"},"fedex_signature_option":{"code":"SIGNATURE_OPTION","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"FedEx Signature Option"},"fedex_evening":{"code":"EVENING","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Evening"},"fedex_date_certain":{"code":"DATE_CERTAIN","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Date Certain"},"doc_files":{"code":"doc_files","meta":{"category":"PAPERLESS","configurable":true},"type":"object","label":"Doc Files"},"doc_references":{"code":"doc_references","meta":{"category":"PAPERLESS","configurable":true},"type":"object","label":"Doc References"},"shipper_instructions":{"code":"shipper_instructions","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"Shipper Instructions"},"recipient_instructions":{"code":"recipient_instructions","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"Recipient Instructions"}},"readme":null},{"id":"freightcom","carrier_name":"freightcom","display_name":"Freightcom","integration_status":"deprecated","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"freightcom_all":"0","freightcom_usf_holland":"1911","freightcom_central_transport":"2029","freightcom_estes":"2107","freightcom_canpar_ground":"3400","freightcom_canpar_select":"3404","freightcom_canpar_overnight":"3407","freightcom_dicom_ground":"3700","freightcom_purolator_ground":"4000","freightcom_purolator_express":"4003","freightcom_purolator_express_9_am":"4004","freightcom_purolator_express_10_30_am":"4005","freightcom_purolator_ground_us":"4016","freightcom_purolator_express_us":"4015","freightcom_purolator_express_us_9_am":"4013","freightcom_purolator_express_us_10_30_am":"4014","freightcom_fedex_express_saver":"4100","freightcom_fedex_ground":"4101","freightcom_fedex_2day":"4102","freightcom_fedex_priority_overnight":"4104","freightcom_fedex_standard_overnight":"4105","freightcom_fedex_first_overnight":"4106","freightcom_fedex_international_priority":"4108","freightcom_fedex_international_economy":"4109","freightcom_ups_standard":"4600","freightcom_ups_expedited":"4601","freightcom_ups_express_saver":"4602","freightcom_ups_express":"4603","freightcom_ups_express_early":"4604","freightcom_ups_3day_select":"4605","freightcom_ups_worldwide_expedited":"4606","freightcom_ups_worldwide_express":"4607","freightcom_ups_worldwide_express_plus":"4608","freightcom_ups_worldwide_express_saver":"4609","freightcom_dhl_express_easy":"5202","freightcom_dhl_express_10_30":"5208","freightcom_dhl_express_worldwide":"5211","freightcom_dhl_express_12_00":"5215","freightcom_dhl_economy_select":"5216","freightcom_dhl_ecommerce_am_service":"5706","freightcom_dhl_ecommerce_ground_service":"5707","freightcom_canadapost_regular_parcel":"6301","freightcom_canadapost_expedited_parcel":"6300","freightcom_canadapost_xpresspost":"6303","freightcom_canadapost_priority":"6302"},"shipping_options":{"freightcom_saturday_pickup_required":{"code":"saturdayPickupRequired","meta":{"configurable":true},"type":"boolean","label":"Saturday Pickup Required"},"freightcom_homeland_security":{"code":"homelandSecurity","meta":{"configurable":true},"type":"boolean","label":"Homeland Security"},"freightcom_exhibition_convention_site":{"code":"exhibitionConventionSite","meta":{"configurable":true},"type":"boolean","label":"Exhibition Convention Site"},"freightcom_military_base_delivery":{"code":"militaryBaseDelivery","meta":{"configurable":true},"type":"boolean","label":"Military Base Delivery"},"freightcom_customs_in_bond_freight":{"code":"customsIn_bondFreight","meta":{"configurable":true},"type":"boolean","label":"Customs In Bond Freight"},"freightcom_limited_access":{"code":"limitedAccess","meta":{"configurable":true},"type":"boolean","label":"Limited Access"},"freightcom_excess_length":{"code":"excessLength","meta":{"configurable":true},"type":"boolean","label":"Excess Length"},"freightcom_tailgate_pickup":{"code":"tailgatePickup","meta":{"configurable":true},"type":"boolean","label":"Tailgate Pickup"},"freightcom_residential_pickup":{"code":"residentialPickup","meta":{"configurable":true},"type":"boolean","label":"Residential Pickup"},"freightcom_cross_border_fee":{"code":"crossBorderFee","meta":{"configurable":true},"type":"boolean","label":"Cross Border Fee"},"freightcom_notify_recipient":{"code":"notifyRecipient","meta":{"configurable":true},"type":"boolean","label":"Notify Recipient"},"freightcom_single_shipment":{"code":"singleShipment","meta":{"configurable":true},"type":"boolean","label":"Single Shipment"},"freightcom_tailgate_delivery":{"code":"tailgateDelivery","meta":{"configurable":true},"type":"boolean","label":"Tailgate Delivery"},"freightcom_residential_delivery":{"code":"residentialDelivery","meta":{"configurable":true},"type":"boolean","label":"Residential Delivery"},"freightcom_insurance_type":{"code":"insuranceType","meta":{"configurable":true},"type":"float","label":"Insurance Type"},"freightcom_inside_delivery":{"code":"insideDelivery","meta":{"configurable":true},"type":"boolean","label":"Inside Delivery"},"freightcom_is_saturday_service":{"code":"isSaturdayService","meta":{"configurable":true},"type":"boolean","label":"Is Saturday Service"},"freightcom_dangerous_goods_type":{"code":"dangerousGoodsType","meta":{"configurable":true},"type":"boolean","label":"Dangerous Goods Type"},"freightcom_stackable":{"code":"stackable","meta":{"configurable":true},"type":"boolean","label":"Stackable"}},"readme":null},{"id":"generic","carrier_name":"generic","display_name":"Custom Carrier","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Rating","Shipping"],"connection_fields":{"display_name":{"name":"display_name","required":true,"sensitive":false,"type":"string","label":"Display Name"},"custom_carrier_name":{"name":"custom_carrier_name","required":true,"sensitive":false,"type":"string","label":"Custom Carrier Name"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"},"label_template":{"name":"label_template","required":false,"sensitive":false,"type":"object","label":"Label Template"}},"config_fields":{"text_color":{"code":"text_color","name":"text_color","required":false,"type":"string","label":"Text Color"},"brand_color":{"code":"brand_color","name":"brand_color","required":false,"type":"string","label":"Brand Color"}},"shipping_services":{"standard_service":"standard"},"shipping_options":{"tracking_number_reference":{"code":"tracking_number","meta":{"configurable":true},"type":"string","label":"Tracking Number Reference"}},"readme":null},{"id":"geodis","carrier_name":"geodis","display_name":"GEODIS","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"identifier":{"name":"identifier","required":true,"sensitive":false,"type":"string","label":"Identifier"},"code_client":{"name":"code_client","required":false,"sensitive":false,"type":"string","label":"Code Client"},"language":{"default":"fr","enum":["fr","en"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"FR","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"agency_code":{"code":"agency_code","name":"agency_code","required":false,"type":"string","label":"Agency Code"}},"shipping_services":{"geodis_EXP":"EXP","geodis_MES":"MES","geodis_express_france":"NTX","geodis_retour_trans_fr_messagerie_plus":"ENL"},"shipping_options":{"geodis_web_appointment":{"code":"RDW","meta":{"configurable":true},"type":"boolean","label":"Web Appointment"},"geodis_telephone_appointment":{"code":"RDT","meta":{"configurable":true},"type":"boolean","label":"Telephone Appointment"},"geodis_pick_up_at_a_geodis_agency":{"code":"BRT","meta":{"configurable":true},"type":"boolean","label":"Pick Up At A Geodis Agency"},"geodis_desired_date_of_delivery":{"code":"DSL","meta":{"configurable":true},"type":"boolean","label":"Desired Date Of Delivery"},"geodis_delivery_on_a_saturday_morning":{"code":"SAT","meta":{"configurable":true},"type":"boolean","label":"Delivery On A Saturday Morning"},"geodis_validate_envoi":{"code":"validate_envoi","meta":{"configurable":true},"type":"boolean","label":"Validate Envoi"},"geodis_no_recepisse":{"code":"no_recepisse","meta":{"configurable":true},"type":"string","label":"No Recepisse"},"geodis_instruction_enlevement":{"code":"instruction_enlevement","meta":{"configurable":true},"type":"string","label":"Instruction Enlevement"},"geodis_date_livraison":{"code":"date_livraison","meta":{"configurable":true},"type":"string","label":"Date Livraison"},"geodis_heure_livraison":{"code":"heure_livraison","meta":{"configurable":true},"type":"string","label":"Heure Livraison"},"geodis_instruction_livraison":{"code":"instruction_livraison","meta":{"configurable":true},"type":"string","label":"Instruction Livraison"}},"readme":null},{"id":"gls","carrier_name":"gls","display_name":"GLS Group","integration_status":"development","website":"https://www.gls-group.net","description":"GLS Group shipping services","documentation":null,"is_enabled":true,"capabilities":["Tracking","Pickup","Rating","Shipping"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"contact_id":{"name":"contact_id","required":false,"sensitive":false,"type":"string","label":"Contact Id"},"account_country_code":{"default":"DE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_format":{"code":"label_format","name":"label_format","required":false,"type":"string","label":"Label Format"},"printer_language":{"code":"printer_language","name":"printer_language","required":false,"type":"string","label":"Printer Language"},"template_name":{"code":"template_name","name":"template_name","required":false,"type":"string","label":"Template Name"}},"shipping_services":{"gls_parcel":"PARCEL","gls_express":"EXPRESS","gls_guaranteed24":"GUARANTEED24","gls_business_parcel":"BUSINESSPARCEL","gls_euro_business_parcel":"EUROBUSINESSPARCEL"},"shipping_options":{"gls_guaranteed24":{"code":"GUARANTEED24","help":"Guaranteed next-day delivery service","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"GLS Guaranteed24"},"gls_saturday_delivery":{"code":"SaturdayService","help":"Enable Saturday delivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"GLS Saturday Delivery"},"gls_flex_delivery":{"code":"FlexDeliveryService","help":"Notify recipient about delivery options","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"GLS Flex Delivery"},"gls_deposit_service":{"code":"DepositService","help":"Enable delivery to a predefined deposit location","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"GLS Deposit Service"},"gls_deposit_description":{"code":"DepositDescription","help":"Description of the deposit location (e.g., 'Behind the garage')","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"GLS Deposit Description"},"gls_deposit_contact":{"code":"DepositContact","help":"Contact person at the deposit location","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"GLS Deposit Contact"},"gls_express_parcel":{"code":"ExpressParcel","help":"Enable express shipping","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"GLS Express Parcel"},"gls_time_definite_service":{"code":"TimeDefiniteService","help":"Set specific delivery time (before 8 AM, 9 AM, 10 AM, 12 PM)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"GLS Time Definite Service"},"gls_shop_delivery":{"code":"ShopDeliveryService","help":"Delivery to a GLS ParcelShop","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"GLS Shop Delivery"},"gls_shop_id":{"code":"ShopID","help":"GLS ParcelShop ID for delivery","meta":{"category":"PUDO","configurable":true},"type":"string","label":"GLS Shop ID"},"gls_shop_auto_determine":{"code":"ShopAutoSelect","help":"Automatically determine nearest GLS ParcelShop based on recipient address","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"GLS Shop Auto Determine"},"gls_addressee_only":{"code":"AddresseeOnlyService","help":"Delivery only to the addressee (no neighbor delivery)","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"GLS Addressee Only"},"gls_signature_service":{"code":"SignatureService","help":"Require signature upon delivery","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"GLS Signature Service"},"gls_ident_pin_service":{"code":"IdentPINService","help":"Identification via PIN code at delivery","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"GLS Ident Pin Service"},"gls_add_on_liability":{"code":"AddOnLiabilityService","help":"Add extra liability coverage for shipments","meta":{"category":"INSURANCE","configurable":true},"type":"boolean","label":"GLS Add On Liability"},"gls_pick_and_return":{"code":"PickAndReturnService","help":"Enable pick and return service","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"GLS Pick And Return"},"gls_shop_return":{"code":"ShopReturnService","help":"Add a pre-printed return label inside the package","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"GLS Shop Return"},"gls_return_enabled":{"code":"ReturnService","help":"Enable return label generation for this shipment","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"GLS Return Enabled"},"gls_limited_quantity":{"code":"LimitedQuantity","help":"Mark shipment as containing limited quantity hazardous materials","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"GLS Limited Quantity"},"gls_limited_quantity_weight":{"code":"LimitedQuantityWeight","help":"Weight of limited quantity hazardous material in kg","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"float","label":"GLS Limited Quantity Weight"},"gls_cod_reference":{"code":"CODReference","help":"Reference number for cash on delivery payment","meta":{"category":"COD","configurable":true},"type":"string","label":"GLS COD Reference"},"gls_premium":{"code":"PremiumService","help":"Enable premium service","meta":{"configurable":true},"type":"boolean","label":"GLS Premium"},"insurance":{"code":"insurance","help":"Insurance value for the shipment","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Insurance"}},"readme":null},{"id":"hay_post","carrier_name":"hay_post","display_name":"HayPost","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"customer_id":{"name":"customer_id","required":true,"sensitive":false,"type":"string","label":"Customer Id"},"customer_type":{"name":"customer_type","required":true,"sensitive":false,"type":"string","label":"Customer Type"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"letter_ordered":"88","letter_simple":"79","letter_valued":"89","package_ordered":"93","package_simple":"92","package_valued":"100","parcel_simple":"94","parcel_valued":"95","postcard_ordered":"91","postcard_simple":"90","sekogram_simple":"96","sprint_simple":"97","yes_ordered_value":"99"},"shipping_options":{"notification":{"code":"2","meta":{"configurable":true},"type":"boolean","label":"Notification"},"ordered_packaging":{"code":"3","meta":{"configurable":true},"type":"boolean","label":"Ordered Packaging"},"pick_up":{"code":"4","meta":{"configurable":true},"type":"boolean","label":"Pick Up"},"postmen_delivery_value":{"code":"5","meta":{"configurable":true},"type":"boolean","label":"Postmen Delivery Value"},"delivery":{"code":"6","meta":{"configurable":true},"type":"boolean","label":"Delivery"},"international_notification":{"code":"15","meta":{"configurable":true},"type":"boolean","label":"International Notification"},"domestic_sms":{"code":"16","meta":{"configurable":true},"type":"boolean","label":"Domestic Sms"},"international_sms":{"code":"17","meta":{"configurable":true},"type":"boolean","label":"International Sms"}},"readme":null},{"id":"hermes","carrier_name":"hermes","display_name":"Hermes","integration_status":"beta","website":"https://www.hermesworld.com","description":"Hermes shipping integration for Karrio","documentation":"https://de-api-int.hermesworld.com/docs/applications/order","is_enabled":true,"capabilities":["Tracking","Pickup","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"account_country_code":{"default":"DE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"label_type":{"code":"label_type","name":"label_type","required":false,"type":"string","label":"Label Type"},"language":{"code":"language","name":"language","required":false,"type":"string","label":"Language"}},"shipping_services":{"hermes_standard":"hermes_standard","hermes_next_day":"hermes_next_day","hermes_stated_day":"hermes_stated_day","hermes_parcel_shop":"hermes_parcel_shop","hermes_international":"hermes_international"},"shipping_options":{"hermes_next_day":{"code":"nextDayService","help":"Enable next-day delivery service","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Hermes Next Day"},"hermes_bulk_goods":{"code":"bulkGoodService","help":"Mark shipment as bulky goods (Sperrgut)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Hermes Bulk Goods"},"hermes_compact_parcel":{"code":"compactParcelService","help":"Enable compact parcel service","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Hermes Compact Parcel"},"hermes_redirection_prohibited":{"code":"redirectionProhibitedService","help":"Do not allow redirection to neighbor","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Hermes Redirection Prohibited"},"hermes_stated_day":{"code":"statedDay","help":"Specific delivery date (YYYY-MM-DD format)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Hermes Stated Day"},"hermes_time_slot":{"code":"timeSlot","help":"Delivery time slot (FORENOON, NOON, AFTERNOON, EVENING)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Hermes Time Slot"},"hermes_express":{"code":"expressService","help":"Enable express delivery service","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Hermes Express"},"hermes_after_hours_delivery":{"code":"afterHoursDeliveryService","help":"Enable after-hours delivery (Feierabendservice)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Hermes After Hours Delivery"},"hermes_parcel_class":{"code":"parcelClass","help":"Parcel size class (XS, S, M, L, XL)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Parcel Class"},"hermes_signature":{"code":"signatureService","help":"Require signature upon delivery","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Signature"},"hermes_household_signature":{"code":"householdSignatureService","help":"Require household member signature","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Household Signature"},"hermes_ident_id":{"code":"identID","help":"ID number for identity verification","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Ident Id"},"hermes_ident_type":{"code":"identType","help":"Type of ID for verification (e.g., GERMAN_IDENTITY_CARD)","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Ident Type"},"hermes_ident_fsk":{"code":"identVerifyFsk","help":"Minimum age verification (e.g., 18)","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Ident Fsk"},"hermes_ident_birthday":{"code":"identVerifyBirthday","help":"Verify recipient birthday (YYYY-MM-DD)","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Ident Birthday"},"hermes_parcel_shop_id":{"code":"psID","help":"Hermes ParcelShop ID for delivery","meta":{"category":"PUDO","configurable":true},"type":"string","label":"Parcel Shop Id"},"hermes_parcel_shop_selection_rule":{"code":"psSelectionRule","help":"ParcelShop selection rule (SELECT_BY_ID, SELECT_BY_RECEIVER_ADDRESS)","meta":{"category":"PUDO","configurable":true},"type":"string","label":"Parcel Shop Selection Rule"},"hermes_parcel_shop_customer_firstname":{"code":"psCustomerFirstName","help":"Customer first name for ParcelShop pickup","meta":{"category":"PUDO","configurable":true},"type":"string","label":"Parcel Shop Customer Firstname"},"hermes_parcel_shop_customer_lastname":{"code":"psCustomerLastName","help":"Customer last name for ParcelShop pickup","meta":{"category":"PUDO","configurable":true},"type":"string","label":"Parcel Shop Customer Lastname"},"hermes_exclude_parcel_shop_auth":{"code":"excludeParcelShopAuthorization","help":"Exclude ParcelShop delivery authorization","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Exclude Parcel Shop Auth"},"hermes_notification_email":{"code":"notificationEmail","help":"Email for delivery notifications","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"Notification Email"},"hermes_notification_type":{"code":"notificationType","help":"Notification type (EMAIL, SMS, EMAIL_SMS)","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"Notification Type"},"hermes_cod_amount":{"code":"codAmount","help":"Cash on delivery amount","meta":{"category":"COD","configurable":true},"type":"float","label":"Cod Amount"},"hermes_cod_currency":{"code":"codCurrency","help":"Currency for COD amount","meta":{"category":"COD","configurable":true},"type":"string","label":"Cod Currency"},"hermes_cod_distribution":{"code":"codDistribution","help":"COD distribution method (e.g., transfer, check)","meta":{"category":"COD","configurable":true},"type":"string","label":"Cod Distribution"},"hermes_limited_quantities":{"code":"limitedQuantitiesService","help":"Mark shipment as containing limited quantity hazardous materials","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Limited Quantities"},"hermes_return_enabled":{"code":"returnService","help":"Enable return label for this shipment","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Return Enabled"},"hermes_include_return_label":{"code":"includeReturnLabel","help":"Include a pre-printed return label inside the package","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Include Return Label"},"hermes_digital_sales_return":{"code":"digitalSalesReturn","help":"Enable digital sales return (digitale Verkaufsretoure)","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Digital Sales Return"},"hermes_customer_reference_1":{"code":"customerReference1","help":"Customer reference field 1 (Kundenreferenz 1)","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"Customer Reference 1"},"hermes_customer_reference_2":{"code":"customerReference2","help":"Customer reference field 2 (Kundenreferenz 2)","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"Customer Reference 2"},"hermes_tan_service":{"code":"tanService","meta":{"configurable":false},"type":"boolean","label":"Tan Service"},"hermes_late_injection":{"code":"lateInjectionService","meta":{"configurable":false},"type":"boolean","label":"Late Injection"},"hermes_part_number":{"code":"partNumber","meta":{"configurable":false},"type":"integer","label":"Part Number"},"hermes_number_of_parts":{"code":"numberOfParts","meta":{"configurable":false},"type":"integer","label":"Number Of Parts"},"hermes_parent_shipment_order_id":{"code":"parentShipmentOrderID","meta":{"configurable":false},"type":"string","label":"Parent Shipment Order Id"}},"readme":null},{"id":"landmark","carrier_name":"landmark","display_name":"Landmark Global","integration_status":"beta","website":"https://landmarkglobal.com","description":"Landmark Global shipping integration for Karrio","documentation":"https://mercurydocs.landmarkglobal.com/docs/api-documentation","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"region":{"default":"Landmark CMH","name":"region","required":false,"sensitive":false,"type":"string","label":"Region"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_type":{"code":"label_type","default":"PDF","enum":["PDF","JPG","GIF","BMP","ZPL","PNG"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"account_currency":{"code":"account_currency","default":"EUR","name":"account_currency","required":false,"type":"string","label":"Account Currency"},"import_request_by_default":{"code":"import_request_by_default","default":false,"name":"import_request_by_default","required":false,"type":"boolean","label":"Import Request By Default"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"}},"shipping_services":{"landmark_maxipak_scan_ddp":"LGINTSTD","landmark_maxipak_scan_ddu":"LGINTSTDU","landmark_minipak_scan_ddp":"LGINTBPIP","landmark_minipak_scan_ddu":"LGINTBPIU","landmark_maxipak_scan_ddp_pudo":"LGINTPUDO","landmark_maxipak_scan_premium_ups_express_ddp":"LGINTUPSS","landmark_maxipak_scan_premium_ups_express_ddu":"LGINTUPSSU","landmark_maxipak_scan_premium_ups_standard_ddp":"LGINTUPST","landmark_maxipak_scan_premium_ups_standard_ddu":"LGINTUPSTU","landmark_maxipak_scan_pddp":"LGINTBPMO","landmark_minipak_scan_pddp":"LGINTBPIO"},"shipping_options":{"landmark_shipment_insurance_freight":{"code":"ShipmentInsuranceFreight","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Landmark Shipment Insurance Freight"},"landmark_order_insurance_freight_total":{"code":"OrderInsuranceFreightTotal","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Landmark Order Insurance Freight Total"},"landmark_produce_label":{"code":"ProduceLabel","meta":{"configurable":true},"type":"boolean","label":"Landmark Produce Label"},"landmark_import_request":{"code":"InportRequest","meta":{"configurable":true},"type":"boolean","label":"Landmark Import Request"},"fulfilled_by_landmark":{"code":"FulfilledByLandmark","meta":{"configurable":true},"type":"boolean","label":"Fulfilled By Landmark"},"landmark_freight_pro_number":{"code":"FreightProNumber","meta":{"configurable":true},"type":"string","label":"Landmark Freight Pro Number"},"landmark_freight_piece_unit":{"code":"FreightPieceUnit","meta":{"configurable":true},"type":"string","label":"Landmark Freight Piece Unit"},"landmark_return_address_code":{"code":"ReturnAddressCode","meta":{"category":"RETURN","configurable":true},"type":"string","label":"Landmark Return Address Code"}},"readme":null},{"id":"laposte","carrier_name":"laposte","display_name":"La Poste","integration_status":"production-ready","website":"https://www.laposte.fr/","description":"La Poste is a postal service company in France, operating in Metropolitan France and French overseas territories. The company provides mail delivery, parcel shipping, banking services, and digital solutions.","documentation":"https://www.lapostegroupe.com/en/services-mail-parcels-business-unit","is_enabled":true,"capabilities":["Tracking"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"lang":{"default":"fr_FR","enum":["fr_FR","en_US"],"name":"lang","required":false,"sensitive":false,"type":"string","label":"Lang"},"account_country_code":{"default":"FR","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"laposte_standard_service":"La Poste Standard Service"},"shipping_options":{},"readme":null},{"id":"locate2u","carrier_name":"locate2u","display_name":"Locate2u","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"},"client_id":{"name":"client_id","required":false,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":false,"sensitive":false,"type":"string","label":"Client Secret"},"account_country_code":{"default":"AU","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"}},"config_fields":{},"shipping_services":{"locate2u_local_delivery":"Locate2u Local Delivery"},"shipping_options":{"appointment_time":{"code":"appointment_time","meta":{"configurable":true},"type":"string","label":"Appointment Time"},"time_window_start":{"code":"time_window_start","meta":{"configurable":true},"type":"string","label":"Time Window Start"},"time_window_end":{"code":"time_window_end","meta":{"configurable":true},"type":"string","label":"Time Window End"},"brand_id":{"code":"brand_id","meta":{"configurable":true},"type":"string","label":"Brand Id"},"duration_minutes":{"code":"duration_minutes","meta":{"configurable":true},"type":"integer","label":"Duration Minutes"},"assigned_team_member_id":{"code":"assigned_team_member_id","meta":{"configurable":true},"type":"string","label":"Assigned Team Member Id"},"source":{"code":"source","meta":{"configurable":true},"type":"string","label":"Source"},"customer_id":{"code":"customer_id","meta":{"configurable":true},"type":"string","label":"Customer Id"},"run_number":{"code":"run_number","meta":{"configurable":true},"type":"string","label":"Run Number"},"team_region_id":{"code":"team_region_id","meta":{"configurable":true},"type":"string","label":"Team Region Id"},"driver_instructions":{"code":"driver_instructions","meta":{"configurable":true},"type":"string","label":"Driver Instructions"},"notes":{"code":"notes","meta":{"configurable":true},"type":"string","label":"Notes"},"latitude":{"code":"latitude","meta":{"configurable":true},"type":"float","label":"Latitude"},"longitude":{"code":"longitude","meta":{"configurable":true},"type":"float","label":"Longitude"}},"readme":null},{"id":"mydhl","carrier_name":"mydhl","display_name":"MyDHL Express","integration_status":"in-development","website":"https://www.dhl.com","description":"DHL Express MyDHL API integration for Karrio","documentation":"https://developer.dhl.com/api-reference/mydhl-express","is_enabled":true,"capabilities":["Rating","Pickup","Paperless","Shipping","Tracking"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"label_type":{"code":"label_type","name":"label_type","required":false,"type":"string","label":"Label Type"}},"shipping_services":{"mydhl_express_worldwide":"P","mydhl_express_12_00":"T","mydhl_express_9_00":"Y","mydhl_express_10_30":"K","mydhl_express_easy":"8","mydhl_medical_express":"Q","mydhl_jetline":"J","mydhl_sprintline":"R","mydhl_globalmail":"G","mydhl_globalmail_business":"M","mydhl_express_domestic":"N","mydhl_express_domestic_12_00":"1","mydhl_express_domestic_10_30":"O","mydhl_express_domestic_9_00":"I","mydhl_medical_express_domestic":"C","mydhl_same_day":"S","mydhl_economy_select":"W","mydhl_europack":"H","mydhl_breakbulk_express":"E","mydhl_express_freight":"F","mydhl_express_worldwide_doc":"D","mydhl_express_envelope":"X","mydhl_express_worldwide_b2c":"7","mydhl_express_easy_b2c":"6"},"shipping_options":{"mydhl_saturday_delivery":{"code":"AA","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"MyDHL Saturday Delivery"},"mydhl_hold_for_collection":{"code":"LX","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"MyDHL Hold For Collection"},"mydhl_neutral_delivery":{"code":"NN","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"MyDHL Neutral Delivery"},"mydhl_residential_delivery":{"code":"TK","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"MyDHL Residential Delivery"},"mydhl_scheduled_delivery":{"code":"TT","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"MyDHL Scheduled Delivery"},"mydhl_collect_from_service_point":{"code":"TV","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"MyDHL Collect From Service Point"},"mydhl_verified_delivery":{"code":"TF","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"MyDHL Verified Delivery"},"mydhl_direct_signature":{"code":"SF","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"MyDHL Direct Signature"},"mydhl_signature_release":{"code":"SX","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"MyDHL Signature Release"},"mydhl_duty_tax_paid":{"code":"DD","meta":{"configurable":true},"type":"boolean","label":"MyDHL Duty Tax Paid"},"mydhl_receiver_paid":{"code":"DE","meta":{"configurable":true},"type":"boolean","label":"MyDHL Receiver Paid"},"mydhl_import_billing":{"code":"DT","meta":{"configurable":true},"type":"boolean","label":"MyDHL Import Billing"},"mydhl_duty_tax_importer":{"code":"DU","meta":{"configurable":true},"type":"boolean","label":"MyDHL Duty Tax Importer"},"mydhl_shipment_insurance":{"code":"II","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"MyDHL Shipment Insurance"},"mydhl_dangerous_goods":{"code":"HE","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Dangerous Goods"},"mydhl_dry_ice":{"code":"HC","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Dry Ice"},"mydhl_lithium_ion_pi966_section_ii":{"code":"HD","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Lithium Ion Pi966 Section Ii"},"mydhl_lithium_ion_pi967_section_ii":{"code":"HV","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Lithium Ion Pi967 Section Ii"},"mydhl_lithium_metal_pi969_section_ii":{"code":"HM","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Lithium Metal Pi969 Section Ii"},"mydhl_lithium_metal_pi970_section_ii":{"code":"HW","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Lithium Metal Pi970 Section Ii"},"mydhl_excepted_quantities":{"code":"HH","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Excepted Quantities"},"mydhl_consumer_commodities":{"code":"HK","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Consumer Commodities"},"mydhl_magnetized_material":{"code":"HX","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Magnetized Material"},"mydhl_not_restricted_dangerous_goods":{"code":"HU","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Not Restricted Dangerous Goods"},"mydhl_active_data_logger":{"code":"HT","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Active Data Logger"},"mydhl_gogreen_climate_neutral":{"code":"EE","meta":{"configurable":true},"type":"boolean","label":"MyDHL Gogreen Climate Neutral"},"mydhl_gogreen_plus_carbon_reduced":{"code":"FE","meta":{"configurable":true},"type":"boolean","label":"MyDHL Gogreen Plus Carbon Reduced"},"mydhl_verbal_notification":{"code":"JA","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"MyDHL Verbal Notification"},"mydhl_verbal_notification_alternative":{"code":"JD","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"MyDHL Verbal Notification Alternative"},"mydhl_broker_notification":{"code":"WG","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"MyDHL Broker Notification"},"mydhl_emergency_situation":{"code":"CR","meta":{"configurable":true},"type":"boolean","label":"MyDHL Emergency Situation"},"mydhl_diplomatic_mail":{"code":"CG","meta":{"configurable":true},"type":"boolean","label":"MyDHL Diplomatic Mail"},"mydhl_cold_storage":{"code":"LG","meta":{"configurable":true},"type":"boolean","label":"MyDHL Cold Storage"},"mydhl_sanctions_routing":{"code":"LU","meta":{"configurable":true},"type":"boolean","label":"MyDHL Sanctions Routing"},"mydhl_courier_time_window":{"code":"JY","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"MyDHL Courier Time Window"},"mydhl_dedicated_pickup":{"code":"QA","meta":{"configurable":true},"type":"boolean","label":"MyDHL Dedicated Pickup"},"mydhl_non_stackable_pallet":{"code":"YC","meta":{"configurable":true},"type":"boolean","label":"MyDHL Non Stackable Pallet"},"mydhl_paperless_trade":{"code":"WY","meta":{"category":"PAPERLESS","configurable":true},"type":"boolean","label":"MyDHL Paperless Trade"},"mydhl_export_declaration":{"code":"WO","meta":{"category":"PAPERLESS","configurable":true},"type":"boolean","label":"MyDHL Export Declaration"},"mydhl_clearance_authorization":{"code":"WD","meta":{"configurable":true},"type":"boolean","label":"MyDHL Clearance Authorization"},"mydhl_clearance_data_modification":{"code":"WF","meta":{"configurable":true},"type":"boolean","label":"MyDHL Clearance Data Modification"},"mydhl_bonded_storage":{"code":"WK","meta":{"configurable":true},"type":"boolean","label":"MyDHL Bonded Storage"},"mydhl_bonded_transit":{"code":"WL","meta":{"configurable":true},"type":"boolean","label":"MyDHL Bonded Transit"},"mydhl_temporary_import_export":{"code":"WM","meta":{"configurable":true},"type":"boolean","label":"MyDHL Temporary Import Export"},"mydhl_non_routine_entry":{"code":"WB","meta":{"configurable":true},"type":"boolean","label":"MyDHL Non Routine Entry"},"mydhl_multiline_entry":{"code":"WE","meta":{"configurable":true},"type":"boolean","label":"MyDHL Multiline Entry"},"mydhl_physical_intervention":{"code":"WH","meta":{"configurable":true},"type":"boolean","label":"MyDHL Physical Intervention"},"mydhl_other_government_agency":{"code":"WI","meta":{"configurable":true},"type":"boolean","label":"MyDHL Other Government Agency"},"mydhl_obtaining_permits_licences":{"code":"WJ","meta":{"configurable":true},"type":"boolean","label":"MyDHL Obtaining Permits Licences"},"mydhl_post_clearance_modification":{"code":"WS","meta":{"configurable":true},"type":"boolean","label":"MyDHL Post Clearance Modification"},"mydhl_sale_in_transit":{"code":"WT","meta":{"configurable":true},"type":"boolean","label":"MyDHL Sale In Transit"},"mydhl_data_entry":{"code":"PD","meta":{"configurable":true},"type":"boolean","label":"MyDHL Data Entry"},"mydhl_label_free":{"code":"PZ","meta":{"configurable":true},"type":"boolean","label":"MyDHL Label Free"},"mydhl_personally_identifiable_data":{"code":"PQ","meta":{"configurable":true},"type":"boolean","label":"MyDHL Personally Identifiable Data"},"mydhl_neutral_description_label":{"code":"PP","meta":{"configurable":true},"type":"boolean","label":"MyDHL Neutral Description Label"},"mydhl_return_to_seller":{"code":"PH","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"MyDHL Return To Seller"},"mydhl_return_to_origin":{"code":"PR","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"MyDHL Return To Origin"},"mydhl_fuel_surcharge":{"code":"FF","meta":{"configurable":true},"type":"boolean","label":"MyDHL Fuel Surcharge"},"mydhl_remote_area_delivery":{"code":"OO","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"MyDHL Remote Area Delivery"},"mydhl_address_correction":{"code":"MA","meta":{"configurable":true},"type":"boolean","label":"MyDHL Address Correction"},"mydhl_packaging":{"code":"GG","meta":{"configurable":true},"type":"boolean","label":"MyDHL Packaging"},"mydhl_import_export_taxes":{"code":"XB","meta":{"configurable":true},"type":"boolean","label":"MyDHL Import Export Taxes"},"mydhl_import_export_duties":{"code":"XX","meta":{"configurable":true},"type":"boolean","label":"MyDHL Import Export Duties"},"mydhl_merchandise_process":{"code":"XE","meta":{"configurable":true},"type":"boolean","label":"MyDHL Merchandise Process"},"mydhl_trade_zone_process":{"code":"XJ","meta":{"configurable":true},"type":"boolean","label":"MyDHL Trade Zone Process"},"mydhl_regulatory_charges":{"code":"XK","meta":{"configurable":true},"type":"boolean","label":"MyDHL Regulatory Charges"},"mydhl_data_staging_03":{"code":"PT","meta":{"configurable":true},"type":"boolean","label":"MyDHL Data Staging 03"},"mydhl_data_staging_06":{"code":"PU","meta":{"configurable":true},"type":"boolean","label":"MyDHL Data Staging 06"},"mydhl_data_staging_12":{"code":"PV","meta":{"configurable":true},"type":"boolean","label":"MyDHL Data Staging 12"},"mydhl_data_staging_24":{"code":"PW","meta":{"configurable":true},"type":"boolean","label":"MyDHL Data Staging 24"},"mydhl_shipment_preparation":{"code":"PA","meta":{"configurable":true},"type":"boolean","label":"MyDHL Shipment Preparation"},"mydhl_automated_digital_imaging":{"code":"PJ","meta":{"configurable":true},"type":"boolean","label":"MyDHL Automated Digital Imaging"},"mydhl_plt_images_pending":{"code":"PK","meta":{"configurable":true},"type":"boolean","label":"MyDHL Plt Images Pending"},"mydhl_optical_character_recognition":{"code":"PL","meta":{"configurable":true},"type":"boolean","label":"MyDHL Optical Character Recognition"},"mydhl_commercial_invoice_data_merge":{"code":"PM","meta":{"configurable":true},"type":"boolean","label":"MyDHL Commercial Invoice Data Merge"},"mydhl_comat":{"code":"PO","meta":{"configurable":true},"type":"boolean","label":"MyDHL Comat"},"mydhl_import_billing_account":{"code":"30","meta":{"configurable":true},"type":"boolean","label":"MyDHL Import Billing Account"},"doc_files":{"code":"doc_files","meta":{"category":"PAPERLESS","configurable":true},"type":"object","label":"Doc Files"}},"readme":null},{"id":"nationex","carrier_name":"nationex","display_name":"Nationex","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"customer_id":{"name":"customer_id","required":true,"sensitive":false,"type":"string","label":"Customer Id"},"billing_account":{"name":"billing_account","required":false,"sensitive":false,"type":"string","label":"Billing Account"},"language":{"default":"en","enum":["en","fr"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"CA","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"parcelone","carrier_name":"parcelone","display_name":"ParcelOne","integration_status":"beta","website":"https://parcel.one","description":"ParcelOne multi-carrier shipping integration for Karrio","documentation":"https://parcel.one/api-documentation","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"mandator_id":{"name":"mandator_id","required":true,"sensitive":false,"type":"string","label":"Mandator Id"},"consigner_id":{"name":"consigner_id","required":true,"sensitive":false,"type":"string","label":"Consigner Id"},"account_country_code":{"default":"DE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"cep_id":{"code":"cep_id","name":"cep_id","required":false,"type":"string","label":"Cep Id"},"product_id":{"code":"product_id","name":"product_id","required":false,"type":"string","label":"Product Id"},"label_format":{"code":"label_format","enum":["PDF","ZPL","PNG"],"name":"label_format","required":false,"type":"string","label":"Label Format"},"label_size":{"code":"label_size","enum":["A6","A4"],"name":"label_size","required":false,"type":"string","label":"Label Size"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"}},"shipping_services":{"parcelone_pa1_basic":"PA1_basic","parcelone_pa1_eco":"PA1_eco","parcelone_pa1_premium":"PA1_premium","parcelone_pa1_express":"PA1_express","parcelone_dhl_paket":"DHL_PAKET","parcelone_dhl_paket_international":"DHL_PAKETINT","parcelone_dhl_express":"DHL_EXPRESS","parcelone_dhl_retoure":"DHL_RETOURE","parcelone_ups_standard":"UPS_STANDARD","parcelone_ups_express":"UPS_EXPRESS","parcelone_ups_express_saver":"UPS_EXPSAVER"},"shipping_options":{"parcelone_saturday_delivery":{"code":"SDO","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"ParcelOne Saturday Delivery"},"parcelone_return_label":{"code":"SRL","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"ParcelOne Return Label"},"parcelone_cod":{"code":"COD","meta":{"category":"COD","configurable":true},"type":"float","label":"ParcelOne COD"},"parcelone_cod_currency":{"code":"COD_CURRENCY","meta":{"category":"COD","configurable":true},"type":"string","label":"ParcelOne COD Currency"},"parcelone_insurance":{"code":"INS","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"ParcelOne Insurance"},"parcelone_insurance_currency":{"code":"INS_CURRENCY","meta":{"category":"INSURANCE","configurable":true},"type":"string","label":"ParcelOne Insurance Currency"},"parcelone_notification_email":{"code":"MAIL","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"ParcelOne Notification Email"},"parcelone_notification_sms":{"code":"SMS","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"ParcelOne Notification Sms"},"parcelone_signature":{"code":"SIG","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"ParcelOne Signature"},"parcelone_ident_check":{"code":"IDENT","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"ParcelOne Ident Check"},"parcelone_age_check":{"code":"AGE","meta":{"category":"SIGNATURE","configurable":true},"type":"integer","label":"ParcelOne Age Check"},"parcelone_personally":{"code":"PERS","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"ParcelOne Personally"},"parcelone_neighbor_delivery":{"code":"NEIGHBOR","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"ParcelOne Neighbor Delivery"},"parcelone_no_neighbor":{"code":"NONEIGHBOR","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"ParcelOne No Neighbor"},"parcelone_drop_off_point":{"code":"DROP","meta":{"category":"PUDO","configurable":true},"type":"string","label":"ParcelOne Drop Off Point"},"parcelone_premium":{"code":"PREMIUM","meta":{"configurable":true},"type":"boolean","label":"ParcelOne Premium"},"parcelone_bulky_goods":{"code":"BULKY","meta":{"configurable":true},"type":"boolean","label":"ParcelOne Bulky Goods"}},"readme":null},{"id":"postat","carrier_name":"postat","display_name":"Austrian Post","integration_status":"beta","website":"https://www.post.at","description":"Austrian Post (Österreichische Post) shipping integration via Post-Labelcenter API","documentation":"https://www.post.at/en/business-post-labelcenter","is_enabled":true,"capabilities":["Rating","Shipping"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"org_unit_id":{"name":"org_unit_id","required":true,"sensitive":false,"type":"string","label":"Org Unit Id"},"org_unit_guid":{"name":"org_unit_guid","required":true,"sensitive":false,"type":"string","label":"Org Unit Guid"},"account_country_code":{"default":"AT","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"server_url":{"code":"server_url","name":"server_url","required":false,"type":"string","label":"Server Url"},"label_format":{"code":"label_format","enum":["PDF","ZPL2"],"name":"label_format","required":false,"type":"string","label":"Label Format"},"label_size":{"code":"label_size","enum":["SIZE_100x150","SIZE_100x200"],"name":"label_size","required":false,"type":"string","label":"Label Size"},"paper_layout":{"code":"paper_layout","enum":["LAYOUT_2xA5inA4","LAYOUT_4xA6inA4","LAYOUT_A4"],"name":"paper_layout","required":false,"type":"string","label":"Paper Layout"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"}},"shipping_services":{"postat_standard_domestic":"10","postat_express_domestic":"20","postat_international_standard":"30","postat_international_express":"40"},"shipping_options":{"postat_label_size":{"code":"label_size","meta":{"configurable":true},"type":"string","label":"PostAT Label Size"},"postat_paper_layout":{"code":"paper_layout","meta":{"configurable":true},"type":"string","label":"PostAT Paper Layout"},"postat_cod":{"code":"COD","meta":{"category":"COD","configurable":true},"type":"float","label":"PostAT COD"},"postat_cod_currency":{"code":"COD_CURRENCY","meta":{"category":"COD","configurable":true},"type":"string","label":"PostAT COD Currency"},"postat_insurance":{"code":"INS","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"PostAT Insurance"},"postat_insurance_currency":{"code":"INS_CURRENCY","meta":{"category":"INSURANCE","configurable":true},"type":"string","label":"PostAT Insurance Currency"},"postat_signature":{"code":"SIG","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"PostAT Signature"},"postat_saturday_delivery":{"code":"SAT","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"PostAT Saturday Delivery"},"postat_email_notification":{"code":"MAIL","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"PostAT Email Notification"},"postat_sms_notification":{"code":"SMS","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"PostAT Sms Notification"},"postat_age_verification":{"code":"AGE","meta":{"category":"SIGNATURE","configurable":true},"type":"integer","label":"PostAT Age Verification"}},"readme":null},{"id":"purolator","carrier_name":"purolator","display_name":"Purolator","integration_status":"production-ready","website":"https://www.purolator.com","description":"Purolator Inc. is a Canadian courier company, delivering parcels and freight in Canada and internationally.","documentation":null,"is_enabled":true,"capabilities":["Tracking","Pickup","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_number":{"name":"account_number","required":true,"sensitive":false,"type":"string","label":"Account Number"},"user_token":{"name":"user_token","required":false,"sensitive":false,"type":"string","label":"User Token"},"language":{"default":"en","enum":["en","fr"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"CA","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"purolator_express_9_am":"PurolatorExpress9AM","purolator_express_us":"PurolatorExpressU.S.","purolator_express_10_30_am":"PurolatorExpress10:30AM","purolator_express_us_9_am":"PurolatorExpressU.S.9AM","purolator_express_12_pm":"PurolatorExpress12PM","purolator_express_us_10_30_am":"PurolatorExpressU.S.10:30AM","purolator_express":"PurolatorExpress","purolator_express_us_12_00":"PurolatorExpressU.S.12:00","purolator_express_evening":"PurolatorExpressEvening","purolator_express_envelope_us":"PurolatorExpressEnvelopeU.S.","purolator_express_envelope_9_am":"PurolatorExpressEnvelope9AM","purolator_express_us_envelope_9_am":"PurolatorExpressU.S.Envelope9AM","purolator_express_envelope_10_30_am":"PurolatorExpressEnvelope10:30AM","purolator_express_us_envelope_10_30_am":"PurolatorExpressU.S.Envelope10:30AM","purolator_express_envelope_12_pm":"PurolatorExpressEnvelope12PM","purolator_express_us_envelope_12_00":"PurolatorExpressU.S.Envelope12:00","purolator_express_envelope":"PurolatorExpressEnvelope","purolator_express_pack_us":"PurolatorExpressPackU.S.","purolator_express_envelope_evening":"PurolatorExpressEnvelopeEvening","purolator_express_us_pack_9_am":"PurolatorExpressU.S.Pack9AM","purolator_express_pack_9_am":"PurolatorExpressPack9AM","purolator_express_us_pack_10_30_am":"PurolatorExpressU.S.Pack10:30AM","purolator_express_pack10_30_am":"PurolatorExpressPack10:30AM","purolator_express_us_pack_12_00":"PurolatorExpressU.S.Pack12:00","purolator_express_pack_12_pm":"PurolatorExpressPack12PM","purolator_express_box_us":"PurolatorExpressBoxU.S.","purolator_express_pack":"PurolatorExpressPack","purolator_express_us_box_9_am":"PurolatorExpressU.S.Box9AM","purolator_express_pack_evening":"PurolatorExpressPackEvening","purolator_express_us_box_10_30_am":"PurolatorExpressU.S.Box10:30AM","purolator_express_box_9_am":"PurolatorExpressBox9AM","purolator_express_us_box_12_00":"PurolatorExpressU.S.Box12:00","purolator_express_box_10_30_am":"PurolatorExpressBox10:30AM","purolator_ground_us":"PurolatorGroundU.S.","purolator_express_box_12_pm":"PurolatorExpressBox12PM","purolator_express_international":"PurolatorExpressInternational","purolator_express_box":"PurolatorExpressBox","purolator_express_international_9_am":"PurolatorExpressInternational9AM","purolator_express_box_evening":"PurolatorExpressBoxEvening","purolator_express_international_10_30_am":"PurolatorExpressInternational10:30AM","purolator_ground":"PurolatorGround","purolator_express_international_12_00":"PurolatorExpressInternational12:00","purolator_ground_9_am":"PurolatorGround9AM","purolator_express_envelope_international":"PurolatorExpressEnvelopeInternational","purolator_ground_10_30_am":"PurolatorGround10:30AM","purolator_express_international_envelope_9_am":"PurolatorExpressInternationalEnvelope9AM","purolator_ground_evening":"PurolatorGroundEvening","purolator_express_international_envelope_10_30_am":"PurolatorExpressInternationalEnvelope10:30AM","purolator_quick_ship":"PurolatorQuickShip","purolator_express_international_envelope_12_00":"PurolatorExpressInternationalEnvelope12:00","purolator_quick_ship_envelope":"PurolatorQuickShipEnvelope","purolator_express_pack_international":"PurolatorExpressPackInternational","purolator_quick_ship_pack":"PurolatorQuickShipPack","purolator_express_international_pack_9_am":"PurolatorExpressInternationalPack9AM","purolator_quick_ship_box":"PurolatorQuickShipBox","purolator_express_international_pack_10_30_am":"PurolatorExpressInternationalPack10:30AM","purolator_express_international_pack_12_00":"PurolatorExpressInternationalPack12:00","purolator_express_box_international":"PurolatorExpressBoxInternational","purolator_express_international_box_9_am":"PurolatorExpressInternationalBox9AM","purolator_express_international_box_10_30_am":"PurolatorExpressInternationalBox10:30AM","purolator_express_international_box_12_00":"PurolatorExpressInternationalBox12:00"},"shipping_options":{"purolator_dangerous_goods":{"code":"Dangerous Goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Purolator Dangerous Goods"},"purolator_chain_of_signature":{"code":"Chain of Signature","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Purolator Chain Of Signature"},"purolator_express_cheque":{"code":"ExpressCheque","meta":{"category":"COD","configurable":true},"type":"string","label":"Purolator Express Cheque"},"purolator_hold_for_pickup":{"code":"Hold For Pickup","meta":{"category":"PUDO","configurable":true},"type":"string","label":"Purolator Hold For Pickup"},"purolator_return_services":{"code":"Return Services","meta":{"category":"RETURN","configurable":true},"type":"string","label":"Purolator Return Services"},"purolator_saturday_service":{"code":"Saturday Service","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Purolator Saturday Service"},"purolator_origin_signature_not_required":{"code":"Origin Signature Not Required (OSNR)","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Purolator Origin Signature Not Required"},"purolator_adult_signature_required":{"code":"Adult Signature Required (ASR)","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Purolator Adult Signature Required"},"purolator_special_handling":{"code":"Special Handling","meta":{"configurable":true},"type":"string","label":"Purolator Special Handling"},"purolator_show_alternative_services":{"code":"Show Alternate Services","meta":{"configurable":true},"type":"boolean","label":"Purolator Show Alternative Services"}},"readme":null},{"id":"roadie","carrier_name":"roadie","display_name":"Roadie","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_country_code":{"default":"US","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"roadie_local_delivery":"Roadie Local Delivery"},"shipping_options":{"pickup_after":{"code":"pickup_after","meta":{"configurable":true},"type":"string","label":"Pickup After"},"deliver_start":{"code":"delivery_start","meta":{"configurable":true},"type":"string","label":"Deliver Start"},"deliver_end":{"code":"delivery_end","meta":{"configurable":true},"type":"string","label":"Deliver End"},"roadie_signature_required":{"code":"signature_required","meta":{"configurable":true},"type":"boolean","label":"Signature Required"},"roadie_notifications_enabled":{"code":"notifications_enabled","meta":{"configurable":true},"type":"boolean","label":"Notifications Enabled"},"roadie_over_21_required":{"code":"over_21_required","meta":{"configurable":true},"type":"boolean","label":"Over 21 Required"},"roadie_extra_compensation":{"code":"extra_compensation","meta":{"configurable":true},"type":"float","label":"Extra Compensation"},"roadie_trailer_required":{"code":"trailer_required","meta":{"configurable":true},"type":"boolean","label":"Trailer Required"},"roadie_decline_insurance":{"code":"decline_insurance","meta":{"configurable":true},"type":"boolean","label":"Decline Insurance"}},"readme":null},{"id":"royalmail","carrier_name":"royalmail","display_name":"Royal Mail","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"account_country_code":{"default":"UK","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"sapient","carrier_name":"sapient","display_name":"SAPIENT","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Pickup","Rating","Shipping"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"shipping_account_id":{"name":"shipping_account_id","required":true,"sensitive":false,"type":"string","label":"Shipping Account Id"},"sapient_carrier_code":{"default":"RM","enum":["DX","EVRI","RM","UPS","YODEL"],"name":"sapient_carrier_code","required":false,"sensitive":false,"type":"string","label":"Sapient Carrier Code"},"account_country_code":{"default":"GB","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"service_level":{"code":"service_level","name":"service_level","required":false,"type":"string","label":"Service Level"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"sapient_royal_mail_hm_forces_mail":"BF1","sapient_royal_mail_hm_forces_signed_for":"BF2","sapient_royal_mail_hm_forces_special_delivery_500":"BF7","sapient_royal_mail_hm_forces_special_delivery_1000":"BF8","sapient_royal_mail_hm_forces_special_delivery_2500":"BF9","sapient_royal_mail_international_business_personal_correspondence_max_sort_residue_ll":"BG1","sapient_royal_mail_international_business_mail_ll_max_sort_residue_standard":"BG2","sapient_royal_mail_international_business_personal_correspondence_max_sort_residue_l":"BP1","sapient_royal_mail_international_business_mail_l_max_sort_residue_standard":"BP2","sapient_royal_mail_international_business_printed_matter_packet":"BPI","sapient_royal_mail_1st_class":"BPL1","sapient_royal_mail_2nd_class":"BPL2","sapient_royal_mail_1st_class_signed_for":"BPR1","sapient_royal_mail_2nd_class_signed_for":"BPR2","sapient_royal_mail_international_business_parcel_priority_country_priced_boxable":"BXB","sapient_royal_mail_international_business_parcel_tracked_country_priced_boxable_extra_comp":"BXC","sapient_royal_mail_international_business_parcel_priority_country_priced_boxable_ddp":"BXD","sapient_royal_mail_international_business_parcel_tracked_country_priced_boxable_ddp":"BXE","sapient_royal_mail_international_business_parcel_tracked_country_priced_boxable":"BXF","sapient_royal_mail_24_standard_signed_for_parcel_daily_rate_service":"CRL1","sapient_royal_mail_48_standard_signed_for_parcel_daily_rate_service":"CRL2","sapient_royal_mail_international_business_parcels_zero_sort_priority":"DE4","sapient_royal_mail_international_business_parcels_zero_sort_priority_DE":"DE6","sapient_royal_mail_de_import_standard_24_parcel":"DEA","sapient_royal_mail_de_import_standard_24_parcel_DE":"DEB","sapient_royal_mail_de_import_standard_24_ll":"DEC","sapient_royal_mail_de_import_standard_48_ll":"DED","sapient_royal_mail_de_import_to_eu_tracked_signed_ll":"DEE","sapient_royal_mail_de_import_to_eu_max_sort_ll":"DEG","sapient_royal_mail_de_import_to_eu_tracked_parcel":"DEI","sapient_royal_mail_de_import_to_eu_tracked_signed_parcel":"DEJ","sapient_royal_mail_de_import_to_eu_tracked_high_vol_ll":"DEK","sapient_royal_mail_de_import_to_eu_max_sort_parcel":"DEM","sapient_royal_mail_international_business_mail_ll_country_priced_priority":"DG4","sapient_royal_mail_international_business_personal_correspondence_l_priority_untracked":"DP3","sapient_royal_mail_international_business_mail_ll_country_sort_priority":"DP6","sapient_royal_mail_international_business_parcels":"DW1","sapient_royal_mail_international_business_parcels_tracked_country_priced_extra_territorial_office_of_exchange":"ETA","sapient_royal_mail_international_business_parcels_tracked_signed_country_priced_extra_territorial_office_of_exchange":"ETB","sapient_royal_mail_international_business_parcels_zero_sort_priority_extra_territorial_office_of_exchange":"ETC","sapient_royal_mail_international_business_mail_tracked_ll_country_priced_extra_territorial_office_of_exchange":"ETD","sapient_royal_mail_international_business_mail_tracked_signed_ll_country_priced_extra_territorial_office_of_exchange":"ETE","sapient_royal_mail_international_business_mail_ll_country_priced_priority_extra_territorial_office_of_exchange":"ETF","sapient_royal_mail_international_tracked_parcels_0_30kg_extra_territorial_office_of_exchange_e":"ETG","sapient_royal_mail_international_tracked_parcels_0_30kg_extra_comp_extra_territorial_office_of_exchange_e":"ETH","sapient_royal_mail_international_tracked_parcels_0_30kg_extra_territorial_office_of_exchange_c":"ETI","sapient_royal_mail_international_tracked_parcels_0_30kg_extra_comp_extra_territorial_office_of_exchange_c":"ETJ","sapient_royal_mail_international_business_personal_correspondence_l_priority_untracked_extra_territorial_office_of_exchange":"ETK","sapient_royal_mail_international_business_personal_correspondence_l_tracked_high_vol_country_priced_extra_territorial_office_of_exchange":"ETL","sapient_royal_mail_international_business_personal_correspondence_l_tracked_signed_high_vol_country_priced_extra_territorial_office_of_exchange":"ETM","sapient_royal_mail_international_business_personal_correspondence_signed_l_high_vol_country_priced_extra_territorial_office_of_exchange":"ETN","sapient_royal_mail_international_business_personal_correspondence_ll_country_sort_priority_extra_territorial_office_of_exchange":"ETO","sapient_royal_mail_international_business_personal_correspondence_tracked_ll_high_vol_extra_comp_country_priced_extra_territorial_office_of_exchange":"ETP","sapient_royal_mail_international_business_personal_correspondence_tracked_signed_ll_high_vol_extra_comp_country_priced_extra_territorial_office_of_exchange":"ETQ","sapient_royal_mail_international_business_personal_correspondence_signed_ll_extra_compensation_country_priced_extra_territorial_office_of_exchange":"ETR","sapient_royal_mail_24_standard_signed_for_large_letter_flat_rate_service":"FS1","sapient_royal_mail_48_standard_signed_for_large_letter_flat_rate_service":"FS2","sapient_royal_mail_24_presorted_ll":"FS7","sapient_royal_mail_48_presorted_ll":"FS8","sapient_royal_mail_international_tracked_parcels_0_30kg":"HVB","sapient_royal_mail_international_business_tracked_express_npc":"HVD","sapient_royal_mail_international_tracked_parcels_0_30kg_extra_comp":"HVE","sapient_royal_mail_international_tracked_parcels_0_30kg_c_prio":"HVK","sapient_royal_mail_international_tracked_parcels_0_30kg_xcomp_c_prio":"HVL","sapient_royal_mail_international_business_parcels_zone_sort_priority_service":"IE1","sapient_royal_mail_international_business_mail_large_letter_zone_sort_priority":"IG1","sapient_royal_mail_international_business_mail_large_letter_zone_sort_priority_machine":"IG4","sapient_royal_mail_international_business_mail_letters_zone_sort_priority":"IP1","sapient_royal_mail_import_de_tracked_returns_24":"ITA","sapient_royal_mail_import_de_tracked_returns_48":"ITB","sapient_royal_mail_import_de_tracked_24_letter_boxable_high_volume":"ITC","sapient_royal_mail_import_de_tracked_48_letter_boxable_high_volume":"ITD","sapient_royal_mail_import_de_tracked_48_letter_boxable":"ITE","sapient_royal_mail_import_de_tracked_24_letter_boxable":"ITF","sapient_royal_mail_import_de_tracked_48_high_volume":"ITL","sapient_royal_mail_import_de_tracked_24_high_volume":"ITM","sapient_royal_mail_import_de_tracked_24":"ITN","sapient_royal_mail_de_import_to_eu_signed_parcel":"ITR","sapient_royal_mail_import_de_tracked_48":"ITS","sapient_royal_mail_international_business_parcels_print_direct_priority":"MB1","sapient_royal_mail_international_business_parcels_print_direct_standard":"MB2","sapient_royal_mail_international_business_parcels_signed_extra_compensation_country_priced":"MP0","sapient_royal_mail_international_business_parcels_tracked_zone_sort":"MP1","sapient_royal_mail_international_business_parcels_tracked_extra_comp_zone_sort":"MP4","sapient_royal_mail_international_business_parcels_signed_zone_sort":"MP5","sapient_royal_mail_international_business_parcels_signed_extra_compensation_zone_sort":"MP6","sapient_royal_mail_international_business_parcels_tracked_country_priced":"MP7","sapient_royal_mail_international_business_parcels_tracked_extra_comp_country_priced":"MP8","sapient_royal_mail_international_business_parcels_signed_country_priced":"MP9","sapient_royal_mail_international_business_mail_tracked_high_vol_country_priced":"MPL","sapient_royal_mail_international_business_mail_tracked_signed_high_vol_country_priced":"MPM","sapient_royal_mail_international_business_mail_signed_high_vol_country_priced":"MPN","sapient_royal_mail_international_business_mail_tracked_high_vol_extra_comp_country_priced":"MPO","sapient_royal_mail_international_business_mail_tracked_signed_high_vol_extra_comp_country_priced":"MPP","sapient_royal_mail_international_business_parcel_tracked_boxable_country_priced":"MPR","sapient_royal_mail_international_business_parcels_tracked_signed_zone_sort":"MTA","sapient_royal_mail_international_business_parcels_tracked_signed_extra_compensation_zone_sort":"MTB","sapient_royal_mail_international_business_mail_tracked_signed_zone_sort":"MTC","sapient_royal_mail_international_business_parcels_tracked_signed_country_priced":"MTE","sapient_royal_mail_international_business_parcels_tracked_signed_extra_compensation_country_priced":"MTF","sapient_royal_mail_international_business_mail_tracked_signed_country_priced":"MTG","sapient_royal_mail_international_business_mail_tracked_zone_sort":"MTI","sapient_royal_mail_international_business_mail_tracked_country_priced":"MTK","sapient_royal_mail_international_business_mail_signed_zone_sort":"MTM","sapient_royal_mail_international_business_mail_signed_country_priced":"MTO","sapient_royal_mail_international_business_mail_signed_extra_compensation_country_priced":"MTP","sapient_royal_mail_international_business_parcels_tracked_direct_ireland_country":"MTS","sapient_royal_mail_international_business_parcels_tracked_signed_ddp":"MTV","sapient_royal_mail_international_standard_on_account":"OLA","sapient_royal_mail_international_economy_on_account":"OLS","sapient_royal_mail_international_signed_on_account":"OSA","sapient_royal_mail_international_signed_on_account_extra_comp":"OSB","sapient_royal_mail_international_tracked_on_account":"OTA","sapient_royal_mail_international_tracked_on_account_extra_comp":"OTB","sapient_royal_mail_international_tracked_signed_on_account":"OTC","sapient_royal_mail_international_tracked_signed_on_account_extra_comp":"OTD","sapient_royal_mail_48_ll_flat_rate":"PK0","sapient_royal_mail_24_standard_signed_for_parcel_sort8_flat_rate_service":"PK1","sapient_royal_mail_48_standard_signed_for_parcel_sort8_flat_rate_service":"PK2","sapient_royal_mail_24_standard_signed_for_parcel_sort8_daily_rate_service":"PK3","sapient_royal_mail_48_standard_signed_for_parcel_sort8_daily_rate_service":"PK4","sapient_royal_mail_24_presorted_p":"PK7","sapient_royal_mail_48_presorted_p":"PK8","sapient_royal_mail_24_ll_flat_rate":"PK9","sapient_royal_mail_rm24_presorted_p_annual_flat_rate":"PKB","sapient_royal_mail_rm48_presorted_p_annual_flat_rate":"PKD","sapient_royal_mail_rm48_presorted_ll_annual_flat_rate":"PKK","sapient_royal_mail_rm24_presorted_ll_annual_flat_rate":"PKM","sapient_royal_mail_24_standard_signed_for_packetpost_flat_rate_service":"PPF1","sapient_royal_mail_48_standard_signed_for_packetpost_flat_rate_service":"PPF2","sapient_royal_mail_parcelpost_flat_rate_annual":"PPJ1","sapient_royal_mail_parcelpost_flat_rate_annual_PPJ":"PPJ2","sapient_royal_mail_rm24_ll_annual_flat_rate":"PPS","sapient_royal_mail_rm48_ll_annual_flat_rate":"PPT","sapient_royal_mail_international_business_personal_correspondence_max_sort_l":"PS5","sapient_royal_mail_international_business_mail_large_letter_max_sort_priority_service":"PS7","sapient_royal_mail_international_business_mail_letters_max_sort_standard":"PSA","sapient_royal_mail_international_business_mail_large_letter_max_sort_standard_service":"PSB","sapient_royal_mail_48_sort8p_annual_flat_rate":"RM0","sapient_royal_mail_24_ll_daily_rate":"RM1","sapient_royal_mail_24_p_daily_rate":"RM2","sapient_royal_mail_48_ll_daily_rate":"RM3","sapient_royal_mail_48_p_daily_rate":"RM4","sapient_royal_mail_24_p_flat_rate":"RM5","sapient_royal_mail_48_p_flat_rate":"RM6","sapient_royal_mail_24_sort8_ll_annual_flat_rate":"RM7","sapient_royal_mail_24_sort8_p_annual_flat_rate":"RM8","sapient_royal_mail_48_sort8_ll_annual_flat_rate":"RM9","sapient_royal_mail_special_delivery_guaranteed_by_1pm_750":"SD1","sapient_royal_mail_special_delivery_guaranteed_by_1pm_1000":"SD2","sapient_royal_mail_special_delivery_guaranteed_by_1pm_2500":"SD3","sapient_royal_mail_special_delivery_guaranteed_by_9am_750":"SD4","sapient_royal_mail_special_delivery_guaranteed_by_9am_1000":"SD5","sapient_royal_mail_special_delivery_guaranteed_by_9am_2500":"SD6","sapient_royal_mail_special_delivery_guaranteed_by_1pm_id_750":"SDA","sapient_royal_mail_special_delivery_guaranteed_by_1pm_id_1000":"SDB","sapient_royal_mail_special_delivery_guaranteed_by_1pm_id_2500":"SDC","sapient_royal_mail_special_delivery_guaranteed_by_9am_id_750":"SDE","sapient_royal_mail_special_delivery_guaranteed_by_9am_id_1000":"SDF","sapient_royal_mail_special_delivery_guaranteed_by_9am_id_2500":"SDG","sapient_royal_mail_special_delivery_guaranteed_by_1pm_age_750":"SDH","sapient_royal_mail_special_delivery_guaranteed_by_1pm_age_1000":"SDJ","sapient_royal_mail_special_delivery_guaranteed_by_1pm_age_2500":"SDK","sapient_royal_mail_special_delivery_guaranteed_by_9am_age_750":"SDM","sapient_royal_mail_special_delivery_guaranteed_by_9am_age_1000":"SDN","sapient_royal_mail_special_delivery_guaranteed_by_9am_age_2500":"SDQ","sapient_royal_mail_special_delivery_guaranteed_age_750":"SDV","sapient_royal_mail_special_delivery_guaranteed_age_1000":"SDW","sapient_royal_mail_special_delivery_guaranteed_age_2500":"SDX","sapient_royal_mail_special_delivery_guaranteed_id_750":"SDY","sapient_royal_mail_special_delivery_guaranteed_id_1000":"SDZ","sapient_royal_mail_special_delivery_guaranteed_id_2500":"SEA","sapient_royal_mail_special_delivery_guaranteed_750":"SEB","sapient_royal_mail_special_delivery_guaranteed_1000":"SEC","sapient_royal_mail_special_delivery_guaranteed_2500":"SED","sapient_royal_mail_1st_class_standard_signed_for_letters_daily_rate_service":"STL1","sapient_royal_mail_2nd_class_standard_signed_for_letters_daily_rate_service":"STL2","sapient_royal_mail_tracked_24_high_volume_signature_age":"TPA","sapient_royal_mail_tracked_48_high_volume_signature_age":"TPB","sapient_royal_mail_tracked_24_signature_age":"TPC","sapient_royal_mail_tracked_48_signature_age":"TPD","sapient_royal_mail_tracked_48_high_volume_signature_no_signature":"TPL","sapient_royal_mail_tracked_24_high_volume_signature_no_signature":"TPM","sapient_royal_mail_tracked_24_signature_no_signature":"TPN","sapient_royal_mail_tracked_48_signature_no_signature":"TPS","sapient_royal_mail_tracked_letter_boxable_48_high_volume_signature_no_signature":"TRL","sapient_royal_mail_tracked_letter_boxable_24_high_volume_signature_no_signature":"TRM","sapient_royal_mail_tracked_letter_boxable_24_signature_no_signature":"TRN","sapient_royal_mail_tracked_letter_boxable_48_signature_no_signature":"TRS","sapient_royal_mail_tracked_returns_24":"TSN","sapient_royal_mail_tracked_returns_48":"TSS","sapient_royal_mail_international_business_parcels_zero_sort_priority_WE":"WE1","sapient_royal_mail_international_business_mail_large_letter_zero_sort_priority":"WG1","sapient_royal_mail_international_business_mail_large_letter_zero_sort_priority_machine":"WG4","sapient_royal_mail_international_business_mail_letters_zero_sort_priority":"WP1"},"shipping_options":{"sapient_CL1":{"code":"CL1","meta":{"configurable":true},"type":"float","label":"Cl1"},"sapient_CL2":{"code":"CL2","meta":{"configurable":true},"type":"float","label":"Cl2"},"sapient_CL3":{"code":"CL3","meta":{"configurable":true},"type":"float","label":"Cl3"},"sapient_CL4":{"code":"CL4","meta":{"configurable":true},"type":"float","label":"Cl4"},"sapient_CL5":{"code":"CL5","meta":{"configurable":true},"type":"float","label":"Cl5"},"sapient_signed":{"code":"Signed","meta":{"configurable":true},"type":"boolean","label":"Signed"},"sapient_SMS":{"code":"SMS","meta":{"configurable":true},"type":"boolean","label":"Sms"},"sapient_email":{"code":"Email","meta":{"configurable":true},"type":"boolean","label":"Email"},"sapient_localcollect":{"code":"LocalCollect","meta":{"configurable":true},"type":"boolean","label":"Localcollect"},"sapient_customs_email":{"code":"CustomsEmail","meta":{"configurable":true},"type":"string","label":"Customs Email"},"sapient_customs_phone":{"code":"CustomsPhone","meta":{"configurable":true},"type":"string","label":"Customs Phone"},"sapient_safeplace_location":{"code":"Safeplace","meta":{"configurable":true},"type":"string","label":"Safeplace Location"},"sapient_airn":{"code":"Airn","meta":{"configurable":true},"type":"string","label":"Airn"},"sapient_ebay_vtn":{"code":"EbayVtn","meta":{"configurable":true},"type":"string","label":"Ebay Vtn"},"sapient_reference_2":{"code":"Reference2","meta":{"configurable":true},"type":"string","label":"Reference 2"},"sapient_container_id":{"code":"ContainerId","meta":{"configurable":true},"type":"string","label":"Container Id"},"sapient_shipping_charges":{"code":"ShippingCharges","meta":{"configurable":true},"type":"float","label":"Shipping Charges"},"sapient_quoted_landed_cost":{"code":"QuotedLandedCost","meta":{"configurable":true},"type":"float","label":"Quoted Landed Cost"},"export_licence_required":{"code":"ExportLicenceRequired","meta":{"configurable":true},"type":"boolean","label":"Export Licence Required"},"sapient_business_transaction_type":{"code":"BusinessTransactionType","meta":{"configurable":true},"type":"string","label":"Business Transaction Type"}},"readme":null},{"id":"seko","carrier_name":"seko","display_name":"SEKO Logistics","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Manifest","Tracking","Rating","Shipping"],"connection_fields":{"access_key":{"name":"access_key","required":true,"sensitive":false,"type":"string","label":"Access Key"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"currency":{"code":"currency","name":"currency","required":false,"type":"string","label":"Currency"},"cost_center":{"code":"cost_center","name":"cost_center","required":false,"type":"string","label":"Cost Center"},"cost_center_id":{"code":"cost_center_id","name":"cost_center_id","required":false,"type":"string","label":"Cost Center Id"},"cost_centre_name":{"code":"cost_centre_name","name":"cost_centre_name","required":false,"type":"string","label":"Cost Centre Name"},"cost_centre_id":{"code":"cost_centre_id","name":"cost_centre_id","required":false,"type":"string","label":"Cost Centre Id"},"label_branding":{"code":"label_branding","name":"label_branding","required":false,"type":"string","label":"Label Branding"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"seko_ecommerce_standard_tracked":"eCommerce Standard Tracked","seko_ecommerce_express_tracked":"eCommerce Express Tracked","seko_domestic_express":"Domestic Express","seko_domestic_standard":"Domestic Standard","seko_domestic_large_parcel":"Domestic Large Parcel"},"shipping_options":{"seko_carrier":{"code":"Carrier","meta":{"configurable":true},"type":"string","label":"SEKO Carrier"},"seko_ship_type":{"code":"ShipType","meta":{"configurable":true},"type":"string","label":"SEKO Ship Type"},"seko_package_id":{"code":"PackageId","meta":{"configurable":true},"type":"string","label":"SEKO Package ID"},"seko_destination_id":{"code":"DestinationId","meta":{"configurable":true},"type":"string","label":"SEKO Destination ID"},"seko_product_category":{"code":"ProductCategory","meta":{"configurable":true},"type":"string","label":"SEKO Product Category"},"origin_instructions":{"code":"OriginInstructions","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"Origin Instructions"},"destination_instructions":{"code":"DestinationInstructions","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"Destination Instructions"},"seko_is_saturday_delivery":{"code":"IsSaturdayDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"SEKO Is Saturday Delivery"},"seko_is_signature_required":{"code":"IsSignatureRequired","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"SEKO Is Signature Required"},"seko_send_tracking_email":{"code":"SendTrackingEmail","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"SEKO Send Tracking Email"},"seko_amount_collected":{"code":"AmountCollected","meta":{"configurable":true},"type":"float","label":"SEKO Amount Collected"},"seko_tax_collected":{"code":"TaxCollected","meta":{"configurable":true},"type":"boolean","label":"SEKO Tax Collected"},"seko_cod_amount":{"code":"CODAmount","meta":{"category":"COD","configurable":true},"type":"float","label":"SEKO COD Amount"},"seko_reference_2":{"code":"Reference2","meta":{"configurable":true},"type":"string","label":"SEKO Reference 2"},"seko_reference_3":{"code":"Reference3","meta":{"configurable":true},"type":"string","label":"SEKO Reference 3"},"seko_invoice_data":{"code":"InvoiceData","meta":{"configurable":true},"type":"string","label":"SEKO Invoice Data"},"seko_origin_id":{"code":"OriginId","meta":{"configurable":true},"type":"integer","label":"SEKO Origin ID"},"seko_print_to_printer":{"code":"PrintToPrinter","meta":{"configurable":true},"type":"boolean","label":"SEKO Print To Printer"},"seko_cif_value":{"code":"CIFValue","meta":{"configurable":true},"type":"float","label":"SEKO Cif Value"},"seko_freight_value":{"code":"FreightValue","meta":{"configurable":true},"type":"float","label":"SEKO Freight Value"},"seko_send_label":{"code":"SendLabel","meta":{"configurable":true},"type":"boolean","label":"SEKO Send Label"},"seko_special_instructions":{"code":"SpecialInstructions","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"SEKO Special Instructions"},"seko_insurance_value":{"code":"InsuranceValue","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"SEKO Insurance Value"},"seko_estimated_delivery_date":{"code":"EstimatedDeliveryDate","meta":{"configurable":true},"type":"string","label":"SEKO Estimated Delivery Date"},"seko_dangerous_goods":{"code":"DangerousGoods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"SEKO Dangerous Goods"},"seko_dg_additional_handling":{"code":"DGAdditionalHandling","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"SEKO Dg Additional Handling"},"seko_dg_hazchem_code":{"code":"DGHazchemCode","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"SEKO Dg Hazchem Code"},"seko_dg_radioactive":{"code":"DGRadioactive","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"SEKO Dg Radioactive"},"seko_dg_cargo_aircraft_only":{"code":"DGCargoAircraftOnly","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"SEKO Dg Cargo Aircraft Only"},"seko_dg_limited_quantity":{"code":"DGLimitedQuantity","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"SEKO Dg Limited Quantity"},"seko_dg_total_quantity":{"code":"DGTotalQuantity","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"integer","label":"SEKO Dg Total Quantity"},"seko_dg_total_kg":{"code":"DGTotalKg","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"float","label":"SEKO Dg Total Kg"},"seko_dg_signoff_name":{"code":"DGSignOffName","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"SEKO Dg Signoff Name"},"seko_dg_signoff_role":{"code":"DGSignOffRole","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"SEKO Dg Signoff Role"},"doc_files":{"code":"doc_files","meta":{"category":"PAPERLESS","configurable":true},"type":"object","label":"Doc Files"},"doc_references":{"code":"doc_references","meta":{"category":"PAPERLESS","configurable":true},"type":"object","label":"Doc References"}},"readme":null},{"id":"sendle","carrier_name":"sendle","display_name":"Sendle","integration_status":"production-ready","website":"https://www.sendle.com","description":"Sendle is a registered B Corp and 100% carbon neutral shipping carrier for small businesses, offering affordable package delivery services in Australia and the United States.","documentation":"https://www.sendle.com/developers","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"sendle_id":{"name":"sendle_id","required":true,"sensitive":false,"type":"string","label":"Sendle Id"},"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"sendle_standard_pickup":"STANDARD-PICKUP","sendle_standard_dropoff":"STANDARD-DROPOFF","sendle_express_pickup":"EXPRESS-PICKUP"},"shipping_options":{"sendle_hide_pickup_address":{"code":"hide_pickup_address","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Sendle Hide Pickup Address"},"sendle_first_mile_option":{"code":"first_mile_option","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Sendle First Mile Option"}},"readme":null},{"id":"shipengine","carrier_name":"shipengine","display_name":"ShipEngine","integration_status":"beta","website":"https://www.shipengine.com","description":"ShipEngine multi-carrier shipping platform integration for Karrio","documentation":"https://www.shipengine.com/docs/","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"carrier_ids":{"name":"carrier_ids","required":false,"sensitive":false,"type":"string","label":"Carrier Ids"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"preferred_carriers":{"code":"preferred_carriers","name":"preferred_carriers","required":false,"type":"list","label":"Preferred Carriers"},"excluded_carriers":{"code":"excluded_carriers","name":"excluded_carriers","required":false,"type":"list","label":"Excluded Carriers"},"default_service_codes":{"code":"default_service_codes","name":"default_service_codes","required":false,"type":"list","label":"Default Service Codes"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"label_format":{"code":"label_format","name":"label_format","required":false,"type":"string","label":"Label Format"},"label_size":{"code":"label_size","name":"label_size","required":false,"type":"string","label":"Label Size"}},"shipping_services":{"shipengine_auto":"ShipEngine Auto-Select","usps_ground_advantage":"USPS Ground Advantage","usps_priority_mail":"USPS Priority Mail","usps_priority_mail_express":"USPS Priority Mail Express","fedex_ground":"FedEx Ground","fedex_2day":"FedEx 2Day","fedex_standard_overnight":"FedEx Standard Overnight","ups_ground":"UPS Ground","ups_3_day_select":"UPS 3 Day Select","ups_2nd_day_air":"UPS 2nd Day Air","ups_next_day_air":"UPS Next Day Air","shipengine_ups_ups_ground":"UPS Ground via ShipEngine"},"shipping_options":{"insurance_amount":{"code":"insurance_amount","meta":{"configurable":true},"type":"float","label":"Insurance Amount"},"delivery_confirmation":{"code":"delivery_confirmation","meta":{"configurable":true},"type":"string","label":"Delivery Confirmation"},"signature_confirmation":{"code":"signature_confirmation","meta":{"configurable":true},"type":"string","label":"Signature Confirmation"},"adult_signature":{"code":"adult_signature","meta":{"configurable":true},"type":"boolean","label":"Adult Signature"},"saturday_delivery":{"code":"saturday_delivery","meta":{"configurable":true},"type":"boolean","label":"Saturday Delivery"},"collect_on_delivery":{"code":"collect_on_delivery","meta":{"configurable":true},"type":"float","label":"Collect On Delivery"}},"readme":null},{"id":"smartkargo","carrier_name":"smartkargo","display_name":"SmartKargo","integration_status":"beta","website":"https://www.smartkargo.com","description":"SmartKargo air cargo shipping integration","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_number":{"name":"account_number","required":true,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"primary_id":{"code":"primary_id","name":"primary_id","required":false,"type":"string","label":"Primary Id"},"site_id":{"code":"site_id","name":"site_id","required":false,"type":"string","label":"Site Id"},"additional_id":{"code":"additional_id","name":"additional_id","required":false,"type":"string","label":"Additional Id"},"origin":{"code":"origin","name":"origin","required":false,"type":"string","label":"Origin"},"destination":{"code":"destination","name":"destination","required":false,"type":"string","label":"Destination"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"currency":{"code":"currency","name":"currency","required":false,"type":"string","label":"Currency"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"partner_tracking_url":{"code":"partner_tracking_url","name":"partner_tracking_url","required":false,"type":"string","label":"Partner Tracking Url"},"partner_tracking_api_code":{"code":"partner_tracking_api_code","name":"partner_tracking_api_code","required":false,"type":"string","label":"Partner Tracking Api Code"}},"shipping_services":{"smartkargo_express":"EXP","smartkargo_priority":"EPR","smartkargo_standard":"EST","smartkargo_economy":"ECL"},"shipping_options":{"smartkargo_insurance":{"code":"hasInsurance","meta":{"configurable":true},"type":"boolean","label":"Insurance"},"smartkargo_declared_value":{"code":"insuranceAmmount","meta":{"configurable":true},"type":"float","label":"Declared Value"},"smartkargo_delivery_type":{"code":"deliveryType","meta":{"configurable":true},"type":"string","label":"Delivery Type"},"smartkargo_channel":{"code":"channel","meta":{"configurable":true},"type":"string","label":"Channel"},"smartkargo_label_ref2":{"code":"labelRef2","meta":{"configurable":true},"type":"string","label":"Label Ref2"},"smartkargo_special_handling":{"code":"specialHandlingType","meta":{"configurable":true},"type":"string","label":"Special Handling"},"smartkargo_commodity_type":{"code":"commodityType","meta":{"configurable":true},"type":"string","label":"Commodity Type"},"smartkargo_incoterm":{"code":"incoterm","meta":{"configurable":true},"type":"string","label":"Incoterm"},"smartkargo_additional_info_01":{"code":"additionalInfo01","meta":{"configurable":true},"type":"string","label":"Additional Info 01"},"smartkargo_additional_info_02":{"code":"additionalInfo02","meta":{"configurable":true},"type":"string","label":"Additional Info 02"},"smartkargo_additional_info_03":{"code":"additionalInfo03","meta":{"configurable":true},"type":"string","label":"Additional Info 03"},"smartkargo_additional_info_04":{"code":"additionalInfo04","meta":{"configurable":true},"type":"string","label":"Additional Info 04"}},"readme":null},{"id":"spring","carrier_name":"spring","display_name":"Spring","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_format":{"code":"label_format","name":"label_format","required":false,"type":"string","label":"Label Format"}},"shipping_services":{"spring_tracked":"TRCK","spring_signature":"SIGN","spring_untracked":"UNTR","spring_collect":"CLLCT","spring_express":"EXPR","spring_import":"IMPRT","spring_back_returns":"BACK","spring_back_tracked":"BACKT","spring_no_label":"NOLABEL","spring_postnl_parcel_eu":"PPLEU","spring_postnl_parcel_benelux":"PPND","spring_postnl_parcel_benelux_sign":"PPNDS","spring_postnl_parcel_benelux_no_neighbor":"PPHD","spring_postnl_parcel_benelux_sign_no_neighbor":"PPHDS","spring_postnl_parcel_benelux_upu":"PPLUP","spring_postnl_parcel_globalpack_ems":"PPLGE","spring_postnl_parcel_globalpack_upu":"PPLGU","spring_postnl_parcel_epg":"PPLEP","spring_postnl_parcel_epg_noneu":"PPNEU","spring_postnl_lightweight_china":"PPLLW","spring_postnl_collect_service":"PPLCS","spring_postnl_packet_tracked":"PPTT","spring_postnl_packet_registered":"PPTR","spring_postnl_packet_non_tracked":"PPNT","spring_postnl_packet_boxable_bag_trace":"PPBBT","spring_postnl_packet_bag_trace":"PPBT","spring_postnl_packet_boxable_tracked":"PPBTT","spring_postnl_packet_boxable_non_tracked":"PPBNT","spring_royal_mail_tracked_24":"RM24","spring_royal_mail_tracked_24_sign":"RM24S","spring_royal_mail_tracked_48":"RM48","spring_royal_mail_tracked_48_2":"RM482","spring_royal_mail_tracked_48_sign":"RM48S","spring_sending_mainland":"SEND","spring_sending_islands":"SEND2","spring_italian_post_crono":"ITCR","spring_italian_post_crono_express":"ITCRX","spring_dpd_de":"DPDDE","spring_hermes_sign":"HEHDS","spring_hermes_collect":"HEDCS","spring_colis_prive":"CPHD","spring_colis_prive_sign":"CPHDS","spring_com_standard":"SCST","spring_com_standard_sign":"SCSTS","spring_com_express":"SCEX","spring_com_express_sign":"SCEXS","spring_usa_parcel_ground":"UPGR","spring_usa_parcel_ground_sign":"UPGRS","spring_usa_parcel_express":"UPEX","spring_usa_parcel_express_sign":"UPEXS","spring_usa_parcel_max":"UPMA","spring_usa_parcel_max_sign":"UPMAS","spring_usa_parcel_ground_dg":"UPDG","spring_usa_parcel_ground_dg_sign":"UDGS","spring_usa_parcel_plus_ground_dg":"UPPDG","spring_usa_parcel_plus_ground_dg_sign":"UPDGS","spring_packeta":"PACHD","spring_mailalliance_boxable":"MABNT","spring_austrian_post":"ATEHD"},"shipping_options":{"spring_customs_duty":{"code":"CustomsDuty","meta":{"configurable":true},"type":"string","label":"Spring Customs Duty"},"spring_declaration_type":{"code":"DeclarationType","meta":{"configurable":true},"type":"string","label":"Spring Declaration Type"},"spring_dangerous_goods":{"code":"DangerousGoods","meta":{"configurable":true},"type":"boolean","label":"Spring Dangerous Goods"},"spring_shipping_value":{"code":"ShippingValue","meta":{"configurable":true},"type":"float","label":"Spring Shipping Value"},"spring_display_id":{"code":"DisplayId","meta":{"configurable":true},"type":"string","label":"Spring Display ID"},"spring_invoice_number":{"code":"InvoiceNumber","meta":{"configurable":true},"type":"string","label":"Spring Invoice Number"},"spring_order_reference":{"code":"OrderReference","meta":{"configurable":true},"type":"string","label":"Spring Order Reference"},"spring_order_date":{"code":"OrderDate","meta":{"configurable":true},"type":"string","label":"Spring Order Date"},"spring_consignor_vat":{"code":"ConsignorVat","meta":{"configurable":true},"type":"string","label":"Spring Consignor Vat"},"spring_consignor_eori":{"code":"ConsignorEori","meta":{"configurable":true},"type":"string","label":"Spring Consignor Eori"},"spring_consignor_nl_vat":{"code":"ConsignorNlVat","meta":{"configurable":true},"type":"string","label":"Spring Consignor Nl Vat"},"spring_consignor_eu_eori":{"code":"ConsignorEuEori","meta":{"configurable":true},"type":"string","label":"Spring Consignor EU Eori"},"spring_consignor_gb_eori":{"code":"ConsignorGbEori","meta":{"configurable":true},"type":"string","label":"Spring Consignor Gb Eori"},"spring_consignor_ioss":{"code":"ConsignorIoss","meta":{"configurable":true},"type":"string","label":"Spring Consignor Ioss"},"spring_consignor_local_tax_number":{"code":"ConsignorLocalTaxNumber","meta":{"configurable":true},"type":"string","label":"Spring Consignor Local Tax Number"},"spring_export_carrier_name":{"code":"ExportCarrierName","meta":{"configurable":true},"type":"string","label":"Spring Export Carrier Name"},"spring_export_awb":{"code":"ExportAwb","meta":{"configurable":true},"type":"string","label":"Spring Export Awb"},"spring_pudo_location_id":{"code":"PudoLocationId","meta":{"configurable":true},"type":"string","label":"Spring Pudo Location ID"}},"readme":null},{"id":"teleship","carrier_name":"teleship","display_name":"Teleship","integration_status":"beta","website":"https://www.teleship.com","description":"Teleship is an international shipping platform providing end-to-end logistics solutions with real-time rates, automated customs compliance, and shipment tracking.","documentation":"https://developers.teleship.com","is_enabled":true,"capabilities":["Manifest","OAuth","Rating","Duties","Pickup","Shipping","Webhook","Tracking"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"label_format":{"code":"label_format","enum":["PDF","ZPL","PNG"],"name":"label_format","required":false,"type":"string","label":"Label Format"}},"shipping_services":{"teleship_expedited_pickup":"TELESHIP-EXPEDITED-PICKUP","teleship_expedited_dropoff":"TELESHIP-EXPEDITED-DROPOFF","teleship_standard_dropoff":"TELESHIP-STANDARD-DROPOFF","teleship_standard_pickup":"TELESHIP-STANDARD-PICKUP","teleship_postal_dropoff":"TELESHIP-POSTAL-DROPOFF","teleship_postal_pickup":"TELESHIP-POSTAL-PICKUP"},"shipping_options":{"teleship_signature_required":{"code":"signatureRequired","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Teleship Signature Required"},"teleship_delivery_warranty":{"code":"deliveryWarranty","meta":{"category":"INSURANCE","configurable":true},"type":"boolean","label":"Teleship Delivery Warranty"},"teleship_delivery_PUDO":{"code":"deliveryPUDO","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Teleship Delivery Pudo"},"teleship_low_carbon":{"code":"lowCarbon","meta":{"configurable":true},"type":"boolean","label":"Teleship Low Carbon"},"teleship_duty_tax_calculation":{"code":"dutyTaxCalculation","meta":{"configurable":true},"type":"boolean","label":"Teleship Duty Tax Calculation"},"teleship_customer_reference":{"code":"customerReference","meta":{"configurable":true},"type":"string","label":"Teleship Customer Reference"},"teleship_order_tracking_reference":{"code":"orderTrackingReference","meta":{"configurable":true},"type":"string","label":"Teleship Order Tracking Reference"},"teleship_commercial_invoice_reference":{"code":"commercialInvoiceReference","meta":{"category":"INVOICE","configurable":true},"type":"string","label":"Teleship Commercial Invoice Reference"}},"readme":null},{"id":"tge","carrier_name":"tge","display_name":"TGE","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Manifest","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"toll_username":{"name":"toll_username","required":true,"sensitive":false,"type":"string","label":"Toll Username"},"toll_password":{"name":"toll_password","required":true,"sensitive":false,"type":"string","label":"Toll Password"},"my_toll_token":{"name":"my_toll_token","required":true,"sensitive":false,"type":"string","label":"My Toll Token"},"my_toll_identity":{"name":"my_toll_identity","required":true,"sensitive":false,"type":"string","label":"My Toll Identity"},"account_code":{"name":"account_code","required":false,"sensitive":false,"type":"string","label":"Account Code"},"sscc_count":{"name":"sscc_count","required":false,"sensitive":false,"type":"integer","label":"Sscc Count"},"shipment_count":{"name":"shipment_count","required":false,"sensitive":false,"type":"integer","label":"Shipment Count"},"account_country_code":{"default":"AU","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"channel":{"code":"channel","name":"channel","required":false,"type":"string","label":"Channel"},"server_url":{"code":"server_url","name":"server_url","required":false,"type":"string","label":"Server Url"},"text_color":{"code":"text_color","name":"text_color","required":false,"type":"string","label":"Text Color"},"brand_color":{"code":"brand_color","name":"brand_color","required":false,"type":"string","label":"Brand Color"},"business_id":{"code":"business_id","name":"business_id","required":false,"type":"string","label":"Business Id"},"freight_mode":{"code":"freight_mode","name":"freight_mode","required":false,"type":"string","label":"Freight Mode"},"message_sender":{"code":"message_sender","name":"message_sender","required":false,"type":"string","label":"Message Sender"},"SYSID":{"code":"SYSID","name":"SYSID","required":false,"type":"string","label":"Sysid"},"SHIP_GS1":{"code":"SHIP_GS1","name":"SHIP_GS1","required":false,"type":"string","label":"Ship Gs1"},"SHIP_range_end":{"code":"SHIP_range_end","name":"SHIP_range_end","required":false,"type":"integer","label":"Ship Range End"},"SHIP_range_start":{"code":"SHIP_range_start","name":"SHIP_range_start","required":false,"type":"integer","label":"Ship Range Start"},"SSCC_GS1":{"code":"SSCC_GS1","name":"SSCC_GS1","required":false,"type":"string","label":"Sscc Gs1"},"SSCC_range_end":{"code":"SSCC_range_end","name":"SSCC_range_end","required":false,"type":"integer","label":"Sscc Range End"},"SSCC_range_start":{"code":"SSCC_range_start","name":"SSCC_range_start","required":false,"type":"integer","label":"Sscc Range Start"}},"shipping_services":{"tge_freight_service":"X"},"shipping_options":{"tge_ssc_ids":{"code":"tge_ssc_ids","meta":{"configurable":true},"type":"list","label":"Ssc Ids"},"tge_shipment_ids":{"code":"tge_shipment_ids","meta":{"configurable":true},"type":"list","label":"Shipment Ids"},"tge_freight_mode":{"code":"tge_freight_mode","meta":{"configurable":true},"type":"string","label":"Freight Mode"},"tge_despatch_date":{"code":"tge_despatch_date","meta":{"configurable":true},"type":"string","label":"Despatch Date"},"tge_special_instruction":{"code":"tge_special_instruction","meta":{"configurable":true},"type":"string","label":"Special Instruction"},"tge_required_delivery_date":{"code":"tge_required_delivery_date","meta":{"configurable":true},"type":"string","label":"Required Delivery Date"}},"readme":null},{"id":"tnt","carrier_name":"tnt","display_name":"TNT","integration_status":"beta","website":"https://www.tnt.com","description":"TNT is an international courier delivery services company with headquarters in the Netherlands.","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"app_id":{"code":"app_id","name":"app_id","required":false,"type":"string","label":"App Id"},"email_from":{"code":"email_from","name":"email_from","required":false,"type":"string","label":"Email From"}},"shipping_services":{"tnt_special_express":"1N","tnt_9_00_express":"09N","tnt_10_00_express":"10N","tnt_12_00_express":"12N","tnt_express":"EX","tnt_economy_express":"48N","tnt_global_express":"15N"},"shipping_options":{"tnt_priority":{"code":"PR","meta":{"configurable":true},"type":"string","label":"Priority"},"tnt_insurance":{"code":"IN","meta":{"configurable":true},"type":"float","label":"Insurance"},"tnt_enhanced_liability":{"code":"EL","meta":{"configurable":true},"type":"string","label":"Enhanced Liability"},"tnt_dangerous_goods_fully_regulated":{"code":"HZ","meta":{"configurable":true},"type":"string","label":"Dangerous Goods Fully Regulated"},"tnt_dangerous_goods_in_limited_quantities":{"code":"LQ","meta":{"configurable":true},"type":"string","label":"Dangerous Goods In Limited Quantities"},"tnt_dry_ice_shipments":{"code":"DI","meta":{"configurable":true},"type":"string","label":"Dry Ice Shipments"},"tnt_biological_substances":{"code":"BB","meta":{"configurable":true},"type":"string","label":"Biological Substances"},"tnt_lithium_batteries":{"code":"LB","meta":{"configurable":true},"type":"string","label":"Lithium Batteries"},"tnt_dangerous_goods_in_excepted_quantities":{"code":"EQ","meta":{"configurable":true},"type":"string","label":"Dangerous Goods In Excepted Quantities"},"tnt_radioactive_materials_in_excepted_packages":{"code":"XP","meta":{"configurable":true},"type":"string","label":"Radioactive Materials In Excepted Packages"},"tnt_pre_delivery_notification":{"code":"SMS","meta":{"configurable":true},"type":"string","label":"Pre Delivery Notification"},"tnt_division_international_shipments":{"code":"G","meta":{"configurable":true},"type":"boolean","label":"Division International Shipments"},"tnt_division_global_link_domestic":{"code":"D","meta":{"configurable":true},"type":"boolean","label":"Division Global Link Domestic"},"tnt_division_german_domestic":{"code":"H","meta":{"configurable":true},"type":"boolean","label":"Division German Domestic"},"tnt_division_uk_domestic":{"code":"010","meta":{"configurable":true},"type":"boolean","label":"Division Uk Domestic"}},"readme":null},{"id":"ups","carrier_name":"ups","display_name":"UPS","integration_status":"production-ready","website":"https://www.ups.com","description":"UPS is an American multinational shipping & receiving and supply chain management company.","documentation":null,"is_enabled":true,"capabilities":["Rating","Pickup","Paperless","Shipping","Tracking"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"cost_center":{"code":"cost_center","name":"cost_center","required":false,"type":"string","label":"Cost Center"},"merchant_id":{"code":"merchant_id","name":"merchant_id","required":false,"type":"string","label":"Merchant Id"},"enforce_zpl":{"code":"enforce_zpl","name":"enforce_zpl","required":false,"type":"boolean","label":"Enforce ZPL"},"label_type":{"code":"label_type","enum":["PDF_6x4","PDF_8x4","ZPL_6x4"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"ups_standard":"UPS Standard","ups_worldwide_express":"UPS Worldwide Express","ups_worldwide_expedited":"UPS Worldwide Expedited","ups_worldwide_express_plus":"UPS Worldwide Express Plus","ups_worldwide_saver":"UPS Worldwide Saver","ups_2nd_day_air":"UPS 2nd Day Air","ups_2nd_day_air_am":"UPS 2nd Day Air A.M.","ups_3_day_select":"UPS 3 Day Select","ups_ground":"UPS Ground","ups_next_day_air":"UPS Next Day Air","ups_next_day_air_early":"UPS Next Day Air Early","ups_next_day_air_saver":"UPS Next Day Air Saver","ups_expedited_ca":"UPS Expedited CA","ups_express_saver_ca":"UPS Express Saver CA","ups_3_day_select_ca_us":"UPS 3 Day Select CA US","ups_access_point_economy_ca":"UPS Access Point Economy CA","ups_express_ca":"UPS Express CA","ups_express_early_ca":"UPS Express Early CA","ups_express_saver_intl_ca":"UPS Express Saver Intl CA","ups_standard_ca":"UPS Standard CA","ups_worldwide_expedited_ca":"UPS Worldwide Expedited CA","ups_worldwide_express_ca":"UPS Worldwide Express CA","ups_worldwide_express_plus_ca":"UPS Worldwide Express Plus CA","ups_express_early_ca_us":"UPS Express Early CA US","ups_access_point_economy_eu":"UPS Access Point Economy EU","ups_expedited_eu":"UPS Expedited EU","ups_express_eu":"UPS Express EU","ups_standard_eu":"UPS Standard EU","ups_worldwide_express_plus_eu":"UPS Worldwide Express Plus EU","ups_worldwide_saver_eu":"UPS Worldwide Saver EU","ups_access_point_economy_mx":"UPS Access Point Economy MX","ups_expedited_mx":"UPS Expedited MX","ups_express_mx":"UPS Express MX","ups_standard_mx":"UPS Standard MX","ups_worldwide_express_plus_mx":"UPS Worldwide Express Plus MX","ups_worldwide_saver_mx":"UPS Worldwide Saver MX","ups_access_point_economy_pl":"UPS Access Point Economy PL","ups_today_dedicated_courrier_pl":"UPS Today Dedicated Courrier PL","ups_today_express_pl":"UPS Today Express PL","ups_today_express_saver_pl":"UPS Today Express Saver PL","ups_today_standard_pl":"UPS Today Standard PL","ups_expedited_pl":"UPS Expedited PL","ups_express_pl":"UPS Express PL","ups_express_plus_pl":"UPS Express Plus PL","ups_express_saver_pl":"UPS Express Saver PL","ups_standard_pl":"UPS Standard PL","ups_2nd_day_air_pr":"UPS 2nd Day Air PR","ups_ground_pr":"UPS Ground PR","ups_next_day_air_pr":"UPS Next Day Air PR","ups_next_day_air_early_pr":"UPS Next Day Air Early PR","ups_worldwide_expedited_pr":"UPS Worldwide Expedited PR","ups_worldwide_express_pr":"UPS Worldwide Express PR","ups_worldwide_express_plus_pr":"UPS Worldwide Express Plus PR","ups_worldwide_saver_pr":"UPS Worldwide Saver PR","ups_express_12_00_de":"UPS Express 12:00 DE","ups_worldwide_express_freight":"UPS Worldwide Express Freight","ups_worldwide_express_freight_midday":"UPS Worldwide Express Freight Midday","ups_worldwide_economy_ddu":"UPS Worldwide Economy DDU","ups_worldwide_economy_ddp":"UPS Worldwide Economy DDP"},"shipping_options":{"ups_saturday_pickup_indicator":{"code":"SaturdayPickupIndicator","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"UPS Saturday Pickup Indicator"},"ups_saturday_delivery_indicator":{"code":"SaturdayDeliveryIndicator","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"UPS Saturday Delivery Indicator"},"ups_sunday_delivery_indicator":{"code":"SundayDeliveryIndicator","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"UPS Sunday Delivery Indicator"},"ups_access_point_cod":{"code":"AccessPointCOD","meta":{"category":"COD","configurable":true},"type":"float","label":"UPS Access Point COD"},"ups_deliver_to_addressee_only_indicator":{"code":"DeliverToAddresseeOnlyIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Deliver To Addressee Only Indicator"},"ups_direct_delivery_only_indicator":{"code":"DirectDeliveryOnlyIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Direct Delivery Only Indicator"},"ups_cod":{"code":"COD","meta":{"category":"COD","configurable":true},"type":"float","label":"UPS COD"},"ups_return_of_document_indicator":{"code":"ReturnOfDocumentIndicator","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"UPS Return Of Document Indicator"},"ups_carbonneutral_indicator":{"code":"UPScarbonneutralIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Carbonneutral Indicator"},"ups_certificate_of_origin_indicator":{"code":"CertificateOfOriginIndicator","meta":{"configurable":true},"type":"string","label":"UPS Certificate Of Origin Indicator"},"ups_shipper_export_declaration_indicator":{"code":"ShipperExportDeclarationIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Shipper Export Declaration Indicator"},"ups_commercial_invoice_removal_indicator":{"code":"CommercialInvoiceRemovalIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Commercial Invoice Removal Indicator"},"ups_import_control":{"code":"ImportControl","enum":["03","04","05"],"meta":{"configurable":true},"type":"string","label":"UPS Import Control"},"ups_return_service":{"code":"ReturnService","enum":["ups_print_and_mail","ups_return_1_attempt","ups_return_3_attempt","ups_electronic_return_label","ups_print_return_label","ups_exchange_print_return_label","ups_pack_collect_1_attempt_box_1","ups_pack_collect_1_attempt_box_2","ups_pack_collect_1_attempt_box_3","ups_pack_collect_1_attempt_box_4","ups_pack_collect_1_attempt_box_5","ups_pack_collect_3_attempt_box_1","ups_pack_collect_3_attempt_box_2","ups_pack_collect_3_attempt_box_3","ups_pack_collect_3_attempt_box_4","ups_pack_collect_3_attempt_box_5"],"meta":{"category":"RETURN","configurable":true},"type":"string","label":"UPS Return Service"},"ups_epra_indicator":{"code":"EPRAIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Epra Indicator"},"ups_lift_gate_at_pickup_indicator":{"code":"LiftGateAtPickupIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Lift Gate At Pickup Indicator"},"ups_lift_gate_at_delivery_indicator":{"code":"LiftGateAtDeliveryIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Lift Gate At Delivery Indicator"},"ups_drop_off_at_ups_facility_indicator":{"code":"DropOffAtUPSFacilityIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Drop Off At UPS Facility Indicator"},"ups_master_carton_indicator":{"code":"MasterCartonIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Master Carton Indicator"},"ups_exchange_forward_indicator":{"code":"ExchangeForwardIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Exchange Forward Indicator"},"ups_hold_for_pickup_indicator":{"code":"HoldForPickupIndicator","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"UPS Hold For Pickup Indicator"},"ups_dropoff_at_ups_facility_indicator":{"code":"DropoffAtUPSFacilityIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Dropoff At UPS Facility Indicator"},"ups_lift_gate_for_pickup_indicator":{"code":"LiftGateForPickupIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Lift Gate For Pickup Indicator"},"ups_lift_gate_for_delivery_indicator":{"code":"LiftGateForDeliveryIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Lift Gate For Delivery Indicator"},"ups_sdl_shipment_indicator":{"code":"SDLShipmentIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Sdl Shipment Indicator"},"ups_item_disposal":{"code":"ItemDisposal","meta":{"configurable":true},"type":"boolean","label":"UPS Item Disposal"},"ups_available_services_option":{"code":"AvailableServicesOption","enum":["1","2","3"],"meta":{"configurable":true},"type":"string","label":"UPS Available Services Option"},"ups_delivery_confirmation":{"code":"DeliveryConfirmation","enum":["1","2"],"meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"UPS Delivery Confirmation"},"ups_delivery_confirmation_level":{"code":"DeliveryConfirmationLevel","enum":["P","S"],"meta":{"configurable":true},"type":"string","label":"UPS Delivery Confirmation Level"},"ups_inside_delivery":{"code":"InsideDelivery","enum":["01","02","03"],"meta":{"configurable":true},"type":"string","label":"UPS Inside Delivery"},"ups_restricted_articles":{"code":"RestrictedArticles","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Restricted Articles"},"ups_alcoholic_beverages_indicator":{"code":"AlcoholicBeveragesIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Alcoholic Beverages Indicator"},"ups_diagnostic_specimens_indicator":{"code":"DiagnosticSpecimensIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Diagnostic Specimens Indicator"},"ups_perishables_indicator":{"code":"PerishablesIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Perishables Indicator"},"ups_plants_indicator":{"code":"PlantsIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Plants Indicator"},"ups_seeds_indicator":{"code":"SeedsIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Seeds Indicator"},"ups_special_exceptions_indicator":{"code":"SpecialExceptionsIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Special Exceptions Indicator"},"ups_tobacco_indicator":{"code":"TobaccoIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Tobacco Indicator"},"ups_ecigarettes_indicator":{"code":"ECigarettesIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Ecigarettes Indicator"},"ups_hemp_cbd_indicator":{"code":"HempCBDIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Hemp Cbd Indicator"},"ups_negotiated_rates_indicator":{"code":"NegotiatedRatesIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Negotiated Rates Indicator"},"ups_frs_shipment_indicator":{"code":"FRSShipmentIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Frs Shipment Indicator"},"ups_rate_chart_indicator":{"code":"RateChartIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Rate Chart Indicator"},"ups_user_level_discount_indicator":{"code":"UserLevelDiscountIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS User Level Discount Indicator"},"ups_tpfc_negotiated_rates_indicator":{"code":"TPFCNegotiatedRatesIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Tpfc Negotiated Rates Indicator"},"ups_access_point_pickup":{"code":"01","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"UPS Access Point Pickup"},"ups_access_point_delivery":{"code":"02","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"UPS Access Point Delivery"}},"readme":null},{"id":"usps","carrier_name":"usps","display_name":"USPS","integration_status":"production-ready","website":"https://www.usps.com","description":"The United States Postal Service is an independent agency of the executive branch of the United States federal government responsible for providing postal service in the United States.","documentation":"https://www.usps.com/business/web-tools-apis","is_enabled":true,"capabilities":["Manifest","Rating","Pickup","Shipping","Tracking"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_type":{"default":"EPS","enum":["EPS","PERMIT","METER"],"name":"account_type","required":false,"sensitive":false,"type":"string","label":"Account Type"},"manifest_MID":{"name":"manifest_MID","required":false,"sensitive":false,"type":"string","label":"Manifest Mid"},"CRID":{"name":"CRID","required":false,"sensitive":false,"type":"string","label":"Crid"},"MID":{"name":"MID","required":false,"sensitive":false,"type":"string","label":"Mid"},"account_country_code":{"default":"US","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"permit_ZIP":{"code":"permit_ZIP","name":"permit_ZIP","required":false,"type":"string","label":"Permit Zip"},"permit_number":{"code":"permit_number","name":"permit_number","required":false,"type":"string","label":"Permit Number"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"usps_parcel_select_lightweight":"PARCEL_SELECT_LIGHTWEIGHT","usps_parcel_select":"PARCEL_SELECT","usps_priority_mail_express":"PRIORITY_MAIL_EXPRESS","usps_priority_mail":"PRIORITY_MAIL","usps_library_mail":"LIBRARY_MAIL","usps_media_mail":"MEDIA_MAIL","usps_bound_printed_matter":"BOUND_PRINTED_MATTER","usps_connect_local":"USPS_CONNECT_LOCAL","usps_connect_mail":"USPS_CONNECT_MAIL","usps_connect_next_day":"USPS_CONNECT_NEXT_DAY","usps_connect_regional":"USPS_CONNECT_REGIONAL","usps_connect_same_day":"USPS_CONNECT_SAME_DAY","usps_ground_advantage":"USPS_GROUND_ADVANTAGE","usps_domestic_matter_for_the_blind":"DOMESTIC_MATTER_FOR_THE_BLIND","usps_all":"ALL"},"shipping_options":{"usps_label_delivery_service":{"code":"415","meta":{"configurable":true},"type":"boolean","label":"USPS Label Delivery Service"},"usps_tracking_plus_6_months":{"code":"480","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Plus 6 Months"},"usps_tracking_plus_1_year":{"code":"481","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Plus 1 Year"},"usps_tracking_plus_3_years":{"code":"482","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Plus 3 Years"},"usps_tracking_plus_5_years":{"code":"483","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Plus 5 Years"},"usps_tracking_plus_7_years":{"code":"484","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Plus 7 Years"},"usps_tracking_plus_10_years":{"code":"485","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Plus 10 Years"},"usps_tracking_plus_signature_3_years":{"code":"486","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Tracking Plus Signature 3 Years"},"usps_tracking_plus_signature_5_years":{"code":"487","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Tracking Plus Signature 5 Years"},"usps_tracking_plus_signature_7_years":{"code":"488","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Tracking Plus Signature 7 Years"},"usps_tracking_plus_signature_10_years":{"code":"489","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Tracking Plus Signature 10 Years"},"usps_hazardous_materials_air_eligible_ethanol":{"code":"810","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Air Eligible Ethanol"},"usps_hazardous_materials_class_1_toy_propellant_safety_fuse_package":{"code":"811","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 1 Toy Propellant Safety Fuse Package"},"usps_hazardous_materials_class_3_flammable_and_combustible_liquids":{"code":"812","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 3 Flammable And Combustible Liquids"},"usps_hazardous_materials_class_7_radioactive_materials":{"code":"813","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 7 Radioactive Materials"},"usps_hazardous_materials_class_8_air_eligible_corrosive_materials":{"code":"814","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 8 Air Eligible Corrosive Materials"},"usps_hazardous_materials_class_8_nonspillable_wet_batteries":{"code":"815","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 8 Nonspillable Wet Batteries"},"usps_hazardous_materials_class_9_lithium_battery_marked_ground_only":{"code":"816","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Lithium Battery Marked Ground Only"},"usps_hazardous_materials_class_9_lithium_battery_returns":{"code":"817","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Lithium Battery Returns"},"usps_hazardous_materials_class_9_marked_lithium_batteries":{"code":"818","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Marked Lithium Batteries"},"usps_hazardous_materials_class_9_dry_ice":{"code":"819","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Dry Ice"},"usps_hazardous_materials_class_9_unmarked_lithium_batteries":{"code":"820","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Unmarked Lithium Batteries"},"usps_hazardous_materials_class_9_magnetized_materials":{"code":"821","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Magnetized Materials"},"usps_hazardous_materials_division_4_1_mailable_flammable_solids_and_safety_matches":{"code":"822","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Division 4 1 Mailable Flammable Solids And Safety Matches"},"usps_hazardous_materials_division_5_1_oxidizers":{"code":"823","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Division 5 1 Oxidizers"},"usps_hazardous_materials_division_5_2_organic_peroxides":{"code":"824","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Division 5 2 Organic Peroxides"},"usps_hazardous_materials_division_6_1_toxic_materials":{"code":"825","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Division 6 1 Toxic Materials"},"usps_hazardous_materials_division_6_2_biological_materials":{"code":"826","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Division 6 2 Biological Materials"},"usps_hazardous_materials_excepted_quantity_provision":{"code":"827","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Excepted Quantity Provision"},"usps_hazardous_materials_ground_only_hazardous_materials":{"code":"828","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Ground Only Hazardous Materials"},"usps_hazardous_materials_air_eligible_id8000_consumer_commodity":{"code":"829","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Air Eligible Id8000 Consumer Commodity"},"usps_hazardous_materials_lighters":{"code":"830","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Lighters"},"usps_hazardous_materials_limited_quantity_ground":{"code":"831","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Limited Quantity Ground"},"usps_hazardous_materials_small_quantity_provision_markings_required":{"code":"832","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Small Quantity Provision Markings Required"},"usps_hazardous_materials":{"code":"857","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials"},"usps_certified_mail":{"code":"910","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Certified Mail"},"usps_certified_mail_restricted_delivery":{"code":"911","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Certified Mail Restricted Delivery"},"usps_certified_mail_adult_signature_required":{"code":"912","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Certified Mail Adult Signature Required"},"usps_certified_mail_adult_signature_restricted_delivery":{"code":"913","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Certified Mail Adult Signature Restricted Delivery"},"usps_collect_on_delivery":{"code":"915","meta":{"category":"COD","configurable":true},"type":"float","label":"USPS Collect On Delivery"},"usps_collect_on_delivery_restricted_delivery":{"code":"917","meta":{"category":"COD","configurable":true},"type":"boolean","label":"USPS Collect On Delivery Restricted Delivery"},"usps_tracking_electronic":{"code":"920","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Electronic"},"usps_signature_confirmation":{"code":"921","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Signature Confirmation"},"usps_adult_signature_required":{"code":"922","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Adult Signature Required"},"usps_adult_signature_restricted_delivery":{"code":"923","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Adult Signature Restricted Delivery"},"usps_signature_confirmation_restricted_delivery":{"code":"924","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Signature Confirmation Restricted Delivery"},"usps_priority_mail_express_merchandise_insurance":{"code":"925","meta":{"category":"INSURANCE","configurable":true},"type":"boolean","label":"USPS Priority Mail Express Merchandise Insurance"},"usps_insurance_below_500":{"code":"930","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"USPS Insurance Below 500"},"usps_insurance_above_500":{"code":"931","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"USPS Insurance Above 500"},"usps_insurance_restricted_delivery":{"code":"934","meta":{"category":"INSURANCE","configurable":true},"type":"boolean","label":"USPS Insurance Restricted Delivery"},"usps_registered_mail":{"code":"940","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Registered Mail"},"usps_registered_mail_restricted_delivery":{"code":"941","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Registered Mail Restricted Delivery"},"usps_return_receipt":{"code":"955","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"USPS Return Receipt"},"usps_return_receipt_electronic":{"code":"957","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"USPS Return Receipt Electronic"},"usps_signature_requested_priority_mail_express_only":{"code":"981","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Signature Requested Priority Mail Express Only"},"usps_parcel_locker_delivery":{"code":"984","meta":{"category":"LOCKER","configurable":true},"type":"boolean","label":"USPS Parcel Locker Delivery"},"usps_po_to_addressee_priority_mail_express_only":{"code":"986","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"USPS Po To Addressee Priority Mail Express Only"},"usps_sunday_delivery":{"code":"981","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"USPS Sunday Delivery"},"usps_mail_class":{"code":"mailClass","enum":["usps_parcel_select_lightweight","usps_parcel_select","usps_priority_mail_express","usps_priority_mail","usps_library_mail","usps_media_mail","usps_bound_printed_matter","usps_connect_local","usps_connect_mail","usps_connect_next_day","usps_connect_regional","usps_connect_same_day","usps_ground_advantage","usps_domestic_matter_for_the_blind","usps_all"],"meta":{"configurable":true},"type":"string","label":"Mail Class"},"usps_facility_id":{"code":"facilityId","meta":{"configurable":true},"type":"string","label":"Facility Id"},"usps_machinable_piece":{"code":"machinable","meta":{"configurable":true},"type":"boolean","label":"Machinable Piece"},"usps_hold_for_pickup":{"code":"holdForPickup","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Hold For Pickup"},"usps_processing_category":{"code":"processingCategory","meta":{"configurable":true},"type":"string","label":"Processing Category"},"usps_carrier_release":{"code":"carrierRelease","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Carrier Release"},"usps_physical_signature_required":{"code":"physicalSignatureRequired","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Physical Signature Required"},"usps_price_type":{"code":"priceType","enum":["RETAIL","COMMERCIAL","CONTRACT"],"meta":{"configurable":true},"type":"string","label":"Price Type"},"usps_destination_entry_facility_type":{"code":"destinationEntryFacilityType","enum":["NONE","DESTINATION_NETWORK_DISTRIBUTION_CENTER","DESTINATION_SECTIONAL_CENTER_FACILITY","DESTINATION_DELIVERY_UNIT","DESTINATION_SERVICE_HUB"],"meta":{"configurable":true},"type":"string","label":"Destination Entry Facility Type"},"usps_extra_services":{"code":"extraServices","meta":{"configurable":true},"type":"list","label":"Extra Services"},"usps_shipping_filter":{"code":"shippingFilter","enum":["PRICE","SERVICE_STANDARDS"],"meta":{"configurable":true},"type":"string","label":"Shipping Filter"}},"readme":null},{"id":"usps_international","carrier_name":"usps_international","display_name":"USPS International","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Manifest","Rating","Pickup","Shipping","Tracking"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_type":{"default":"EPS","enum":["EPS","PERMIT","METER"],"name":"account_type","required":false,"sensitive":false,"type":"string","label":"Account Type"},"manifest_MID":{"name":"manifest_MID","required":false,"sensitive":false,"type":"string","label":"Manifest Mid"},"CRID":{"name":"CRID","required":false,"sensitive":false,"type":"string","label":"Crid"},"MID":{"name":"MID","required":false,"sensitive":false,"type":"string","label":"Mid"},"account_country_code":{"default":"US","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"permit_ZIP":{"code":"permit_ZIP","name":"permit_ZIP","required":false,"type":"string","label":"Permit Zip"},"permit_number":{"code":"permit_number","name":"permit_number","required":false,"type":"string","label":"Permit Number"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"price_type":{"code":"price_type","enum":["RETAIL","COMMERCIAL","COMMERCIAL_BASE","COMMERCIAL_PLUS","CONTRACT"],"name":"price_type","required":false,"type":"string","label":"Price Type"}},"shipping_services":{"usps_first_class_package_international_service":"FIRST-CLASS_PACKAGE_INTERNATIONAL_SERVICE","usps_priority_mail_international":"PRIORITY_MAIL_INTERNATIONAL","usps_priority_mail_express_international":"PRIORITY_MAIL_EXPRESS_INTERNATIONAL","usps_global_express_guaranteed":"GLOBAL_EXPRESS_GUARANTEED","usps_all":"ALL"},"shipping_options":{"usps_hazardous_materials_class_7_radioactive_materials":{"code":"813","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 7 Radioactive Materials"},"usps_hazardous_materials_class_9_unmarked_lithium_batteries":{"code":"820","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Unmarked Lithium Batteries"},"usps_hazardous_materials_division_6_2_biological_materials":{"code":"826","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Division 6 2 Biological Materials"},"usps_hazardous_materials":{"code":"857","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials"},"usps_insurance_below_500":{"code":"930","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"USPS Insurance Below 500"},"usps_insurance_above_500":{"code":"931","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"USPS Insurance Above 500"},"usps_return_receipt":{"code":"955","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"USPS Return Receipt"},"usps_mail_class":{"code":"mailClass","enum":["usps_first_class_package_international_service","usps_priority_mail_international","usps_priority_mail_express_international","usps_global_express_guaranteed","usps_all"],"meta":{"configurable":true},"type":"string","label":"Usps Mail Class"},"usps_facility_id":{"code":"facilityId","meta":{"configurable":true},"type":"string","label":"Usps Facility Id"},"usps_machinable_piece":{"code":"machinable","meta":{"configurable":true},"type":"boolean","label":"Usps Machinable Piece"},"usps_price_type":{"code":"priceType","enum":["RETAIL","COMMERCIAL","COMMERCIAL_BASE","COMMERCIAL_PLUS","CONTRACT"],"meta":{"configurable":true},"type":"string","label":"Usps Price Type"},"usps_hold_for_pickup":{"code":"holdForPickup","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Usps Hold For Pickup"},"usps_carrier_release":{"code":"carrierRelease","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Usps Carrier Release"},"usps_processing_category":{"code":"processingCategory","meta":{"configurable":true},"type":"string","label":"Usps Processing Category"},"usps_rate_indicator":{"code":"rateIndicator","enum":["E4","E6","FA","FB","FE","FP","FS","PA","PL","SP","EP","HA","HB","HE","HL","HP","HS","LE"],"meta":{"configurable":true},"type":"string","label":"Usps Rate Indicator"},"usps_physical_signature_required":{"code":"physicalSignatureRequired","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Usps Physical Signature Required"},"usps_extra_services":{"code":"extraServices","meta":{"configurable":true},"type":"list","label":"Usps Extra Services"},"usps_shipping_filter":{"code":"shippingFilter","enum":["PRICE"],"meta":{"configurable":true},"type":"string","label":"Usps Shipping Filter"}},"readme":null},{"id":"veho","carrier_name":"veho","display_name":"Veho","integration_status":"beta","website":"","description":"Veho shipping integration for Karrio","documentation":"","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"label_type":{"code":"label_type","name":"label_type","required":false,"type":"string","label":"Label Type"},"delivery_max_datetime":{"code":"delivery_max_datetime","name":"delivery_max_datetime","required":false,"type":"string","label":"Delivery Max Datetime"},"label_date":{"code":"label_date","name":"label_date","required":false,"type":"string","label":"Label Date"}},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"zoom2u","carrier_name":"zoom2u","display_name":"Zoom2u","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_country_code":{"default":"AU","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"zoom2u_VIP":"VIP","zoom2u_3_hour":"3 hour","zoom2u_same_day":"Same day"},"shipping_options":{"purchase_order_number":{"code":"purchase_order_number","meta":{"configurable":true},"type":"string","label":"Purchase Order Number"},"ready_datetime":{"code":"ready_datetime","meta":{"configurable":true},"type":"string","label":"Ready Datetime"},"vehicle_type":{"code":"vehicle_type","meta":{"configurable":true},"type":"string","label":"Vehicle Type"},"pickup_notes":{"code":"pickup_notes","meta":{"configurable":true},"type":"string","label":"Pickup Notes"},"dropoff_notes":{"code":"dropoff_notes","meta":{"configurable":true},"type":"string","label":"Dropoff Notes"}},"readme":null}] \ No newline at end of file +[{"id":"aramex","carrier_name":"aramex","display_name":"Aramex","integration_status":"beta","website":"https://www.aramex.com/ae/en","description":"Aramex is the leading global logistics provider.","documentation":"https://www.aramex.com/us/en/developers-solution-center/aramex-apis","is_enabled":true,"capabilities":["Tracking"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_pin":{"name":"account_pin","required":true,"sensitive":false,"type":"string","label":"Account PIN"},"account_entity":{"name":"account_entity","required":true,"sensitive":false,"type":"string","label":"Account Entity"},"account_number":{"name":"account_number","required":true,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":true,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"asendia","carrier_name":"asendia","display_name":"Asendia","integration_status":"beta","website":"https://www.asendia.com","description":"Asendia international e-commerce shipping services","documentation":"https://www.asendia-sync.com/swagger-ui/index.html","is_enabled":true,"capabilities":["Manifest","Tracking","Paperless","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"customer_id":{"name":"customer_id","required":false,"sensitive":false,"type":"string","label":"Customer Id"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_type":{"code":"label_type","name":"label_type","required":false,"type":"string","label":"Label Type"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"asendia_epaq_standard":"EPAQSTD","asendia_epaq_standard_cup":"EPAQSTD_CUP","asendia_epaq_plus":"EPAQPLUS","asendia_epaq_plus_cup":"EPAQPLUS_CUP","asendia_epaq_elite":"EPAQELITE","asendia_epaq_elite_cup":"EPAQELITE_CUP","asendia_epaq_returns":"EPAQRET","asendia_epaq_returns_domestic":"EPAQRETDOM","asendia_country_road":"CROAD","asendia_country_road_plus":"CROADPLUS","asendia_priority":"PRIORITY","asendia_priority_tracked":"PRIORITYTRK"},"shipping_options":{"asendia_insurance":{"code":"insurance","meta":{"configurable":true},"type":"string","label":"Asendia Insurance"},"asendia_return_label":{"code":"return_label","meta":{"configurable":true},"type":"boolean","label":"Asendia Return Label"},"asendia_return_label_type":{"code":"return_label_type","meta":{"configurable":true},"type":"string","label":"Asendia Return Label Type"},"asendia_return_label_payment":{"code":"return_label_payment","meta":{"configurable":true},"type":"string","label":"Asendia Return Label Payment"},"asendia_sender_eori":{"code":"sender_eori","meta":{"configurable":true},"type":"string","label":"Asendia Sender Eori"},"asendia_seller_eori":{"code":"seller_eori","meta":{"configurable":true},"type":"string","label":"Asendia Seller Eori"},"asendia_sender_tax_id":{"code":"sender_tax_id","meta":{"configurable":true},"type":"string","label":"Asendia Sender Tax ID"},"asendia_receiver_tax_id":{"code":"receiver_tax_id","meta":{"configurable":true},"type":"string","label":"Asendia Receiver Tax ID"}},"readme":null},{"id":"asendia_us","carrier_name":"asendia_us","display_name":"Asendia US","integration_status":"beta","website":"https://www.asendia.com/","description":"deliver cross-border e-commerce solutions that are loved by your shoppers worldwide.","documentation":"https://a1api.asendiausa.com/swagger/index.html","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"default":"US","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"sub_account":{"code":"sub_account","name":"sub_account","required":false,"type":"string","label":"Sub Account"},"processing_location":{"code":"processing_location","name":"processing_location","required":false,"type":"string","label":"Processing Location"}},"shipping_services":{"asendia_us_e_com_tracked_ddp":"19","asendia_us_fully_tracked":"65","asendia_us_country_tracked":"66"},"shipping_options":{"asendia_us_processing_location":{"code":"asendia_us_processing_location","meta":{"configurable":true},"type":"string","label":"Processing Location"}},"readme":null},{"id":"australiapost","carrier_name":"australiapost","display_name":"Australia Post","integration_status":"beta","website":"https://auspost.com.au/","description":"Australia Post, formally known as the Australian Postal Corporation, is a Commonwealth government-owned corporation that provides postal services throughout Australia.","documentation":"https://developers.auspost.com.au/apis/shipping-and-tracking/reference","is_enabled":true,"capabilities":["Manifest","Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_number":{"name":"account_number","required":true,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"default":"AU","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"australiapost_parcel_post":"T28","australiapost_express_post":"E34","australiapost_parcel_post_signature":"3D55","australiapost_express_post_signature":"3J55","australiapost_intl_standard_pack_track":"PTI8","australiapost_intl_standard_with_signature":"PTI7","australiapost_intl_express_merch":"ECM8","australiapost_intl_express_docs":"ECD8","australiapost_eparcel_post_returns":"PR","australiapost_express_eparcel_post_returns":"XPR"},"shipping_options":{"australiapost_delivery_date":{"code":"DELIVERY_DATE","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Australia Post Delivery Date"},"australiapost_delivery_time_start":{"code":"DELIVERY_TIMES","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Australia Post Delivery Time Start"},"australiapost_pickup_date":{"code":"PICKUP_DATE","meta":{"configurable":true},"type":"string","label":"Australia Post Pickup Date"},"australiapost_pickup_time":{"code":"PICKUP_TIME","meta":{"configurable":true},"type":"string","label":"Australia Post Pickup Time"},"australiapost_identity_on_delivery":{"code":"IDENTITY_ON_DELIVERY","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Australia Post Identity On Delivery"},"australiapost_print_at_depot":{"code":"PRINT_AT_DEPOT","meta":{"configurable":true},"type":"boolean","label":"Australia Post Print At Depot"},"australiapost_transit_cover":{"code":"TRANSIT_COVER","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Australia Post Transit Cover"},"australiapost_sameday_identity_on_delivery":{"code":"SAMEDAY_IDENTITY_ON_DELIVERY","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Australia Post Sameday Identity On Delivery"},"australiapost_authority_to_leave":{"code":"authority_to_leave","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Australia Post Authority To Leave"},"australiapost_allow_partial_delivery":{"code":"allow_partial_delivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Australia Post Allow Partial Delivery"},"australiapost_contains_dangerous_goods":{"code":"contains_dangerous_goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Australia Post Contains Dangerous Goods"}},"readme":null},{"id":"boxknight","carrier_name":"boxknight","display_name":"BoxKnight","integration_status":"beta","website":"https://www.boxknight.com/","description":"Specializes in same-day delivery at affordable prices for e-commerce retailers. Our mission is to get packages to your customers when they are actually home and as quickly as possible.","documentation":"https://www.docs.boxknight.com/","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_country_code":{"default":"CA","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"boxknight_sameday":"SAMEDAY","boxknight_nextday":"NEXTDAY","boxknight_scheduled":"SCHEDULED"},"shipping_options":{"boxknight_signature_required":{"code":"signatureRequired","meta":{"configurable":true},"type":"boolean","label":"Signature Required"},"boxknight_merchant_display_name":{"code":"merchantDisplayName","meta":{"configurable":true},"type":"string","label":"Merchant Display Name"},"boxknight_notes":{"code":"notes","meta":{"configurable":true},"type":"string","label":"Notes"}},"readme":null},{"id":"bpost","carrier_name":"bpost","display_name":"Belgian Post","integration_status":"beta","website":"https://bpostgroup.com/","description":"The Belgian company responsible for the delivery of national and international mail.","documentation":"https://bpost.freshdesk.com/support/solutions/articles/4000037653-where-can-i-find-the-bpack-integration-manual-examples-and-xsd-s-","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"account_id":{"name":"account_id","required":true,"sensitive":false,"type":"string","label":"Account Id"},"passphrase":{"name":"passphrase","required":true,"sensitive":false,"type":"string","label":"Passphrase"},"account_country_code":{"default":"BE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"cost_center":{"code":"cost_center","name":"cost_center","required":false,"type":"string","label":"Cost Center"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"lang":{"code":"lang","enum":["FR","EN"],"name":"lang","required":false,"type":"string","label":"Lang"}},"shipping_services":{"bpack_24h_pro":"bpack 24h Pro","bpack_24h_business":"bpack 24h business","bpack_bus":"bpack Bus","bpack_pallet":"bpack Pallet","bpack_easy_retour":"bpack Easy Retour","bpack_xl":"bpack XL","bpack_bpost":"bpack@bpost","bpack_24_7":"bpack 24/7","bpack_world_business":"bpack World Business","bpack_world_express_pro":"bpack World Express Pro","bpack_europe_business":"bpack Europe Business","bpack_world_easy_return":"bpack World Easy Return","bpack_bpost_international":"bpack@bpost international","bpack_24_7_international":"bpack 24/7 international"},"shipping_options":{"bpost_info_distributed":{"code":"infoDistributed","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"bpost Info Distributed"},"bpost_info_next_day":{"code":"infoNextDay","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"bpost Info Next Day"},"bpost_info_reminder":{"code":"infoReminder","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"bpost Info Reminder"},"bpost_keep_me_informed":{"code":"keepMeInformed","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"bpost Keep Me Informed"},"bpost_automatic_second_presentation":{"code":"automaticSecondPresentation","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"bpost Automatic Second Presentation"},"bpost_fragile":{"code":"fragile","meta":{"configurable":true},"type":"boolean","label":"bpost Fragile"},"bpost_insured":{"code":"insured","meta":{"category":"INSURANCE","configurable":true},"type":"string","label":"bpost Insured"},"bpost_signed":{"code":"signed","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"bpost Signed"},"bpost_time_slot_delivery":{"code":"timeSlotDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"bpost Time Slot Delivery"},"bpost_saturday_delivery":{"code":"saturdayDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"bpost Saturday Delivery"},"bpost_sunday_delivery":{"code":"sundayDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"bpost Sunday Delivery"},"bpost_same_day_delivery":{"code":"sameDayDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"bpost Same Day Delivery"},"bpost_cod":{"code":"cod","meta":{"category":"COD","configurable":true},"type":"float","label":"bpost COD"},"bpost_preferred_delivery_window":{"code":"preferredDeliveryWindow","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"bpost Preferred Delivery Window"},"bpost_full_service":{"code":"fullService","meta":{"configurable":true},"type":"boolean","label":"bpost Full Service"},"bpost_door_step_plus_service":{"code":"doorStepPlusService","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"bpost Door Step Plus Service"},"bpost_ultra_late_in_evening_delivery":{"code":"ultraLateInEveningDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"bpost Ultra Late In Evening Delivery"},"bpost_pugo_id":{"code":"pugoId","meta":{"category":"PUDO","configurable":true},"type":"string","label":"bpost Pugo ID"},"bpost_pugo_name":{"code":"pugoName","meta":{"category":"PUDO","configurable":true},"type":"string","label":"bpost Pugo Name"},"bpost_pugo_address":{"code":"pugoAddress","meta":{"category":"PUDO","configurable":true},"type":"object","label":"bpost Pugo Address"},"bpost_parcels_depot_id":{"code":"parcelsDepotId","meta":{"category":"PUDO","configurable":true},"type":"string","label":"bpost Parcels Depot ID"},"bpost_parcels_depot_name":{"code":"parcelsDepotName","meta":{"category":"PUDO","configurable":true},"type":"string","label":"bpost Parcels Depot Name"},"bpost_parcels_depot_address":{"code":"parcelsDepotAddress","meta":{"category":"PUDO","configurable":true},"type":"object","label":"bpost Parcels Depot Address"},"bpost_parcel_return_instructions":{"code":"parcelReturnInstructions","meta":{"category":"RETURN","configurable":true},"type":"string","label":"bpost Parcel Return Instructions"}},"readme":null},{"id":"canadapost","carrier_name":"canadapost","display_name":"Canada Post","integration_status":"production-ready","website":"https://www.canadapost-postescanada.ca/cpc/en/home.page","description":"Mailing and shipping for Personal and Business.","documentation":"https://www.canadapost-postescanada.ca/information/app/drc/home","is_enabled":true,"capabilities":["Manifest","Rating","Pickup","Shipping","Tracking"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":true,"type":"string","label":"Password"},"customer_number":{"name":"customer_number","required":false,"sensitive":false,"type":"string","label":"Customer Number"},"contract_id":{"name":"contract_id","required":false,"sensitive":false,"type":"string","label":"Contract ID"},"language":{"default":"en","enum":["en","fr"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"CA","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"cost_center":{"code":"cost_center","name":"cost_center","required":false,"type":"string","label":"Cost Center"},"label_type":{"code":"label_type","enum":["PDF_4x6","PDF_8_5x11","ZPL_4x6"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"transmit_shipment_by_default":{"code":"transmit_shipment_by_default","name":"transmit_shipment_by_default","required":false,"type":"boolean","label":"Transmit Shipment By Default"}},"shipping_services":{"canadapost_regular_parcel":"DOM.RP","canadapost_expedited_parcel":"DOM.EP","canadapost_xpresspost":"DOM.XP","canadapost_xpresspost_certified":"DOM.XP.CERT","canadapost_priority":"DOM.PC","canadapost_library_books":"DOM.LIB","canadapost_expedited_parcel_usa":"USA.EP","canadapost_priority_worldwide_envelope_usa":"USA.PW.ENV","canadapost_priority_worldwide_pak_usa":"USA.PW.PAK","canadapost_priority_worldwide_parcel_usa":"USA.PW.PARCEL","canadapost_small_packet_usa_air":"USA.SP.AIR","canadapost_tracked_packet_usa":"USA.TP","canadapost_tracked_packet_usa_lvm":"USA.TP.LVM","canadapost_xpresspost_usa":"USA.XP","canadapost_xpresspost_international":"INT.XP","canadapost_international_parcel_air":"INT.IP.AIR","canadapost_international_parcel_surface":"INT.IP.SURF","canadapost_priority_worldwide_envelope_intl":"INT.PW.ENV","canadapost_priority_worldwide_pak_intl":"INT.PW.PAK","canadapost_priority_worldwide_parcel_intl":"INT.PW.PARCEL","canadapost_small_packet_international_air":"INT.SP.AIR","canadapost_small_packet_international_surface":"INT.SP.SURF","canadapost_tracked_packet_international":"INT.TP"},"shipping_options":{"canadapost_signature":{"code":"SO","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Canada Post Signature"},"canadapost_coverage":{"code":"COV","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Canada Post Coverage"},"canadapost_collect_on_delivery":{"code":"COD","meta":{"category":"COD","configurable":true},"type":"float","label":"Canada Post Collect On Delivery"},"canadapost_proof_of_age_required_18":{"code":"PA18","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Canada Post Proof Of Age Required 18"},"canadapost_proof_of_age_required_19":{"code":"PA19","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Canada Post Proof Of Age Required 19"},"canadapost_card_for_pickup":{"code":"HFP","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Canada Post Card For Pickup"},"canadapost_do_not_safe_drop":{"code":"DNS","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Canada Post Do Not Safe Drop"},"canadapost_leave_at_door":{"code":"LAD","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Canada Post Leave At Door"},"canadapost_deliver_to_post_office":{"code":"D2PO","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Canada Post Deliver To Post Office"},"canadapost_return_at_senders_expense":{"code":"RASE","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Canada Post Return At Senders Expense"},"canadapost_return_to_sender":{"code":"RTS","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Canada Post Return To Sender"},"canadapost_abandon":{"code":"ABAN","meta":{"configurable":true},"type":"boolean","label":"Canada Post Abandon"},"canadapost_cost_center":{"code":"cost-centre","meta":{"configurable":true},"type":"string","label":"Canada Post Cost Center"},"canadapost_submit_shipment":{"code":"transmit-shipment","meta":{"configurable":true},"type":"boolean","label":"Canada Post Submit Shipment"}},"readme":null},{"id":"canpar","carrier_name":"canpar","display_name":"Canpar","integration_status":"beta","website":"https://www.canpar.com/","description":"Everything Canpar Express does-product development, technological upgrades, customer service-is shaped and tailored to transporting our customers' parcels efficiently and cost-effectively.","documentation":"https://www.canpar.com/en/solutions/ecommerce_tools.htm","is_enabled":true,"capabilities":["Tracking","Pickup","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"language":{"default":"en","enum":["en","fr"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"CA","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"chronopost","carrier_name":"chronopost","display_name":"Chronopost","integration_status":"beta","website":"https://www.chronopost.fr/en","description":"Provides express shipping and delivery service both domestically and internationally.","documentation":"https://www.chrono-api.fr/docs/api/","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"account_number":{"name":"account_number","required":true,"sensitive":false,"type":"string","label":"Account Number"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"id_emit":{"default":"CHRFR","name":"id_emit","required":false,"sensitive":false,"type":"string","label":"Id Emit"},"language":{"default":"en_GB","enum":["en_GB","fr_FR"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"FR","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"chronopost_retrait_bureau":"0","chronopost_13":"1","chronopost_10":"2","chronopost_18":"16","chronopost_relais":"86","chronopost_express_international":"17","chronopost_premium_international":"37","chronopost_classic_international":"44"},"shipping_options":{"chronopost_delivery_on_monday":{"code":"1","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Chronopost Delivery On Monday"},"chronopost_delivery_on_saturday":{"code":"6","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Chronopost Delivery On Saturday"},"chronopost_delivery_normal":{"code":"0","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Chronopost Delivery Normal"}},"readme":null},{"id":"colissimo","carrier_name":"colissimo","display_name":"Colissimo","integration_status":"beta","website":"https://www.colissimo.entreprise.laposte.fr/en","description":"Envoi de colis en France et dans le monde entier, livraison à domicile ou en point de retrait, Colissimo vous offre un choix de services qui facilitent votre quotidien.","documentation":"https://www.colissimo.entreprise.laposte.fr/en/tools-and-services","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"contract_number":{"name":"contract_number","required":true,"sensitive":false,"type":"string","label":"Contract Number"},"laposte_api_key":{"name":"laposte_api_key","required":false,"sensitive":false,"type":"string","label":"Laposte Api Key"},"account_country_code":{"default":"FR","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"lang":{"code":"lang","enum":["FR","EN"],"name":"lang","required":false,"type":"string","label":"Lang"}},"shipping_services":{"colissimo_home_without_signature":"DOM","colissimo_home_with_signature":"DOS","colissimo_eco_france":"CECO","colissimo_return_france":"CORE","colissimo_flash_without_signature":"COLR","colissimo_flash_with_signature":"J+1","colissimo_oversea_home_without_signature":"COM","colissimo_oversea_home_with_signature":"CDS","colissimo_eco_om_without_signature":"ECO","colissimo_eco_om_with_signature":"ECOS","colissimo_retour_om":"CORI","colissimo_return_international_from_france":"CORF","colissimo_economical_big_export_offer":"ACCI","colissimo_out_of_home_national_international":"HD"},"shipping_options":{"colissimo_insurance_value":{"code":"insuranceValue","meta":{"configurable":true},"type":"float","label":"Insurance Value"},"colissimo_cod_amount":{"code":"CODAmount","meta":{"configurable":true},"type":"float","label":"Cod Amount"},"colissimo_return_receipt":{"code":"returnReceipt","meta":{"configurable":true},"type":"boolean","label":"Return Receipt"},"colissimo_ftd":{"code":"ftd","meta":{"configurable":true},"type":"boolean","label":"Ftd"},"colissimo_non_machinable":{"code":"nonMachinable","meta":{"configurable":true},"type":"boolean","label":"Non Machinable"},"colissimo_ddp":{"code":"ddp","meta":{"configurable":true},"type":"boolean","label":"Ddp"},"colissimo_instructions":{"code":"instructions","meta":{"configurable":true},"type":"string","label":"Instructions"}},"readme":null},{"id":"dhl_express","carrier_name":"dhl_express","display_name":"DHL Express","integration_status":"production-ready","website":"https://www.dhl.com/ca-en/home/express.html","description":"When your shipment needs to be there fast, choose the International Specialists for quick, reliable expedited shipments to and from more than 220 countries and territories.","documentation":"https://developer.dhl.com/api-reference/dhl-express-xml","is_enabled":true,"capabilities":["Rating","Pickup","Paperless","Shipping","Tracking"],"connection_fields":{"site_id":{"name":"site_id","required":true,"sensitive":false,"type":"string","label":"Site ID"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_type":{"code":"label_type","enum":["PDF_6x4","PDF_8x4","PDF_8x4_A4","PDF_6x4_A4","PDF_8x4_CI","PDF_8x4_RU_A4","ZPL_8x4","ZPL_6x4","ZPL_8x4_CI"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"software_name":{"code":"software_name","name":"software_name","required":false,"type":"string","label":"Software Name"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"skip_service_filter":{"code":"skip_service_filter","name":"skip_service_filter","required":false,"type":"boolean","label":"Skip Service Filter"}},"shipping_services":{"dhl_logistics_services":"0","dhl_domestic_express_12_00":"1","dhl_express_choice":"2","dhl_express_choice_nondoc":"3","dhl_jetline":"4","dhl_sprintline":"5","dhl_air_capacity_sales":"6","dhl_express_easy":"7","dhl_express_easy_nondoc":"8","dhl_parcel_product":"9","dhl_accounting":"A","dhl_breakbulk_express":"B","dhl_medical_express":"C","dhl_express_worldwide_doc":"D","dhl_express_9_00_nondoc":"E","dhl_freight_worldwide_nondoc":"F","dhl_economy_select_domestic":"G","dhl_economy_select_nondoc":"H","dhl_express_domestic_9_00":"I","dhl_jumbo_box_nondoc":"J","dhl_express_9_00":"K","dhl_express_10_30":"L","dhl_express_10_30_nondoc":"M","dhl_express_domestic":"N","dhl_express_domestic_10_30":"O","dhl_express_worldwide_nondoc":"P","dhl_medical_express_nondoc":"Q","dhl_globalmail":"R","dhl_same_day":"S","dhl_express_12_00":"T","dhl_express_worldwide":"U","dhl_parcel_product_nondoc":"V","dhl_economy_select":"W","dhl_express_envelope":"X","dhl_express_12_00_nondoc":"Y","dhl_destination_charges":"Z","dhl_express_all":null},"shipping_options":{"dhl_logistics_services":{"code":"0A","meta":{"configurable":true},"type":"boolean","label":"Dhl Logistics Services"},"dhl_mailroom_management":{"code":"0B","meta":{"configurable":true},"type":"boolean","label":"Dhl Mailroom Management"},"dhl_pallet_administration":{"code":"0C","meta":{"configurable":true},"type":"boolean","label":"Dhl Pallet Administration"},"dhl_warehousing":{"code":"0D","meta":{"configurable":true},"type":"boolean","label":"Dhl Warehousing"},"dhl_express_logistics_centre":{"code":"0E","meta":{"configurable":true},"type":"boolean","label":"Logistics Centre"},"dhl_strategic_parts_centre":{"code":"0F","meta":{"configurable":true},"type":"boolean","label":"Dhl Strategic Parts Centre"},"dhl_local_distribution_centre":{"code":"0G","meta":{"configurable":true},"type":"boolean","label":"Dhl Local Distribution Centre"},"dhl_terminal_handling":{"code":"0H","meta":{"configurable":true},"type":"boolean","label":"Dhl Terminal Handling"},"dhl_cross_docking":{"code":"0I","meta":{"configurable":true},"type":"boolean","label":"Dhl Cross Docking"},"dhl_inventory_management":{"code":"0J","meta":{"configurable":true},"type":"boolean","label":"Dhl Inventory Management"},"dhl_loading_unloading":{"code":"0K","meta":{"configurable":true},"type":"boolean","label":"Dhl Loading Unloading"},"dhl_product_kitting":{"code":"0L","meta":{"configurable":true},"type":"boolean","label":"Dhl Product Kitting"},"dhl_priority_account_desk":{"code":"0M","meta":{"configurable":true},"type":"boolean","label":"Dhl Priority Account Desk"},"dhl_document_archiving":{"code":"0N","meta":{"configurable":true},"type":"boolean","label":"Dhl Document Archiving"},"dhl_saturday_delivery":{"code":"AA","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Dhl Saturday Delivery"},"dhl_saturday_pickup":{"code":"AB","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Dhl Saturday Pickup"},"dhl_holiday_delivery":{"code":"AC","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Dhl Holiday Delivery"},"dhl_holiday_pickup":{"code":"AD","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Dhl Holiday Pickup"},"dhl_domestic_saturday_delivery":{"code":"AG","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Dhl Domestic Saturday Delivery"},"dhl_standard":{"code":"BA","meta":{"configurable":true},"type":"boolean","label":"Dhl Standard"},"dhl_globalmail_item":{"code":"BB","meta":{"configurable":true},"type":"boolean","label":"Dhl Globalmail Item"},"dhl_letter":{"code":"BC","meta":{"configurable":true},"type":"boolean","label":"Dhl Letter"},"dhl_packet":{"code":"BD","meta":{"configurable":true},"type":"boolean","label":"Dhl Packet"},"dhl_letter_plus":{"code":"BE","meta":{"configurable":true},"type":"boolean","label":"Dhl Letter Plus"},"dhl_packet_plus":{"code":"BF","meta":{"configurable":true},"type":"boolean","label":"Dhl Packet Plus"},"dhl_elevated_risk":{"code":"CA","meta":{"configurable":true},"type":"boolean","label":"Dhl Elevated Risk"},"dhl_restricted_destination":{"code":"CB","meta":{"configurable":true},"type":"boolean","label":"Dhl Restricted Destination"},"dhl_security_validation":{"code":"CC","meta":{"configurable":true},"type":"boolean","label":"Dhl Security Validation"},"dhl_secure_protection":{"code":"CD","meta":{"configurable":true},"type":"boolean","label":"Dhl Secure Protection"},"dhl_proof_of_identity":{"code":"CE","meta":{"configurable":true},"type":"boolean","label":"Dhl Proof Of Identity"},"dhl_secure_storage":{"code":"CF","meta":{"configurable":true},"type":"boolean","label":"Dhl Secure Storage"},"dhl_diplomatic_material":{"code":"CG","meta":{"configurable":true},"type":"boolean","label":"Dhl Diplomatic Material"},"dhl_smart_sensor":{"code":"CH","meta":{"configurable":true},"type":"boolean","label":"Dhl Smart Sensor"},"dhl_visa_program":{"code":"CI","meta":{"configurable":true},"type":"boolean","label":"Dhl Visa Program"},"dhl_onboard_courier":{"code":"CJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Onboard Courier"},"dhl_secure_safebox":{"code":"CK","meta":{"configurable":true},"type":"boolean","label":"Dhl Secure Safebox"},"dhl_smart_sentry":{"code":"CL","meta":{"configurable":true},"type":"boolean","label":"Dhl Smart Sentry"},"dhl_split_duties_and_tax":{"code":"DC","meta":{"configurable":true},"type":"boolean","label":"Dhl Split Duties And Tax"},"dhl_duties_and_taxes_paid":{"code":"DD","meta":{"configurable":true},"type":"boolean","label":"Dhl Duties And Taxes Paid"},"dhl_receiver_paid":{"code":"DE","meta":{"configurable":true},"type":"boolean","label":"Dhl Receiver Paid"},"dhl_duties_and_taxes_unpaid":{"code":"DS","meta":{"configurable":true},"type":"boolean","label":"Dhl Duties And Taxes Unpaid"},"dhl_import_billing":{"code":"DT","meta":{"configurable":true},"type":"boolean","label":"Dhl Import Billing"},"dhl_importer_of_record":{"code":"DU","meta":{"configurable":true},"type":"boolean","label":"Dhl Importer Of Record"},"dhl_go_green_carbon_neutral":{"code":"EA","meta":{"configurable":true},"type":"boolean","label":"Dhl Go Green Carbon Neutral"},"dhl_go_green_carbon_footprint":{"code":"EB","meta":{"configurable":true},"type":"boolean","label":"Dhl Go Green Carbon Footprint"},"dhl_go_green_carbon_estimate":{"code":"EC","meta":{"configurable":true},"type":"boolean","label":"Dhl Go Green Carbon Estimate"},"dhl_fuel_surcharge_b":{"code":"FB","meta":{"configurable":true},"type":"boolean","label":"Dhl Fuel Surcharge B"},"dhl_fuel_surcharge_c":{"code":"FC","meta":{"configurable":true},"type":"boolean","label":"Dhl Fuel Surcharge C"},"dhl_fuel_surcharge_f":{"code":"FF","meta":{"configurable":true},"type":"boolean","label":"Dhl Fuel Surcharge F"},"dhl_smartphone_box":{"code":"GA","meta":{"configurable":true},"type":"boolean","label":"Dhl Smartphone Box"},"dhl_laptop_box":{"code":"GB","meta":{"configurable":true},"type":"boolean","label":"Dhl Laptop Box"},"dhl_bottle_box":{"code":"GC","meta":{"configurable":true},"type":"boolean","label":"Dhl Bottle Box"},"dhl_repacking":{"code":"GD","meta":{"configurable":true},"type":"boolean","label":"Dhl Repacking"},"dhl_tablet_box":{"code":"GE","meta":{"configurable":true},"type":"boolean","label":"Dhl Tablet Box"},"dhl_filler_material":{"code":"GF","meta":{"configurable":true},"type":"boolean","label":"Dhl Filler Material"},"dhl_packaging":{"code":"GG","meta":{"configurable":true},"type":"boolean","label":"Dhl Packaging"},"dhl_diplomatic_bag":{"code":"GH","meta":{"configurable":true},"type":"boolean","label":"Dhl Diplomatic Bag"},"dhl_pallet_box":{"code":"GI","meta":{"configurable":true},"type":"boolean","label":"Dhl Pallet Box"},"dhl_lock_box":{"code":"GJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Lock Box"},"dhl_lithium_ion_pi965_section_ii":{"code":"HB","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Lithium Ion Pi965 Section Ii"},"dhl_dry_ice_un1845":{"code":"HC","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Dry Ice Un1845"},"dhl_lithium_ion_pi965_966_section_ii":{"code":"HD","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Lithium Ion Pi965 966 Section Ii"},"dhl_dangerous_goods":{"code":"HE","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Dangerous Goods"},"dhl_perishable_cargo":{"code":"HG","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Perishable Cargo"},"dhl_excepted_quantity":{"code":"HH","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Excepted Quantity"},"dhl_spill_cleaning":{"code":"HI","meta":{"configurable":true},"type":"boolean","label":"Dhl Spill Cleaning"},"dhl_consumer_commodities":{"code":"HK","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Consumer Commodities"},"dhl_limited_quantities_adr":{"code":"HL","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Limited Quantities Adr"},"dhl_lithium_metal_pi969_section_ii":{"code":"HM","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Lithium Metal Pi969 Section Ii"},"dhl_adr_load_exemption":{"code":"HN","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Adr Load Exemption"},"dhl_lithium_ion_pi967_section_ii":{"code":"HV","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Lithium Ion Pi967 Section Ii"},"dhl_lithium_metal_pi970_section_ii":{"code":"HW","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Lithium Metal Pi970 Section Ii"},"dhl_biological_un3373":{"code":"HY","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Dhl Biological Un3373"},"dhl_extended_liability":{"code":"IB","meta":{"category":"INSURANCE","configurable":true},"type":"boolean","label":"Dhl Extended Liability"},"dhl_contract_insurance":{"code":"IC","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Dhl Contract Insurance"},"dhl_shipment_insurance":{"code":"II","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Dhl Shipment Insurance"},"dhl_delivery_notification":{"code":"JA","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"Dhl Delivery Notification"},"dhl_pickup_notification":{"code":"JC","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"Dhl Pickup Notification"},"dhl_proactive_tracking":{"code":"JD","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"Dhl Proactive Tracking"},"dhl_performance_reporting":{"code":"JE","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"Dhl Performance Reporting"},"dhl_prealert_notification":{"code":"JY","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"Dhl Prealert Notification"},"dhl_change_of_billing":{"code":"KA","meta":{"configurable":true},"type":"boolean","label":"Dhl Change Of Billing"},"dhl_cash_on_delivery":{"code":"KB","meta":{"category":"COD","configurable":true},"type":"float","label":"Dhl Cash On Delivery"},"dhl_printed_invoice":{"code":"KD","meta":{"configurable":true},"type":"boolean","label":"Dhl Printed Invoice"},"dhl_waybill_copy":{"code":"KE","meta":{"configurable":true},"type":"boolean","label":"Dhl Waybill Copy"},"dhl_import_paperwork":{"code":"KF","meta":{"configurable":true},"type":"boolean","label":"Dhl Import Paperwork"},"dhl_payment_on_pickup":{"code":"KY","meta":{"configurable":true},"type":"boolean","label":"Dhl Payment On Pickup"},"dhl_shipment_intercept":{"code":"LA","meta":{"configurable":true},"type":"boolean","label":"Dhl Shipment Intercept"},"dhl_shipment_redirect":{"code":"LC","meta":{"configurable":true},"type":"boolean","label":"Dhl Shipment Redirect"},"dhl_storage_at_facility":{"code":"LE","meta":{"configurable":true},"type":"boolean","label":"Dhl Storage At Facility"},"dhl_cold_storage":{"code":"LG","meta":{"configurable":true},"type":"boolean","label":"Dhl Cold Storage"},"dhl_specific_routing":{"code":"LH","meta":{"configurable":true},"type":"boolean","label":"Dhl Specific Routing"},"dhl_service_recovery":{"code":"LV","meta":{"configurable":true},"type":"boolean","label":"Dhl Service Recovery"},"dhl_alternative_address":{"code":"LW","meta":{"configurable":true},"type":"boolean","label":"Dhl Alternative Address"},"dhl_hold_for_collection":{"code":"LX","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Dhl Hold For Collection"},"dhl_address_correction_a":{"code":"MA","meta":{"configurable":true},"type":"boolean","label":"Dhl Address Correction A"},"dhl_address_correction_b":{"code":"MB","meta":{"configurable":true},"type":"boolean","label":"Dhl Address Correction B"},"dhl_neutral_delivery":{"code":"NN","meta":{"configurable":true},"type":"boolean","label":"Dhl Neutral Delivery"},"dhl_remote_area_pickup":{"code":"OB","meta":{"configurable":true},"type":"boolean","label":"Dhl Remote Area Pickup"},"dhl_remote_area_delivery_c":{"code":"OC","meta":{"configurable":true},"type":"boolean","label":"Dhl Remote Area Delivery C"},"dhl_out_of_service_area":{"code":"OE","meta":{"configurable":true},"type":"boolean","label":"Dhl Out Of Service Area"},"dhl_remote_area_delivery_o":{"code":"OO","meta":{"configurable":true},"type":"boolean","label":"Dhl Remote Area Delivery O"},"dhl_shipment_preparation":{"code":"PA","meta":{"configurable":true},"type":"boolean","label":"Dhl Shipment Preparation"},"dhl_shipment_labeling":{"code":"PB","meta":{"configurable":true},"type":"boolean","label":"Dhl Shipment Labeling"},"dhl_shipment_consolidation":{"code":"PC","meta":{"configurable":true},"type":"boolean","label":"Dhl Shipment Consolidation"},"dhl_relabeling_data_entry":{"code":"PD","meta":{"configurable":true},"type":"boolean","label":"Dhl Relabeling Data Entry"},"dhl_preprinted_waybill":{"code":"PE","meta":{"configurable":true},"type":"boolean","label":"Dhl Preprinted Waybill"},"dhl_piece_labelling":{"code":"PS","meta":{"configurable":true},"type":"boolean","label":"Dhl Piece Labelling"},"dhl_data_staging_03":{"code":"PT","meta":{"configurable":true},"type":"boolean","label":"Dhl Data Staging 03"},"dhl_data_staging_06":{"code":"PU","meta":{"configurable":true},"type":"boolean","label":"Dhl Data Staging 06"},"dhl_data_staging_12":{"code":"PV","meta":{"configurable":true},"type":"boolean","label":"Dhl Data Staging 12"},"dhl_data_staging_24":{"code":"PW","meta":{"configurable":true},"type":"boolean","label":"Dhl Data Staging 24"},"dhl_standard_pickup":{"code":"PX","meta":{"configurable":true},"type":"boolean","label":"Dhl Standard Pickup"},"dhl_scheduled_pickup":{"code":"PY","meta":{"configurable":true},"type":"boolean","label":"Dhl Scheduled Pickup"},"dhl_dedicated_pickup":{"code":"QA","meta":{"configurable":true},"type":"boolean","label":"Dhl Dedicated Pickup"},"dhl_early_pickup":{"code":"QB","meta":{"configurable":true},"type":"boolean","label":"Dhl Early Pickup"},"dhl_late_pickup":{"code":"QD","meta":{"configurable":true},"type":"boolean","label":"Dhl Late Pickup"},"dhl_residential_pickup":{"code":"QE","meta":{"configurable":true},"type":"boolean","label":"Dhl Residential Pickup"},"dhl_loading_waiting":{"code":"QF","meta":{"configurable":true},"type":"boolean","label":"Dhl Loading Waiting"},"dhl_bypass_injection":{"code":"QH","meta":{"configurable":true},"type":"boolean","label":"Dhl Bypass Injection"},"dhl_direct_injection":{"code":"QI","meta":{"configurable":true},"type":"boolean","label":"Dhl Direct Injection"},"dhl_drop_off_at_facility":{"code":"QY","meta":{"configurable":true},"type":"boolean","label":"Dhl Drop Off At Facility"},"dhl_delivery_signature":{"code":"SA","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl Delivery Signature"},"dhl_content_signature":{"code":"SB","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl Content Signature"},"dhl_named_signature":{"code":"SC","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl Named Signature"},"dhl_adult_signature":{"code":"SD","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl Adult Signature"},"dhl_contract_signature":{"code":"SE","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl Contract Signature"},"dhl_alternative_signature":{"code":"SW","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl Alternative Signature"},"dhl_no_signature_required":{"code":"SX","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Dhl No Signature Required"},"dhl_dedicated_delivery":{"code":"TA","meta":{"configurable":true},"type":"boolean","label":"Dhl Dedicated Delivery"},"dhl_early_delivery":{"code":"TB","meta":{"configurable":true},"type":"boolean","label":"Dhl Early Delivery"},"dhl_time_window_delivery":{"code":"TC","meta":{"configurable":true},"type":"boolean","label":"Dhl Time Window Delivery"},"dhl_evening_delivery":{"code":"TD","meta":{"configurable":true},"type":"boolean","label":"Dhl Evening Delivery"},"dhl_delivery_on_appointment":{"code":"TE","meta":{"configurable":true},"type":"boolean","label":"Dhl Delivery On Appointment"},"dhl_return_undeliverable":{"code":"TG","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Dhl Return Undeliverable"},"dhl_swap_delivery":{"code":"TH","meta":{"configurable":true},"type":"boolean","label":"Dhl Swap Delivery"},"dhl_unloading_waiting":{"code":"TJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Unloading Waiting"},"dhl_residential_delivery":{"code":"TK","meta":{"configurable":true},"type":"boolean","label":"Dhl Residential Delivery"},"dhl_repeat_delivery":{"code":"TN","meta":{"configurable":true},"type":"boolean","label":"Dhl Repeat Delivery"},"dhl_alternative_date":{"code":"TT","meta":{"configurable":true},"type":"boolean","label":"Dhl Alternative Date"},"dhl_no_partial_delivery":{"code":"TU","meta":{"configurable":true},"type":"boolean","label":"Dhl No Partial Delivery"},"dhl_service_point_24_7":{"code":"TV","meta":{"configurable":true},"type":"boolean","label":"Dhl Service Point 24 7"},"dhl_pre_9_00":{"code":"TW","meta":{"configurable":true},"type":"boolean","label":"Dhl Pre 9 00"},"dhl_pre_10_30":{"code":"TX","meta":{"configurable":true},"type":"boolean","label":"Dhl Pre 10 30"},"dhl_pre_12_00":{"code":"TY","meta":{"configurable":true},"type":"boolean","label":"Dhl Pre 12 00"},"dhl_thermo_packaging":{"code":"UA","meta":{"configurable":true},"type":"boolean","label":"Dhl Thermo Packaging"},"dhl_ambient_vialsafe":{"code":"UB","meta":{"configurable":true},"type":"boolean","label":"Dhl Ambient Vialsafe"},"dhl_ambient_non_insulated":{"code":"UC","meta":{"configurable":true},"type":"boolean","label":"Dhl Ambient Non Insulated"},"dhl_ambient_insulated":{"code":"UD","meta":{"configurable":true},"type":"boolean","label":"Dhl Ambient Insulated"},"dhl_ambient_extreme":{"code":"UE","meta":{"configurable":true},"type":"boolean","label":"Dhl Ambient Extreme"},"dhl_chilled_box_s":{"code":"UF","meta":{"configurable":true},"type":"boolean","label":"Dhl Chilled Box S"},"dhl_chilled_box_m":{"code":"UG","meta":{"configurable":true},"type":"boolean","label":"Dhl Chilled Box M"},"dhl_chilled_box_l":{"code":"UH","meta":{"configurable":true},"type":"boolean","label":"Dhl Chilled Box L"},"dhl_frozen_no_ice_s":{"code":"UI","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen No Ice S"},"dhl_frozen_no_ice_m":{"code":"UJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen No Ice M"},"dhl_frozen_no_ice_l":{"code":"UK","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen No Ice L"},"dhl_frozen_ice_sticks_s":{"code":"UL","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Sticks S"},"dhl_frozen_ice_sticks_m":{"code":"UM","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Sticks M"},"dhl_frozen_ice_sticks_l":{"code":"UN","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Sticks L"},"dhl_frozen_ice_plates_s":{"code":"UO","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Plates S"},"dhl_frozen_ice_plates_m":{"code":"UP","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Plates M"},"dhl_frozen_ice_plates_l":{"code":"UQ","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Plates L"},"dhl_combination_no_ice":{"code":"UR","meta":{"configurable":true},"type":"boolean","label":"Dhl Combination No Ice"},"dhl_combination_dry_ice":{"code":"US","meta":{"configurable":true},"type":"boolean","label":"Dhl Combination Dry Ice"},"dhl_frozen_ice_sticks_e":{"code":"UT","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Sticks E"},"dhl_frozen_ice_plates_e":{"code":"UV","meta":{"configurable":true},"type":"boolean","label":"Dhl Frozen Ice Plates E"},"dhl_customer_tcp_1":{"code":"UW","meta":{"configurable":true},"type":"boolean","label":"Dhl Customer Tcp 1"},"dhl_thermo_accessories":{"code":"VA","meta":{"configurable":true},"type":"boolean","label":"Dhl Thermo Accessories"},"dhl_absorbent_sleeve":{"code":"VB","meta":{"configurable":true},"type":"boolean","label":"Dhl Absorbent Sleeve"},"dhl_cooland_wrap":{"code":"VC","meta":{"configurable":true},"type":"boolean","label":"Dhl Cooland Wrap"},"dhl_dry_ice_supplies":{"code":"VD","meta":{"configurable":true},"type":"boolean","label":"Dhl Dry Ice Supplies"},"dhl_pressure_bag_s":{"code":"VE","meta":{"configurable":true},"type":"boolean","label":"Dhl Pressure Bag S"},"dhl_pressure_bag_m":{"code":"VF","meta":{"configurable":true},"type":"boolean","label":"Dhl Pressure Bag M"},"dhl_pressure_bag_l":{"code":"VG","meta":{"configurable":true},"type":"boolean","label":"Dhl Pressure Bag L"},"dhl_informal_clearance":{"code":"WA","meta":{"configurable":true},"type":"boolean","label":"Dhl Informal Clearance"},"dhl_formal_clearance":{"code":"WB","meta":{"configurable":true},"type":"boolean","label":"Dhl Formal Clearance"},"dhl_payment_deferment":{"code":"WC","meta":{"configurable":true},"type":"boolean","label":"Dhl Payment Deferment"},"dhl_clearance_authorization":{"code":"WD","meta":{"configurable":true},"type":"boolean","label":"Dhl Clearance Authorization"},"dhl_multiline_entry":{"code":"WE","meta":{"configurable":true},"type":"boolean","label":"Dhl Multiline Entry"},"dhl_post_clearance_modification":{"code":"WF","meta":{"configurable":true},"type":"boolean","label":"Dhl Post Clearance Modification"},"dhl_handover_to_broker":{"code":"WG","meta":{"configurable":true},"type":"boolean","label":"Dhl Handover To Broker"},"dhl_physical_intervention":{"code":"WH","meta":{"configurable":true},"type":"boolean","label":"Dhl Physical Intervention"},"dhl_bio_phyto_veterinary_controls":{"code":"WI","meta":{"configurable":true},"type":"boolean","label":"Dhl Bio Phyto Veterinary Controls"},"dhl_obtaining_permits_and_licences":{"code":"WJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Obtaining Permits And Licences"},"dhl_bonded_storage":{"code":"WK","meta":{"configurable":true},"type":"boolean","label":"Dhl Bonded Storage"},"dhl_bonded_transit_documents":{"code":"WL","meta":{"configurable":true},"type":"boolean","label":"Dhl Bonded Transit Documents"},"dhl_temporary_import_export":{"code":"WM","meta":{"configurable":true},"type":"boolean","label":"Dhl Temporary Import Export"},"dhl_under_bond_guarantee":{"code":"WN","meta":{"configurable":true},"type":"boolean","label":"Dhl Under Bond Guarantee"},"dhl_export_declaration":{"code":"WO","meta":{"configurable":true},"type":"boolean","label":"Dhl Export Declaration"},"dhl_exporter_validation":{"code":"WP","meta":{"configurable":true},"type":"boolean","label":"Dhl Exporter Validation"},"dhl_certificate_of_origin":{"code":"WQ","meta":{"configurable":true},"type":"boolean","label":"Dhl Certificate Of Origin"},"dhl_document_translation":{"code":"WR","meta":{"configurable":true},"type":"boolean","label":"Dhl Document Translation"},"dhl_personal_effects":{"code":"WS","meta":{"configurable":true},"type":"boolean","label":"Dhl Personal Effects"},"dhl_paperless_trade":{"code":"WY","meta":{"category":"PAPERLESS","configurable":true},"type":"boolean","label":"Dhl Paperless Trade"},"dhl_import_export_taxes":{"code":"XB","meta":{"configurable":true},"type":"boolean","label":"Dhl Import Export Taxes"},"dhl_unrecoverable_origin_tax":{"code":"XC","meta":{"configurable":true},"type":"boolean","label":"Dhl Unrecoverable Origin Tax"},"dhl_quarantine_inspection":{"code":"XD","meta":{"configurable":true},"type":"boolean","label":"Dhl Quarantine Inspection"},"dhl_merchandise_process":{"code":"XE","meta":{"configurable":true},"type":"boolean","label":"Dhl Merchandise Process"},"dhl_domestic_postal_tax":{"code":"XF","meta":{"configurable":true},"type":"boolean","label":"Dhl Domestic Postal Tax"},"dhl_tier_two_tax":{"code":"XG","meta":{"configurable":true},"type":"boolean","label":"Dhl Tier Two Tax"},"dhl_tier_three_tax":{"code":"XH","meta":{"configurable":true},"type":"boolean","label":"Dhl Tier Three Tax"},"dhl_import_penalty":{"code":"XI","meta":{"configurable":true},"type":"boolean","label":"Dhl Import Penalty"},"dhl_cargo_zone_process":{"code":"XJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Cargo Zone Process"},"dhl_import_export_duties":{"code":"XX","meta":{"configurable":true},"type":"boolean","label":"Dhl Import Export Duties"},"dhl_premium_09_00":{"code":"Y1","meta":{"configurable":true},"type":"boolean","label":"Dhl Premium 09 00"},"dhl_premium_10_30":{"code":"Y2","meta":{"configurable":true},"type":"boolean","label":"Dhl Premium 10 30"},"dhl_premium_12_00":{"code":"Y3","meta":{"configurable":true},"type":"boolean","label":"Dhl Premium 12 00"},"dhl_over_sized_piece_b":{"code":"YB","meta":{"configurable":true},"type":"boolean","label":"Dhl Over Sized Piece B"},"dhl_over_handled_piece_c":{"code":"YC","meta":{"configurable":true},"type":"boolean","label":"Dhl Over Handled Piece C"},"dhl_multipiece_shipment":{"code":"YE","meta":{"configurable":true},"type":"boolean","label":"Dhl Multipiece Shipment"},"dhl_over_weight_piece_f":{"code":"YF","meta":{"configurable":true},"type":"boolean","label":"Dhl Over Weight Piece F"},"dhl_over_sized_piece_g":{"code":"YG","meta":{"configurable":true},"type":"boolean","label":"Dhl Over Sized Piece G"},"dhl_over_handled_piece_h":{"code":"YH","meta":{"configurable":true},"type":"boolean","label":"Dhl Over Handled Piece H"},"dhl_premium_9_00_i":{"code":"YI","meta":{"configurable":true},"type":"boolean","label":"Dhl Premium 9 00 I"},"dhl_premium_10_30_j":{"code":"YJ","meta":{"configurable":true},"type":"boolean","label":"Dhl Premium 10 30 J"},"dhl_premium_12_00_k":{"code":"YK","meta":{"configurable":true},"type":"boolean","label":"Dhl Premium 12 00 K"},"dhl_paket_shipment":{"code":"YV","meta":{"configurable":true},"type":"boolean","label":"Dhl Paket Shipment"},"dhl_breakbulk_mother":{"code":"YW","meta":{"configurable":true},"type":"boolean","label":"Dhl Breakbulk Mother"},"dhl_breakbulk_baby":{"code":"YX","meta":{"configurable":true},"type":"boolean","label":"Dhl Breakbulk Baby"},"dhl_over_weight_piece_y":{"code":"YY","meta":{"configurable":true},"type":"boolean","label":"Dhl Over Weight Piece Y"},"dhl_customer_claim":{"code":"ZA","meta":{"configurable":true},"type":"boolean","label":"Dhl Customer Claim"},"dhl_damage_compensation":{"code":"ZB","meta":{"configurable":true},"type":"boolean","label":"Dhl Damage Compensation"},"dhl_loss_compensation":{"code":"ZC","meta":{"configurable":true},"type":"boolean","label":"Dhl Loss Compensation"},"dhl_customer_rebate":{"code":"ZD","meta":{"configurable":true},"type":"boolean","label":"Dhl Customer Rebate"},"dhl_e_com_discount":{"code":"ZE","meta":{"configurable":true},"type":"boolean","label":"Dhl E Com Discount"},"dhl_shipment_content":{"code":"content","meta":{"configurable":true},"type":"string","label":"Dhl Shipment Content"}},"readme":null},{"id":"dhl_parcel_de","carrier_name":"dhl_parcel_de","display_name":"DHL Germany","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Pickup","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":false,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":false,"sensitive":false,"type":"string","label":"Password"},"client_id":{"name":"client_id","required":false,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":false,"sensitive":false,"type":"string","label":"Client Secret"},"account_country_code":{"default":"DE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_type":{"code":"label_type","enum":["PDF_A4","ZPL2_A4","PDF_910_300_700","ZPL2_910_300_700","PDF_910_300_700_oz","ZPL2_910_300_700_oz","PDF_910_300_710","ZPL2_910_300_710","PDF_910_300_600","ZPL2_910_300_600","PDF_910_300_610","ZPL2_910_300_610","PDF_910_300_400","ZPL2_910_300_400","PDF_910_300_410","ZPL2_910_300_410","PDF_910_300_300","ZPL2_910_300_300","PDF_910_300_300_oz","ZPL2_910_300_300_oz"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"language":{"code":"language","default":"en","enum":["de","en"],"name":"language","required":false,"type":"string","label":"Language"},"default_billing_number":{"code":"default_billing_number","default":"33333333330102","name":"default_billing_number","required":false,"type":"string","label":"Default Billing Number"},"service_billing_numbers":{"code":"service_billing_numbers","default":[{"billing_number":"33333333330102","service":"dhl_parcel_de_paket"},{"billing_number":"33333333335301","service":"dhl_parcel_de_paket_international"},{"billing_number":"33333333335401","service":"dhl_parcel_de_europaket"},{"billing_number":"33333333336201","service":"dhl_parcel_de_kleinpaket"},{"billing_number":"33333333336601","service":"dhl_parcel_de_warenpost_international"},{"billing_number":"33333333330701","service":"dhl_parcel_de_retoure"}],"fields":{"billing_number":{"name":"billing_number","required":true,"type":"string"},"name":{"name":"name","required":false,"type":"string"},"service":{"enum":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket","dhl_parcel_de_europaket","dhl_parcel_de_paket_international","dhl_parcel_de_warenpost_international","dhl_parcel_de_retoure"],"name":"service","required":true,"type":"string"}},"item_type":"ServiceBillingNumberType","name":"service_billing_numbers","required":false,"type":"list","label":"Service Billing Numbers"},"pickup_billing_number":{"code":"pickup_billing_number","default":"22222222220801","name":"pickup_billing_number","required":false,"type":"string","label":"Pickup Billing Number"},"return_billing_number":{"code":"return_billing_number","default":"33333333330701","name":"return_billing_number","required":false,"type":"string","label":"Return Billing Number"},"profile":{"code":"profile","name":"profile","required":false,"type":"string","label":"Profile"},"cost_center":{"code":"cost_center","name":"cost_center","required":false,"type":"string","label":"Cost Center"},"creation_software":{"code":"creation_software","name":"creation_software","required":false,"type":"string","label":"Creation Software"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"dhl_parcel_de_paket":"V01PAK","dhl_parcel_de_kleinpaket":"V62KP","dhl_parcel_de_europaket":"V54EPAK","dhl_parcel_de_paket_international":"V53WPAK","dhl_parcel_de_warenpost_international":"V66WPI","dhl_parcel_de_retoure":"V07PAK"},"shipping_options":{"dhl_parcel_de_preferred_neighbour":{"code":"preferredNeighbour","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Preferred neighbour for delivery if recipient not home"},"type":"string","label":"Preferred Neighbour"},"dhl_parcel_de_preferred_location":{"code":"preferredLocation","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Preferred drop-off location (e.g., garage, shed)"},"type":"string","label":"Preferred Location"},"dhl_parcel_de_named_person_only":{"code":"namedPersonOnly","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Delivery only to the named recipient"},"type":"boolean","label":"Named Person Only"},"dhl_parcel_de_signed_for_by_recipient":{"code":"signedForByRecipient","meta":{"category":"SIGNATURE","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Require signature from recipient"},"type":"boolean","label":"Signed For By Recipient"},"dhl_parcel_de_preferred_day":{"code":"preferredDay","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Preferred delivery day (format: YYYY-MM-DD)"},"type":"string","label":"Preferred Day"},"dhl_parcel_de_no_neighbour_delivery":{"code":"noNeighbourDelivery","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Do not deliver to neighbours"},"type":"boolean","label":"No Neighbour Delivery"},"dhl_parcel_de_additional_insurance":{"code":"additionalInsurance","meta":{"category":"INSURANCE","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_kleinpaket","dhl_parcel_de_warenpost_international"],"configurable":false,"help":"Additional insurance value in EUR (0-2500, 0-25000, or 0-50000)"},"type":"float","label":"Additional Insurance"},"dhl_parcel_de_bulky_goods":{"code":"bulkyGoods","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Mark shipment as bulky goods (Sperrgut)"},"type":"boolean","label":"Bulky Goods"},"dhl_parcel_de_cash_on_delivery":{"code":"cashOnDelivery","meta":{"category":"COD","compatible_services":["dhl_parcel_de_paket"],"configurable":false,"help":"Cash on delivery amount in EUR"},"type":"float","label":"Cash On Delivery"},"dhl_parcel_de_individual_sender_requirement":{"code":"individualSenderRequirement","meta":{"category":"INSTRUCTIONS","configurable":true,"help":"Custom sender note for the label"},"type":"string","label":"Individual Sender Requirement"},"dhl_parcel_de_premium":{"code":"premium","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Premium shipping service"},"type":"boolean","label":"Premium"},"dhl_parcel_de_closest_drop_point":{"code":"closestDropPoint","meta":{"category":"PUDO","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Deliver to closest drop point (CDP)"},"type":"boolean","label":"Closest Drop Point"},"dhl_parcel_de_parcel_outlet_routing":{"code":"parcelOutletRouting","meta":{"category":"PUDO","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":true,"help":"Filial routing - deliver to retail outlet"},"type":"string","label":"Parcel Outlet Routing"},"dhl_parcel_de_postal_delivery_duty_paid":{"code":"postalDeliveryDutyPaid","meta":{"category":"PAPERLESS","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket"],"configurable":true,"help":"Postal Delivered Duty Paid (pDDP) - sender pays customs duties"},"type":"boolean","label":"Postal Delivery Duty Paid"},"dhl_parcel_de_postal_charges":{"code":"postalCharges","meta":{"configurable":false},"type":"float","label":"Postal Charges"},"dhl_parcel_de_post_number":{"code":"postNumber","meta":{"category":"PUDO","configurable":false,"help":"Postnummer for Packstation delivery"},"type":"string","label":"Post Number"},"dhl_parcel_de_retail_id":{"code":"retailID","meta":{"category":"PUDO","configurable":false,"help":"Retail outlet ID"},"type":"string","label":"Retail Id"},"dhl_parcel_de_po_box_id":{"code":"poBoxID","meta":{"category":"PUDO","configurable":false,"help":"PO Box ID"},"type":"string","label":"Po Box Id"},"dhl_parcel_de_shipper_customs_ref":{"code":"shipperCustomsRef","meta":{"category":"INVOICE","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Sender EORI number for customs"},"type":"string","label":"Shipper Customs Ref"},"dhl_parcel_de_consignee_customs_ref":{"code":"consigneeCustomsRef","meta":{"category":"INVOICE","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Recipient ID for customs"},"type":"string","label":"Consignee Customs Ref"},"dhl_parcel_de_permit_no":{"code":"permitNo","meta":{"category":"INVOICE","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Export permit number"},"type":"string","label":"Permit No"},"dhl_parcel_de_attestation_no":{"code":"attestationNo","meta":{"category":"INVOICE","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Attestation number"},"type":"string","label":"Attestation No"},"dhl_parcel_de_has_electronic_export_notification":{"code":"hasElectronicExportNotification","meta":{"category":"PAPERLESS","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Electronic export notification (EEN)"},"type":"boolean","label":"Has Electronic Export Notification"},"dhl_parcel_de_MRN":{"code":"MRN","meta":{"category":"INVOICE","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Movement Reference Number for customs declaration"},"type":"string","label":"Mrn"},"dhl_parcel_de_locker_id":{"code":"lockerID","meta":{"category":"LOCKER","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":false,"help":"Packstation locker ID"},"type":"integer","label":"Locker Id"},"dhl_parcel_de_ident_check":{"code":"identCheck","fields":{"dateOfBirth":{"name":"dateOfBirth","required":false,"type":"string"},"firstName":{"name":"firstName","required":false,"type":"string"},"lastName":{"name":"lastName","required":false,"type":"string"},"minimumAge":{"name":"minimumAge","required":false,"type":"string"}},"meta":{"compatible_services":["dhl_parcel_de_paket"],"configurable":false},"type":"object","label":"Ident Check"},"dhl_parcel_de_return_enabled":{"code":"returnEnabled","meta":{"category":"RETURN","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":true,"help":"Enable DHL Retoure - include return label with shipment"},"type":"boolean","label":"Return Enabled"},"dhl_parcel_de_return_receiver_id":{"code":"returnReceiverId","meta":{"category":"RETURN","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":true,"help":"DHL Receiver ID for returns (configured in DHL Business Portal)"},"type":"string","label":"Return Receiver Id"},"dhl_parcel_de_return_billing_number":{"code":"returnBillingNumber","meta":{"category":"RETURN","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":true,"help":"Billing number for returns (if different from main billing number)"},"type":"string","label":"Return Billing Number"},"dhl_parcel_de_return_reference":{"code":"returnReference","meta":{"category":"RETURN","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":true,"help":"Reference text for the return label"},"type":"string","label":"Return Reference"},"dhl_parcel_de_dhl_retoure":{"code":"dhlRetoure","fields":{"billingNumber":{"name":"billingNumber","required":false,"type":"string"},"refNo":{"name":"refNo","required":false,"type":"string"},"returnAddress":{"fields":{"additionalAddressInformation1":{"name":"additionalAddressInformation1","required":false,"type":"string"},"additionalAddressInformation2":{"name":"additionalAddressInformation2","required":false,"type":"string"},"addressHouse":{"name":"addressHouse","required":false,"type":"string"},"addressStreet":{"name":"addressStreet","required":false,"type":"string"},"city":{"name":"city","required":false,"type":"string"},"contactName":{"name":"contactName","required":false,"type":"string"},"country":{"name":"country","required":false,"type":"string"},"dispatchingInformation":{"name":"dispatchingInformation","required":false,"type":"string"},"email":{"name":"email","required":false,"type":"string"},"lockerID":{"name":"lockerID","required":false,"type":"integer"},"name":{"name":"name","required":false,"type":"string"},"name1":{"name":"name1","required":false,"type":"string"},"name2":{"name":"name2","required":false,"type":"string"},"name3":{"name":"name3","required":false,"type":"string"},"phone":{"name":"phone","required":false,"type":"string"},"poBoxID":{"name":"poBoxID","required":false,"type":"integer"},"postNumber":{"name":"postNumber","required":false,"type":"integer"},"postalCode":{"name":"postalCode","required":false,"type":"string"},"retailID":{"name":"retailID","required":false,"type":"integer"},"state":{"name":"state","required":false,"type":"string"}},"name":"returnAddress","required":false,"type":"object"}},"meta":{"category":"RETURN","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":false,"help":"DHL Retoure configuration for return labels"},"type":"object","label":"Dhl Retoure"},"dhl_parcel_de_visual_check_of_age":{"code":"visualCheckOfAge","enum":["A16","A18"],"meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket"],"configurable":true,"help":"Visual age check at delivery (A16=16+, A18=18+)"},"type":"string","label":"Visual Check Of Age"},"dhl_parcel_de_endorsement":{"code":"endorsement","default":"RETURN","enum":["RETURN","ABANDON"],"meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Action if delivery fails (RETURN or ABANDON)"},"type":"string","label":"Endorsement"},"dhl_parcel_de_economy":{"code":"economy","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket_international","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"Economy shipping service"},"type":"boolean","label":"Economy"},"dhl_parcel_de_gogreen_plus":{"code":"goGreenPlus","meta":{"category":"DELIVERY_OPTIONS","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket","dhl_parcel_de_paket_international","dhl_parcel_de_europaket","dhl_parcel_de_warenpost_international"],"configurable":true,"help":"GoGreen Plus climate-neutral shipping"},"type":"boolean","label":"Gogreen Plus"},"dhl_parcel_de_label_type":{"code":"label_type","enum":["PDF_A4","ZPL2_A4","PDF_910_300_700","ZPL2_910_300_700","PDF_910_300_700_oz","ZPL2_910_300_700_oz","PDF_910_300_710","ZPL2_910_300_710","PDF_910_300_600","ZPL2_910_300_600","PDF_910_300_610","ZPL2_910_300_610","PDF_910_300_400","ZPL2_910_300_400","PDF_910_300_410","ZPL2_910_300_410","PDF_910_300_300","ZPL2_910_300_300","PDF_910_300_300_oz","ZPL2_910_300_300_oz"],"meta":{"category":"DELIVERY_OPTIONS","configurable":true,"help":"Label format for this shipping method (PDF or ZPL variant). Overrides carrier connection setting."},"type":"string","label":"Label Type"},"dhl_parcel_de_cost_center":{"code":"cost_center","meta":{"category":"INVOICE","configurable":true,"help":"Cost center code for billing allocation. Overrides carrier connection setting."},"type":"string","label":"Cost Center"},"dhl_parcel_de_return_service_code":{"code":"return_service_code","enum":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket","dhl_parcel_de_europaket","dhl_parcel_de_paket_international","dhl_parcel_de_warenpost_international","dhl_parcel_de_retoure"],"meta":{"category":"RETURN","compatible_services":["dhl_parcel_de_paket","dhl_parcel_de_kleinpaket"],"configurable":true,"help":"Return service for this shipping method (determines return billing number). Overrides carrier connection setting."},"type":"string","label":"Return Service Code"},"dhl_parcel_de_profile":{"code":"profile","meta":{"category":"OTHER","configurable":true,"help":"DHL shipping profile (Gruppenprofil). Overrides carrier connection setting."},"type":"string","label":"Profile"}},"readme":null},{"id":"dhl_poland","carrier_name":"dhl_poland","display_name":"DHL Parcel Poland","integration_status":"production-ready","website":"https://dhl24.com.pl/en","description":"Global Logistics and International Shipping Poland.","documentation":"https://dhl24.com.pl/en/webapi2/doc.html","is_enabled":true,"capabilities":["Shipping","Rating","Tracking"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"default":"PL","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"dhl_poland_premium":"PR","dhl_poland_polska":"AH","dhl_poland_09":"09","dhl_poland_12":"12","dhl_poland_connect":"EK","dhl_poland_international":"PI"},"shipping_options":{"dhl_poland_delivery_in_18_22_hours":{"code":"1722","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DHL Poland Delivery In 18 22 Hours"},"dhl_poland_delivery_on_saturday":{"code":"SATURDAY","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DHL Poland Delivery On Saturday"},"dhl_poland_pickup_on_staturday":{"code":"NAD_SOBOTA","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DHL Poland Pickup On Staturday"},"dhl_poland_insuration":{"code":"UBEZP","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"DHL Poland Insuration"},"dhl_poland_collect_on_delivery":{"code":"COD","meta":{"category":"COD","configurable":true},"type":"float","label":"DHL Poland Collect On Delivery"},"dhl_poland_information_to_receiver":{"code":"PDI","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"DHL Poland Information To Receiver"},"dhl_poland_return_of_document":{"code":"ROD","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"DHL Poland Return Of Document"},"dhl_poland_proof_of_delivery":{"code":"POD","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DHL Poland Proof Of Delivery"},"dhl_poland_delivery_to_neighbour":{"code":"SAS","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DHL Poland Delivery To Neighbour"},"dhl_poland_self_collect":{"code":"ODB","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"DHL Poland Self Collect"}},"readme":null},{"id":"dhl_universal","carrier_name":"dhl_universal","display_name":"DHL Universal","integration_status":"production-ready","website":"https://www.dhl.com/","description":"DHL is a German logistics company providing courier, package delivery and express mail service, delivering over 1.8 billion parcels per year.","documentation":"https://developer.dhl.com/api-reference/shipment-tracking","is_enabled":true,"capabilities":["Tracking"],"connection_fields":{"consumer_key":{"name":"consumer_key","required":true,"sensitive":false,"type":"string","label":"Consumer Key"},"consumer_secret":{"name":"consumer_secret","required":true,"sensitive":false,"type":"string","label":"Consumer Secret"},"language":{"default":"en","enum":["en","de"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"DE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"dicom","carrier_name":"dicom","display_name":"Dicom","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"billing_account":{"name":"billing_account","required":false,"sensitive":false,"type":"string","label":"Billing Account"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"dpd","carrier_name":"dpd","display_name":"DPD","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"delis_id":{"name":"delis_id","required":true,"sensitive":false,"type":"string","label":"Delis Id"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"depot":{"name":"depot","required":false,"sensitive":false,"type":"string","label":"Depot"},"message_language":{"default":"en_EN","name":"message_language","required":false,"sensitive":false,"type":"string","label":"Message Language"},"account_country_code":{"default":"BE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"dpd_cl":"CL","dpd_express_10h":"E10","dpd_express_12h":"E12","dpd_express_18h_guarantee":"E18","dpd_express_b2b_predict":"B2B MSG option"},"shipping_options":{"dpd_order_type":{"code":"orderType","meta":{"configurable":true},"type":"string","label":"DPD Order Type"},"dpd_saturday_delivery":{"code":"saturdayDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DPD Saturday Delivery"},"dpd_ex_works_delivery":{"code":"exWorksDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DPD Ex Works Delivery"},"dpd_tyres":{"code":"tyres","meta":{"configurable":true},"type":"boolean","label":"DPD Tyres"},"dpd_parcel_shop_delivery":{"code":"parcelShopDelivery","meta":{"category":"PUDO","configurable":true},"type":"string","label":"DPD Parcel Shop Delivery"}},"readme":null},{"id":"dpd_meta","carrier_name":"dpd_meta","display_name":"DPD Meta","integration_status":"beta","website":"https://www.dpdgroup.com","description":"DPD Meta shipping integration for Karrio","documentation":"https://api-preprod.dpsin.dpdgroup.com:8443/shipping/v1/meta-api-docs","is_enabled":true,"capabilities":["Pickup","Rating","Shipping"],"connection_fields":{"dpd_login":{"name":"dpd_login","required":false,"sensitive":false,"type":"string","label":"Dpd Login"},"dpd_password":{"name":"dpd_password","required":false,"sensitive":false,"type":"string","label":"Dpd Password"},"dpd_client_id":{"name":"dpd_client_id","required":false,"sensitive":false,"type":"string","label":"Dpd Client Id"},"dpd_client_secret":{"name":"dpd_client_secret","required":false,"sensitive":false,"type":"string","label":"Dpd Client Secret"},"dpd_bucode":{"name":"dpd_bucode","required":false,"sensitive":false,"type":"string","label":"Dpd Bucode"},"customer_id":{"name":"customer_id","required":false,"sensitive":false,"type":"string","label":"Customer Id"},"customer_account_number":{"name":"customer_account_number","required":false,"sensitive":false,"type":"string","label":"Customer Account Number"},"customer_sub_account_number":{"name":"customer_sub_account_number","required":false,"sensitive":false,"type":"string","label":"Customer Sub Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"label_type":{"code":"label_type","enum":["A4","A5","A6","A7"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"label_format":{"code":"label_format","enum":["PDF","EPL","ZPL","TIFF","PNG","PPR","SPD","Z2D","THE","XML","XML2D","THEPSG","ZPLPSG","ZPL300","JSON","PS","DATA","CLP","HTML"],"name":"label_format","required":false,"type":"string","label":"Label Format"},"label_paper_format":{"code":"label_paper_format","enum":["A4","A5","A6","A7"],"name":"label_paper_format","required":false,"type":"string","label":"Label Paper Format"},"label_printer_position":{"code":"label_printer_position","enum":["UPPER_LEFT","UPPER_RIGHT","LOWER_LEFT","LOWER_RIGHT"],"name":"label_printer_position","required":false,"type":"string","label":"Label Printer Position"},"dropoff_type":{"code":"dropoff_type","enum":["FULL_LABEL","QR_CODE","BOTH"],"name":"dropoff_type","required":false,"type":"string","label":"Dropoff Type"},"sending_depot":{"code":"sending_depot","name":"sending_depot","required":false,"type":"string","label":"Sending Depot"},"simulate":{"code":"simulate","name":"simulate","required":false,"type":"boolean","label":"Simulate"},"extra_barcode":{"code":"extra_barcode","name":"extra_barcode","required":false,"type":"boolean","label":"Extra Barcode"},"with_document":{"code":"with_document","name":"with_document","required":false,"type":"boolean","label":"With Document"}},"shipping_services":{"dpd_meta_classic":"CL","dpd_meta_express_830":"E830","dpd_meta_express_12":"E12","dpd_meta_express_18":"E18","dpd_meta_international_express":"IE2","dpd_meta_parcel_letter":"PL","dpd_meta_mail":"MAIL","dpd_meta_max":"MAX"},"shipping_options":{"dpd_meta_saturday_delivery":{"code":"saturday_delivery","help":"Enable Saturday delivery service","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DPD Meta Saturday Delivery"},"dpd_meta_small_parcel":{"code":"small_parcel","help":"Mark shipment as small package (Kleinpaket) for reduced rates","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DPD Meta Small Parcel"},"dpd_meta_exchange_service":{"code":"exchange_service","help":"Enable exchange service (Austauschservice) — simultaneous delivery and pickup","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DPD Meta Exchange Service"},"dpd_meta_ex_works":{"code":"ex_works","help":"Enable EX Works delivery mode","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"DPD Meta Ex Works"},"dpd_meta_delivery_date_from":{"code":"delivery_date_from","help":"Earliest delivery date (YYYY-MM-DD)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"DPD Meta Delivery Date From"},"dpd_meta_delivery_date_to":{"code":"delivery_date_to","help":"Latest delivery date (YYYY-MM-DD)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"DPD Meta Delivery Date To"},"dpd_meta_delivery_time_from":{"code":"delivery_time_from","help":"Earliest delivery time (HH:MM)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"DPD Meta Delivery Time From"},"dpd_meta_delivery_time_to":{"code":"delivery_time_to","help":"Latest delivery time (HH:MM)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"DPD Meta Delivery Time To"},"dpd_meta_dropoff_type":{"code":"dropoff_type","help":"Drop-off type for parcel shop delivery (FULL_LABEL, QR_CODE, BOTH)","meta":{"category":"PUDO","configurable":true},"type":"string","label":"DPD Meta Dropoff Type"},"dpd_meta_parcel_shop_id":{"code":"parcel_shop_id","help":"DPD ParcelShop ID for shop delivery","meta":{"category":"PUDO","configurable":true},"type":"string","label":"DPD Meta Parcel Shop ID"},"dpd_meta_notification_email":{"code":"notification_email","help":"Email for delivery notifications (Predict service)","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"DPD Meta Notification Email"},"dpd_meta_notification_sms":{"code":"notification_sms","help":"SMS number for delivery notifications","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"DPD Meta Notification Sms"},"dpd_meta_insurance_description":{"code":"insurance_description","help":"Free text description for additional insurance purpose","meta":{"category":"INSURANCE","configurable":true},"type":"string","label":"DPD Meta Insurance Description"},"dpd_meta_cod_collect_type":{"code":"cod_collect_type","help":"COD collection type (cash, cheque, etc.)","meta":{"category":"COD","configurable":true},"type":"string","label":"DPD Meta COD Collect Type"},"dpd_meta_cod_bank_code":{"code":"cod_bank_code","help":"Bank code for COD payment","meta":{"category":"COD","configurable":false},"type":"string","label":"DPD Meta COD Bank Code"},"dpd_meta_cod_bank_name":{"code":"cod_bank_name","help":"Bank name for COD payment","meta":{"category":"COD","configurable":false},"type":"string","label":"DPD Meta COD Bank Name"},"dpd_meta_cod_bank_account_number":{"code":"cod_bank_account_number","help":"Bank account number for COD payment","meta":{"category":"COD","configurable":false},"type":"string","label":"DPD Meta COD Bank Account Number"},"dpd_meta_cod_bank_account_name":{"code":"cod_bank_account_name","help":"Account holder name for COD payment","meta":{"category":"COD","configurable":false},"type":"string","label":"DPD Meta COD Bank Account Name"},"dpd_meta_cod_iban":{"code":"cod_iban","help":"IBAN for COD payment","meta":{"category":"COD","configurable":false},"type":"string","label":"DPD Meta COD Iban"},"dpd_meta_cod_bic":{"code":"cod_bic","help":"BIC/SWIFT code for COD payment","meta":{"category":"COD","configurable":false},"type":"string","label":"DPD Meta COD Bic"},"dpd_meta_cod_purpose":{"code":"cod_purpose","help":"Purpose text for COD payment","meta":{"category":"COD","configurable":true},"type":"string","label":"DPD Meta COD Purpose"},"dpd_meta_dangerous_goods":{"code":"dangerous_goods","help":"Enable shipment of dangerous goods (Gefahrgut)","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"DPD Meta Dangerous Goods"},"dpd_meta_dg_identification_class":{"code":"dg_identification_class","help":"Hazard identification number (Identifikationsklasse)","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Identification Class"},"dpd_meta_dg_un_number":{"code":"dg_un_number","help":"UN number for the hazardous material","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Un Number"},"dpd_meta_dg_weight":{"code":"dg_weight","help":"Weight of dangerous goods package in kg","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"float","label":"Dg Weight"},"dpd_meta_dg_description":{"code":"dg_description","help":"Detailed description of the dangerous goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Description"},"dpd_meta_dg_hazard_factor":{"code":"dg_hazard_factor","help":"Factor defining the risk level of the hazardous goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Hazard Factor"},"dpd_meta_dg_hazard_class":{"code":"dg_hazard_class","help":"Official hazard classification (e.g. 3 for flammable liquids)","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Hazard Class"},"dpd_meta_dg_nag_entry":{"code":"dg_nag_entry","help":"Not otherwise specified (N.A.G.) entry for hazardous goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Nag Entry"},"dpd_meta_dg_packing_group":{"code":"dg_packing_group","help":"Packing group (I, II, III) indicating packaging strength","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Packing Group"},"dpd_meta_dg_packing_code":{"code":"dg_packing_code","help":"DPD-specific packaging code for dangerous goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Packing Code"},"dpd_meta_dg_subsidiary_risks":{"code":"dg_subsidiary_risks","help":"Additional hazard classifications (subsidiary risks)","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Subsidiary Risks"},"dpd_meta_dg_tunnel_restriction_code":{"code":"dg_tunnel_restriction_code","help":"Code defining tunnel access restrictions for hazardous goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Dg Tunnel Restriction Code"},"dpd_meta_return_enabled":{"code":"return_enabled","help":"Enable DPD return label creation","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Return Enabled"},"dpd_meta_return_description":{"code":"return_description","help":"Description or reason for the return shipment","meta":{"category":"RETURN","configurable":true},"type":"string","label":"Return Description"},"dpd_meta_include_return_label":{"code":"include_return_label","help":"Add a printed return label with the outbound shipment (Beilegeretoure)","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Include Return Label"},"dpd_meta_label_format":{"code":"label_format","meta":{"configurable":false},"type":"string","label":"Label Format"},"dpd_meta_label_paper_format":{"code":"label_paper_format","meta":{"configurable":false},"type":"string","label":"Label Paper Format"},"dpd_meta_label_printer_position":{"code":"label_printer_position","meta":{"configurable":false},"type":"string","label":"Label Printer Position"},"dpd_meta_simulate":{"code":"simulate","meta":{"configurable":false},"type":"boolean","label":"Simulate"},"dpd_meta_extra_barcode":{"code":"extra_barcode","meta":{"configurable":false},"type":"boolean","label":"Extra Barcode"},"dpd_meta_with_document":{"code":"with_document","meta":{"configurable":false},"type":"boolean","label":"With Document"},"cash_on_delivery":{"code":"cash_on_delivery","help":"Cash on delivery amount","meta":{"category":"COD","configurable":true},"type":"float","label":"Cash On Delivery"},"insurance":{"code":"insurance","help":"Additional insurance value","meta":{"category":"INSURANCE","configurable":false},"type":"float","label":"Insurance"},"declared_value":{"code":"declared_value","help":"Declared value for customs","meta":{"category":"INVOICE","configurable":true},"type":"float","label":"Declared Value"},"currency":{"code":"currency","help":"Currency code for values","meta":{"configurable":false},"type":"string","label":"Currency"}},"readme":null},{"id":"dtdc","carrier_name":"dtdc","display_name":"DTDC","integration_status":"beta","website":"https://dtdc.in","description":"DTDC is India's leading express distribution and logistics solutions provider, offering domestic and international courier services.","documentation":"https://www.dtdc.in/dtdcapi/trackCnno.do","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"customer_code":{"name":"customer_code","required":true,"sensitive":false,"type":"string","label":"Customer Code"},"username":{"name":"username","required":false,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":false,"sensitive":false,"type":"string","label":"Password"},"account_country_code":{"default":"IN","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_type":{"code":"label_type","enum":["SHIP_LABEL_A4","SHIP_LABEL_A6","SHIP_LABEL_POD","SHIP_LABEL_4X6","ROUTE_LABEL_A4","ROUTE_LABEL_4X4","ADDR_LABEL_A4","ADDR_LABEL_4X2"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"dtdc_b2c_priority":"B2C PRIORITY","dtdc_b2c_economy":"B2C SMART EXPRESS","dtdc_b2c_express":"B2C PREMIUM","dtdc_b2c_ground":"B2C GROUND ECONOMY","dtdc_priority":"PRIORITY","dtdc_ground_express":"GROUND EXPRESS","dtdc_premium":"PREMIUM","dtdc_economy_ground":"GEC","dtdc_standard_express":"STD EXP-A"},"shipping_options":{"dtdc_is_risk_surcharge_applicable":{"code":"is_risk_surcharge_applicable","meta":{"configurable":true},"type":"boolean","label":"Is Risk Surcharge Applicable"},"dtdc_invoice_number":{"code":"invoice_number","meta":{"configurable":true},"type":"string","label":"Invoice Number"},"dtdc_invoice_date":{"code":"invoice_date","meta":{"configurable":true},"type":"string","label":"Invoice Date"},"dtdc_commodity_id":{"code":"commodity_id","meta":{"configurable":true},"type":"string","label":"Commodity Id"},"dtdc_cod_amount":{"code":"cod_amount","meta":{"configurable":true},"type":"float","label":"Cod Amount"},"dtdc_eway_bill":{"code":"eway_bill","meta":{"configurable":true},"type":"string","label":"Eway Bill"},"dtdc_cod_collection_mode":{"code":"cod_collection_mode","enum":["CASH","CHEQUE"],"meta":{"configurable":true},"type":"string","label":"Cod Collection Mode"}},"readme":null},{"id":"easypost","carrier_name":"easypost","display_name":"EasyPost","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"easypost_amazonmws_ups_rates":"UPS Rates","easypost_amazonmws_usps_rates":"USPS Rates","easypost_amazonmws_fedex_rates":"FedEx Rates","easypost_amazonmws_ups_labels":"UPS Labels","easypost_amazonmws_usps_labels":"USPS Labels","easypost_amazonmws_fedex_labels":"FedEx Labels","easypost_amazonmws_ups_tracking":"UPS Tracking","easypost_amazonmws_usps_tracking":"USPS Tracking","easypost_amazonmws_fedex_tracking":"FedEx Tracking","easypost_apc_parcel_connect_book_service":"parcelConnectBookService","easypost_apc_parcel_connect_expedited_ddp":"parcelConnectExpeditedDDP","easypost_apc_parcel_connect_expedited_ddu":"parcelConnectExpeditedDDU","easypost_apc_parcel_connect_priority_ddp":"parcelConnectPriorityDDP","easypost_apc_parcel_connect_priority_ddp_delcon":"parcelConnectPriorityDDPDelcon","easypost_apc_parcel_connect_priority_ddu":"parcelConnectPriorityDDU","easypost_apc_parcel_connect_priority_ddu_delcon":"parcelConnectPriorityDDUDelcon","easypost_apc_parcel_connect_priority_ddupqw":"parcelConnectPriorityDDUPQW","easypost_apc_parcel_connect_standard_ddu":"parcelConnectStandardDDU","easypost_apc_parcel_connect_standard_ddupqw":"parcelConnectStandardDDUPQW","easypost_apc_parcel_connect_packet_ddu":"parcelConnectPacketDDU","easypost_asendia_pmi":"PMI","easypost_asendia_e_packet":"ePacket","easypost_asendia_ipa":"IPA","easypost_asendia_isal":"ISAL","easypost_asendia_us_ads":"ADS","easypost_asendia_us_air_freight_inbound":"AirFreightInbound","easypost_asendia_us_air_freight_outbound":"AirFreightOutbound","easypost_asendia_us_domestic_bound_printer_matter_expedited":"AsendiaDomesticBoundPrinterMatterExpedited","easypost_asendia_us_domestic_bound_printer_matter_ground":"AsendiaDomesticBoundPrinterMatterGround","easypost_asendia_us_domestic_flats_expedited":"AsendiaDomesticFlatsExpedited","easypost_asendia_us_domestic_flats_ground":"AsendiaDomesticFlatsGround","easypost_asendia_us_domestic_parcel_ground_over1lb":"AsendiaDomesticParcelGroundOver1lb","easypost_asendia_us_domestic_parcel_ground_under1lb":"AsendiaDomesticParcelGroundUnder1lb","easypost_asendia_us_domestic_parcel_max_over1lb":"AsendiaDomesticParcelMAXOver1lb","easypost_asendia_us_domestic_parcel_max_under1lb":"AsendiaDomesticParcelMAXUnder1lb","easypost_asendia_us_domestic_parcel_over1lb_expedited":"AsendiaDomesticParcelOver1lbExpedited","easypost_asendia_us_domestic_parcel_under1lb_expedited":"AsendiaDomesticParcelUnder1lbExpedited","easypost_asendia_us_domestic_promo_parcel_expedited":"AsendiaDomesticPromoParcelExpedited","easypost_asendia_us_domestic_promo_parcel_ground":"AsendiaDomesticPromoParcelGround","easypost_asendia_us_bulk_freight":"BulkFreight","easypost_asendia_us_business_mail_canada_lettermail":"BusinessMailCanadaLettermail","easypost_asendia_us_business_mail_canada_lettermail_machineable":"BusinessMailCanadaLettermailMachineable","easypost_asendia_us_business_mail_economy":"BusinessMailEconomy","easypost_asendia_us_business_mail_economy_lp_wholesale":"BusinessMailEconomyLPWholesale","easypost_asendia_us_business_mail_economy_sp_wholesale":"BusinessMailEconomySPWholesale","easypost_asendia_us_business_mail_ipa":"BusinessMailIPA","easypost_asendia_us_business_mail_isal":"BusinessMailISAL","easypost_asendia_us_business_mail_priority":"BusinessMailPriority","easypost_asendia_us_business_mail_priority_lp_wholesale":"BusinessMailPriorityLPWholesale","easypost_asendia_us_business_mail_priority_sp_wholesale":"BusinessMailPrioritySPWholesale","easypost_asendia_us_marketing_mail_canada_personalized_lcp":"MarketingMailCanadaPersonalizedLCP","easypost_asendia_us_marketing_mail_canada_personalized_machineable":"MarketingMailCanadaPersonalizedMachineable","easypost_asendia_us_marketing_mail_canada_personalized_ndg":"MarketingMailCanadaPersonalizedNDG","easypost_asendia_us_marketing_mail_economy":"MarketingMailEconomy","easypost_asendia_us_marketing_mail_ipa":"MarketingMailIPA","easypost_asendia_us_marketing_mail_isal":"MarketingMailISAL","easypost_asendia_us_marketing_mail_priority":"MarketingMailPriority","easypost_asendia_us_publications_canada_lcp":"PublicationsCanadaLCP","easypost_asendia_us_publications_canada_ndg":"PublicationsCanadaNDG","easypost_asendia_us_publications_economy":"PublicationsEconomy","easypost_asendia_us_publications_ipa":"PublicationsIPA","easypost_asendia_us_publications_isal":"PublicationsISAL","easypost_asendia_us_publications_priority":"PublicationsPriority","easypost_asendia_us_epaq_elite":"ePAQElite","easypost_asendia_us_epaq_elite_custom":"ePAQEliteCustom","easypost_asendia_us_epaq_elite_dap":"ePAQEliteDAP","easypost_asendia_us_epaq_elite_ddp":"ePAQEliteDDP","easypost_asendia_us_epaq_elite_ddp_oversized":"ePAQEliteDDPOversized","easypost_asendia_us_epaq_elite_dpd":"ePAQEliteDPD","easypost_asendia_us_epaq_elite_direct_access_canada_ddp":"ePAQEliteDirectAccessCanadaDDP","easypost_asendia_us_epaq_elite_oversized":"ePAQEliteOversized","easypost_asendia_us_epaq_plus":"ePAQPlus","easypost_asendia_us_epaq_plus_custom":"ePAQPlusCustom","easypost_asendia_us_epaq_plus_customs_prepaid":"ePAQPlusCustomsPrepaid","easypost_asendia_us_epaq_plus_dap":"ePAQPlusDAP","easypost_asendia_us_epaq_plus_ddp":"ePAQPlusDDP","easypost_asendia_us_epaq_plus_economy":"ePAQPlusEconomy","easypost_asendia_us_epaq_plus_wholesale":"ePAQPlusWholesale","easypost_asendia_us_epaq_pluse_packet":"ePAQPlusePacket","easypost_asendia_us_epaq_pluse_packet_canada_customs_pre_paid":"ePAQPlusePacketCanadaCustomsPrePaid","easypost_asendia_us_epaq_pluse_packet_canada_ddp":"ePAQPlusePacketCanadaDDP","easypost_asendia_us_epaq_returns_domestic":"ePAQReturnsDomestic","easypost_asendia_us_epaq_returns_international":"ePAQReturnsInternational","easypost_asendia_us_epaq_select":"ePAQSelect","easypost_asendia_us_epaq_select_custom":"ePAQSelectCustom","easypost_asendia_us_epaq_select_customs_prepaid_by_shopper":"ePAQSelectCustomsPrepaidByShopper","easypost_asendia_us_epaq_select_dap":"ePAQSelectDAP","easypost_asendia_us_epaq_select_ddp":"ePAQSelectDDP","easypost_asendia_us_epaq_select_ddp_direct_access":"ePAQSelectDDPDirectAccess","easypost_asendia_us_epaq_select_direct_access":"ePAQSelectDirectAccess","easypost_asendia_us_epaq_select_direct_access_canada_ddp":"ePAQSelectDirectAccessCanadaDDP","easypost_asendia_us_epaq_select_economy":"ePAQSelectEconomy","easypost_asendia_us_epaq_select_oversized":"ePAQSelectOversized","easypost_asendia_us_epaq_select_oversized_ddp":"ePAQSelectOversizedDDP","easypost_asendia_us_epaq_select_pmei":"ePAQSelectPMEI","easypost_asendia_us_epaq_select_pmei_canada_customs_pre_paid":"ePAQSelectPMEICanadaCustomsPrePaid","easypost_asendia_us_epaq_select_pmeipc_postage":"ePAQSelectPMEIPCPostage","easypost_asendia_us_epaq_select_pmi":"ePAQSelectPMI","easypost_asendia_us_epaq_select_pmi_canada_customs_prepaid":"ePAQSelectPMICanadaCustomsPrepaid","easypost_asendia_us_epaq_select_pmi_canada_ddp":"ePAQSelectPMICanadaDDP","easypost_asendia_us_epaq_select_pmi_non_presort":"ePAQSelectPMINonPresort","easypost_asendia_us_epaq_select_pmipc_postage":"ePAQSelectPMIPCPostage","easypost_asendia_us_epaq_standard":"ePAQStandard","easypost_asendia_us_epaq_standard_custom":"ePAQStandardCustom","easypost_asendia_us_epaq_standard_economy":"ePAQStandardEconomy","easypost_asendia_us_epaq_standard_ipa":"ePAQStandardIPA","easypost_asendia_us_epaq_standard_isal":"ePAQStandardISAL","easypost_asendia_us_epaq_select_pmei_non_presort":"ePaqSelectPMEINonPresort","easypost_australiapost_express_post":"ExpressPost","easypost_australiapost_express_post_signature":"ExpressPostSignature","easypost_australiapost_parcel_post":"ParcelPost","easypost_australiapost_parcel_post_signature":"ParcelPostSignature","easypost_australiapost_parcel_post_extra":"ParcelPostExtra","easypost_australiapost_parcel_post_wine_plus_signature":"ParcelPostWinePlusSignature","easypost_axlehire_delivery":"AxleHireDelivery","easypost_better_trucks_next_day":"NEXT_DAY","easypost_bond_standard":"Standard","easypost_canadapost_regular_parcel":"RegularParcel","easypost_canadapost_expedited_parcel":"ExpeditedParcel","easypost_canadapost_xpresspost":"Xpresspost","easypost_canadapost_xpresspost_certified":"XpresspostCertified","easypost_canadapost_priority":"Priority","easypost_canadapost_library_books":"LibraryBooks","easypost_canadapost_expedited_parcel_usa":"ExpeditedParcelUSA","easypost_canadapost_priority_worldwide_envelope_usa":"PriorityWorldwideEnvelopeUSA","easypost_canadapost_priority_worldwide_pak_usa":"PriorityWorldwidePakUSA","easypost_canadapost_priority_worldwide_parcel_usa":"PriorityWorldwideParcelUSA","easypost_canadapost_small_packet_usa_air":"SmallPacketUSAAir","easypost_canadapost_tracked_packet_usa":"TrackedPacketUSA","easypost_canadapost_tracked_packet_usalvm":"TrackedPacketUSALVM","easypost_canadapost_xpresspost_usa":"XpresspostUSA","easypost_canadapost_xpresspost_international":"XpresspostInternational","easypost_canadapost_international_parcel_air":"InternationalParcelAir","easypost_canadapost_international_parcel_surface":"InternationalParcelSurface","easypost_canadapost_priority_worldwide_envelope_intl":"PriorityWorldwideEnvelopeIntl","easypost_canadapost_priority_worldwide_pak_intl":"PriorityWorldwidePakIntl","easypost_canadapost_priority_worldwide_parcel_intl":"PriorityWorldwideParcelIntl","easypost_canadapost_small_packet_international_air":"SmallPacketInternationalAir","easypost_canadapost_small_packet_international_surface":"SmallPacketInternationalSurface","easypost_canadapost_tracked_packet_international":"TrackedPacketInternational","easypost_canpar_ground":"Ground","easypost_canpar_select_letter":"SelectLetter","easypost_canpar_select_pak":"SelectPak","easypost_canpar_select":"Select","easypost_canpar_overnight_letter":"OvernightLetter","easypost_canpar_overnight_pak":"OvernightPak","easypost_canpar_overnight":"Overnight","easypost_canpar_select_usa":"SelectUSA","easypost_canpar_usa_pak":"USAPak","easypost_canpar_usa_letter":"USALetter","easypost_canpar_usa":"USA","easypost_canpar_international":"International","easypost_cdl_distribution":"DISTRIBUTION","easypost_cdl_same_day":"Same Day","easypost_courier_express_basic_parcel":"BASIC_PARCEL","easypost_couriersplease_domestic_priority_signature":"DomesticPrioritySignature","easypost_couriersplease_domestic_priority":"DomesticPriority","easypost_couriersplease_domestic_off_peak_signature":"DomesticOffPeakSignature","easypost_couriersplease_domestic_off_peak":"DomesticOffPeak","easypost_couriersplease_gold_domestic_signature":"GoldDomesticSignature","easypost_couriersplease_gold_domestic":"GoldDomestic","easypost_couriersplease_australian_city_express_signature":"AustralianCityExpressSignature","easypost_couriersplease_australian_city_express":"AustralianCityExpress","easypost_couriersplease_domestic_saver_signature":"DomesticSaverSignature","easypost_couriersplease_domestic_saver":"DomesticSaver","easypost_couriersplease_road_express":"RoadExpress","easypost_couriersplease_5_kg_satchel":"5KgSatchel","easypost_couriersplease_3_kg_satchel":"3KgSatchel","easypost_couriersplease_1_kg_satchel":"1KgSatchel","easypost_couriersplease_5_kg_satchel_atl":"5KgSatchelATL","easypost_couriersplease_3_kg_satchel_atl":"3KgSatchelATL","easypost_couriersplease_1_kg_satchel_atl":"1KgSatchelATL","easypost_couriersplease_500_gram_satchel":"500GramSatchel","easypost_couriersplease_500_gram_satchel_atl":"500GramSatchelATL","easypost_couriersplease_25_kg_parcel":"25KgParcel","easypost_couriersplease_10_kg_parcel":"10KgParcel","easypost_couriersplease_5_kg_parcel":"5KgParcel","easypost_couriersplease_3_kg_parcel":"3KgParcel","easypost_couriersplease_1_kg_parcel":"1KgParcel","easypost_couriersplease_500_gram_parcel":"500GramParcel","easypost_couriersplease_500_gram_parcel_atl":"500GramParcelATL","easypost_couriersplease_express_international_priority":"ExpressInternationalPriority","easypost_couriersplease_international_saver":"InternationalSaver","easypost_couriersplease_international_express_import":"InternationalExpressImport","easypost_couriersplease_domestic_tracked":"DomesticTracked","easypost_couriersplease_international_economy":"InternationalEconomy","easypost_couriersplease_international_standard":"InternationalStandard","easypost_couriersplease_international_express":"InternationalExpress","easypost_deutschepost_packet_plus":"PacketPlus","easypost_deutschepost_uk_priority_packet_plus":"PriorityPacketPlus","easypost_deutschepost_uk_priority_packet":"PriorityPacket","easypost_deutschepost_uk_priority_packet_tracked":"PriorityPacketTracked","easypost_deutschepost_uk_business_mail_registered":"BusinessMailRegistered","easypost_deutschepost_uk_standard_packet":"StandardPacket","easypost_deutschepost_uk_business_mail_standard":"BusinessMailStandard","easypost_dhl_ecom_asia_packet":"Packet","easypost_dhl_ecom_asia_parcel_direct":"ParcelDirect","easypost_dhl_ecom_asia_parcel_direct_expedited":"ParcelDirectExpedited","easypost_dhl_ecom_parcel_expedited":"DHLParcelExpedited","easypost_dhl_ecom_parcel_expedited_max":"DHLParcelExpeditedMax","easypost_dhl_ecom_parcel_ground":"DHLParcelGround","easypost_dhl_ecom_bpm_expedited":"DHLBPMExpedited","easypost_dhl_ecom_bpm_ground":"DHLBPMGround","easypost_dhl_ecom_parcel_international_direct":"DHLParcelInternationalDirect","easypost_dhl_ecom_parcel_international_standard":"DHLParcelInternationalStandard","easypost_dhl_ecom_packet_international":"DHLPacketInternational","easypost_dhl_ecom_parcel_international_direct_priority":"DHLParcelInternationalDirectPriority","easypost_dhl_ecom_parcel_international_direct_standard":"DHLParcelInternationalDirectStandard","easypost_dhl_express_break_bulk_economy":"BreakBulkEconomy","easypost_dhl_express_break_bulk_express":"BreakBulkExpress","easypost_dhl_express_domestic_economy_select":"DomesticEconomySelect","easypost_dhl_express_domestic_express":"DomesticExpress","easypost_dhl_express_domestic_express1030":"DomesticExpress1030","easypost_dhl_express_domestic_express1200":"DomesticExpress1200","easypost_dhl_express_economy_select":"EconomySelect","easypost_dhl_express_economy_select_non_doc":"EconomySelectNonDoc","easypost_dhl_express_euro_pack":"EuroPack","easypost_dhl_express_europack_non_doc":"EuropackNonDoc","easypost_dhl_express_express1030":"Express1030","easypost_dhl_express_express1030_non_doc":"Express1030NonDoc","easypost_dhl_express_express1200_non_doc":"Express1200NonDoc","easypost_dhl_express_express1200":"Express1200","easypost_dhl_express_express900":"Express900","easypost_dhl_express_express900_non_doc":"Express900NonDoc","easypost_dhl_express_express_easy":"ExpressEasy","easypost_dhl_express_express_easy_non_doc":"ExpressEasyNonDoc","easypost_dhl_express_express_envelope":"ExpressEnvelope","easypost_dhl_express_express_worldwide":"ExpressWorldwide","easypost_dhl_express_express_worldwide_b2_c":"ExpressWorldwideB2C","easypost_dhl_express_express_worldwide_b2_c_non_doc":"ExpressWorldwideB2CNonDoc","easypost_dhl_express_express_worldwide_ecx":"ExpressWorldwideECX","easypost_dhl_express_express_worldwide_non_doc":"ExpressWorldwideNonDoc","easypost_dhl_express_freight_worldwide":"FreightWorldwide","easypost_dhl_express_globalmail_business":"GlobalmailBusiness","easypost_dhl_express_jet_line":"JetLine","easypost_dhl_express_jumbo_box":"JumboBox","easypost_dhl_express_logistics_services":"LogisticsServices","easypost_dhl_express_same_day":"SameDay","easypost_dhl_express_secure_line":"SecureLine","easypost_dhl_express_sprint_line":"SprintLine","easypost_dpd_classic":"DPDCLASSIC","easypost_dpd_8_30":"DPD8:30","easypost_dpd_10_00":"DPD10:00","easypost_dpd_12_00":"DPD12:00","easypost_dpd_18_00":"DPD18:00","easypost_dpd_express":"DPDEXPRESS","easypost_dpd_parcelletter":"DPDPARCELLETTER","easypost_dpd_parcelletterplus":"DPDPARCELLETTERPLUS","easypost_dpd_internationalmail":"DPDINTERNATIONALMAIL","easypost_dpd_uk_air_express_international_air":"AirExpressInternationalAir","easypost_dpd_uk_air_classic_international_air":"AirClassicInternationalAir","easypost_dpd_uk_parcel_sunday":"ParcelSunday","easypost_dpd_uk_freight_parcel_sunday":"FreightParcelSunday","easypost_dpd_uk_pallet_sunday":"PalletSunday","easypost_dpd_uk_pallet_dpd_classic":"PalletDpdClassic","easypost_dpd_uk_expresspak_dpd_classic":"ExpresspakDpdClassic","easypost_dpd_uk_expresspak_sunday":"ExpresspakSunday","easypost_dpd_uk_parcel_dpd_classic":"ParcelDpdClassic","easypost_dpd_uk_parcel_dpd_two_day":"ParcelDpdTwoDay","easypost_dpd_uk_parcel_dpd_next_day":"ParcelDpdNextDay","easypost_dpd_uk_parcel_dpd12":"ParcelDpd12","easypost_dpd_uk_parcel_dpd10":"ParcelDpd10","easypost_dpd_uk_parcel_return_to_shop":"ParcelReturnToShop","easypost_dpd_uk_parcel_saturday":"ParcelSaturday","easypost_dpd_uk_parcel_saturday12":"ParcelSaturday12","easypost_dpd_uk_parcel_saturday10":"ParcelSaturday10","easypost_dpd_uk_parcel_sunday12":"ParcelSunday12","easypost_dpd_uk_freight_parcel_dpd_classic":"FreightParcelDpdClassic","easypost_dpd_uk_freight_parcel_sunday12":"FreightParcelSunday12","easypost_dpd_uk_expresspak_dpd_next_day":"ExpresspakDpdNextDay","easypost_dpd_uk_expresspak_dpd12":"ExpresspakDpd12","easypost_dpd_uk_expresspak_dpd10":"ExpresspakDpd10","easypost_dpd_uk_expresspak_saturday":"ExpresspakSaturday","easypost_dpd_uk_expresspak_saturday12":"ExpresspakSaturday12","easypost_dpd_uk_expresspak_saturday10":"ExpresspakSaturday10","easypost_dpd_uk_expresspak_sunday12":"ExpresspakSunday12","easypost_dpd_uk_pallet_sunday12":"PalletSunday12","easypost_dpd_uk_pallet_dpd_two_day":"PalletDpdTwoDay","easypost_dpd_uk_pallet_dpd_next_day":"PalletDpdNextDay","easypost_dpd_uk_pallet_dpd12":"PalletDpd12","easypost_dpd_uk_pallet_dpd10":"PalletDpd10","easypost_dpd_uk_pallet_saturday":"PalletSaturday","easypost_dpd_uk_pallet_saturday12":"PalletSaturday12","easypost_dpd_uk_pallet_saturday10":"PalletSaturday10","easypost_dpd_uk_freight_parcel_dpd_two_day":"FreightParcelDpdTwoDay","easypost_dpd_uk_freight_parcel_dpd_next_day":"FreightParcelDpdNextDay","easypost_dpd_uk_freight_parcel_dpd12":"FreightParcelDpd12","easypost_dpd_uk_freight_parcel_dpd10":"FreightParcelDpd10","easypost_dpd_uk_freight_parcel_saturday":"FreightParcelSaturday","easypost_dpd_uk_freight_parcel_saturday12":"FreightParcelSaturday12","easypost_dpd_uk_freight_parcel_saturday10":"FreightParcelSaturday10","easypost_epost_courier_service_ddp":"CourierServiceDDP","easypost_epost_courier_service_ddu":"CourierServiceDDU","easypost_epost_domestic_economy_parcel":"DomesticEconomyParcel","easypost_epost_domestic_parcel_bpm":"DomesticParcelBPM","easypost_epost_domestic_priority_parcel":"DomesticPriorityParcel","easypost_epost_domestic_priority_parcel_bpm":"DomesticPriorityParcelBPM","easypost_epost_emi_service":"EMIService","easypost_epost_economy_parcel_service":"EconomyParcelService","easypost_epost_ipa_service":"IPAService","easypost_epost_isal_service":"ISALService","easypost_epost_pmi_service":"PMIService","easypost_epost_priority_parcel_ddp":"PriorityParcelDDP","easypost_epost_priority_parcel_ddu":"PriorityParcelDDU","easypost_epost_priority_parcel_delivery_confirmation_ddp":"PriorityParcelDeliveryConfirmationDDP","easypost_epost_priority_parcel_delivery_confirmation_ddu":"PriorityParcelDeliveryConfirmationDDU","easypost_epost_epacket_service":"ePacketService","easypost_estafeta_next_day_by930":"NextDayBy930","easypost_estafeta_next_day_by1130":"NextDayBy1130","easypost_estafeta_next_day":"NextDay","easypost_estafeta_two_day":"TwoDay","easypost_estafeta_ltl":"LTL","easypost_fastway_parcel":"Parcel","easypost_fastway_satchel":"Satchel","easypost_fedex_ground":"FEDEX_GROUND","easypost_fedex_2_day":"FEDEX_2_DAY","easypost_fedex_2_day_am":"FEDEX_2_DAY_AM","easypost_fedex_express_saver":"FEDEX_EXPRESS_SAVER","easypost_fedex_standard_overnight":"STANDARD_OVERNIGHT","easypost_fedex_first_overnight":"FIRST_OVERNIGHT","easypost_fedex_priority_overnight":"PRIORITY_OVERNIGHT","easypost_fedex_international_economy":"INTERNATIONAL_ECONOMY","easypost_fedex_international_first":"INTERNATIONAL_FIRST","easypost_fedex_international_priority":"INTERNATIONAL_PRIORITY","easypost_fedex_ground_home_delivery":"GROUND_HOME_DELIVERY","easypost_fedex_crossborder_cbec":"CBEC","easypost_fedex_crossborder_cbecl":"CBECL","easypost_fedex_crossborder_cbecp":"CBECP","easypost_fedex_sameday_city_economy_service":"EconomyService","easypost_fedex_sameday_city_standard_service":"StandardService","easypost_fedex_sameday_city_priority_service":"PriorityService","easypost_fedex_sameday_city_last_mile":"LastMile","easypost_fedex_smart_post":"SMART_POST","easypost_globegistics_pmei":"PMEI","easypost_globegistics_ecom_domestic":"eComDomestic","easypost_globegistics_ecom_europe":"eComEurope","easypost_globegistics_ecom_express":"eComExpress","easypost_globegistics_ecom_extra":"eComExtra","easypost_globegistics_ecom_ipa":"eComIPA","easypost_globegistics_ecom_isal":"eComISAL","easypost_globegistics_ecom_pmei_duty_paid":"eComPMEIDutyPaid","easypost_globegistics_ecom_pmi_duty_paid":"eComPMIDutyPaid","easypost_globegistics_ecom_packet":"eComPacket","easypost_globegistics_ecom_packet_ddp":"eComPacketDDP","easypost_globegistics_ecom_priority":"eComPriority","easypost_globegistics_ecom_standard":"eComStandard","easypost_globegistics_ecom_tracked_ddp":"eComTrackedDDP","easypost_globegistics_ecom_tracked_ddu":"eComTrackedDDU","easypost_gso_early_priority_overnight":"EarlyPriorityOvernight","easypost_gso_priority_overnight":"PriorityOvernight","easypost_gso_california_parcel_service":"CaliforniaParcelService","easypost_gso_saturday_delivery_service":"SaturdayDeliveryService","easypost_gso_early_saturday_service":"EarlySaturdayService","easypost_hermes_domestic_delivery":"DomesticDelivery","easypost_hermes_domestic_delivery_signed":"DomesticDeliverySigned","easypost_hermes_international_delivery":"InternationalDelivery","easypost_hermes_international_delivery_signed":"InternationalDeliverySigned","easypost_interlink_air_classic_international_air":"InterlinkAirClassicInternationalAir","easypost_interlink_air_express_international_air":"InterlinkAirExpressInternationalAir","easypost_interlink_expresspak1_by10_30":"InterlinkExpresspak1By10:30","easypost_interlink_expresspak1_by12":"InterlinkExpresspak1By12","easypost_interlink_expresspak1_next_day":"InterlinkExpresspak1NextDay","easypost_interlink_expresspak1_saturday":"InterlinkExpresspak1Saturday","easypost_interlink_expresspak1_saturday_by10_30":"InterlinkExpresspak1SaturdayBy10:30","easypost_interlink_expresspak1_saturday_by12":"InterlinkExpresspak1SaturdayBy12","easypost_interlink_expresspak1_sunday":"InterlinkExpresspak1Sunday","easypost_interlink_expresspak1_sunday_by12":"InterlinkExpresspak1SundayBy12","easypost_interlink_expresspak5_by10":"InterlinkExpresspak5By10","easypost_interlink_expresspak5_by10_30":"InterlinkExpresspak5By10:30","easypost_interlink_expresspak5_by12":"InterlinkExpresspak5By12","easypost_interlink_expresspak5_next_day":"InterlinkExpresspak5NextDay","easypost_interlink_expresspak5_saturday":"InterlinkExpresspak5Saturday","easypost_interlink_expresspak5_saturday_by10":"InterlinkExpresspak5SaturdayBy10","easypost_interlink_expresspak5_saturday_by10_30":"InterlinkExpresspak5SaturdayBy10:30","easypost_interlink_expresspak5_saturday_by12":"InterlinkExpresspak5SaturdayBy12","easypost_interlink_expresspak5_sunday":"InterlinkExpresspak5Sunday","easypost_interlink_expresspak5_sunday_by12":"InterlinkExpresspak5SundayBy12","easypost_interlink_freight_by10":"InterlinkFreightBy10","easypost_interlink_freight_by12":"InterlinkFreightBy12","easypost_interlink_freight_next_day":"InterlinkFreightNextDay","easypost_interlink_freight_saturday":"InterlinkFreightSaturday","easypost_interlink_freight_saturday_by10":"InterlinkFreightSaturdayBy10","easypost_interlink_freight_saturday_by12":"InterlinkFreightSaturdayBy12","easypost_interlink_freight_sunday":"InterlinkFreightSunday","easypost_interlink_freight_sunday_by12":"InterlinkFreightSundayBy12","easypost_interlink_parcel_by10":"InterlinkParcelBy10","easypost_interlink_parcel_by10_30":"InterlinkParcelBy10:30","easypost_interlink_parcel_by12":"InterlinkParcelBy12","easypost_interlink_parcel_dpd_europe_by_road":"InterlinkParcelDpdEuropeByRoad","easypost_interlink_parcel_next_day":"InterlinkParcelNextDay","easypost_interlink_parcel_return":"InterlinkParcelReturn","easypost_interlink_parcel_return_to_shop":"InterlinkParcelReturnToShop","easypost_interlink_parcel_saturday":"InterlinkParcelSaturday","easypost_interlink_parcel_saturday_by10":"InterlinkParcelSaturdayBy10","easypost_interlink_parcel_saturday_by10_30":"InterlinkParcelSaturdayBy10:30","easypost_interlink_parcel_saturday_by12":"InterlinkParcelSaturdayBy12","easypost_interlink_parcel_ship_to_shop":"InterlinkParcelShipToShop","easypost_interlink_parcel_sunday":"InterlinkParcelSunday","easypost_interlink_parcel_sunday_by12":"InterlinkParcelSundayBy12","easypost_interlink_parcel_two_day":"InterlinkParcelTwoDay","easypost_interlink_pickup_parcel_dpd_europe_by_road":"InterlinkPickupParcelDpdEuropeByRoad","easypost_lasership_weekend":"Weekend","easypost_loomis_ground":"LoomisGround","easypost_loomis_express1800":"LoomisExpress1800","easypost_loomis_express1200":"LoomisExpress1200","easypost_loomis_express900":"LoomisExpress900","easypost_lso_ground_early":"GroundEarly","easypost_lso_ground_basic":"GroundBasic","easypost_lso_priority_basic":"PriorityBasic","easypost_lso_priority_early":"PriorityEarly","easypost_lso_priority_saturday":"PrioritySaturday","easypost_lso_priority2nd_day":"Priority2ndDay","easypost_newgistics_parcel_select":"ParcelSelect","easypost_newgistics_parcel_select_lightweight":"ParcelSelectLightweight","easypost_newgistics_express":"Express","easypost_newgistics_first_class_mail":"FirstClassMail","easypost_newgistics_priority_mail":"PriorityMail","easypost_newgistics_bound_printed_matter":"BoundPrintedMatter","easypost_ontrac_sunrise":"Sunrise","easypost_ontrac_gold":"Gold","easypost_ontrac_on_trac_ground":"OnTracGround","easypost_ontrac_palletized_freight":"PalletizedFreight","easypost_osm_first":"First","easypost_osm_expedited":"Expedited","easypost_osm_bpm":"BPM","easypost_osm_media_mail":"MediaMail","easypost_osm_marketing_parcel":"MarketingParcel","easypost_osm_marketing_parcel_tracked":"MarketingParcelTracked","easypost_parcll_economy_west":"Economy West","easypost_parcll_economy_east":"Economy East","easypost_parcll_economy_central":"Economy Central","easypost_parcll_economy_northeast":"Economy Northeast","easypost_parcll_economy_south":"Economy South","easypost_parcll_expedited_west":"Expedited West","easypost_parcll_expedited_northeast":"Expedited Northeast","easypost_parcll_regional_west":"Regional West","easypost_parcll_regional_east":"Regional East","easypost_parcll_regional_central":"Regional Central","easypost_parcll_regional_northeast":"Regional Northeast","easypost_parcll_regional_south":"Regional South","easypost_parcll_us_to_canada_economy_west":"US to Canada Economy West","easypost_parcll_us_to_canada_economy_central":"US to Canada Economy Central","easypost_parcll_us_to_canada_economy_northeast":"US to Canada Economy Northeast","easypost_parcll_us_to_europe_economy_west":"US to Europe Economy West","easypost_parcll_us_to_europe_economy_northeast":"US to Europe Economy Northeast","easypost_purolator_express":"PurolatorExpress","easypost_purolator_express12_pm":"PurolatorExpress12PM","easypost_purolator_express_pack12_pm":"PurolatorExpressPack12PM","easypost_purolator_express_box12_pm":"PurolatorExpressBox12PM","easypost_purolator_express_envelope12_pm":"PurolatorExpressEnvelope12PM","easypost_purolator_express1030_am":"PurolatorExpress1030AM","easypost_purolator_express9_am":"PurolatorExpress9AM","easypost_purolator_express_box":"PurolatorExpressBox","easypost_purolator_express_box1030_am":"PurolatorExpressBox1030AM","easypost_purolator_express_box9_am":"PurolatorExpressBox9AM","easypost_purolator_express_box_evening":"PurolatorExpressBoxEvening","easypost_purolator_express_box_international":"PurolatorExpressBoxInternational","easypost_purolator_express_box_international1030_am":"PurolatorExpressBoxInternational1030AM","easypost_purolator_express_box_international1200":"PurolatorExpressBoxInternational1200","easypost_purolator_express_box_international9_am":"PurolatorExpressBoxInternational9AM","easypost_purolator_express_box_us":"PurolatorExpressBoxUS","easypost_purolator_express_box_us1030_am":"PurolatorExpressBoxUS1030AM","easypost_purolator_express_box_us1200":"PurolatorExpressBoxUS1200","easypost_purolator_express_box_us9_am":"PurolatorExpressBoxUS9AM","easypost_purolator_express_envelope":"PurolatorExpressEnvelope","easypost_purolator_express_envelope1030_am":"PurolatorExpressEnvelope1030AM","easypost_purolator_express_envelope9_am":"PurolatorExpressEnvelope9AM","easypost_purolator_express_envelope_evening":"PurolatorExpressEnvelopeEvening","easypost_purolator_express_envelope_international":"PurolatorExpressEnvelopeInternational","easypost_purolator_express_envelope_international1030_am":"PurolatorExpressEnvelopeInternational1030AM","easypost_purolator_express_envelope_international1200":"PurolatorExpressEnvelopeInternational1200","easypost_purolator_express_envelope_international9_am":"PurolatorExpressEnvelopeInternational9AM","easypost_purolator_express_envelope_us":"PurolatorExpressEnvelopeUS","easypost_purolator_express_envelope_us1030_am":"PurolatorExpressEnvelopeUS1030AM","easypost_purolator_express_envelope_us1200":"PurolatorExpressEnvelopeUS1200","easypost_purolator_express_envelope_us9_am":"PurolatorExpressEnvelopeUS9AM","easypost_purolator_express_evening":"PurolatorExpressEvening","easypost_purolator_express_international":"PurolatorExpressInternational","easypost_purolator_express_international1030_am":"PurolatorExpressInternational1030AM","easypost_purolator_express_international1200":"PurolatorExpressInternational1200","easypost_purolator_express_international9_am":"PurolatorExpressInternational9AM","easypost_purolator_express_pack":"PurolatorExpressPack","easypost_purolator_express_pack1030_am":"PurolatorExpressPack1030AM","easypost_purolator_express_pack9_am":"PurolatorExpressPack9AM","easypost_purolator_express_pack_evening":"PurolatorExpressPackEvening","easypost_purolator_express_pack_international":"PurolatorExpressPackInternational","easypost_purolator_express_pack_international1030_am":"PurolatorExpressPackInternational1030AM","easypost_purolator_express_pack_international1200":"PurolatorExpressPackInternational1200","easypost_purolator_express_pack_international9_am":"PurolatorExpressPackInternational9AM","easypost_purolator_express_pack_us":"PurolatorExpressPackUS","easypost_purolator_express_pack_us1030_am":"PurolatorExpressPackUS1030AM","easypost_purolator_express_pack_us1200":"PurolatorExpressPackUS1200","easypost_purolator_express_pack_us9_am":"PurolatorExpressPackUS9AM","easypost_purolator_express_us":"PurolatorExpressUS","easypost_purolator_express_us1030_am":"PurolatorExpressUS1030AM","easypost_purolator_express_us1200":"PurolatorExpressUS1200","easypost_purolator_express_us9_am":"PurolatorExpressUS9AM","easypost_purolator_ground":"PurolatorGround","easypost_purolator_ground1030_am":"PurolatorGround1030AM","easypost_purolator_ground9_am":"PurolatorGround9AM","easypost_purolator_ground_distribution":"PurolatorGroundDistribution","easypost_purolator_ground_evening":"PurolatorGroundEvening","easypost_purolator_ground_regional":"PurolatorGroundRegional","easypost_purolator_ground_us":"PurolatorGroundUS","easypost_royalmail_international_signed":"InternationalSigned","easypost_royalmail_international_tracked":"InternationalTracked","easypost_royalmail_international_tracked_and_signed":"InternationalTrackedAndSigned","easypost_royalmail_1st_class":"1stClass","easypost_royalmail_1st_class_signed_for":"1stClassSignedFor","easypost_royalmail_2nd_class":"2ndClass","easypost_royalmail_2nd_class_signed_for":"2ndClassSignedFor","easypost_royalmail_royal_mail24":"RoyalMail24","easypost_royalmail_royal_mail24_signed_for":"RoyalMail24SignedFor","easypost_royalmail_royal_mail48":"RoyalMail48","easypost_royalmail_royal_mail48_signed_for":"RoyalMail48SignedFor","easypost_royalmail_special_delivery_guaranteed1pm":"SpecialDeliveryGuaranteed1pm","easypost_royalmail_special_delivery_guaranteed9am":"SpecialDeliveryGuaranteed9am","easypost_royalmail_standard_letter1st_class":"StandardLetter1stClass","easypost_royalmail_standard_letter1st_class_signed_for":"StandardLetter1stClassSignedFor","easypost_royalmail_standard_letter2nd_class":"StandardLetter2ndClass","easypost_royalmail_standard_letter2nd_class_signed_for":"StandardLetter2ndClassSignedFor","easypost_royalmail_tracked24":"Tracked24","easypost_royalmail_tracked24_high_volume":"Tracked24HighVolume","easypost_royalmail_tracked24_high_volume_signature":"Tracked24HighVolumeSignature","easypost_royalmail_tracked24_signature":"Tracked24Signature","easypost_royalmail_tracked48":"Tracked48","easypost_royalmail_tracked48_high_volume":"Tracked48HighVolume","easypost_royalmail_tracked48_high_volume_signature":"Tracked48HighVolumeSignature","easypost_royalmail_tracked48_signature":"Tracked48Signature","easypost_seko_ecommerce_standard_tracked":"eCommerce Standard Tracked","easypost_seko_ecommerce_express_tracked":"eCommerce Express Tracked","easypost_seko_domestic_express":"Domestic Express","easypost_seko_domestic_standard":"Domestic Standard","easypost_sendle_easy":"Easy","easypost_sendle_pro":"Pro","easypost_sendle_plus":"Plus","easypost_sfexpress_international_standard_express_doc":"International Standard Express - Doc","easypost_sfexpress_international_standard_express_parcel":"International Standard Express - Parcel","easypost_sfexpress_international_economy_express_pilot":"International Economy Express - Pilot","easypost_sfexpress_international_economy_express_doc":"International Economy Express - Doc","easypost_speedee_delivery":"SpeeDeeDelivery","easypost_startrack_express":"StartrackExpress","easypost_startrack_premium":"StartrackPremium","easypost_startrack_fixed_price_premium":"StartrackFixedPricePremium","easypost_tforce_same_day_white_glove":"SameDayWhiteGlove","easypost_tforce_next_day_white_glove":"NextDayWhiteGlove","easypost_uds_delivery_service":"DeliveryService","easypost_ups_standard":"UPSStandard","easypost_ups_saver":"UPSSaver","easypost_ups_express_plus":"ExpressPlus","easypost_ups_next_day_air":"NextDayAir","easypost_ups_next_day_air_saver":"NextDayAirSaver","easypost_ups_next_day_air_early_am":"NextDayAirEarlyAM","easypost_ups_2nd_day_air":"2ndDayAir","easypost_ups_2nd_day_air_am":"2ndDayAirAM","easypost_ups_3_day_select":"3DaySelect","easypost_ups_mail_expedited_mail_innovations":"ExpeditedMailInnovations","easypost_ups_mail_priority_mail_innovations":"PriorityMailInnovations","easypost_ups_mail_economy_mail_innovations":"EconomyMailInnovations","easypost_usps_library_mail":"LibraryMail","easypost_usps_first_class_mail_international":"FirstClassMailInternational","easypost_usps_first_class_package_international_service":"FirstClassPackageInternationalService","easypost_usps_priority_mail_international":"PriorityMailInternational","easypost_usps_express_mail_international":"ExpressMailInternational","easypost_veho_next_day":"nextDay","easypost_veho_same_day":"sameDay"},"shipping_options":{"easypost_additional_handling":{"code":"additional_handling","meta":{"configurable":true},"type":"boolean","label":"Additional Handling"},"easypost_address_validation_level":{"code":"address_validation_level","meta":{"configurable":true},"type":"string","label":"Address Validation Level"},"easypost_alcohol":{"code":"alcohol","meta":{"configurable":true},"type":"boolean","label":"Alcohol"},"easypost_by_drone":{"code":"by_drone","meta":{"configurable":true},"type":"boolean","label":"By Drone"},"easypost_carbon_neutral":{"code":"carbon_neutral","meta":{"configurable":true},"type":"boolean","label":"Carbon Neutral"},"easypost_cod_amount":{"code":"cod_amount","meta":{"configurable":true},"type":"string","label":"Cod Amount"},"easypost_cod_method":{"code":"cod_method","meta":{"configurable":true},"type":"string","label":"Cod Method"},"easypost_cod_address_id":{"code":"cod_address_id","meta":{"configurable":true},"type":"string","label":"Cod Address Id"},"easypost_currency":{"code":"currency","meta":{"configurable":true},"type":"string","label":"Currency"},"easypost_delivery_confirmation":{"code":"delivery_confirmation","meta":{"configurable":true},"type":"string","label":"Delivery Confirmation"},"easypost_dropoff_type":{"code":"dropoff_type","meta":{"configurable":true},"type":"string","label":"Dropoff Type"},"easypost_dry_ice":{"code":"dry_ice","meta":{"configurable":true},"type":"boolean","label":"Dry Ice"},"easypost_dry_ice_medical":{"code":"dry_ice_medical","meta":{"configurable":true},"type":"boolean","label":"Dry Ice Medical"},"easypost_dry_ice_weight":{"code":"dry_ice_weight","meta":{"configurable":true},"type":"string","label":"Dry Ice Weight"},"easypost_endorsement":{"code":"endorsement","meta":{"configurable":true},"type":"string","label":"Endorsement"},"easypost_freight_charge":{"code":"freight_charge","meta":{"configurable":true},"type":"float","label":"Freight Charge"},"easypost_handling_instructions":{"code":"handling_instructions","meta":{"configurable":true},"type":"string","label":"Handling Instructions"},"easypost_hazmat":{"code":"hazmat","meta":{"configurable":true},"type":"string","label":"Hazmat"},"easypost_hold_for_pickup":{"code":"hold_for_pickup","meta":{"configurable":true},"type":"boolean","label":"Hold For Pickup"},"easypost_incoterm":{"code":"incoterm","meta":{"configurable":true},"type":"string","label":"Incoterm"},"easypost_invoice_number":{"code":"invoice_number","meta":{"configurable":true},"type":"string","label":"Invoice Number"},"easypost_label_date":{"code":"label_date","meta":{"configurable":true},"type":"string","label":"Label Date"},"easypost_label_format":{"code":"label_format","meta":{"configurable":true},"type":"string","label":"Label Format"},"easypost_machinable":{"code":"machinable","meta":{"configurable":true},"type":"boolean","label":"Machinable"},"easypost_payment":{"code":"payment","meta":{"configurable":true},"type":"object","label":"Payment"},"easypost_print_custom_1":{"code":"print_custom_1","meta":{"configurable":true},"type":"string","label":"Print Custom 1"},"easypost_print_custom_2":{"code":"print_custom_2","meta":{"configurable":true},"type":"string","label":"Print Custom 2"},"easypost_print_custom_3":{"code":"print_custom_3","meta":{"configurable":true},"type":"string","label":"Print Custom 3"},"easypost_print_custom_1_barcode":{"code":"print_custom_1_barcode","meta":{"configurable":true},"type":"string","label":"Print Custom 1 Barcode"},"easypost_print_custom_2_barcode":{"code":"print_custom_2_barcode","meta":{"configurable":true},"type":"string","label":"Print Custom 2 Barcode"},"easypost_print_custom_3_barcode":{"code":"print_custom_3_barcode","meta":{"configurable":true},"type":"string","label":"Print Custom 3 Barcode"},"easypost_print_custom_1_code":{"code":"print_custom_1_code","meta":{"configurable":true},"type":"string","label":"Print Custom 1 Code"},"easypost_print_custom_2_code":{"code":"print_custom_2_code","meta":{"configurable":true},"type":"string","label":"Print Custom 2 Code"},"easypost_print_custom_3_code":{"code":"print_custom_3_code","meta":{"configurable":true},"type":"string","label":"Print Custom 3 Code"},"easypost_saturday_delivery":{"code":"saturday_delivery","meta":{"configurable":true},"type":"boolean","label":"Saturday Delivery"},"easypost_special_rates_eligibility":{"code":"special_rates_eligibility","meta":{"configurable":true},"type":"string","label":"Special Rates Eligibility"},"easypost_smartpost_hub":{"code":"smartpost_hub","meta":{"configurable":true},"type":"string","label":"Smartpost Hub"},"easypost_smartpost_manifest":{"code":"smartpost_manifest","meta":{"configurable":true},"type":"string","label":"Smartpost Manifest"},"easypost_billing_ref":{"code":"billing_ref","meta":{"configurable":true},"type":"string","label":"Billing Ref"},"easypost_certified_mail":{"code":"certified_mail","meta":{"configurable":true},"type":"boolean","label":"Certified Mail"},"easypost_registered_mail":{"code":"registered_mail","meta":{"configurable":true},"type":"boolean","label":"Registered Mail"},"easypost_registered_mail_amount":{"code":"registered_mail_amount","meta":{"configurable":true},"type":"float","label":"Registered Mail Amount"},"easypost_return_receipt":{"code":"return_receipt","meta":{"configurable":true},"type":"boolean","label":"Return Receipt"}},"readme":null},{"id":"easyship","carrier_name":"easyship","display_name":"Easyship","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Manifest","Rating","Pickup","Shipping","Tracking"],"connection_fields":{"access_token":{"name":"access_token","required":true,"sensitive":false,"type":"string","label":"Access Token"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"platform_name":{"code":"platform_name","name":"platform_name","required":false,"type":"string","label":"Platform Name"},"apply_shipping_rules":{"code":"apply_shipping_rules","name":"apply_shipping_rules","required":false,"type":"boolean","label":"Apply Shipping Rules"},"allow_courier_fallback":{"code":"allow_courier_fallback","name":"allow_courier_fallback","required":false,"type":"boolean","label":"Allow Courier Fallback"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"easyship_aramex_parcel":"Parcel","easyship_sfexpress_domestic":"Domestic","easyship_hkpost_speedpost":"Speedpost","easyship_hkpost_air_mail_tracking":"Air Mail Tracking","easyship_hkpost_eexpress":"EExpress","easyship_hkpost_air_parcel":"Air Parcel ","easyship_sfexpress_mail":"Mail ","easyship_hkpost_local_parcel":"Local Parcel","easyship_ups_saver_net_battery":"SaverNet Battery","easyship_ups_worldwide_saver":"Worldwide Saver®","easyship_hkpost_air_parcel_xp":"Air Parcel XP","easyship_singpost_airmail":"Airmail","easyship_simplypost_express":"Express","easyship_singpost_e_pack":"ePack","easyship_usps_priority_mail_express":"Priority Mail Express","easyship_usps_first_class_international":"First Class International","easyship_usps_priority_mail_international_express":"Priority Mail International Express","easyship_usps_priority_mail_international":"Priority Mail International","easyship_fedex_international_priority":"InternationalPriority","easyship_usps_ground_advantage":"GroundAdvantage","easyship_usps_priority_mail":"PriorityMail","easyship_ups_worldwide_express":"Worldwide Express®","easyship_ups_ground":"Ground","easyship_ups_worldwide_expedited":"Worldwide Expedited®","easyship_fedex_international_economy":"International Economy®","easyship_fedex_priority_overnight":"Priority Overnight®","easyship_fedex_standard_overnight":"Standard Overnight®","easyship_fedex_2_day_a_m":"2Day® A.M.","easyship_fedex_2_day":"2Day®","easyship_fedex_express_saver":"Express Saver®","easyship_ups_next_day_air":"Next Day Air®","easyship_ups_2nd_day_air":"2nd Day Air®","easyship_ups_3_day_select":"3DaySelect","easyship_ups_standard":"Standard","easyship_usps_media":"Media","easyship_sfexpress_standard_express":"Standard Express","easyship_sfexpress_economy_express":"Economy Express","easyship_global_post_global_post_economy":"GlobalPost Economy","easyship_global_post_global_post_priority":"GlobalPost Priority","easyship_singpost_speed_post_priority":"SpeedPost Priority","easyship_skypostal_standard_private_delivery":"Standard Private Delivery","easyship_tnt_1000_express":"1000Express","easyship_toll_express_parcel":"Express Parcel","easyship_sendle_premium_international":"Premium International","easyship_sendle_premium_domestic":"PremiumDomestic","easyship_sendle_pro_domestic":"Pro Domestic","easyship_quantium_e_pac":"ePac","easyship_usps_pm_flat_rate":"PM Flat Rate","easyship_usps_pmi_flat_rate":"PMI Flat Rate","easyship_quantium_mail":"Mail","easyship_quantium_international_mail":"International Mail","easyship_apc_parcel_connect_expedited":"ParcelConnect Expedited","easyship_aramex_epx":"EPX","easyship_tnt_road_express":"Road Express","easyship_tnt_overnight":"Overnight","easyship_usps_pme_flat_rate":"PME Flat Rate","easyship_usps_pmei_flat_rate":"PMEI Flat Rate","easyship_easyship_cdek_russia":"CDEK Russia","easyship_usps_pmei_flat_rate_padded_envelope":"PMEI Flat Rate Padded Envelope","easyship_easyship_mate_bike_shipping_services":"Mate Bike Shipping Services","easyship_dhl_express_documents":"Documents","easyship_evri_uk_home_delivery":"UK_HomeDelivery","easyship_evri_home_delivery":"HomeDelivery","easyship_dpd_next_day":"NextDay","easyship_dpd_classic_parcel":"ClassicParcel","easyship_dpd_classic_expresspak":"ClassicExpresspak","easyship_dpd_air_classic":"AirClassic","easyship_singpost_speed_post_express":"SpeedPostExpress","easyship_ups_expedited":"Expedited","easyship_tnt_0900_express":"0900Express","easyship_tnt_1200_express":"1200Express","easyship_canadapost_domestic_regular_parcel":"Domestic Regular Parcel","easyship_canadapost_domestic_expedited_parcel":"Domestic Expedited Parcel","easyship_canadapost_domestic_xpresspost_domestic":"Domestic Xpresspost Domestic","easyship_canadapost_domestic_priority":"Domestic Priority","easyship_canadapost_usa_small_packet_air":"USA Small Packet Air","easyship_canadapost_usa_expedited_parcel":"USA Expedited Parcel","easyship_canadapost_usa_tracked_parcel":"USA Tracked Parcel","easyship_canadapost_usa_xpresspost":"USA Xpresspost","easyship_canadapost_international_xpresspost":"International Xpresspost","easyship_canadapost_international_small_packet_air":"International Small Packet Air","easyship_canadapost_international_tracked_packet":"International Tracked Packet","easyship_canadapost_international_small_packet_surface":"International Small Packet Surface","easyship_canadapost_international_parcel_surface":"International Parcel Surface","easyship_canadapost_international_parcel_air":"International Parcel Air","easyship_couriersplease_atl":"ATL","easyship_couriersplease_signature":"Signature","easyship_canpar_international":"International","easyship_canpar_usa":"USA","easyship_canpar_select_usa":"Select USA","easyship_canpar_usa_pak":"USA Pak","easyship_canpar_overnight_pak":"Overnight Pak","easyship_canpar_select_pak":"Select Pak","easyship_canpar_select":"Select","easyship_ups_express_saver":"ExpressSaver","easyship_ebay_send_sf_express_economy_express":"SF Express Economy Express","easyship_ups_worldwide_express_plus":"Worldwide Express Plus®","easyship_quantium_intl_priority":"IntlPriority","easyship_ups_next_day_air_early":"Next Day Air® Early","easyship_ups_next_day_air_saver":"Next Day Air Saver®","easyship_ups_2nd_day_air_a_m":"2nd Day Air® A.M.","easyship_fedex_home_delivery":"Home Delivery®","easyship_asendia_country_tracked":"CountryTracked","easyship_asendia_fully_tracked":"FullyTracked","easyship_dhl_express_express_dg":"ExpressDG","easyship_fedex_international_priority_dg":"InternationalPriorityDG","easyship_colissimo_expert":"Expert","easyship_colissimo_access":"Access","easyship_mondialrelay_international_home_delivery":"InternationalHomeDelivery","easyship_fedex_economy":"Economy","easyship_dhl_express_express1200":"Express1200","easyship_dhl_express_express0900":"Express0900","easyship_dhl_express_express1800":"Express1800","easyship_dhl_express_express_worldwide":"ExpressWorldwide","easyship_dhl_express_economy_select":"EconomySelect","easyship_dhl_express_express1030_international":"Express1030International","easyship_dhl_express_domestic_express0900":"DomesticExpress0900","easyship_dhl_express_domestic_express1200":"DomesticExpress1200","easyship_evri_lightand_large":"LightandLarge","easyship_ninjavan_standard_deliveries":"Standard Deliveries","easyship_couriersplease_parcel_tier2":"ParcelTier2","easyship_skypostal_postal_packet_standard":"Postal Packet Standard","easyship_easyshipdemo_basic":"Basic","easyship_easyshipdemo_tracked":"Tracked","easyship_easyshipdemo_battery":"Battery","easyship_dhl_express_domestic_express":"DomesticExpress","easyship_fedex_smart_post":"SmartPost","easyship_fedex_international_connect_plus":"InternationalConnectPlus","easyship_ups_saver_net":"SaverNet","easyship_chronopost_chrono_classic":"ChronoClassic","easyship_chronopost_chrono_express":"ChronoExpress","easyship_chronopost_chrono10":"Chrono10","easyship_chronopost_chrono13":"Chrono13","easyship_chronopost_chrono18":"Chrono18","easyship_omniparcel_parcel_expedited":"Parcel Expedited","easyship_omniparcel_parcel_expedited_plus":"Parcel Expedited Plus","easyship_evri_home_delivery_domestic":"HomeDeliveryDomestic","easyship_evri_home_domestic_postable":"HomeDomesticPostable","easyship_skypostal_packet_express":"PacketExpress","easyship_parcelforce_express48_large":"Express48Large","easyship_parcelforce_express24":"Express24","easyship_parcelforce_express1000":"Express1000","easyship_parcelforce_express_am":"ExpressAM","easyship_parcelforce_express48":"Express48","easyship_parcelforce_euro_economy":"EuroEconomy","easyship_parcelforce_global_priority":"GlobalPriority","easyship_fedex_cross_border_trakpak_worldwide_hermes":"TrakpakWorldwideHermes","easyship_fedex_cross_border_trakpak_worldwide":"TrakpakWorldwide","easyship_evri_home_domestic_postable_next_day":"HomeDomesticPostableNextDay","easyship_dpd_express_pak_next_day":"ExpressPakNextDay","easyship_dpd_classic_express_pak":"ClassicExpressPak","easyship_evri_light_and_large":"LightAndLarge","easyship_evri_home_delivery_domestic_next_day":"Home Delivery Domestic NextDay","easyship_evri_home_delivery_eu":"HomeDeliveryEU","easyship_asendia_epaq_plus":"EpaqPlus","easyship_asendia_epaq_select":"EpaqSelect","easyship_usps_lightweight_standard":"LightweightStandard","easyship_usps_lightweight_economy":"LightweightEconomy","easyship_ups_domestic_express_saver":"DomesticExpressSaver","easyship_apg_e_packet":"ePacket","easyship_apg_e_packet_plus":"ePacketPlus","easyship_couriersplease_ecom_base_kilo":"EComBaseKilo","easyship_couriersplease_stdatlbase_kilo":"STDATLBaseKilo","easyship_nz_post_international_courier":"InternationalCourier","easyship_nz_post_air_small_parcel":"AirSmallParcel","easyship_nz_post_tracked_air_satchel":"TrackedAirSatchel","easyship_nz_post_economy_parcel":"Economy Parcel","easyship_nz_post_parcel_local":"ParcelLocal","easyship_dhl_express_express_domestic":"ExpressDomestic","easyship_alliedexpress_roadexpress":"Roadexpress","easyship_flatexportrate_asendiae_paqselect":"AsendiaePAQSelect","easyship_flatexportrate_asendia_country_tracked":"AsendiaCountryTracked","easyship_singpost_nsaver":"NSaver","easyship_colisprive_home":"Home","easyship_osm_domestic_parcel":"Domestic Parcel","easyship_malca_amit_door_to_door":"Door To Door","easyship_ninjavan_next_day_deliveries":"Next Day Deliveries","easyship_asendia_e_paqselect":"ePAQSelect","easyship_dpd_classic":"Classic","easyship_usps_priority_mail_signature":"PriorityMailSignature","easyship_bringer_packet_standard":"PacketStandard","easyship_bringer_prime":"Prime","easyship_orangeds_expedited_ddp":"ExpeditedDDP","easyship_orangeds_expedited_ddu":"ExpeditedDDU","easyship_sendle_preferred":"Preferred","easyship_ups_ground_saver":"GroundSaver","easyship_ups_upsground_saver_us":"UPSGroundSaverUS","easyship_passport_priority_delcon_dduewr":"PriorityDelconDDUEWR","easyship_passport_priority_delcon_ddpewr":"PriorityDelconDDPEWR","easyship_bringer_tracked_parcel":"TrackedParcel","easyship_ups_express_early":"ExpressEarly","easyship_ups_wolrdwide_express":"WolrdwideExpress"},"shipping_options":{"easyship_box_slug":{"code":"box_slug","meta":{"configurable":true},"type":"string","label":"Box Slug"},"easyship_courier_id":{"code":"courier_id","meta":{"configurable":true},"type":"string","label":"Courier Id"},"easyship_eei_reference":{"code":"eei_reference","meta":{"configurable":true},"type":"string","label":"Eei Reference"},"easyship_incoterms":{"code":"incoterms","enum":["DDU","DDP"],"meta":{"configurable":true},"type":"string","label":"Incoterms"},"easyship_apply_shipping_rules":{"code":"apply_shipping_rules","meta":{"configurable":true},"type":"boolean","label":"Apply Shipping Rules"},"easyship_show_courier_logo_url":{"code":"show_courier_logo_url","meta":{"configurable":true},"type":"boolean","label":"Show Courier Logo Url"},"easyship_allow_courier_fallback":{"code":"allow_courier_fallback","meta":{"configurable":true},"type":"boolean","label":"Allow Courier Fallback"},"easyship_list_unavailable_couriers":{"code":"list_unavailable_couriers","meta":{"configurable":true},"type":"boolean","label":"List Unavailable Couriers"},"easyship_buyer_notes":{"code":"buyer_notes","meta":{"configurable":true},"type":"string","label":"Buyer Notes"},"easyship_seller_notes":{"code":"seller_notes","meta":{"configurable":true},"type":"string","label":"Seller Notes"},"easyship_sender_address_id":{"code":"sender_address_id","meta":{"configurable":true},"type":"string","label":"Sender Address Id"},"easyship_return_address_id":{"code":"return_address_id","meta":{"configurable":true},"type":"string","label":"Return Address Id"}},"readme":null},{"id":"eshipper","carrier_name":"eshipper","display_name":"eShipper","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"principal":{"name":"principal","required":true,"sensitive":false,"type":"string","label":"Principal"},"credential":{"name":"credential","required":true,"sensitive":false,"type":"string","label":"Credential"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"eshipper_aramex_economy_document_express":"eshipper_aramex_economy_document_express","eshipper_aramex_economy_parcel_express":"eshipper_aramex_economy_parcel_express","eshipper_aramex_priority_letter_express":"eshipper_aramex_priority_letter_express","eshipper_aramex_priority_parcel_express":"eshipper_aramex_priority_parcel_express","eshipper_canadapost_air_parcel_intl":"eshipper_canadapost_air_parcel_intl","eshipper_canadapost_expedited":"eshipper_canadapost_expedited","eshipper_canadapost_expedited_parcel_usa":"eshipper_canadapost_expedited_parcel_usa","eshipper_canadapost_priority_courier":"eshipper_canadapost_priority_courier","eshipper_canadapost_regular":"eshipper_canadapost_regular","eshipper_canadapost_small_packet":"eshipper_canadapost_small_packet","eshipper_canadapost_small_packet_international_air":"eshipper_canadapost_small_packet_international_air","eshipper_canadapost_small_packet_international_surface":"eshipper_canadapost_small_packet_international_surface","eshipper_canadapost_surface_parcel_intl":"eshipper_canadapost_surface_parcel_intl","eshipper_canadapost_xpress_post":"eshipper_canadapost_xpress_post","eshipper_canadapost_xpress_post_intl":"eshipper_canadapost_xpress_post_intl","eshipper_canadapost_xpress_post_usa":"eshipper_canadapost_xpress_post_usa","eshipper_canpar_express_letter":"eshipper_canpar_express_letter","eshipper_canpar_express_pak":"eshipper_canpar_express_pak","eshipper_canpar_express_parcel":"eshipper_canpar_express_parcel","eshipper_canpar_ground":"eshipper_canpar_ground","eshipper_canpar_international":"eshipper_canpar_international","eshipper_canpar_select_letter":"eshipper_canpar_select_letter","eshipper_canpar_select_pak":"eshipper_canpar_select_pak","eshipper_canpar_select_parcel":"eshipper_canpar_select_parcel","eshipper_canpar_usa":"eshipper_canpar_usa","eshipper_canpar_usa_select_letter":"eshipper_canpar_usa_select_letter","eshipper_canpar_usa_select_pak":"eshipper_canpar_usa_select_pak","eshipper_canpar_usa_select_parcel":"eshipper_canpar_usa_select_parcel","eshipper_cpx_canada_post":"eshipper_cpx_canada_post","eshipper_day_ross_ltl":"eshipper_day_ross_ltl","eshipper_dhl_ground":"eshipper_dhl_ground","eshipper_dhl_economy_select":"eshipper_dhl_economy_select","eshipper_dhl_esi_export":"eshipper_dhl_esi_export","eshipper_dhl_express_1030am":"eshipper_dhl_express_1030am","eshipper_dhl_express_12pm":"eshipper_dhl_express_12pm","eshipper_dhl_express_9am":"eshipper_dhl_express_9am","eshipper_dhl_express_envelope":"eshipper_dhl_express_envelope","eshipper_dhl_express_worldwide":"eshipper_dhl_express_worldwide","eshipper_dhl_import_express":"eshipper_dhl_import_express","eshipper_dhl_import_express_12pm":"eshipper_dhl_import_express_12pm","eshipper_dhl_import_express_9am":"eshipper_dhl_import_express_9am","eshipper_apex_v":"eshipper_apex_v","eshipper_apex_trucking":"eshipper_apex_trucking","eshipper_apex_trucking_v":"eshipper_apex_trucking_v","eshipper_fastfrate_rail":"eshipper_fastfrate_rail","eshipper_kindersley_expedited":"eshipper_kindersley_expedited","eshipper_kindersley_rail":"eshipper_kindersley_rail","eshipper_kindersley_regular":"eshipper_kindersley_regular","eshipper_kindersley_road":"eshipper_kindersley_road","eshipper_kingsway_road":"eshipper_kingsway_road","eshipper_m_o_eastbound":"eshipper_m_o_eastbound","eshipper_mo_rail":"eshipper_mo_rail","eshipper_national_fastfreight_rail":"eshipper_national_fastfreight_rail","eshipper_national_fastfreight_road":"eshipper_national_fastfreight_road","eshipper_vitran_rail":"eshipper_vitran_rail","eshipper_vitran_road":"eshipper_vitran_road","eshipper_western_canada_rail":"eshipper_western_canada_rail","eshipper_fedex_2day_freight":"eshipper_fedex_2day_freight","eshipper_fedex_3day_freight":"eshipper_fedex_3day_freight","eshipper_fedex_2nd_day":"eshipper_fedex_2nd_day","eshipper_fedex_economy":"eshipper_fedex_economy","eshipper_fedex_first_overnight":"eshipper_fedex_first_overnight","eshipper_fedex_ground":"eshipper_fedex_ground","eshipper_fedex_ground_us":"eshipper_fedex_ground_us","eshipper_fedex_international_priority":"eshipper_fedex_international_priority","eshipper_fedex_international_priority_express":"eshipper_fedex_international_priority_express","eshipper_fedex_intl_economy":"eshipper_fedex_intl_economy","eshipper_fedex_intl_economy_freight":"eshipper_fedex_intl_economy_freight","eshipper_fedex_intl_priority":"eshipper_fedex_intl_priority","eshipper_fedex_intl_priority_express":"eshipper_fedex_intl_priority_express","eshipper_fedex_intl_priority_freight":"eshipper_fedex_intl_priority_freight","eshipper_fedex_priority":"eshipper_fedex_priority","eshipper_fedex_standard_overnight":"eshipper_fedex_standard_overnight","eshipper_flash_bird_ground":"eshipper_flash_bird_ground","eshipper_fleet_optics_ground":"eshipper_fleet_optics_ground","eshipper_project44_a_duie_pyle":"eshipper_project44_a_duie_pyle","eshipper_project44_aaa_cooper_transportation":"eshipper_project44_aaa_cooper_transportation","eshipper_project44_aberdeen_express":"eshipper_project44_aberdeen_express","eshipper_project44_abfs":"eshipper_project44_abfs","eshipper_project44_averitt_express":"eshipper_project44_averitt_express","eshipper_project44_brown_transfer_company":"eshipper_project44_brown_transfer_company","eshipper_project44_central_freight_lines":"eshipper_project44_central_freight_lines","eshipper_project44_central_transport":"eshipper_project44_central_transport","eshipper_project44_chicago_suburban_express":"eshipper_project44_chicago_suburban_express","eshipper_project44_clear_lane_freight":"eshipper_project44_clear_lane_freight","eshipper_project44_con_way_freight":"eshipper_project44_con_way_freight","eshipper_project44_crosscountry_courier":"eshipper_project44_crosscountry_courier","eshipper_project44_day_ross":"eshipper_project44_day_ross","eshipper_project44_day_ross_v":"eshipper_project44_day_ross_v","eshipper_project44_dayton_freight_lines":"eshipper_project44_dayton_freight_lines","eshipper_project44_dependable_highway_express":"eshipper_project44_dependable_highway_express","eshipper_project44_dohrn_transfer_company":"eshipper_project44_dohrn_transfer_company","eshipper_project44_dugan_truck_line":"eshipper_project44_dugan_truck_line","eshipper_project44_estes_express_lines":"eshipper_project44_estes_express_lines","eshipper_project44_expedited_freight_systems":"eshipper_project44_expedited_freight_systems","eshipper_project44_fedex_freight_canada":"eshipper_project44_fedex_freight_canada","eshipper_project44_fedex_freight_east":"eshipper_project44_fedex_freight_east","eshipper_project44_fedex_freight_national_canada":"eshipper_project44_fedex_freight_national_canada","eshipper_project44_fedex_freight_national_usa":"eshipper_project44_fedex_freight_national_usa","eshipper_project44_fedex_freight_usa":"eshipper_project44_fedex_freight_usa","eshipper_project44_fedex_national":"eshipper_project44_fedex_national","eshipper_project44_forwardair":"eshipper_project44_forwardair","eshipper_project44_frontline_freight":"eshipper_project44_frontline_freight","eshipper_project44_holland_motor_express":"eshipper_project44_holland_motor_express","eshipper_project44_lakeville_motor_express":"eshipper_project44_lakeville_motor_express","eshipper_project44_manitoulin_tlx_inc":"eshipper_project44_manitoulin_tlx_inc","eshipper_project44_midwest_motor_express":"eshipper_project44_midwest_motor_express","eshipper_project44_monroe_transportation_services":"eshipper_project44_monroe_transportation_services","eshipper_project44_n_m_transfer":"eshipper_project44_n_m_transfer","eshipper_project44_new_england_motor_freight":"eshipper_project44_new_england_motor_freight","eshipper_project44_new_penn_motor_express":"eshipper_project44_new_penn_motor_express","eshipper_project44_pitt_ohio":"eshipper_project44_pitt_ohio","eshipper_project44_polaris":"eshipper_project44_polaris","eshipper_project44_purolator_freight":"eshipper_project44_purolator_freight","eshipper_project44_r_l_carriers":"eshipper_project44_r_l_carriers","eshipper_project44_roadrunner_transportation_services":"eshipper_project44_roadrunner_transportation_services","eshipper_project44_saia_motor_freight":"eshipper_project44_saia_motor_freight","eshipper_project44_southeastern_freight_lines":"eshipper_project44_southeastern_freight_lines","eshipper_project44_southwestern_motor_transport":"eshipper_project44_southwestern_motor_transport","eshipper_project44_standard_forwarding":"eshipper_project44_standard_forwarding","eshipper_project44_total_transportation_distribution":"eshipper_project44_total_transportation_distribution","eshipper_project44_tst_overland_express":"eshipper_project44_tst_overland_express","eshipper_project44_ups":"eshipper_project44_ups","eshipper_project44_usf_reddaway":"eshipper_project44_usf_reddaway","eshipper_project44_valley_cartage":"eshipper_project44_valley_cartage","eshipper_project44_vision_express_ltl":"eshipper_project44_vision_express_ltl","eshipper_project44_ward_trucking":"eshipper_project44_ward_trucking","eshipper_project44_xpo_logistics":"eshipper_project44_xpo_logistics","eshipper_project44_xpress_global_systems":"eshipper_project44_xpress_global_systems","eshipper_project44_yrc":"eshipper_project44_yrc","eshipper_purolator_express":"eshipper_purolator_express","eshipper_purolator_express_1030":"eshipper_purolator_express_1030","eshipper_purolator_express_9am":"eshipper_purolator_express_9am","eshipper_purolator_expresscheque":"eshipper_purolator_expresscheque","eshipper_purolator_ground":"eshipper_purolator_ground","eshipper_purolator_ground_1030":"eshipper_purolator_ground_1030","eshipper_purolator_ground_9am":"eshipper_purolator_ground_9am","eshipper_purolator":"eshipper_purolator","eshipper_purolator_10_30":"eshipper_purolator_10_30","eshipper_purolator_9am":"eshipper_purolator_9am","eshipper_purolator_puropak":"eshipper_purolator_puropak","eshipper_purolator_puropak_10_30":"eshipper_purolator_puropak_10_30","eshipper_purolator_puropak_9am":"eshipper_purolator_puropak_9am","eshipper_pyk_ground_advantage":"eshipper_pyk_ground_advantage","eshipper_pyk_priority_mail":"eshipper_pyk_priority_mail","eshipper_sameday_9_am_guaranteed":"eshipper_sameday_9_am_guaranteed","eshipper_sameday_am_service":"eshipper_sameday_am_service","eshipper_sameday_ground_service":"eshipper_sameday_ground_service","eshipper_sameday_h1_deliver_to_curbside":"eshipper_sameday_h1_deliver_to_curbside","eshipper_sameday_h4_delivery_to_curbside":"eshipper_sameday_h4_delivery_to_curbside","eshipper_sameday_h5_delivery_to_room_of_choice_2_man":"eshipper_sameday_h5_delivery_to_room_of_choice_2_man","eshipper_sameday_h6_delivery_packaging_removal_2_man":"eshipper_sameday_h6_delivery_packaging_removal_2_man","eshipper_sameday_ltl_service":"eshipper_sameday_ltl_service","eshipper_sameday_pm_service":"eshipper_sameday_pm_service","eshipper_sameday_urgent_letter":"eshipper_sameday_urgent_letter","eshipper_sameday_urgent_pac":"eshipper_sameday_urgent_pac","eshipper_skip":"eshipper_skip","eshipper_smarte_post_intl_dhl_parcel_international_direct_ngr":"eshipper_smarte_post_intl_dhl_parcel_international_direct_ngr","eshipper_smarte_post_intl_global_mail_business_priority":"eshipper_smarte_post_intl_global_mail_business_priority","eshipper_smarte_post_intl_global_mail_business_standard":"eshipper_smarte_post_intl_global_mail_business_standard","eshipper_smarte_post_intl_global_mail_packet_plus_priority":"eshipper_smarte_post_intl_global_mail_packet_plus_priority","eshipper_smarte_post_intl_global_mail_packet_priority":"eshipper_smarte_post_intl_global_mail_packet_priority","eshipper_smarte_post_intl_global_mail_packet_standard":"eshipper_smarte_post_intl_global_mail_packet_standard","eshipper_smarte_post_intl_global_mail_parcel_direct_priority_yyz":"eshipper_smarte_post_intl_global_mail_parcel_direct_priority_yyz","eshipper_smarte_post_intl_global_mail_parcel_direct_standard_yyz":"eshipper_smarte_post_intl_global_mail_parcel_direct_standard_yyz","eshipper_smarte_post_intl_global_mail_parcel_priority":"eshipper_smarte_post_intl_global_mail_parcel_priority","eshipper_smarte_post_intl_global_mail_parcel_standard":"eshipper_smarte_post_intl_global_mail_parcel_standard","eshipper_ups_expedited":"eshipper_ups_expedited","eshipper_ups_express":"eshipper_ups_express","eshipper_ups_express_early_am":"eshipper_ups_express_early_am","eshipper_ups_ground":"eshipper_ups_ground","eshipper_ups_second_day_air_a_m":"eshipper_ups_second_day_air_a_m","eshipper_ups_standard":"eshipper_ups_standard","eshipper_ups_three_day_select":"eshipper_ups_three_day_select","eshipper_ups_saver":"eshipper_ups_saver","eshipper_ups_worldwide_expedited":"eshipper_ups_worldwide_expedited","eshipper_ups_worldwide_express":"eshipper_ups_worldwide_express","eshipper_ups_worldwide_express_plus":"eshipper_ups_worldwide_express_plus","eshipper_usps_first_class_package_return_service":"eshipper_usps_first_class_package_return_service","eshipper_usps_library_mail":"eshipper_usps_library_mail","eshipper_usps_media_mail":"eshipper_usps_media_mail","eshipper_usps_parcel_select":"eshipper_usps_parcel_select","eshipper_usps_pbx":"eshipper_usps_pbx","eshipper_usps_pbx_lightweight":"eshipper_usps_pbx_lightweight","eshipper_usps_priority_mail":"eshipper_usps_priority_mail","eshipper_usps_priority_mail_express":"eshipper_usps_priority_mail_express","eshipper_usps_priority_mail_open_and_distribute":"eshipper_usps_priority_mail_open_and_distribute","eshipper_usps_priority_mail_return_service":"eshipper_usps_priority_mail_return_service","eshipper_usps_retail_ground_formerly_standard_post":"eshipper_usps_retail_ground_formerly_standard_post","eshipper_project44_abf_freight":"eshipper_project44_abf_freight","eshipper_canada_worldwide_next_flight_out":"eshipper_canada_worldwide_next_flight_out","eshipper_project44_conway_freight":"eshipper_project44_conway_freight","eshipper_day_and_ross":"eshipper_day_and_ross","eshipper_day_ross_r_and_l":"eshipper_day_ross_r_and_l","eshipper_project44_daylight_transport":"eshipper_project44_daylight_transport","eshipper_smarte_post_int_l_dhl_packet_international":"eshipper_smarte_post_int_l_dhl_packet_international","eshipper_smarte_post_int_l_dhl_parcel_international_direct":"eshipper_smarte_post_int_l_dhl_parcel_international_direct","eshipper_smarte_post_int_l_dhl_parcel_international_standard":"eshipper_smarte_post_int_l_dhl_parcel_international_standard","eshipper_envoi_same_day_delivery":"eshipper_envoi_same_day_delivery","eshipper_smarte_post_dom_expedited":"eshipper_smarte_post_dom_expedited","eshipper_fedex_freight_economy":"eshipper_fedex_freight_economy","eshipper_fedex_freight_priority":"eshipper_fedex_freight_priority","eshipper_fedex_international_connect_plus":"eshipper_fedex_international_connect_plus","eshipper_project44_forward_air":"eshipper_project44_forward_air","eshipper_sameday_h3_delivery_packaging_removal":"eshipper_sameday_h3_delivery_packaging_removal","eshipper_project44_jp_express":"eshipper_project44_jp_express","eshipper_mainliner_road":"eshipper_mainliner_road","eshipper_project44_mountain_valley_express":"eshipper_project44_mountain_valley_express","eshipper_project44_oak_harbor_freight":"eshipper_project44_oak_harbor_freight","eshipper_project44_old_dominion_freight":"eshipper_project44_old_dominion_freight","eshipper_purolator_puroletter":"eshipper_purolator_puroletter","eshipper_purolator_puroletter_10_30":"eshipper_purolator_puroletter_10_30","eshipper_purolator_puroletter_9am":"eshipper_purolator_puroletter_9am","eshipper_project44_rl_carriers":"eshipper_project44_rl_carriers","eshipper_project44_saia_ltl_freight":"eshipper_project44_saia_ltl_freight","eshipper_speedy":"eshipper_speedy","eshipper_tforce_freight_ltl":"eshipper_tforce_freight_ltl","eshipper_tforce_freight_ltl_guaranteed":"eshipper_tforce_freight_ltl_guaranteed","eshipper_tforce_freight_ltl_guaranteed_a_m":"eshipper_tforce_freight_ltl_guaranteed_a_m","eshipper_tforce_standard_ltl":"eshipper_tforce_standard_ltl","eshipper_ups":"eshipper_ups","eshipper_ups_freight":"eshipper_ups_freight","eshipper_ups_freight_canada":"eshipper_ups_freight_canada","eshipper_ods_usps_light_weight_parcel_budget":"eshipper_ods_usps_light_weight_parcel_budget","eshipper_ods_usps_light_weight_parcel_expedited":"eshipper_ods_usps_light_weight_parcel_expedited","eshipper_ods_usps_parcel_select_budget":"eshipper_ods_usps_parcel_select_budget","eshipper_ods_usps_parcel_select_expedited":"eshipper_ods_usps_parcel_select_expedited","eshipper_smarte_post_dom_xpresspost":"eshipper_smarte_post_dom_xpresspost"},"shipping_options":{"eshipper_signature_required":{"code":"signatureRequired","meta":{"configurable":true},"type":"boolean","label":"Signature Required"},"eshipper_insurance_type":{"code":"insuranceType","meta":{"configurable":true},"type":"string","label":"Insurance Type"},"eshipper_dangerous_goods_type":{"code":"dangerousGoodsType","meta":{"configurable":true},"type":"boolean","label":"Dangerous Goods Type"},"eshipper_cod":{"code":"cod","meta":{"configurable":true},"type":"float","label":"Cod"},"eshipper_is_saturday_service":{"code":"isSaturdayService","meta":{"configurable":true},"type":"boolean","label":"Is Saturday Service"},"eshipper_hold_for_pickup_required":{"code":"holdForPickupRequired","meta":{"configurable":true},"type":"boolean","label":"Hold For Pickup Required"},"eshipper_special_equipment":{"code":"specialEquipment","meta":{"configurable":true},"type":"boolean","label":"Special Equipment"},"eshipper_inside_delivery":{"code":"insideDelivery","meta":{"configurable":true},"type":"boolean","label":"Inside Delivery"},"eshipper_delivery_appointment":{"code":"deliveryAppointment","meta":{"configurable":true},"type":"boolean","label":"Delivery Appointment"},"eshipper_inside_pickup":{"code":"insidePickup","meta":{"configurable":true},"type":"boolean","label":"Inside Pickup"},"eshipper_saturday_pickup_required":{"code":"saturdayPickupRequired","meta":{"configurable":true},"type":"boolean","label":"Saturday Pickup Required"},"eshipper_stackable":{"code":"stackable","meta":{"configurable":true},"type":"boolean","label":"Stackable"}},"readme":null},{"id":"fedex","carrier_name":"fedex","display_name":"FedEx","integration_status":"production-ready","website":"https://www.fedex.com","description":"FedEx Corporation is an American multinational conglomerate holding company which focuses on transportation, e-commerce and business services.","documentation":null,"is_enabled":true,"capabilities":["Rating","Pickup","Paperless","Shipping","Tracking"],"connection_fields":{"api_key":{"name":"api_key","required":false,"sensitive":false,"type":"string","label":"API Key"},"secret_key":{"name":"secret_key","required":false,"sensitive":false,"type":"string","label":"Secret Key"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"track_api_key":{"name":"track_api_key","required":false,"sensitive":false,"type":"string","label":"Track Api Key"},"track_secret_key":{"name":"track_secret_key","required":false,"sensitive":false,"type":"string","label":"Track Secret Key"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_type":{"code":"label_type","enum":["PDF_4x6","PDF_4x6_75","PDF_4x8","PDF_4x9","ZPL_4x6","ZPL_4x6_75","ZPL_4x8","ZPL_4x9"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"smart_post_hub_id":{"code":"smart_post_hub_id","name":"smart_post_hub_id","required":false,"type":"string","label":"Smart Post Hub Id"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"locale":{"code":"locale","enum":["en_US","fr_CA"],"name":"locale","required":false,"type":"string","label":"Locale"}},"shipping_services":{"fedex_international_priority_express":"FEDEX_INTERNATIONAL_PRIORITY_EXPRESS","fedex_international_first":"INTERNATIONAL_FIRST","fedex_international_priority":"FEDEX_INTERNATIONAL_PRIORITY","fedex_international_economy":"INTERNATIONAL_ECONOMY","fedex_ground":"FEDEX_GROUND","fedex_cargo_mail":"FEDEX_CARGO_MAIL","fedex_cargo_international_premium":"FEDEX_CARGO_INTERNATIONAL_PREMIUM","fedex_first_overnight":"FIRST_OVERNIGHT","fedex_first_overnight_freight":"FIRST_OVERNIGHT_FREIGHT","fedex_1_day_freight":"FEDEX_1_DAY_FREIGHT","fedex_2_day_freight":"FEDEX_2_DAY_FREIGHT","fedex_3_day_freight":"FEDEX_3_DAY_FREIGHT","fedex_international_priority_freight":"INTERNATIONAL_PRIORITY_FREIGHT","fedex_international_economy_freight":"INTERNATIONAL_ECONOMY_FREIGHT","fedex_cargo_airport_to_airport":"FEDEX_CARGO_AIRPORT_TO_AIRPORT","fedex_international_priority_distribution":"INTERNATIONAL_PRIORITY_DISTRIBUTION","fedex_ip_direct_distribution_freight":"FEDEX_IP_DIRECT_DISTRIBUTION_FREIGHT","fedex_intl_ground_distribution":"INTL_GROUND_DISTRIBUTION","fedex_ground_home_delivery":"GROUND_HOME_DELIVERY","fedex_smart_post":"SMART_POST","fedex_priority_overnight":"PRIORITY_OVERNIGHT","fedex_standard_overnight":"STANDARD_OVERNIGHT","fedex_2_day":"FEDEX_2_DAY","fedex_2_day_am":"FEDEX_2_DAY_AM","fedex_express_saver":"FEDEX_EXPRESS_SAVER","fedex_same_day":"SAME_DAY","fedex_same_day_city":"SAME_DAY_CITY","fedex_one_day_freight":"FEDEX_ONE_DAY_FREIGHT","fedex_international_economy_distribution":"INTERNATIONAL_ECONOMY_DISTRIBUTION","fedex_international_connect_plus":"FEDEX_INTERNATIONAL_CONNECT_PLUS","fedex_international_distribution_freight":"INTERNATIONAL_DISTRIBUTION_FREIGHT","fedex_regional_economy":"FEDEX_REGIONAL_ECONOMY","fedex_next_day_freight":"FEDEX_NEXT_DAY_FREIGHT","fedex_next_day":"FEDEX_NEXT_DAY","fedex_next_day_10am":"FEDEX_NEXT_DAY_10AM","fedex_next_day_12pm":"FEDEX_NEXT_DAY_12PM","fedex_next_day_end_of_day":"FEDEX_NEXT_DAY_END_OF_DAY","fedex_distance_deferred":"FEDEX_DISTANCE_DEFERRED"},"shipping_options":{"fedex_appointment":{"code":"APPOINTMENT","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Appointment"},"fedex_broker_select_option":{"code":"BROKER_SELECT_OPTION","meta":{"configurable":true},"type":"boolean","label":"FedEx Broker Select Option"},"fedex_call_before_delivery":{"code":"CALL_BEFORE_DELIVERY","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"FedEx Call Before Delivery"},"fedex_cod":{"code":"COD","meta":{"category":"COD","configurable":true},"type":"float","label":"FedEx COD"},"fedex_custom_delivery_window":{"code":"CUSTOM_DELIVERY_WINDOW","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Custom Delivery Window"},"fedex_cut_flowers":{"code":"CUT_FLOWERS","meta":{"configurable":true},"type":"boolean","label":"FedEx Cut Flowers"},"fedex_do_not_break_down_pallets":{"code":"DO_NOT_BREAK_DOWN_PALLETS","meta":{"configurable":true},"type":"boolean","label":"FedEx Do Not Break Down Pallets"},"fedex_do_not_stack_pallets":{"code":"DO_NOT_STACK_PALLETS","meta":{"configurable":true},"type":"boolean","label":"FedEx Do Not Stack Pallets"},"fedex_dry_ice":{"code":"DRY_ICE","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"FedEx Dry Ice"},"fedex_east_coast_special":{"code":"EAST_COAST_SPECIAL","meta":{"configurable":true},"type":"boolean","label":"FedEx East Coast Special"},"fedex_exclude_from_consolidation":{"code":"EXCLUDE_FROM_CONSOLIDATION","meta":{"configurable":true},"type":"boolean","label":"FedEx Exclude From Consolidation"},"fedex_extreme_length":{"code":"EXTREME_LENGTH","meta":{"configurable":true},"type":"boolean","label":"FedEx Extreme Length"},"fedex_inside_delivery":{"code":"INSIDE_DELIVERY","meta":{"configurable":true},"type":"boolean","label":"FedEx Inside Delivery"},"fedex_inside_pickup":{"code":"INSIDE_PICKUP","meta":{"configurable":true},"type":"boolean","label":"FedEx Inside Pickup"},"fedex_international_controlled_export_service":{"code":"INTERNATIONAL_CONTROLLED_EXPORT_SERVICE","meta":{"configurable":true},"type":"boolean","label":"FedEx International Controlled Export Service"},"fedex_third_party_consignee":{"code":"THIRD_PARTY_CONSIGNEE","meta":{"configurable":true},"type":"boolean","label":"FedEx Third Party Consignee"},"fedex_electronic_trade_documents":{"code":"ELECTRONIC_TRADE_DOCUMENTS","meta":{"category":"PAPERLESS","configurable":true},"type":"boolean","label":"FedEx Electronic Trade Documents"},"fedex_food":{"code":"FOOD","meta":{"configurable":true},"type":"boolean","label":"FedEx Food"},"fedex_future_day_shipment":{"code":"FUTURE_DAY_SHIPMENT","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Future Day Shipment"},"fedex_hold_at_location":{"code":"HOLD_AT_LOCATION","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"FedEx Hold At Location"},"fedex_international_traffic_in_arms_regulations":{"code":"INTERNATIONAL_TRAFFIC_IN_ARMS_REGULATIONS","meta":{"configurable":true},"type":"boolean","label":"FedEx International Traffic In Arms Regulations"},"fedex_liftgate_delivery":{"code":"LIFTGATE_DELIVERY","meta":{"configurable":true},"type":"boolean","label":"FedEx Liftgate Delivery"},"fedex_liftgate_pickup":{"code":"LIFTGATE_PICKUP","meta":{"configurable":true},"type":"boolean","label":"FedEx Liftgate Pickup"},"fedex_limited_access_delivery":{"code":"LIMITED_ACCESS_DELIVERY","meta":{"configurable":true},"type":"boolean","label":"FedEx Limited Access Delivery"},"fedex_limited_access_pickup":{"code":"LIMITED_ACCESS_PICKUP","meta":{"configurable":true},"type":"boolean","label":"FedEx Limited Access Pickup"},"fedex_over_length":{"code":"OVER_LENGTH","meta":{"configurable":true},"type":"boolean","label":"FedEx Over Length"},"fedex_pending_shipment":{"code":"PENDING_SHIPMENT","meta":{"configurable":true},"type":"boolean","label":"FedEx Pending Shipment"},"fedex_pharmacy_delivery":{"code":"PHARMACY_DELIVERY","meta":{"configurable":true},"type":"boolean","label":"FedEx Pharmacy Delivery"},"fedex_poison":{"code":"POISON","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"FedEx Poison"},"fedex_home_delivery_premium":{"code":"HOME_DELIVERY_PREMIUM","meta":{"configurable":true},"type":"boolean","label":"FedEx Home Delivery Premium"},"fedex_protection_from_freezing":{"code":"PROTECTION_FROM_FREEZING","meta":{"configurable":true},"type":"boolean","label":"FedEx Protection From Freezing"},"fedex_returns_clearance":{"code":"RETURNS_CLEARANCE","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"FedEx Returns Clearance"},"fedex_return_shipment":{"code":"RETURN_SHIPMENT","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"FedEx Return Shipment"},"fedex_saturday_pickup":{"code":"SATURDAY_PICKUP","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Saturday Pickup"},"fedex_event_notification":{"code":"EVENT_NOTIFICATION","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"FedEx Event Notification"},"fedex_delivery_on_invoice_acceptance":{"code":"DELIVERY_ON_INVOICE_ACCEPTANCE","meta":{"configurable":true},"type":"boolean","label":"FedEx Delivery On Invoice Acceptance"},"fedex_top_load":{"code":"TOP_LOAD","meta":{"configurable":true},"type":"boolean","label":"FedEx Top Load"},"fedex_one_rate":{"code":"FEDEX_ONE_RATE","meta":{"configurable":true},"type":"boolean","label":"FedEx One Rate"},"fedex_freight_guarantee":{"code":"FREIGHT_GUARANTEE","meta":{"configurable":true},"type":"boolean","label":"FedEx Freight Guarantee"},"fedex_saturday_delivery":{"code":"SATURDAY_DELIVERY","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Saturday Delivery"},"fedex_pickup_type":{"code":"fedex_pickup_type","enum":["DROPOFF_AT_FEDEX_LOCATION","CONTACT_FEDEX_TO_SCHEDULE","USE_SCHEDULED_PICKUP"],"help":"How the shipper will tender the package to FedEx. Valid values: DROPOFF_AT_FEDEX_LOCATION, CONTACT_FEDEX_TO_SCHEDULE, USE_SCHEDULED_PICKUP. Defaults to DROPOFF_AT_FEDEX_LOCATION.","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"FedEx Pickup Type"},"fedex_department_number":{"code":"fedex_department_number","meta":{"configurable":true},"type":"string","label":"FedEx Department Number"},"fedex_po_number":{"code":"fedex_po_number","meta":{"configurable":true},"type":"string","label":"FedEx PO Number"},"fedex_rma_association":{"code":"fedex_rma_association","meta":{"configurable":true},"type":"string","label":"FedEx RMA Association"},"fedex_smart_post_hub_id":{"code":"SMART_POST_HUB_ID","meta":{"configurable":true},"type":"string","label":"FedEx Smart Post Hub ID"},"fedex_smart_post_allowed_indicia":{"code":"SMART_POST_ALLOWED_INDICIA","meta":{"configurable":true},"type":"string","label":"FedEx Smart Post Allowed Indicia"},"fedex_alcohol":{"code":"ALCOHOL","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"FedEx Alcohol"},"fedex_battery":{"code":"BATTERY","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"FedEx Battery"},"fedex_dangerous_goods":{"code":"DANGEROUS_GOODS","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"FedEx Dangerous Goods"},"fedex_priority_alert":{"code":"PRIORITY_ALERT","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"FedEx Priority Alert"},"fedex_priority_alert_plus":{"code":"PRIORITY_ALERT_PLUS","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"FedEx Priority Alert Plus"},"fedex_non_standard_container":{"code":"NON_STANDARD_CONTAINER","meta":{"configurable":true},"type":"boolean","label":"FedEx Non Standard Container"},"fedex_piece_count_verification":{"code":"PIECE_COUNT_VERIFICATION","meta":{"configurable":true},"type":"boolean","label":"FedEx Piece Count Verification"},"fedex_signature_option":{"code":"SIGNATURE_OPTION","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"FedEx Signature Option"},"fedex_evening":{"code":"EVENING","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Evening"},"fedex_date_certain":{"code":"DATE_CERTAIN","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"FedEx Date Certain"},"doc_files":{"code":"doc_files","meta":{"category":"PAPERLESS","configurable":true},"type":"object","label":"Doc Files"},"doc_references":{"code":"doc_references","meta":{"category":"PAPERLESS","configurable":true},"type":"object","label":"Doc References"},"shipper_instructions":{"code":"shipper_instructions","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"Shipper Instructions"},"recipient_instructions":{"code":"recipient_instructions","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"Recipient Instructions"}},"readme":null},{"id":"freightcom","carrier_name":"freightcom","display_name":"Freightcom","integration_status":"deprecated","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"freightcom_all":"0","freightcom_usf_holland":"1911","freightcom_central_transport":"2029","freightcom_estes":"2107","freightcom_canpar_ground":"3400","freightcom_canpar_select":"3404","freightcom_canpar_overnight":"3407","freightcom_dicom_ground":"3700","freightcom_purolator_ground":"4000","freightcom_purolator_express":"4003","freightcom_purolator_express_9_am":"4004","freightcom_purolator_express_10_30_am":"4005","freightcom_purolator_ground_us":"4016","freightcom_purolator_express_us":"4015","freightcom_purolator_express_us_9_am":"4013","freightcom_purolator_express_us_10_30_am":"4014","freightcom_fedex_express_saver":"4100","freightcom_fedex_ground":"4101","freightcom_fedex_2day":"4102","freightcom_fedex_priority_overnight":"4104","freightcom_fedex_standard_overnight":"4105","freightcom_fedex_first_overnight":"4106","freightcom_fedex_international_priority":"4108","freightcom_fedex_international_economy":"4109","freightcom_ups_standard":"4600","freightcom_ups_expedited":"4601","freightcom_ups_express_saver":"4602","freightcom_ups_express":"4603","freightcom_ups_express_early":"4604","freightcom_ups_3day_select":"4605","freightcom_ups_worldwide_expedited":"4606","freightcom_ups_worldwide_express":"4607","freightcom_ups_worldwide_express_plus":"4608","freightcom_ups_worldwide_express_saver":"4609","freightcom_dhl_express_easy":"5202","freightcom_dhl_express_10_30":"5208","freightcom_dhl_express_worldwide":"5211","freightcom_dhl_express_12_00":"5215","freightcom_dhl_economy_select":"5216","freightcom_dhl_ecommerce_am_service":"5706","freightcom_dhl_ecommerce_ground_service":"5707","freightcom_canadapost_regular_parcel":"6301","freightcom_canadapost_expedited_parcel":"6300","freightcom_canadapost_xpresspost":"6303","freightcom_canadapost_priority":"6302"},"shipping_options":{"freightcom_saturday_pickup_required":{"code":"saturdayPickupRequired","meta":{"configurable":true},"type":"boolean","label":"Saturday Pickup Required"},"freightcom_homeland_security":{"code":"homelandSecurity","meta":{"configurable":true},"type":"boolean","label":"Homeland Security"},"freightcom_exhibition_convention_site":{"code":"exhibitionConventionSite","meta":{"configurable":true},"type":"boolean","label":"Exhibition Convention Site"},"freightcom_military_base_delivery":{"code":"militaryBaseDelivery","meta":{"configurable":true},"type":"boolean","label":"Military Base Delivery"},"freightcom_customs_in_bond_freight":{"code":"customsIn_bondFreight","meta":{"configurable":true},"type":"boolean","label":"Customs In Bond Freight"},"freightcom_limited_access":{"code":"limitedAccess","meta":{"configurable":true},"type":"boolean","label":"Limited Access"},"freightcom_excess_length":{"code":"excessLength","meta":{"configurable":true},"type":"boolean","label":"Excess Length"},"freightcom_tailgate_pickup":{"code":"tailgatePickup","meta":{"configurable":true},"type":"boolean","label":"Tailgate Pickup"},"freightcom_residential_pickup":{"code":"residentialPickup","meta":{"configurable":true},"type":"boolean","label":"Residential Pickup"},"freightcom_cross_border_fee":{"code":"crossBorderFee","meta":{"configurable":true},"type":"boolean","label":"Cross Border Fee"},"freightcom_notify_recipient":{"code":"notifyRecipient","meta":{"configurable":true},"type":"boolean","label":"Notify Recipient"},"freightcom_single_shipment":{"code":"singleShipment","meta":{"configurable":true},"type":"boolean","label":"Single Shipment"},"freightcom_tailgate_delivery":{"code":"tailgateDelivery","meta":{"configurable":true},"type":"boolean","label":"Tailgate Delivery"},"freightcom_residential_delivery":{"code":"residentialDelivery","meta":{"configurable":true},"type":"boolean","label":"Residential Delivery"},"freightcom_insurance_type":{"code":"insuranceType","meta":{"configurable":true},"type":"float","label":"Insurance Type"},"freightcom_inside_delivery":{"code":"insideDelivery","meta":{"configurable":true},"type":"boolean","label":"Inside Delivery"},"freightcom_is_saturday_service":{"code":"isSaturdayService","meta":{"configurable":true},"type":"boolean","label":"Is Saturday Service"},"freightcom_dangerous_goods_type":{"code":"dangerousGoodsType","meta":{"configurable":true},"type":"boolean","label":"Dangerous Goods Type"},"freightcom_stackable":{"code":"stackable","meta":{"configurable":true},"type":"boolean","label":"Stackable"}},"readme":null},{"id":"generic","carrier_name":"generic","display_name":"Custom Carrier","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Rating","Shipping"],"connection_fields":{"display_name":{"name":"display_name","required":true,"sensitive":false,"type":"string","label":"Display Name"},"custom_carrier_name":{"name":"custom_carrier_name","required":true,"sensitive":false,"type":"string","label":"Custom Carrier Name"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"},"label_template":{"name":"label_template","required":false,"sensitive":false,"type":"object","label":"Label Template"}},"config_fields":{"text_color":{"code":"text_color","name":"text_color","required":false,"type":"string","label":"Text Color"},"brand_color":{"code":"brand_color","name":"brand_color","required":false,"type":"string","label":"Brand Color"}},"shipping_services":{"standard_service":"standard"},"shipping_options":{"tracking_number_reference":{"code":"tracking_number","meta":{"configurable":true},"type":"string","label":"Tracking Number Reference"}},"readme":null},{"id":"geodis","carrier_name":"geodis","display_name":"GEODIS","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"identifier":{"name":"identifier","required":true,"sensitive":false,"type":"string","label":"Identifier"},"code_client":{"name":"code_client","required":false,"sensitive":false,"type":"string","label":"Code Client"},"language":{"default":"fr","enum":["fr","en"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"FR","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"agency_code":{"code":"agency_code","name":"agency_code","required":false,"type":"string","label":"Agency Code"}},"shipping_services":{"geodis_EXP":"EXP","geodis_MES":"MES","geodis_express_france":"NTX","geodis_retour_trans_fr_messagerie_plus":"ENL"},"shipping_options":{"geodis_web_appointment":{"code":"RDW","meta":{"configurable":true},"type":"boolean","label":"Web Appointment"},"geodis_telephone_appointment":{"code":"RDT","meta":{"configurable":true},"type":"boolean","label":"Telephone Appointment"},"geodis_pick_up_at_a_geodis_agency":{"code":"BRT","meta":{"configurable":true},"type":"boolean","label":"Pick Up At A Geodis Agency"},"geodis_desired_date_of_delivery":{"code":"DSL","meta":{"configurable":true},"type":"boolean","label":"Desired Date Of Delivery"},"geodis_delivery_on_a_saturday_morning":{"code":"SAT","meta":{"configurable":true},"type":"boolean","label":"Delivery On A Saturday Morning"},"geodis_validate_envoi":{"code":"validate_envoi","meta":{"configurable":true},"type":"boolean","label":"Validate Envoi"},"geodis_no_recepisse":{"code":"no_recepisse","meta":{"configurable":true},"type":"string","label":"No Recepisse"},"geodis_instruction_enlevement":{"code":"instruction_enlevement","meta":{"configurable":true},"type":"string","label":"Instruction Enlevement"},"geodis_date_livraison":{"code":"date_livraison","meta":{"configurable":true},"type":"string","label":"Date Livraison"},"geodis_heure_livraison":{"code":"heure_livraison","meta":{"configurable":true},"type":"string","label":"Heure Livraison"},"geodis_instruction_livraison":{"code":"instruction_livraison","meta":{"configurable":true},"type":"string","label":"Instruction Livraison"}},"readme":null},{"id":"gls","carrier_name":"gls","display_name":"GLS Group","integration_status":"development","website":"https://www.gls-group.net","description":"GLS Group shipping services","documentation":null,"is_enabled":true,"capabilities":["Tracking","Pickup","Rating","Shipping"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"contact_id":{"name":"contact_id","required":false,"sensitive":false,"type":"string","label":"Contact Id"},"account_country_code":{"default":"DE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_format":{"code":"label_format","name":"label_format","required":false,"type":"string","label":"Label Format"},"printer_language":{"code":"printer_language","name":"printer_language","required":false,"type":"string","label":"Printer Language"},"template_name":{"code":"template_name","name":"template_name","required":false,"type":"string","label":"Template Name"}},"shipping_services":{"gls_parcel":"PARCEL","gls_express":"EXPRESS","gls_guaranteed24":"GUARANTEED24","gls_business_parcel":"BUSINESSPARCEL","gls_euro_business_parcel":"EUROBUSINESSPARCEL"},"shipping_options":{"gls_guaranteed24":{"code":"GUARANTEED24","help":"Guaranteed next-day delivery service","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"GLS Guaranteed24"},"gls_saturday_delivery":{"code":"SaturdayService","help":"Enable Saturday delivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"GLS Saturday Delivery"},"gls_flex_delivery":{"code":"FlexDeliveryService","help":"Notify recipient about delivery options","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"GLS Flex Delivery"},"gls_deposit_service":{"code":"DepositService","help":"Enable delivery to a predefined deposit location","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"GLS Deposit Service"},"gls_deposit_description":{"code":"DepositDescription","help":"Description of the deposit location (e.g., 'Behind the garage')","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"GLS Deposit Description"},"gls_deposit_contact":{"code":"DepositContact","help":"Contact person at the deposit location","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"GLS Deposit Contact"},"gls_express_parcel":{"code":"ExpressParcel","help":"Enable express shipping","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"GLS Express Parcel"},"gls_time_definite_service":{"code":"TimeDefiniteService","help":"Set specific delivery time (before 8 AM, 9 AM, 10 AM, 12 PM)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"GLS Time Definite Service"},"gls_shop_delivery":{"code":"ShopDeliveryService","help":"Delivery to a GLS ParcelShop","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"GLS Shop Delivery"},"gls_shop_id":{"code":"ShopID","help":"GLS ParcelShop ID for delivery","meta":{"category":"PUDO","configurable":true},"type":"string","label":"GLS Shop ID"},"gls_shop_auto_determine":{"code":"ShopAutoSelect","help":"Automatically determine nearest GLS ParcelShop based on recipient address","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"GLS Shop Auto Determine"},"gls_addressee_only":{"code":"AddresseeOnlyService","help":"Delivery only to the addressee (no neighbor delivery)","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"GLS Addressee Only"},"gls_signature_service":{"code":"SignatureService","help":"Require signature upon delivery","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"GLS Signature Service"},"gls_ident_pin_service":{"code":"IdentPINService","help":"Identification via PIN code at delivery","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"GLS Ident Pin Service"},"gls_add_on_liability":{"code":"AddOnLiabilityService","help":"Add extra liability coverage for shipments","meta":{"category":"INSURANCE","configurable":true},"type":"boolean","label":"GLS Add On Liability"},"gls_pick_and_return":{"code":"PickAndReturnService","help":"Enable pick and return service","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"GLS Pick And Return"},"gls_shop_return":{"code":"ShopReturnService","help":"Add a pre-printed return label inside the package","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"GLS Shop Return"},"gls_return_enabled":{"code":"ReturnService","help":"Enable return label generation for this shipment","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"GLS Return Enabled"},"gls_limited_quantity":{"code":"LimitedQuantity","help":"Mark shipment as containing limited quantity hazardous materials","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"GLS Limited Quantity"},"gls_limited_quantity_weight":{"code":"LimitedQuantityWeight","help":"Weight of limited quantity hazardous material in kg","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"float","label":"GLS Limited Quantity Weight"},"gls_cod_reference":{"code":"CODReference","help":"Reference number for cash on delivery payment","meta":{"category":"COD","configurable":true},"type":"string","label":"GLS COD Reference"},"gls_premium":{"code":"PremiumService","help":"Enable premium service","meta":{"configurable":true},"type":"boolean","label":"GLS Premium"},"insurance":{"code":"insurance","help":"Insurance value for the shipment","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Insurance"}},"readme":null},{"id":"hay_post","carrier_name":"hay_post","display_name":"HayPost","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"customer_id":{"name":"customer_id","required":true,"sensitive":false,"type":"string","label":"Customer Id"},"customer_type":{"name":"customer_type","required":true,"sensitive":false,"type":"string","label":"Customer Type"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"letter_ordered":"88","letter_simple":"79","letter_valued":"89","package_ordered":"93","package_simple":"92","package_valued":"100","parcel_simple":"94","parcel_valued":"95","postcard_ordered":"91","postcard_simple":"90","sekogram_simple":"96","sprint_simple":"97","yes_ordered_value":"99"},"shipping_options":{"notification":{"code":"2","meta":{"configurable":true},"type":"boolean","label":"Notification"},"ordered_packaging":{"code":"3","meta":{"configurable":true},"type":"boolean","label":"Ordered Packaging"},"pick_up":{"code":"4","meta":{"configurable":true},"type":"boolean","label":"Pick Up"},"postmen_delivery_value":{"code":"5","meta":{"configurable":true},"type":"boolean","label":"Postmen Delivery Value"},"delivery":{"code":"6","meta":{"configurable":true},"type":"boolean","label":"Delivery"},"international_notification":{"code":"15","meta":{"configurable":true},"type":"boolean","label":"International Notification"},"domestic_sms":{"code":"16","meta":{"configurable":true},"type":"boolean","label":"Domestic Sms"},"international_sms":{"code":"17","meta":{"configurable":true},"type":"boolean","label":"International Sms"}},"readme":null},{"id":"hermes","carrier_name":"hermes","display_name":"Hermes","integration_status":"beta","website":"https://www.hermesworld.com","description":"Hermes shipping integration for Karrio","documentation":"https://de-api-int.hermesworld.com/docs/applications/order","is_enabled":true,"capabilities":["Tracking","Pickup","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"account_country_code":{"default":"DE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"label_type":{"code":"label_type","name":"label_type","required":false,"type":"string","label":"Label Type"},"language":{"code":"language","name":"language","required":false,"type":"string","label":"Language"}},"shipping_services":{"hermes_standard":"hermes_standard","hermes_next_day":"hermes_next_day","hermes_stated_day":"hermes_stated_day","hermes_parcel_shop":"hermes_parcel_shop","hermes_international":"hermes_international"},"shipping_options":{"hermes_next_day":{"code":"nextDayService","help":"Enable next-day delivery service","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Hermes Next Day"},"hermes_bulk_goods":{"code":"bulkGoodService","help":"Mark shipment as bulky goods (Sperrgut)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Hermes Bulk Goods"},"hermes_compact_parcel":{"code":"compactParcelService","help":"Enable compact parcel service","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Hermes Compact Parcel"},"hermes_redirection_prohibited":{"code":"redirectionProhibitedService","help":"Do not allow redirection to neighbor","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Hermes Redirection Prohibited"},"hermes_stated_day":{"code":"statedDay","help":"Specific delivery date (YYYY-MM-DD format)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Hermes Stated Day"},"hermes_time_slot":{"code":"timeSlot","help":"Delivery time slot (FORENOON, NOON, AFTERNOON, EVENING)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Hermes Time Slot"},"hermes_express":{"code":"expressService","help":"Enable express delivery service","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Hermes Express"},"hermes_after_hours_delivery":{"code":"afterHoursDeliveryService","help":"Enable after-hours delivery (Feierabendservice)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Hermes After Hours Delivery"},"hermes_parcel_class":{"code":"parcelClass","help":"Parcel size class (XS, S, M, L, XL)","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Parcel Class"},"hermes_signature":{"code":"signatureService","help":"Require signature upon delivery","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Signature"},"hermes_household_signature":{"code":"householdSignatureService","help":"Require household member signature","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Household Signature"},"hermes_ident_id":{"code":"identID","help":"ID number for identity verification","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Ident Id"},"hermes_ident_type":{"code":"identType","help":"Type of ID for verification (e.g., GERMAN_IDENTITY_CARD)","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Ident Type"},"hermes_ident_fsk":{"code":"identVerifyFsk","help":"Minimum age verification (e.g., 18)","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Ident Fsk"},"hermes_ident_birthday":{"code":"identVerifyBirthday","help":"Verify recipient birthday (YYYY-MM-DD)","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Ident Birthday"},"hermes_parcel_shop_id":{"code":"psID","help":"Hermes ParcelShop ID for delivery","meta":{"category":"PUDO","configurable":true},"type":"string","label":"Parcel Shop Id"},"hermes_parcel_shop_selection_rule":{"code":"psSelectionRule","help":"ParcelShop selection rule (SELECT_BY_ID, SELECT_BY_RECEIVER_ADDRESS)","meta":{"category":"PUDO","configurable":true},"type":"string","label":"Parcel Shop Selection Rule"},"hermes_parcel_shop_customer_firstname":{"code":"psCustomerFirstName","help":"Customer first name for ParcelShop pickup","meta":{"category":"PUDO","configurable":true},"type":"string","label":"Parcel Shop Customer Firstname"},"hermes_parcel_shop_customer_lastname":{"code":"psCustomerLastName","help":"Customer last name for ParcelShop pickup","meta":{"category":"PUDO","configurable":true},"type":"string","label":"Parcel Shop Customer Lastname"},"hermes_exclude_parcel_shop_auth":{"code":"excludeParcelShopAuthorization","help":"Exclude ParcelShop delivery authorization","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Exclude Parcel Shop Auth"},"hermes_notification_email":{"code":"notificationEmail","help":"Email for delivery notifications","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"Notification Email"},"hermes_notification_type":{"code":"notificationType","help":"Notification type (EMAIL, SMS, EMAIL_SMS)","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"Notification Type"},"hermes_cod_amount":{"code":"codAmount","help":"Cash on delivery amount","meta":{"category":"COD","configurable":true},"type":"float","label":"Cod Amount"},"hermes_cod_currency":{"code":"codCurrency","help":"Currency for COD amount","meta":{"category":"COD","configurable":true},"type":"string","label":"Cod Currency"},"hermes_cod_distribution":{"code":"codDistribution","help":"COD distribution method (e.g., transfer, check)","meta":{"category":"COD","configurable":true},"type":"string","label":"Cod Distribution"},"hermes_limited_quantities":{"code":"limitedQuantitiesService","help":"Mark shipment as containing limited quantity hazardous materials","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"Limited Quantities"},"hermes_return_enabled":{"code":"returnService","help":"Enable return label for this shipment","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Return Enabled"},"hermes_include_return_label":{"code":"includeReturnLabel","help":"Include a pre-printed return label inside the package","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Include Return Label"},"hermes_digital_sales_return":{"code":"digitalSalesReturn","help":"Enable digital sales return (digitale Verkaufsretoure)","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"Digital Sales Return"},"hermes_customer_reference_1":{"code":"customerReference1","help":"Customer reference field 1 (Kundenreferenz 1)","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"Customer Reference 1"},"hermes_customer_reference_2":{"code":"customerReference2","help":"Customer reference field 2 (Kundenreferenz 2)","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"Customer Reference 2"},"hermes_tan_service":{"code":"tanService","meta":{"configurable":false},"type":"boolean","label":"Tan Service"},"hermes_late_injection":{"code":"lateInjectionService","meta":{"configurable":false},"type":"boolean","label":"Late Injection"},"hermes_part_number":{"code":"partNumber","meta":{"configurable":false},"type":"integer","label":"Part Number"},"hermes_number_of_parts":{"code":"numberOfParts","meta":{"configurable":false},"type":"integer","label":"Number Of Parts"},"hermes_parent_shipment_order_id":{"code":"parentShipmentOrderID","meta":{"configurable":false},"type":"string","label":"Parent Shipment Order Id"}},"readme":null},{"id":"landmark","carrier_name":"landmark","display_name":"Landmark Global","integration_status":"beta","website":"https://landmarkglobal.com","description":"Landmark Global shipping integration for Karrio","documentation":"https://mercurydocs.landmarkglobal.com/docs/api-documentation","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"region":{"default":"Landmark CMH","name":"region","required":false,"sensitive":false,"type":"string","label":"Region"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_type":{"code":"label_type","default":"PDF","enum":["PDF","JPG","GIF","BMP","ZPL","PNG"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"account_currency":{"code":"account_currency","default":"EUR","name":"account_currency","required":false,"type":"string","label":"Account Currency"},"import_request_by_default":{"code":"import_request_by_default","default":false,"name":"import_request_by_default","required":false,"type":"boolean","label":"Import Request By Default"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"}},"shipping_services":{"landmark_maxipak_scan_ddp":"LGINTSTD","landmark_maxipak_scan_ddu":"LGINTSTDU","landmark_minipak_scan_ddp":"LGINTBPIP","landmark_minipak_scan_ddu":"LGINTBPIU","landmark_maxipak_scan_ddp_pudo":"LGINTPUDO","landmark_maxipak_scan_premium_ups_express_ddp":"LGINTUPSS","landmark_maxipak_scan_premium_ups_express_ddu":"LGINTUPSSU","landmark_maxipak_scan_premium_ups_standard_ddp":"LGINTUPST","landmark_maxipak_scan_premium_ups_standard_ddu":"LGINTUPSTU","landmark_maxipak_scan_pddp":"LGINTBPMO","landmark_minipak_scan_pddp":"LGINTBPIO"},"shipping_options":{"landmark_shipment_insurance_freight":{"code":"ShipmentInsuranceFreight","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Landmark Shipment Insurance Freight"},"landmark_order_insurance_freight_total":{"code":"OrderInsuranceFreightTotal","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"Landmark Order Insurance Freight Total"},"landmark_produce_label":{"code":"ProduceLabel","meta":{"configurable":true},"type":"boolean","label":"Landmark Produce Label"},"landmark_import_request":{"code":"InportRequest","meta":{"configurable":true},"type":"boolean","label":"Landmark Import Request"},"fulfilled_by_landmark":{"code":"FulfilledByLandmark","meta":{"configurable":true},"type":"boolean","label":"Fulfilled By Landmark"},"landmark_freight_pro_number":{"code":"FreightProNumber","meta":{"configurable":true},"type":"string","label":"Landmark Freight Pro Number"},"landmark_freight_piece_unit":{"code":"FreightPieceUnit","meta":{"configurable":true},"type":"string","label":"Landmark Freight Piece Unit"},"landmark_return_address_code":{"code":"ReturnAddressCode","meta":{"category":"RETURN","configurable":true},"type":"string","label":"Landmark Return Address Code"}},"readme":null},{"id":"laposte","carrier_name":"laposte","display_name":"La Poste","integration_status":"production-ready","website":"https://www.laposte.fr/","description":"La Poste is a postal service company in France, operating in Metropolitan France and French overseas territories. The company provides mail delivery, parcel shipping, banking services, and digital solutions.","documentation":"https://www.lapostegroupe.com/en/services-mail-parcels-business-unit","is_enabled":true,"capabilities":["Tracking"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"lang":{"default":"fr_FR","enum":["fr_FR","en_US"],"name":"lang","required":false,"sensitive":false,"type":"string","label":"Lang"},"account_country_code":{"default":"FR","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"laposte_standard_service":"La Poste Standard Service"},"shipping_options":{},"readme":null},{"id":"locate2u","carrier_name":"locate2u","display_name":"Locate2u","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"},"client_id":{"name":"client_id","required":false,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":false,"sensitive":false,"type":"string","label":"Client Secret"},"account_country_code":{"default":"AU","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"}},"config_fields":{},"shipping_services":{"locate2u_local_delivery":"Locate2u Local Delivery"},"shipping_options":{"appointment_time":{"code":"appointment_time","meta":{"configurable":true},"type":"string","label":"Appointment Time"},"time_window_start":{"code":"time_window_start","meta":{"configurable":true},"type":"string","label":"Time Window Start"},"time_window_end":{"code":"time_window_end","meta":{"configurable":true},"type":"string","label":"Time Window End"},"brand_id":{"code":"brand_id","meta":{"configurable":true},"type":"string","label":"Brand Id"},"duration_minutes":{"code":"duration_minutes","meta":{"configurable":true},"type":"integer","label":"Duration Minutes"},"assigned_team_member_id":{"code":"assigned_team_member_id","meta":{"configurable":true},"type":"string","label":"Assigned Team Member Id"},"source":{"code":"source","meta":{"configurable":true},"type":"string","label":"Source"},"customer_id":{"code":"customer_id","meta":{"configurable":true},"type":"string","label":"Customer Id"},"run_number":{"code":"run_number","meta":{"configurable":true},"type":"string","label":"Run Number"},"team_region_id":{"code":"team_region_id","meta":{"configurable":true},"type":"string","label":"Team Region Id"},"driver_instructions":{"code":"driver_instructions","meta":{"configurable":true},"type":"string","label":"Driver Instructions"},"notes":{"code":"notes","meta":{"configurable":true},"type":"string","label":"Notes"},"latitude":{"code":"latitude","meta":{"configurable":true},"type":"float","label":"Latitude"},"longitude":{"code":"longitude","meta":{"configurable":true},"type":"float","label":"Longitude"}},"readme":null},{"id":"mydhl","carrier_name":"mydhl","display_name":"MyDHL Express","integration_status":"in-development","website":"https://www.dhl.com","description":"DHL Express MyDHL API integration for Karrio","documentation":"https://developer.dhl.com/api-reference/mydhl-express","is_enabled":true,"capabilities":["Rating","Pickup","Paperless","Shipping","Tracking"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"label_type":{"code":"label_type","name":"label_type","required":false,"type":"string","label":"Label Type"}},"shipping_services":{"mydhl_express_worldwide":"P","mydhl_express_12_00":"T","mydhl_express_9_00":"Y","mydhl_express_10_30":"K","mydhl_express_easy":"8","mydhl_medical_express":"Q","mydhl_jetline":"J","mydhl_sprintline":"R","mydhl_globalmail":"G","mydhl_globalmail_business":"M","mydhl_express_domestic":"N","mydhl_express_domestic_12_00":"1","mydhl_express_domestic_10_30":"O","mydhl_express_domestic_9_00":"I","mydhl_medical_express_domestic":"C","mydhl_same_day":"S","mydhl_economy_select":"W","mydhl_europack":"H","mydhl_breakbulk_express":"E","mydhl_express_freight":"F","mydhl_express_worldwide_doc":"D","mydhl_express_envelope":"X","mydhl_express_worldwide_b2c":"7","mydhl_express_easy_b2c":"6"},"shipping_options":{"mydhl_saturday_delivery":{"code":"AA","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"MyDHL Saturday Delivery"},"mydhl_hold_for_collection":{"code":"LX","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"MyDHL Hold For Collection"},"mydhl_neutral_delivery":{"code":"NN","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"MyDHL Neutral Delivery"},"mydhl_residential_delivery":{"code":"TK","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"MyDHL Residential Delivery"},"mydhl_scheduled_delivery":{"code":"TT","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"MyDHL Scheduled Delivery"},"mydhl_collect_from_service_point":{"code":"TV","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"MyDHL Collect From Service Point"},"mydhl_verified_delivery":{"code":"TF","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"MyDHL Verified Delivery"},"mydhl_direct_signature":{"code":"SF","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"MyDHL Direct Signature"},"mydhl_signature_release":{"code":"SX","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"MyDHL Signature Release"},"mydhl_duty_tax_paid":{"code":"DD","meta":{"configurable":true},"type":"boolean","label":"MyDHL Duty Tax Paid"},"mydhl_receiver_paid":{"code":"DE","meta":{"configurable":true},"type":"boolean","label":"MyDHL Receiver Paid"},"mydhl_import_billing":{"code":"DT","meta":{"configurable":true},"type":"boolean","label":"MyDHL Import Billing"},"mydhl_duty_tax_importer":{"code":"DU","meta":{"configurable":true},"type":"boolean","label":"MyDHL Duty Tax Importer"},"mydhl_shipment_insurance":{"code":"II","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"MyDHL Shipment Insurance"},"mydhl_dangerous_goods":{"code":"HE","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Dangerous Goods"},"mydhl_dry_ice":{"code":"HC","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Dry Ice"},"mydhl_lithium_ion_pi966_section_ii":{"code":"HD","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Lithium Ion Pi966 Section Ii"},"mydhl_lithium_ion_pi967_section_ii":{"code":"HV","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Lithium Ion Pi967 Section Ii"},"mydhl_lithium_metal_pi969_section_ii":{"code":"HM","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Lithium Metal Pi969 Section Ii"},"mydhl_lithium_metal_pi970_section_ii":{"code":"HW","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Lithium Metal Pi970 Section Ii"},"mydhl_excepted_quantities":{"code":"HH","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Excepted Quantities"},"mydhl_consumer_commodities":{"code":"HK","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Consumer Commodities"},"mydhl_magnetized_material":{"code":"HX","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Magnetized Material"},"mydhl_not_restricted_dangerous_goods":{"code":"HU","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Not Restricted Dangerous Goods"},"mydhl_active_data_logger":{"code":"HT","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"MyDHL Active Data Logger"},"mydhl_gogreen_climate_neutral":{"code":"EE","meta":{"configurable":true},"type":"boolean","label":"MyDHL Gogreen Climate Neutral"},"mydhl_gogreen_plus_carbon_reduced":{"code":"FE","meta":{"configurable":true},"type":"boolean","label":"MyDHL Gogreen Plus Carbon Reduced"},"mydhl_verbal_notification":{"code":"JA","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"MyDHL Verbal Notification"},"mydhl_verbal_notification_alternative":{"code":"JD","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"MyDHL Verbal Notification Alternative"},"mydhl_broker_notification":{"code":"WG","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"MyDHL Broker Notification"},"mydhl_emergency_situation":{"code":"CR","meta":{"configurable":true},"type":"boolean","label":"MyDHL Emergency Situation"},"mydhl_diplomatic_mail":{"code":"CG","meta":{"configurable":true},"type":"boolean","label":"MyDHL Diplomatic Mail"},"mydhl_cold_storage":{"code":"LG","meta":{"configurable":true},"type":"boolean","label":"MyDHL Cold Storage"},"mydhl_sanctions_routing":{"code":"LU","meta":{"configurable":true},"type":"boolean","label":"MyDHL Sanctions Routing"},"mydhl_courier_time_window":{"code":"JY","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"MyDHL Courier Time Window"},"mydhl_dedicated_pickup":{"code":"QA","meta":{"configurable":true},"type":"boolean","label":"MyDHL Dedicated Pickup"},"mydhl_non_stackable_pallet":{"code":"YC","meta":{"configurable":true},"type":"boolean","label":"MyDHL Non Stackable Pallet"},"mydhl_paperless_trade":{"code":"WY","meta":{"category":"PAPERLESS","configurable":true},"type":"boolean","label":"MyDHL Paperless Trade"},"mydhl_export_declaration":{"code":"WO","meta":{"category":"PAPERLESS","configurable":true},"type":"boolean","label":"MyDHL Export Declaration"},"mydhl_clearance_authorization":{"code":"WD","meta":{"configurable":true},"type":"boolean","label":"MyDHL Clearance Authorization"},"mydhl_clearance_data_modification":{"code":"WF","meta":{"configurable":true},"type":"boolean","label":"MyDHL Clearance Data Modification"},"mydhl_bonded_storage":{"code":"WK","meta":{"configurable":true},"type":"boolean","label":"MyDHL Bonded Storage"},"mydhl_bonded_transit":{"code":"WL","meta":{"configurable":true},"type":"boolean","label":"MyDHL Bonded Transit"},"mydhl_temporary_import_export":{"code":"WM","meta":{"configurable":true},"type":"boolean","label":"MyDHL Temporary Import Export"},"mydhl_non_routine_entry":{"code":"WB","meta":{"configurable":true},"type":"boolean","label":"MyDHL Non Routine Entry"},"mydhl_multiline_entry":{"code":"WE","meta":{"configurable":true},"type":"boolean","label":"MyDHL Multiline Entry"},"mydhl_physical_intervention":{"code":"WH","meta":{"configurable":true},"type":"boolean","label":"MyDHL Physical Intervention"},"mydhl_other_government_agency":{"code":"WI","meta":{"configurable":true},"type":"boolean","label":"MyDHL Other Government Agency"},"mydhl_obtaining_permits_licences":{"code":"WJ","meta":{"configurable":true},"type":"boolean","label":"MyDHL Obtaining Permits Licences"},"mydhl_post_clearance_modification":{"code":"WS","meta":{"configurable":true},"type":"boolean","label":"MyDHL Post Clearance Modification"},"mydhl_sale_in_transit":{"code":"WT","meta":{"configurable":true},"type":"boolean","label":"MyDHL Sale In Transit"},"mydhl_data_entry":{"code":"PD","meta":{"configurable":true},"type":"boolean","label":"MyDHL Data Entry"},"mydhl_label_free":{"code":"PZ","meta":{"configurable":true},"type":"boolean","label":"MyDHL Label Free"},"mydhl_personally_identifiable_data":{"code":"PQ","meta":{"configurable":true},"type":"boolean","label":"MyDHL Personally Identifiable Data"},"mydhl_neutral_description_label":{"code":"PP","meta":{"configurable":true},"type":"boolean","label":"MyDHL Neutral Description Label"},"mydhl_return_to_seller":{"code":"PH","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"MyDHL Return To Seller"},"mydhl_return_to_origin":{"code":"PR","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"MyDHL Return To Origin"},"mydhl_fuel_surcharge":{"code":"FF","meta":{"configurable":true},"type":"boolean","label":"MyDHL Fuel Surcharge"},"mydhl_remote_area_delivery":{"code":"OO","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"MyDHL Remote Area Delivery"},"mydhl_address_correction":{"code":"MA","meta":{"configurable":true},"type":"boolean","label":"MyDHL Address Correction"},"mydhl_packaging":{"code":"GG","meta":{"configurable":true},"type":"boolean","label":"MyDHL Packaging"},"mydhl_import_export_taxes":{"code":"XB","meta":{"configurable":true},"type":"boolean","label":"MyDHL Import Export Taxes"},"mydhl_import_export_duties":{"code":"XX","meta":{"configurable":true},"type":"boolean","label":"MyDHL Import Export Duties"},"mydhl_merchandise_process":{"code":"XE","meta":{"configurable":true},"type":"boolean","label":"MyDHL Merchandise Process"},"mydhl_trade_zone_process":{"code":"XJ","meta":{"configurable":true},"type":"boolean","label":"MyDHL Trade Zone Process"},"mydhl_regulatory_charges":{"code":"XK","meta":{"configurable":true},"type":"boolean","label":"MyDHL Regulatory Charges"},"mydhl_data_staging_03":{"code":"PT","meta":{"configurable":true},"type":"boolean","label":"MyDHL Data Staging 03"},"mydhl_data_staging_06":{"code":"PU","meta":{"configurable":true},"type":"boolean","label":"MyDHL Data Staging 06"},"mydhl_data_staging_12":{"code":"PV","meta":{"configurable":true},"type":"boolean","label":"MyDHL Data Staging 12"},"mydhl_data_staging_24":{"code":"PW","meta":{"configurable":true},"type":"boolean","label":"MyDHL Data Staging 24"},"mydhl_shipment_preparation":{"code":"PA","meta":{"configurable":true},"type":"boolean","label":"MyDHL Shipment Preparation"},"mydhl_automated_digital_imaging":{"code":"PJ","meta":{"configurable":true},"type":"boolean","label":"MyDHL Automated Digital Imaging"},"mydhl_plt_images_pending":{"code":"PK","meta":{"configurable":true},"type":"boolean","label":"MyDHL Plt Images Pending"},"mydhl_optical_character_recognition":{"code":"PL","meta":{"configurable":true},"type":"boolean","label":"MyDHL Optical Character Recognition"},"mydhl_commercial_invoice_data_merge":{"code":"PM","meta":{"configurable":true},"type":"boolean","label":"MyDHL Commercial Invoice Data Merge"},"mydhl_comat":{"code":"PO","meta":{"configurable":true},"type":"boolean","label":"MyDHL Comat"},"mydhl_import_billing_account":{"code":"30","meta":{"configurable":true},"type":"boolean","label":"MyDHL Import Billing Account"},"doc_files":{"code":"doc_files","meta":{"category":"PAPERLESS","configurable":true},"type":"object","label":"Doc Files"}},"readme":null},{"id":"nationex","carrier_name":"nationex","display_name":"Nationex","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"customer_id":{"name":"customer_id","required":true,"sensitive":false,"type":"string","label":"Customer Id"},"billing_account":{"name":"billing_account","required":false,"sensitive":false,"type":"string","label":"Billing Account"},"language":{"default":"en","enum":["en","fr"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"CA","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"parcelone","carrier_name":"parcelone","display_name":"ParcelOne","integration_status":"beta","website":"https://parcel.one","description":"ParcelOne multi-carrier shipping integration for Karrio","documentation":"https://parcel.one/api-documentation","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"mandator_id":{"name":"mandator_id","required":true,"sensitive":false,"type":"string","label":"Mandator Id"},"consigner_id":{"name":"consigner_id","required":true,"sensitive":false,"type":"string","label":"Consigner Id"},"account_country_code":{"default":"DE","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"cep_id":{"code":"cep_id","name":"cep_id","required":false,"type":"string","label":"Cep Id"},"product_id":{"code":"product_id","name":"product_id","required":false,"type":"string","label":"Product Id"},"label_format":{"code":"label_format","enum":["PDF","ZPL","PNG"],"name":"label_format","required":false,"type":"string","label":"Label Format"},"label_size":{"code":"label_size","enum":["A6","A4"],"name":"label_size","required":false,"type":"string","label":"Label Size"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"}},"shipping_services":{"parcelone_pa1_basic":"PA1_basic","parcelone_pa1_eco":"PA1_eco","parcelone_pa1_premium":"PA1_premium","parcelone_pa1_express":"PA1_express","parcelone_dhl_paket":"DHL_PAKET","parcelone_dhl_paket_international":"DHL_PAKETINT","parcelone_dhl_express":"DHL_EXPRESS","parcelone_dhl_retoure":"DHL_RETOURE","parcelone_ups_standard":"UPS_STANDARD","parcelone_ups_express":"UPS_EXPRESS","parcelone_ups_express_saver":"UPS_EXPSAVER"},"shipping_options":{"parcelone_saturday_delivery":{"code":"SDO","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"ParcelOne Saturday Delivery"},"parcelone_return_label":{"code":"SRL","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"ParcelOne Return Label"},"parcelone_cod":{"code":"COD","meta":{"category":"COD","configurable":true},"type":"float","label":"ParcelOne COD"},"parcelone_cod_currency":{"code":"COD_CURRENCY","meta":{"category":"COD","configurable":true},"type":"string","label":"ParcelOne COD Currency"},"parcelone_insurance":{"code":"INS","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"ParcelOne Insurance"},"parcelone_insurance_currency":{"code":"INS_CURRENCY","meta":{"category":"INSURANCE","configurable":true},"type":"string","label":"ParcelOne Insurance Currency"},"parcelone_notification_email":{"code":"MAIL","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"ParcelOne Notification Email"},"parcelone_notification_sms":{"code":"SMS","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"ParcelOne Notification Sms"},"parcelone_signature":{"code":"SIG","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"ParcelOne Signature"},"parcelone_ident_check":{"code":"IDENT","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"ParcelOne Ident Check"},"parcelone_age_check":{"code":"AGE","meta":{"category":"SIGNATURE","configurable":true},"type":"integer","label":"ParcelOne Age Check"},"parcelone_personally":{"code":"PERS","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"ParcelOne Personally"},"parcelone_neighbor_delivery":{"code":"NEIGHBOR","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"ParcelOne Neighbor Delivery"},"parcelone_no_neighbor":{"code":"NONEIGHBOR","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"ParcelOne No Neighbor"},"parcelone_drop_off_point":{"code":"DROP","meta":{"category":"PUDO","configurable":true},"type":"string","label":"ParcelOne Drop Off Point"},"parcelone_premium":{"code":"PREMIUM","meta":{"configurable":true},"type":"boolean","label":"ParcelOne Premium"},"parcelone_bulky_goods":{"code":"BULKY","meta":{"configurable":true},"type":"boolean","label":"ParcelOne Bulky Goods"}},"readme":null},{"id":"postat","carrier_name":"postat","display_name":"Austrian Post","integration_status":"beta","website":"https://www.post.at","description":"Austrian Post (Österreichische Post) shipping integration via Post-Labelcenter API","documentation":"https://www.post.at/en/business-post-labelcenter","is_enabled":true,"capabilities":["Rating","Shipping"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"org_unit_id":{"name":"org_unit_id","required":true,"sensitive":false,"type":"string","label":"Org Unit Id"},"org_unit_guid":{"name":"org_unit_guid","required":true,"sensitive":false,"type":"string","label":"Org Unit Guid"},"account_country_code":{"default":"AT","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"server_url":{"code":"server_url","name":"server_url","required":false,"type":"string","label":"Server Url"},"label_format":{"code":"label_format","enum":["PDF","ZPL2"],"name":"label_format","required":false,"type":"string","label":"Label Format"},"label_size":{"code":"label_size","enum":["SIZE_100x150","SIZE_100x200"],"name":"label_size","required":false,"type":"string","label":"Label Size"},"paper_layout":{"code":"paper_layout","enum":["LAYOUT_2xA5inA4","LAYOUT_4xA6inA4","LAYOUT_A4"],"name":"paper_layout","required":false,"type":"string","label":"Paper Layout"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"}},"shipping_services":{"postat_standard_domestic":"10","postat_express_domestic":"20","postat_international_standard":"30","postat_international_express":"40"},"shipping_options":{"postat_label_size":{"code":"label_size","meta":{"configurable":true},"type":"string","label":"PostAT Label Size"},"postat_paper_layout":{"code":"paper_layout","meta":{"configurable":true},"type":"string","label":"PostAT Paper Layout"},"postat_cod":{"code":"COD","meta":{"category":"COD","configurable":true},"type":"float","label":"PostAT COD"},"postat_cod_currency":{"code":"COD_CURRENCY","meta":{"category":"COD","configurable":true},"type":"string","label":"PostAT COD Currency"},"postat_insurance":{"code":"INS","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"PostAT Insurance"},"postat_insurance_currency":{"code":"INS_CURRENCY","meta":{"category":"INSURANCE","configurable":true},"type":"string","label":"PostAT Insurance Currency"},"postat_signature":{"code":"SIG","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"PostAT Signature"},"postat_saturday_delivery":{"code":"SAT","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"PostAT Saturday Delivery"},"postat_email_notification":{"code":"MAIL","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"PostAT Email Notification"},"postat_sms_notification":{"code":"SMS","meta":{"category":"NOTIFICATION","configurable":true},"type":"string","label":"PostAT Sms Notification"},"postat_age_verification":{"code":"AGE","meta":{"category":"SIGNATURE","configurable":true},"type":"integer","label":"PostAT Age Verification"}},"readme":null},{"id":"purolator","carrier_name":"purolator","display_name":"Purolator","integration_status":"production-ready","website":"https://www.purolator.com","description":"Purolator Inc. is a Canadian courier company, delivering parcels and freight in Canada and internationally.","documentation":null,"is_enabled":true,"capabilities":["Tracking","Pickup","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_number":{"name":"account_number","required":true,"sensitive":false,"type":"string","label":"Account Number"},"user_token":{"name":"user_token","required":false,"sensitive":false,"type":"string","label":"User Token"},"language":{"default":"en","enum":["en","fr"],"name":"language","required":false,"sensitive":false,"type":"string","label":"Language"},"account_country_code":{"default":"CA","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"purolator_express_9_am":"PurolatorExpress9AM","purolator_express_us":"PurolatorExpressU.S.","purolator_express_10_30_am":"PurolatorExpress10:30AM","purolator_express_us_9_am":"PurolatorExpressU.S.9AM","purolator_express_12_pm":"PurolatorExpress12PM","purolator_express_us_10_30_am":"PurolatorExpressU.S.10:30AM","purolator_express":"PurolatorExpress","purolator_express_us_12_00":"PurolatorExpressU.S.12:00","purolator_express_evening":"PurolatorExpressEvening","purolator_express_envelope_us":"PurolatorExpressEnvelopeU.S.","purolator_express_envelope_9_am":"PurolatorExpressEnvelope9AM","purolator_express_us_envelope_9_am":"PurolatorExpressU.S.Envelope9AM","purolator_express_envelope_10_30_am":"PurolatorExpressEnvelope10:30AM","purolator_express_us_envelope_10_30_am":"PurolatorExpressU.S.Envelope10:30AM","purolator_express_envelope_12_pm":"PurolatorExpressEnvelope12PM","purolator_express_us_envelope_12_00":"PurolatorExpressU.S.Envelope12:00","purolator_express_envelope":"PurolatorExpressEnvelope","purolator_express_pack_us":"PurolatorExpressPackU.S.","purolator_express_envelope_evening":"PurolatorExpressEnvelopeEvening","purolator_express_us_pack_9_am":"PurolatorExpressU.S.Pack9AM","purolator_express_pack_9_am":"PurolatorExpressPack9AM","purolator_express_us_pack_10_30_am":"PurolatorExpressU.S.Pack10:30AM","purolator_express_pack10_30_am":"PurolatorExpressPack10:30AM","purolator_express_us_pack_12_00":"PurolatorExpressU.S.Pack12:00","purolator_express_pack_12_pm":"PurolatorExpressPack12PM","purolator_express_box_us":"PurolatorExpressBoxU.S.","purolator_express_pack":"PurolatorExpressPack","purolator_express_us_box_9_am":"PurolatorExpressU.S.Box9AM","purolator_express_pack_evening":"PurolatorExpressPackEvening","purolator_express_us_box_10_30_am":"PurolatorExpressU.S.Box10:30AM","purolator_express_box_9_am":"PurolatorExpressBox9AM","purolator_express_us_box_12_00":"PurolatorExpressU.S.Box12:00","purolator_express_box_10_30_am":"PurolatorExpressBox10:30AM","purolator_ground_us":"PurolatorGroundU.S.","purolator_express_box_12_pm":"PurolatorExpressBox12PM","purolator_express_international":"PurolatorExpressInternational","purolator_express_box":"PurolatorExpressBox","purolator_express_international_9_am":"PurolatorExpressInternational9AM","purolator_express_box_evening":"PurolatorExpressBoxEvening","purolator_express_international_10_30_am":"PurolatorExpressInternational10:30AM","purolator_ground":"PurolatorGround","purolator_express_international_12_00":"PurolatorExpressInternational12:00","purolator_ground_9_am":"PurolatorGround9AM","purolator_express_envelope_international":"PurolatorExpressEnvelopeInternational","purolator_ground_10_30_am":"PurolatorGround10:30AM","purolator_express_international_envelope_9_am":"PurolatorExpressInternationalEnvelope9AM","purolator_ground_evening":"PurolatorGroundEvening","purolator_express_international_envelope_10_30_am":"PurolatorExpressInternationalEnvelope10:30AM","purolator_quick_ship":"PurolatorQuickShip","purolator_express_international_envelope_12_00":"PurolatorExpressInternationalEnvelope12:00","purolator_quick_ship_envelope":"PurolatorQuickShipEnvelope","purolator_express_pack_international":"PurolatorExpressPackInternational","purolator_quick_ship_pack":"PurolatorQuickShipPack","purolator_express_international_pack_9_am":"PurolatorExpressInternationalPack9AM","purolator_quick_ship_box":"PurolatorQuickShipBox","purolator_express_international_pack_10_30_am":"PurolatorExpressInternationalPack10:30AM","purolator_express_international_pack_12_00":"PurolatorExpressInternationalPack12:00","purolator_express_box_international":"PurolatorExpressBoxInternational","purolator_express_international_box_9_am":"PurolatorExpressInternationalBox9AM","purolator_express_international_box_10_30_am":"PurolatorExpressInternationalBox10:30AM","purolator_express_international_box_12_00":"PurolatorExpressInternationalBox12:00"},"shipping_options":{"purolator_dangerous_goods":{"code":"Dangerous Goods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"Purolator Dangerous Goods"},"purolator_chain_of_signature":{"code":"Chain of Signature","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Purolator Chain Of Signature"},"purolator_express_cheque":{"code":"ExpressCheque","meta":{"category":"COD","configurable":true},"type":"string","label":"Purolator Express Cheque"},"purolator_hold_for_pickup":{"code":"Hold For Pickup","meta":{"category":"PUDO","configurable":true},"type":"string","label":"Purolator Hold For Pickup"},"purolator_return_services":{"code":"Return Services","meta":{"category":"RETURN","configurable":true},"type":"string","label":"Purolator Return Services"},"purolator_saturday_service":{"code":"Saturday Service","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"string","label":"Purolator Saturday Service"},"purolator_origin_signature_not_required":{"code":"Origin Signature Not Required (OSNR)","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Purolator Origin Signature Not Required"},"purolator_adult_signature_required":{"code":"Adult Signature Required (ASR)","meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"Purolator Adult Signature Required"},"purolator_special_handling":{"code":"Special Handling","meta":{"configurable":true},"type":"string","label":"Purolator Special Handling"},"purolator_show_alternative_services":{"code":"Show Alternate Services","meta":{"configurable":true},"type":"boolean","label":"Purolator Show Alternative Services"}},"readme":null},{"id":"roadie","carrier_name":"roadie","display_name":"Roadie","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_country_code":{"default":"US","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"roadie_local_delivery":"Roadie Local Delivery"},"shipping_options":{"pickup_after":{"code":"pickup_after","meta":{"configurable":true},"type":"string","label":"Pickup After"},"deliver_start":{"code":"delivery_start","meta":{"configurable":true},"type":"string","label":"Deliver Start"},"deliver_end":{"code":"delivery_end","meta":{"configurable":true},"type":"string","label":"Deliver End"},"roadie_signature_required":{"code":"signature_required","meta":{"configurable":true},"type":"boolean","label":"Signature Required"},"roadie_notifications_enabled":{"code":"notifications_enabled","meta":{"configurable":true},"type":"boolean","label":"Notifications Enabled"},"roadie_over_21_required":{"code":"over_21_required","meta":{"configurable":true},"type":"boolean","label":"Over 21 Required"},"roadie_extra_compensation":{"code":"extra_compensation","meta":{"configurable":true},"type":"float","label":"Extra Compensation"},"roadie_trailer_required":{"code":"trailer_required","meta":{"configurable":true},"type":"boolean","label":"Trailer Required"},"roadie_decline_insurance":{"code":"decline_insurance","meta":{"configurable":true},"type":"boolean","label":"Decline Insurance"}},"readme":null},{"id":"royalmail","carrier_name":"royalmail","display_name":"Royal Mail","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"account_country_code":{"default":"UK","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"sapient","carrier_name":"sapient","display_name":"SAPIENT","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Pickup","Rating","Shipping"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"shipping_account_id":{"name":"shipping_account_id","required":true,"sensitive":false,"type":"string","label":"Shipping Account Id"},"sapient_carrier_code":{"default":"RM","enum":["DX","EVRI","RM","UPS","YODEL"],"name":"sapient_carrier_code","required":false,"sensitive":false,"type":"string","label":"Sapient Carrier Code"},"account_country_code":{"default":"GB","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"service_level":{"code":"service_level","name":"service_level","required":false,"type":"string","label":"Service Level"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"sapient_royal_mail_hm_forces_mail":"BF1","sapient_royal_mail_hm_forces_signed_for":"BF2","sapient_royal_mail_hm_forces_special_delivery_500":"BF7","sapient_royal_mail_hm_forces_special_delivery_1000":"BF8","sapient_royal_mail_hm_forces_special_delivery_2500":"BF9","sapient_royal_mail_international_business_personal_correspondence_max_sort_residue_ll":"BG1","sapient_royal_mail_international_business_mail_ll_max_sort_residue_standard":"BG2","sapient_royal_mail_international_business_personal_correspondence_max_sort_residue_l":"BP1","sapient_royal_mail_international_business_mail_l_max_sort_residue_standard":"BP2","sapient_royal_mail_international_business_printed_matter_packet":"BPI","sapient_royal_mail_1st_class":"BPL1","sapient_royal_mail_2nd_class":"BPL2","sapient_royal_mail_1st_class_signed_for":"BPR1","sapient_royal_mail_2nd_class_signed_for":"BPR2","sapient_royal_mail_international_business_parcel_priority_country_priced_boxable":"BXB","sapient_royal_mail_international_business_parcel_tracked_country_priced_boxable_extra_comp":"BXC","sapient_royal_mail_international_business_parcel_priority_country_priced_boxable_ddp":"BXD","sapient_royal_mail_international_business_parcel_tracked_country_priced_boxable_ddp":"BXE","sapient_royal_mail_international_business_parcel_tracked_country_priced_boxable":"BXF","sapient_royal_mail_24_standard_signed_for_parcel_daily_rate_service":"CRL1","sapient_royal_mail_48_standard_signed_for_parcel_daily_rate_service":"CRL2","sapient_royal_mail_international_business_parcels_zero_sort_priority":"DE4","sapient_royal_mail_international_business_parcels_zero_sort_priority_DE":"DE6","sapient_royal_mail_de_import_standard_24_parcel":"DEA","sapient_royal_mail_de_import_standard_24_parcel_DE":"DEB","sapient_royal_mail_de_import_standard_24_ll":"DEC","sapient_royal_mail_de_import_standard_48_ll":"DED","sapient_royal_mail_de_import_to_eu_tracked_signed_ll":"DEE","sapient_royal_mail_de_import_to_eu_max_sort_ll":"DEG","sapient_royal_mail_de_import_to_eu_tracked_parcel":"DEI","sapient_royal_mail_de_import_to_eu_tracked_signed_parcel":"DEJ","sapient_royal_mail_de_import_to_eu_tracked_high_vol_ll":"DEK","sapient_royal_mail_de_import_to_eu_max_sort_parcel":"DEM","sapient_royal_mail_international_business_mail_ll_country_priced_priority":"DG4","sapient_royal_mail_international_business_personal_correspondence_l_priority_untracked":"DP3","sapient_royal_mail_international_business_mail_ll_country_sort_priority":"DP6","sapient_royal_mail_international_business_parcels":"DW1","sapient_royal_mail_international_business_parcels_tracked_country_priced_extra_territorial_office_of_exchange":"ETA","sapient_royal_mail_international_business_parcels_tracked_signed_country_priced_extra_territorial_office_of_exchange":"ETB","sapient_royal_mail_international_business_parcels_zero_sort_priority_extra_territorial_office_of_exchange":"ETC","sapient_royal_mail_international_business_mail_tracked_ll_country_priced_extra_territorial_office_of_exchange":"ETD","sapient_royal_mail_international_business_mail_tracked_signed_ll_country_priced_extra_territorial_office_of_exchange":"ETE","sapient_royal_mail_international_business_mail_ll_country_priced_priority_extra_territorial_office_of_exchange":"ETF","sapient_royal_mail_international_tracked_parcels_0_30kg_extra_territorial_office_of_exchange_e":"ETG","sapient_royal_mail_international_tracked_parcels_0_30kg_extra_comp_extra_territorial_office_of_exchange_e":"ETH","sapient_royal_mail_international_tracked_parcels_0_30kg_extra_territorial_office_of_exchange_c":"ETI","sapient_royal_mail_international_tracked_parcels_0_30kg_extra_comp_extra_territorial_office_of_exchange_c":"ETJ","sapient_royal_mail_international_business_personal_correspondence_l_priority_untracked_extra_territorial_office_of_exchange":"ETK","sapient_royal_mail_international_business_personal_correspondence_l_tracked_high_vol_country_priced_extra_territorial_office_of_exchange":"ETL","sapient_royal_mail_international_business_personal_correspondence_l_tracked_signed_high_vol_country_priced_extra_territorial_office_of_exchange":"ETM","sapient_royal_mail_international_business_personal_correspondence_signed_l_high_vol_country_priced_extra_territorial_office_of_exchange":"ETN","sapient_royal_mail_international_business_personal_correspondence_ll_country_sort_priority_extra_territorial_office_of_exchange":"ETO","sapient_royal_mail_international_business_personal_correspondence_tracked_ll_high_vol_extra_comp_country_priced_extra_territorial_office_of_exchange":"ETP","sapient_royal_mail_international_business_personal_correspondence_tracked_signed_ll_high_vol_extra_comp_country_priced_extra_territorial_office_of_exchange":"ETQ","sapient_royal_mail_international_business_personal_correspondence_signed_ll_extra_compensation_country_priced_extra_territorial_office_of_exchange":"ETR","sapient_royal_mail_24_standard_signed_for_large_letter_flat_rate_service":"FS1","sapient_royal_mail_48_standard_signed_for_large_letter_flat_rate_service":"FS2","sapient_royal_mail_24_presorted_ll":"FS7","sapient_royal_mail_48_presorted_ll":"FS8","sapient_royal_mail_international_tracked_parcels_0_30kg":"HVB","sapient_royal_mail_international_business_tracked_express_npc":"HVD","sapient_royal_mail_international_tracked_parcels_0_30kg_extra_comp":"HVE","sapient_royal_mail_international_tracked_parcels_0_30kg_c_prio":"HVK","sapient_royal_mail_international_tracked_parcels_0_30kg_xcomp_c_prio":"HVL","sapient_royal_mail_international_business_parcels_zone_sort_priority_service":"IE1","sapient_royal_mail_international_business_mail_large_letter_zone_sort_priority":"IG1","sapient_royal_mail_international_business_mail_large_letter_zone_sort_priority_machine":"IG4","sapient_royal_mail_international_business_mail_letters_zone_sort_priority":"IP1","sapient_royal_mail_import_de_tracked_returns_24":"ITA","sapient_royal_mail_import_de_tracked_returns_48":"ITB","sapient_royal_mail_import_de_tracked_24_letter_boxable_high_volume":"ITC","sapient_royal_mail_import_de_tracked_48_letter_boxable_high_volume":"ITD","sapient_royal_mail_import_de_tracked_48_letter_boxable":"ITE","sapient_royal_mail_import_de_tracked_24_letter_boxable":"ITF","sapient_royal_mail_import_de_tracked_48_high_volume":"ITL","sapient_royal_mail_import_de_tracked_24_high_volume":"ITM","sapient_royal_mail_import_de_tracked_24":"ITN","sapient_royal_mail_de_import_to_eu_signed_parcel":"ITR","sapient_royal_mail_import_de_tracked_48":"ITS","sapient_royal_mail_international_business_parcels_print_direct_priority":"MB1","sapient_royal_mail_international_business_parcels_print_direct_standard":"MB2","sapient_royal_mail_international_business_parcels_signed_extra_compensation_country_priced":"MP0","sapient_royal_mail_international_business_parcels_tracked_zone_sort":"MP1","sapient_royal_mail_international_business_parcels_tracked_extra_comp_zone_sort":"MP4","sapient_royal_mail_international_business_parcels_signed_zone_sort":"MP5","sapient_royal_mail_international_business_parcels_signed_extra_compensation_zone_sort":"MP6","sapient_royal_mail_international_business_parcels_tracked_country_priced":"MP7","sapient_royal_mail_international_business_parcels_tracked_extra_comp_country_priced":"MP8","sapient_royal_mail_international_business_parcels_signed_country_priced":"MP9","sapient_royal_mail_international_business_mail_tracked_high_vol_country_priced":"MPL","sapient_royal_mail_international_business_mail_tracked_signed_high_vol_country_priced":"MPM","sapient_royal_mail_international_business_mail_signed_high_vol_country_priced":"MPN","sapient_royal_mail_international_business_mail_tracked_high_vol_extra_comp_country_priced":"MPO","sapient_royal_mail_international_business_mail_tracked_signed_high_vol_extra_comp_country_priced":"MPP","sapient_royal_mail_international_business_parcel_tracked_boxable_country_priced":"MPR","sapient_royal_mail_international_business_parcels_tracked_signed_zone_sort":"MTA","sapient_royal_mail_international_business_parcels_tracked_signed_extra_compensation_zone_sort":"MTB","sapient_royal_mail_international_business_mail_tracked_signed_zone_sort":"MTC","sapient_royal_mail_international_business_parcels_tracked_signed_country_priced":"MTE","sapient_royal_mail_international_business_parcels_tracked_signed_extra_compensation_country_priced":"MTF","sapient_royal_mail_international_business_mail_tracked_signed_country_priced":"MTG","sapient_royal_mail_international_business_mail_tracked_zone_sort":"MTI","sapient_royal_mail_international_business_mail_tracked_country_priced":"MTK","sapient_royal_mail_international_business_mail_signed_zone_sort":"MTM","sapient_royal_mail_international_business_mail_signed_country_priced":"MTO","sapient_royal_mail_international_business_mail_signed_extra_compensation_country_priced":"MTP","sapient_royal_mail_international_business_parcels_tracked_direct_ireland_country":"MTS","sapient_royal_mail_international_business_parcels_tracked_signed_ddp":"MTV","sapient_royal_mail_international_standard_on_account":"OLA","sapient_royal_mail_international_economy_on_account":"OLS","sapient_royal_mail_international_signed_on_account":"OSA","sapient_royal_mail_international_signed_on_account_extra_comp":"OSB","sapient_royal_mail_international_tracked_on_account":"OTA","sapient_royal_mail_international_tracked_on_account_extra_comp":"OTB","sapient_royal_mail_international_tracked_signed_on_account":"OTC","sapient_royal_mail_international_tracked_signed_on_account_extra_comp":"OTD","sapient_royal_mail_48_ll_flat_rate":"PK0","sapient_royal_mail_24_standard_signed_for_parcel_sort8_flat_rate_service":"PK1","sapient_royal_mail_48_standard_signed_for_parcel_sort8_flat_rate_service":"PK2","sapient_royal_mail_24_standard_signed_for_parcel_sort8_daily_rate_service":"PK3","sapient_royal_mail_48_standard_signed_for_parcel_sort8_daily_rate_service":"PK4","sapient_royal_mail_24_presorted_p":"PK7","sapient_royal_mail_48_presorted_p":"PK8","sapient_royal_mail_24_ll_flat_rate":"PK9","sapient_royal_mail_rm24_presorted_p_annual_flat_rate":"PKB","sapient_royal_mail_rm48_presorted_p_annual_flat_rate":"PKD","sapient_royal_mail_rm48_presorted_ll_annual_flat_rate":"PKK","sapient_royal_mail_rm24_presorted_ll_annual_flat_rate":"PKM","sapient_royal_mail_24_standard_signed_for_packetpost_flat_rate_service":"PPF1","sapient_royal_mail_48_standard_signed_for_packetpost_flat_rate_service":"PPF2","sapient_royal_mail_parcelpost_flat_rate_annual":"PPJ1","sapient_royal_mail_parcelpost_flat_rate_annual_PPJ":"PPJ2","sapient_royal_mail_rm24_ll_annual_flat_rate":"PPS","sapient_royal_mail_rm48_ll_annual_flat_rate":"PPT","sapient_royal_mail_international_business_personal_correspondence_max_sort_l":"PS5","sapient_royal_mail_international_business_mail_large_letter_max_sort_priority_service":"PS7","sapient_royal_mail_international_business_mail_letters_max_sort_standard":"PSA","sapient_royal_mail_international_business_mail_large_letter_max_sort_standard_service":"PSB","sapient_royal_mail_48_sort8p_annual_flat_rate":"RM0","sapient_royal_mail_24_ll_daily_rate":"RM1","sapient_royal_mail_24_p_daily_rate":"RM2","sapient_royal_mail_48_ll_daily_rate":"RM3","sapient_royal_mail_48_p_daily_rate":"RM4","sapient_royal_mail_24_p_flat_rate":"RM5","sapient_royal_mail_48_p_flat_rate":"RM6","sapient_royal_mail_24_sort8_ll_annual_flat_rate":"RM7","sapient_royal_mail_24_sort8_p_annual_flat_rate":"RM8","sapient_royal_mail_48_sort8_ll_annual_flat_rate":"RM9","sapient_royal_mail_special_delivery_guaranteed_by_1pm_750":"SD1","sapient_royal_mail_special_delivery_guaranteed_by_1pm_1000":"SD2","sapient_royal_mail_special_delivery_guaranteed_by_1pm_2500":"SD3","sapient_royal_mail_special_delivery_guaranteed_by_9am_750":"SD4","sapient_royal_mail_special_delivery_guaranteed_by_9am_1000":"SD5","sapient_royal_mail_special_delivery_guaranteed_by_9am_2500":"SD6","sapient_royal_mail_special_delivery_guaranteed_by_1pm_id_750":"SDA","sapient_royal_mail_special_delivery_guaranteed_by_1pm_id_1000":"SDB","sapient_royal_mail_special_delivery_guaranteed_by_1pm_id_2500":"SDC","sapient_royal_mail_special_delivery_guaranteed_by_9am_id_750":"SDE","sapient_royal_mail_special_delivery_guaranteed_by_9am_id_1000":"SDF","sapient_royal_mail_special_delivery_guaranteed_by_9am_id_2500":"SDG","sapient_royal_mail_special_delivery_guaranteed_by_1pm_age_750":"SDH","sapient_royal_mail_special_delivery_guaranteed_by_1pm_age_1000":"SDJ","sapient_royal_mail_special_delivery_guaranteed_by_1pm_age_2500":"SDK","sapient_royal_mail_special_delivery_guaranteed_by_9am_age_750":"SDM","sapient_royal_mail_special_delivery_guaranteed_by_9am_age_1000":"SDN","sapient_royal_mail_special_delivery_guaranteed_by_9am_age_2500":"SDQ","sapient_royal_mail_special_delivery_guaranteed_age_750":"SDV","sapient_royal_mail_special_delivery_guaranteed_age_1000":"SDW","sapient_royal_mail_special_delivery_guaranteed_age_2500":"SDX","sapient_royal_mail_special_delivery_guaranteed_id_750":"SDY","sapient_royal_mail_special_delivery_guaranteed_id_1000":"SDZ","sapient_royal_mail_special_delivery_guaranteed_id_2500":"SEA","sapient_royal_mail_special_delivery_guaranteed_750":"SEB","sapient_royal_mail_special_delivery_guaranteed_1000":"SEC","sapient_royal_mail_special_delivery_guaranteed_2500":"SED","sapient_royal_mail_1st_class_standard_signed_for_letters_daily_rate_service":"STL1","sapient_royal_mail_2nd_class_standard_signed_for_letters_daily_rate_service":"STL2","sapient_royal_mail_tracked_24_high_volume_signature_age":"TPA","sapient_royal_mail_tracked_48_high_volume_signature_age":"TPB","sapient_royal_mail_tracked_24_signature_age":"TPC","sapient_royal_mail_tracked_48_signature_age":"TPD","sapient_royal_mail_tracked_48_high_volume_signature_no_signature":"TPL","sapient_royal_mail_tracked_24_high_volume_signature_no_signature":"TPM","sapient_royal_mail_tracked_24_signature_no_signature":"TPN","sapient_royal_mail_tracked_48_signature_no_signature":"TPS","sapient_royal_mail_tracked_letter_boxable_48_high_volume_signature_no_signature":"TRL","sapient_royal_mail_tracked_letter_boxable_24_high_volume_signature_no_signature":"TRM","sapient_royal_mail_tracked_letter_boxable_24_signature_no_signature":"TRN","sapient_royal_mail_tracked_letter_boxable_48_signature_no_signature":"TRS","sapient_royal_mail_tracked_returns_24":"TSN","sapient_royal_mail_tracked_returns_48":"TSS","sapient_royal_mail_international_business_parcels_zero_sort_priority_WE":"WE1","sapient_royal_mail_international_business_mail_large_letter_zero_sort_priority":"WG1","sapient_royal_mail_international_business_mail_large_letter_zero_sort_priority_machine":"WG4","sapient_royal_mail_international_business_mail_letters_zero_sort_priority":"WP1"},"shipping_options":{"sapient_CL1":{"code":"CL1","meta":{"configurable":true},"type":"float","label":"Cl1"},"sapient_CL2":{"code":"CL2","meta":{"configurable":true},"type":"float","label":"Cl2"},"sapient_CL3":{"code":"CL3","meta":{"configurable":true},"type":"float","label":"Cl3"},"sapient_CL4":{"code":"CL4","meta":{"configurable":true},"type":"float","label":"Cl4"},"sapient_CL5":{"code":"CL5","meta":{"configurable":true},"type":"float","label":"Cl5"},"sapient_signed":{"code":"Signed","meta":{"configurable":true},"type":"boolean","label":"Signed"},"sapient_SMS":{"code":"SMS","meta":{"configurable":true},"type":"boolean","label":"Sms"},"sapient_email":{"code":"Email","meta":{"configurable":true},"type":"boolean","label":"Email"},"sapient_localcollect":{"code":"LocalCollect","meta":{"configurable":true},"type":"boolean","label":"Localcollect"},"sapient_customs_email":{"code":"CustomsEmail","meta":{"configurable":true},"type":"string","label":"Customs Email"},"sapient_customs_phone":{"code":"CustomsPhone","meta":{"configurable":true},"type":"string","label":"Customs Phone"},"sapient_safeplace_location":{"code":"Safeplace","meta":{"configurable":true},"type":"string","label":"Safeplace Location"},"sapient_airn":{"code":"Airn","meta":{"configurable":true},"type":"string","label":"Airn"},"sapient_ebay_vtn":{"code":"EbayVtn","meta":{"configurable":true},"type":"string","label":"Ebay Vtn"},"sapient_reference_2":{"code":"Reference2","meta":{"configurable":true},"type":"string","label":"Reference 2"},"sapient_container_id":{"code":"ContainerId","meta":{"configurable":true},"type":"string","label":"Container Id"},"sapient_shipping_charges":{"code":"ShippingCharges","meta":{"configurable":true},"type":"float","label":"Shipping Charges"},"sapient_quoted_landed_cost":{"code":"QuotedLandedCost","meta":{"configurable":true},"type":"float","label":"Quoted Landed Cost"},"export_licence_required":{"code":"ExportLicenceRequired","meta":{"configurable":true},"type":"boolean","label":"Export Licence Required"},"sapient_business_transaction_type":{"code":"BusinessTransactionType","meta":{"configurable":true},"type":"string","label":"Business Transaction Type"}},"readme":null},{"id":"seko","carrier_name":"seko","display_name":"SEKO Logistics","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Manifest","Tracking","Rating","Shipping"],"connection_fields":{"access_key":{"name":"access_key","required":true,"sensitive":false,"type":"string","label":"Access Key"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"currency":{"code":"currency","name":"currency","required":false,"type":"string","label":"Currency"},"cost_center":{"code":"cost_center","name":"cost_center","required":false,"type":"string","label":"Cost Center"},"cost_center_id":{"code":"cost_center_id","name":"cost_center_id","required":false,"type":"string","label":"Cost Center Id"},"cost_centre_name":{"code":"cost_centre_name","name":"cost_centre_name","required":false,"type":"string","label":"Cost Centre Name"},"cost_centre_id":{"code":"cost_centre_id","name":"cost_centre_id","required":false,"type":"string","label":"Cost Centre Id"},"label_branding":{"code":"label_branding","name":"label_branding","required":false,"type":"string","label":"Label Branding"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"seko_ecommerce_standard_tracked":"eCommerce Standard Tracked","seko_ecommerce_express_tracked":"eCommerce Express Tracked","seko_domestic_express":"Domestic Express","seko_domestic_standard":"Domestic Standard","seko_domestic_large_parcel":"Domestic Large Parcel"},"shipping_options":{"seko_carrier":{"code":"Carrier","meta":{"configurable":true},"type":"string","label":"SEKO Carrier"},"seko_ship_type":{"code":"ShipType","meta":{"configurable":true},"type":"string","label":"SEKO Ship Type"},"seko_package_id":{"code":"PackageId","meta":{"configurable":true},"type":"string","label":"SEKO Package ID"},"seko_destination_id":{"code":"DestinationId","meta":{"configurable":true},"type":"string","label":"SEKO Destination ID"},"seko_product_category":{"code":"ProductCategory","meta":{"configurable":true},"type":"string","label":"SEKO Product Category"},"origin_instructions":{"code":"OriginInstructions","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"Origin Instructions"},"destination_instructions":{"code":"DestinationInstructions","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"Destination Instructions"},"seko_is_saturday_delivery":{"code":"IsSaturdayDelivery","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"SEKO Is Saturday Delivery"},"seko_is_signature_required":{"code":"IsSignatureRequired","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"SEKO Is Signature Required"},"seko_send_tracking_email":{"code":"SendTrackingEmail","meta":{"category":"NOTIFICATION","configurable":true},"type":"boolean","label":"SEKO Send Tracking Email"},"seko_amount_collected":{"code":"AmountCollected","meta":{"configurable":true},"type":"float","label":"SEKO Amount Collected"},"seko_tax_collected":{"code":"TaxCollected","meta":{"configurable":true},"type":"boolean","label":"SEKO Tax Collected"},"seko_cod_amount":{"code":"CODAmount","meta":{"category":"COD","configurable":true},"type":"float","label":"SEKO COD Amount"},"seko_reference_2":{"code":"Reference2","meta":{"configurable":true},"type":"string","label":"SEKO Reference 2"},"seko_reference_3":{"code":"Reference3","meta":{"configurable":true},"type":"string","label":"SEKO Reference 3"},"seko_invoice_data":{"code":"InvoiceData","meta":{"configurable":true},"type":"string","label":"SEKO Invoice Data"},"seko_origin_id":{"code":"OriginId","meta":{"configurable":true},"type":"integer","label":"SEKO Origin ID"},"seko_print_to_printer":{"code":"PrintToPrinter","meta":{"configurable":true},"type":"boolean","label":"SEKO Print To Printer"},"seko_cif_value":{"code":"CIFValue","meta":{"configurable":true},"type":"float","label":"SEKO Cif Value"},"seko_freight_value":{"code":"FreightValue","meta":{"configurable":true},"type":"float","label":"SEKO Freight Value"},"seko_send_label":{"code":"SendLabel","meta":{"configurable":true},"type":"boolean","label":"SEKO Send Label"},"seko_special_instructions":{"code":"SpecialInstructions","meta":{"category":"INSTRUCTIONS","configurable":true},"type":"string","label":"SEKO Special Instructions"},"seko_insurance_value":{"code":"InsuranceValue","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"SEKO Insurance Value"},"seko_estimated_delivery_date":{"code":"EstimatedDeliveryDate","meta":{"configurable":true},"type":"string","label":"SEKO Estimated Delivery Date"},"seko_dangerous_goods":{"code":"DangerousGoods","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"SEKO Dangerous Goods"},"seko_dg_additional_handling":{"code":"DGAdditionalHandling","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"SEKO Dg Additional Handling"},"seko_dg_hazchem_code":{"code":"DGHazchemCode","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"SEKO Dg Hazchem Code"},"seko_dg_radioactive":{"code":"DGRadioactive","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"SEKO Dg Radioactive"},"seko_dg_cargo_aircraft_only":{"code":"DGCargoAircraftOnly","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"SEKO Dg Cargo Aircraft Only"},"seko_dg_limited_quantity":{"code":"DGLimitedQuantity","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"SEKO Dg Limited Quantity"},"seko_dg_total_quantity":{"code":"DGTotalQuantity","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"integer","label":"SEKO Dg Total Quantity"},"seko_dg_total_kg":{"code":"DGTotalKg","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"float","label":"SEKO Dg Total Kg"},"seko_dg_signoff_name":{"code":"DGSignOffName","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"SEKO Dg Signoff Name"},"seko_dg_signoff_role":{"code":"DGSignOffRole","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"string","label":"SEKO Dg Signoff Role"},"doc_files":{"code":"doc_files","meta":{"category":"PAPERLESS","configurable":true},"type":"object","label":"Doc Files"},"doc_references":{"code":"doc_references","meta":{"category":"PAPERLESS","configurable":true},"type":"object","label":"Doc References"}},"readme":null},{"id":"sendle","carrier_name":"sendle","display_name":"Sendle","integration_status":"production-ready","website":"https://www.sendle.com","description":"Sendle is a registered B Corp and 100% carbon neutral shipping carrier for small businesses, offering affordable package delivery services in Australia and the United States.","documentation":"https://www.sendle.com/developers","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"sendle_id":{"name":"sendle_id","required":true,"sensitive":false,"type":"string","label":"Sendle Id"},"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"sendle_standard_pickup":"STANDARD-PICKUP","sendle_standard_dropoff":"STANDARD-DROPOFF","sendle_express_pickup":"EXPRESS-PICKUP"},"shipping_options":{"sendle_hide_pickup_address":{"code":"hide_pickup_address","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Sendle Hide Pickup Address"},"sendle_first_mile_option":{"code":"first_mile_option","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Sendle First Mile Option"}},"readme":null},{"id":"shipengine","carrier_name":"shipengine","display_name":"ShipEngine","integration_status":"beta","website":"https://www.shipengine.com","description":"ShipEngine multi-carrier shipping platform integration for Karrio","documentation":"https://www.shipengine.com/docs/","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"carrier_ids":{"name":"carrier_ids","required":false,"sensitive":false,"type":"string","label":"Carrier Ids"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"preferred_carriers":{"code":"preferred_carriers","name":"preferred_carriers","required":false,"type":"list","label":"Preferred Carriers"},"excluded_carriers":{"code":"excluded_carriers","name":"excluded_carriers","required":false,"type":"list","label":"Excluded Carriers"},"default_service_codes":{"code":"default_service_codes","name":"default_service_codes","required":false,"type":"list","label":"Default Service Codes"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"label_format":{"code":"label_format","name":"label_format","required":false,"type":"string","label":"Label Format"},"label_size":{"code":"label_size","name":"label_size","required":false,"type":"string","label":"Label Size"}},"shipping_services":{"shipengine_auto":"ShipEngine Auto-Select","usps_ground_advantage":"USPS Ground Advantage","usps_priority_mail":"USPS Priority Mail","usps_priority_mail_express":"USPS Priority Mail Express","fedex_ground":"FedEx Ground","fedex_2day":"FedEx 2Day","fedex_standard_overnight":"FedEx Standard Overnight","ups_ground":"UPS Ground","ups_3_day_select":"UPS 3 Day Select","ups_2nd_day_air":"UPS 2nd Day Air","ups_next_day_air":"UPS Next Day Air","shipengine_ups_ups_ground":"UPS Ground via ShipEngine"},"shipping_options":{"insurance_amount":{"code":"insurance_amount","meta":{"configurable":true},"type":"float","label":"Insurance Amount"},"delivery_confirmation":{"code":"delivery_confirmation","meta":{"configurable":true},"type":"string","label":"Delivery Confirmation"},"signature_confirmation":{"code":"signature_confirmation","meta":{"configurable":true},"type":"string","label":"Signature Confirmation"},"adult_signature":{"code":"adult_signature","meta":{"configurable":true},"type":"boolean","label":"Adult Signature"},"saturday_delivery":{"code":"saturday_delivery","meta":{"configurable":true},"type":"boolean","label":"Saturday Delivery"},"collect_on_delivery":{"code":"collect_on_delivery","meta":{"configurable":true},"type":"float","label":"Collect On Delivery"}},"readme":null},{"id":"smartkargo","carrier_name":"smartkargo","display_name":"SmartKargo","integration_status":"beta","website":"https://www.smartkargo.com","description":"SmartKargo air cargo shipping integration","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_number":{"name":"account_number","required":true,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"primary_id":{"code":"primary_id","name":"primary_id","required":false,"type":"string","label":"Primary Id"},"site_id":{"code":"site_id","name":"site_id","required":false,"type":"string","label":"Site Id"},"additional_id":{"code":"additional_id","name":"additional_id","required":false,"type":"string","label":"Additional Id"},"origin":{"code":"origin","name":"origin","required":false,"type":"string","label":"Origin"},"destination":{"code":"destination","name":"destination","required":false,"type":"string","label":"Destination"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"currency":{"code":"currency","name":"currency","required":false,"type":"string","label":"Currency"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"partner_tracking_url":{"code":"partner_tracking_url","name":"partner_tracking_url","required":false,"type":"string","label":"Partner Tracking Url"},"partner_tracking_api_code":{"code":"partner_tracking_api_code","name":"partner_tracking_api_code","required":false,"type":"string","label":"Partner Tracking Api Code"}},"shipping_services":{"smartkargo_express":"EXP","smartkargo_priority":"EPR","smartkargo_standard":"EST","smartkargo_economy":"ECL"},"shipping_options":{"smartkargo_insurance":{"code":"hasInsurance","meta":{"configurable":true},"type":"boolean","label":"Insurance"},"smartkargo_declared_value":{"code":"insuranceAmmount","meta":{"configurable":true},"type":"float","label":"Declared Value"},"smartkargo_delivery_type":{"code":"deliveryType","meta":{"configurable":true},"type":"string","label":"Delivery Type"},"smartkargo_channel":{"code":"channel","meta":{"configurable":true},"type":"string","label":"Channel"},"smartkargo_label_ref2":{"code":"labelRef2","meta":{"configurable":true},"type":"string","label":"Label Ref2"},"smartkargo_special_handling":{"code":"specialHandlingType","meta":{"configurable":true},"type":"string","label":"Special Handling"},"smartkargo_commodity_type":{"code":"commodityType","meta":{"configurable":true},"type":"string","label":"Commodity Type"},"smartkargo_incoterm":{"code":"incoterm","meta":{"configurable":true},"type":"string","label":"Incoterm"},"smartkargo_additional_info_01":{"code":"additionalInfo01","meta":{"configurable":true},"type":"string","label":"Additional Info 01"},"smartkargo_additional_info_02":{"code":"additionalInfo02","meta":{"configurable":true},"type":"string","label":"Additional Info 02"},"smartkargo_additional_info_03":{"code":"additionalInfo03","meta":{"configurable":true},"type":"string","label":"Additional Info 03"},"smartkargo_additional_info_04":{"code":"additionalInfo04","meta":{"configurable":true},"type":"string","label":"Additional Info 04"}},"readme":null},{"id":"spring","carrier_name":"spring","display_name":"Spring","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"label_format":{"code":"label_format","name":"label_format","required":false,"type":"string","label":"Label Format"}},"shipping_services":{"spring_tracked":"TRCK","spring_signature":"SIGN","spring_untracked":"UNTR","spring_collect":"CLLCT","spring_express":"EXPR","spring_import":"IMPRT","spring_back_returns":"BACK","spring_back_tracked":"BACKT","spring_no_label":"NOLABEL","spring_postnl_parcel_eu":"PPLEU","spring_postnl_parcel_benelux":"PPND","spring_postnl_parcel_benelux_sign":"PPNDS","spring_postnl_parcel_benelux_no_neighbor":"PPHD","spring_postnl_parcel_benelux_sign_no_neighbor":"PPHDS","spring_postnl_parcel_benelux_upu":"PPLUP","spring_postnl_parcel_globalpack_ems":"PPLGE","spring_postnl_parcel_globalpack_upu":"PPLGU","spring_postnl_parcel_epg":"PPLEP","spring_postnl_parcel_epg_noneu":"PPNEU","spring_postnl_lightweight_china":"PPLLW","spring_postnl_collect_service":"PPLCS","spring_postnl_packet_tracked":"PPTT","spring_postnl_packet_registered":"PPTR","spring_postnl_packet_non_tracked":"PPNT","spring_postnl_packet_boxable_bag_trace":"PPBBT","spring_postnl_packet_bag_trace":"PPBT","spring_postnl_packet_boxable_tracked":"PPBTT","spring_postnl_packet_boxable_non_tracked":"PPBNT","spring_royal_mail_tracked_24":"RM24","spring_royal_mail_tracked_24_sign":"RM24S","spring_royal_mail_tracked_48":"RM48","spring_royal_mail_tracked_48_2":"RM482","spring_royal_mail_tracked_48_sign":"RM48S","spring_sending_mainland":"SEND","spring_sending_islands":"SEND2","spring_italian_post_crono":"ITCR","spring_italian_post_crono_express":"ITCRX","spring_dpd_de":"DPDDE","spring_hermes_sign":"HEHDS","spring_hermes_collect":"HEDCS","spring_colis_prive":"CPHD","spring_colis_prive_sign":"CPHDS","spring_com_standard":"SCST","spring_com_standard_sign":"SCSTS","spring_com_express":"SCEX","spring_com_express_sign":"SCEXS","spring_usa_parcel_ground":"UPGR","spring_usa_parcel_ground_sign":"UPGRS","spring_usa_parcel_express":"UPEX","spring_usa_parcel_express_sign":"UPEXS","spring_usa_parcel_max":"UPMA","spring_usa_parcel_max_sign":"UPMAS","spring_usa_parcel_ground_dg":"UPDG","spring_usa_parcel_ground_dg_sign":"UDGS","spring_usa_parcel_plus_ground_dg":"UPPDG","spring_usa_parcel_plus_ground_dg_sign":"UPDGS","spring_packeta":"PACHD","spring_mailalliance_boxable":"MABNT","spring_austrian_post":"ATEHD"},"shipping_options":{"spring_customs_duty":{"code":"CustomsDuty","meta":{"configurable":true},"type":"string","label":"Spring Customs Duty"},"spring_declaration_type":{"code":"DeclarationType","meta":{"configurable":true},"type":"string","label":"Spring Declaration Type"},"spring_dangerous_goods":{"code":"DangerousGoods","meta":{"configurable":true},"type":"boolean","label":"Spring Dangerous Goods"},"spring_shipping_value":{"code":"ShippingValue","meta":{"configurable":true},"type":"float","label":"Spring Shipping Value"},"spring_display_id":{"code":"DisplayId","meta":{"configurable":true},"type":"string","label":"Spring Display ID"},"spring_invoice_number":{"code":"InvoiceNumber","meta":{"configurable":true},"type":"string","label":"Spring Invoice Number"},"spring_order_reference":{"code":"OrderReference","meta":{"configurable":true},"type":"string","label":"Spring Order Reference"},"spring_order_date":{"code":"OrderDate","meta":{"configurable":true},"type":"string","label":"Spring Order Date"},"spring_consignor_vat":{"code":"ConsignorVat","meta":{"configurable":true},"type":"string","label":"Spring Consignor Vat"},"spring_consignor_eori":{"code":"ConsignorEori","meta":{"configurable":true},"type":"string","label":"Spring Consignor Eori"},"spring_consignor_nl_vat":{"code":"ConsignorNlVat","meta":{"configurable":true},"type":"string","label":"Spring Consignor Nl Vat"},"spring_consignor_eu_eori":{"code":"ConsignorEuEori","meta":{"configurable":true},"type":"string","label":"Spring Consignor EU Eori"},"spring_consignor_gb_eori":{"code":"ConsignorGbEori","meta":{"configurable":true},"type":"string","label":"Spring Consignor Gb Eori"},"spring_consignor_ioss":{"code":"ConsignorIoss","meta":{"configurable":true},"type":"string","label":"Spring Consignor Ioss"},"spring_consignor_local_tax_number":{"code":"ConsignorLocalTaxNumber","meta":{"configurable":true},"type":"string","label":"Spring Consignor Local Tax Number"},"spring_export_carrier_name":{"code":"ExportCarrierName","meta":{"configurable":true},"type":"string","label":"Spring Export Carrier Name"},"spring_export_awb":{"code":"ExportAwb","meta":{"configurable":true},"type":"string","label":"Spring Export Awb"},"spring_pudo_location_id":{"code":"PudoLocationId","meta":{"configurable":true},"type":"string","label":"Spring Pudo Location ID"}},"readme":null},{"id":"teleship","carrier_name":"teleship","display_name":"Teleship","integration_status":"beta","website":"https://www.teleship.com","description":"Teleship is an international shipping platform providing end-to-end logistics solutions with real-time rates, automated customs compliance, and shipment tracking.","documentation":"https://developers.teleship.com","is_enabled":true,"capabilities":["Manifest","OAuth","Rating","Duties","Pickup","Shipping","Webhook","Tracking"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"label_format":{"code":"label_format","enum":["PDF","ZPL","PNG"],"name":"label_format","required":false,"type":"string","label":"Label Format"}},"shipping_services":{"teleship_expedited_pickup":"TELESHIP-EXPEDITED-PICKUP","teleship_expedited_dropoff":"TELESHIP-EXPEDITED-DROPOFF","teleship_standard_dropoff":"TELESHIP-STANDARD-DROPOFF","teleship_standard_pickup":"TELESHIP-STANDARD-PICKUP","teleship_postal_dropoff":"TELESHIP-POSTAL-DROPOFF","teleship_postal_pickup":"TELESHIP-POSTAL-PICKUP"},"shipping_options":{"teleship_signature_required":{"code":"signatureRequired","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Teleship Signature Required"},"teleship_delivery_warranty":{"code":"deliveryWarranty","meta":{"category":"INSURANCE","configurable":true},"type":"boolean","label":"Teleship Delivery Warranty"},"teleship_delivery_PUDO":{"code":"deliveryPUDO","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Teleship Delivery Pudo"},"teleship_low_carbon":{"code":"lowCarbon","meta":{"configurable":true},"type":"boolean","label":"Teleship Low Carbon"},"teleship_duty_tax_calculation":{"code":"dutyTaxCalculation","meta":{"configurable":true},"type":"boolean","label":"Teleship Duty Tax Calculation"},"teleship_customer_reference":{"code":"customerReference","meta":{"configurable":true},"type":"string","label":"Teleship Customer Reference"},"teleship_order_tracking_reference":{"code":"orderTrackingReference","meta":{"configurable":true},"type":"string","label":"Teleship Order Tracking Reference"},"teleship_commercial_invoice_reference":{"code":"commercialInvoiceReference","meta":{"category":"INVOICE","configurable":true},"type":"string","label":"Teleship Commercial Invoice Reference"}},"readme":null},{"id":"tge","carrier_name":"tge","display_name":"TGE","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Manifest","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"toll_username":{"name":"toll_username","required":true,"sensitive":false,"type":"string","label":"Toll Username"},"toll_password":{"name":"toll_password","required":true,"sensitive":false,"type":"string","label":"Toll Password"},"my_toll_token":{"name":"my_toll_token","required":true,"sensitive":false,"type":"string","label":"My Toll Token"},"my_toll_identity":{"name":"my_toll_identity","required":true,"sensitive":false,"type":"string","label":"My Toll Identity"},"account_code":{"name":"account_code","required":false,"sensitive":false,"type":"string","label":"Account Code"},"sscc_count":{"name":"sscc_count","required":false,"sensitive":false,"type":"integer","label":"Sscc Count"},"shipment_count":{"name":"shipment_count","required":false,"sensitive":false,"type":"integer","label":"Shipment Count"},"account_country_code":{"default":"AU","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"channel":{"code":"channel","name":"channel","required":false,"type":"string","label":"Channel"},"server_url":{"code":"server_url","name":"server_url","required":false,"type":"string","label":"Server Url"},"text_color":{"code":"text_color","name":"text_color","required":false,"type":"string","label":"Text Color"},"brand_color":{"code":"brand_color","name":"brand_color","required":false,"type":"string","label":"Brand Color"},"business_id":{"code":"business_id","name":"business_id","required":false,"type":"string","label":"Business Id"},"freight_mode":{"code":"freight_mode","name":"freight_mode","required":false,"type":"string","label":"Freight Mode"},"message_sender":{"code":"message_sender","name":"message_sender","required":false,"type":"string","label":"Message Sender"},"SYSID":{"code":"SYSID","name":"SYSID","required":false,"type":"string","label":"Sysid"},"SHIP_GS1":{"code":"SHIP_GS1","name":"SHIP_GS1","required":false,"type":"string","label":"Ship Gs1"},"SHIP_range_end":{"code":"SHIP_range_end","name":"SHIP_range_end","required":false,"type":"integer","label":"Ship Range End"},"SHIP_range_start":{"code":"SHIP_range_start","name":"SHIP_range_start","required":false,"type":"integer","label":"Ship Range Start"},"SSCC_GS1":{"code":"SSCC_GS1","name":"SSCC_GS1","required":false,"type":"string","label":"Sscc Gs1"},"SSCC_range_end":{"code":"SSCC_range_end","name":"SSCC_range_end","required":false,"type":"integer","label":"Sscc Range End"},"SSCC_range_start":{"code":"SSCC_range_start","name":"SSCC_range_start","required":false,"type":"integer","label":"Sscc Range Start"}},"shipping_services":{"tge_freight_service":"X"},"shipping_options":{"tge_ssc_ids":{"code":"tge_ssc_ids","meta":{"configurable":true},"type":"list","label":"Ssc Ids"},"tge_shipment_ids":{"code":"tge_shipment_ids","meta":{"configurable":true},"type":"list","label":"Shipment Ids"},"tge_freight_mode":{"code":"tge_freight_mode","meta":{"configurable":true},"type":"string","label":"Freight Mode"},"tge_despatch_date":{"code":"tge_despatch_date","meta":{"configurable":true},"type":"string","label":"Despatch Date"},"tge_special_instruction":{"code":"tge_special_instruction","meta":{"configurable":true},"type":"string","label":"Special Instruction"},"tge_required_delivery_date":{"code":"tge_required_delivery_date","meta":{"configurable":true},"type":"string","label":"Required Delivery Date"}},"readme":null},{"id":"tnt","carrier_name":"tnt","display_name":"TNT","integration_status":"beta","website":"https://www.tnt.com","description":"TNT is an international courier delivery services company with headquarters in the Netherlands.","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"username":{"name":"username","required":true,"sensitive":false,"type":"string","label":"Username"},"password":{"name":"password","required":true,"sensitive":false,"type":"string","label":"Password"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"app_id":{"code":"app_id","name":"app_id","required":false,"type":"string","label":"App Id"},"email_from":{"code":"email_from","name":"email_from","required":false,"type":"string","label":"Email From"}},"shipping_services":{"tnt_special_express":"1N","tnt_9_00_express":"09N","tnt_10_00_express":"10N","tnt_12_00_express":"12N","tnt_express":"EX","tnt_economy_express":"48N","tnt_global_express":"15N"},"shipping_options":{"tnt_priority":{"code":"PR","meta":{"configurable":true},"type":"string","label":"Priority"},"tnt_insurance":{"code":"IN","meta":{"configurable":true},"type":"float","label":"Insurance"},"tnt_enhanced_liability":{"code":"EL","meta":{"configurable":true},"type":"string","label":"Enhanced Liability"},"tnt_dangerous_goods_fully_regulated":{"code":"HZ","meta":{"configurable":true},"type":"string","label":"Dangerous Goods Fully Regulated"},"tnt_dangerous_goods_in_limited_quantities":{"code":"LQ","meta":{"configurable":true},"type":"string","label":"Dangerous Goods In Limited Quantities"},"tnt_dry_ice_shipments":{"code":"DI","meta":{"configurable":true},"type":"string","label":"Dry Ice Shipments"},"tnt_biological_substances":{"code":"BB","meta":{"configurable":true},"type":"string","label":"Biological Substances"},"tnt_lithium_batteries":{"code":"LB","meta":{"configurable":true},"type":"string","label":"Lithium Batteries"},"tnt_dangerous_goods_in_excepted_quantities":{"code":"EQ","meta":{"configurable":true},"type":"string","label":"Dangerous Goods In Excepted Quantities"},"tnt_radioactive_materials_in_excepted_packages":{"code":"XP","meta":{"configurable":true},"type":"string","label":"Radioactive Materials In Excepted Packages"},"tnt_pre_delivery_notification":{"code":"SMS","meta":{"configurable":true},"type":"string","label":"Pre Delivery Notification"},"tnt_division_international_shipments":{"code":"G","meta":{"configurable":true},"type":"boolean","label":"Division International Shipments"},"tnt_division_global_link_domestic":{"code":"D","meta":{"configurable":true},"type":"boolean","label":"Division Global Link Domestic"},"tnt_division_german_domestic":{"code":"H","meta":{"configurable":true},"type":"boolean","label":"Division German Domestic"},"tnt_division_uk_domestic":{"code":"010","meta":{"configurable":true},"type":"boolean","label":"Division Uk Domestic"}},"readme":null},{"id":"ups","carrier_name":"ups","display_name":"UPS","integration_status":"production-ready","website":"https://www.ups.com","description":"UPS is an American multinational shipping & receiving and supply chain management company.","documentation":null,"is_enabled":true,"capabilities":["Rating","Pickup","Paperless","Shipping","Tracking"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"cost_center":{"code":"cost_center","name":"cost_center","required":false,"type":"string","label":"Cost Center"},"merchant_id":{"code":"merchant_id","name":"merchant_id","required":false,"type":"string","label":"Merchant Id"},"enforce_zpl":{"code":"enforce_zpl","name":"enforce_zpl","required":false,"type":"boolean","label":"Enforce ZPL"},"label_type":{"code":"label_type","enum":["PDF_6x4","PDF_8x4","ZPL_6x4"],"name":"label_type","required":false,"type":"string","label":"Label Type"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"ups_standard":"UPS Standard","ups_worldwide_express":"UPS Worldwide Express","ups_worldwide_expedited":"UPS Worldwide Expedited","ups_worldwide_express_plus":"UPS Worldwide Express Plus","ups_worldwide_saver":"UPS Worldwide Saver","ups_2nd_day_air":"UPS 2nd Day Air","ups_2nd_day_air_am":"UPS 2nd Day Air A.M.","ups_3_day_select":"UPS 3 Day Select","ups_ground":"UPS Ground","ups_next_day_air":"UPS Next Day Air","ups_next_day_air_early":"UPS Next Day Air Early","ups_next_day_air_saver":"UPS Next Day Air Saver","ups_expedited_ca":"UPS Expedited CA","ups_express_saver_ca":"UPS Express Saver CA","ups_3_day_select_ca_us":"UPS 3 Day Select CA US","ups_access_point_economy_ca":"UPS Access Point Economy CA","ups_express_ca":"UPS Express CA","ups_express_early_ca":"UPS Express Early CA","ups_express_saver_intl_ca":"UPS Express Saver Intl CA","ups_standard_ca":"UPS Standard CA","ups_worldwide_expedited_ca":"UPS Worldwide Expedited CA","ups_worldwide_express_ca":"UPS Worldwide Express CA","ups_worldwide_express_plus_ca":"UPS Worldwide Express Plus CA","ups_express_early_ca_us":"UPS Express Early CA US","ups_access_point_economy_eu":"UPS Access Point Economy EU","ups_expedited_eu":"UPS Expedited EU","ups_express_eu":"UPS Express EU","ups_standard_eu":"UPS Standard EU","ups_worldwide_express_plus_eu":"UPS Worldwide Express Plus EU","ups_worldwide_saver_eu":"UPS Worldwide Saver EU","ups_access_point_economy_mx":"UPS Access Point Economy MX","ups_expedited_mx":"UPS Expedited MX","ups_express_mx":"UPS Express MX","ups_standard_mx":"UPS Standard MX","ups_worldwide_express_plus_mx":"UPS Worldwide Express Plus MX","ups_worldwide_saver_mx":"UPS Worldwide Saver MX","ups_access_point_economy_pl":"UPS Access Point Economy PL","ups_today_dedicated_courrier_pl":"UPS Today Dedicated Courrier PL","ups_today_express_pl":"UPS Today Express PL","ups_today_express_saver_pl":"UPS Today Express Saver PL","ups_today_standard_pl":"UPS Today Standard PL","ups_expedited_pl":"UPS Expedited PL","ups_express_pl":"UPS Express PL","ups_express_plus_pl":"UPS Express Plus PL","ups_express_saver_pl":"UPS Express Saver PL","ups_standard_pl":"UPS Standard PL","ups_2nd_day_air_pr":"UPS 2nd Day Air PR","ups_ground_pr":"UPS Ground PR","ups_next_day_air_pr":"UPS Next Day Air PR","ups_next_day_air_early_pr":"UPS Next Day Air Early PR","ups_worldwide_expedited_pr":"UPS Worldwide Expedited PR","ups_worldwide_express_pr":"UPS Worldwide Express PR","ups_worldwide_express_plus_pr":"UPS Worldwide Express Plus PR","ups_worldwide_saver_pr":"UPS Worldwide Saver PR","ups_express_12_00_de":"UPS Express 12:00 DE","ups_worldwide_express_freight":"UPS Worldwide Express Freight","ups_worldwide_express_freight_midday":"UPS Worldwide Express Freight Midday","ups_worldwide_economy_ddu":"UPS Worldwide Economy DDU","ups_worldwide_economy_ddp":"UPS Worldwide Economy DDP"},"shipping_options":{"ups_saturday_pickup_indicator":{"code":"SaturdayPickupIndicator","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"UPS Saturday Pickup Indicator"},"ups_saturday_delivery_indicator":{"code":"SaturdayDeliveryIndicator","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"UPS Saturday Delivery Indicator"},"ups_sunday_delivery_indicator":{"code":"SundayDeliveryIndicator","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"UPS Sunday Delivery Indicator"},"ups_access_point_cod":{"code":"AccessPointCOD","meta":{"category":"COD","configurable":true},"type":"float","label":"UPS Access Point COD"},"ups_deliver_to_addressee_only_indicator":{"code":"DeliverToAddresseeOnlyIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Deliver To Addressee Only Indicator"},"ups_direct_delivery_only_indicator":{"code":"DirectDeliveryOnlyIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Direct Delivery Only Indicator"},"ups_cod":{"code":"COD","meta":{"category":"COD","configurable":true},"type":"float","label":"UPS COD"},"ups_return_of_document_indicator":{"code":"ReturnOfDocumentIndicator","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"UPS Return Of Document Indicator"},"ups_carbonneutral_indicator":{"code":"UPScarbonneutralIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Carbonneutral Indicator"},"ups_certificate_of_origin_indicator":{"code":"CertificateOfOriginIndicator","meta":{"configurable":true},"type":"string","label":"UPS Certificate Of Origin Indicator"},"ups_shipper_export_declaration_indicator":{"code":"ShipperExportDeclarationIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Shipper Export Declaration Indicator"},"ups_commercial_invoice_removal_indicator":{"code":"CommercialInvoiceRemovalIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Commercial Invoice Removal Indicator"},"ups_import_control":{"code":"ImportControl","enum":["03","04","05"],"meta":{"configurable":true},"type":"string","label":"UPS Import Control"},"ups_return_service":{"code":"ReturnService","enum":["ups_print_and_mail","ups_return_1_attempt","ups_return_3_attempt","ups_electronic_return_label","ups_print_return_label","ups_exchange_print_return_label","ups_pack_collect_1_attempt_box_1","ups_pack_collect_1_attempt_box_2","ups_pack_collect_1_attempt_box_3","ups_pack_collect_1_attempt_box_4","ups_pack_collect_1_attempt_box_5","ups_pack_collect_3_attempt_box_1","ups_pack_collect_3_attempt_box_2","ups_pack_collect_3_attempt_box_3","ups_pack_collect_3_attempt_box_4","ups_pack_collect_3_attempt_box_5"],"meta":{"category":"RETURN","configurable":true},"type":"string","label":"UPS Return Service"},"ups_epra_indicator":{"code":"EPRAIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Epra Indicator"},"ups_lift_gate_at_pickup_indicator":{"code":"LiftGateAtPickupIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Lift Gate At Pickup Indicator"},"ups_lift_gate_at_delivery_indicator":{"code":"LiftGateAtDeliveryIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Lift Gate At Delivery Indicator"},"ups_drop_off_at_ups_facility_indicator":{"code":"DropOffAtUPSFacilityIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Drop Off At UPS Facility Indicator"},"ups_master_carton_indicator":{"code":"MasterCartonIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Master Carton Indicator"},"ups_exchange_forward_indicator":{"code":"ExchangeForwardIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Exchange Forward Indicator"},"ups_hold_for_pickup_indicator":{"code":"HoldForPickupIndicator","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"UPS Hold For Pickup Indicator"},"ups_dropoff_at_ups_facility_indicator":{"code":"DropoffAtUPSFacilityIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Dropoff At UPS Facility Indicator"},"ups_lift_gate_for_pickup_indicator":{"code":"LiftGateForPickupIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Lift Gate For Pickup Indicator"},"ups_lift_gate_for_delivery_indicator":{"code":"LiftGateForDeliveryIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Lift Gate For Delivery Indicator"},"ups_sdl_shipment_indicator":{"code":"SDLShipmentIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Sdl Shipment Indicator"},"ups_item_disposal":{"code":"ItemDisposal","meta":{"configurable":true},"type":"boolean","label":"UPS Item Disposal"},"ups_available_services_option":{"code":"AvailableServicesOption","enum":["1","2","3"],"meta":{"configurable":true},"type":"string","label":"UPS Available Services Option"},"ups_delivery_confirmation":{"code":"DeliveryConfirmation","enum":["1","2"],"meta":{"category":"SIGNATURE","configurable":true},"type":"string","label":"UPS Delivery Confirmation"},"ups_delivery_confirmation_level":{"code":"DeliveryConfirmationLevel","enum":["P","S"],"meta":{"configurable":true},"type":"string","label":"UPS Delivery Confirmation Level"},"ups_inside_delivery":{"code":"InsideDelivery","enum":["01","02","03"],"meta":{"configurable":true},"type":"string","label":"UPS Inside Delivery"},"ups_restricted_articles":{"code":"RestrictedArticles","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Restricted Articles"},"ups_alcoholic_beverages_indicator":{"code":"AlcoholicBeveragesIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Alcoholic Beverages Indicator"},"ups_diagnostic_specimens_indicator":{"code":"DiagnosticSpecimensIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Diagnostic Specimens Indicator"},"ups_perishables_indicator":{"code":"PerishablesIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Perishables Indicator"},"ups_plants_indicator":{"code":"PlantsIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Plants Indicator"},"ups_seeds_indicator":{"code":"SeedsIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Seeds Indicator"},"ups_special_exceptions_indicator":{"code":"SpecialExceptionsIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Special Exceptions Indicator"},"ups_tobacco_indicator":{"code":"TobaccoIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Tobacco Indicator"},"ups_ecigarettes_indicator":{"code":"ECigarettesIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Ecigarettes Indicator"},"ups_hemp_cbd_indicator":{"code":"HempCBDIndicator","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"UPS Hemp Cbd Indicator"},"ups_negotiated_rates_indicator":{"code":"NegotiatedRatesIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Negotiated Rates Indicator"},"ups_frs_shipment_indicator":{"code":"FRSShipmentIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Frs Shipment Indicator"},"ups_rate_chart_indicator":{"code":"RateChartIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Rate Chart Indicator"},"ups_user_level_discount_indicator":{"code":"UserLevelDiscountIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS User Level Discount Indicator"},"ups_tpfc_negotiated_rates_indicator":{"code":"TPFCNegotiatedRatesIndicator","meta":{"configurable":true},"type":"boolean","label":"UPS Tpfc Negotiated Rates Indicator"},"ups_access_point_pickup":{"code":"01","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"UPS Access Point Pickup"},"ups_access_point_delivery":{"code":"02","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"UPS Access Point Delivery"}},"readme":null},{"id":"usps","carrier_name":"usps","display_name":"USPS","integration_status":"production-ready","website":"https://www.usps.com","description":"The United States Postal Service is an independent agency of the executive branch of the United States federal government responsible for providing postal service in the United States.","documentation":"https://www.usps.com/business/web-tools-apis","is_enabled":true,"capabilities":["Manifest","Rating","Pickup","Shipping","Tracking"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_type":{"default":"EPS","enum":["EPS","PERMIT","METER"],"name":"account_type","required":false,"sensitive":false,"type":"string","label":"Account Type"},"manifest_MID":{"name":"manifest_MID","required":false,"sensitive":false,"type":"string","label":"Manifest Mid"},"CRID":{"name":"CRID","required":false,"sensitive":false,"type":"string","label":"Crid"},"MID":{"name":"MID","required":false,"sensitive":false,"type":"string","label":"Mid"},"account_country_code":{"default":"US","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"permit_ZIP":{"code":"permit_ZIP","name":"permit_ZIP","required":false,"type":"string","label":"Permit Zip"},"permit_number":{"code":"permit_number","name":"permit_number","required":false,"type":"string","label":"Permit Number"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"}},"shipping_services":{"usps_parcel_select_lightweight":"PARCEL_SELECT_LIGHTWEIGHT","usps_parcel_select":"PARCEL_SELECT","usps_priority_mail_express":"PRIORITY_MAIL_EXPRESS","usps_priority_mail":"PRIORITY_MAIL","usps_library_mail":"LIBRARY_MAIL","usps_media_mail":"MEDIA_MAIL","usps_bound_printed_matter":"BOUND_PRINTED_MATTER","usps_connect_local":"USPS_CONNECT_LOCAL","usps_connect_mail":"USPS_CONNECT_MAIL","usps_connect_next_day":"USPS_CONNECT_NEXT_DAY","usps_connect_regional":"USPS_CONNECT_REGIONAL","usps_connect_same_day":"USPS_CONNECT_SAME_DAY","usps_ground_advantage":"USPS_GROUND_ADVANTAGE","usps_domestic_matter_for_the_blind":"DOMESTIC_MATTER_FOR_THE_BLIND","usps_all":"ALL"},"shipping_options":{"usps_label_delivery_service":{"code":"415","meta":{"configurable":true},"type":"boolean","label":"USPS Label Delivery Service"},"usps_tracking_plus_6_months":{"code":"480","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Plus 6 Months"},"usps_tracking_plus_1_year":{"code":"481","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Plus 1 Year"},"usps_tracking_plus_3_years":{"code":"482","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Plus 3 Years"},"usps_tracking_plus_5_years":{"code":"483","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Plus 5 Years"},"usps_tracking_plus_7_years":{"code":"484","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Plus 7 Years"},"usps_tracking_plus_10_years":{"code":"485","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Plus 10 Years"},"usps_tracking_plus_signature_3_years":{"code":"486","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Tracking Plus Signature 3 Years"},"usps_tracking_plus_signature_5_years":{"code":"487","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Tracking Plus Signature 5 Years"},"usps_tracking_plus_signature_7_years":{"code":"488","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Tracking Plus Signature 7 Years"},"usps_tracking_plus_signature_10_years":{"code":"489","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Tracking Plus Signature 10 Years"},"usps_hazardous_materials_air_eligible_ethanol":{"code":"810","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Air Eligible Ethanol"},"usps_hazardous_materials_class_1_toy_propellant_safety_fuse_package":{"code":"811","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 1 Toy Propellant Safety Fuse Package"},"usps_hazardous_materials_class_3_flammable_and_combustible_liquids":{"code":"812","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 3 Flammable And Combustible Liquids"},"usps_hazardous_materials_class_7_radioactive_materials":{"code":"813","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 7 Radioactive Materials"},"usps_hazardous_materials_class_8_air_eligible_corrosive_materials":{"code":"814","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 8 Air Eligible Corrosive Materials"},"usps_hazardous_materials_class_8_nonspillable_wet_batteries":{"code":"815","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 8 Nonspillable Wet Batteries"},"usps_hazardous_materials_class_9_lithium_battery_marked_ground_only":{"code":"816","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Lithium Battery Marked Ground Only"},"usps_hazardous_materials_class_9_lithium_battery_returns":{"code":"817","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Lithium Battery Returns"},"usps_hazardous_materials_class_9_marked_lithium_batteries":{"code":"818","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Marked Lithium Batteries"},"usps_hazardous_materials_class_9_dry_ice":{"code":"819","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Dry Ice"},"usps_hazardous_materials_class_9_unmarked_lithium_batteries":{"code":"820","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Unmarked Lithium Batteries"},"usps_hazardous_materials_class_9_magnetized_materials":{"code":"821","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Magnetized Materials"},"usps_hazardous_materials_division_4_1_mailable_flammable_solids_and_safety_matches":{"code":"822","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Division 4 1 Mailable Flammable Solids And Safety Matches"},"usps_hazardous_materials_division_5_1_oxidizers":{"code":"823","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Division 5 1 Oxidizers"},"usps_hazardous_materials_division_5_2_organic_peroxides":{"code":"824","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Division 5 2 Organic Peroxides"},"usps_hazardous_materials_division_6_1_toxic_materials":{"code":"825","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Division 6 1 Toxic Materials"},"usps_hazardous_materials_division_6_2_biological_materials":{"code":"826","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Division 6 2 Biological Materials"},"usps_hazardous_materials_excepted_quantity_provision":{"code":"827","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Excepted Quantity Provision"},"usps_hazardous_materials_ground_only_hazardous_materials":{"code":"828","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Ground Only Hazardous Materials"},"usps_hazardous_materials_air_eligible_id8000_consumer_commodity":{"code":"829","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Air Eligible Id8000 Consumer Commodity"},"usps_hazardous_materials_lighters":{"code":"830","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Lighters"},"usps_hazardous_materials_limited_quantity_ground":{"code":"831","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Limited Quantity Ground"},"usps_hazardous_materials_small_quantity_provision_markings_required":{"code":"832","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Small Quantity Provision Markings Required"},"usps_hazardous_materials":{"code":"857","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials"},"usps_certified_mail":{"code":"910","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Certified Mail"},"usps_certified_mail_restricted_delivery":{"code":"911","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Certified Mail Restricted Delivery"},"usps_certified_mail_adult_signature_required":{"code":"912","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Certified Mail Adult Signature Required"},"usps_certified_mail_adult_signature_restricted_delivery":{"code":"913","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Certified Mail Adult Signature Restricted Delivery"},"usps_collect_on_delivery":{"code":"915","meta":{"category":"COD","configurable":true},"type":"float","label":"USPS Collect On Delivery"},"usps_collect_on_delivery_restricted_delivery":{"code":"917","meta":{"category":"COD","configurable":true},"type":"boolean","label":"USPS Collect On Delivery Restricted Delivery"},"usps_tracking_electronic":{"code":"920","meta":{"configurable":true},"type":"boolean","label":"USPS Tracking Electronic"},"usps_signature_confirmation":{"code":"921","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Signature Confirmation"},"usps_adult_signature_required":{"code":"922","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Adult Signature Required"},"usps_adult_signature_restricted_delivery":{"code":"923","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Adult Signature Restricted Delivery"},"usps_signature_confirmation_restricted_delivery":{"code":"924","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Signature Confirmation Restricted Delivery"},"usps_priority_mail_express_merchandise_insurance":{"code":"925","meta":{"category":"INSURANCE","configurable":true},"type":"boolean","label":"USPS Priority Mail Express Merchandise Insurance"},"usps_insurance_below_500":{"code":"930","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"USPS Insurance Below 500"},"usps_insurance_above_500":{"code":"931","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"USPS Insurance Above 500"},"usps_insurance_restricted_delivery":{"code":"934","meta":{"category":"INSURANCE","configurable":true},"type":"boolean","label":"USPS Insurance Restricted Delivery"},"usps_registered_mail":{"code":"940","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Registered Mail"},"usps_registered_mail_restricted_delivery":{"code":"941","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Registered Mail Restricted Delivery"},"usps_return_receipt":{"code":"955","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"USPS Return Receipt"},"usps_return_receipt_electronic":{"code":"957","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"USPS Return Receipt Electronic"},"usps_signature_requested_priority_mail_express_only":{"code":"981","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"USPS Signature Requested Priority Mail Express Only"},"usps_parcel_locker_delivery":{"code":"984","meta":{"category":"LOCKER","configurable":true},"type":"boolean","label":"USPS Parcel Locker Delivery"},"usps_po_to_addressee_priority_mail_express_only":{"code":"986","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"USPS Po To Addressee Priority Mail Express Only"},"usps_sunday_delivery":{"code":"981","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"USPS Sunday Delivery"},"usps_mail_class":{"code":"mailClass","enum":["usps_parcel_select_lightweight","usps_parcel_select","usps_priority_mail_express","usps_priority_mail","usps_library_mail","usps_media_mail","usps_bound_printed_matter","usps_connect_local","usps_connect_mail","usps_connect_next_day","usps_connect_regional","usps_connect_same_day","usps_ground_advantage","usps_domestic_matter_for_the_blind","usps_all"],"meta":{"configurable":true},"type":"string","label":"Mail Class"},"usps_facility_id":{"code":"facilityId","meta":{"configurable":true},"type":"string","label":"Facility Id"},"usps_machinable_piece":{"code":"machinable","meta":{"configurable":true},"type":"boolean","label":"Machinable Piece"},"usps_hold_for_pickup":{"code":"holdForPickup","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Hold For Pickup"},"usps_processing_category":{"code":"processingCategory","meta":{"configurable":true},"type":"string","label":"Processing Category"},"usps_carrier_release":{"code":"carrierRelease","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Carrier Release"},"usps_physical_signature_required":{"code":"physicalSignatureRequired","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Physical Signature Required"},"usps_price_type":{"code":"priceType","enum":["RETAIL","COMMERCIAL","CONTRACT"],"meta":{"configurable":true},"type":"string","label":"Price Type"},"usps_destination_entry_facility_type":{"code":"destinationEntryFacilityType","enum":["NONE","DESTINATION_NETWORK_DISTRIBUTION_CENTER","DESTINATION_SECTIONAL_CENTER_FACILITY","DESTINATION_DELIVERY_UNIT","DESTINATION_SERVICE_HUB"],"meta":{"configurable":true},"type":"string","label":"Destination Entry Facility Type"},"usps_extra_services":{"code":"extraServices","meta":{"configurable":true},"type":"list","label":"Extra Services"},"usps_shipping_filter":{"code":"shippingFilter","enum":["PRICE","SERVICE_STANDARDS"],"meta":{"configurable":true},"type":"string","label":"Shipping Filter"}},"readme":null},{"id":"usps_international","carrier_name":"usps_international","display_name":"USPS International","integration_status":"production-ready","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Manifest","Rating","Pickup","Shipping","Tracking"],"connection_fields":{"client_id":{"name":"client_id","required":true,"sensitive":false,"type":"string","label":"Client ID"},"client_secret":{"name":"client_secret","required":true,"sensitive":false,"type":"string","label":"Client Secret"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_type":{"default":"EPS","enum":["EPS","PERMIT","METER"],"name":"account_type","required":false,"sensitive":false,"type":"string","label":"Account Type"},"manifest_MID":{"name":"manifest_MID","required":false,"sensitive":false,"type":"string","label":"Manifest Mid"},"CRID":{"name":"CRID","required":false,"sensitive":false,"type":"string","label":"Crid"},"MID":{"name":"MID","required":false,"sensitive":false,"type":"string","label":"Mid"},"account_country_code":{"default":"US","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"permit_ZIP":{"code":"permit_ZIP","name":"permit_ZIP","required":false,"type":"string","label":"Permit Zip"},"permit_number":{"code":"permit_number","name":"permit_number","required":false,"type":"string","label":"Permit Number"},"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"price_type":{"code":"price_type","enum":["RETAIL","COMMERCIAL","COMMERCIAL_BASE","COMMERCIAL_PLUS","CONTRACT"],"name":"price_type","required":false,"type":"string","label":"Price Type"}},"shipping_services":{"usps_first_class_package_international_service":"FIRST-CLASS_PACKAGE_INTERNATIONAL_SERVICE","usps_priority_mail_international":"PRIORITY_MAIL_INTERNATIONAL","usps_priority_mail_express_international":"PRIORITY_MAIL_EXPRESS_INTERNATIONAL","usps_global_express_guaranteed":"GLOBAL_EXPRESS_GUARANTEED","usps_all":"ALL"},"shipping_options":{"usps_hazardous_materials_class_7_radioactive_materials":{"code":"813","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 7 Radioactive Materials"},"usps_hazardous_materials_class_9_unmarked_lithium_batteries":{"code":"820","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Class 9 Unmarked Lithium Batteries"},"usps_hazardous_materials_division_6_2_biological_materials":{"code":"826","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials Division 6 2 Biological Materials"},"usps_hazardous_materials":{"code":"857","meta":{"category":"DANGEROUS_GOOD","configurable":true},"type":"boolean","label":"USPS Hazardous Materials"},"usps_insurance_below_500":{"code":"930","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"USPS Insurance Below 500"},"usps_insurance_above_500":{"code":"931","meta":{"category":"INSURANCE","configurable":true},"type":"float","label":"USPS Insurance Above 500"},"usps_return_receipt":{"code":"955","meta":{"category":"RETURN","configurable":true},"type":"boolean","label":"USPS Return Receipt"},"usps_mail_class":{"code":"mailClass","enum":["usps_first_class_package_international_service","usps_priority_mail_international","usps_priority_mail_express_international","usps_global_express_guaranteed","usps_all"],"meta":{"configurable":true},"type":"string","label":"Usps Mail Class"},"usps_facility_id":{"code":"facilityId","meta":{"configurable":true},"type":"string","label":"Usps Facility Id"},"usps_machinable_piece":{"code":"machinable","meta":{"configurable":true},"type":"boolean","label":"Usps Machinable Piece"},"usps_price_type":{"code":"priceType","enum":["RETAIL","COMMERCIAL","COMMERCIAL_BASE","COMMERCIAL_PLUS","CONTRACT"],"meta":{"configurable":true},"type":"string","label":"Usps Price Type"},"usps_hold_for_pickup":{"code":"holdForPickup","meta":{"category":"PUDO","configurable":true},"type":"boolean","label":"Usps Hold For Pickup"},"usps_carrier_release":{"code":"carrierRelease","meta":{"category":"DELIVERY_OPTIONS","configurable":true},"type":"boolean","label":"Usps Carrier Release"},"usps_processing_category":{"code":"processingCategory","meta":{"configurable":true},"type":"string","label":"Usps Processing Category"},"usps_rate_indicator":{"code":"rateIndicator","enum":["E4","E6","FA","FB","FE","FP","FS","PA","PL","SP","EP","HA","HB","HE","HL","HP","HS","LE"],"meta":{"configurable":true},"type":"string","label":"Usps Rate Indicator"},"usps_physical_signature_required":{"code":"physicalSignatureRequired","meta":{"category":"SIGNATURE","configurable":true},"type":"boolean","label":"Usps Physical Signature Required"},"usps_extra_services":{"code":"extraServices","meta":{"configurable":true},"type":"list","label":"Usps Extra Services"},"usps_shipping_filter":{"code":"shippingFilter","enum":["PRICE"],"meta":{"configurable":true},"type":"string","label":"Usps Shipping Filter"}},"readme":null},{"id":"veho","carrier_name":"veho","display_name":"Veho","integration_status":"beta","website":"","description":"Veho shipping integration for Karrio","documentation":"","is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_number":{"name":"account_number","required":false,"sensitive":false,"type":"string","label":"Account Number"},"account_country_code":{"name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{"shipping_options":{"code":"shipping_options","name":"shipping_options","required":false,"type":"list","label":"Shipping Options"},"shipping_services":{"code":"shipping_services","name":"shipping_services","required":false,"type":"list","label":"Shipping Services"},"label_type":{"code":"label_type","name":"label_type","required":false,"type":"string","label":"Label Type"},"delivery_max_datetime":{"code":"delivery_max_datetime","name":"delivery_max_datetime","required":false,"type":"string","label":"Delivery Max Datetime"},"label_date":{"code":"label_date","name":"label_date","required":false,"type":"string","label":"Label Date"}},"shipping_services":{},"shipping_options":{},"readme":null},{"id":"zoom2u","carrier_name":"zoom2u","display_name":"Zoom2u","integration_status":"beta","website":null,"description":"","documentation":null,"is_enabled":true,"capabilities":["Tracking","Rating","Shipping"],"connection_fields":{"api_key":{"name":"api_key","required":true,"sensitive":false,"type":"string","label":"API Key"},"account_country_code":{"default":"AU","name":"account_country_code","required":false,"sensitive":false,"type":"string","label":"Account Country Code"},"metadata":{"default":{},"name":"metadata","required":false,"sensitive":false,"type":"object","label":"Metadata"},"config":{"default":{},"name":"config","required":false,"sensitive":false,"type":"object","label":"Config"}},"config_fields":{},"shipping_services":{"zoom2u_VIP":"VIP","zoom2u_3_hour":"3 hour","zoom2u_same_day":"Same day"},"shipping_options":{"purchase_order_number":{"code":"purchase_order_number","meta":{"configurable":true},"type":"string","label":"Purchase Order Number"},"ready_datetime":{"code":"ready_datetime","meta":{"configurable":true},"type":"string","label":"Ready Datetime"},"vehicle_type":{"code":"vehicle_type","meta":{"configurable":true},"type":"string","label":"Vehicle Type"},"pickup_notes":{"code":"pickup_notes","meta":{"configurable":true},"type":"string","label":"Pickup Notes"},"dropoff_notes":{"code":"dropoff_notes","meta":{"configurable":true},"type":"string","label":"Dropoff Notes"}},"readme":null}] \ No newline at end of file diff --git a/bin/regenerate-rate-sheets b/bin/regenerate-rate-sheets new file mode 100755 index 0000000000..d777cb4e92 --- /dev/null +++ b/bin/regenerate-rate-sheets @@ -0,0 +1,398 @@ +#!/usr/bin/env python3 +"""Regenerate per-carrier rate sheet CSVs from the master JTL xlsx workbook. + +Output schema: legacy v2.0 flat format — 42 columns, one row per +(service_name × zone × weight_range). Variants (e.g. "DHL Paket GoGreen +Plus") are distinct rows with their own service_name and may share a +carrier-API service_code with their base service. + +Column mapping xlsx → CSV: + service_name → service_name (verbatim, variant label) + (derived pattern match) → service_code (canonical carrier code) + plan_margin__eur → plan_cost_ (AMOUNT, EUR) + plan_rate_ → (not emitted) (xlsx absolute customer price is derived at quote time) + notes → notes (per-row weight bucket) + fuel/energy/... surcharges → (same names) (visible surcharges) + +Columns explicitly NOT emitted (not in legacy v2.0 schema): + shipping_method, option_*, age_check, gogreen_plus, personal_handover, + qr_code, visual_age_check, id_check, no_neighbor, multicollo, insurance, + saturday, neighbor_delivery. + +Usage: + karrio/bin/regenerate-rate-sheets \\ + --xlsx ~/Downloads/JTL_Shipping_Master_FinalV4.xlsx \\ + --out ~/Downloads/rate-sheets \\ + [--wipe] +""" + +from __future__ import annotations + +import argparse +import csv +import re +import sys +from pathlib import Path + +import openpyxl + +# --------------------------------------------------------------------------- +# CSV output schema — legacy v2.0 (42 columns, ordered) +# --------------------------------------------------------------------------- +HEADERS = [ + "carrier_name", + "service_code", + "carrier_service_code", + "service_name", + "shipment_type", + "origin_country", + "zone_label", + "country_codes", + "min_weight", + "max_weight", + "weight_unit", + "max_length", + "max_width", + "max_height", + "dimension_unit", + "currency", + "base_rate", + "cost", + "transit_days", + "transit_time", + "plan_rate_start", + "plan_cost_start", + "plan_rate_advanced", + "plan_cost_advanced", + "plan_rate_pro", + "plan_cost_pro", + "plan_rate_enterprise", + "plan_cost_enterprise", + "tracked", + "b2c", + "b2b", + "first_mile", + "last_mile", + "form_factor", + "signature", + "insurance", + "multicollo", + "neighbor_delivery", + "saturday_delivery", + "age_check", + "fuel_surcharge", + "seasonal_surcharge", + "customs_surcharge", + "energy_surcharge", + "road_toll", + "security_surcharge", + "notes", +] + +PLAN_SLUGS = ("start", "advanced", "pro", "enterprise") + +# --------------------------------------------------------------------------- +# Carrier-specific mappings +# --------------------------------------------------------------------------- +# xlsx sheet name → karrio carrier code. Extend as carriers are added. +SHEET_TO_CARRIER = { + "DHL-DE": "dhl_parcel_de", + "DPD-DE": "dpd_meta", + "UPS-DE": "ups", + "UPS-NL": "ups", + "UPS-AT": "ups", + "UPS-PT": "ups", + "UPS-IT": "ups", + "UPS-FR": "ups", + "UPS-ES": "ups", + "UPS-CZ": "ups", + "UPS-CH": "ups", + "SpringGDS-DE": "spring", + "ParcelOne": "parcelone", + "BRT Italy": "dpd_meta", + "Landmark Global": "landmark", + "Asendia": "asendia", + "Chronopost": "chronopost", + "GLS-DE": "gls", + "PICK-UPS": "ups", +} + +# Pattern-match xlsx `service_name` → canonical carrier service_code. +# Multiple service_name variants map to the same service_code (the carrier +# API only knows a few endpoint codes; variants differ by merchant label). +SERVICE_CODE_RULES: dict[str, list[tuple[re.Pattern, str, str]]] = { + "dhl_parcel_de": [ + # (pattern, service_code, carrier_service_code) + (re.compile(r"retoure", re.I), "dhl_parcel_de_retoure", ""), + (re.compile(r"kleinpaket", re.I), "dhl_parcel_de_kleinpaket", "V62KP"), + (re.compile(r"paket", re.I), "dhl_parcel_de_paket", "V01PAK"), + ], + # Best-effort defaults for other carriers — slug-normalize the service name + # to produce a stable code when no explicit mapping exists. +} + + +def _slugify(s: str) -> str: + return re.sub(r"[^a-z0-9]+", "_", (s or "").strip().lower()).strip("_") + + +def resolve_service_code(carrier: str, service_name: str) -> tuple[str, str]: + """Return (service_code, carrier_service_code) for the given variant. + For known carriers the pattern rules map variants to canonical codes. + For unknown carriers, slugify the service name.""" + rules = SERVICE_CODE_RULES.get(carrier, []) + for pattern, code, carrier_code in rules: + if pattern.search(service_name or ""): + return code, carrier_code + return f"{carrier}_{_slugify(service_name)}", "" + + +# --------------------------------------------------------------------------- +# Value helpers +# --------------------------------------------------------------------------- +def _str(v) -> str: + if v is None: + return "" + return str(v).strip() + + +def _num(v) -> str: + """Emit numeric cells as strings, stripping trailing zeros where safe.""" + if v is None or v == "": + return "" + if isinstance(v, bool): + return "TRUE" if v else "FALSE" + try: + f = float(v) + if f == int(f): + return str(int(f)) + return f"{f:g}" + except (ValueError, TypeError): + return _str(v) + + +def _bool(v) -> str: + if v is None or v == "": + return "" + if isinstance(v, bool): + return "TRUE" if v else "FALSE" + s = str(v).strip().upper() + if s in ("TRUE", "1", "YES"): + return "TRUE" + if s in ("FALSE", "0", "NO"): + return "FALSE" + return s + + +# --------------------------------------------------------------------------- +# Row builder +# --------------------------------------------------------------------------- +def build_row(carrier: str, xlsx_row: dict) -> dict | None: + """Map one xlsx row to one CSV row (legacy v2.0 schema).""" + service_name = _str(xlsx_row.get("service_name")) + if not service_name: + return None + + # Some xlsx sheets (Chronopost) include zero-width breakpoint rows where + # min_weight == max_weight (duplicates of the preceding bracket). The + # importer rejects these with "min_weight must be less than max_weight". + # Skip them at regen. + try: + mn, mx = float(xlsx_row.get("min_weight") or 0), float(xlsx_row.get("max_weight") or 0) + if mn and mx and mn >= mx: + return None + except (ValueError, TypeError): + pass + + # Disambiguate variants that share a service_name but differ by age_check + # (e.g. "DHL Paket GoGreen Plus - Age Verification" 16+ vs 18+). The xlsx + # expresses this ambiguity via the age_check column — append the suffix + # so each variant lands as a distinct ServiceLevel. + age = _str(xlsx_row.get("age_check")) + if age and age not in ("", "0", "FALSE", "False", "false") and age not in service_name: + service_name = f"{service_name} {age}+" + + # Disambiguate size-tier variants (XS/S/M/L/XL) that share service_name, + # weight range, and zone but differ only by `notes` (e.g. DPD Shop2Shop + # Domestic XS vs S vs M vs L). The notes column carries the tier token — + # lift it into the service_name so each tier lands as a distinct + # ServiceLevel with its own rate card. + service_code, carrier_service_code = resolve_service_code(carrier, service_name) + + # plan_cost_ = plan_margin__eur (AMOUNT). plan_rate is left + # blank — if the xlsx has plan_margin__pct the user can opt in via + # UI. Importer's behavior when both are set is documented: + # plan_cost wins. + plan_cells = {} + for slug in PLAN_SLUGS: + margin_eur = xlsx_row.get(f"plan_margin_{slug}_eur") + if margin_eur not in (None, ""): + plan_cells[f"plan_cost_{slug}"] = _num(margin_eur) + plan_cells.setdefault(f"plan_rate_{slug}", "") + plan_cells.setdefault(f"plan_cost_{slug}", "") + + return { + "carrier_name": carrier, + "service_code": service_code, + "carrier_service_code": carrier_service_code, + "service_name": service_name, + "shipment_type": _str(xlsx_row.get("shipment_type")) or "outbound", + "origin_country": (_str(xlsx_row.get("origin_country")) or "").upper(), + "zone_label": _str(xlsx_row.get("zone_label")), + "country_codes": _str(xlsx_row.get("country_codes")), + "min_weight": _num(xlsx_row.get("min_weight")), + "max_weight": _num(xlsx_row.get("max_weight")), + "weight_unit": _str(xlsx_row.get("weight_unit")) or "KG", + "max_length": _num(xlsx_row.get("max_length")), + "max_width": _num(xlsx_row.get("max_width")), + "max_height": _num(xlsx_row.get("max_height")), + "dimension_unit": _str(xlsx_row.get("dimension_unit")) or "CM", + "currency": _str(xlsx_row.get("currency")) or "EUR", + "base_rate": _num(xlsx_row.get("base_rate")), + "cost": _num(xlsx_row.get("cost")), + "transit_days": _num(xlsx_row.get("transit_days")), + "transit_time": _str(xlsx_row.get("transit_time")) or "best_effort", + **plan_cells, + "tracked": _bool(xlsx_row.get("tracked")), + "b2c": _bool(xlsx_row.get("b2c")), + "b2b": _bool(xlsx_row.get("b2b")), + "first_mile": _str(xlsx_row.get("first_mile")), + "last_mile": _str(xlsx_row.get("last_mile")), + "form_factor": _str(xlsx_row.get("form_factor")), + "signature": _bool(xlsx_row.get("signature")), + "insurance": _bool(xlsx_row.get("insurance")), + "multicollo": _bool(xlsx_row.get("multicollo")), + "neighbor_delivery": _bool(xlsx_row.get("neighbor_delivery")), + "saturday_delivery": _bool(xlsx_row.get("saturday") or xlsx_row.get("saturday_delivery")), + "age_check": _str(xlsx_row.get("age_check")), + "fuel_surcharge": _num(xlsx_row.get("fuel_surcharge")), + "seasonal_surcharge": _num(xlsx_row.get("seasonal_surcharge")), + "customs_surcharge": _num(xlsx_row.get("customs_surcharge")), + "energy_surcharge": _num(xlsx_row.get("energy_surcharge")), + "road_toll": _num(xlsx_row.get("road_toll")), + "security_surcharge": _num(xlsx_row.get("security_surcharge")), + "notes": _str(xlsx_row.get("notes")), + } + + +# --------------------------------------------------------------------------- +# Sheet driver +# --------------------------------------------------------------------------- +def emit_sheet(ws, carrier: str, out_dir: Path) -> tuple[int, Path]: + headers = [cell.value for cell in ws[1]] + + def row_to_dict(row): + return {headers[i]: row[i] for i in range(len(headers)) if i < len(headers)} + + out_rows = [] + for row in ws.iter_rows(min_row=2, values_only=True): + if all(v is None for v in row): + continue + xlsx_row = row_to_dict(row) + built = build_row(carrier, xlsx_row) + if built: + built["_xlsx_notes"] = _str(xlsx_row.get("notes")) + out_rows.append(built) + + # Resolve collisions on (service_name, zone_label, min_weight, max_weight). + # When the xlsx carries genuinely distinct rate cards under the same + # service_name/zone/weight (size tiers, product sub-variants only labeled + # in `notes`), append a disambiguator derived from the notes suffix so + # each lands as its own ServiceLevel. + from collections import defaultdict + groups: dict = defaultdict(list) + for r in out_rows: + key = (r["service_name"], r["zone_label"], r["min_weight"], r["max_weight"]) + groups[key].append(r) + def _notes_token(r): + """Extract the distinguishing noun from the xlsx notes column. + Strips the weight-range suffix and any service-name echo.""" + raw = r.get("_xlsx_notes", "") or "" + tok = re.sub(r"\s*\d+\s*-\s*\d+\s*kg.*$", "", raw, flags=re.I).strip() + if r["service_name"] and tok.lower().startswith(r["service_name"].lower().split(" - ")[0]): + tok = "" # pure echo of service_name prefix → not informative + return re.sub(r"\s+", " ", tok).strip() + + def _compact_origin(o: str) -> str: + """ZONE 601 → Z601; everything else passes through.""" + m = re.fullmatch(r"\s*ZONE\s+(\S+)\s*", o or "", flags=re.I) + return f"Z{m.group(1)}" if m else (o or "") + + for key, rows in groups.items(): + if len(rows) <= 1: + continue + # Determine which dimensions actually distinguish rows in this group. + origins = {r["origin_country"] for r in rows} + notes_tokens = {_notes_token(r) for r in rows} + use_origin = len(origins) > 1 + use_notes = len(notes_tokens) > 1 + + for idx, r in enumerate(rows, start=1): + parts = [] + if use_notes: + nt = _notes_token(r) + if nt: + parts.append(nt) + if use_origin and r["origin_country"]: + parts.append(_compact_origin(r["origin_country"])) + suffix = " ".join(parts) or f"v{idx}" + if not re.search(rf"\b{re.escape(suffix)}\b", r["service_name"], flags=re.I): + r["service_name"] = f"{r['service_name']} {suffix}" + + # Second pass: if disambiguation produced further collisions (e.g. same + # notes AND same origin across multiple rows), append a numeric suffix + # as last resort. + groups2: dict = defaultdict(list) + for r in out_rows: + key = (r["service_name"], r["zone_label"], r["min_weight"], r["max_weight"]) + groups2[key].append(r) + for rows in groups2.values(): + if len(rows) <= 1: + continue + for idx, r in enumerate(rows, start=1): + r["service_name"] = f"{r['service_name']} v{idx}" + for r in out_rows: + r.pop("_xlsx_notes", None) + + out_path = out_dir / f"{ws.title}.csv" + with out_path.open("w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=HEADERS, extrasaction="ignore") + writer.writeheader() + writer.writerows(out_rows) + return len(out_rows), out_path + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--xlsx", required=True, type=Path, help="Path to master xlsx workbook.") + parser.add_argument("--out", required=True, type=Path, help="Output directory for per-carrier CSVs.") + parser.add_argument("--wipe", action="store_true", help="Delete existing *.csv in the output directory first.") + args = parser.parse_args() + + if not args.xlsx.exists(): + print(f"ERROR: xlsx not found: {args.xlsx}", file=sys.stderr) + return 1 + + args.out.mkdir(parents=True, exist_ok=True) + if args.wipe: + for p in args.out.glob("*.csv"): + p.unlink() + + wb = openpyxl.load_workbook(args.xlsx, data_only=True) + total_rows = 0 + for sheet_name in wb.sheetnames: + carrier = SHEET_TO_CARRIER.get(sheet_name) + if not carrier: + print(f"SKIP: unmapped sheet '{sheet_name}' — add to SHEET_TO_CARRIER", file=sys.stderr) + continue + ws = wb[sheet_name] + n, path = emit_sheet(ws, carrier, args.out) + total_rows += n + print(f" {sheet_name:<20} → {path.name:<30} {n:>5} rows") + + print(f"\nTotal rows written: {total_rows}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/modules/admin/karrio/server/admin/admin.py b/modules/admin/karrio/server/admin/admin.py index 8c38f3f3da..846f6b4061 100644 --- a/modules/admin/karrio/server/admin/admin.py +++ b/modules/admin/karrio/server/admin/admin.py @@ -1,3 +1 @@ -from django.contrib import admin - # Register your models here. diff --git a/modules/admin/karrio/server/admin/forms.py b/modules/admin/karrio/server/admin/forms.py index 498f5b9fa6..89b0b07639 100644 --- a/modules/admin/karrio/server/admin/forms.py +++ b/modules/admin/karrio/server/admin/forms.py @@ -1,5 +1,5 @@ -import django.forms as forms import django.db.transaction as transaction +import django.forms as forms import karrio.server.user.forms as user_forms @@ -22,7 +22,7 @@ def save(self, commit=True): for k, v in statuses.items(): setattr(user, k, v) - if any(statuses.keys()): + if any(statuses): user.save() user.save() diff --git a/modules/admin/karrio/server/admin/migrations/0001_initial.py b/modules/admin/karrio/server/admin/migrations/0001_initial.py index 4fcd5d6dd3..b2ef2472ff 100644 --- a/modules/admin/karrio/server/admin/migrations/0001_initial.py +++ b/modules/admin/karrio/server/admin/migrations/0001_initial.py @@ -2,7 +2,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [] diff --git a/modules/admin/karrio/server/admin/migrations/0002_rename_admin_task_e_queued__b1c3e3_idx_admin_task__queued__be444e_idx_and_more.py b/modules/admin/karrio/server/admin/migrations/0002_rename_admin_task_e_queued__b1c3e3_idx_admin_task__queued__be444e_idx_and_more.py index 9647556920..15a57c4840 100644 --- a/modules/admin/karrio/server/admin/migrations/0002_rename_admin_task_e_queued__b1c3e3_idx_admin_task__queued__be444e_idx_and_more.py +++ b/modules/admin/karrio/server/admin/migrations/0002_rename_admin_task_e_queued__b1c3e3_idx_admin_task__queued__be444e_idx_and_more.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("karrio_admin", "0001_initial"), ] diff --git a/modules/admin/karrio/server/admin/migrations/0003_deduplicate_and_unique_task_id.py b/modules/admin/karrio/server/admin/migrations/0003_deduplicate_and_unique_task_id.py index 96defe94e9..789739c1b0 100644 --- a/modules/admin/karrio/server/admin/migrations/0003_deduplicate_and_unique_task_id.py +++ b/modules/admin/karrio/server/admin/migrations/0003_deduplicate_and_unique_task_id.py @@ -10,11 +10,7 @@ def deduplicate_and_fix_stale(apps, schema_editor): from django.db.models import Count # 1. Deduplicate: find task_ids with multiple records - dupes = ( - TaskExecution.objects.values("task_id") - .annotate(cnt=Count("id")) - .filter(cnt__gt=1) - ) + dupes = TaskExecution.objects.values("task_id").annotate(cnt=Count("id")).filter(cnt__gt=1) for entry in dupes: task_id = entry["task_id"] @@ -33,7 +29,6 @@ def deduplicate_and_fix_stale(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ( "karrio_admin", diff --git a/modules/admin/karrio/server/admin/models.py b/modules/admin/karrio/server/admin/models.py index 41afc9ee54..45ebfd3e9d 100644 --- a/modules/admin/karrio/server/admin/models.py +++ b/modules/admin/karrio/server/admin/models.py @@ -1,3 +1 @@ -from django.db import models - from karrio.server.admin.worker.models import * # noqa diff --git a/modules/admin/karrio/server/admin/schema.py b/modules/admin/karrio/server/admin/schema.py index 95438ab621..c30774b1e4 100644 --- a/modules/admin/karrio/server/admin/schema.py +++ b/modules/admin/karrio/server/admin/schema.py @@ -1,10 +1,10 @@ import pkgutil -import strawberry -import strawberry.schema.config as config -from strawberry.types import Info import karrio.server.admin.schemas as schemas +import strawberry +import strawberry.schema.config as config from karrio.server.core.logging import logger +from strawberry.types import Info QUERIES: list = [] MUTATIONS: list = [] @@ -24,6 +24,14 @@ logger.warning("Failed to register schema", schema_name=name, error=str(e), exc_info=True) +def _most_derived(classes: list) -> list: + return [c for c in classes if not any(o is not c and issubclass(o, c) for o in classes)] + + +QUERIES = _most_derived(QUERIES) +MUTATIONS = _most_derived(MUTATIONS) + + # Fallback placeholder to prevent empty schema errors when no modules are installed if not QUERIES: diff --git a/modules/admin/karrio/server/admin/schemas/base/__init__.py b/modules/admin/karrio/server/admin/schemas/base/__init__.py index b7ef31aa11..5cc01e9fa3 100644 --- a/modules/admin/karrio/server/admin/schemas/base/__init__.py +++ b/modules/admin/karrio/server/admin/schemas/base/__init__.py @@ -1,14 +1,13 @@ -import typing -import strawberry -from strawberry.types import Info - -import karrio.server.iam.models as iam +import karrio.server.admin.schemas.base.inputs as inputs +import karrio.server.admin.schemas.base.mutations as mutations +import karrio.server.admin.schemas.base.types as types +import karrio.server.graph.schemas.base.types as base_types import karrio.server.graph.utils as utils +import karrio.server.iam.models as iam import karrio.server.pricing.models as pricing import karrio.server.providers.models as providers -import karrio.server.admin.schemas.base.types as types -import karrio.server.admin.schemas.base.inputs as inputs -import karrio.server.admin.schemas.base.mutations as mutations +import strawberry +from strawberry.types import Info extra_types = [] @@ -16,43 +15,25 @@ @strawberry.type class Query: me: types.SystemUserType = strawberry.field(resolver=types.SystemUserType.me) - user: typing.Optional[types.SystemUserType] = strawberry.field( - resolver=types.SystemUserType.resolve - ) - users: utils.Connection[types.SystemUserType] = strawberry.field( - resolver=types.SystemUserType.resolve_list - ) + user: types.SystemUserType | None = strawberry.field(resolver=types.SystemUserType.resolve) + users: utils.Connection[types.SystemUserType] = strawberry.field(resolver=types.SystemUserType.resolve_list) - configs: types.InstanceConfigType = strawberry.field( - resolver=types.InstanceConfigType.resolve - ) - usage: types.AdminSystemUsageType = strawberry.field( - resolver=types.AdminSystemUsageType.resolve - ) + configs: types.InstanceConfigType = strawberry.field(resolver=types.InstanceConfigType.resolve) + usage: types.AdminSystemUsageType = strawberry.field(resolver=types.AdminSystemUsageType.resolve) - markup: typing.Optional[types.MarkupType] = strawberry.field( - resolver=types.MarkupType.resolve - ) - markups: utils.Connection[types.MarkupType] = strawberry.field( - resolver=types.MarkupType.resolve_list - ) - fee: typing.Optional[types.FeeType] = strawberry.field( - resolver=types.FeeType.resolve - ) - fees: utils.Connection[types.FeeType] = strawberry.field( - resolver=types.FeeType.resolve_list - ) + markup: types.MarkupType | None = strawberry.field(resolver=types.MarkupType.resolve) + markups: utils.Connection[types.MarkupType] = strawberry.field(resolver=types.MarkupType.resolve_list) + fee: types.FeeType | None = strawberry.field(resolver=types.FeeType.resolve) + fees: utils.Connection[types.FeeType] = strawberry.field(resolver=types.FeeType.resolve_list) - system_carrier_connection: typing.Optional[types.SystemCarrierConnectionType] = ( - strawberry.field(resolver=types.SystemCarrierConnectionType.resolve) + system_carrier_connection: types.SystemCarrierConnectionType | None = strawberry.field( + resolver=types.SystemCarrierConnectionType.resolve ) - system_carrier_connections: utils.Connection[types.SystemCarrierConnectionType] = ( - strawberry.field(resolver=types.SystemCarrierConnectionType.resolve_list) + system_carrier_connections: utils.Connection[types.SystemCarrierConnectionType] = strawberry.field( + resolver=types.SystemCarrierConnectionType.resolve_list ) - rate_sheet: typing.Optional[types.SystemRateSheetType] = strawberry.field( - resolver=types.SystemRateSheetType.resolve - ) + rate_sheet: types.SystemRateSheetType | None = strawberry.field(resolver=types.SystemRateSheetType.resolve) rate_sheets: utils.Connection[types.SystemRateSheetType] = strawberry.field( resolver=types.SystemRateSheetType.resolve_list ) @@ -61,39 +42,27 @@ class Query: resolver=types.PermissionGroupType.resolve_list ) - task_executions: utils.Connection[types.TaskExecutionType] = strawberry.field( - resolver=types.TaskExecutionType.resolve_list - ) - worker_health: types.WorkerHealthType = strawberry.field( - resolver=types.WorkerHealthType.resolve - ) + config_fieldsets: list[types.ConfigFieldsetType] = strawberry.field(resolver=types.ConfigFieldsetType.resolve_list) + config_schema: list[types.ConfigSchemaItemType] = strawberry.field(resolver=types.ConfigSchemaItemType.resolve_list) - config_fieldsets: typing.List[types.ConfigFieldsetType] = strawberry.field( - resolver=types.ConfigFieldsetType.resolve_list - ) - config_schema: typing.List[types.ConfigSchemaItemType] = strawberry.field( - resolver=types.ConfigSchemaItemType.resolve_list + tag_definitions: list[base_types.TagDefinitionType] = strawberry.field( + resolver=base_types.TagDefinitionType.from_registry, + description="Service-level tag registry (drives admin rate-sheet editor + derived badges).", ) @strawberry.type class Mutation: @strawberry.mutation - def create_user( - self, info: Info, input: inputs.CreateUserMutationInput - ) -> mutations.CreateUserMutation: + def create_user(self, info: Info, input: inputs.CreateUserMutationInput) -> mutations.CreateUserMutation: return mutations.CreateUserMutation.mutate(info, **input.to_dict()) @strawberry.mutation - def update_user( - self, info: Info, input: inputs.UpdateUserMutationInput - ) -> mutations.UpdateUserMutation: + def update_user(self, info: Info, input: inputs.UpdateUserMutationInput) -> mutations.UpdateUserMutation: return mutations.UpdateUserMutation.mutate(info, **input.to_dict()) @strawberry.mutation - def remove_user( - self, info: Info, input: inputs.DeleteUserMutationInput - ) -> mutations.DeleteUserMutation: + def remove_user(self, info: Info, input: inputs.DeleteUserMutationInput) -> mutations.DeleteUserMutation: def validator(instance, **kwargs): if instance.id == info.context.request.user.id: raise Exception("You can't remove yourself") @@ -106,26 +75,20 @@ def validator(instance, **kwargs): ) @strawberry.mutation - def update_configs( - self, info: Info, input: inputs.InstanceConfigMutationInput - ) -> mutations.InstanceConfigMutation: + def update_configs(self, info: Info, input: inputs.InstanceConfigMutationInput) -> mutations.InstanceConfigMutation: return mutations.InstanceConfigMutation.mutate(info, **input.to_dict()) @strawberry.mutation def create_system_carrier_connection( self, info: Info, input: inputs.CreateConnectionMutationInput ) -> mutations.CreateSystemCarrierConnectionMutation: - return mutations.CreateSystemCarrierConnectionMutation.mutate( - info, **input.to_dict() - ) + return mutations.CreateSystemCarrierConnectionMutation.mutate(info, **input.to_dict()) @strawberry.mutation def update_system_carrier_connection( self, info: Info, input: inputs.UpdateConnectionMutationInput ) -> mutations.UpdateSystemCarrierConnectionMutation: - return mutations.UpdateSystemCarrierConnectionMutation.mutate( - info, **input.to_dict() - ) + return mutations.UpdateSystemCarrierConnectionMutation.mutate(info, **input.to_dict()) @strawberry.mutation def delete_system_carrier_connection( @@ -134,24 +97,16 @@ def delete_system_carrier_connection( return mutations.DeleteConnectionMutation.mutate(info, **input.to_dict()) @strawberry.mutation - def create_markup( - self, info: Info, input: inputs.CreateMarkupMutationInput - ) -> mutations.CreateMarkupMutation: + def create_markup(self, info: Info, input: inputs.CreateMarkupMutationInput) -> mutations.CreateMarkupMutation: return mutations.CreateMarkupMutation.mutate(info, **input.to_dict()) @strawberry.mutation - def update_markup( - self, info: Info, input: inputs.UpdateMarkupMutationInput - ) -> mutations.UpdateMarkupMutation: + def update_markup(self, info: Info, input: inputs.UpdateMarkupMutationInput) -> mutations.UpdateMarkupMutation: return mutations.UpdateMarkupMutation.mutate(info, **input.to_dict()) @strawberry.mutation - def delete_markup( - self, info: Info, input: inputs.base.DeleteMutationInput - ) -> mutations.base.DeleteMutation: - return mutations.base.DeleteMutation.mutate( - info, model=pricing.Markup, **input.to_dict() - ) + def delete_markup(self, info: Info, input: inputs.base.DeleteMutationInput) -> mutations.base.DeleteMutation: + return mutations.base.DeleteMutation.mutate(info, model=pricing.Markup, **input.to_dict()) @strawberry.mutation def create_rate_sheet( @@ -172,9 +127,7 @@ def delete_rate_sheet_service( return mutations.DeleteRateSheetServiceMutation.mutate(info, **input.to_dict()) @strawberry.mutation - def delete_rate_sheet( - self, info: Info, input: inputs.base.DeleteMutationInput - ) -> mutations.base.DeleteMutation: + def delete_rate_sheet(self, info: Info, input: inputs.base.DeleteMutationInput) -> mutations.base.DeleteMutation: id = input.to_dict().get("id") instance = providers.SystemRateSheet.objects.get(id=id) instance.delete(keep_parents=True) @@ -295,31 +248,9 @@ def trigger_tracker_update( return mutations.TriggerTrackerUpdateMutation.mutate(info, **input.to_dict()) @strawberry.mutation - def retry_webhook( - self, info: Info, input: inputs.RetryWebhookInput - ) -> mutations.RetryWebhookMutation: + def retry_webhook(self, info: Info, input: inputs.RetryWebhookInput) -> mutations.RetryWebhookMutation: return mutations.RetryWebhookMutation.mutate(info, **input.to_dict()) @strawberry.mutation - def revoke_task( - self, info: Info, input: inputs.RevokeTaskInput - ) -> mutations.RevokeTaskMutation: - return mutations.RevokeTaskMutation.mutate(info, **input.to_dict()) - - @strawberry.mutation - def cleanup_task_executions( - self, info: Info, input: inputs.CleanupTaskExecutionsInput - ) -> mutations.CleanupTaskExecutionsMutation: - return mutations.CleanupTaskExecutionsMutation.mutate(info, **input.to_dict()) - - @strawberry.mutation - def reset_stuck_tasks( - self, info: Info, input: inputs.ResetStuckTasksInput - ) -> mutations.ResetStuckTasksMutation: - return mutations.ResetStuckTasksMutation.mutate(info, **input.to_dict()) - - @strawberry.mutation - def trigger_data_archiving( - self, info: Info - ) -> mutations.TriggerDataArchivingMutation: + def trigger_data_archiving(self, info: Info) -> mutations.TriggerDataArchivingMutation: return mutations.TriggerDataArchivingMutation.mutate(info) diff --git a/modules/admin/karrio/server/admin/schemas/base/inputs.py b/modules/admin/karrio/server/admin/schemas/base/inputs.py index 0836714e21..c0b484f76c 100644 --- a/modules/admin/karrio/server/admin/schemas/base/inputs.py +++ b/modules/admin/karrio/server/admin/schemas/base/inputs.py @@ -1,54 +1,45 @@ -import typing -import strawberry - -import karrio.server.graph.utils as utils import karrio.server.admin.utils as admin import karrio.server.graph.schemas.base.inputs as base +import karrio.server.graph.utils as utils +import strawberry @strawberry.input class UserFilter(utils.Paginated): - id: typing.Optional[str] = strawberry.UNSET - email: typing.Optional[str] = strawberry.UNSET - is_staff: typing.Optional[bool] = strawberry.UNSET - is_active: typing.Optional[bool] = strawberry.UNSET - is_superuser: typing.Optional[bool] = strawberry.UNSET - order_by: typing.Optional[str] = strawberry.UNSET + id: str | None = strawberry.UNSET + email: str | None = strawberry.UNSET + is_staff: bool | None = strawberry.UNSET + is_active: bool | None = strawberry.UNSET + is_superuser: bool | None = strawberry.UNSET + order_by: str | None = strawberry.UNSET @strawberry.input class MarkupFilter(utils.Paginated): """Filter for Markup model queries.""" - id: typing.Optional[str] = strawberry.UNSET - name: typing.Optional[str] = strawberry.UNSET - active: typing.Optional[bool] = strawberry.UNSET - markup_type: typing.Optional[admin.MarkupTypeEnum] = strawberry.UNSET - account_id: typing.Optional[str] = strawberry.UNSET - meta_key: typing.Optional[str] = strawberry.UNSET - meta_value: typing.Optional[str] = strawberry.UNSET - metadata_key: typing.Optional[str] = strawberry.UNSET - metadata_value: typing.Optional[str] = strawberry.UNSET + + id: str | None = strawberry.UNSET + name: str | None = strawberry.UNSET + active: bool | None = strawberry.UNSET + markup_type: admin.MarkupTypeEnum | None = strawberry.UNSET + account_id: str | None = strawberry.UNSET + meta_key: str | None = strawberry.UNSET + meta_value: str | None = strawberry.UNSET + metadata_key: str | None = strawberry.UNSET + metadata_value: str | None = strawberry.UNSET @strawberry.input class FeeFilter(utils.Paginated): """Filter for Fee model queries.""" - id: typing.Optional[str] = strawberry.UNSET - markup_id: typing.Optional[str] = strawberry.UNSET - shipment_id: typing.Optional[str] = strawberry.UNSET - account_id: typing.Optional[str] = strawberry.UNSET - carrier_code: typing.Optional[str] = strawberry.UNSET - date_after: typing.Optional[str] = strawberry.UNSET - date_before: typing.Optional[str] = strawberry.UNSET - -@strawberry.input -class TaskExecutionFilter(utils.Paginated): - """Filter for task execution records.""" - status: typing.Optional[str] = strawberry.UNSET - task_name: typing.Optional[str] = strawberry.UNSET - date_after: typing.Optional[str] = strawberry.UNSET - date_before: typing.Optional[str] = strawberry.UNSET + id: str | None = strawberry.UNSET + markup_id: str | None = strawberry.UNSET + shipment_id: str | None = strawberry.UNSET + account_id: str | None = strawberry.UNSET + carrier_code: str | None = strawberry.UNSET + date_after: str | None = strawberry.UNSET + date_before: str | None = strawberry.UNSET @strawberry.input @@ -56,24 +47,24 @@ class CreateUserMutationInput(utils.BaseInput): email: str password1: str password2: str - redirect_url: typing.Optional[str] = strawberry.UNSET - full_name: typing.Optional[str] = strawberry.UNSET - is_staff: typing.Optional[bool] = strawberry.UNSET - is_active: typing.Optional[bool] = strawberry.UNSET - is_superuser: typing.Optional[bool] = strawberry.UNSET - organization_id: typing.Optional[str] = strawberry.UNSET - permissions: typing.Optional[typing.List[str]] = strawberry.UNSET + redirect_url: str | None = strawberry.UNSET + full_name: str | None = strawberry.UNSET + is_staff: bool | None = strawberry.UNSET + is_active: bool | None = strawberry.UNSET + is_superuser: bool | None = strawberry.UNSET + organization_id: str | None = strawberry.UNSET + permissions: list[str] | None = strawberry.UNSET @strawberry.input class UpdateUserMutationInput(utils.BaseInput): id: int - email: typing.Optional[str] = strawberry.UNSET - full_name: typing.Optional[str] = strawberry.UNSET - is_staff: typing.Optional[bool] = strawberry.UNSET - is_active: typing.Optional[bool] = strawberry.UNSET - is_superuser: typing.Optional[bool] = strawberry.UNSET - permissions: typing.Optional[typing.List[str]] = strawberry.UNSET + email: str | None = strawberry.UNSET + full_name: str | None = strawberry.UNSET + is_staff: bool | None = strawberry.UNSET + is_active: bool | None = strawberry.UNSET + is_superuser: bool | None = strawberry.UNSET + permissions: list[str] | None = strawberry.UNSET @strawberry.input @@ -104,75 +95,63 @@ class DeleteConnectionMutationInput(utils.BaseInput): @strawberry.input class CreateMarkupMutationInput(utils.BaseInput): """Input for creating a new Markup.""" + name: str amount: float markup_type: admin.MarkupTypeEnum - active: typing.Optional[bool] = strawberry.UNSET - is_visible: typing.Optional[bool] = strawberry.UNSET - carrier_codes: typing.Optional[typing.List[str]] = strawberry.UNSET - service_codes: typing.Optional[typing.List[str]] = strawberry.UNSET - connection_ids: typing.Optional[typing.List[str]] = strawberry.UNSET - organizations: typing.Optional[typing.List[str]] = strawberry.UNSET - meta: typing.Optional[utils.JSON] = strawberry.UNSET - metadata: typing.Optional[utils.JSON] = strawberry.UNSET + active: bool | None = strawberry.UNSET + is_visible: bool | None = strawberry.UNSET + carrier_codes: list[str] | None = strawberry.UNSET + service_codes: list[str] | None = strawberry.UNSET + connection_ids: list[str] | None = strawberry.UNSET + organizations: list[str] | None = strawberry.UNSET + meta: utils.JSON | None = strawberry.UNSET + metadata: utils.JSON | None = strawberry.UNSET @strawberry.input class UpdateMarkupMutationInput(utils.BaseInput): """Input for updating an existing Markup.""" + id: str - name: typing.Optional[str] = strawberry.UNSET - amount: typing.Optional[float] = strawberry.UNSET - markup_type: typing.Optional[admin.MarkupTypeEnum] = strawberry.UNSET - active: typing.Optional[bool] = strawberry.UNSET - is_visible: typing.Optional[bool] = strawberry.UNSET - carrier_codes: typing.Optional[typing.List[str]] = strawberry.UNSET - service_codes: typing.Optional[typing.List[str]] = strawberry.UNSET - connection_ids: typing.Optional[typing.List[str]] = strawberry.UNSET - organizations: typing.Optional[typing.List[str]] = strawberry.UNSET - meta: typing.Optional[utils.JSON] = strawberry.UNSET - metadata: typing.Optional[utils.JSON] = strawberry.UNSET + name: str | None = strawberry.UNSET + amount: float | None = strawberry.UNSET + markup_type: admin.MarkupTypeEnum | None = strawberry.UNSET + active: bool | None = strawberry.UNSET + is_visible: bool | None = strawberry.UNSET + carrier_codes: list[str] | None = strawberry.UNSET + service_codes: list[str] | None = strawberry.UNSET + connection_ids: list[str] | None = strawberry.UNSET + organizations: list[str] | None = strawberry.UNSET + meta: utils.JSON | None = strawberry.UNSET + metadata: utils.JSON | None = strawberry.UNSET @strawberry.input class InstanceConfigMutationInput(utils.BaseInput): - configs: typing.Optional[utils.JSON] = strawberry.UNSET + configs: utils.JSON | None = strawberry.UNSET + # Admin-specific filter inputs + @strawberry.input class ResourceUsageFilter(utils.UsageFilter): - carrier_connection_id: typing.Optional[str] = strawberry.UNSET - markup_id: typing.Optional[str] = strawberry.UNSET - account_id: typing.Optional[str] = strawberry.UNSET + carrier_connection_id: str | None = strawberry.UNSET + markup_id: str | None = strawberry.UNSET + account_id: str | None = strawberry.UNSET # ----------------------------------------------------------- # Worker Management Mutation Inputs # ----------------------------------------------------------- + @strawberry.input class TriggerTrackerUpdateInput(utils.BaseInput): - tracker_ids: typing.Optional[typing.List[str]] = strawberry.UNSET + tracker_ids: list[str] | None = strawberry.UNSET @strawberry.input class RetryWebhookInput(utils.BaseInput): event_id: str - - -@strawberry.input -class RevokeTaskInput(utils.BaseInput): - task_id: str - - -@strawberry.input -class CleanupTaskExecutionsInput(utils.BaseInput): - retention_days: typing.Optional[int] = strawberry.UNSET - statuses: typing.Optional[typing.List[str]] = strawberry.UNSET - - -@strawberry.input -class ResetStuckTasksInput(utils.BaseInput): - threshold_minutes: typing.Optional[int] = strawberry.UNSET - statuses: typing.Optional[typing.List[str]] = strawberry.UNSET diff --git a/modules/admin/karrio/server/admin/schemas/base/mutations.py b/modules/admin/karrio/server/admin/schemas/base/mutations.py index e1fa74316f..88574590dd 100644 --- a/modules/admin/karrio/server/admin/schemas/base/mutations.py +++ b/modules/admin/karrio/server/admin/schemas/base/mutations.py @@ -1,27 +1,26 @@ -import typing import datetime -import strawberry -from constance import config -from strawberry.types import Info -import django.db.transaction as transaction -from django.utils import timezone -from rest_framework import exceptions +import django.db.transaction as transaction import karrio.lib as lib -import karrio.server.conf as conf -import karrio.server.iam.models as iam -import karrio.server.graph.utils as utils -import karrio.server.admin.utils as admin import karrio.server.admin.forms as forms -import karrio.server.pricing.models as pricing -import karrio.server.serializers as serializers -import karrio.server.providers.models as providers -import karrio.server.admin.schemas.base.types as types import karrio.server.admin.schemas.base.inputs as inputs +import karrio.server.admin.schemas.base.types as types +import karrio.server.admin.utils as admin +import karrio.server.conf as conf import karrio.server.graph.schemas.base.mutations as base import karrio.server.graph.serializers as graph_serializers +import karrio.server.graph.utils as utils +import karrio.server.iam.models as iam +import karrio.server.pricing.models as pricing +import karrio.server.providers.models as providers import karrio.server.providers.serializers as providers_serializers +import karrio.server.serializers as serializers +import strawberry +from constance import config +from django.utils import timezone from karrio.server.core.logging import logger +from rest_framework import exceptions +from strawberry.types import Info # Feature fields that clients may send at service root level instead of nested # in features{}. Pop them from each service dict and merge into features. @@ -33,11 +32,7 @@ def _merge_root_features(services: list) -> list: result = [] for svc in services: svc = svc.copy() if isinstance(svc, dict) else dict(svc) - root_features = { - k: svc.pop(k) - for k in _ROOT_FEATURE_KEYS - if k in svc and svc[k] is not None - } + root_features = {k: svc.pop(k) for k in _ROOT_FEATURE_KEYS if k in svc and svc[k] is not None} if root_features: features = svc.get("features") or {} svc["features"] = {**features, **root_features} @@ -47,10 +42,9 @@ def _merge_root_features(services: list) -> list: @strawberry.type class InstanceConfigMutation(utils.BaseMutation): - configs: typing.Optional[types.InstanceConfigType] = None + configs: types.InstanceConfigType | None = None @staticmethod - @admin.staff_required @utils.authentication_required def mutate(info: Info, **input) -> "InstanceConfigMutation": data = input.get("configs") or {} @@ -63,8 +57,7 @@ def mutate(info: Info, **input) -> "InstanceConfigMutation": "feature_flags": { k: v for k, v in data.items() - if k in conf.settings.CONSTANCE_CONFIG - and k not in ["APP_NAME", "APP_WEBSITE"] + if k in conf.settings.CONSTANCE_CONFIG and k not in ["APP_NAME", "APP_WEBSITE"] } }, conf.settings.tenant, @@ -82,9 +75,7 @@ def mutate(info: Info, **input) -> "InstanceConfigMutation": if k in conf.settings.CONSTANCE_CONFIG: setattr(config, k, v) - return InstanceConfigMutation( - configs=types.InstanceConfigType.resolve(info) - ) + return InstanceConfigMutation(configs=types.InstanceConfigType.resolve(info)) except Exception as e: logger.error("Failed to update instance config", error=str(e), exc_info=True) raise e @@ -92,7 +83,7 @@ def mutate(info: Info, **input) -> "InstanceConfigMutation": @strawberry.type class CreateUserMutation(utils.BaseMutation): - user: typing.Optional[types.SystemUserType] = None + user: types.SystemUserType | None = None @staticmethod @transaction.atomic @@ -101,8 +92,8 @@ class CreateUserMutation(utils.BaseMutation): @admin.superuser_required def mutate( info: Info, - organization_id: typing.Optional[str] = None, - permissions: typing.Optional[typing.List[str]] = None, + organization_id: str | None = None, + permissions: list[str] | None = None, **input: inputs.CreateUserMutationInput, ) -> "CreateUserMutation": try: @@ -142,9 +133,7 @@ def mutate( if any(permissions or []): user.groups.set(iam.Group.objects.filter(name__in=permissions)) - return CreateUserMutation( - user=iam.User.objects.get(id=user.id) - ) # type:ignore + return CreateUserMutation(user=iam.User.objects.get(id=user.id)) # type:ignore except Exception as e: logger.error("Failed to create user", email=input.get("email"), error=str(e), exc_info=True) raise e @@ -152,7 +141,7 @@ def mutate( @strawberry.type class UpdateUserMutation(utils.BaseMutation): - user: typing.Optional[types.SystemUserType] = None + user: types.SystemUserType | None = None @staticmethod @transaction.atomic @@ -162,7 +151,7 @@ class UpdateUserMutation(utils.BaseMutation): def mutate( info: Info, id: int, - permissions: typing.Optional[typing.List[str]] = None, + permissions: list[str] | None = None, **input: inputs.UpdateUserMutationInput, ) -> "UpdateUserMutation": instance = iam.User.objects.get(id=id) @@ -193,7 +182,6 @@ class DeleteConnectionMutation(utils.BaseMutation): @staticmethod @transaction.atomic @utils.error_wrapper - @admin.staff_required @utils.authentication_required @utils.authorization_required(["manage_carriers"]) def mutate(info: Info, **input) -> "DeleteConnectionMutation": @@ -205,14 +193,13 @@ def mutate(info: Info, **input) -> "DeleteConnectionMutation": @strawberry.type class CreateSystemCarrierConnectionMutation(utils.BaseMutation): - connection: typing.Optional[types.SystemCarrierConnectionType] = None + connection: types.SystemCarrierConnectionType | None = None @staticmethod @transaction.atomic @utils.error_wrapper @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required def mutate(info: Info, **input) -> "CreateSystemCarrierConnectionMutation": data = input.copy() @@ -232,14 +219,13 @@ def mutate(info: Info, **input) -> "CreateSystemCarrierConnectionMutation": @strawberry.type class UpdateSystemCarrierConnectionMutation(utils.BaseMutation): - connection: typing.Optional[types.SystemCarrierConnectionType] = None + connection: types.SystemCarrierConnectionType | None = None @staticmethod @transaction.atomic @utils.error_wrapper @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required def mutate(info: Info, **input) -> "UpdateSystemCarrierConnectionMutation": data = input.copy() id = data.get("id") @@ -266,11 +252,10 @@ def mutate(info: Info, **input) -> "UpdateSystemCarrierConnectionMutation": class CreateMarkupMutation(utils.BaseMutation): """Create a new Markup.""" - markup: typing.Optional[types.MarkupType] = None + markup: types.MarkupType | None = None @staticmethod @utils.authentication_required - @admin.staff_required @transaction.atomic def mutate( info: Info, @@ -285,20 +270,17 @@ def mutate( instance.save() - return CreateMarkupMutation( - markup=pricing.Markup.objects.get(id=instance.id) - ) # type:ignore + return CreateMarkupMutation(markup=pricing.Markup.objects.get(id=instance.id)) # type:ignore @strawberry.type class UpdateMarkupMutation(utils.BaseMutation): """Update an existing Markup.""" - markup: typing.Optional[types.MarkupType] = None + markup: types.MarkupType | None = None @staticmethod @utils.authentication_required - @admin.staff_required @transaction.atomic def mutate( info: Info, @@ -318,23 +300,18 @@ def mutate( instance.save() - return UpdateMarkupMutation( - markup=pricing.Markup.objects.get(id=id) - ) # type:ignore + return UpdateMarkupMutation(markup=pricing.Markup.objects.get(id=id)) # type:ignore @strawberry.type class CreateRateSheetMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.CreateRateSheetMutationInput - ) -> "CreateRateSheetMutation": + def mutate(info: Info, **input: inputs.base.CreateRateSheetMutationInput) -> "CreateRateSheetMutation": data = input.copy() carriers = data.pop("carriers", []) zones_data = data.pop("zones", []) @@ -384,16 +361,13 @@ def mutate( @strawberry.type class UpdateRateSheetMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.UpdateRateSheetMutationInput - ) -> "UpdateRateSheetMutation": + def mutate(info: Info, **input: inputs.base.UpdateRateSheetMutationInput) -> "UpdateRateSheetMutation": data = input.copy() carriers = data.pop("carriers", []) if "services" in data: @@ -425,35 +399,31 @@ def mutate( carrier_code=rate_sheet.carrier_name, ) connection_qs.filter(id__in=carriers).update(rate_sheet=rate_sheet) - connection_qs.filter(rate_sheet=rate_sheet).exclude(id__in=carriers).update( - rate_sheet=None - ) + connection_qs.filter(rate_sheet=rate_sheet).exclude(id__in=carriers).update(rate_sheet=None) - return UpdateRateSheetMutation( - rate_sheet=providers.SystemRateSheet.objects.get(id=input["id"]) - ) + return UpdateRateSheetMutation(rate_sheet=providers.SystemRateSheet.objects.get(id=input["id"])) @strawberry.type class DeleteRateSheetServiceMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required def mutate( info: Info, **input: inputs.base.DeleteRateSheetServiceMutationInput ) -> "DeleteRateSheetServiceMutation": - rate_sheet = providers.SystemRateSheet.objects.get( - id=input["rate_sheet_id"] - ) + rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) service = rate_sheet.services.get(id=input["service_id"]) - # Remove service from rate sheet and delete it + # Remove service from rate sheet and delete it. Also scrub any + # service_rates entries that pointed at the removed service so a + # subsequent re-import doesn't resurface them as 'removed' rows. rate_sheet.services.remove(service) service.delete() + rate_sheet.scrub_orphan_service_rates() return DeleteRateSheetServiceMutation(rate_sheet=rate_sheet) @@ -465,16 +435,13 @@ def mutate( @strawberry.type class AddSharedZoneMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.AddSharedZoneMutationInput - ) -> "AddSharedZoneMutation": + def mutate(info: Info, **input: inputs.base.AddSharedZoneMutationInput) -> "AddSharedZoneMutation": from rest_framework import exceptions rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) @@ -484,23 +451,20 @@ def mutate( try: rate_sheet.add_zone(zone_dict) except ValueError as e: - raise exceptions.ValidationError({"zone": str(e)}) + raise exceptions.ValidationError({"zone": str(e)}) from e return AddSharedZoneMutation(rate_sheet=rate_sheet) @strawberry.type class UpdateSharedZoneMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.UpdateSharedZoneMutationInput - ) -> "UpdateSharedZoneMutation": + def mutate(info: Info, **input: inputs.base.UpdateSharedZoneMutationInput) -> "UpdateSharedZoneMutation": from rest_framework import exceptions rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) @@ -510,23 +474,20 @@ def mutate( try: rate_sheet.update_zone(input["zone_id"], zone_dict) except ValueError as e: - raise exceptions.ValidationError({"zone_id": str(e)}) + raise exceptions.ValidationError({"zone_id": str(e)}) from e return UpdateSharedZoneMutation(rate_sheet=rate_sheet) @strawberry.type class DeleteSharedZoneMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.DeleteSharedZoneMutationInput - ) -> "DeleteSharedZoneMutation": + def mutate(info: Info, **input: inputs.base.DeleteSharedZoneMutationInput) -> "DeleteSharedZoneMutation": from rest_framework import exceptions rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) @@ -534,7 +495,7 @@ def mutate( try: rate_sheet.remove_zone(input["zone_id"]) except ValueError as e: - raise exceptions.ValidationError({"zone_id": str(e)}) + raise exceptions.ValidationError({"zone_id": str(e)}) from e return DeleteSharedZoneMutation(rate_sheet=rate_sheet) @@ -546,16 +507,13 @@ def mutate( @strawberry.type class AddSharedSurchargeMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.AddSharedSurchargeMutationInput - ) -> "AddSharedSurchargeMutation": + def mutate(info: Info, **input: inputs.base.AddSharedSurchargeMutationInput) -> "AddSharedSurchargeMutation": from rest_framework import exceptions rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) @@ -565,23 +523,20 @@ def mutate( try: rate_sheet.add_surcharge(surcharge_dict) except ValueError as e: - raise exceptions.ValidationError({"surcharge": str(e)}) + raise exceptions.ValidationError({"surcharge": str(e)}) from e return AddSharedSurchargeMutation(rate_sheet=rate_sheet) @strawberry.type class UpdateSharedSurchargeMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.UpdateSharedSurchargeMutationInput - ) -> "UpdateSharedSurchargeMutation": + def mutate(info: Info, **input: inputs.base.UpdateSharedSurchargeMutationInput) -> "UpdateSharedSurchargeMutation": from rest_framework import exceptions rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) @@ -591,23 +546,20 @@ def mutate( try: rate_sheet.update_surcharge(input["surcharge_id"], surcharge_dict) except ValueError as e: - raise exceptions.ValidationError({"surcharge_id": str(e)}) + raise exceptions.ValidationError({"surcharge_id": str(e)}) from e return UpdateSharedSurchargeMutation(rate_sheet=rate_sheet) @strawberry.type class DeleteSharedSurchargeMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.DeleteSharedSurchargeMutationInput - ) -> "DeleteSharedSurchargeMutation": + def mutate(info: Info, **input: inputs.base.DeleteSharedSurchargeMutationInput) -> "DeleteSharedSurchargeMutation": from rest_framework import exceptions rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) @@ -615,35 +567,29 @@ def mutate( try: rate_sheet.remove_surcharge(input["surcharge_id"]) except ValueError as e: - raise exceptions.ValidationError({"surcharge_id": str(e)}) + raise exceptions.ValidationError({"surcharge_id": str(e)}) from e return DeleteSharedSurchargeMutation(rate_sheet=rate_sheet) @strawberry.type class BatchUpdateSurchargesMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.BatchUpdateSurchargesMutationInput - ) -> "BatchUpdateSurchargesMutation": + def mutate(info: Info, **input: inputs.base.BatchUpdateSurchargesMutationInput) -> "BatchUpdateSurchargesMutation": from rest_framework import exceptions rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) - surcharges = [ - {k: v for k, v in s.items() if not utils.is_unset(v)} - for s in input["surcharges"] - ] + surcharges = [{k: v for k, v in s.items() if not utils.is_unset(v)} for s in input["surcharges"]] try: rate_sheet.batch_update_surcharges(surcharges) except ValueError as e: - raise exceptions.ValidationError({"surcharges": str(e)}) + raise exceptions.ValidationError({"surcharges": str(e)}) from e return BatchUpdateSurchargesMutation(rate_sheet=rate_sheet) @@ -655,16 +601,13 @@ def mutate( @strawberry.type class UpdateServiceRateMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.UpdateServiceRateMutationInput - ) -> "UpdateServiceRateMutation": + def mutate(info: Info, **input: inputs.base.UpdateServiceRateMutationInput) -> "UpdateServiceRateMutation": from rest_framework import exceptions rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) @@ -678,25 +621,22 @@ def mutate( try: rate_sheet.update_service_rate( - service_id=input["service_id"], - zone_id=input["zone_id"], - rate_data=rate_data + service_id=input["service_id"], zone_id=input["zone_id"], rate_data=rate_data ) except ValueError as e: - raise exceptions.ValidationError({"rate": str(e)}) + raise exceptions.ValidationError({"rate": str(e)}) from e return UpdateServiceRateMutation(rate_sheet=rate_sheet) @strawberry.type class BatchUpdateServiceRatesMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required def mutate( info: Info, **input: inputs.base.BatchUpdateServiceRatesMutationInput ) -> "BatchUpdateServiceRatesMutation": @@ -704,31 +644,25 @@ def mutate( rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) - updates = [ - {k: v for k, v in rate.items() if not utils.is_unset(v)} - for rate in input["rates"] - ] + updates = [{k: v for k, v in rate.items() if not utils.is_unset(v)} for rate in input["rates"]] try: rate_sheet.batch_update_service_rates(updates) except ValueError as e: - raise exceptions.ValidationError({"rates": str(e)}) + raise exceptions.ValidationError({"rates": str(e)}) from e return BatchUpdateServiceRatesMutation(rate_sheet=rate_sheet) @strawberry.type class DeleteServiceRateMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.DeleteServiceRateMutationInput - ) -> "DeleteServiceRateMutation": + def mutate(info: Info, **input: inputs.base.DeleteServiceRateMutationInput) -> "DeleteServiceRateMutation": rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) min_weight = input.get("min_weight") @@ -755,16 +689,13 @@ def mutate( @strawberry.type class AddWeightRangeMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.AddWeightRangeMutationInput - ) -> "AddWeightRangeMutation": + def mutate(info: Info, **input: inputs.base.AddWeightRangeMutationInput) -> "AddWeightRangeMutation": rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) rate_sheet.add_weight_range( @@ -777,16 +708,13 @@ def mutate( @strawberry.type class RemoveWeightRangeMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.RemoveWeightRangeMutationInput - ) -> "RemoveWeightRangeMutation": + def mutate(info: Info, **input: inputs.base.RemoveWeightRangeMutationInput) -> "RemoveWeightRangeMutation": rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) rate_sheet.remove_weight_range( @@ -804,16 +732,13 @@ def mutate( @strawberry.type class UpdateServiceZoneIdsMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required - def mutate( - info: Info, **input: inputs.base.UpdateServiceZoneIdsMutationInput - ) -> "UpdateServiceZoneIdsMutation": + def mutate(info: Info, **input: inputs.base.UpdateServiceZoneIdsMutationInput) -> "UpdateServiceZoneIdsMutation": rate_sheet = providers.SystemRateSheet.objects.get(id=input["rate_sheet_id"]) service = rate_sheet.services.get(id=input["service_id"]) @@ -825,13 +750,12 @@ def mutate( @strawberry.type class UpdateServiceSurchargeIdsMutation(utils.BaseMutation): - rate_sheet: typing.Optional[types.SystemRateSheetType] = None + rate_sheet: types.SystemRateSheetType | None = None @staticmethod @transaction.atomic @utils.authentication_required @utils.authorization_required(["manage_carriers"]) - @admin.staff_required def mutate( info: Info, **input: inputs.base.UpdateServiceSurchargeIdsMutationInput ) -> "UpdateServiceSurchargeIdsMutation": @@ -857,8 +781,8 @@ class TriggerTrackerUpdateMutation(utils.BaseMutation): @utils.authentication_required @admin.superuser_required def mutate(info: Info, **input) -> "TriggerTrackerUpdateMutation": - from karrio.server.events.task_definitions.base import tracking import karrio.server.core.utils as core_utils + from karrio.server.events.task_definitions.base import tracking tracker_ids = input.get("tracker_ids") or [] task_count = 0 @@ -881,7 +805,7 @@ def _run(**kwargs): @strawberry.type class RetryWebhookMutation(utils.BaseMutation): - event_id: typing.Optional[str] = None + event_id: str | None = None @staticmethod @utils.authentication_required @@ -907,7 +831,7 @@ def mutate(info: Info, **input) -> "RetryWebhookMutation": @strawberry.type class RevokeTaskMutation(utils.BaseMutation): - task_id: typing.Optional[str] = None + task_id: str | None = None @staticmethod @utils.authentication_required @@ -985,8 +909,8 @@ class TriggerDataArchivingMutation(utils.BaseMutation): @utils.authentication_required @admin.superuser_required def mutate(info: Info, **input) -> "TriggerDataArchivingMutation": - from karrio.server.events.task_definitions.base import archiving import karrio.server.core.utils as core_utils + from karrio.server.events.task_definitions.base import archiving @core_utils.run_on_all_tenants def _run(**kwargs): diff --git a/modules/admin/karrio/server/admin/schemas/base/types.py b/modules/admin/karrio/server/admin/schemas/base/types.py index 89bab975ad..b198df0fcd 100644 --- a/modules/admin/karrio/server/admin/schemas/base/types.py +++ b/modules/admin/karrio/server/admin/schemas/base/types.py @@ -1,22 +1,19 @@ -import typing import datetime -import strawberry -from constance import config -from django.utils import timezone -from strawberry.types import Info +import typing import karrio.lib as lib +import karrio.server.admin.schemas.base.inputs as inputs import karrio.server.conf as conf -import karrio.server.iam.models as iam -import karrio.server.graph.utils as utils -import karrio.server.admin.utils as admin import karrio.server.core.filters as filters -import karrio.server.pricing.models as pricing +import karrio.server.graph.schemas.base.types as base +import karrio.server.graph.utils as utils +import karrio.server.iam.models as iam import karrio.server.manager.models as manager +import karrio.server.pricing.models as pricing import karrio.server.providers.models as providers -import karrio.server.graph.schemas.base.types as base -import karrio.server.admin.schemas.base.inputs as inputs - +import strawberry +from django.utils import timezone +from strawberry.types import Info PRIVATE_CONFIGS = [ "EMAIL_USE_TLS", @@ -35,14 +32,14 @@ class SystemUserType(base.UserType): id: int @strawberry.field - def permissions(self: iam.User, info: Info) -> typing.Optional[typing.List[str]]: + def permissions(self: iam.User, info: Info) -> list[str] | None: return self.permissions @strawberry.field - def metadata(self: iam.User) -> typing.Optional[utils.JSON]: + def metadata(self: iam.User) -> utils.JSON | None: try: return lib.to_dict(self.metadata) - except: + except Exception: return self.metadata @staticmethod @@ -52,7 +49,6 @@ def me(info: Info) -> "SystemUserType": @staticmethod @utils.authentication_required - @admin.staff_required def resolve( info: Info, email: str = strawberry.UNSET, @@ -62,15 +58,12 @@ def resolve( @staticmethod @utils.authentication_required - @admin.staff_required def resolve_list( info: Info, - filter: typing.Optional[inputs.UserFilter] = strawberry.UNSET, + filter: inputs.UserFilter | None = strawberry.UNSET, ) -> utils.Connection["SystemUserType"]: _filter = filter if not utils.is_unset(filter) else inputs.UserFilter() - queryset = filters.UserFilter( - _filter.to_dict(), iam.User.objects.filter(is_staff=True) - ).qs + queryset = filters.UserFilter(_filter.to_dict(), iam.User.objects.filter(is_staff=True)).qs return utils.paginated_connection(queryset, **_filter.pagination()) @@ -81,19 +74,16 @@ class PermissionGroupType: name: str @strawberry.field - def permissions(self: iam.Group) -> typing.Optional[typing.List[str]]: + def permissions(self: iam.Group) -> list[str] | None: return self.permissions.all().values_list("name", flat=True) @staticmethod @utils.authentication_required - @admin.staff_required def resolve_list( info: Info, - filter: typing.Optional[inputs.PermissionGroupFilter] = strawberry.UNSET, + filter: inputs.PermissionGroupFilter | None = strawberry.UNSET, ) -> utils.Connection["PermissionGroupType"]: - _filter = ( - filter if not utils.is_unset(filter) else inputs.PermissionGroupFilter() - ) + _filter = filter if not utils.is_unset(filter) else inputs.PermissionGroupFilter() queryset = iam.Group.objects.filter() return utils.paginated_connection(queryset, **_filter.pagination()) @@ -107,18 +97,35 @@ class SystemCarrierConnectionType(base.CarrierConnectionType): This type now maps to the SystemConnection model (admin-managed platform connections). """ - object_type: typing.Optional[str] + object_type: str | None + + @strawberry.field + def id(self: providers.SystemConnection) -> str: + # Admin graph (/admin/graphql) is staff-only at the route level — + # raw SystemConnection PKs are appropriate here. Override the + # tenant-graph default (which would encrypt it). + return self.id @strawberry.field def credentials(self: providers.SystemConnection, info: Info) -> utils.JSON: """Get decrypted credentials for admin management.""" return self.get_credentials() + @strawberry.field + def config(self: providers.SystemConnection, info: Info) -> utils.JSON | None: + """Return the raw system-connection config for admin editing. + + Overrides the base resolver which returns None for SystemConnection to + prevent billing numbers leaking via the tenant graph. Admin staff need + the full config to manage billing numbers, label_type, default values, etc. + """ + return getattr(self, "config", None) + @strawberry.field def usage( self: providers.SystemConnection, info: Info, - filter: typing.Optional[utils.UsageFilter] = strawberry.UNSET, + filter: utils.UsageFilter | None = strawberry.UNSET, ) -> "ResourceUsageType": # Check for batch-precomputed usage on request context (avoids N+1) _cache = getattr(info.context.request, "_usage_cache", None) @@ -147,31 +154,24 @@ def usage( def shipments( self: providers.SystemConnection, info: Info, - filter: typing.Optional[inputs.base.ShipmentFilter] = strawberry.UNSET, + filter: inputs.base.ShipmentFilter | None = strawberry.UNSET, ) -> utils.Connection[base.ShipmentType]: - _filter = ( - filter if not utils.is_unset(filter) else inputs.base.ShipmentFilter() - ) + _filter = filter if not utils.is_unset(filter) else inputs.base.ShipmentFilter() _filter_data = _filter.to_dict() # Query shipments that used this system connection via brokered connections brokered_ids = list( - providers.BrokeredConnection.objects.filter( - system_connection=self - ).values_list("id", flat=True) + providers.BrokeredConnection.objects.filter(system_connection=self).values_list("id", flat=True) ) # Include both direct system connection usage and brokered usage queryset = filters.ShipmentFilters( _filter_data, - manager.Shipment.objects.filter( - selected_rate__meta__connection_id__in=[self.id] + brokered_ids - ), + manager.Shipment.objects.filter(selected_rate__meta__connection_id__in=[self.id] + brokered_ids), ).qs return utils.paginated_connection(queryset, **_filter.pagination()) @staticmethod @utils.authentication_required - @admin.staff_required def resolve(info: Info, id: str) -> typing.Optional["SystemCarrierConnectionType"]: # Now queries SystemConnection model directly return providers.SystemConnection.objects.filter( @@ -181,10 +181,9 @@ def resolve(info: Info, id: str) -> typing.Optional["SystemCarrierConnectionType @staticmethod @utils.authentication_required - @admin.staff_required def resolve_list( info: Info, - filter: typing.Optional[inputs.base.CarrierFilter] = strawberry.UNSET, + filter: inputs.base.CarrierFilter | None = strawberry.UNSET, ) -> utils.Connection["SystemCarrierConnectionType"]: _filter = filter if not utils.is_unset(filter) else inputs.base.CarrierFilter() _filter_data = _filter.to_dict() @@ -197,24 +196,43 @@ def resolve_list( if _filter_data.get("active") is not None: queryset = queryset.filter(active=_filter_data["active"]) if _filter_data.get("carrier_name"): - queryset = queryset.filter(carrier_code=_filter_data["carrier_name"]) + # carrier_name is a list[str] on CarrierFilter — match any of them. + queryset = queryset.filter(carrier_code__in=_filter_data["carrier_name"]) # Batch-prefetch usage for all connections to avoid N+1 queries connection_ids = list(queryset.values_list("id", flat=True)) if connection_ids: - info.context.request._usage_cache = ( - ResourceUsageType.batch_resolve_usage( - info, connection_ids=connection_ids - ) + info.context.request._usage_cache = ResourceUsageType.batch_resolve_usage( + info, connection_ids=connection_ids ) - return utils.paginated_connection(queryset) + return utils.paginated_connection(queryset, **_filter.pagination()) + + +def _ensure_aware(value): + """Coerce a datetime / ISO-string into a tz-aware datetime. + + Admin usage-stat queries feed `created_at__gte=` / `__lte=` on + Fee/Shipment DateTimeFields, which are tz-aware. Naive values slip + through when the GraphQL input carries an ISO string without a + trailing 'Z' / offset — Django then emits the familiar + ``DateTimeField received a naive datetime`` RuntimeWarning on every + matching row. Normalise once at the query boundary. + """ + if value is None: + return None + if isinstance(value, str): + parsed = timezone.datetime.fromisoformat(value.replace("Z", "+00:00")) + value = parsed + if isinstance(value, datetime.datetime) and timezone.is_naive(value): + return timezone.make_aware(value, timezone.get_current_timezone()) + return value def _bulk_load_constance_config(keys): """Bulk load constance configuration values to avoid N+1 queries.""" - from django.apps import apps from constance.codecs import loads + from django.apps import apps Constance = apps.get_model("constance", "Constance") @@ -223,9 +241,7 @@ def _bulk_load_constance_config(keys): prefixed_keys = [f"{prefix}{key}" for key in keys] # Use Django ORM to bulk fetch all values in a single query - constance_values = Constance.objects.filter(key__in=prefixed_keys).values( - "key", "value" - ) + constance_values = Constance.objects.filter(key__in=prefixed_keys).values("key", "value") # Build dict mapping unprefixed key to deserialized value values_dict = {} @@ -250,17 +266,13 @@ def _bulk_load_constance_config(keys): @strawberry.type class InstanceConfigType: - configs: typing.Optional[utils.JSON] = None + configs: utils.JSON | None = None @staticmethod @utils.authentication_required - @admin.staff_required def resolve(info: Info) -> "InstanceConfigType": if conf.settings.tenant: - values = { - k: getattr(conf.settings, k, None) - for k in conf.settings.CONSTANCE_CONFIG.keys() - } + values = {k: getattr(conf.settings, k, None) for k in conf.settings.CONSTANCE_CONFIG} else: all_keys = list(conf.settings.CONSTANCE_CONFIG.keys()) values = _bulk_load_constance_config(all_keys) @@ -271,17 +283,13 @@ def resolve(info: Info) -> "InstanceConfigType": @strawberry.type class ConfigFieldsetType: name: str - keys: typing.List[str] + keys: list[str] @staticmethod @utils.authentication_required - @admin.staff_required - def resolve_list(info: Info) -> typing.List["ConfigFieldsetType"]: + def resolve_list(info: Info) -> list["ConfigFieldsetType"]: fieldsets = getattr(conf.settings, "CONSTANCE_CONFIG_FIELDSETS", {}) - return [ - ConfigFieldsetType(name=name, keys=list(keys)) - for name, keys in fieldsets.items() - ] + return [ConfigFieldsetType(name=name, keys=list(keys)) for name, keys in fieldsets.items()] @strawberry.type @@ -289,12 +297,11 @@ class ConfigSchemaItemType: key: str description: str value_type: str - default_value: typing.Optional[str] = None + default_value: str | None = None @staticmethod @utils.authentication_required - @admin.staff_required - def resolve_list(info: Info) -> typing.List["ConfigSchemaItemType"]: + def resolve_list(info: Info) -> list["ConfigSchemaItemType"]: config = getattr(conf.settings, "CONSTANCE_CONFIG", {}) return [ ConfigSchemaItemType( @@ -314,12 +321,11 @@ class SystemRateSheetType(base.RateSheetType): @strawberry.field def carriers( self: providers.SystemRateSheet, - ) -> typing.List[SystemCarrierConnectionType]: + ) -> list[SystemCarrierConnectionType]: return self.carriers @staticmethod @utils.authentication_required - @admin.staff_required def resolve( info: Info, id: str, @@ -328,16 +334,16 @@ def resolve( @staticmethod @utils.authentication_required - @admin.staff_required def resolve_list( info: Info, - filter: typing.Optional[inputs.base.RateSheetFilter] = strawberry.UNSET, + filter: inputs.base.RateSheetFilter | None = strawberry.UNSET, ) -> utils.Connection["SystemRateSheetType"]: - _filter = ( - filter if not utils.is_unset(filter) else inputs.base.RateSheetFilter() - ) + _filter = filter if not utils.is_unset(filter) else inputs.base.RateSheetFilter() + # service_rates (the ~300 KB rate matrix) is only rendered by the detail + # view; defer it so the list doesn't load + json.loads it per row. queryset = filters.SystemRateSheetFilter( - _filter.to_dict(), providers.SystemRateSheet.objects.all() + _filter.to_dict(), + providers.SystemRateSheet.objects.all().defer("service_rates"), ).qs return utils.paginated_connection(queryset, **_filter.pagination()) @@ -345,20 +351,20 @@ def resolve_list( @strawberry.type class ResourceUsageType: - total_trackers: typing.Optional[int] = None - total_shipments: typing.Optional[int] = None - total_addons_charges: typing.Optional[float] = None - total_shipping_spend: typing.Optional[float] = None - addons_charges: typing.Optional[typing.List[utils.UsageStatType]] = None - shipping_spend: typing.Optional[typing.List[utils.UsageStatType]] = None - tracker_count: typing.Optional[typing.List[utils.UsageStatType]] = None + total_trackers: int | None = None + total_shipments: int | None = None + total_addons_charges: float | None = None + total_shipping_spend: float | None = None + addons_charges: list[utils.UsageStatType] | None = None + shipping_spend: list[utils.UsageStatType] | None = None + tracker_count: list[utils.UsageStatType] | None = None @staticmethod def batch_resolve_usage( info: Info, - connection_ids: typing.List[str], + connection_ids: list[str], filter: utils.UsageFilter = strawberry.UNSET, - ) -> typing.Dict[str, "ResourceUsageType"]: + ) -> dict[str, "ResourceUsageType"]: """Batch-compute usage for multiple connections in 4 queries total.""" import django.db.models as models import django.db.models.functions as functions @@ -370,6 +376,8 @@ def batch_resolve_usage( "date_after": (timezone.now() - datetime.timedelta(days=30)), **(filter.to_dict() if not utils.is_unset(filter) else {}), } + _filter["date_before"] = _ensure_aware(_filter.get("date_before")) + _filter["date_after"] = _ensure_aware(_filter.get("date_after")) # Query 1: Shipment stats grouped by connection_id shipment_qs = ( @@ -385,20 +393,14 @@ def batch_resolve_usage( ), ) .qs.annotate( - connection_id=models.F( - "selected_rate__meta__carrier_connection_id" - ), + connection_id=models.F("selected_rate__meta__carrier_connection_id"), date=functions.TruncDay("created_at"), ) .values("connection_id", "date") .annotate( count=models.Count("id"), amount=functions.Coalesce( - models.Sum( - functions.Cast( - "selected_rate__total_charge", models.FloatField() - ) - ), + models.Sum(functions.Cast("selected_rate__total_charge", models.FloatField())), models.Value(0.0), ), ) @@ -441,21 +443,21 @@ def batch_resolve_usage( ) # Build per-connection data from batch results - shipment_data: typing.Dict[str, list] = {cid: [] for cid in connection_ids} - shipment_counts: typing.Dict[str, int] = {cid: 0 for cid in connection_ids} + shipment_data: dict[str, list] = {cid: [] for cid in connection_ids} + shipment_counts: dict[str, int] = {cid: 0 for cid in connection_ids} for row in shipment_qs: cid = row["connection_id"] if cid in shipment_data: shipment_data[cid].append(row) shipment_counts[cid] += row["count"] - fee_data: typing.Dict[str, list] = {cid: [] for cid in connection_ids} + fee_data: dict[str, list] = {cid: [] for cid in connection_ids} for row in fee_qs: cid = row["connection_id"] if cid in fee_data: fee_data[cid].append(row) - tracker_data: typing.Dict[str, list] = {cid: [] for cid in connection_ids} + tracker_data: dict[str, list] = {cid: [] for cid in connection_ids} for row in tracker_qs: cid = row["connection_id"] if cid in tracker_data: @@ -468,33 +470,18 @@ def batch_resolve_usage( _fees = fee_data[cid] _trackers = tracker_data[cid] - total_shipping_spend = lib.to_decimal( - sum((r["amount"] for r in _shipping if r["amount"] is not None), 0.0) - ) - total_addons_charges = lib.to_decimal( - sum((r["amount"] for r in _fees if r["amount"] is not None), 0.0) - ) - total_trackers = sum( - (r["count"] for r in _trackers if r["count"] is not None), 0 - ) + total_shipping_spend = lib.to_decimal(sum((r["amount"] for r in _shipping if r["amount"] is not None), 0.0)) + total_addons_charges = lib.to_decimal(sum((r["amount"] for r in _fees if r["amount"] is not None), 0.0)) + total_trackers = sum((r["count"] for r in _trackers if r["count"] is not None), 0) result[cid] = ResourceUsageType( total_trackers=total_trackers, total_shipments=shipment_counts[cid], total_addons_charges=lib.to_decimal(total_addons_charges), total_shipping_spend=lib.to_decimal(total_shipping_spend), - shipping_spend=[ - utils.UsageStatType.parse(r, label="shipping_spend") - for r in _shipping - ], - tracker_count=[ - utils.UsageStatType.parse(r, label="tracker_count") - for r in _trackers - ], - addons_charges=[ - utils.UsageStatType.parse(r, label="addons_charges") - for r in _fees - ], + shipping_spend=[utils.UsageStatType.parse(r, label="shipping_spend") for r in _shipping], + tracker_count=[utils.UsageStatType.parse(r, label="tracker_count") for r in _trackers], + addons_charges=[utils.UsageStatType.parse(r, label="addons_charges") for r in _fees], ) return result @@ -515,22 +502,16 @@ def resolve_usage( "date_after": (timezone.now() - datetime.timedelta(days=30)), **filter.to_dict(), } - _account_filter = lib.identity( - dict(org__id=_filter.get("account_id")) if _filter.get("account_id") else {} - ) + _filter["date_before"] = _ensure_aware(_filter.get("date_before")) + _filter["date_after"] = _ensure_aware(_filter.get("date_after")) + _account_filter = lib.identity(dict(org__id=_filter.get("account_id")) if _filter.get("account_id") else {}) _connection_filter = lib.identity( - dict( - selected_rate__meta__carrier_connection_id=_filter.get( - "carrier_connection_id" - ) - ) + dict(selected_rate__meta__carrier_connection_id=_filter.get("carrier_connection_id")) if _filter.get("carrier_connection_id") else {} ) _tracker_filter = lib.identity( - dict(carrier__id=_filter.get("carrier_connection_id")) - if _filter.get("carrier_connection_id") - else {} + dict(carrier__id=_filter.get("carrier_connection_id")) if _filter.get("carrier_connection_id") else {} ) shipments = lib.identity( @@ -556,11 +537,7 @@ def resolve_usage( .annotate( count=models.Count("id"), amount=functions.Coalesce( - models.Sum( - functions.Cast( - "selected_rate__total_charge", models.FloatField() - ) - ), + models.Sum(functions.Cast("selected_rate__total_charge", models.FloatField())), models.Value(0.0), ), ) @@ -611,45 +588,26 @@ def resolve_usage( total_shipments = shipments.qs.count() total_shipping_spend = lib.to_decimal( sum( - [ - item["amount"] - for item in shipping_spend - if item["amount"] is not None - ], + [item["amount"] for item in shipping_spend if item["amount"] is not None], 0.0, ) ) total_addons_charges = lib.to_decimal( sum( - [ - item["amount"] - for item in addons_charges - if item["amount"] is not None - ], + [item["amount"] for item in addons_charges if item["amount"] is not None], 0.0, ) ) - total_trackers = sum( - [item["count"] for item in tracker_count if item["count"] is not None], 0 - ) + total_trackers = sum([item["count"] for item in tracker_count if item["count"] is not None], 0) return ResourceUsageType( total_trackers=total_trackers, total_shipments=total_shipments, total_addons_charges=lib.to_decimal(total_addons_charges), total_shipping_spend=lib.to_decimal(total_shipping_spend), - shipping_spend=[ - utils.UsageStatType.parse(item, label="shipping_spend") - for item in shipping_spend - ], - tracker_count=[ - utils.UsageStatType.parse(item, label="tracker_count") - for item in tracker_count - ], - addons_charges=[ - utils.UsageStatType.parse(item, label="addons_charges") - for item in addons_charges - ], + shipping_spend=[utils.UsageStatType.parse(item, label="shipping_spend") for item in shipping_spend], + tracker_count=[utils.UsageStatType.parse(item, label="tracker_count") for item in tracker_count], + addons_charges=[utils.UsageStatType.parse(item, label="addons_charges") for item in addons_charges], ) @@ -664,52 +622,44 @@ class MarkupType: amount: float markup_type: str is_visible: bool = True - meta: typing.Optional[utils.JSON] = None - metadata: typing.Optional[utils.JSON] = None + meta: utils.JSON | None = None + metadata: utils.JSON | None = None @strawberry.field - def service_codes(self: pricing.Markup) -> typing.List[str]: + def service_codes(self: pricing.Markup) -> list[str]: return self.service_codes or [] @strawberry.field - def carrier_codes(self: pricing.Markup) -> typing.List[str]: + def carrier_codes(self: pricing.Markup) -> list[str]: return self.carrier_codes or [] @strawberry.field - def connection_ids(self: pricing.Markup) -> typing.List[str]: + def connection_ids(self: pricing.Markup) -> list[str]: return self.connection_ids or [] @strawberry.field - def organization_ids(self: pricing.Markup) -> typing.List[str]: + def organization_ids(self: pricing.Markup) -> list[str]: return self.organization_ids or [] @strawberry.field def shipments( self: pricing.Markup, info: Info, - filter: typing.Optional[inputs.base.ShipmentFilter] = strawberry.UNSET, - ) -> typing.Optional[utils.Connection[base.ShipmentType]]: - _filter = ( - filter if not utils.is_unset(filter) else inputs.base.ShipmentFilter() - ) + filter: inputs.base.ShipmentFilter | None = strawberry.UNSET, + ) -> utils.Connection[base.ShipmentType] | None: + _filter = filter if not utils.is_unset(filter) else inputs.base.ShipmentFilter() _filter_data = _filter.to_dict() _test_mode = getattr(info.context.request, "test_mode", False) # Get shipment IDs from Fee table (indexed lookup) _fee_filters = dict(markup_id=self.id, test_mode=_test_mode) - shipment_ids = list( - pricing.Fee.objects.filter(**_fee_filters) - .values_list("shipment_id", flat=True) - .distinct() - ) + shipment_ids = list(pricing.Fee.objects.filter(**_fee_filters).values_list("shipment_id", flat=True).distinct()) return utils.paginated_connection( filters.ShipmentFilters( _filter_data, - manager.Shipment.objects.filter( - id__in=shipment_ids, test_mode=_test_mode - ), + manager.Shipment.objects.filter(id__in=shipment_ids, test_mode=_test_mode), ).qs, **_filter.pagination(), ) @@ -718,7 +668,7 @@ def shipments( def fees( self: pricing.Markup, info: Info, - filter: typing.Optional[utils.Paginated] = strawberry.UNSET, + filter: utils.Paginated | None = strawberry.UNSET, ) -> utils.Connection["FeeType"]: """Get fees generated by this markup.""" _filter = filter if not utils.is_unset(filter) else utils.Paginated() @@ -729,7 +679,7 @@ def fees( def usage( self: pricing.Markup, info: Info, - filter: typing.Optional[utils.UsageFilter] = strawberry.UNSET, + filter: utils.UsageFilter | None = strawberry.UNSET, ) -> "ResourceUsageType": filter = { **(filter.to_dict() if not utils.is_unset(filter) else {}), @@ -739,16 +689,14 @@ def usage( @staticmethod @utils.authentication_required - @admin.staff_required def resolve(info: Info, id: str) -> typing.Optional["MarkupType"]: return pricing.Markup.objects.filter(id=id).first() @staticmethod @utils.authentication_required - @admin.staff_required def resolve_list( info: Info, - filter: typing.Optional[inputs.MarkupFilter] = strawberry.UNSET, + filter: inputs.MarkupFilter | None = strawberry.UNSET, ) -> utils.Connection["MarkupType"]: _filter = filter if not utils.is_unset(filter) else inputs.MarkupFilter() _filter_data = _filter.to_dict() @@ -791,28 +739,26 @@ class FeeType: amount: float currency: str fee_type: str - percentage: typing.Optional[float] = None - markup_id: typing.Optional[str] = None + percentage: float | None = None + markup_id: str | None = None shipment_id: str - account_id: typing.Optional[str] = None + account_id: str | None = None connection_id: str carrier_code: str - service_code: typing.Optional[str] = None + service_code: str | None = None test_mode: bool = False created_at: datetime.datetime @staticmethod @utils.authentication_required - @admin.staff_required def resolve(info: Info, id: str) -> typing.Optional["FeeType"]: return pricing.Fee.objects.filter(id=id).first() @staticmethod @utils.authentication_required - @admin.staff_required def resolve_list( info: Info, - filter: typing.Optional[inputs.FeeFilter] = strawberry.UNSET, + filter: inputs.FeeFilter | None = strawberry.UNSET, ) -> utils.Connection["FeeType"]: _filter = filter if not utils.is_unset(filter) else inputs.FeeFilter() _filter_data = _filter.to_dict() @@ -835,101 +781,20 @@ def resolve_list( return utils.paginated_connection(queryset, **_filter.pagination()) -@strawberry.type -class TaskExecutionType: - """Admin type for Huey task execution records.""" - - id: int - task_id: str - task_name: str - status: str - queued_at: typing.Optional[datetime.datetime] = None - started_at: typing.Optional[datetime.datetime] = None - completed_at: typing.Optional[datetime.datetime] = None - duration_ms: typing.Optional[int] = None - error: typing.Optional[str] = None - retries: int = 0 - args_summary: typing.Optional[str] = None - - @staticmethod - @utils.authentication_required - @admin.staff_required - def resolve_list( - info: Info, - filter: typing.Optional[inputs.TaskExecutionFilter] = strawberry.UNSET, - ) -> utils.Connection["TaskExecutionType"]: - from karrio.server.admin.worker.models import TaskExecution - - _filter = ( - filter if not utils.is_unset(filter) else inputs.TaskExecutionFilter() - ) - _filter_data = _filter.to_dict() - _queryset_filters = {} - - if _filter_data.get("status"): - _queryset_filters["status"] = _filter_data["status"] - if _filter_data.get("task_name"): - _queryset_filters["task_name__icontains"] = _filter_data["task_name"] - if _filter_data.get("date_after"): - _queryset_filters["queued_at__gte"] = _filter_data["date_after"] - if _filter_data.get("date_before"): - _queryset_filters["queued_at__lte"] = _filter_data["date_before"] - - queryset = TaskExecution.objects.filter(**_queryset_filters) - return utils.paginated_connection(queryset, **_filter.pagination()) - - -@strawberry.type -class QueueInfoType: - pending_count: int - scheduled_count: int - result_count: int - - -@strawberry.type -class WorkerHealthType: - is_available: bool - queue: typing.Optional[QueueInfoType] = None - - @staticmethod - @utils.authentication_required - @admin.staff_required - def resolve(info: Info) -> "WorkerHealthType": - try: - from huey.contrib.djhuey import HUEY as huey_instance - - storage = huey_instance.storage - return WorkerHealthType( - is_available=True, - queue=QueueInfoType( - pending_count=storage.queue_size(), - scheduled_count=storage.schedule_size(), - result_count=storage.result_store_size(), - ), - ) - except Exception: - return WorkerHealthType(is_available=False) - - @strawberry.type class AdminSystemUsageType(base.SystemUsageType): - total_addons_charges: typing.Optional[float] = None - addons_charges: typing.Optional[typing.List[utils.UsageStatType]] = None + total_addons_charges: float | None = None + addons_charges: list[utils.UsageStatType] | None = None @staticmethod @utils.authentication_required - @admin.staff_required def resolve( info: Info, - filter: typing.Optional[utils.UsageFilter] = strawberry.UNSET, + filter: utils.UsageFilter | None = strawberry.UNSET, ) -> "AdminSystemUsageType": _filter_data = filter.to_dict() if not utils.is_unset(filter) else {} - base_usage = base.SystemUsageType.resolve( - info, filter=utils.UsageFilter(**_filter_data) - ) - resource_usage = ResourceUsageType.resolve_usage( - info, filter=utils.UsageFilter(**_filter_data) - ) + base_usage = base.SystemUsageType.resolve(info, filter=utils.UsageFilter(**_filter_data)) + resource_usage = ResourceUsageType.resolve_usage(info, filter=utils.UsageFilter(**_filter_data)) return AdminSystemUsageType( addons_charges=resource_usage.addons_charges, diff --git a/modules/admin/karrio/server/admin/signals.py b/modules/admin/karrio/server/admin/signals.py index d7ccd56345..258e0bf632 100644 --- a/modules/admin/karrio/server/admin/signals.py +++ b/modules/admin/karrio/server/admin/signals.py @@ -2,7 +2,11 @@ def register_signals(): - from karrio.server.admin.worker.signals import register_huey_signals + try: + from karrio.server.huey.signals import register_huey_signals + + register_huey_signals() + except ImportError: + logger.debug("Huey module not installed, skipping signal registration") - register_huey_signals() logger.info("Signal registration complete", module="karrio.admin") diff --git a/modules/admin/karrio/server/admin/tests/__init__.py b/modules/admin/karrio/server/admin/tests/__init__.py index 750c10ee85..e68f6024d0 100644 --- a/modules/admin/karrio/server/admin/tests/__init__.py +++ b/modules/admin/karrio/server/admin/tests/__init__.py @@ -1,3 +1,4 @@ +# ruff: noqa: I001 # Re-export all test classes for backward-compatible discovery. # `karrio test karrio.server.admin.tests` still works without changes. @@ -31,3 +32,8 @@ TestAdminMarkupValidation, TestAdminDataIsolation, ) +from karrio.server.admin.tests.test_admin_route_gate import ( # noqa: F401 + TestAdminGraphQLRouteGate, + TestAdminGraphQLRouteGateUnauthenticated, + TestAdminGraphQLRouteGateStaffPasses, +) diff --git a/modules/admin/karrio/server/admin/tests/base.py b/modules/admin/karrio/server/admin/tests/base.py index 488940221c..a1136bf096 100644 --- a/modules/admin/karrio/server/admin/tests/base.py +++ b/modules/admin/karrio/server/admin/tests/base.py @@ -1,12 +1,12 @@ -import json import dataclasses +import json + from django.contrib.auth import get_user_model from django.urls import reverse -from rest_framework import status -from rest_framework.test import APITestCase as BaseAPITestCase, APIClient - from karrio.server.user.models import Token -import karrio.server.providers.models as providers +from rest_framework import status +from rest_framework.test import APIClient +from rest_framework.test import APITestCase as BaseAPITestCase @dataclasses.dataclass @@ -25,20 +25,18 @@ class (not once per test method), cutting setUp cost significantly. @classmethod def setUpTestData(cls) -> None: - cls.user = get_user_model().objects.create_superuser( - "admin@example.com", "test" - ) + cls.user = get_user_model().objects.create_superuser("admin@example.com", "test") cls.user.is_staff = True cls.user.save() cls.token = Token.objects.create(user=cls.user, test_mode=False) from django.conf import settings + if settings.MULTI_ORGANIZATIONS: from karrio.server.orgs.models import Organization, TokenLink - cls.organization = Organization.objects.create( - name="Test Organization", slug="test-org" - ) + + cls.organization = Organization.objects.create(name="Test Organization", slug="test-org") owner = cls.organization.add_user(cls.user, is_admin=True) cls.organization.change_owner(owner) cls.organization.save() diff --git a/modules/admin/karrio/server/admin/tests/test_admin_mfa.py b/modules/admin/karrio/server/admin/tests/test_admin_mfa.py new file mode 100644 index 0000000000..a21be84c6d --- /dev/null +++ b/modules/admin/karrio/server/admin/tests/test_admin_mfa.py @@ -0,0 +1,94 @@ +""" +Tests that the Django admin site enforces OTP verification. +""" + +from django.contrib import admin +from django.contrib.auth import get_user_model +from django.test import Client, TestCase, override_settings +from django_otp import DEVICE_ID_SESSION_KEY +from django_otp.plugins.otp_email.models import EmailDevice +from two_factor.admin import AdminSiteOTPRequiredMixin + +# Test settings override so admin's static-file references don't require a +# pre-built manifest. The default Whitenoise CompressedManifestStaticFilesStorage +# is not populated under `karrio test`. +TEST_STORAGES = { + "staticfiles": { + "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", + }, +} + + +class TestAdminSiteMixin(TestCase): + def test_admin_site_has_otp_mixin(self): + # admin.site is a DefaultAdminSite LazyObject; isinstance proxies through. + self.assertIsInstance(admin.site, AdminSiteOTPRequiredMixin) + + +@override_settings(STORAGES=TEST_STORAGES) +class TestAdminMFAGate(TestCase): + @classmethod + def setUpTestData(cls): + User = get_user_model() + cls.staff = User.objects.create_user( + email="staff@example.com", + password="t0pSecret!", + is_staff=True, + is_active=True, + ) + cls.regular = User.objects.create_user( + email="user@example.com", + password="t0pSecret!", + is_active=True, + ) + cls.device = EmailDevice.objects.create( + user=cls.staff, + name="default", + confirmed=True, + ) + + def setUp(self): + self.maxDiff = None + self.client = Client() + + def test_anonymous_redirects_to_login(self): + response = self.client.get("/admin/", follow=False) + self.assertEqual(response.status_code, 302) + self.assertIn("/login/", response["Location"]) + + def test_non_staff_redirects_to_login(self): + self.client.force_login(self.regular) + response = self.client.get("/admin/", follow=False) + self.assertEqual(response.status_code, 302) + self.assertIn("/login/", response["Location"]) + + def test_staff_without_otp_verified_redirects(self): + # force_login bypasses two_factor wizard — session has no OTP device key + self.client.force_login(self.staff) + response = self.client.get("/admin/", follow=False) + self.assertEqual(response.status_code, 302) + self.assertIn("/login/", response["Location"]) + + def test_staff_with_otp_verified_session_admitted(self): + self.client.force_login(self.staff) + # Simulate a completed OTP challenge by writing the device id to session + session = self.client.session + session[DEVICE_ID_SESSION_KEY] = self.device.persistent_id + session.save() + + response = self.client.get("/admin/", follow=False) + self.assertEqual(response.status_code, 200) + + def test_setup_wizard_reachable_when_authenticated(self): + # `self.regular` has no OTP device, so the wizard is the right starting page. + # `self.staff` already has a confirmed device — the wizard would redirect + # them to setup_complete, which is correct stock library behavior we don't + # need to assert. + self.client.force_login(self.regular) + response = self.client.get("/account/two_factor/setup/", follow=False) + self.assertEqual(response.status_code, 200) + + def test_setup_wizard_requires_login(self): + response = self.client.get("/account/two_factor/setup/", follow=False) + self.assertEqual(response.status_code, 302) + self.assertIn("/login/", response["Location"]) diff --git a/modules/admin/karrio/server/admin/tests/test_admin_route_gate.py b/modules/admin/karrio/server/admin/tests/test_admin_route_gate.py new file mode 100644 index 0000000000..37f0fc61b8 --- /dev/null +++ b/modules/admin/karrio/server/admin/tests/test_admin_route_gate.py @@ -0,0 +1,105 @@ +""" +Regression tests for SHIP2-1164. + +The /admin/graphql endpoint must enforce staff-only access at the route level, +before any GraphQL resolver runs. Authorization is centralized on the route — +per-resolver decorators are not used — so adding a new query or mutation to +any admin schema cannot accidentally bypass the staff check. + +These tests verify that: +1. A non-staff authenticated user receives HTTP 403 for any /admin/graphql + request — including the queries that previously leaked. +2. An unauthenticated user receives HTTP 401. +3. A staff user is not blocked by the route-level gate. +""" + +from django.contrib.auth import get_user_model +from django.urls import reverse +from karrio.server.admin.tests.base import AdminGraphTestCase +from karrio.server.user.models import Token +from rest_framework import status +from rest_framework.test import APIClient, APITestCase + +# Queries that previously leaked cross-tenant data. The schema only contains +# the orgs admin types when karrio-insiders is installed; the gate must reject +# the request regardless of whether the field resolves so we use a query that +# is always present (rate_sheets) AND the previously-vulnerable ones. +PICKUPS_QUERY = "{ pickups { edges { node { id } } } }" +SHIPMENTS_QUERY = "{ shipments { edges { node { id } } } }" +TRACKERS_QUERY = "{ trackers { edges { node { id } } } }" +ORDERS_QUERY = "{ orders { edges { node { id } } } }" +RATE_SHEETS_QUERY = "{ rate_sheets { edges { node { id } } } }" + + +class TestAdminGraphQLRouteGate(APITestCase): + """The /admin/graphql route rejects non-staff before the schema executes.""" + + @classmethod + def setUpTestData(cls): + cls.regular_user = get_user_model().objects.create_user(email="regular-route@example.com", password="test") + cls.regular_token = Token.objects.create(user=cls.regular_user, test_mode=False) + + def setUp(self): + self.client = APIClient() + self.client.credentials(HTTP_AUTHORIZATION="Token " + self.regular_token.key) + self.url = reverse("karrio.server.admin:admin-graph") + + def _post(self, query: str): + return self.client.post(self.url, {"query": query}) + + def test_non_staff_pickups_query_is_rejected_at_route(self): + """SHIP2-1164: pickups query must be blocked before the resolver runs.""" + response = self._post(PICKUPS_QUERY) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + body = response.json() + self.assertIn("errors", body) + self.assertNotIn("data", body) + + def test_non_staff_shipments_query_is_rejected_at_route(self): + response = self._post(SHIPMENTS_QUERY) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertNotIn("data", response.json()) + + def test_non_staff_trackers_query_is_rejected_at_route(self): + response = self._post(TRACKERS_QUERY) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertNotIn("data", response.json()) + + def test_non_staff_orders_query_is_rejected_at_route(self): + response = self._post(ORDERS_QUERY) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertNotIn("data", response.json()) + + def test_non_staff_rate_sheets_query_is_rejected_at_route(self): + """Even queries that were always decorated must now hit the route gate.""" + response = self._post(RATE_SHEETS_QUERY) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertNotIn("data", response.json()) + + def test_non_staff_mutation_is_rejected_at_route(self): + response = self._post('mutation { create_rate_sheet(input: {name: "x", carrier_name: "ups"}) { id } }') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + + +class TestAdminGraphQLRouteGateUnauthenticated(APITestCase): + """Unauthenticated requests to /admin/graphql receive HTTP 401.""" + + def setUp(self): + self.client = APIClient() # no credentials + self.url = reverse("karrio.server.admin:admin-graph") + + def test_unauthenticated_query_returns_401(self): + response = self.client.post(self.url, {"query": PICKUPS_QUERY}) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + body = response.json() + self.assertIn("errors", body) + + +class TestAdminGraphQLRouteGateStaffPasses(AdminGraphTestCase): + """The route-level gate must not block legitimate staff access.""" + + def test_staff_can_query_rate_sheets(self): + result = self.query(RATE_SHEETS_QUERY) + # Either data resolves (200, no errors) or schema-level error — what + # matters is the route gate did not return 401/403. + self.assertNotIn(result.status_code, [status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN]) diff --git a/modules/admin/karrio/server/admin/tests/test_auth.py b/modules/admin/karrio/server/admin/tests/test_auth.py index e942f714b7..44c5aa0da0 100644 --- a/modules/admin/karrio/server/admin/tests/test_auth.py +++ b/modules/admin/karrio/server/admin/tests/test_auth.py @@ -8,19 +8,19 @@ - Invalid / missing input (400 / GraphQL errors) - Cross-user data isolation """ -from django.contrib.auth import get_user_model -from rest_framework.test import APITestCase, APIClient -from rest_framework import status -from karrio.server.user.models import Token -from karrio.server.admin.tests.base import AdminGraphTestCase, Result import karrio.server.providers.models as providers - +from django.contrib.auth import get_user_model +from karrio.server.admin.tests.base import AdminGraphTestCase +from karrio.server.user.models import Token +from rest_framework import status +from rest_framework.test import APIClient, APITestCase # --------------------------------------------------------------------------- # Unauthenticated access # --------------------------------------------------------------------------- + class TestAdminUnauthenticated(APITestCase): """Admin GraphQL endpoint must reject unauthenticated requests.""" @@ -32,6 +32,7 @@ def test_unauthenticated_graphql_returns_error(self): requirement is that rate_sheets data is NOT returned. """ from django.urls import reverse + client = APIClient() # No credentials response = client.post( reverse("karrio.server.admin:admin-graph"), @@ -49,6 +50,7 @@ def test_unauthenticated_graphql_returns_error(self): def test_unauthenticated_mutation_is_rejected(self): """Unauthenticated mutation must not create resources.""" from django.urls import reverse + client = APIClient() response = client.post( reverse("karrio.server.admin:admin-graph"), @@ -73,14 +75,13 @@ def test_unauthenticated_mutation_is_rejected(self): # Non-admin (non-staff) user access # --------------------------------------------------------------------------- + class TestAdminNonStaffUser(APITestCase): """Regular (non-staff) users must not access admin endpoints.""" @classmethod def setUpTestData(cls): - cls.regular_user = get_user_model().objects.create_user( - email="regular@example.com", password="test" - ) + cls.regular_user = get_user_model().objects.create_user(email="regular@example.com", password="test") cls.regular_token = Token.objects.create(user=cls.regular_user, test_mode=False) def setUp(self): @@ -89,20 +90,21 @@ def setUp(self): def test_non_staff_cannot_query_admin_rate_sheets(self): from django.urls import reverse + response = self.client.post( reverse("karrio.server.admin:admin-graph"), {"query": "{ rate_sheets { edges { node { id } } } }"}, ) # Must either reject outright or return a permission error in GraphQL errors data = response.json() if response.status_code == 200 else {} - non_staff_blocked = ( - response.status_code in [status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN] - or (data.get("errors") is not None and len(data["errors"]) > 0) + non_staff_blocked = response.status_code in [status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN] or ( + data.get("errors") is not None and len(data["errors"]) > 0 ) self.assertTrue(non_staff_blocked, f"Non-staff got unrestricted access: {response.status_code} {data}") def test_non_staff_cannot_create_system_connection(self): from django.urls import reverse + response = self.client.post( reverse("karrio.server.admin:admin-graph"), { @@ -118,9 +120,8 @@ def test_non_staff_cannot_create_system_connection(self): }, ) data = response.json() if response.status_code == 200 else {} - non_staff_blocked = ( - response.status_code in [status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN] - or (data.get("errors") is not None and len(data["errors"]) > 0) + non_staff_blocked = response.status_code in [status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN] or ( + data.get("errors") is not None and len(data["errors"]) > 0 ) self.assertTrue(non_staff_blocked) @@ -129,6 +130,7 @@ def test_non_staff_cannot_create_system_connection(self): # Invalid input validation # --------------------------------------------------------------------------- + class TestAdminRateSheetValidation(AdminGraphTestCase): """Admin mutations must reject invalid or missing input.""" @@ -166,8 +168,7 @@ def test_create_rate_sheet_invalid_carrier_name_returns_error(self): }, ) has_error = ( - result.data.get("errors") is not None - or result.data.get("data", {}).get("create_rate_sheet") is None + result.data.get("errors") is not None or result.data.get("data", {}).get("create_rate_sheet") is None ) self.assertTrue(has_error, "Expected error for unknown carrier_name") @@ -186,10 +187,7 @@ def test_add_zone_invalid_rate_sheet_id_returns_error(self): } }, ) - has_error = ( - result.data.get("errors") is not None - or result.data.get("data", {}).get("add_shared_zone") is None - ) + has_error = result.data.get("errors") is not None or result.data.get("data", {}).get("add_shared_zone") is None self.assertTrue(has_error, "Expected error for nonexistent rate_sheet_id") def test_delete_nonexistent_rate_sheet_returns_error(self): @@ -203,8 +201,7 @@ def test_delete_nonexistent_rate_sheet_returns_error(self): variables={"id": "nonexistent-rate-sheet-id"}, ) has_error = ( - result.data.get("errors") is not None - or result.data.get("data", {}).get("delete_rate_sheet") is None + result.data.get("errors") is not None or result.data.get("data", {}).get("delete_rate_sheet") is None ) self.assertTrue(has_error, "Expected error when deleting nonexistent rate sheet") @@ -285,10 +282,7 @@ def test_create_markup_with_negative_amount_returns_error(self): } }, ) - has_error = ( - result.data.get("errors") is not None - or result.data.get("data", {}).get("create_markup") is None - ) + has_error = result.data.get("errors") is not None or result.data.get("data", {}).get("create_markup") is None self.assertTrue(has_error, "Expected validation error for negative markup amount") def test_delete_nonexistent_markup_returns_error(self): @@ -301,10 +295,7 @@ def test_delete_nonexistent_markup_returns_error(self): operation_name="delete_markup", variables={"id": "nonexistent-markup-id"}, ) - has_error = ( - result.data.get("errors") is not None - or result.data.get("data", {}).get("delete_markup") is None - ) + has_error = result.data.get("errors") is not None or result.data.get("data", {}).get("delete_markup") is None self.assertTrue(has_error, "Expected error when deleting nonexistent markup") @@ -312,6 +303,7 @@ def test_delete_nonexistent_markup_returns_error(self): # Cross-user data isolation # --------------------------------------------------------------------------- + class TestAdminDataIsolation(APITestCase): """ Verify that a staff user from org A cannot modify system resources @@ -321,20 +313,17 @@ class TestAdminDataIsolation(APITestCase): @classmethod def setUpTestData(cls): from django.urls import reverse as _reverse + cls.reverse = staticmethod(_reverse) # Admin 1 - cls.admin1 = get_user_model().objects.create_superuser( - "admin1@example.com", "test" - ) + cls.admin1 = get_user_model().objects.create_superuser("admin1@example.com", "test") cls.admin1.is_staff = True cls.admin1.save() cls.token1 = Token.objects.create(user=cls.admin1, test_mode=False) # Admin 2 - cls.admin2 = get_user_model().objects.create_superuser( - "admin2@example.com", "test" - ) + cls.admin2 = get_user_model().objects.create_superuser("admin2@example.com", "test") cls.admin2.is_staff = True cls.admin2.save() cls.token2 = Token.objects.create(user=cls.admin2, test_mode=False) @@ -354,8 +343,8 @@ def _client_for(self, token): return client def _query(self, client, query, variables=None): - import json from django.urls import reverse + response = client.post( reverse("karrio.server.admin:admin-graph"), {"query": query, "variables": variables}, @@ -371,10 +360,7 @@ def test_admin2_can_read_system_connections(self): ) self.assertEqual(code, 200) self.assertIsNone(data.get("errors")) - ids = [ - edge["node"]["carrier_id"] - for edge in data["data"]["system_carrier_connections"]["edges"] - ] + ids = [edge["node"]["carrier_id"] for edge in data["data"]["system_carrier_connections"]["edges"]] self.assertIn("dhl_isolation_test", ids) def test_token_for_admin1_cannot_be_used_by_admin2(self): @@ -385,8 +371,8 @@ def test_token_for_admin1_cannot_be_used_by_admin2(self): client_fake.credentials(HTTP_AUTHORIZATION="Token " + self.token1.key) # Make a query that would reveal admin1's context; the test itself # just verifies the token resolves to admin1's user in the response - import json from django.urls import reverse + response = client_fake.post( reverse("karrio.server.admin:admin-graph"), {"query": "{ user { email } }"}, diff --git a/modules/admin/karrio/server/admin/tests/test_connections.py b/modules/admin/karrio/server/admin/tests/test_connections.py index 1e048f496c..b6b956ddfc 100644 --- a/modules/admin/karrio/server/admin/tests/test_connections.py +++ b/modules/admin/karrio/server/admin/tests/test_connections.py @@ -1,9 +1,7 @@ -from unittest.mock import ANY +import karrio.server.providers.models as providers from django.contrib.auth import get_user_model -from rest_framework.test import APIClient from karrio.server.admin.tests.base import AdminGraphTestCase -from karrio.server.user.models import Token -import karrio.server.providers.models as providers + class TestAdminCarrierConnections(AdminGraphTestCase): """Tests for Admin Carrier Connection queries and mutations. @@ -26,13 +24,22 @@ def setUp(self): created_by=self.user, ) - # Create a system connection (SystemConnection) + # Create a system connection (SystemConnection) with a realistic + # admin-set config so we can assert the config resolver exposes it + # on the admin graph (it's stripped on the tenant graph). self.system_connection = providers.SystemConnection.objects.create( carrier_code="ups", carrier_id="ups_system_account", credentials={"api_key": "system_key"}, test_mode=False, active=True, + config={ + "label_type": "PDF", + "default_billing_number": "11111111110000", + "service_billing_numbers": [ + {"id": "sbn_a", "service": "dhl_parcel_de_paket", "billing_number": "22222222220101"}, + ], + }, ) def test_query_system_carrier_connections(self): @@ -61,6 +68,58 @@ def test_query_system_carrier_connections(self): connection_ids = [e["node"]["carrier_id"] for e in edges] self.assertIn("ups_system_account", connection_ids) + def test_filter_system_connections_by_carrier_name(self): + """`carrier_name` is a list filter — it must match ANY of the supplied + carrier codes (carrier_code__in), not exact-match against the whole list + (which silently returned nothing). Regression for the empty + upload-invoice connection list (issue #797).""" + providers.SystemConnection.objects.create( + carrier_code="dhl_parcel_de", + carrier_id="dhl_system_account", + credentials={"api_key": "dhl_key"}, + test_mode=False, + active=True, + ) + providers.SystemConnection.objects.create( + carrier_code="dpd", + carrier_id="dpd_system_account", + credentials={"api_key": "dpd_key"}, + test_mode=False, + active=True, + ) + + query = """ + query q($filter: CarrierFilter) { + system_carrier_connections(filter: $filter) { + edges { node { carrier_id carrier_name } } + } + } + """ + + def carrier_ids(filter_value): + response = self.query(query, operation_name="q", variables={"filter": filter_value}) + self.assertResponseNoErrors(response) + return {e["node"]["carrier_id"] for e in response.data["data"]["system_carrier_connections"]["edges"]} + + # Single-element list — only the matching carrier comes back. + self.assertEqual(carrier_ids({"carrier_name": ["dhl_parcel_de"]}), {"dhl_system_account"}) + + # Multi-element list — every requested carrier comes back (the bug: + # this returned nothing before the carrier_code__in fix). + self.assertEqual( + carrier_ids({"carrier_name": ["dhl_parcel_de", "dpd"]}), + {"dhl_system_account", "dpd_system_account"}, + ) + + # Non-matching carrier — empty result, no error. + self.assertEqual(carrier_ids({"carrier_name": ["does_not_exist"]}), set()) + + # Empty list is falsy -> treated as "no carrier filter" -> all returned. + self.assertEqual( + carrier_ids({"carrier_name": []}), + {"ups_system_account", "dhl_system_account", "dpd_system_account"}, + ) + def test_query_single_system_connection(self): """Test querying a specific system connection by ID.""" response = self.query( @@ -82,6 +141,27 @@ def test_query_single_system_connection(self): self.assertEqual(connection["carrier_id"], "ups_system_account") self.assertEqual(connection["carrier_name"], "ups") + def test_admin_graph_returns_system_connection_config(self): + """Admin graph must expose the raw SystemConnection config so staff can + manage billing numbers. (The tenant graph strips it — see base types.)""" + response = self.query( + """ + query q($id: String!) { + system_carrier_connection(id: $id) { config credentials } + } + """, + operation_name="q", + variables={"id": self.system_connection.id}, + ) + self.assertResponseNoErrors(response) + conn = response.data["data"]["system_carrier_connection"] + self.assertIsNotNone(conn["config"]) + self.assertEqual(conn["config"]["default_billing_number"], "11111111110000") + self.assertEqual( + conn["config"]["service_billing_numbers"][0]["billing_number"], + "22222222220101", + ) + def test_create_system_carrier_connection(self): """Test creating a system carrier connection through admin API.""" response = self.query( @@ -112,9 +192,7 @@ def test_create_system_carrier_connection(self): ) self.assertResponseNoErrors(response) - connection = response.data["data"]["create_system_carrier_connection"][ - "connection" - ] + connection = response.data["data"]["create_system_carrier_connection"]["connection"] self.assertEqual(connection["carrier_id"], "usps_system_account") self.assertEqual(connection["carrier_name"], "usps") self.assertTrue(connection["active"]) @@ -142,9 +220,7 @@ def test_update_system_carrier_connection(self): ) self.assertResponseNoErrors(response) - connection = response.data["data"]["update_system_carrier_connection"][ - "connection" - ] + connection = response.data["data"]["update_system_carrier_connection"]["connection"] self.assertEqual(connection["carrier_id"], "ups_system_updated") def test_delete_system_connection(self): @@ -175,9 +251,8 @@ def test_delete_system_connection(self): response.data["data"]["delete_system_carrier_connection"]["id"], to_delete.id, ) - self.assertFalse( - providers.SystemConnection.objects.filter(id=to_delete.id).exists() - ) + self.assertFalse(providers.SystemConnection.objects.filter(id=to_delete.id).exists()) + class TestAdminBrokeredConnections(AdminGraphTestCase): """Tests for BrokeredConnection edge cases. @@ -223,9 +298,7 @@ def test_brokered_connection_inherits_credentials(self): ) # The brokered connection should inherit system credentials - self.assertEqual( - brokered.system_connection.credentials.get("api_key"), "system_key" - ) + self.assertEqual(brokered.system_connection.credentials.get("api_key"), "system_key") def test_brokered_connection_with_config_overrides(self): """Test brokered connection config overrides merge with system config.""" @@ -239,12 +312,8 @@ def test_brokered_connection_with_config_overrides(self): created_by=self.user, ) - self.assertEqual( - brokered.config_overrides.get("account_number"), "override_account" - ) - self.assertEqual( - brokered.config_overrides.get("meter_number"), "override_meter" - ) + self.assertEqual(brokered.config_overrides.get("account_number"), "override_account") + self.assertEqual(brokered.config_overrides.get("meter_number"), "override_meter") def test_brokered_connection_deletion_preserves_system(self): """Test that deleting brokered connection does not delete system connection.""" @@ -259,13 +328,9 @@ def test_brokered_connection_deletion_preserves_system(self): brokered.delete() # Brokered connection should be deleted - self.assertFalse( - providers.BrokeredConnection.objects.filter(id=brokered_id).exists() - ) + self.assertFalse(providers.BrokeredConnection.objects.filter(id=brokered_id).exists()) # System connection should still exist - self.assertTrue( - providers.SystemConnection.objects.filter(id=system_id).exists() - ) + self.assertTrue(providers.SystemConnection.objects.filter(id=system_id).exists()) def test_system_connection_deletion_cascades_to_brokered(self): """Test that deleting system connection cascades to brokered connections.""" @@ -279,16 +344,12 @@ def test_system_connection_deletion_cascades_to_brokered(self): self.system_connection.delete() # Brokered connection should also be deleted (cascade) - self.assertFalse( - providers.BrokeredConnection.objects.filter(id=brokered_id).exists() - ) + self.assertFalse(providers.BrokeredConnection.objects.filter(id=brokered_id).exists()) def test_multiple_users_can_have_brokered_connections_to_same_system(self): """Test that multiple users can create brokered connections to same system.""" # Create another user - other_user = get_user_model().objects.create_user( - email="other@example.com", password="test" - ) + other_user = get_user_model().objects.create_user(email="other@example.com", password="test") # Both users create brokered connections to same system brokered1 = providers.BrokeredConnection.objects.create( @@ -306,12 +367,8 @@ def test_multiple_users_can_have_brokered_connections_to_same_system(self): # Both should exist with different overrides self.assertNotEqual(brokered1.id, brokered2.id) - self.assertEqual( - brokered1.config_overrides.get("account_number"), "user1_account" - ) - self.assertEqual( - brokered2.config_overrides.get("account_number"), "user2_account" - ) + self.assertEqual(brokered1.config_overrides.get("account_number"), "user1_account") + self.assertEqual(brokered2.config_overrides.get("account_number"), "user2_account") def test_brokered_connection_inherits_test_mode(self): """Test that brokered connection inherits test_mode from system connection.""" @@ -328,5 +385,3 @@ def test_brokered_connection_inherits_test_mode(self): # test_mode is inherited from system connection self.assertEqual(brokered.test_mode, self.system_connection.test_mode) self.assertFalse(brokered.test_mode) - - diff --git a/modules/admin/karrio/server/admin/tests/test_create_user.py b/modules/admin/karrio/server/admin/tests/test_create_user.py new file mode 100644 index 0000000000..c61ec9ca6e --- /dev/null +++ b/modules/admin/karrio/server/admin/tests/test_create_user.py @@ -0,0 +1,66 @@ +"""Admin create_user mutation regression tests. + +Covers the regression where admin create_user failed with +"Signup is not allowed" whenever the public ALLOW_SIGNUP setting +was False — the gate belongs at the tenant RegisterUserMutation, +not in the shared SignUpForm. +""" + +from unittest.mock import patch + +from django.contrib.auth import get_user_model +from karrio.server.admin.tests.base import AdminGraphTestCase + +CREATE_USER_MUTATION = """ +mutation create_user($data: CreateUserMutationInput!) { + create_user(input: $data) { + user { + id + email + full_name + is_staff + is_active + is_superuser + } + errors { + field + messages + } + } +} +""" + + +class TestAdminCreateUser(AdminGraphTestCase): + @patch("karrio.server.conf.settings.ALLOW_SIGNUP", False) + def test_create_staff_user_succeeds_when_public_signup_disabled(self): + """Admin create_user must work even when ALLOW_SIGNUP is False. + + Regression: the SignUpForm.save() ALLOW_SIGNUP gate was inherited by + the admin CreateUserForm and rejected staff creation. The gate now + lives only at the tenant RegisterUserMutation layer. + """ + result = self.query( + CREATE_USER_MUTATION, + operation_name="create_user", + variables={ + "data": { + "email": "new-staff@example.com", + "full_name": "New Staff", + "password1": "TestPassword123!", + "password2": "TestPassword123!", + "is_staff": True, + "is_active": True, + } + }, + ) + + self.assertResponseNoErrors(result) + user = result.data["data"]["create_user"]["user"] + self.assertEqual(user["email"], "new-staff@example.com") + self.assertTrue(user["is_staff"]) + self.assertTrue(user["is_active"]) + + created = get_user_model().objects.get(email="new-staff@example.com") + self.assertTrue(created.is_staff) + self.assertTrue(created.is_active) diff --git a/modules/admin/karrio/server/admin/tests/test_markups.py b/modules/admin/karrio/server/admin/tests/test_markups.py index 2f7bce0353..7b7933b5d8 100644 --- a/modules/admin/karrio/server/admin/tests/test_markups.py +++ b/modules/admin/karrio/server/admin/tests/test_markups.py @@ -1,9 +1,6 @@ -from unittest.mock import ANY -from django.contrib.auth import get_user_model -from rest_framework.test import APITestCase as BaseAPITestCase, APIClient from karrio.server.admin.tests.base import AdminGraphTestCase -from karrio.server.user.models import Token -import karrio.server.providers.models as providers +from rest_framework.test import APITestCase as BaseAPITestCase + class TestAdminMarkups(AdminGraphTestCase): """Tests for Admin Markup CRUD operations including meta field.""" @@ -422,12 +419,8 @@ def test_delete_markup(self): ) self.assertResponseNoErrors(response) - self.assertEqual( - response.data["data"]["delete_markup"]["id"], to_delete.id - ) - self.assertFalse( - self.pricing.Markup.objects.filter(id=to_delete.id).exists() - ) + self.assertEqual(response.data["data"]["delete_markup"]["id"], to_delete.id) + self.assertFalse(self.pricing.Markup.objects.filter(id=to_delete.id).exists()) def test_query_markups_returns_all(self): """Test querying markups returns all markups.""" @@ -858,9 +851,9 @@ class TestMarkupFeatureGating(BaseAPITestCase): def setUp(self): super().setUp() - import karrio.server.pricing.models as pricing - import karrio.server.core.datatypes as datatypes import karrio.core.models as karrio_models + import karrio.server.core.datatypes as datatypes + import karrio.server.pricing.models as pricing self.pricing = pricing self.datatypes = datatypes @@ -972,9 +965,7 @@ def test_address_validation_markup_feature_gate(self): rate_with = self.make_rate(service_features=["address_validation"]) rate_without = self.make_rate(service_features=["tracked"]) - result_applies = self.address_validation_markup._is_applicable( - rate_with, options={"address_validation": True} - ) + result_applies = self.address_validation_markup._is_applicable(rate_with, options={"address_validation": True}) result_no_feature = self.address_validation_markup._is_applicable( rate_without, options={"address_validation": True} ) @@ -994,15 +985,9 @@ def test_surcharge_with_feature_gate_is_conditional(self): rate_with = self.make_rate(service_features=["fuel_surcharge"]) rate_without = self.make_rate(service_features=["tracked"]) - result_applies = self.surcharge_markup._is_applicable( - rate_with, options={"fuel_surcharge": True} - ) - result_no_feature = self.surcharge_markup._is_applicable( - rate_without, options={"fuel_surcharge": True} - ) - result_no_option = self.surcharge_markup._is_applicable( - rate_with, options={} - ) + result_applies = self.surcharge_markup._is_applicable(rate_with, options={"fuel_surcharge": True}) + result_no_feature = self.surcharge_markup._is_applicable(rate_without, options={"fuel_surcharge": True}) + result_no_option = self.surcharge_markup._is_applicable(rate_with, options={}) self.assertTrue(result_applies) self.assertFalse(result_no_feature) diff --git a/modules/admin/karrio/server/admin/tests/test_rate_sheets.py b/modules/admin/karrio/server/admin/tests/test_rate_sheets.py index 6f50f74db3..3a66ed060e 100644 --- a/modules/admin/karrio/server/admin/tests/test_rate_sheets.py +++ b/modules/admin/karrio/server/admin/tests/test_rate_sheets.py @@ -1,11 +1,14 @@ import json -from unittest.mock import ANY + +import karrio.server.providers.models as providers from django.contrib.auth import get_user_model +from django.db import connection as db_connection +from django.test.utils import CaptureQueriesContext from django.urls import reverse -from rest_framework.test import APIClient from karrio.server.admin.tests.base import AdminGraphTestCase, Result from karrio.server.user.models import Token -import karrio.server.providers.models as providers +from rest_framework.test import APIClient + class TestAdminRateSheets(AdminGraphTestCase): """Tests for Admin Rate Sheet CRUD operations.""" @@ -19,7 +22,6 @@ def setUp(self): name="Admin Test Rate Sheet", carrier_name="dhl_parcel_de", slug="admin_test_rate_sheet", - zones=[ { "id": "zone_1", @@ -101,6 +103,23 @@ def test_query_rate_sheets(self): self.assertEqual(len(edges), 1) self.assertEqual(edges[0]["node"]["name"], "Admin Test Rate Sheet") + def test_query_rate_sheets_does_not_load_service_rates(self): + """The list query must not load the heavy service_rates column.""" + with CaptureQueriesContext(db_connection) as ctx: + response = self.query( + """ + query get_rate_sheets { + rate_sheets { edges { node { id name carrier_name slug } } } + } + """, + operation_name="get_rate_sheets", + ) + self.assertResponseNoErrors(response) + sheet_selects = [q["sql"] for q in ctx.captured_queries if 'from "system-rate-sheet"' in q["sql"].lower()] + self.assertTrue(sheet_selects, "expected a system-rate-sheet SELECT") + for sql in sheet_selects: + self.assertNotIn("service_rates", sql, "list must defer the heavy service_rates column") + def test_create_rate_sheet(self): """Test creating a new rate sheet through admin API.""" response = self.query( @@ -181,6 +200,79 @@ def test_update_rate_sheet(self): "Updated Admin Rate Sheet", ) + def test_update_rate_sheet_accepts_all_service_level_feature_flags(self): + """Regression: the admin Service Editor dialog sends a 14-flag + features object (tracked, b2c, b2b, signature, insurance, express, + dangerous_goods, saturday_delivery, sunday_delivery, multicollo, + neighbor_delivery, labelless, notification, address_validation). + ServiceLevelFeaturesInput must accept all three of + labelless / notification / address_validation — otherwise any save + from the admin UI fails with + "Field 'labelless' is not defined by type 'ServiceLevelFeaturesInput'".""" + response = self.query( + """ + mutation update_rate_sheet($data: UpdateRateSheetMutationInput!) { + update_rate_sheet(input: $data) { + errors { field messages } + rate_sheet { + id + services { + id + features { + tracked + labelless + notification + address_validation + } + } + } + } + } + """, + operation_name="update_rate_sheet", + variables={ + "data": { + "id": self.rate_sheet.id, + "services": [ + { + "service_name": "DHL Paket", + "service_code": "dhl_parcel_de_paket", + "carrier_service_code": "V01PAK", + "currency": "EUR", + "features": { + "tracked": True, + "b2c": True, + "b2b": True, + "signature": False, + "insurance": False, + "express": False, + "dangerous_goods": False, + "saturday_delivery": False, + "sunday_delivery": False, + "multicollo": False, + "neighbor_delivery": False, + "labelless": True, + "notification": True, + "address_validation": False, + "first_mile": "pickup_dropoff", + "last_mile": "home_delivery", + "form_factor": "parcel", + "shipment_type": "outbound", + }, + } + ], + } + }, + ) + + self.assertResponseNoErrors(response) + result = response.data["data"]["update_rate_sheet"] + self.assertIsNone(result.get("errors")) + service = next(s for s in result["rate_sheet"]["services"] if s["features"] is not None) + self.assertTrue(service["features"]["labelless"]) + self.assertTrue(service["features"]["notification"]) + self.assertFalse(service["features"]["address_validation"]) + def test_delete_rate_sheet(self): """Test deleting a rate sheet through admin API.""" # Create a rate sheet to delete @@ -188,7 +280,6 @@ def test_delete_rate_sheet(self): name="To Be Deleted", carrier_name="ups", slug="to_be_deleted", - created_by=self.user, ) @@ -209,6 +300,188 @@ def test_delete_rate_sheet(self): self.assertFalse(providers.SystemRateSheet.objects.filter(id=new_sheet.id).exists()) +class TestAdminRateSheetServiceTags(AdminGraphTestCase): + """Admin GraphQL CRUD on `ServiceLevel.tags` and derived-badge resolution. + + Covers PR #662 surfaces that the unit tests don't exercise: + - tag_definitions query (registry shape over the admin graph) + - tags input on update_rate_sheet (write path through ServiceLevelModelSerializer.validate_tags) + - tags + derived_badges read fields on the service node + - validate_tags rejects unknown keys + invalid enum values + """ + + def setUp(self): + super().setUp() + self.rate_sheet = providers.SystemRateSheet.objects.create( + name="Tags Rate Sheet", + carrier_name="dhl_parcel_de", + slug="tags_rate_sheet", + created_by=self.user, + ) + self.service = providers.ServiceLevel.objects.create( + service_name="DHL Paket", + service_code="dhl_parcel_de_paket", + carrier_service_code="V01PAK", + currency="EUR", + active=True, + created_by=self.user, + ) + self.rate_sheet.services.add(self.service) + + def test_tag_definitions_query_returns_registry(self): + """tag_definitions mirrors providers.tags.TAG_REGISTRY through the admin graph.""" + from karrio.server.providers.tags import TAG_REGISTRY + + response = self.query( + """ + query GetTagDefinitions { + tag_definitions { + key + kind + values + range + default + label_key + value_label_key_prefix + filterable + sortable + badges { value badge { label_key style priority } } + } + } + """, + operation_name="GetTagDefinitions", + ) + self.assertResponseNoErrors(response) + defs = response.data["data"]["tag_definitions"] + keys = {d["key"] for d in defs} + self.assertEqual(keys, set(TAG_REGISTRY.keys())) + # display_priority is sortable + has a range; recommended is filterable bool. + by_key = {d["key"]: d for d in defs} + self.assertEqual(by_key["display_priority"]["kind"], "int") + self.assertTrue(by_key["display_priority"]["sortable"]) + self.assertEqual(by_key["display_priority"]["range"], [0, 100]) + self.assertEqual(by_key["recommended"]["kind"], "bool") + self.assertFalse(by_key["recommended"]["sortable"]) + # recommendation_type carries the 5 badge entries. + type_badges = {b["value"] for b in by_key["recommendation_type"]["badges"]} + self.assertEqual(type_badges, {"recommended", "best_price", "fastest", "eco", "new"}) + + def test_update_rate_sheet_persists_service_tags(self): + """update_rate_sheet routes `tags` through validate_tags and stores them on ServiceLevel.""" + expected_tags = { + "recommended": True, + "recommendation_category": "home_delivery", + "recommendation_type": "best_price", + "display_priority": 85, + "surface_visibility": "default", + } + response = self.query( + """ + mutation update_rate_sheet($data: UpdateRateSheetMutationInput!) { + update_rate_sheet(input: $data) { + errors { field messages } + rate_sheet { + services { id service_name tags } + } + } + } + """, + operation_name="update_rate_sheet", + variables={ + "data": { + "id": self.rate_sheet.id, + "services": [ + { + "service_name": "DHL Paket", + "service_code": "dhl_parcel_de_paket", + "carrier_service_code": "V01PAK", + "currency": "EUR", + "tags": expected_tags, + } + ], + } + }, + ) + self.assertResponseNoErrors(response) + result = response.data["data"]["update_rate_sheet"] + self.assertIsNone(result.get("errors")) + svc = next(s for s in result["rate_sheet"]["services"] if s["tags"]) + self.assertDictEqual(svc["tags"], expected_tags) + + # DB sanity: the persisted ServiceLevel row carries the same tag map. + # Match by id from the response (the upsert may rebind by service_name). + persisted = providers.ServiceLevel.objects.get(id=svc["id"]) + self.assertDictEqual(persisted.tags or {}, expected_tags) + + def test_query_rate_sheet_resolves_derived_badges(self): + """ServiceLevelType.derived_badges picks the highest-priority registry badge.""" + self.service.tags = { + "recommended": True, + "recommendation_type": "best_price", + } + self.service.save(update_fields=["tags"]) + + response = self.query( + """ + query get_rate_sheet($id: String!) { + rate_sheet(id: $id) { + services { id tags derived_badges { label_key style priority } } + } + } + """, + operation_name="get_rate_sheet", + variables={"id": self.rate_sheet.id}, + ) + self.assertResponseNoErrors(response) + svc = next(s for s in response.data["data"]["rate_sheet"]["services"] if s["id"] == self.service.id) + # best_price (priority=60) outranks recommended (priority=50). + self.assertEqual(len(svc["derived_badges"]), 1) + self.assertEqual(svc["derived_badges"][0]["label_key"], "tags.badge.best_price") + self.assertEqual(svc["derived_badges"][0]["priority"], 60) + + def test_invalid_tag_keys_and_values_are_rejected(self): + """Unknown keys + out-of-range ints raise top-level GraphQL errors via validate_tags. + + ServiceLevelModelSerializer.validate_tags raises ValidationError, which + Strawberry/DRF surface as a top-level `errors[*]` entry (not the + mutation's structured `errors` payload). Both messages must appear. + """ + response = self.query( + """ + mutation update_rate_sheet($data: UpdateRateSheetMutationInput!) { + update_rate_sheet(input: $data) { + rate_sheet { id } + } + } + """, + operation_name="update_rate_sheet", + variables={ + "data": { + "id": self.rate_sheet.id, + "services": [ + { + "service_name": "DHL Paket", + "service_code": "dhl_parcel_de_paket", + "carrier_service_code": "V01PAK", + "currency": "EUR", + "tags": {"display_priority": 999, "bogus_key": "x"}, + } + ], + } + }, + ) + errors = response.data.get("errors") or [] + self.assertTrue(errors, "Expected top-level GraphQL errors from validate_tags") + flat = " ".join(e.get("message", "") for e in errors) + self.assertIn("display_priority", flat) + self.assertIn("bogus_key", flat) + self.assertIn("out of range", flat) + # The bad tags must not have been persisted. + self.service.refresh_from_db() + self.assertNotIn("bogus_key", self.service.tags or {}) + self.assertNotEqual((self.service.tags or {}).get("display_priority"), 999) + + class TestAdminRateSheetCrossAdminAccess(AdminGraphTestCase): """Tests that any staff admin can read/update system ratesheets, regardless of which admin created them and regardless of org membership.""" @@ -217,19 +490,17 @@ def setUp(self): super().setUp() # Creates self.user (admin1) + self.organization # Create a second independent admin with their own org - self.admin2 = get_user_model().objects.create_superuser( - "admin2@example.com", "test2" - ) + self.admin2 = get_user_model().objects.create_superuser("admin2@example.com", "test2") self.admin2.is_staff = True self.admin2.save() self.token2 = Token.objects.create(user=self.admin2, test_mode=False) from django.conf import settings + if settings.MULTI_ORGANIZATIONS: from karrio.server.orgs.models import Organization, TokenLink - self.org2 = Organization.objects.create( - name="Second Organization", slug="second-org" - ) + + self.org2 = Organization.objects.create(name="Second Organization", slug="second-org") owner2 = self.org2.add_user(self.admin2, is_admin=True) self.org2.change_owner(owner2) self.org2.save() @@ -240,13 +511,11 @@ def setUp(self): name="Cross Admin Rate Sheet", carrier_name="dhl_parcel_de", slug="cross_admin_rate_sheet", - created_by=self.user, ) def query_as_admin2(self, query, operation_name=None, variables=None): """Execute a GraphQL query authenticated as admin2.""" - from rest_framework.test import APIClient client = APIClient() client.credentials(HTTP_AUTHORIZATION="Token " + self.token2.key) url = reverse("karrio.server.admin:admin-graph") @@ -299,6 +568,7 @@ def test_admin2_can_update_system_ratesheet_created_by_admin1(self): def test_system_ratesheet_has_no_org_link_after_create(self): """System ratesheets must not be linked to any org, regardless of who created them.""" from django.conf import settings + if not settings.MULTI_ORGANIZATIONS: self.skipTest("MULTI_ORGANIZATIONS is disabled") @@ -352,7 +622,6 @@ def setUp(self): name="Zone Test Sheet", carrier_name="ups", slug="zone_test_sheet", - zones=[ { "id": "zone_1", @@ -483,7 +752,6 @@ def setUp(self): name="Surcharge Test Sheet", carrier_name="ups", slug="surcharge_test_sheet", - surcharges=[ { "id": "surch_1", @@ -528,9 +796,7 @@ def test_add_shared_surcharge(self): ) self.assertResponseNoErrors(response) - surcharges = response.data["data"]["add_shared_surcharge"]["rate_sheet"][ - "surcharges" - ] + surcharges = response.data["data"]["add_shared_surcharge"]["rate_sheet"]["surcharges"] self.assertEqual(len(surcharges), 2) self.assertEqual(surcharges[1]["name"], "Handling Fee") self.assertEqual(surcharges[1]["amount"], 5.0) @@ -565,9 +831,7 @@ def test_update_shared_surcharge(self): ) self.assertResponseNoErrors(response) - surcharges = response.data["data"]["update_shared_surcharge"]["rate_sheet"][ - "surcharges" - ] + surcharges = response.data["data"]["update_shared_surcharge"]["rate_sheet"]["surcharges"] self.assertEqual(surcharges[0]["name"], "Updated Fuel Surcharge") self.assertEqual(surcharges[0]["amount"], 15.0) @@ -607,9 +871,7 @@ def test_delete_shared_surcharge(self): ) self.assertResponseNoErrors(response) - surcharges = response.data["data"]["delete_shared_surcharge"]["rate_sheet"][ - "surcharges" - ] + surcharges = response.data["data"]["delete_shared_surcharge"]["rate_sheet"]["surcharges"] self.assertEqual(len(surcharges), 1) self.assertEqual(surcharges[0]["id"], "surch_1") @@ -652,9 +914,7 @@ def test_batch_update_surcharges(self): ) self.assertResponseNoErrors(response) - surcharges = response.data["data"]["batch_update_surcharges"]["rate_sheet"][ - "surcharges" - ] + surcharges = response.data["data"]["batch_update_surcharges"]["rate_sheet"]["surcharges"] self.assertEqual(len(surcharges), 2) @@ -668,7 +928,6 @@ def setUp(self): name="Service Rate Test Sheet", carrier_name="ups", slug="service_rate_test_sheet", - zones=[ {"id": "zone_1", "label": "Zone 1", "country_codes": ["US"]}, {"id": "zone_2", "label": "Zone 2", "country_codes": ["CA"]}, @@ -717,9 +976,7 @@ def test_update_service_rate(self): ) self.assertResponseNoErrors(response) - rates = response.data["data"]["update_service_rate"]["rate_sheet"][ - "service_rates" - ] + rates = response.data["data"]["update_service_rate"]["rate_sheet"]["service_rates"] self.assertEqual(len(rates), 1) self.assertEqual(rates[0]["rate"], 15.99) self.assertEqual(rates[0]["cost"], 12.00) @@ -764,9 +1021,7 @@ def test_batch_update_service_rates(self): ) self.assertResponseNoErrors(response) - rates = response.data["data"]["batch_update_service_rates"]["rate_sheet"][ - "service_rates" - ] + rates = response.data["data"]["batch_update_service_rates"]["rate_sheet"]["service_rates"] self.assertEqual(len(rates), 2) @@ -780,7 +1035,6 @@ def setUp(self): name="Assignment Test Sheet", carrier_name="ups", slug="assignment_test_sheet", - zones=[ {"id": "zone_1", "label": "Zone 1", "country_codes": ["US"]}, {"id": "zone_2", "label": "Zone 2", "country_codes": ["CA"]}, @@ -839,9 +1093,7 @@ def test_update_service_zone_ids(self): ) self.assertResponseNoErrors(response) - services = response.data["data"]["update_service_zone_ids"]["rate_sheet"][ - "services" - ] + services = response.data["data"]["update_service_zone_ids"]["rate_sheet"]["services"] self.assertEqual(services[0]["zone_ids"], ["zone_1", "zone_2"]) def test_update_service_surcharge_ids(self): @@ -870,9 +1122,7 @@ def test_update_service_surcharge_ids(self): ) self.assertResponseNoErrors(response) - services = response.data["data"]["update_service_surcharge_ids"]["rate_sheet"][ - "services" - ] + services = response.data["data"]["update_service_surcharge_ids"]["rate_sheet"]["services"] self.assertEqual(services[0]["surcharge_ids"], ["surch_1", "surch_2"]) @@ -886,7 +1136,6 @@ def setUp(self): name="Service Test Sheet", carrier_name="ups", slug="service_test_sheet", - created_by=self.user, ) @@ -923,13 +1172,9 @@ def test_delete_rate_sheet_service(self): ) self.assertResponseNoErrors(response) - services = response.data["data"]["delete_rate_sheet_service"]["rate_sheet"][ - "services" - ] + services = response.data["data"]["delete_rate_sheet_service"]["rate_sheet"]["services"] self.assertEqual(len(services), 0) - self.assertFalse( - providers.ServiceLevel.objects.filter(id=self.service.id).exists() - ) + self.assertFalse(providers.ServiceLevel.objects.filter(id=self.service.id).exists()) class TestAdminDeleteServiceRate(AdminGraphTestCase): @@ -942,7 +1187,6 @@ def setUp(self): name="Delete Rate Test Sheet", carrier_name="ups", slug="delete_rate_test_sheet", - zones=[ {"id": "zone_1", "label": "Zone 1", "country_codes": ["US"]}, {"id": "zone_2", "label": "Zone 2", "country_codes": ["CA"]}, @@ -963,7 +1207,14 @@ def setUp(self): self.rate_sheet.service_rates = [ {"service_id": self.service.id, "zone_id": "zone_1", "rate": 10.00, "cost": 8.00}, {"service_id": self.service.id, "zone_id": "zone_2", "rate": 15.00, "cost": 12.00}, - {"service_id": self.service.id, "zone_id": "zone_1", "rate": 20.00, "cost": 16.00, "min_weight": 0.0, "max_weight": 5.0}, + { + "service_id": self.service.id, + "zone_id": "zone_1", + "rate": 20.00, + "cost": 16.00, + "min_weight": 0.0, + "max_weight": 5.0, + }, ] self.rate_sheet.save() @@ -1033,8 +1284,7 @@ def test_delete_service_rate_with_weight_range(self): self.assertResponseNoErrors(response) rates = response.data["data"]["delete_service_rate"]["rate_sheet"]["service_rates"] zone_1_weighted = [ - r for r in rates - if r["zone_id"] == "zone_1" and r.get("min_weight") == 0 and r.get("max_weight") == 5 + r for r in rates if r["zone_id"] == "zone_1" and r.get("min_weight") == 0 and r.get("max_weight") == 5 ] self.assertEqual(len(zone_1_weighted), 0) # The base zone_1 rate without weight should remain @@ -1095,7 +1345,6 @@ def setUp(self): name="Weight Range Test Sheet", carrier_name="ups", slug="weight_range_test_sheet", - zones=[ {"id": "zone_1", "label": "Zone 1", "country_codes": ["US"]}, {"id": "zone_2", "label": "Zone 2", "country_codes": ["CA"]}, @@ -1327,7 +1576,6 @@ def setUp(self): name="Query Verify Test Sheet", carrier_name="ups", slug="query_verify_test_sheet", - zones=[ {"id": "zone_1", "label": "Zone 1", "country_codes": ["US"]}, ], @@ -1639,7 +1887,6 @@ def setUp(self): name="Edge Case Sheet", carrier_name="ups", slug="edge_case_sheet", - zones=[], surcharges=[], service_rates=[], @@ -1719,7 +1966,6 @@ def test_delete_rate_sheet_cascades_services(self): name="Cascade Test Sheet", carrier_name="ups", slug="cascade_test_sheet", - created_by=self.user, ) service = providers.ServiceLevel.objects.create( @@ -1819,3 +2065,525 @@ def test_zone_with_empty_arrays(self): self.assertEqual(zone["postal_codes"], []) +class TestAdminRateSheetCSVImport(AdminGraphTestCase): + """Integration tests for CSV import via the admin import endpoint. + + Verifies: + - Dry run returns correct diff against an existing rate sheet + - Confirm import updates rates in the targeted rate sheet + - rate_sheet_id parameter targets the correct sheet (not CSV-derived slug) + """ + + CSV_HEADER = ( + "carrier_name,service_code,carrier_service_code,service_name," + "shipment_type,origin_country,zone_label,country_codes," + "min_weight,max_weight,weight_unit,max_length,max_width,max_height," + "dimension_unit,currency,base_rate,cost,transit_days,transit_time" + ) + + def setUp(self): + super().setUp() + + # Create a rate sheet with a custom slug (different from carrier_name) + self.rate_sheet = providers.SystemRateSheet.objects.create( + name="JTL Test Import Sheet", + carrier_name="dhl_parcel_de", + slug="jtl_test_import_sheet", + zones=[ + {"id": "zone_de", "label": "DE", "country_codes": ["DE"]}, + ], + service_rates=[], + created_by=self.user, + ) + + # Create a service and attach to rate sheet + self.service = providers.ServiceLevel.objects.create( + service_name="DHL Paket", + service_code="dhl_parcel_de_paket", + carrier_service_code="V01PAK", + currency="EUR", + active=True, + zone_ids=["zone_de"], + surcharge_ids=[], + created_by=self.user, + ) + self.rate_sheet.services.add(self.service) + + # Set initial rates + self.rate_sheet.service_rates = [ + { + "service_id": self.service.id, + "zone_id": "zone_de", + "rate": 7.50, + "cost": 4.80, + "min_weight": 0, + "max_weight": 2.001, + }, + { + "service_id": self.service.id, + "zone_id": "zone_de", + "rate": 10.30, + "cost": 8.00, + "min_weight": 5.001, + "max_weight": 10.001, + }, + ] + self.rate_sheet.save() + + self.import_url = reverse("karrio.server.admin:admin-data-import") + + def _make_csv(self, rows): + """Build a CSV bytes object from a list of row strings.""" + import io + + lines = [self.CSV_HEADER] + rows + return io.BytesIO("\n".join(lines).encode("utf-8")) + + def test_dry_run_shows_correct_diff_for_existing_rates(self): + """Dry run with rate_sheet_id should show existing rates as updated, not added.""" + csv_file = self._make_csv( + [ + "dhl_parcel_de,dhl_parcel_de_paket,V01PAK,DHL Paket," + "outbound,DE,DE,DE," + "0,2.001,KG,60,30,15,CM,EUR,99.99,5.0,2,", + ] + ) + csv_file.name = "test-update.csv" + + response = self.client.post( + self.import_url, + { + "resource_type": "rate_sheet", + "dry_run": "true", + "data_file": csv_file, + "rate_sheet_id": self.rate_sheet.id, + }, + format="multipart", + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + print(data) + + self.assertTrue(data["dry_run"]) + self.assertIn("diff", data) + + summary = data["diff"]["summary"] + # The existing rate (7.50 → 99.99) should show as updated, not added + self.assertGreater( + summary["updated"], + 0, + "Should detect existing rate as 'updated' when rate_sheet_id is provided", + ) + + # Verify the diff row has old and new rates + updated_rows = [r for r in data["diff"]["rows"] if r["change"] == "updated"] + self.assertTrue(len(updated_rows) > 0) + first_updated = updated_rows[0] + self.assertEqual(first_updated["new_rate"], 99.99) + self.assertEqual(first_updated["old_rate"], 7.50) + + def test_confirm_import_updates_rates_in_target_sheet(self): + """Confirm import should update the targeted rate sheet's rates.""" + # Verify initial rate + self.rate_sheet.refresh_from_db() + initial_rate = next(r for r in self.rate_sheet.service_rates if r["min_weight"] == 0) + self.assertEqual(initial_rate["rate"], 7.50) + + csv_file = self._make_csv( + [ + "dhl_parcel_de,dhl_parcel_de_paket,V01PAK,DHL Paket," + "outbound,DE,DE,DE," + "0,2.001,KG,60,30,15,CM,EUR,25.99,5.0,2,", + "dhl_parcel_de,dhl_parcel_de_paket,V01PAK,DHL Paket," + "outbound,DE,DE,DE," + "5.001,10.001,KG,120,60,60,CM,EUR,35.50,12.0,2,", + ] + ) + csv_file.name = "test-confirm.csv" + + response = self.client.post( + self.import_url, + { + "resource_type": "rate_sheet", + "data_file": csv_file, + "rate_sheet_id": self.rate_sheet.id, + }, + format="multipart", + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + print(data) + self.assertFalse(data.get("dry_run", False)) + + # Reload the rate sheet and verify rates were updated + self.rate_sheet.refresh_from_db() + rates_by_weight = {r["min_weight"]: r["rate"] for r in self.rate_sheet.service_rates} + self.assertEqual(rates_by_weight[0], 25.99, "0-2kg rate should be updated to 25.99") + self.assertEqual(rates_by_weight[5.001], 35.50, "5-10kg rate should be updated to 35.50") + + def test_import_targets_correct_sheet_not_slug_match(self): + """When rate_sheet_id is set, import should update that sheet even if + another sheet exists with a slug matching the CSV carrier_name.""" + # Create a second sheet with slug=dhl_parcel_de (matches CSV carrier_name) + other_sheet = providers.SystemRateSheet.objects.create( + name="Other DHL Sheet", + carrier_name="dhl_parcel_de", + slug="dhl_parcel_de", + zones=[{"id": "zone_other", "label": "Other"}], + service_rates=[], + created_by=self.user, + ) + + csv_file = self._make_csv( + [ + "dhl_parcel_de,dhl_parcel_de_paket,V01PAK,DHL Paket," + "outbound,DE,DE,DE," + "0,2.001,KG,60,30,15,CM,EUR,88.88,5.0,2,", + ] + ) + csv_file.name = "test-targeting.csv" + + response = self.client.post( + self.import_url, + { + "resource_type": "rate_sheet", + "data_file": csv_file, + "rate_sheet_id": self.rate_sheet.id, # target the JTL sheet, not the slug-matched one + }, + format="multipart", + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + print(data) + + # The JTL sheet should be updated, not the other one + self.rate_sheet.refresh_from_db() + other_sheet.refresh_from_db() + + target_rates = {r["min_weight"]: r["rate"] for r in self.rate_sheet.service_rates} + self.assertEqual( + target_rates.get(0), + 88.88, + "Target sheet (JTL) should have the imported rate", + ) + + # The other sheet should be unchanged (no service_rates added) + self.assertEqual( + len(other_sheet.service_rates or []), + 0, + "Other sheet (slug=dhl_parcel_de) should NOT be modified", + ) + + +class TestAdminRateSheetPlanRateImport(AdminGraphTestCase): + """Integration tests for per-plan rate and surcharge import. + + Uses realistic CSV fixtures from DHL-DE, DPD-DE, and Landmark Global + with plan_rate_*, plan_cost_*, and surcharge columns. + """ + + CSV_HEADER = ( + "carrier_name,service_code,carrier_service_code,service_name," + "shipment_type,origin_country,zone_label,country_codes," + "min_weight,max_weight,weight_unit,max_length,max_width,max_height," + "dimension_unit,currency,base_rate,cost,transit_days,transit_time," + "plan_rate_start,plan_cost_start,plan_rate_advanced,plan_cost_advanced," + "plan_rate_pro,plan_cost_pro,plan_rate_enterprise,plan_cost_enterprise," + "tracked,b2c,b2b,first_mile,last_mile,form_factor,signature,age_check," + "saturday,neighbor_delivery,insurance," + "fuel_surcharge,seasonal_surcharge,customs_surcharge," + "energy_surcharge,road_toll,security_surcharge,notes\n" + ) + + # DHL Parcel DE — 3 rows with surcharges (energy_surcharge, road_toll) + DHL_CSV = ( + CSV_HEADER + "dhl_parcel_de,dhl_parcel_de_kleinpaket,V62KP,DHL KleinPaket," + "outbound,DE,DE,," + "0.01,1.001,KG,35,25,8,CM,EUR,3.39,3.432375,,best_effort," + "4.08,4.08,3.98,3.98,3.9,3.9,3.82,3.82," + "True,True,True,dropoff,home_delivery,mailbox,False,," + "True,True,20," + ",0,0,0.042375,,,DHL Kleinpaket 0-1kg\n" + "dhl_parcel_de,dhl_parcel_de_paket,V01PAK,DHL Paket," + "outbound,DE,DE,," + "0.01,2.001,KG,60,30,15,CM,EUR,6,6.19,,best_effort," + "6.81,6.81,6.64,6.64,6.5,6.5,6.37,6.37," + "True,True,True,pickup_dropoff,home_delivery,parcel,False,," + "True,True,500," + ",0,0,,0.19,,DHL Paket 0-2kg\n" + "dhl_parcel_de,dhl_parcel_de_paket,V01PAK,DHL Paket," + "outbound,DE,DE,," + "5.001,10.001,KG,120,60,60,CM,EUR,10.3,10.49,,best_effort," + "11.59,11.59,11.31,11.31,11.07,11.07,10.84,10.84," + "True,True,True,pickup_dropoff,home_delivery,parcel,False,," + "True,True,500," + ",0,0,,0.19,,DHL Paket 5-10kg\n" + ) + + # DPD (dpd_meta) — 2 rows, different plan rate margins + DPD_CSV = ( + CSV_HEADER + "dpd_meta,dpd_parcel_letter,PL,DPD ParcelLetter," + "outbound,DE,DE,DE," + "0.01,1.001,KG,35,25,3,CM,EUR,1.62,1.62,,best_effort," + "2.51,2.51,2.43,2.43,2.37,2.37,2.31,2.31," + "True,True,True,dropoff,home_delivery,mailbox,False,," + "False,False,0," + ",,,,,,DPD ParcelLetter 0-1kg\n" + "dpd_meta,dpd_classic,CL,DPD Classic," + "outbound,DE,DE,DE," + "0.01,31.501,KG,175,100,120,CM,EUR,5.29,5.29,,best_effort," + "6.49,6.49,6.3,6.3,6.14,6.14,5.98,5.98," + "True,True,True,pickup_dropoff,home_delivery,parcel,False,," + "False,True,500," + ",,,,,,DPD Classic 0-31.5kg\n" + ) + + # Landmark Global — 2 rows, international zones with country_codes + LANDMARK_CSV = ( + CSV_HEADER + "landmark,landmark_eu_di_home_dpd_at,,Landmark Global EU DI HOME - DPD AT," + "outbound,DE,Austria,AT," + "0.01,2.001,KG,120,60,60,CM,EUR,4.03,4.03,,best_effort," + "4.92,4.92,4.77,4.77,4.65,4.65,4.52,4.52," + "True,True,True,pickup_dropoff,home_delivery,parcel,False,," + "False,False,0," + ",,,,,,Landmark AT 0-2kg\n" + "landmark,landmark_eu_di_home_dpd_at,,Landmark Global EU DI HOME - DPD AT," + "outbound,DE,Austria,AT," + "2.001,5.001,KG,120,60,60,CM,EUR,5.18,5.18,,best_effort," + "6.38,6.38,6.19,6.19,6.03,6.03,5.87,5.87," + "True,True,True,pickup_dropoff,home_delivery,parcel,False,," + "False,False,0," + ",,,,,,Landmark AT 2-5kg\n" + ) + + def setUp(self): + super().setUp() + self.import_url = reverse("karrio.server.admin:admin-data-import") + + def _make_csv_file(self, content, name="test.csv"): + import io + + f = io.BytesIO(content.encode("utf-8")) + f.name = name + return f + + def _import_csv(self, content, name="test.csv", **extra): + csv_file = self._make_csv_file(content, name) + payload = {"resource_type": "rate_sheet", "data_file": csv_file, **extra} + response = self.client.post(self.import_url, payload, format="multipart") + self.assertEqual(response.status_code, 200, response.json()) + return response.json() + + # ── DHL Parcel DE tests ────────────────────────────────────────────────── + + def test_dhl_import_stores_plan_rates(self): + """DHL CSV import persists plan_rate_*/plan_cost_* on service_rates.""" + data = self._import_csv(self.DHL_CSV, "DHL-DE.csv") + print(data) + + from karrio.server.providers.models import SystemRateSheet + + sheet = SystemRateSheet.objects.get(id=data["rate_sheet_id"]) + + # Verify kleinpaket plan rates + kr = next(r for r in sheet.service_rates if r["min_weight"] == 0.01 and r["max_weight"] == 1.001) + self.assertEqual(kr["rate"], 3.39) + self.assertEqual(kr["plan_rate_start"], 4.08) + self.assertEqual(kr["plan_cost_start"], 4.08) + self.assertEqual(kr["plan_rate_advanced"], 3.98) + self.assertEqual(kr["plan_rate_pro"], 3.9) + self.assertEqual(kr["plan_rate_enterprise"], 3.82) + + # Verify paket 5-10kg plan rates + pr = next(r for r in sheet.service_rates if r["min_weight"] == 5.001) + self.assertEqual(pr["rate"], 10.3) + self.assertEqual(pr["plan_rate_start"], 11.59) + self.assertEqual(pr["plan_rate_enterprise"], 10.84) + + sheet.delete() + + def test_dhl_import_creates_surcharges(self): + """DHL CSV creates energy_surcharge and road_toll, linked to services.""" + data = self._import_csv(self.DHL_CSV, "DHL-DE.csv") + print(data) + + from karrio.server.providers.models import SystemRateSheet + + sheet = SystemRateSheet.objects.get(id=data["rate_sheet_id"]) + + surcharge_ids = {s["id"] for s in sheet.surcharges} + self.assertIn("energy_surcharge", surcharge_ids) + self.assertIn("road_toll", surcharge_ids) + + services = {s.service_code: s for s in sheet.services.all()} + self.assertIn("road_toll", services["dhl_parcel_de_paket"].surcharge_ids) + self.assertIn("energy_surcharge", services["dhl_parcel_de_kleinpaket"].surcharge_ids) + + sheet.delete() + + def test_dhl_plan_rate_change_detected_in_diff(self): + """Changing only plan_rate_start (base_rate unchanged) shows as 'updated'.""" + data = self._import_csv(self.DHL_CSV, "DHL-DE.csv") + sheet_id = data["rate_sheet_id"] + + modified = self.DHL_CSV.replace( + "4.08,4.08,3.98,3.98,3.9,3.9,3.82,3.82", "99.99,4.08,3.98,3.98,3.9,3.9,3.82,3.82", 1 + ) + diff_data = self._import_csv(modified, "DHL-DE-mod.csv", dry_run="true", rate_sheet_id=sheet_id) + print(diff_data) + + self.assertTrue(diff_data["dry_run"]) + updated = [r for r in diff_data["diff"]["rows"] if r["change"] == "updated"] + self.assertEqual(len(updated), 1, "Only kleinpaket row should be updated") + self.assertEqual(updated[0]["plan_rate_start"], 99.99) + self.assertEqual(updated[0]["old_rate"], 3.39) # base_rate unchanged + + from karrio.server.providers.models import SystemRateSheet + + SystemRateSheet.objects.filter(id=sheet_id).delete() + + # ── DPD (dpd_meta) tests ───────────────────────────────────────────────── + + def test_dpd_import_stores_plan_rates(self): + """DPD CSV import stores correct plan rates for ParcelLetter and Classic.""" + data = self._import_csv(self.DPD_CSV, "DPD-DE.csv") + print(data) + + from karrio.server.providers.models import SystemRateSheet + + sheet = SystemRateSheet.objects.get(id=data["rate_sheet_id"]) + + self.assertEqual(sheet.carrier_name, "dpd_meta") + self.assertEqual(len(sheet.service_rates), 2) + + # ParcelLetter: base=1.62, start=2.51, enterprise=2.31 + pl = next(r for r in sheet.service_rates if r["rate"] == 1.62) + self.assertEqual(pl["plan_rate_start"], 2.51) + self.assertEqual(pl["plan_rate_enterprise"], 2.31) + + # Classic: base=5.29, start=6.49, enterprise=5.98 + cl = next(r for r in sheet.service_rates if r["rate"] == 5.29) + self.assertEqual(cl["plan_rate_start"], 6.49) + self.assertEqual(cl["plan_rate_enterprise"], 5.98) + + # Verify services created + service_codes = {s.service_code for s in sheet.services.all()} + self.assertEqual(service_codes, {"dpd_parcel_letter", "dpd_classic"}) + + sheet.delete() + + def test_dpd_reimport_shows_unchanged(self): + """Importing the same DPD CSV twice shows all rows as unchanged.""" + data = self._import_csv(self.DPD_CSV, "DPD-DE.csv") + sheet_id = data["rate_sheet_id"] + + diff_data = self._import_csv(self.DPD_CSV, "DPD-DE.csv", dry_run="true", rate_sheet_id=sheet_id) + print(diff_data) + + self.assertEqual(diff_data["diff"]["summary"]["unchanged"], 2) + self.assertEqual(diff_data["diff"]["summary"]["added"], 0) + self.assertEqual(diff_data["diff"]["summary"]["updated"], 0) + + from karrio.server.providers.models import SystemRateSheet + + SystemRateSheet.objects.filter(id=sheet_id).delete() + + # ── Landmark Global tests ──────────────────────────────────────────────── + + def test_landmark_import_with_international_zones(self): + """Landmark CSV creates zones with country_codes and correct plan rates.""" + data = self._import_csv(self.LANDMARK_CSV, "Landmark.csv") + print(data) + + from karrio.server.providers.models import SystemRateSheet + + sheet = SystemRateSheet.objects.get(id=data["rate_sheet_id"]) + + self.assertEqual(sheet.carrier_name, "landmark") + self.assertEqual(len(sheet.service_rates), 2) + + # Verify zone has country_codes + austria_zone = next((z for z in sheet.zones if z["label"] == "Austria"), None) + self.assertIsNotNone(austria_zone) + self.assertIn("AT", austria_zone["country_codes"]) + + # Verify plan rates on 0-2kg + r1 = next(r for r in sheet.service_rates if r["min_weight"] == 0.01) + self.assertEqual(r1["rate"], 4.03) + self.assertEqual(r1["plan_rate_start"], 4.92) + self.assertEqual(r1["plan_rate_enterprise"], 4.52) + + # Verify plan rates on 2-5kg + r2 = next(r for r in sheet.service_rates if r["min_weight"] == 2.001) + self.assertEqual(r2["rate"], 5.18) + self.assertEqual(r2["plan_rate_start"], 6.38) + self.assertEqual(r2["plan_rate_enterprise"], 5.87) + + sheet.delete() + + def test_landmark_plan_rate_update_detected(self): + """Landmark: changing enterprise plan rate is detected in diff.""" + data = self._import_csv(self.LANDMARK_CSV, "Landmark.csv") + sheet_id = data["rate_sheet_id"] + + # Change enterprise rate for 0-2kg: 4.52 -> 10.00 + modified = self.LANDMARK_CSV.replace( + "4.92,4.92,4.77,4.77,4.65,4.65,4.52,4.52", "4.92,4.92,4.77,4.77,4.65,4.65,10.00,10.00" + ) + diff_data = self._import_csv(modified, "Landmark-mod.csv", dry_run="true", rate_sheet_id=sheet_id) + print(diff_data) + + updated = [r for r in diff_data["diff"]["rows"] if r["change"] == "updated"] + self.assertEqual(len(updated), 1) + self.assertEqual(updated[0]["plan_rate_enterprise"], 10.00) + self.assertEqual(updated[0]["old_rate"], 4.03) # base unchanged + + from karrio.server.providers.models import SystemRateSheet + + SystemRateSheet.objects.filter(id=sheet_id).delete() + + # ── Cross-carrier roundtrip test ───────────────────────────────────────── + + def test_full_roundtrip_all_carriers(self): + """Import DHL, DPD, and Landmark — verify all preserve plan rates.""" + from karrio.server.providers.models import SystemRateSheet + + sheets = [] + for csv, name, carrier, expected_rates in [ + (self.DHL_CSV, "DHL-DE.csv", "dhl_parcel_de", 3), + (self.DPD_CSV, "DPD-DE.csv", "dpd_meta", 2), + (self.LANDMARK_CSV, "Landmark.csv", "landmark", 2), + ]: + data = self._import_csv(csv, name) + sheet = SystemRateSheet.objects.get(id=data["rate_sheet_id"]) + sheets.append(sheet) + + self.assertEqual(sheet.carrier_name, carrier) + self.assertEqual(len(sheet.service_rates), expected_rates) + + # Every rate should have plan fields + for sr in sheet.service_rates: + self.assertIn("plan_rate_start", sr, f"{carrier}: missing plan_rate_start") + self.assertIn("plan_rate_enterprise", sr, f"{carrier}: missing plan_rate_enterprise") + self.assertIsNotNone(sr["plan_rate_start"], f"{carrier}: plan_rate_start is None") + self.assertGreater(sr["plan_rate_start"], 0, f"{carrier}: plan_rate_start should be > 0") + + # Every service should have features populated from CSV + for svc in sheet.services.all(): + features = svc.features or {} + self.assertIn( + "shipment_type", + features, + f"{carrier}/{svc.service_code}: missing features.shipment_type", + ) + self.assertIn( + features["shipment_type"], + ("outbound", "returns", "both"), + f"{carrier}/{svc.service_code}: invalid shipment_type={features['shipment_type']}", + ) + + for sheet in sheets: + sheet.delete() diff --git a/modules/admin/karrio/server/admin/urls.py b/modules/admin/karrio/server/admin/urls.py index c6ace036d7..4f69133221 100644 --- a/modules/admin/karrio/server/admin/urls.py +++ b/modules/admin/karrio/server/admin/urls.py @@ -1,8 +1,8 @@ """ karrio server admin module urls """ -from django.urls import path, include +from django.urls import include, path app_name = "karrio.server.admin" urlpatterns = [ diff --git a/modules/admin/karrio/server/admin/utils.py b/modules/admin/karrio/server/admin/utils.py index 2df72c1ee2..090f953bb1 100644 --- a/modules/admin/karrio/server/admin/utils.py +++ b/modules/admin/karrio/server/admin/utils.py @@ -1,11 +1,11 @@ import enum -import typing import functools -import strawberry +import typing + +import karrio.server.pricing.serializers as serializers import rest_framework.exceptions as exceptions +import strawberry from strawberry.types import Info -import karrio.server.pricing.serializers as serializers -from karrio.server.core.logging import logger # Markup type enum for GraphQL MarkupTypeEnum: typing.Any = strawberry.enum( # type: ignore @@ -13,17 +13,6 @@ ) -def staff_required(func): - @functools.wraps(func) - def wrapper(info: Info, **kwargs): - if info.context.request.user.is_staff is False: - raise exceptions.PermissionDenied() - - return func(info, **kwargs) - - return wrapper - - def superuser_required(func): @functools.wraps(func) def wrapper(info: Info, **kwargs): diff --git a/modules/admin/karrio/server/admin/views.py b/modules/admin/karrio/server/admin/views.py index 7f99b4fc1a..e51c10e2e9 100644 --- a/modules/admin/karrio/server/admin/views.py +++ b/modules/admin/karrio/server/admin/views.py @@ -1,19 +1,130 @@ +import karrio.lib as lib +import karrio.server.admin.schema as schema +import karrio.server.core.views.api as api +import karrio.server.graph.views as views +from django.http import JsonResponse from django.urls import path from django.views.decorators.csrf import csrf_exempt +from rest_framework import status +from rest_framework.parsers import FormParser, MultiPartParser +from rest_framework.permissions import IsAuthenticated +from rest_framework.request import Request +from rest_framework.response import Response -import karrio.server.graph.views as views -import karrio.server.admin.schema as schema + +class IsStaffPermission(IsAuthenticated): + """Only allow staff users.""" + + def has_permission(self, request, view): + return super().has_permission(request, view) and getattr(request.user, "is_staff", False) + + +class AdminGraphQLView(views.GraphQLView): + """GraphQL view for /admin/graphql that enforces staff-only access at the route. + + The base graph view only checks that the request is authenticated. Admin + schemas expose system-wide data and must additionally verify that the + caller is a staff user. Enforcing this here, before the schema executes, + is the single source of truth for admin authorization — per-resolver + decorators are not used; new admin queries/mutations inherit the gate by + virtue of being mounted under this route. + """ + + def dispatch(self, request, *args, **kwargs): + if lib.failsafe(lambda: self.should_render_graphiql(request)): + return super().dispatch(request, *args, **kwargs) + + user = getattr(request, "user", None) + if user is None or getattr(user, "is_anonymous", True): + return JsonResponse( + { + "errors": [ + { + "message": "Authentication required", + "code": "authentication_required", + "status_code": status.HTTP_401_UNAUTHORIZED, + } + ] + }, + status=status.HTTP_401_UNAUTHORIZED, + ) + + if not getattr(user, "is_staff", False): + return JsonResponse( + { + "errors": [ + { + "message": "Staff access required", + "code": "permission_denied", + "status_code": status.HTTP_403_FORBIDDEN, + } + ] + }, + status=status.HTTP_403_FORBIDDEN, + ) + + return super().dispatch(request, *args, **kwargs) + + +class AdminDataImport(api.BaseAPIView): + """Admin-only rate sheet import — creates SystemRateSheet.""" + + parser_classes = [MultiPartParser, FormParser] + permission_classes = [IsStaffPermission] + + def post(self, request: Request): + import karrio.server.data.serializers.batch_rate_sheets as batch_rate_sheets + + data_file = request.data.get("data_file") + if not data_file: + return Response( + {"errors": [{"message": "data_file is required"}]}, + status=status.HTTP_400_BAD_REQUEST, + ) + dry_run = str(request.data.get("dry_run", "false")).lower() in ( + "true", + "1", + "yes", + ) + rate_sheet_id = request.data.get("rate_sheet_id") + create_mode = str(request.data.get("create_mode", "false")).lower() in ( + "true", + "1", + "yes", + ) + try: + result = batch_rate_sheets.process_rate_sheet_import( + data_file=data_file, + context=request, + dry_run=dry_run, + rate_sheet_id=rate_sheet_id, + system=True, + create_mode=create_mode, + ) + except Exception as exc: + return Response( + {"errors": [{"message": str(exc)}]}, + status=status.HTTP_400_BAD_REQUEST, + ) + if result.get("errors"): + return Response(result, status=status.HTTP_400_BAD_REQUEST) + return Response(result, status=status.HTTP_200_OK) urlpatterns = [ path( "admin/graphql/", - csrf_exempt(views.GraphQLView.as_view(schema=schema.schema)), + csrf_exempt(AdminGraphQLView.as_view(schema=schema.schema)), name="admin-graph", ), path( "admin/graphql", - csrf_exempt(views.GraphQLView.as_view(schema=schema.schema)), + csrf_exempt(AdminGraphQLView.as_view(schema=schema.schema)), name="admin-graph", ), + path( + "admin/batches/data/import", + AdminDataImport.as_view(), + name="admin-data-import", + ), ] diff --git a/modules/admin/karrio/server/admin/worker/signals.py b/modules/admin/karrio/server/admin/worker/signals.py index 8d46aae564..4b44f7037c 100644 --- a/modules/admin/karrio/server/admin/worker/signals.py +++ b/modules/admin/karrio/server/admin/worker/signals.py @@ -1,117 +1,14 @@ +"""Backwards-compatible re-export. Canonical location: karrio.server.huey.signals""" + import logging -from django.db import models -from django.utils import timezone logger = logging.getLogger(__name__) def register_huey_signals(): - """Register Huey signal handlers to track task execution lifecycle. - - Uses split create/update pattern instead of update_or_create to avoid - SELECT ... FOR UPDATE lock contention when multiple tasks are enqueued - in rapid succession (e.g. background_trackers_update dispatching - per-carrier batches). - """ try: - from huey.signals import ( - SIGNAL_ENQUEUED, - SIGNAL_EXECUTING, - SIGNAL_COMPLETE, - SIGNAL_ERROR, - SIGNAL_RETRYING, - SIGNAL_REVOKED, - SIGNAL_EXPIRED, - ) - from huey.contrib.djhuey import HUEY as huey_instance - except ImportError: - logger.debug("Huey not available, skipping signal registration") - return - - @huey_instance.signal( - SIGNAL_ENQUEUED, - SIGNAL_EXECUTING, - SIGNAL_COMPLETE, - SIGNAL_ERROR, - SIGNAL_RETRYING, - SIGNAL_REVOKED, - SIGNAL_EXPIRED, - ) - def task_signal_handler(signal, task, exc=None): - try: - from karrio.server.admin.worker.models import TaskExecution - - now = timezone.now() - task_id = task.id - task_name = task.name or "unknown" - - SIGNAL_STATUS_MAP = { - SIGNAL_ENQUEUED: "queued", - SIGNAL_EXECUTING: "executing", - SIGNAL_COMPLETE: "complete", - SIGNAL_ERROR: "error", - SIGNAL_RETRYING: "retrying", - SIGNAL_REVOKED: "revoked", - SIGNAL_EXPIRED: "expired", - } - - status = SIGNAL_STATUS_MAP.get(signal, signal) - - if signal == SIGNAL_RETRYING: - TaskExecution.objects.filter(task_id=task_id).update( - retries=models.F("retries") + 1, - status="retrying", - ) - return - - # ENQUEUED: create a new record (no SELECT FOR UPDATE needed) - if signal == SIGNAL_ENQUEUED: - TaskExecution.objects.create( - task_id=task_id, - task_name=task_name, - status=status, - queued_at=now, - args_summary=str(task.args)[:500] if task.args else None, - ) - return + from karrio.server.huey.signals import register_huey_signals as _register - # All other signals: lightweight UPDATE (no row lock) - updates = {"status": status, "task_name": task_name} - - if signal == SIGNAL_EXECUTING: - updates["started_at"] = now - - elif signal in (SIGNAL_COMPLETE, SIGNAL_ERROR, SIGNAL_REVOKED, SIGNAL_EXPIRED): - updates["completed_at"] = now - - if signal == SIGNAL_ERROR and exc: - updates["error"] = str(exc)[:2000] - - # Calculate duration inline to avoid a second UPDATE round-trip - if signal in (SIGNAL_COMPLETE, SIGNAL_ERROR): - obj = TaskExecution.objects.filter(task_id=task_id).values("started_at").first() - if obj and obj["started_at"]: - updates["duration_ms"] = int( - (now - obj["started_at"]).total_seconds() * 1000 - ) - - TaskExecution.objects.filter(task_id=task_id).update(**updates) - - except Exception: - # Handle duplicate records from race conditions on ENQUEUED - try: - from karrio.server.admin.worker.models import TaskExecution - - if signal == SIGNAL_ENQUEUED: - # Duplicate task_id — just update the existing record - TaskExecution.objects.filter(task_id=task_id).update( - task_name=task_name, - status="queued", - queued_at=now, - args_summary=str(task.args)[:500] if task.args else None, - ) - return - except Exception: - pass - - logger.exception("Failed to record task signal") + _register() + except ImportError: + logger.debug("Huey module not installed, skipping signal registration") diff --git a/modules/admin/karrio/server/admin/worker/tasks.py b/modules/admin/karrio/server/admin/worker/tasks.py index 69e5ca20a7..704c9015b2 100644 --- a/modules/admin/karrio/server/admin/worker/tasks.py +++ b/modules/admin/karrio/server/admin/worker/tasks.py @@ -1,5 +1,6 @@ import logging from datetime import timedelta + from django.utils import timezone logger = logging.getLogger(__name__) diff --git a/modules/admin/karrio/server/settings/admin.py b/modules/admin/karrio/server/settings/admin.py index 1a4408357c..d85cf47db6 100644 --- a/modules/admin/karrio/server/settings/admin.py +++ b/modules/admin/karrio/server/settings/admin.py @@ -1,6 +1,6 @@ # type: ignore -from karrio.server.settings.base import * import karrio.server.settings.constance as constance +from karrio.server.settings.base import * INSTALLED_APPS += [ "karrio.server.admin", diff --git a/modules/cli/karrio_cli/__main__.py b/modules/cli/karrio_cli/__main__.py index 61a7af7654..5c721b0d3a 100644 --- a/modules/cli/karrio_cli/__main__.py +++ b/modules/cli/karrio_cli/__main__.py @@ -1,24 +1,26 @@ import typer + +import karrio_cli.commands.codegen as codegen +import karrio_cli.commands.login as login import karrio_cli.commands.sdk as sdk +import karrio_cli.resources.carriers as carriers +import karrio_cli.resources.connections as connections +import karrio_cli.resources.events as events import karrio_cli.resources.logs as logs -import karrio_cli.commands.login as login import karrio_cli.resources.orders as orders -import karrio_cli.resources.events as events -import karrio_cli.commands.codegen as codegen -import karrio_cli.resources.trackers as trackers -import karrio_cli.resources.carriers as carriers import karrio_cli.resources.shipments as shipments -import karrio_cli.resources.connections as connections +import karrio_cli.resources.trackers as trackers try: - import karrio import karrio_cli.commands.plugins as plugins + has_sdk_dep = True except ImportError: has_sdk_dep = False try: import karrio_cli.commands.studio as studio + has_studio_dep = True except ImportError: has_studio_dep = False diff --git a/modules/cli/karrio_cli/commands/codegen.py b/modules/cli/karrio_cli/commands/codegen.py index 9407fbb64a..21e0a6fac0 100644 --- a/modules/cli/karrio_cli/commands/codegen.py +++ b/modules/cli/karrio_cli/commands/codegen.py @@ -1,24 +1,19 @@ -import re -import sys -import typer import importlib -from typing import Optional, List, Set import logging import os +import re +import sys + +import typer app = typer.Typer() + @app.command("transform") def transform( - input_file: Optional[str] = typer.Argument( - None, help="Input file path. If not provided, reads from stdin." - ), - output_file: Optional[str] = typer.Argument( - None, help="Output file path. If not provided, writes to stdout." - ), - append_type_suffix: bool = typer.Option( - True, help="Append 'Type' to class names", is_flag=True - ), + input_file: str | None = typer.Argument(None, help="Input file path. If not provided, reads from stdin."), + output_file: str | None = typer.Argument(None, help="Output file path. If not provided, writes to stdout."), + append_type_suffix: bool = typer.Option(True, help="Append 'Type' to class names", is_flag=True), ): """ Transform Python code generated by quicktype (using dataclasses) @@ -26,7 +21,7 @@ def transform( """ # Read input from file or stdin if input_file: - with open(input_file, "r") as f: + with open(input_file) as f: content = f.read() else: content = sys.stdin.read() @@ -41,10 +36,12 @@ def transform( else: print(transformed) -def extract_class_names(content: str) -> Set[str]: + +def extract_class_names(content: str) -> set[str]: """Extract all class names from the content.""" return set(re.findall(r"class\s+(\w+)", content)) + def transform_content(content: str, append_type_suffix: bool = True) -> str: """Transform dataclass-based code to jstruct-based code.""" # Check if we already have a typing import @@ -54,7 +51,7 @@ def transform_content(content: str, append_type_suffix: bool = True) -> str: if "from dataclasses import dataclass" in content: content = content.replace( "from dataclasses import dataclass", - "import attr\nimport jstruct" + ("" if has_typing_import else "\nimport typing") + "import attr\nimport jstruct" + ("" if has_typing_import else "\nimport typing"), ) else: # If dataclasses import is not found, add the imports anyway @@ -79,7 +76,7 @@ def transform_content(content: str, append_type_suffix: bool = True) -> str: if append_type_suffix: for class_name in class_names: # If the class name already ends with 'Type', append 'ObjectType' instead - if class_name.endswith('Type'): + if class_name.endswith("Type"): class_name_mapping[class_name] = f"{class_name}ObjectType" else: class_name_mapping[class_name] = f"{class_name}Type" @@ -87,41 +84,39 @@ def transform_content(content: str, append_type_suffix: bool = True) -> str: # Rename class definitions only, not field names for original_name, new_name in class_name_mapping.items(): # Replace class definitions - content = re.sub( - rf"class\s+{original_name}\s*:", - f"class {new_name}:", - content - ) + content = re.sub(rf"class\s+{original_name}\s*:", f"class {new_name}:", content) # Process all property definitions - lines = content.split('\n') + lines = content.split("\n") for i in range(len(lines)): line = lines[i] # Skip if this is not a class property line - if not re.search(r'^\s+\w+\s*:', line): + if not re.search(r"^\s+\w+\s*:", line): continue # Replace typing annotations with module references - line = re.sub(r':\s*Any\s*=', r': typing.Any =', line) # Replace standalone Any as a type - line = re.sub(r':\s*Union\s*=', r': typing.Union =', line) # Replace standalone Union as a type - line = re.sub(r':\s*Dict\s*=', r': typing.Dict =', line) # Replace standalone Dict as a type - line = re.sub(r'Optional\[', r'typing.Optional[', line) - line = re.sub(r'List\[', r'typing.List[', line) - line = re.sub(r'Union\[', r'typing.Union[', line) # Add handling for Union types - line = re.sub(r'Dict\[', r'typing.Dict[', line) # Add handling for Dict types - line = re.sub(r'(\[|,\s*)Any(\]|,|\s*\]=)', r'\1typing.Any\2', line) # Match Any inside brackets or between commas - line = re.sub(r'(\[|,\s*)Union(\[)', r'\1typing.Union\2', line) # Match Union inside brackets or between commas - line = re.sub(r'(\[|,\s*)Dict(\[)', r'\1typing.Dict\2', line) # Match Dict inside brackets or between commas + line = re.sub(r":\s*Any\s*=", r": typing.Any =", line) # Replace standalone Any as a type + line = re.sub(r":\s*Union\s*=", r": typing.Union =", line) # Replace standalone Union as a type + line = re.sub(r":\s*Dict\s*=", r": typing.Dict =", line) # Replace standalone Dict as a type + line = re.sub(r"Optional\[", r"typing.Optional[", line) + line = re.sub(r"List\[", r"typing.List[", line) + line = re.sub(r"Union\[", r"typing.Union[", line) # Add handling for Union types + line = re.sub(r"Dict\[", r"typing.Dict[", line) # Add handling for Dict types + line = re.sub( + r"(\[|,\s*)Any(\]|,|\s*\]=)", r"\1typing.Any\2", line + ) # Match Any inside brackets or between commas + line = re.sub(r"(\[|,\s*)Union(\[)", r"\1typing.Union\2", line) # Match Union inside brackets or between commas + line = re.sub(r"(\[|,\s*)Dict(\[)", r"\1typing.Dict\2", line) # Match Dict inside brackets or between commas # Handle nested Union inside Optional - line = re.sub(r'typing\.Optional\[(Union\[)', r'typing.Optional[typing.\1', line) + line = re.sub(r"typing\.Optional\[(Union\[)", r"typing.Optional[typing.\1", line) # Handle nested Dict inside Optional - line = re.sub(r'typing\.Optional\[(Dict\[)', r'typing.Optional[typing.\1', line) + line = re.sub(r"typing\.Optional\[(Dict\[)", r"typing.Optional[typing.\1", line) # Handle properties with null values (e.g., "category: None" -> "category: typing.Any = None") - line = re.sub(r'(\w+):\s*None\s*$', r'\1: typing.Any = None', line) + line = re.sub(r"(\w+):\s*None\s*$", r"\1: typing.Any = None", line) lines[i] = line # Handle complex type annotations for each class @@ -134,81 +129,80 @@ def transform_content(content: str, append_type_suffix: bool = True) -> str: # This prevents changing field names that match class names # Handle typing.Optional[OriginalName] - pattern = re.compile(rf'(typing\.\w+\[)({original_name})(\])') - lines[i] = re.sub(pattern, f'\\1{class_name}\\3', lines[i]) + pattern = re.compile(rf"(typing\.\w+\[)({original_name})(\])") + lines[i] = re.sub(pattern, f"\\1{class_name}\\3", lines[i]) # Handle Union types that contain the class name - union_pattern = re.compile(rf'(typing\.Union\[[^,\]]*,\s*)({original_name})(\s*\]|\s*,)') - lines[i] = re.sub(union_pattern, f'\\1{class_name}\\3', lines[i]) + union_pattern = re.compile(rf"(typing\.Union\[[^,\]]*,\s*)({original_name})(\s*\]|\s*,)") + lines[i] = re.sub(union_pattern, f"\\1{class_name}\\3", lines[i]) # Also handle Union when the class name is the first element - union_first_pattern = re.compile(rf'(typing\.Union\[)({original_name})(\s*,)') - lines[i] = re.sub(union_first_pattern, f'\\1{class_name}\\3', lines[i]) + union_first_pattern = re.compile(rf"(typing\.Union\[)({original_name})(\s*,)") + lines[i] = re.sub(union_first_pattern, f"\\1{class_name}\\3", lines[i]) # Handle Dict with class name as key or value type - dict_pattern = re.compile(rf'(typing\.Dict\[[^,\]]*,\s*)({original_name})(\s*\])') - lines[i] = re.sub(dict_pattern, f'\\1{class_name}\\3', lines[i]) + dict_pattern = re.compile(rf"(typing\.Dict\[[^,\]]*,\s*)({original_name})(\s*\])") + lines[i] = re.sub(dict_pattern, f"\\1{class_name}\\3", lines[i]) # Handle Dict with class name as key type - dict_key_pattern = re.compile(rf'(typing\.Dict\[)({original_name})(\s*,)') - lines[i] = re.sub(dict_key_pattern, f'\\1{class_name}\\3', lines[i]) + dict_key_pattern = re.compile(rf"(typing\.Dict\[)({original_name})(\s*,)") + lines[i] = re.sub(dict_key_pattern, f"\\1{class_name}\\3", lines[i]) # Handle nested types: typing.Optional[typing.List[OriginalName]] - nested_pattern = re.compile(rf'(typing\.\w+\[typing\.\w+\[)({original_name})(\]\])') - lines[i] = re.sub(nested_pattern, f'\\1{class_name}\\3', lines[i]) + nested_pattern = re.compile(rf"(typing\.\w+\[typing\.\w+\[)({original_name})(\]\])") + lines[i] = re.sub(nested_pattern, f"\\1{class_name}\\3", lines[i]) # Handle jstruct.JStruct[OriginalName] - jstruct_pattern = re.compile(rf'(jstruct\.JStruct\[)({original_name})(\])') - lines[i] = re.sub(jstruct_pattern, f'\\1{class_name}\\3', lines[i]) + jstruct_pattern = re.compile(rf"(jstruct\.JStruct\[)({original_name})(\])") + lines[i] = re.sub(jstruct_pattern, f"\\1{class_name}\\3", lines[i]) # Handle jstruct.JList[OriginalName] - jlist_pattern = re.compile(rf'(jstruct\.JList\[)({original_name})(\])') - lines[i] = re.sub(jlist_pattern, f'\\1{class_name}\\3', lines[i]) + jlist_pattern = re.compile(rf"(jstruct\.JList\[)({original_name})(\])") + lines[i] = re.sub(jlist_pattern, f"\\1{class_name}\\3", lines[i]) # Check for Optional[ClassType] - if re.search(rf'typing\.Optional\[{class_name}\]\s*=\s*None', lines[i]): + if re.search(rf"typing\.Optional\[{class_name}\]\s*=\s*None", lines[i]): lines[i] = re.sub( - rf'typing\.Optional\[{class_name}\]\s*=\s*None', - f'typing.Optional[{class_name}] = jstruct.JStruct[{class_name}]', - lines[i] + rf"typing\.Optional\[{class_name}\]\s*=\s*None", + f"typing.Optional[{class_name}] = jstruct.JStruct[{class_name}]", + lines[i], ) # Check for List[ClassType] - elif re.search(rf'typing\.List\[{class_name}\]\s*=\s*None', lines[i]): + elif re.search(rf"typing\.List\[{class_name}\]\s*=\s*None", lines[i]): lines[i] = re.sub( - rf'typing\.List\[{class_name}\]\s*=\s*None', - f'typing.List[{class_name}] = jstruct.JList[{class_name}]', - lines[i] + rf"typing\.List\[{class_name}\]\s*=\s*None", + f"typing.List[{class_name}] = jstruct.JList[{class_name}]", + lines[i], ) # Check for Optional[List[ClassType]] - elif re.search(rf'typing\.Optional\[typing\.List\[{class_name}\]\]\s*=\s*None', lines[i]): + elif re.search(rf"typing\.Optional\[typing\.List\[{class_name}\]\]\s*=\s*None", lines[i]): lines[i] = re.sub( - rf'typing\.Optional\[typing\.List\[{class_name}\]\]\s*=\s*None', - f'typing.Optional[typing.List[{class_name}]] = jstruct.JList[{class_name}]', - lines[i] + rf"typing\.Optional\[typing\.List\[{class_name}\]\]\s*=\s*None", + f"typing.Optional[typing.List[{class_name}]] = jstruct.JList[{class_name}]", + lines[i], ) # Check for Optional[Union[...]] - elif re.search(rf'typing\.Optional\[typing\.Union\[[^\]]*{class_name}[^\]]*\]\]\s*=\s*None', lines[i]): + elif re.search(rf"typing\.Optional\[typing\.Union\[[^\]]*{class_name}[^\]]*\]\]\s*=\s*None", lines[i]): # For simplicity, we're not replacing the value here since Union types are complex # and we don't want to make assumptions about the appropriate jstruct type pass # Check for Optional[Dict[...]] - elif re.search(rf'typing\.Optional\[typing\.Dict\[[^\]]*{class_name}[^\]]*\]\]\s*=\s*None', lines[i]): + elif re.search(rf"typing\.Optional\[typing\.Dict\[[^\]]*{class_name}[^\]]*\]\]\s*=\s*None", lines[i]): # For simplicity, we're not replacing the value here since Dict types are complex # and we don't want to make assumptions about the appropriate jstruct type pass - return '\n'.join(lines) + return "\n".join(lines) + @app.command("generate") def generate( input_file: str = typer.Argument(..., help="Input JSON schema file path"), - output_file: Optional[str] = typer.Argument( - None, help="Output Python file path. If not provided, writes to stdout." - ), + output_file: str | None = typer.Argument(None, help="Output Python file path. If not provided, writes to stdout."), python_version: str = typer.Option("3.7", help="Python version to target"), just_types: bool = typer.Option(True, help="Generate just the type definitions without serialization code"), append_type_suffix: bool = typer.Option(True, help="Append 'Type' to class names"), @@ -219,7 +213,7 @@ def generate( """ import subprocess - logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") logger = logging.getLogger(__name__) # Build quicktype command @@ -229,12 +223,16 @@ def generate( "--no-uuids", "--no-enums", "--no-date-times", - "--src-lang", "json", - "--lang", "python", + "--src-lang", + "json", + "--lang", + "python", "--all-properties-optional", "--no-nice-property-names", - f"--python-version", python_version, - "--src", input_file + "--python-version", + python_version, + "--src", + input_file, ] if just_types: @@ -263,6 +261,7 @@ def generate( print(transformed) logger.info(f"Generated code from {os.path.basename(input_file)} to stdout") + def instantiate_tree(cls, indent=0, alias=""): tree = f"{alias}{cls.__name__}(\n" indent += 1 @@ -285,10 +284,7 @@ def instantiate_tree(cls, indent=0, alias=""): else: tree += " " * indent * 4 + f"{name}=[],\n" elif hasattr(typ, "__annotations__"): - tree += ( - " " * indent * 4 - + f"{name}={instantiate_tree(typ, indent, alias=alias)},\n" - ) + tree += " " * indent * 4 + f"{name}={instantiate_tree(typ, indent, alias=alias)},\n" else: tree += " " * indent * 4 + f"{name}=None,\n" @@ -332,5 +328,6 @@ def create_tree( ) typer.echo(output) + if __name__ == "__main__": app() diff --git a/modules/cli/karrio_cli/commands/login.py b/modules/cli/karrio_cli/commands/login.py index 2aefc0a579..06ceef71cb 100644 --- a/modules/cli/karrio_cli/commands/login.py +++ b/modules/cli/karrio_cli/commands/login.py @@ -1,6 +1,7 @@ import os -import typer + import requests +import typer app = typer.Typer() @@ -10,7 +11,7 @@ def get_config(): config_file = os.path.expanduser("~/.karrio/config") if os.path.exists(config_file): - with open(config_file, "r") as f: + with open(config_file) as f: return dict(line.strip().split("=") for line in f) return {} @@ -67,7 +68,7 @@ def login( except requests.RequestException as e: typer.echo(f"Error connecting to Karrio instance: {str(e)}", err=True) - except IOError as e: + except OSError as e: typer.echo(f"Error saving configuration: {str(e)}", err=True) @@ -87,7 +88,7 @@ def logout(): typer.echo("Successfully logged out. Karrio configuration removed.") except FileNotFoundError: typer.echo("No saved Karrio configuration found.") - except IOError as e: + except OSError as e: typer.echo(f"Error removing configuration: {str(e)}", err=True) diff --git a/modules/cli/karrio_cli/commands/plugins.py b/modules/cli/karrio_cli/commands/plugins.py index 6ad64d785c..c8f8deba1f 100644 --- a/modules/cli/karrio_cli/commands/plugins.py +++ b/modules/cli/karrio_cli/commands/plugins.py @@ -1,9 +1,9 @@ -import typer import karrio.references as references -import typing +import typer app = typer.Typer() + @app.command("list") def list_plugins( pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), @@ -41,25 +41,29 @@ def list_plugins( results = [] for plugin_id, plugin in plugins.items(): enabled = registry.get(f"{plugin_id.upper()}_ENABLED", True) - results.append({ - "id": plugin_id, - "label": plugin.get("label", ""), - "status": plugin.get("status", ""), - "enabled": enabled, - "description": plugin.get("description", ""), - }) + results.append( + { + "id": plugin_id, + "label": plugin.get("label", ""), + "status": plugin.get("status", ""), + "enabled": enabled, + "description": plugin.get("description", ""), + } + ) if pretty: import json + typer.echo(json.dumps(results, indent=2)) else: try: from tabulate import tabulate + table = [ - [i, plugin['id'], plugin['label'], plugin['status'], 'ENABLED' if plugin['enabled'] else 'DISABLED'] + [i, plugin["id"], plugin["label"], plugin["status"], "ENABLED" if plugin["enabled"] else "DISABLED"] for i, plugin in enumerate(results, 1) ] - headers = ['#', 'ID', 'Label', 'Status', 'Enabled'] - typer.echo(tabulate(table, headers=headers, tablefmt='github')) + headers = ["#", "ID", "Label", "Status", "Enabled"] + typer.echo(tabulate(table, headers=headers, tablefmt="github")) except ImportError: for i, plugin in enumerate(results, 1): line = f"{i}. {plugin['id']} - {plugin['label']} ({'ENABLED' if plugin['enabled'] else 'DISABLED'}) - {plugin['status']}" @@ -68,6 +72,7 @@ def list_plugins( else: typer.echo(line) + @app.command("show") def show_plugin( plugin_id: str, @@ -105,6 +110,7 @@ def show_plugin( typer.echo(f"Plugin '{plugin_id}' not found.", err=True) raise typer.Exit(code=1) import json + if pretty: typer.echo(json.dumps(details, indent=2)) else: @@ -115,6 +121,7 @@ def show_plugin( else: typer.echo(line) + @app.command("enable") def enable_plugin( plugin_id: str, @@ -139,7 +146,8 @@ def enable_plugin( typer.echo(f"Plugin '{plugin_id}' enabled.") except Exception as e: typer.echo(f"Failed to enable plugin '{plugin_id}': {e}", err=True) - raise typer.Exit(code=1) + raise typer.Exit(code=1) from e + @app.command("disable") def disable_plugin( @@ -165,4 +173,4 @@ def disable_plugin( typer.echo(f"Plugin '{plugin_id}' disabled.") except Exception as e: typer.echo(f"Failed to disable plugin '{plugin_id}': {e}", err=True) - raise typer.Exit(code=1) + raise typer.Exit(code=1) from e diff --git a/modules/cli/karrio_cli/commands/sdk.py b/modules/cli/karrio_cli/commands/sdk.py index fe8ca92ec6..0476cf45a4 100644 --- a/modules/cli/karrio_cli/commands/sdk.py +++ b/modules/cli/karrio_cli/commands/sdk.py @@ -1,13 +1,13 @@ +import datetime import os +import pathlib import time + import typer -import string -import pathlib from rich.progress import Progress, SpinnerColumn, TextColumn -import karrio_cli.templates as templates + import karrio_cli.common.utils as utils -import typing -import datetime +import karrio_cli.templates as templates def _add_extension( @@ -31,11 +31,7 @@ def _add_extension( features=features, version=version, is_xml_api=is_xml_api, - compact_name=name.strip() - .replace("-", "") - .replace("_", "") - .replace("&", "") - .replace(" ", ""), + compact_name=name.strip().replace("-", "").replace("_", "").replace("&", "").replace(" ", ""), ) # Update the directory templates with the base directory @@ -62,48 +58,28 @@ def _add_extension( time.sleep(1) # project files - templates.PYPROJECT_TEMPLATE.stream(**context).dump( - f"{root_dir}/pyproject.toml" + templates.PYPROJECT_TEMPLATE.stream(**context).dump(f"{root_dir}/pyproject.toml") + templates.README_TEMPLATE.stream(**context).dump(f"{root_dir}/README.md") + (templates.XML_GENERATE_TEMPLATE if is_xml_api else templates.JSON_GENERATE_TEMPLATE).stream(**context).dump( + f"{root_dir}/generate" ) - templates.README_TEMPLATE.stream(**context).dump( - f"{root_dir}/README.md" - ) - ( - templates.XML_GENERATE_TEMPLATE - if is_xml_api - else templates.JSON_GENERATE_TEMPLATE - ).stream(**context).dump(f"{root_dir}/generate") # schema files - create error response schema for all carriers if is_xml_api: - templates.XML_SCHEMA_ERROR_TEMPLATE.stream(**context).dump( - f"{schemas_dir}/error_response.xsd" - ) + templates.XML_SCHEMA_ERROR_TEMPLATE.stream(**context).dump(f"{schemas_dir}/error_response.xsd") else: - templates.JSON_SCHEMA_ERROR_TEMPLATE.stream(**context).dump( - f"{schemas_dir}/error_response.json" - ) + templates.JSON_SCHEMA_ERROR_TEMPLATE.stream(**context).dump(f"{schemas_dir}/error_response.json") - templates.EMPTY_FILE_TEMPLATE.stream(**context).dump( - f"{schema_datatypes_dir}/__init__.py" - ) + templates.EMPTY_FILE_TEMPLATE.stream(**context).dump(f"{schema_datatypes_dir}/__init__.py") # Generate schema files for selected features if "rating" in features: if is_xml_api: - templates.XML_SCHEMA_RATE_REQUEST_TEMPLATE.stream(**context).dump( - f"{schemas_dir}/rate_request.xsd" - ) - templates.XML_SCHEMA_RATE_RESPONSE_TEMPLATE.stream(**context).dump( - f"{schemas_dir}/rate_response.xsd" - ) + templates.XML_SCHEMA_RATE_REQUEST_TEMPLATE.stream(**context).dump(f"{schemas_dir}/rate_request.xsd") + templates.XML_SCHEMA_RATE_RESPONSE_TEMPLATE.stream(**context).dump(f"{schemas_dir}/rate_response.xsd") else: - templates.JSON_SCHEMA_RATE_REQUEST_TEMPLATE.stream(**context).dump( - f"{schemas_dir}/rate_request.json" - ) - templates.JSON_SCHEMA_RATE_RESPONSE_TEMPLATE.stream(**context).dump( - f"{schemas_dir}/rate_response.json" - ) + templates.JSON_SCHEMA_RATE_REQUEST_TEMPLATE.stream(**context).dump(f"{schemas_dir}/rate_request.json") + templates.JSON_SCHEMA_RATE_RESPONSE_TEMPLATE.stream(**context).dump(f"{schemas_dir}/rate_response.json") if "tracking" in features: if is_xml_api: @@ -205,170 +181,111 @@ def _add_extension( f"{schemas_dir}/address_validation_response.json" ) + if "location" in features: + if is_xml_api: + templates.XML_SCHEMA_LOCATION_REQUEST_TEMPLATE.stream(**context).dump( + f"{schemas_dir}/location_request.xsd" + ) + templates.XML_SCHEMA_LOCATION_RESPONSE_TEMPLATE.stream(**context).dump( + f"{schemas_dir}/location_response.xsd" + ) + else: + templates.JSON_SCHEMA_LOCATION_REQUEST_TEMPLATE.stream(**context).dump( + f"{schemas_dir}/location_request.json" + ) + templates.JSON_SCHEMA_LOCATION_RESPONSE_TEMPLATE.stream(**context).dump( + f"{schemas_dir}/location_response.json" + ) + # tests files - templates.TEST_FIXTURE_TEMPLATE.stream(**context).dump( - f"{tests_dir}/fixture.py" - ) - templates.TEST_PROVIDER_IMPORTS_TEMPLATE.stream(**context).dump( - f"{tests_dir}/__init__.py" - ) - templates.TEST_IMPORTS_TEMPLATE.stream(**context).dump( - f"{root_dir}/tests/__init__.py" - ) + templates.TEST_FIXTURE_TEMPLATE.stream(**context).dump(f"{tests_dir}/fixture.py") + templates.TEST_PROVIDER_IMPORTS_TEMPLATE.stream(**context).dump(f"{tests_dir}/__init__.py") + templates.TEST_IMPORTS_TEMPLATE.stream(**context).dump(f"{root_dir}/tests/__init__.py") # plugin files (new structure) - templates.PLUGIN_METADATA_TEMPLATE.stream(**context).dump( - f"{plugins_dir}/__init__.py" - ) + templates.PLUGIN_METADATA_TEMPLATE.stream(**context).dump(f"{plugins_dir}/__init__.py") # mappers files (legacy structure) - templates.MAPPER_TEMPLATE.stream(**context).dump( - f"{mappers_dir}/mapper.py" - ) - templates.MAPPER_PROXY_TEMPLATE.stream(**context).dump( - f"{mappers_dir}/proxy.py" - ) - templates.MAPPER_SETTINGS_TEMPLATE.stream(**context).dump( - f"{mappers_dir}/settings.py" - ) - templates.MAPPER_IMPORTS_TEMPLATE.stream(**context).dump( - f"{mappers_dir}/__init__.py" - ) + templates.MAPPER_TEMPLATE.stream(**context).dump(f"{mappers_dir}/mapper.py") + templates.MAPPER_PROXY_TEMPLATE.stream(**context).dump(f"{mappers_dir}/proxy.py") + templates.MAPPER_SETTINGS_TEMPLATE.stream(**context).dump(f"{mappers_dir}/settings.py") + templates.MAPPER_IMPORTS_TEMPLATE.stream(**context).dump(f"{mappers_dir}/__init__.py") # providers files - templates.PROVIDER_ERROR_TEMPLATE.stream(**context).dump( - f"{providers_dir}/error.py" - ) - templates.PROVIDER_UNITS_TEMPLATE.stream(**context).dump( - f"{providers_dir}/units.py" - ) - templates.PROVIDER_UTILS_TEMPLATE.stream(**context).dump( - f"{providers_dir}/utils.py" - ) - templates.PROVIDER_IMPORTS_TEMPLATE.stream(**context).dump( - f"{providers_dir}/__init__.py" - ) + templates.PROVIDER_ERROR_TEMPLATE.stream(**context).dump(f"{providers_dir}/error.py") + templates.PROVIDER_UNITS_TEMPLATE.stream(**context).dump(f"{providers_dir}/units.py") + templates.PROVIDER_UTILS_TEMPLATE.stream(**context).dump(f"{providers_dir}/utils.py") + templates.PROVIDER_IMPORTS_TEMPLATE.stream(**context).dump(f"{providers_dir}/__init__.py") if "address" in features: - templates.TEST_ADDRESS_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_address.py" - ) + templates.TEST_ADDRESS_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_address.py") - templates.PROVIDER_ADDRESS_TEMPLATE.stream(**context).dump( - f"{providers_dir}/address.py" - ) + templates.PROVIDER_ADDRESS_TEMPLATE.stream(**context).dump(f"{providers_dir}/address.py") + + if "location" in features: + templates.TEST_LOCATION_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_location.py") + + templates.PROVIDER_LOCATION_TEMPLATE.stream(**context).dump(f"{providers_dir}/location.py") if "rating" in features: - templates.TEST_RATE_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_rate.py" - ) + templates.TEST_RATE_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_rate.py") - templates.PROVIDER_RATE_TEMPLATE.stream(**context).dump( - f"{providers_dir}/rate.py" - ) + templates.PROVIDER_RATE_TEMPLATE.stream(**context).dump(f"{providers_dir}/rate.py") if "tracking" in features: - templates.TEST_TRACKING_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_tracking.py" - ) + templates.TEST_TRACKING_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_tracking.py") - templates.PROVIDER_TRACKING_TEMPLATE.stream(**context).dump( - f"{providers_dir}/tracking.py" - ) + templates.PROVIDER_TRACKING_TEMPLATE.stream(**context).dump(f"{providers_dir}/tracking.py") if "document" in features: - templates.TEST_DOCUMENT_UPLOAD_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_document.py" - ) + templates.TEST_DOCUMENT_UPLOAD_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_document.py") - templates.PROVIDER_DOCUMENT_UPLOAD_TEMPLATE.stream(**context).dump( - f"{providers_dir}/document.py" - ) + templates.PROVIDER_DOCUMENT_UPLOAD_TEMPLATE.stream(**context).dump(f"{providers_dir}/document.py") if "manifest" in features: - templates.TEST_MANIFEST_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_manifest.py" - ) + templates.TEST_MANIFEST_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_manifest.py") - templates.PROVIDER_MANIFEST_TEMPLATE.stream(**context).dump( - f"{providers_dir}/manifest.py" - ) + templates.PROVIDER_MANIFEST_TEMPLATE.stream(**context).dump(f"{providers_dir}/manifest.py") if "shipping" in features: - templates.TEST_SHIPMENT_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_shipment.py" - ) + templates.TEST_SHIPMENT_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_shipment.py") os.makedirs(f"{providers_dir}/shipment", exist_ok=True) - templates.PROVIDER_SHIPMENT_CANCEL_TEMPLATE.stream(**context).dump( - f"{providers_dir}/shipment/cancel.py" - ) - templates.PROVIDER_SHIPMENT_CREATE_TEMPLATE.stream(**context).dump( - f"{providers_dir}/shipment/create.py" - ) - templates.PROVIDER_SHIPMENT_IMPORTS_TEMPLATE.stream(**context).dump( - f"{providers_dir}/shipment/__init__.py" - ) + templates.PROVIDER_SHIPMENT_CANCEL_TEMPLATE.stream(**context).dump(f"{providers_dir}/shipment/cancel.py") + templates.PROVIDER_SHIPMENT_CREATE_TEMPLATE.stream(**context).dump(f"{providers_dir}/shipment/create.py") + templates.PROVIDER_SHIPMENT_IMPORTS_TEMPLATE.stream(**context).dump(f"{providers_dir}/shipment/__init__.py") if "pickup" in features: - templates.TEST_PICKUP_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_pickup.py" - ) + templates.TEST_PICKUP_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_pickup.py") os.makedirs(f"{providers_dir}/pickup", exist_ok=True) - templates.PROVIDER_PICKUP_CANCEL_TEMPLATE.stream(**context).dump( - f"{providers_dir}/pickup/cancel.py" - ) - templates.PROVIDER_PICKUP_CREATE_TEMPLATE.stream(**context).dump( - f"{providers_dir}/pickup/create.py" - ) - templates.PROVIDER_PICKUP_UPDATE_TEMPLATE.stream(**context).dump( - f"{providers_dir}/pickup/update.py" - ) - templates.PROVIDER_PICKUP_IMPORTS_TEMPLATE.stream(**context).dump( - f"{providers_dir}/pickup/__init__.py" - ) + templates.PROVIDER_PICKUP_CANCEL_TEMPLATE.stream(**context).dump(f"{providers_dir}/pickup/cancel.py") + templates.PROVIDER_PICKUP_CREATE_TEMPLATE.stream(**context).dump(f"{providers_dir}/pickup/create.py") + templates.PROVIDER_PICKUP_UPDATE_TEMPLATE.stream(**context).dump(f"{providers_dir}/pickup/update.py") + templates.PROVIDER_PICKUP_IMPORTS_TEMPLATE.stream(**context).dump(f"{providers_dir}/pickup/__init__.py") if "webhook" in features: os.makedirs(f"{providers_dir}/webhook", exist_ok=True) - templates.webhook.WEBHOOK_REGISTER_TEMPLATE.stream(**context).dump( - f"{providers_dir}/webhook/register.py" - ) + templates.webhook.WEBHOOK_REGISTER_TEMPLATE.stream(**context).dump(f"{providers_dir}/webhook/register.py") templates.webhook.WEBHOOK_DEREGISTER_TEMPLATE.stream(**context).dump( f"{providers_dir}/webhook/deregister.py" ) - templates.webhook.WEBHOOK_INIT_TEMPLATE.stream(**context).dump( - f"{providers_dir}/webhook/__init__.py" - ) + templates.webhook.WEBHOOK_INIT_TEMPLATE.stream(**context).dump(f"{providers_dir}/webhook/__init__.py") os.makedirs(f"{providers_dir}/callback", exist_ok=True) - templates.callback.CALLBACK_EVENT_TEMPLATE.stream(**context).dump( - f"{providers_dir}/callback/event.py" - ) - templates.callback.CALLBACK_OAUTH_TEMPLATE.stream(**context).dump( - f"{providers_dir}/callback/oauth.py" - ) - templates.callback.CALLBACK_INIT_TEMPLATE.stream(**context).dump( - f"{providers_dir}/callback/__init__.py" - ) + templates.callback.CALLBACK_EVENT_TEMPLATE.stream(**context).dump(f"{providers_dir}/callback/event.py") + templates.callback.CALLBACK_OAUTH_TEMPLATE.stream(**context).dump(f"{providers_dir}/callback/oauth.py") + templates.callback.CALLBACK_INIT_TEMPLATE.stream(**context).dump(f"{providers_dir}/callback/__init__.py") - templates.callback.CALLBACK_MAPPER_TEMPLATE.stream(**context).dump( - f"{mappers_dir}/callback.py" - ) + templates.callback.CALLBACK_MAPPER_TEMPLATE.stream(**context).dump(f"{mappers_dir}/callback.py") if "duties" in features: - templates.duties.DUTIES_TEMPLATE.stream(**context).dump( - f"{providers_dir}/duties.py" - ) + templates.duties.DUTIES_TEMPLATE.stream(**context).dump(f"{providers_dir}/duties.py") if "insurance" in features: os.makedirs(f"{providers_dir}/insurance", exist_ok=True) - templates.insurance.INSURANCE_APPLY_TEMPLATE.stream(**context).dump( - f"{providers_dir}/insurance/apply.py" - ) - templates.insurance.INSURANCE_INIT_TEMPLATE.stream(**context).dump( - f"{providers_dir}/insurance/__init__.py" - ) + templates.insurance.INSURANCE_APPLY_TEMPLATE.stream(**context).dump(f"{providers_dir}/insurance/apply.py") + templates.insurance.INSURANCE_INIT_TEMPLATE.stream(**context).dump(f"{providers_dir}/insurance/__init__.py") if "manifest" in features: if is_xml_api: @@ -403,87 +320,54 @@ def _add_extension( ) if "address" in features: - templates.TEST_ADDRESS_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_address.py" - ) + templates.TEST_ADDRESS_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_address.py") - templates.PROVIDER_ADDRESS_TEMPLATE.stream(**context).dump( - f"{providers_dir}/address.py" - ) + templates.PROVIDER_ADDRESS_TEMPLATE.stream(**context).dump(f"{providers_dir}/address.py") + + if "location" in features: + templates.TEST_LOCATION_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_location.py") + + templates.PROVIDER_LOCATION_TEMPLATE.stream(**context).dump(f"{providers_dir}/location.py") if "rating" in features: - templates.TEST_RATE_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_rate.py" - ) + templates.TEST_RATE_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_rate.py") - templates.PROVIDER_RATE_TEMPLATE.stream(**context).dump( - f"{providers_dir}/rate.py" - ) + templates.PROVIDER_RATE_TEMPLATE.stream(**context).dump(f"{providers_dir}/rate.py") if "tracking" in features: - templates.TEST_TRACKING_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_tracking.py" - ) + templates.TEST_TRACKING_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_tracking.py") - templates.PROVIDER_TRACKING_TEMPLATE.stream(**context).dump( - f"{providers_dir}/tracking.py" - ) + templates.PROVIDER_TRACKING_TEMPLATE.stream(**context).dump(f"{providers_dir}/tracking.py") if "document" in features: - templates.TEST_DOCUMENT_UPLOAD_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_document.py" - ) + templates.TEST_DOCUMENT_UPLOAD_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_document.py") - templates.PROVIDER_DOCUMENT_UPLOAD_TEMPLATE.stream(**context).dump( - f"{providers_dir}/document.py" - ) + templates.PROVIDER_DOCUMENT_UPLOAD_TEMPLATE.stream(**context).dump(f"{providers_dir}/document.py") if "manifest" in features: - templates.TEST_MANIFEST_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_manifest.py" - ) + templates.TEST_MANIFEST_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_manifest.py") - templates.PROVIDER_MANIFEST_TEMPLATE.stream(**context).dump( - f"{providers_dir}/manifest.py" - ) + templates.PROVIDER_MANIFEST_TEMPLATE.stream(**context).dump(f"{providers_dir}/manifest.py") if "shipping" in features: - templates.TEST_SHIPMENT_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_shipment.py" - ) + templates.TEST_SHIPMENT_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_shipment.py") os.makedirs(f"{providers_dir}/shipment", exist_ok=True) - templates.PROVIDER_SHIPMENT_CANCEL_TEMPLATE.stream(**context).dump( - f"{providers_dir}/shipment/cancel.py" - ) - templates.PROVIDER_SHIPMENT_CREATE_TEMPLATE.stream(**context).dump( - f"{providers_dir}/shipment/create.py" - ) + templates.PROVIDER_SHIPMENT_CANCEL_TEMPLATE.stream(**context).dump(f"{providers_dir}/shipment/cancel.py") + templates.PROVIDER_SHIPMENT_CREATE_TEMPLATE.stream(**context).dump(f"{providers_dir}/shipment/create.py") templates.PROVIDER_SHIPMENT_RETURN_TEMPLATE.stream(**context).dump( f"{providers_dir}/shipment/return_shipment.py" ) - templates.PROVIDER_SHIPMENT_IMPORTS_TEMPLATE.stream(**context).dump( - f"{providers_dir}/shipment/__init__.py" - ) + templates.PROVIDER_SHIPMENT_IMPORTS_TEMPLATE.stream(**context).dump(f"{providers_dir}/shipment/__init__.py") if "pickup" in features: - templates.TEST_PICKUP_TEMPLATE.stream(**context).dump( - f"{tests_dir}/test_pickup.py" - ) + templates.TEST_PICKUP_TEMPLATE.stream(**context).dump(f"{tests_dir}/test_pickup.py") os.makedirs(f"{providers_dir}/pickup", exist_ok=True) - templates.PROVIDER_PICKUP_CANCEL_TEMPLATE.stream(**context).dump( - f"{providers_dir}/pickup/cancel.py" - ) - templates.PROVIDER_PICKUP_CREATE_TEMPLATE.stream(**context).dump( - f"{providers_dir}/pickup/create.py" - ) - templates.PROVIDER_PICKUP_UPDATE_TEMPLATE.stream(**context).dump( - f"{providers_dir}/pickup/update.py" - ) - templates.PROVIDER_PICKUP_IMPORTS_TEMPLATE.stream(**context).dump( - f"{providers_dir}/pickup/__init__.py" - ) + templates.PROVIDER_PICKUP_CANCEL_TEMPLATE.stream(**context).dump(f"{providers_dir}/pickup/cancel.py") + templates.PROVIDER_PICKUP_CREATE_TEMPLATE.stream(**context).dump(f"{providers_dir}/pickup/create.py") + templates.PROVIDER_PICKUP_UPDATE_TEMPLATE.stream(**context).dump(f"{providers_dir}/pickup/update.py") + templates.PROVIDER_PICKUP_IMPORTS_TEMPLATE.stream(**context).dump(f"{providers_dir}/pickup/__init__.py") typer.echo("Done!") @@ -512,11 +396,7 @@ def _add_features( name=name, features=features, is_xml_api=is_xml_api, - compact_name=name.strip() - .replace("-", "") - .replace("_", "") - .replace("&", "") - .replace(" ", ""), + compact_name=name.strip().replace("-", "").replace("_", "").replace("&", "").replace(" ", ""), ) # Update the directory templates with the base directory @@ -526,8 +406,6 @@ def _add_features( plugins_dir = os.path.join(root_dir, "karrio", "plugins", id) # New plugin structure mappers_dir = os.path.join(root_dir, "karrio", "mappers", id) providers_dir = os.path.join(root_dir, "karrio", "providers", id) - schema_datatypes_dir = os.path.join(root_dir, "karrio", "schemas", id) - with Progress( SpinnerColumn(), TextColumn("[progress.description]{task.description}"), @@ -556,14 +434,10 @@ def _add_features( ) utils.karrio_log("Adding rating test file...") - templates.TEST_RATE_TEMPLATE.stream(**context).dump( - os.path.join(tests_dir, "test_rate.py") - ) + templates.TEST_RATE_TEMPLATE.stream(**context).dump(os.path.join(tests_dir, "test_rate.py")) utils.karrio_log("Adding rating provider files...") - templates.PROVIDER_RATE_TEMPLATE.stream(**context).dump( - os.path.join(providers_dir, "rate.py") - ) + templates.PROVIDER_RATE_TEMPLATE.stream(**context).dump(os.path.join(providers_dir, "rate.py")) if "tracking" in features: if is_xml_api: @@ -584,20 +458,14 @@ def _add_features( ) utils.karrio_log("Adding tracking test file...") - templates.TEST_TRACKING_TEMPLATE.stream(**context).dump( - os.path.join(tests_dir, "test_tracking.py") - ) + templates.TEST_TRACKING_TEMPLATE.stream(**context).dump(os.path.join(tests_dir, "test_tracking.py")) utils.karrio_log("Adding tracking provider files...") - templates.PROVIDER_TRACKING_TEMPLATE.stream(**context).dump( - os.path.join(providers_dir, "tracking.py") - ) + templates.PROVIDER_TRACKING_TEMPLATE.stream(**context).dump(os.path.join(providers_dir, "tracking.py")) if "shipping" in features: os.makedirs(os.path.join(providers_dir, "shipment"), exist_ok=True) - templates.EMPTY_FILE_TEMPLATE.stream(**context).dump( - os.path.join(providers_dir, "shipment", "__init__.py") - ) + templates.EMPTY_FILE_TEMPLATE.stream(**context).dump(os.path.join(providers_dir, "shipment", "__init__.py")) if is_xml_api: utils.karrio_log("Adding shipment schema files...") @@ -629,9 +497,7 @@ def _add_features( ) utils.karrio_log("Adding shipment test file...") - templates.TEST_SHIPMENT_TEMPLATE.stream(**context).dump( - os.path.join(tests_dir, "test_shipment.py") - ) + templates.TEST_SHIPMENT_TEMPLATE.stream(**context).dump(os.path.join(tests_dir, "test_shipment.py")) utils.karrio_log("Adding shipment provider files...") templates.PROVIDER_SHIPMENT_TEMPLATE.stream(**context).dump( @@ -649,9 +515,7 @@ def _add_features( if "pickup" in features: os.makedirs(os.path.join(providers_dir, "pickup"), exist_ok=True) - templates.EMPTY_FILE_TEMPLATE.stream(**context).dump( - os.path.join(providers_dir, "pickup", "__init__.py") - ) + templates.EMPTY_FILE_TEMPLATE.stream(**context).dump(os.path.join(providers_dir, "pickup", "__init__.py")) if is_xml_api: utils.karrio_log("Adding pickup schema files...") @@ -695,9 +559,7 @@ def _add_features( ) utils.karrio_log("Adding pickup test file...") - templates.TEST_PICKUP_TEMPLATE.stream(**context).dump( - os.path.join(tests_dir, "test_pickup.py") - ) + templates.TEST_PICKUP_TEMPLATE.stream(**context).dump(os.path.join(tests_dir, "test_pickup.py")) utils.karrio_log("Adding pickup provider files...") templates.PROVIDER_PICKUP_TEMPLATE.stream(**context).dump( @@ -709,9 +571,7 @@ def _add_features( templates.PROVIDER_PICKUP_CANCEL_TEMPLATE.stream(**context).dump( os.path.join(providers_dir, "pickup", "cancel.py") ) - templates.PROVIDER_PICKUP_INDEX_TEMPLATE.stream(**context).dump( - os.path.join(providers_dir, "pickup.py") - ) + templates.PROVIDER_PICKUP_INDEX_TEMPLATE.stream(**context).dump(os.path.join(providers_dir, "pickup.py")) if "address" in features: if is_xml_api: @@ -732,24 +592,40 @@ def _add_features( ) utils.karrio_log("Adding address test file...") - templates.TEST_ADDRESS_TEMPLATE.stream(**context).dump( - os.path.join(tests_dir, "test_address.py") - ) + templates.TEST_ADDRESS_TEMPLATE.stream(**context).dump(os.path.join(tests_dir, "test_address.py")) utils.karrio_log("Adding address provider files...") - templates.PROVIDER_ADDRESS_TEMPLATE.stream(**context).dump( - os.path.join(providers_dir, "address.py") - ) + templates.PROVIDER_ADDRESS_TEMPLATE.stream(**context).dump(os.path.join(providers_dir, "address.py")) # If this is a validator plugin, create validators directory validators_dir = os.path.join(root_dir, "karrio", "validators", id) os.makedirs(validators_dir, exist_ok=True) - templates.VALIDATOR_TEMPLATE.stream(**context).dump( - os.path.join(validators_dir, "validator.py") - ) - templates.VALIDATOR_IMPORTS_TEMPLATE.stream(**context).dump( - os.path.join(validators_dir, "__init__.py") - ) + templates.VALIDATOR_TEMPLATE.stream(**context).dump(os.path.join(validators_dir, "validator.py")) + templates.VALIDATOR_IMPORTS_TEMPLATE.stream(**context).dump(os.path.join(validators_dir, "__init__.py")) + + if "location" in features: + if is_xml_api: + utils.karrio_log("Adding location schema files...") + templates.XML_SCHEMA_LOCATION_REQUEST_TEMPLATE.stream(**context).dump( + os.path.join(schemas_dir, "location_request.xsd") + ) + templates.XML_SCHEMA_LOCATION_RESPONSE_TEMPLATE.stream(**context).dump( + os.path.join(schemas_dir, "location_response.xsd") + ) + else: + utils.karrio_log("Adding location schema files...") + templates.JSON_SCHEMA_LOCATION_REQUEST_TEMPLATE.stream(**context).dump( + os.path.join(schemas_dir, "location_request.json") + ) + templates.JSON_SCHEMA_LOCATION_RESPONSE_TEMPLATE.stream(**context).dump( + os.path.join(schemas_dir, "location_response.json") + ) + + utils.karrio_log("Adding location test file...") + templates.TEST_LOCATION_TEMPLATE.stream(**context).dump(os.path.join(tests_dir, "test_location.py")) + + utils.karrio_log("Adding location provider files...") + templates.PROVIDER_LOCATION_TEMPLATE.stream(**context).dump(os.path.join(providers_dir, "location.py")) if "document" in features: if is_xml_api: @@ -770,14 +646,10 @@ def _add_features( ) utils.karrio_log("Adding document test file...") - templates.TEST_DOCUMENT_TEMPLATE.stream(**context).dump( - os.path.join(tests_dir, "test_document.py") - ) + templates.TEST_DOCUMENT_TEMPLATE.stream(**context).dump(os.path.join(tests_dir, "test_document.py")) utils.karrio_log("Adding document provider files...") - templates.PROVIDER_DOCUMENT_TEMPLATE.stream(**context).dump( - os.path.join(providers_dir, "document.py") - ) + templates.PROVIDER_DOCUMENT_TEMPLATE.stream(**context).dump(os.path.join(providers_dir, "document.py")) if "manifest" in features: if is_xml_api: @@ -798,14 +670,10 @@ def _add_features( ) utils.karrio_log("Adding manifest test file...") - templates.TEST_MANIFEST_TEMPLATE.stream(**context).dump( - os.path.join(tests_dir, "test_manifest.py") - ) + templates.TEST_MANIFEST_TEMPLATE.stream(**context).dump(os.path.join(tests_dir, "test_manifest.py")) utils.karrio_log("Adding manifest provider files...") - templates.PROVIDER_MANIFEST_TEMPLATE.stream(**context).dump( - os.path.join(providers_dir, "manifest.py") - ) + templates.PROVIDER_MANIFEST_TEMPLATE.stream(**context).dump(os.path.join(providers_dir, "manifest.py")) # Create plugins directory if it doesn't exist (for new structure) if not os.path.exists(plugins_dir): @@ -817,19 +685,15 @@ def _add_features( mappers_init_path = os.path.join(mappers_dir, "__init__.py") if os.path.exists(mappers_init_path): utils.karrio_log("Creating plugin METADATA file...") - templates.PLUGIN_METADATA_TEMPLATE.stream(**context).dump( - os.path.join(plugins_dir, "__init__.py") - ) + templates.PLUGIN_METADATA_TEMPLATE.stream(**context).dump(os.path.join(plugins_dir, "__init__.py")) # If the mapper has METADATA, convert it to just imports - with open(mappers_init_path, "r") as f: + with open(mappers_init_path) as f: content = f.read() if "METADATA" in content: utils.karrio_log("Updating mapper __init__.py to use imports only...") - templates.MAPPER_IMPORTS_TEMPLATE.stream(**context).dump( - mappers_init_path - ) + templates.MAPPER_IMPORTS_TEMPLATE.stream(**context).dump(mappers_init_path) app = typer.Typer() @@ -839,23 +703,15 @@ def _add_features( def add_extension( path: str = typer.Option(..., "--path", "-p", help="Path where the extension will be created"), carrier_slug: str = typer.Option( - ..., - prompt=True, - help="The unique identifier for the carrier (e.g., dhl_express, ups, fedex, canadapost)" + ..., prompt=True, help="The unique identifier for the carrier (e.g., dhl_express, ups, fedex, canadapost)" ), display_name: str = typer.Option( - ..., - prompt=True, - help="The human-readable name for the carrier (e.g., DHL, UPS, FedEx, Canada Post)" + ..., prompt=True, help="The human-readable name for the carrier (e.g., DHL, UPS, FedEx, Canada Post)" ), - features: typing.Optional[str] = typer.Option( - ", ".join(utils.DEFAULT_FEATURES), prompt=True - ), - version: typing.Optional[str] = typer.Option( - f"{datetime.datetime.now().strftime('%Y.%-m')}", prompt=True - ), - is_xml_api: typing.Optional[bool] = typer.Option(False, prompt="Is XML API?"), - confirm: typing.Optional[bool] = typer.Option(False, "--confirm", help="Skip confirmation prompt"), + features: str | None = typer.Option(", ".join(utils.DEFAULT_FEATURES), prompt=True), + version: str | None = typer.Option(f"{datetime.datetime.now().strftime('%Y.%-m')}", prompt=True), + is_xml_api: bool | None = typer.Option(False, prompt="Is XML API?"), + confirm: bool | None = typer.Option(False, "--confirm", help="Skip confirmation prompt"), ): # Resolve the provided path for confirmation full_path = pathlib.Path(path).resolve() / carrier_slug.lower() @@ -885,21 +741,15 @@ def add_extension( @app.command() def add_features( carrier_slug: str = typer.Option( - ..., - prompt=True, - help="The unique identifier for the carrier (e.g., dhl_express, ups, fedex, canadapost)" + ..., prompt=True, help="The unique identifier for the carrier (e.g., dhl_express, ups, fedex, canadapost)" ), display_name: str = typer.Option( - ..., - prompt=True, - help="The human-readable name for the carrier (e.g., DHL, UPS, FedEx, Canada Post)" - ), - features: typing.Optional[str] = typer.Option( - ", ".join(utils.DEFAULT_FEATURES), prompt=True + ..., prompt=True, help="The human-readable name for the carrier (e.g., DHL, UPS, FedEx, Canada Post)" ), - is_xml_api: typing.Optional[bool] = typer.Option(False, prompt="Is XML API?"), + features: str | None = typer.Option(", ".join(utils.DEFAULT_FEATURES), prompt=True), + is_xml_api: bool | None = typer.Option(False, prompt="Is XML API?"), path: str = typer.Option(..., "--path", "-p", help="Path where the features will be created"), - confirm: typing.Optional[bool] = typer.Option(False, "--confirm", help="Skip confirmation prompt"), + confirm: bool | None = typer.Option(False, "--confirm", help="Skip confirmation prompt"), ): # Resolve the provided path for confirmation full_path = pathlib.Path(path).resolve() / carrier_slug.lower() diff --git a/modules/cli/karrio_cli/commands/studio.py b/modules/cli/karrio_cli/commands/studio.py index 014763613b..94023a41da 100644 --- a/modules/cli/karrio_cli/commands/studio.py +++ b/modules/cli/karrio_cli/commands/studio.py @@ -1,18 +1,20 @@ +import importlib.util +import json +import logging import os +import pathlib +import shutil +import subprocess import sys -import json import time +from typing import Any + import typer import uvicorn -import logging -import pathlib -import subprocess -import shutil -import importlib.util -from typing import List, Optional, Tuple, Dict, Any -from fastapi import FastAPI, Request, Form, HTTPException +from fastapi import FastAPI, Form, HTTPException, Request from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates + import karrio_cli.commands.sdk as sdk_cmd app = typer.Typer() @@ -25,11 +27,11 @@ ROOT_DIR = pathlib.Path(os.getenv("KARRIO_ROOT", os.getcwd())).resolve() CACHE_TTL = 5 -EXTENSION_CACHE: Dict[str, Any] = {"timestamp": 0, "data": []} -AI_TOOL_CACHE: Dict[str, Any] = {"timestamp": 0, "tools": []} +EXTENSION_CACHE: dict[str, Any] = {"timestamp": 0, "data": []} +AI_TOOL_CACHE: dict[str, Any] = {"timestamp": 0, "tools": []} -def detect_ai_tools(force: bool = False) -> List[Dict[str, Any]]: +def detect_ai_tools(force: bool = False) -> list[dict[str, Any]]: now = time.time() if not force and now - AI_TOOL_CACHE["timestamp"] < CACHE_TTL: return AI_TOOL_CACHE["tools"] @@ -333,11 +335,11 @@ def detect_ai_tools(force: bool = False) -> List[Dict[str, Any]]: ] -def _scan_extensions() -> List[Dict[str, Any]]: +def _scan_extensions() -> list[dict[str, Any]]: connectors_dir = ROOT_DIR / "modules" / "connectors" plugins_dir = ROOT_DIR / "community" / "plugins" - extensions: List[Dict[str, Any]] = [] + extensions: list[dict[str, Any]] = [] for base_dir in [connectors_dir, plugins_dir]: if not base_dir.exists(): @@ -380,7 +382,7 @@ def _scan_extensions() -> List[Dict[str, Any]]: return extensions -def get_extensions(force_refresh: bool = False) -> List[Dict[str, Any]]: +def get_extensions(force_refresh: bool = False) -> list[dict[str, Any]]: now = time.time() if not force_refresh and now - EXTENSION_CACHE["timestamp"] < CACHE_TTL: return EXTENSION_CACHE["data"] @@ -390,22 +392,28 @@ def get_extensions(force_refresh: bool = False) -> List[Dict[str, Any]]: EXTENSION_CACHE["data"] = data return data + @studio_api.get("/", response_class=HTMLResponse) async def index(request: Request): return templates.TemplateResponse("index.html", {"request": request}) + @studio_api.get("/partials/dashboard", response_class=HTMLResponse) async def dashboard(request: Request): extensions = get_extensions() - connectors_count = sum(1 for e in extensions if e['type'] == 'connector') + connectors_count = sum(1 for e in extensions if e["type"] == "connector") plugins_count = len(extensions) - connectors_count - - return templates.TemplateResponse("dashboard.html", { - "request": request, - "extensions": extensions, - "connectors_count": connectors_count, - "plugins_count": plugins_count - }) + + return templates.TemplateResponse( + "dashboard.html", + { + "request": request, + "extensions": extensions, + "connectors_count": connectors_count, + "plugins_count": plugins_count, + }, + ) + @studio_api.get("/api/extensions") async def list_extensions_sidebar(request: Request): @@ -415,24 +423,25 @@ async def list_extensions_sidebar(request: Request): {"request": request, "extensions": extensions}, ) + @studio_api.get("/partials/new-extension", response_class=HTMLResponse) async def new_extension_form(request: Request): return templates.TemplateResponse("new_extension.html", {"request": request}) + @studio_api.post("/api/extensions", response_class=HTMLResponse) async def create_extension( request: Request, display_name: str = Form(...), carrier_slug: str = Form(...), type: str = Form(...), - features: List[str] = Form(...), - is_xml_api: bool = Form(False) + features: list[str] = Form(...), + is_xml_api: bool = Form(False), ): try: - root_dir = pathlib.Path(os.getcwd()) path = "modules/connectors" if type == "connector" else "community/plugins" - version = "2025.5" # Default version - + version = "2025.5" # Default version + # Call the SDK command logic sdk_cmd._add_extension( id=carrier_slug.lower(), @@ -440,11 +449,11 @@ async def create_extension( feature=",".join(features), version=version, is_xml_api=is_xml_api, - path=path + path=path, ) get_extensions(force_refresh=True) - + # Return a success toast and redirect to dashboard response = await dashboard(request) response.headers["HX-Trigger"] = json.dumps( @@ -454,30 +463,23 @@ async def create_extension( } ) return response - + except Exception as e: logging.error(f"Failed to create extension: {e}") # Return error toast (using HX-Trigger with error) return HTMLResponse( status_code=200, # HTMX handles 200 best for swapping content=f"
Error: {str(e)}
", - headers={ - "HX-Trigger": json.dumps( - {"notify": {"message": "Failed to create extension", "type": "error"}} - ) - }, + headers={"HX-Trigger": json.dumps({"notify": {"message": "Failed to create extension", "type": "error"}})}, ) -def _format_progress_report(items: List[Dict[str, Any]]) -> str: - return "\n".join( - f"{'✅' if item['status'] else '⚠️'} {item['label']}: {item['detail']}" - for item in items - ) +def _format_progress_report(items: list[dict[str, Any]]) -> str: + return "\n".join(f"{'✅' if item['status'] else '⚠️'} {item['label']}: {item['detail']}" for item in items) -def _evaluate_extension(ext: Dict[str, Any]) -> Dict[str, Any]: - items: List[Dict[str, Any]] = [] +def _evaluate_extension(ext: dict[str, Any]) -> dict[str, Any]: + items: list[dict[str, Any]] = [] def add(label: str, detail: str, status: bool): items.append({"label": label, "detail": detail, "status": status}) @@ -489,36 +491,26 @@ def add(label: str, detail: str, status: bool): add("Generate script", "generate file present", generate_script.exists()) schemas_dir = root / "schemas" - schema_files = ( - [f for f in schemas_dir.glob("*") if f.is_file()] if schemas_dir.exists() else [] - ) + schema_files = [f for f in schemas_dir.glob("*") if f.is_file()] if schemas_dir.exists() else [] add("Schema samples", f"{len(schema_files)} files in schemas/", len(schema_files) >= 2) generated_dir = root / "karrio" / "schemas" / carrier_id - generated_files = ( - [f for f in generated_dir.glob("*.py")] if generated_dir.exists() else [] - ) + generated_files = [f for f in generated_dir.glob("*.py")] if generated_dir.exists() else [] add("Generated dataclasses", f"{len(generated_files)} python files", len(generated_files) > 0) providers_dir = root / "karrio" / "providers" / carrier_id - provider_files = ( - [f for f in providers_dir.glob("*.py")] if providers_dir.exists() else [] - ) + provider_files = [f for f in providers_dir.glob("*.py")] if providers_dir.exists() else [] add("Provider logic", f"{len(provider_files)} modules", len(provider_files) > 0) tests_dir = root / "tests" / carrier_id - test_files = ( - [f for f in tests_dir.glob("test_*.py")] if tests_dir.exists() else [] - ) + test_files = [f for f in tests_dir.glob("test_*.py")] if tests_dir.exists() else [] add("Carrier tests", f"{len(test_files)} test files", len(test_files) > 0) plugin_file = root / "karrio" / "plugins" / carrier_id / "__init__.py" metadata_ok = False if plugin_file.exists(): try: - spec = importlib.util.spec_from_file_location( - f"studio_plugin_{carrier_id}", plugin_file - ) + spec = importlib.util.spec_from_file_location(f"studio_plugin_{carrier_id}", plugin_file) if spec and spec.loader: module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) # type: ignore @@ -539,7 +531,8 @@ def add(label: str, detail: str, status: bool): "report": _format_progress_report(items), } -def get_extension_details(id: str) -> Optional[Tuple[Dict[str, Any], List[str]]]: + +def get_extension_details(id: str) -> tuple[dict[str, Any], list[str]] | None: lookup = id.strip().lower() extensions = get_extensions() ext = next((e for e in extensions if e["id"].lower() == lookup), None) @@ -551,20 +544,19 @@ def get_extension_details(id: str) -> Optional[Tuple[Dict[str, Any], List[str]]] ext_path = ROOT_DIR / ext["path"] schema_dir = ext_path / "schemas" - schemas: List[str] = [] + schemas: list[str] = [] if schema_dir.exists(): - schemas = sorted( - f.name for f in schema_dir.iterdir() if f.is_file() and f.suffix in [".json", ".xsd"] - ) + schemas = sorted(f.name for f in schema_dir.iterdir() if f.is_file() and f.suffix in [".json", ".xsd"]) return ext, schemas + @studio_api.get("/partials/extension/{id}", response_class=HTMLResponse) async def extension_detail(request: Request, id: str): data = get_extension_details(id) if not data: return HTMLResponse("Extension not found", status_code=404) - + ext, schemas = data progress = _evaluate_extension(ext) prompt_cards = [ @@ -590,7 +582,7 @@ async def extension_detail(request: Request, id: str): ) -def run_cli_command(command: List[str]) -> Dict[str, Any]: +def run_cli_command(command: list[str]) -> dict[str, Any]: """Execute repository CLI commands and capture output.""" process = subprocess.run( command, @@ -606,7 +598,7 @@ def run_cli_command(command: List[str]) -> Dict[str, Any]: } -def render_action_response(request: Request, title: str, result: Dict[str, Any]): +def render_action_response(request: Request, title: str, result: dict[str, Any]): status = "success" if result["success"] else "error" message = f"{title} {'completed' if result['success'] else 'failed'}" headers = {"HX-Trigger": json.dumps({"notify": {"message": message, "type": status}})} @@ -629,14 +621,14 @@ def render_action_response(request: Request, title: str, result: Dict[str, Any]) ) -def _build_script_command(script_id: str, ext: Dict[str, Any]) -> Optional[List[str]]: +def _build_script_command(script_id: str, ext: dict[str, Any]) -> list[str] | None: definition = SCRIPT_COMMAND_DEFS.get(script_id) if not definition: return None return definition["build"](ext) -def _build_ai_prompt(prompt_id: str, ext: Dict[str, Any], progress: Dict[str, Any]) -> Optional[str]: +def _build_ai_prompt(prompt_id: str, ext: dict[str, Any], progress: dict[str, Any]) -> str | None: definition = AI_PROMPT_DEFS.get(prompt_id) if not definition: return None @@ -650,7 +642,7 @@ def _build_ai_prompt(prompt_id: str, ext: Dict[str, Any], progress: Dict[str, An return definition["template"].format(**context) -def _run_ai_prompt(prompt: str) -> Dict[str, Any]: +def _run_ai_prompt(prompt: str) -> dict[str, Any]: tools = detect_ai_tools() tool = next((t for t in tools if t["preferred"]), tools[0] if tools else None) @@ -751,5 +743,6 @@ def start( typer.echo(f"Starting Karrio Studio at http://{host}:{port}") uvicorn.run(studio_api, host=host, port=port) + if __name__ == "__main__": app() diff --git a/modules/cli/karrio_cli/common/utils.py b/modules/cli/karrio_cli/common/utils.py index c70b8443fe..d579a4e29a 100644 --- a/modules/cli/karrio_cli/common/utils.py +++ b/modules/cli/karrio_cli/common/utils.py @@ -1,13 +1,14 @@ +import importlib import json -import enum import pydoc -import typer -import requests -import importlib + import karrio_cli.commands.login as login -from rich.syntax import Syntax +import requests +import typer from rich.console import Console -from .queries import GET_LOGS, GET_LOG, GET_EVENTS, GET_EVENT +from rich.syntax import Syntax + +from .queries import GET_EVENT, GET_EVENTS, GET_LOG, GET_LOGS DEFAULT_FEATURES = [ "tracking", @@ -55,6 +56,7 @@ } """ + def parse_json_or_xml_string(value: str): """Parse a string that might be JSON or XML.""" if not value or not isinstance(value, str): @@ -80,7 +82,9 @@ def parse_records(records): **record["record"], "data": parse_json_or_xml_string(record["record"].get("data")), "response": parse_json_or_xml_string(record["record"].get("response")), - } if record.get("record") else record["record"] + } + if record.get("record") + else record["record"], } for record in records ] @@ -181,9 +185,7 @@ def make_get_request( ) -def make_post_request( - endpoint: str, payload: dict, pretty_print: bool = False, line_numbers: bool = False -): +def make_post_request(endpoint: str, payload: dict, pretty_print: bool = False, line_numbers: bool = False): return make_request( "POST", endpoint, @@ -193,9 +195,7 @@ def make_post_request( ) -def make_patch_request( - endpoint: str, payload: dict, pretty_print: bool = False, line_numbers: bool = False -): +def make_patch_request(endpoint: str, payload: dict, pretty_print: bool = False, line_numbers: bool = False): return make_request( "PATCH", endpoint, @@ -205,12 +205,8 @@ def make_patch_request( ) -def make_delete_request( - endpoint: str, pretty_print: bool = False, line_numbers: bool = False -): - return make_request( - "DELETE", endpoint, pretty_print=pretty_print, line_numbers=line_numbers - ) +def make_delete_request(endpoint: str, pretty_print: bool = False, line_numbers: bool = False): + return make_request("DELETE", endpoint, pretty_print=pretty_print, line_numbers=line_numbers) def parse_event_response(data): @@ -251,10 +247,7 @@ def make_graphql_request( if not query: raise ValueError(f"Unknown query: {query_name}") - payload = { - "query": query, - "variables": variables or {} - } + payload = {"query": query, "variables": variables or {}} try: response = requests.post(url, json=payload, headers=headers) @@ -289,9 +282,9 @@ def gen(entity): def format_dimension(code, dim): return ( - f"| `{ code }` " - f'| { f" x ".join([str(d) for d in dim.values() if isinstance(d, float)]) } ' - f'| { f" x ".join([k for k in dim.keys() if isinstance(dim[k], float)]) }' + f"| `{code}` " + f"| {' x '.join([str(d) for d in dim.values() if isinstance(d, float)])} " + f"| {' x '.join([k for k in dim.keys() if isinstance(dim[k], float)])}" ) @@ -317,10 +310,7 @@ def instantiate_tree(cls, indent=0, alias=""): else: tree += " " * indent * 4 + f"{name}=[],\n" elif hasattr(typ, "__annotations__"): - tree += ( - " " * indent * 4 - + f"{name}={instantiate_tree(typ, indent, alias=alias)},\n" - ) + tree += " " * indent * 4 + f"{name}={instantiate_tree(typ, indent, alias=alias)},\n" else: tree += " " * indent * 4 + f"{name}=None,\n" @@ -359,9 +349,7 @@ def set_nested(d, path, value): for prop in properties: if "=" not in prop: - raise ValueError( - f"Invalid property format: {prop}. Use 'key=value' or 'nested[key]=value'." - ) + raise ValueError(f"Invalid property format: {prop}. Use 'key=value' or 'nested[key]=value'.") path, value = prop.split("=", 1) set_nested(result, path, value) diff --git a/modules/cli/karrio_cli/resources/carriers.py b/modules/cli/karrio_cli/resources/carriers.py index d5f7f7154a..4eeb117664 100644 --- a/modules/cli/karrio_cli/resources/carriers.py +++ b/modules/cli/karrio_cli/resources/carriers.py @@ -1,9 +1,10 @@ import typer + import karrio_cli.common.utils as utils -import typing app = typer.Typer() + @app.command("list") def list_carriers( pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), @@ -45,9 +46,8 @@ def list_carriers( } ``` """ - utils.make_get_request( - "v1/carriers", params=None, pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_get_request("v1/carriers", params=None, pretty_print=pretty, line_numbers=line_numbers) + @app.command("retrieve") def retrieve_carrier( @@ -86,6 +86,4 @@ def retrieve_carrier( } ``` """ - utils.make_get_request( - f"v1/carriers/{carrier_name}", pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_get_request(f"v1/carriers/{carrier_name}", pretty_print=pretty, line_numbers=line_numbers) diff --git a/modules/cli/karrio_cli/resources/connections.py b/modules/cli/karrio_cli/resources/connections.py index 06070f834c..a9e1a65958 100644 --- a/modules/cli/karrio_cli/resources/connections.py +++ b/modules/cli/karrio_cli/resources/connections.py @@ -1,12 +1,13 @@ import typer + import karrio_cli.common.utils as utils -import typing app = typer.Typer() + @app.command("list") def list_connections( - carrier_name: typing.Optional[str] = None, + carrier_name: str | None = None, system_only: bool = typer.Option(False, "--system-only", help="Filter for system connections only"), limit: int = typer.Option(20, help="Number of results to return per page"), offset: int = typer.Option(0, help="The initial index from which to return the results"), @@ -53,9 +54,8 @@ def list_connections( "offset": offset, } params = {k: v for k, v in params.items() if v is not None} - utils.make_get_request( - "v1/connections", params=params, pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_get_request("v1/connections", params=params, pretty_print=pretty, line_numbers=line_numbers) + @app.command("retrieve") def retrieve_connection( @@ -88,13 +88,12 @@ def retrieve_connection( } ``` """ - utils.make_get_request( - f"v1/connections/{connection_id}", pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_get_request(f"v1/connections/{connection_id}", pretty_print=pretty, line_numbers=line_numbers) + @app.command("create") def create_connection( - property: typing.List[str] = typer.Option( + property: list[str] = typer.Option( [], "--property", "-d", help="Set nested properties (e.g. -d carrier_name=ups -d credentials[api_key]=xxx)" ), pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), @@ -134,14 +133,13 @@ def create_connection( except ValueError as e: typer.echo(str(e), err=True) raise typer.Exit(code=1) - utils.make_post_request( - "v1/connections", payload=payload, pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_post_request("v1/connections", payload=payload, pretty_print=pretty, line_numbers=line_numbers) + @app.command("update") def update_connection( connection_id: str, - property: typing.List[str] = typer.Option( + property: list[str] = typer.Option( [], "--property", "-d", help="Set nested properties (e.g. -d credentials[api_key]=newvalue)" ), pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), @@ -181,6 +179,7 @@ def update_connection( f"v1/connections/{connection_id}", payload=payload, pretty_print=pretty, line_numbers=line_numbers ) + @app.command("delete") def delete_connection( connection_id: str, @@ -202,6 +201,4 @@ def delete_connection( } ``` """ - utils.make_delete_request( - f"v1/connections/{connection_id}", pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_delete_request(f"v1/connections/{connection_id}", pretty_print=pretty, line_numbers=line_numbers) diff --git a/modules/cli/karrio_cli/resources/events.py b/modules/cli/karrio_cli/resources/events.py index e2187bdc46..48de27e4cf 100644 --- a/modules/cli/karrio_cli/resources/events.py +++ b/modules/cli/karrio_cli/resources/events.py @@ -1,14 +1,15 @@ +import datetime + import typer + import karrio_cli.common.utils as utils -import typing -import datetime app = typer.Typer() @app.command("list") def list_events( - type: typing.Optional[str] = typer.Option( + type: str | None = typer.Option( None, help="Event type (e.g. shipment_created, order_created, tracker_created)", autocompletion=lambda: [ @@ -30,20 +31,14 @@ def list_events( "shipment_purchased", "tracker_created", "tracker_updated", - ] + ], ), - created_after: typing.Optional[datetime.datetime] = None, - created_before: typing.Optional[datetime.datetime] = None, + created_after: datetime.datetime | None = None, + created_before: datetime.datetime | None = None, limit: int = typer.Option(20, help="Number of results to return per page"), - offset: int = typer.Option( - 0, help="The initial index from which to return the results" - ), - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + offset: int = typer.Option(0, help="The initial index from which to return the results"), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ List all events with optional filters and pagination. @@ -100,23 +95,14 @@ def list_events( # Remove None values from params params = {k: v for k, v in params.items() if v is not None} - utils.make_graphql_request( - "get_events", - {"filter": params}, - pretty_print=pretty, - line_numbers=line_numbers - ) + utils.make_graphql_request("get_events", {"filter": params}, pretty_print=pretty, line_numbers=line_numbers) @app.command("retrieve") def retrieve_event( event_id: str, - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ Retrieve an event by ID. @@ -143,9 +129,4 @@ def retrieve_event( } ``` """ - utils.make_graphql_request( - "get_event", - {"id": event_id}, - pretty_print=pretty, - line_numbers=line_numbers - ) + utils.make_graphql_request("get_event", {"id": event_id}, pretty_print=pretty, line_numbers=line_numbers) diff --git a/modules/cli/karrio_cli/resources/logs.py b/modules/cli/karrio_cli/resources/logs.py index b5bfbf7e29..94577777ab 100644 --- a/modules/cli/karrio_cli/resources/logs.py +++ b/modules/cli/karrio_cli/resources/logs.py @@ -1,28 +1,23 @@ +import datetime + import typer + import karrio_cli.common.utils as utils -import typing -import datetime app = typer.Typer() @app.command("list") def list_logs( - entity_id: typing.Optional[str] = None, - method: typing.Optional[str] = None, - status_code: typing.Optional[str] = None, - created_after: typing.Optional[datetime.datetime] = None, - created_before: typing.Optional[datetime.datetime] = None, + entity_id: str | None = None, + method: str | None = None, + status_code: str | None = None, + created_after: datetime.datetime | None = None, + created_before: datetime.datetime | None = None, limit: int = typer.Option(20, help="Number of results to return per page"), - offset: int = typer.Option( - 0, help="The initial index from which to return the results" - ), - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + offset: int = typer.Option(0, help="The initial index from which to return the results"), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ List all logs with optional filters and pagination. @@ -85,23 +80,14 @@ def list_logs( # Remove None values from params params = {k: v for k, v in params.items() if v is not None} - utils.make_graphql_request( - "get_logs", - {"filter": params}, - pretty_print=pretty, - line_numbers=line_numbers - ) + utils.make_graphql_request("get_logs", {"filter": params}, pretty_print=pretty, line_numbers=line_numbers) @app.command("retrieve") def retrieve_log( log_id: str, - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ Retrieve a log by ID. @@ -143,9 +129,4 @@ def retrieve_log( } ``` """ - utils.make_graphql_request( - "get_log", - {"id": int(log_id)}, - pretty_print=pretty, - line_numbers=line_numbers - ) + utils.make_graphql_request("get_log", {"id": int(log_id)}, pretty_print=pretty, line_numbers=line_numbers) diff --git a/modules/cli/karrio_cli/resources/orders.py b/modules/cli/karrio_cli/resources/orders.py index c8f0057633..2825efafd5 100644 --- a/modules/cli/karrio_cli/resources/orders.py +++ b/modules/cli/karrio_cli/resources/orders.py @@ -1,29 +1,24 @@ +import datetime + import typer + import karrio_cli.common.utils as utils -import typing -import datetime app = typer.Typer() @app.command("list") def list_orders( - created_after: typing.Optional[datetime.datetime] = None, - created_before: typing.Optional[datetime.datetime] = None, - status: typing.Optional[str] = None, - reference: typing.Optional[str] = None, - metadata_key: typing.Optional[str] = None, - metadata_value: typing.Optional[str] = None, + created_after: datetime.datetime | None = None, + created_before: datetime.datetime | None = None, + status: str | None = None, + reference: str | None = None, + metadata_key: str | None = None, + metadata_value: str | None = None, limit: int = typer.Option(20, help="Number of results to return per page"), - offset: int = typer.Option( - 0, help="The initial index from which to return the results" - ), - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + offset: int = typer.Option(0, help="The initial index from which to return the results"), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ List all orders with optional filters and pagination. @@ -75,20 +70,14 @@ def list_orders( # Remove None values from params params = {k: v for k, v in params.items() if v is not None} - utils.make_get_request( - "v1/orders", params=params, pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_get_request("v1/orders", params=params, pretty_print=pretty, line_numbers=line_numbers) @app.command("retrieve") def retrieve_order( order_id: str, - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ Retrieve an order by ID. @@ -98,26 +87,20 @@ def retrieve_order( kcli orders retrieve ord_987654321 | jq "{id, status, created: .created_at, total: .total_charge.amount, items: .line_items | length}" ``` """ - utils.make_get_request( - f"v1/orders/{order_id}", pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_get_request(f"v1/orders/{order_id}", pretty_print=pretty, line_numbers=line_numbers) @app.command("cancel") def cancel_order( order_id: str, - property: typing.List[str] = typer.Option( + property: list[str] = typer.Option( [], "--property", "-d", help="Set nested properties (e.g. -d reason=customer_request)", ), - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ Cancel an order. diff --git a/modules/cli/karrio_cli/resources/shipments.py b/modules/cli/karrio_cli/resources/shipments.py index 03085470f5..d322250ca4 100644 --- a/modules/cli/karrio_cli/resources/shipments.py +++ b/modules/cli/karrio_cli/resources/shipments.py @@ -1,41 +1,36 @@ +import datetime + import typer + import karrio_cli.common.utils as utils -import typing -import datetime app = typer.Typer() @app.command("list") def list_shipments( - address: typing.Optional[str] = None, - carrier_name: typing.Optional[str] = None, - created_after: typing.Optional[datetime.datetime] = None, - created_before: typing.Optional[datetime.datetime] = None, - has_manifest: typing.Optional[bool] = None, - has_tracker: typing.Optional[bool] = None, - id: typing.Optional[str] = None, - keyword: typing.Optional[str] = None, - meta_key: typing.Optional[str] = None, - meta_value: typing.Optional[str] = None, - metadata_key: typing.Optional[str] = None, - metadata_value: typing.Optional[str] = None, - option_key: typing.Optional[str] = None, - option_value: typing.Optional[str] = None, - reference: typing.Optional[str] = None, - service: typing.Optional[str] = None, - status: typing.Optional[str] = None, - tracking_number: typing.Optional[str] = None, + address: str | None = None, + carrier_name: str | None = None, + created_after: datetime.datetime | None = None, + created_before: datetime.datetime | None = None, + has_manifest: bool | None = None, + has_tracker: bool | None = None, + id: str | None = None, + keyword: str | None = None, + meta_key: str | None = None, + meta_value: str | None = None, + metadata_key: str | None = None, + metadata_value: str | None = None, + option_key: str | None = None, + option_value: str | None = None, + reference: str | None = None, + service: str | None = None, + status: str | None = None, + tracking_number: str | None = None, limit: int = typer.Option(20, help="Number of results to return per page"), - offset: int = typer.Option( - 0, help="The initial index from which to return the results" - ), - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + offset: int = typer.Option(0, help="The initial index from which to return the results"), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ List all shipments with optional filters and pagination. @@ -77,20 +72,14 @@ def list_shipments( # Remove None values from params params = {k: v for k, v in params.items() if v is not None} - utils.make_get_request( - "v1/shipments", params=params, pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_get_request("v1/shipments", params=params, pretty_print=pretty, line_numbers=line_numbers) @app.command("retrieve") def retrieve_shipment( shipment_id: str, - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ Retrieve a shipment by ID. @@ -100,9 +89,7 @@ def retrieve_shipment( kcli shipments retrieve shp_123456789 | jq '{id, status, carrier: .carrier_name, tracking: .tracking_number, created: .created_at}' ``` """ - utils.make_get_request( - f"v1/shipments/{shipment_id}", pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_get_request(f"v1/shipments/{shipment_id}", pretty_print=pretty, line_numbers=line_numbers) @app.command("buy-label") @@ -110,18 +97,14 @@ def buy_label( shipment_id: str, selected_rate_id: str = typer.Option(..., help="The ID of the selected rate"), label_type: str = typer.Option("PDF", help="The type of label to generate"), - property: typing.List[str] = typer.Option( + property: list[str] = typer.Option( [], "--property", "-d", help="Set nested properties (e.g. -d payment[paid_by]=sender)", ), - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ Purchase a label for a shipment. @@ -160,12 +143,8 @@ def buy_label( @app.command("cancel") def cancel_shipment( shipment_id: str, - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ Cancel a shipment. @@ -186,12 +165,8 @@ def cancel_shipment( @app.command("fetch-rates") def fetch_rates( shipment_id: str, - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ Fetch rates for a shipment. diff --git a/modules/cli/karrio_cli/resources/trackers.py b/modules/cli/karrio_cli/resources/trackers.py index 0d5d677e3e..7f5062e6cf 100644 --- a/modules/cli/karrio_cli/resources/trackers.py +++ b/modules/cli/karrio_cli/resources/trackers.py @@ -1,6 +1,7 @@ -import typer -import typing import datetime + +import typer + import karrio_cli.common.utils as utils app = typer.Typer() @@ -8,21 +9,15 @@ @app.command("list") def list_trackers( - carrier_name: typing.Optional[str] = None, - tracking_number: typing.Optional[str] = None, - status: typing.Optional[str] = None, - created_after: typing.Optional[datetime.datetime] = None, - created_before: typing.Optional[datetime.datetime] = None, + carrier_name: str | None = None, + tracking_number: str | None = None, + status: str | None = None, + created_after: datetime.datetime | None = None, + created_before: datetime.datetime | None = None, limit: int = typer.Option(20, help="Number of results to return per page"), - offset: int = typer.Option( - 0, help="The initial index from which to return the results" - ), - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + offset: int = typer.Option(0, help="The initial index from which to return the results"), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ List all trackers with optional filters and pagination. @@ -78,20 +73,14 @@ def list_trackers( # Remove None values from params params = {k: v for k, v in params.items() if v is not None} - utils.make_get_request( - "v1/trackers", params=params, pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_get_request("v1/trackers", params=params, pretty_print=pretty, line_numbers=line_numbers) @app.command("retrieve") def retrieve_tracker( tracker_id: str, - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ Retrieve a tracker by ID. @@ -123,33 +112,23 @@ def retrieve_tracker( } ``` """ - utils.make_get_request( - f"v1/trackers/{tracker_id}", pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_get_request(f"v1/trackers/{tracker_id}", pretty_print=pretty, line_numbers=line_numbers) @app.command("create") def create_tracker( tracking_number: str = typer.Option(..., help="The tracking number"), carrier_name: str = typer.Option(..., help="The carrier name"), - account_number: typing.Optional[str] = typer.Option( - None, help="The account number" - ), - reference: typing.Optional[str] = typer.Option( - None, help="A reference for the tracker" - ), - property: typing.List[str] = typer.Option( + account_number: str | None = typer.Option(None, help="The account number"), + reference: str | None = typer.Option(None, help="A reference for the tracker"), + property: list[str] = typer.Option( [], "--property", "-d", help="Set nested properties (e.g. -d info[customer_name]=John Doe)", ), - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ Create a new tracker. @@ -194,26 +173,20 @@ def create_tracker( typer.echo(str(e), err=True) raise typer.Exit(code=1) - utils.make_post_request( - "v1/trackers", payload=payload, pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_post_request("v1/trackers", payload=payload, pretty_print=pretty, line_numbers=line_numbers) @app.command("update") def update_tracker( tracker_id: str, - property: typing.List[str] = typer.Option( + property: list[str] = typer.Option( [], "--property", "-d", help="Set nested properties (e.g. -d info[customer_name]=John Doe)", ), - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ Update an existing tracker. @@ -260,12 +233,8 @@ def update_tracker( @app.command("delete") def delete_tracker( tracker_id: str, - pretty: bool = typer.Option( - False, "--pretty", "-p", help="Pretty print the output" - ), - line_numbers: bool = typer.Option( - False, "--line-numbers", "-n", help="Show line numbers in pretty print" - ), + pretty: bool = typer.Option(False, "--pretty", "-p", help="Pretty print the output"), + line_numbers: bool = typer.Option(False, "--line-numbers", "-n", help="Show line numbers in pretty print"), ): """ Delete a tracker. @@ -282,6 +251,4 @@ def delete_tracker( } ``` """ - utils.make_delete_request( - f"v1/trackers/{tracker_id}", pretty_print=pretty, line_numbers=line_numbers - ) + utils.make_delete_request(f"v1/trackers/{tracker_id}", pretty_print=pretty, line_numbers=line_numbers) diff --git a/modules/cli/karrio_cli/templates/__init__.py b/modules/cli/karrio_cli/templates/__init__.py index 3388ea92b3..2d48ea3397 100644 --- a/modules/cli/karrio_cli/templates/__init__.py +++ b/modules/cli/karrio_cli/templates/__init__.py @@ -1,4 +1,5 @@ from karrio_cli.templates.address import * +from karrio_cli.templates.location import * from karrio_cli.templates.docs import * from karrio_cli.templates.documents import * from karrio_cli.templates.manifest import * diff --git a/modules/cli/karrio_cli/templates/location.py b/modules/cli/karrio_cli/templates/location.py new file mode 100644 index 0000000000..1b4f8f1220 --- /dev/null +++ b/modules/cli/karrio_cli/templates/location.py @@ -0,0 +1,324 @@ +from jinja2 import Template + +PROVIDER_LOCATION_TEMPLATE = Template( + '''"""Karrio {{name}} location finder API implementation.""" + +# IMPLEMENTATION INSTRUCTIONS: +# 1. Uncomment the imports when the schema types are generated +# 2. Import the specific request and response types you need +# 3. Create a request instance with the appropriate request type +# 4. Extract location details from the response +# +# NOTE: JSON schema types are generated with "Type" suffix (e.g., LocationRequestType), +# while XML schema types don't have this suffix (e.g., LocationRequest). + +import karrio.schemas.{{id}}.location_request as {{id}}_req +import karrio.schemas.{{id}}.location_response as {{id}}_res + +import typing +import karrio.lib as lib +import karrio.core.units as units +import karrio.core.models as models +import karrio.providers.{{id}}.error as error +import karrio.providers.{{id}}.utils as provider_utils +import karrio.providers.{{id}}.units as provider_units + + +def parse_location_response( + _response: lib.Deserializable[{% if is_xml_api %}lib.Element{% else %}dict{% endif %}], + settings: provider_utils.Settings, +) -> typing.Tuple[typing.List[models.LocationDetails], typing.List[models.Message]]: + response = _response.deserialize() + messages = error.parse_error_response(response, settings) + + # Extract the carrier locations from the response and map each one with + # `_extract_location`. The exact path depends on the carrier API shape. + locations = [ + _extract_location(location, settings) + for location in [] # e.g. response.get("locations", []) + ] + + return locations, messages + + +def _extract_location( + data: {% if is_xml_api %}lib.Element{% else %}dict{% endif %}, + settings: provider_utils.Settings, +) -> models.LocationDetails: + """Map a single carrier location entry to a unified LocationDetails.""" + location = lib.to_object({{id}}_res.{% if is_xml_api %}Location{% else %}LocationType{% endif %}, data) + + return models.LocationDetails( + carrier_id=settings.carrier_id, + carrier_name=settings.carrier_name, + location_id=None, + location_type=None, # depot, parcel_shop, locker, pickup_point, ... + name=None, + address=None, + latitude=None, + longitude=None, + distance=None, + ) + + +def location_request( + payload: models.LocationRequest, + settings: provider_utils.Settings, +) -> lib.Serializable: + """ + Create a location search request for the carrier API + + payload: The standardized LocationRequest from karrio + settings: The carrier connection settings + + Returns a Serializable object that can be sent to the carrier API + """ + # The search is anchored on the unified Address abstraction + address = lib.to_address(payload.address) + + {% if is_xml_api %} + # For XML API request + request = {{id}}_req.LocationRequest( + country_code=address.country_code, + postcode=address.postal_code, + city_name=address.city, + street=address.address_line1, + location_type=payload.location_type, + radius=payload.radius_km, + max_results=payload.max_results, + ) + {% else %} + # For JSON API request - Using camelCase attribute names to match schema definition + request = {{id}}_req.LocationRequestType( + countryCode=address.country_code, + postalCode=address.postal_code, + cityLocality=address.city, + streetAddress=address.address_line1, + locationType=payload.location_type, + radiusKm=payload.radius_km, + maxResults=payload.max_results, + ) + {% endif %} + + return lib.Serializable(request, {% if is_xml_api %}lib.to_xml{% else %}lib.to_dict{% endif %}) +''' +) + +JSON_SCHEMA_LOCATION_REQUEST_TEMPLATE = Template(''' +{ + "countryCode": "US", + "postalCode": "12345", + "cityLocality": "City Name", + "streetAddress": "123 Main St", + "locationType": "pickup_point", + "radiusKm": 10, + "maxResults": 20 +} +''') + +JSON_SCHEMA_LOCATION_RESPONSE_TEMPLATE = Template(''' +{ + "locations": [ + { + "locationId": "LOC-001", + "locationType": "pickup_point", + "name": "Downtown Pickup Point", + "address": { + "streetAddress": "123 Main St", + "cityLocality": "City Name", + "postalCode": "12345", + "countryCode": "US" + }, + "latitude": 40.7128, + "longitude": -74.006, + "distanceKm": 1.2, + "openingHours": [], + "services": [] + } + ] +} +''') + +XML_SCHEMA_LOCATION_REQUEST_TEMPLATE = Template(''' + + + + + + + + + + + + + + + +''') + +XML_SCHEMA_LOCATION_RESPONSE_TEMPLATE = Template(''' + + + + + + + + + + + + + + + + + + + + + + + + +''') + +TEST_LOCATION_TEMPLATE = Template('''"""{{name}} carrier location finder tests.""" + +import unittest +from unittest.mock import patch, ANY +from .fixture import gateway +import logging +import karrio.sdk as karrio +import karrio.lib as lib +import karrio.core.models as models + +logger = logging.getLogger(__name__) + +class Test{{compact_name}}Location(unittest.TestCase): + def setUp(self): + self.maxDiff = None + self.LocationRequest = models.LocationRequest(**LocationPayload) + + def test_create_location_request(self): + request = gateway.mapper.create_location_request(self.LocationRequest) + self.assertEqual(lib.to_dict(request.serialize()), LocationRequest) + + def test_get_locations(self): + with patch("karrio.mappers.{{id}}.proxy.lib.request") as mock: + mock.return_value = {% if is_xml_api %}""{% else %}"{}"{% endif %} + karrio.Location.search(self.LocationRequest).from_(gateway) + self.assertEqual( + mock.call_args[1]["url"], + f"{gateway.settings.server_url}/locations" + ) + + def test_parse_location_response(self): + with patch("karrio.mappers.{{id}}.proxy.lib.request") as mock: + mock.return_value = LocationResponse + parsed_response = ( + karrio.Location.search(self.LocationRequest) + .from_(gateway) + .parse() + ) + self.assertListEqual(lib.to_dict(parsed_response), ParsedLocationResponse) + + def test_parse_error_response(self): + with patch("karrio.mappers.{{id}}.proxy.lib.request") as mock: + mock.return_value = ErrorResponse + parsed_response = ( + karrio.Location.search(self.LocationRequest) + .from_(gateway) + .parse() + ) + self.assertListEqual(lib.to_dict(parsed_response), ParsedErrorResponse) + + +if __name__ == "__main__": + unittest.main() + + +LocationPayload = { + "address": { + "country_code": "US", + "postal_code": "12345", + "city": "City Name", + }, + "location_type": "pickup_point", +} + +LocationRequest = {% if is_xml_api %}""" + + US + 12345 + City Name + pickup_point +"""{% else %}{ + "countryCode": "US", + "postalCode": "12345", + "cityLocality": "City Name", + "locationType": "pickup_point" +}{% endif %} + +LocationResponse = {% if is_xml_api %}""" + + + LOC-001 + pickup_point + Downtown Pickup Point + 123 Main St + City Name + 12345 + US + +"""{% else %}"""{ + "locations": [ + { + "locationId": "LOC-001", + "locationType": "pickup_point", + "name": "Downtown Pickup Point", + "address": { + "streetAddress": "123 Main St", + "cityLocality": "City Name", + "postalCode": "12345", + "countryCode": "US" + } + } + ] +}"""{% endif %} + +ErrorResponse = {% if is_xml_api %}""" + + + location_error + Unable to find locations +
Invalid search parameters provided
+
+
"""{% else %}"""{ + "error": { + "code": "location_error", + "message": "Unable to find locations", + "details": "Invalid search parameters provided" + } +}"""{% endif %} + +ParsedLocationResponse = [ + [], + [] +] + +ParsedErrorResponse = [ + [], + [ + { + "carrier_id": "{{id}}", + "carrier_name": "{{id}}", + "code": "location_error", + "message": "Unable to find locations", + "details": { + "details": "Invalid search parameters provided" + } + } + ] +]''') diff --git a/modules/cli/karrio_cli/templates/sdk.py b/modules/cli/karrio_cli/templates/sdk.py index bd8f7e9cd1..ebcb000e20 100644 --- a/modules/cli/karrio_cli/templates/sdk.py +++ b/modules/cli/karrio_cli/templates/sdk.py @@ -114,7 +114,9 @@ generateDS --no-namespace-defs -o "${LIB_MODULES}/error.py" $SCHEMAS/error_response.xsd{% if "address" in features %} generateDS --no-namespace-defs -o "${LIB_MODULES}/address_validation_request.py" $SCHEMAS/address_validation_request.xsd -generateDS --no-namespace-defs -o "${LIB_MODULES}/address_validation_response.py" $SCHEMAS/address_validation_response.xsd{% endif %}{% if "rating" in features %} +generateDS --no-namespace-defs -o "${LIB_MODULES}/address_validation_response.py" $SCHEMAS/address_validation_response.xsd{% endif %}{% if "location" in features %} +generateDS --no-namespace-defs -o "${LIB_MODULES}/location_request.py" $SCHEMAS/location_request.xsd +generateDS --no-namespace-defs -o "${LIB_MODULES}/location_response.py" $SCHEMAS/location_response.xsd{% endif %}{% if "rating" in features %} generateDS --no-namespace-defs -o "${LIB_MODULES}/rate_request.py" $SCHEMAS/rate_request.xsd generateDS --no-namespace-defs -o "${LIB_MODULES}/rate_response.py" $SCHEMAS/rate_response.xsd{% endif %}{% if "pickup" in features %} generateDS --no-namespace-defs -o "${LIB_MODULES}/pickup_create_request.py" $SCHEMAS/pickup_create_request.xsd @@ -147,7 +149,9 @@ kcli codegen generate "${SCHEMAS}/error_response.json" "${LIB_MODULES}/error_response.py"{% if "address" in features %} kcli codegen generate "${SCHEMAS}/address_validation_request.json" "${LIB_MODULES}/address_validation_request.py" -kcli codegen generate "${SCHEMAS}/address_validation_response.json" "${LIB_MODULES}/address_validation_response.py"{% endif %}{% if "rating" in features %} +kcli codegen generate "${SCHEMAS}/address_validation_response.json" "${LIB_MODULES}/address_validation_response.py"{% endif %}{% if "location" in features %} +kcli codegen generate "${SCHEMAS}/location_request.json" "${LIB_MODULES}/location_request.py" +kcli codegen generate "${SCHEMAS}/location_response.json" "${LIB_MODULES}/location_response.py"{% endif %}{% if "rating" in features %} kcli codegen generate "${SCHEMAS}/rate_request.json" "${LIB_MODULES}/rate_request.py" kcli codegen generate "${SCHEMAS}/rate_response.json" "${LIB_MODULES}/rate_response.py"{% endif %}{% if "pickup" in features %} kcli codegen generate "${SCHEMAS}/pickup_create_request.json" "${LIB_MODULES}/pickup_create_request.py" @@ -278,6 +282,11 @@ def create_address_validation_request( self, payload: models.AddressValidationRequest ) -> lib.Serializable: return provider.address_validation_request(payload, self.settings) + {% endif %}{% if "location" in features %} + def create_location_request( + self, payload: models.LocationRequest + ) -> lib.Serializable: + return provider.location_request(payload, self.settings) {% endif %}{% if "manifest" in features %} def create_manifest_request( self, payload: models.ManifestRequest @@ -339,6 +348,11 @@ def parse_address_validation_response( self, response: lib.Deserializable[str] ) -> typing.Tuple[typing.List[models.AddressValidationDetails], typing.List[models.Message]]: return provider.parse_address_validation_response(response, self.settings) + {% endif %}{% if "location" in features %} + def parse_location_response( + self, response: lib.Deserializable[str] + ) -> typing.Tuple[typing.List[models.LocationDetails], typing.List[models.Message]]: + return provider.parse_location_response(response, self.settings) {% endif %}{% if "webhook" in features %} def create_webhook_registration_request( self, payload: models.WebhookRegistrationRequest @@ -570,6 +584,26 @@ def validate_address(self, request: lib.Serializable) -> lib.Deserializable[str] # During development, use stub response from schema examples {% if is_xml_api %}response = ''{% else %}response = lib.to_json({}){% endif %} + return lib.Deserializable(response, {% if is_xml_api %}lib.to_element{% else %}lib.to_dict{% endif %}) + {% endif %}{% if "location" in features %} + def get_locations(self, request: lib.Serializable) -> lib.Deserializable[str]: + # REPLACE THIS WITH YOUR ACTUAL API CALL IMPLEMENTATION + # --------------------------------------------------------- + # Example implementation: + # response = lib.request( + # url=f"{self.settings.server_url}/locations", + # data={% if is_xml_api %}request.serialize(){% else %}lib.to_json(request.serialize()){% endif %}, + # trace=self.trace_as({% if is_xml_api %}"xml"{% else %}"json"{% endif %}), + # method="POST", + # headers={ + # "Content-Type": {% if is_xml_api %}"application/xml"{% else %}"application/json"{% endif %}, + # {% if is_xml_api %}"Authorization": f"Basic {self.settings.authorization}"{% else %}"Authorization": f"Bearer {self.settings.api_key}"{% endif %} + # }, + # ) + + # During development, use stub response from schema examples + {% if is_xml_api %}response = ''{% else %}response = lib.to_json({}){% endif %} + return lib.Deserializable(response, {% if is_xml_api %}lib.to_element{% else %}lib.to_dict{% endif %}) {% endif %}{% if "document" in features %} def upload_document(self, request: lib.Serializable) -> lib.Deserializable[str]: @@ -793,6 +827,10 @@ class Settings(provider_utils.Settings): from karrio.providers.{{id}}.address import ( parse_address_validation_response, address_validation_request, +){% endif %}{% if "location" in features %} +from karrio.providers.{{id}}.location import ( + parse_location_response, + location_request, ){% endif %}{% if "document" in features %} from karrio.providers.{{id}}.document import ( parse_document_upload_response, diff --git a/modules/connectors/amazon_shipping/README.md b/modules/connectors/amazon_shipping/README.md new file mode 100644 index 0000000000..daf1d714d1 --- /dev/null +++ b/modules/connectors/amazon_shipping/README.md @@ -0,0 +1,31 @@ + +# karrio.amazon_shipping + +This package is a Amazon Shipping extension of the [karrio](https://pypi.org/project/karrio) multi carrier shipping SDK. + +## Requirements + +`Python 3.7+` + +## Installation + +```bash +pip install karrio.amazon_shipping +``` + +## Usage + +```python +import karrio.sdk as karrio +from karrio.mappers.amazon_shipping.settings import Settings + + +# Initialize a carrier gateway +amazon_shipping = karrio.gateway["amazon_shipping"].create( + Settings( + ... + ) +) +``` + +Check the [Karrio Mutli-carrier SDK docs](https://docs.karrio.io) for Shipping API requests diff --git a/modules/connectors/amazon_shipping/SPECS.md b/modules/connectors/amazon_shipping/SPECS.md new file mode 100644 index 0000000000..f8ce3b4722 --- /dev/null +++ b/modules/connectors/amazon_shipping/SPECS.md @@ -0,0 +1,465 @@ +# Amazon Shipping integration — specification + +Reference for the Amazon Shipping connector. Amazon Shipping exposes a +**JSON REST** API (the Selling Partner API **Shipping v2** surface) for +rate quotes, label purchase, tracking, and shipment cancellation, +behind a **Login with Amazon (LWA) OAuth2 refresh-token** flow. This +connector supports **rate quotes, shipment create (one-click rate + +buy), shipment cancel, and tracking**. There is no pickup surface on +the v2 API. + +The **vendor source of truth** lives under `vendor/`: +`shippingV2.json` (the current Swagger 2.0 model pulled from +[`amzn/selling-partner-api-models`](https://github.com/amzn/selling-partner-api-models/blob/main/models/shipping-api-model/shippingV2.json)), +`openapi.yaml` (the developer-docs OpenAPI 3 export), and +`postman.json` (the published Postman collection). + +This connector was ported from the karrio community plugin at +[`karrioapi/community @ 5522868 · plugins/amazon_shipping`](https://github.com/karrioapi/community/tree/5522868b13752fe009b9c4c911c1ef4218f297d2/plugins/amazon_shipping). + +## Table of contents + +1. [Architecture overview](#architecture-overview) +2. [Data flow](#data-flow) +3. [Endpoints](#endpoints) +4. [Authentication](#authentication) +5. [Supported operations](#supported-operations) +6. [Services](#services) +7. [Options](#options) +8. [Connection config](#connection-config) +9. [Data mapping](#data-mapping) +10. [Wire-shape invariants & gotchas](#wire-shape-invariants--gotchas) +11. [Tracking](#tracking) +12. [Error parsing](#error-parsing) +13. [References](#references) + +--- + +## Architecture overview + +``` +┌─────────────────────────┐ +│ Unified shipping model │ karrio RateRequest / ShipmentRequest / +│ (karrio core) │ ShipmentCancelRequest / TrackingRequest +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ providers/amazon_… │ Pure data transforms. +│ rate.py │ Unified model → typed Amazon request, +│ shipment/create.py │ typed Amazon response → unified model. +│ shipment/cancel.py │ No HTTP, no side effects. +│ tracking.py │ +│ error.py │ +│ units.py │ ShippingService, ShippingOption, +│ utils.py │ ConnectionConfig, TrackingStatus, +│ │ TrackingIncidentReason, region→host map +└───────────┬─────────────┘ + │ + ▼ +┌──────────────────────────────┐ +│ mappers/amazon_shipping/ │ HTTP transport only. +│ proxy.py │ - LWA OAuth2 refresh-token (cached) +│ - get_rates │ - x-amz-access-token on every call +│ - create_shipment │ - x-amzn-shipping-business-id header +│ - cancel_shipment │ - parallel per-tracking-number GETs +│ - get_tracking │ +└───────────┬──────────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ Amazon SP-API │ +│ ───────────────────── │ +│ Shipping v2 REST │ /shipping/v2/shipments/rates, +│ (sellingpartnerapi-*) │ /oneClickShipment, /tracking, +│ │ /shipments/{id}/cancel +│ LWA OAuth2 │ api.amazon.com/auth/o2/token +└─────────────────────────┘ +``` + +**Key architectural choices:** + +- **One-click shipment, not rate-then-buy.** Shipment create posts to + `oneClickShipment`, which rates and purchases a label in a single + call, so the connector never carries a `request_token` between a rate + and a buy. `getRates` is wired only for standalone rate quotes. +- **LWA refresh-token grant**, not client-credentials. Amazon Shipping + v2 is **seller-scoped**: it requires a seller-authorized + `refresh_token` exchanged for a short-lived `access_token`. The token + is cached per `amazon_shipping|` key and refreshed 5 + minutes before expiry. +- **Region drives the host.** `aws_region` selects the SP-API regional + endpoint; `test_mode` rewrites the host to the `sandbox.` variant. +- **Tracking fans out.** `getTracking` takes a single tracking id per + call, so the proxy issues one parallel GET per requested number and + zips `(tracking_id, response)` pairs back for parsing. +- **Generated schemas** — `karrio/schemas/amazon_shipping/*.py` is + generated from `schemas/*.json` with + `kcli ... --no-append-type-suffix --no-nice-property-names` (the + Amazon API uses camelCase field names, preserved verbatim). Don't + hand-edit; regenerate with + `./bin/run-generate-on modules/connectors/amazon_shipping`. + +## Data flow + +### Rate quote + +``` +RateRequest Amazon SP-API + │ │ + ├─► rate_request() │ + │ to_address(shipper/recipient) │ + │ to_packages(required=[weight]) │ + │ to_services(ShippingService) │ + │ to_shipping_options(initializer) │ + │ to_customs_info() → declared_value │ + │ │ + ├─► { shipFrom, shipTo, packages[], │ + │ channelDetails, labelSpecs, │ + │ serviceSelection? } │ + │ │ + │ ─ POST /shipping/v2/shipments/rates ►│ + │ ◄ { "payload": { rates[], … } } ─────│ + │ │ + ├─► parse_rate_response: │ + │ unwrap payload │ + │ rates[] → RateDetails │ + ▼ ▼ +[RateDetails] (one per rate) +``` + +### One-click shipment (rate + buy) + +``` +ShipmentRequest Amazon SP-API + │ │ + ├─► shipment_request() │ + │ (same builders as rate + │ + │ serviceSelection from service) │ + │ │ + │ ─ POST /shipping/v2/oneClickShipment►│ + │ ◄ { "payload": { │ + │ shipmentId, │ + │ packageDocumentDetails[], │ + │ carrier, service, totalCharge }} │ + │ │ + ├─► parse_shipment_response: │ + │ unwrap payload │ + │ packageDocuments[type=LABEL] │ + │ → docs.label (PNG→PDF) │ + │ trackingId → tracking_number │ + │ shipmentId → shipment_identifier │ + ▼ ▼ +ShipmentDetails (single-piece) +``` + +## Endpoints + +`{base}` resolves from `Settings.server_url` (region + test_mode): + +| `aws_region` | Prod base | Sandbox base (`test_mode=True`) | +|---|---|---| +| `us-east-1` (default) | `https://sellingpartnerapi-na.amazon.com` | `https://sandbox.sellingpartnerapi-na.amazon.com` | +| `eu-west-1` | `https://sellingpartnerapi-eu.amazon.com` | `https://sandbox.sellingpartnerapi-eu.amazon.com` | +| `us-west-2` | `https://sellingpartnerapi-fe.amazon.com` | `https://sandbox.sellingpartnerapi-fe.amazon.com` | + +Unknown regions fall back to the `us-east-1` host. + +| Purpose | Method | Path | +|---|---|---| +| LWA token | POST | `https://api.amazon.com/auth/o2/token` | +| Get rates | POST | `{base}/shipping/v2/shipments/rates` | +| Create shipment (one-click) | POST | `{base}/shipping/v2/oneClickShipment` | +| Cancel shipment | PUT | `{base}/shipping/v2/shipments/{shipmentId}/cancel` | +| Get tracking | GET | `{base}/shipping/v2/tracking?trackingId=…&carrierId=…` | + +Every non-auth call sends `Content-Type: application/json`, +`x-amz-access-token: `, and — when configured — +`x-amzn-shipping-business-id` (from `Settings.shipping_business_id` or +`connection_config.shipping_business_id`). + +## Authentication + +LWA OAuth2 **refresh-token grant**. Credentials live on `Settings`: +`client_id`, `client_secret`, `refresh_token`. + +``` + ┌──────────────────┐ +authenticate() │ connection_cache│ + │ │ (thread-safe) │ + ▼ │ │ +┌──────────────┐ miss / expiring │ key: │ +│ access_token │◄────────────────────────│ amazon_shipping|│ +│ │ (buffer 5 min) │ │ +│ │ cache hit │ │ +│ │────────────────────────►│ │ +└──────┬───────┘ └──────────────────┘ + │ get_token() + ▼ POST api.amazon.com/auth/o2/token + ┌───────────────────────────────────────────────────┐ + │ Content-Type: application/x-www-form-urlencoded │ + │ Body: grant_type=refresh_token & refresh_token & │ + │ client_id & client_secret │ + └───────────────────────────────────────────────────┘ + │ + ▼ { access_token, expires_in, ... } + stamped with expiry = now + expires_in (default 3600s) +``` + +`get_token()` raises `ParsedMessagesError` when the body carries an +`error` key (surfacing `error_description`/`error`) or when no +`access_token` is returned (`AUTH_ERROR`). The cache helper tolerates +either a `Token` object (`.get_state()`) or a bare dict/string. + +> **Seller scope (live-test caveat).** Amazon Shipping v2 is **not** on +> the LWA grantless allow-list — a bare `client_credentials` grant +> returns `400 invalid_scope` for every published scope. Exercising +> rates/ship/track/cancel against the sandbox requires a real +> seller-authorized `refresh_token` (`Atzr|…`). + +## Supported operations + +| Operation | Wired? | Provider entry point | Notes | +|---|---|---|---| +| Rate | yes | `rate.rate_request` / `parse_rate_response` | `POST .../shipments/rates` | +| Shipment create | yes | `shipment.create.shipment_request` | one-click rate + buy | +| Shipment cancel | yes | `shipment.cancel.shipment_cancel_request` | `PUT .../{id}/cancel`, empty body on success | +| Tracking | yes | `tracking.tracking_request` | one parallel GET per number | +| Pickup | **no** | — | No pickup surface on Shipping v2 | + +## Services + +Amazon Shipping service IDs are **dynamic** — the real list is returned +by `getRates`. `ShippingService` (`units.py`) carries the common +patterns as a static fallback so a method can be pre-selected: + +| karrio code | wire `serviceId` | +|---|---| +| `amazon_shipping_standard` | `AMZN_US_STD` | +| `amazon_shipping_premium` | `AMZN_US_PREM` | +| `amazon_shipping_ground` | `AMZN_US_GND` | +| `amazon_shipping_uk_standard` | `AMZN_UK_STD` | +| `amazon_shipping_uk_premium` | `AMZN_UK_PREM` | +| `amazon_shipping` | `AMZN` (generic fallback) | + +On rate parse, `ShippingService.map(serviceId).name_or_key` maps the +returned id back to a karrio code (unknown ids pass through verbatim). +On shipment create, `serviceSelection.serviceId` is sent only when a +service was selected. + +`PackagingType` collapses every unified packaging preset +(`envelope`, `pak`, `tube`, `pallet`, `small_box`, `medium_box`, +`your_packaging`) to the single Amazon `PACKAGE` type. + +## Options + +`ShippingOption` (`units.py`) — the first `OptionEnum` arg is the wire +key: + +| Option | Wire key | Type | Notes | +|---|---|---|---| +| `amazon_shipping_channel_type` | `channel_type` | str | `AMAZON` / `EXTERNAL`; defaults to `EXTERNAL` | +| `amazon_shipping_label_format` | `label_format` | str | `PNG` / `PDF` / `ZPL`; wins over `connection_config.label_format`, default `PNG` | +| `amazon_shipping_label_size` | `label_size` | str | optional label size hint | + +`shipping_options_initializer()` merges package-level options over the +shipment options (mutating a local copy so nothing leaks across +requests through the shared `{}` default). Standard options +`shipment_date`, `declared_value`, `currency`, and +`delivery_instructions` are also consumed (see data mapping). + +## Connection config + +`ConnectionConfig` (`utils.py`): + +| Key | Type | Default | Purpose | +|---|---|---|---| +| `shipping_business_id` | str | — | `x-amzn-shipping-business-id` header (e.g. `AmazonShipping_US`) | +| `label_format` | str | `PNG` | label format when no option supplied | +| `label_size_width` | float | `4` | label width in `labelSpecifications.size` | +| `label_size_length` | float | `6` | label length in `labelSpecifications.size` | +| `label_size_unit` | str | `INCH` | label size unit | + +`ShippingBusinessId` (`units.py`) enumerates the known marketplace +header values (`AmazonShipping_US/UK/IN/IT/ES/FR`). + +## Data mapping + +### Address — karrio `Address` → Amazon address block + +``` +karrio Address Amazon shipFrom / shipTo +───────────────── ──────────────────────── +company_name or person_name ► name +street ► addressLine1 +address_line2 ► addressLine2 +company_name ► companyName +state_code ► stateOrRegion +city ► city +country_code ► countryCode +postal_code ► postalCode +email ► email +phone_number ► phoneNumber +``` + +`addressLine3` is always sent `None`. + +### Package — karrio `Package` → Amazon `Package` + +``` +karrio Package Amazon Package Unit +────────────── ────────────── ──── +length/width/height ──► dimensions{…, INCH} inches (omitted if no dims) +weight ──► weight{value, POUND} pounds +declared_value ──► insuredValue{value,unit} money (see below) +items[] ──► items[] (see below) +parcel.id or index ──► packageClientReferenceId str +``` + +Dimensions are sent in **inches** (`package.length.IN`) and weight in +**pounds** (`package.weight.LB`). + +### Customs / declared value + +`insuredValue` is **required** by the v2 spec, so it is always emitted. +Value resolves from `package.options.declared_value` → customs +`duty.declared_value` → `0`; currency from `package.options.currency` → +`USD`. + +`items` is **required** by the v2 spec, so it is always emitted (empty +list when the parcel has none). Each parcel item maps to: + +``` +karrio item Amazon item +────────── ─────────── +quantity (or 1) ───► quantity +description ───► description +sku or hs_code ───► itemIdentifier +weight ───► weight{value, POUND} (omitted if absent) +value_amount ───► itemValue{value, unit} (omitted if absent) +``` + +### Label specification + +`labelSpecifications` is built from the resolved label format and the +`connection_config` size keys: + +``` +format = option.label_format → config.label_format → "PNG" +size.length = config.label_size_length → 6 +size.width = config.label_size_width → 4 +size.unit = config.label_size_unit → "INCH" +dpi = 300 +pageLayout = "DEFAULT" +needFileJoining = (shipment) len(packages) > 1 | (rate) False +requestedDocumentTypes = ["LABEL"] +``` + +### Shipment response → unified + +``` +oneClickShipment payload + ├─ shipmentId ───► shipment_identifier, meta.shipment_id + ├─ packageDocumentDetails[].trackingId ─► tracking_number (first), meta.tracking_numbers[] + ├─ packageDocuments[type=LABEL].contents ─► docs.label + ├─ carrier.{id,name} ───► meta.carrier_id / carrier_name + ├─ service.{id,name} ───► meta.service_id / service_name + └─ totalCharge.{value,unit} ───► meta.total_charge / currency +``` + +Labels: multiple package labels are bundled with `lib.bundle_base64`; +a single label passes through. A `PNG` label is converted to `PDF` via +`lib.image_to_pdf` and `label_type` is reported as `PDF`. + +## Wire-shape invariants & gotchas + +- **Success responses are wrapped in `{"payload": {...}}`.** Rate, + one-click shipment, and tracking all nest their result under + `payload`; the parsers unwrap it before deserializing. (The upstream + community plugin parsed the body un-wrapped — confirmed against the + live sandbox that the envelope is real, see [References](#references).) +- **Errors are `{"errors": [{code, message, details}]}`** at any HTTP + status, including `403` auth failures — handled uniformly by + `parse_error_response`. +- **`insuredValue` and `items` are required** on every package by the + v2 spec, so both are always emitted (with `0`/empty defaults) even + when the unified model carries no declared value or line items. +- **Dimensions in inches, weight in pounds**, unlike many EU carriers. +- **Cancel returns an empty body on success.** The proxy substitutes + `"{}"` for a blank response so the parser sees a dict; success is + inferred from the **absence** of an `errors` array. +- **Tracking is one id per call.** `getTracking` has no batch form, so + the proxy fans out N parallel GETs and the parser walks + `(tracking_id, response)` pairs, attaching errors per tracking number. +- **Generated schema field names are camelCase verbatim** — the + `generate` script runs `kcli` with `--no-append-type-suffix + --no-nice-property-names`. Don't rename them in + `karrio/schemas/amazon_shipping/`. + +## Tracking + +`GET /shipping/v2/tracking` with `trackingId` + `carrierId` (default +`AMZN_US`) query params, one call per requested number. The response +`payload` (`GetTrackingResult`) carries `eventHistory[]`, `summary`, +and `promisedDeliveryDate`. + +Events are taken **most-recent-first** as returned. Each maps to a +`TrackingEvent` with `date`/`time` parsed from `eventTime` +(`%Y-%m-%dT%H:%M:%SZ`), `code`/`description` from `eventCode`, +`location` joined from `event.location.{city, stateOrRegion, +postalCode, countryCode}`, an ISO `timestamp`, plus normalised `status` +and `reason`. `estimated_delivery` comes from `promisedDeliveryDate`; +`delivered` is set when `summary.status == "Delivered"`. +`meta.received_by` is taken from `summary.proofOfDelivery.receivedBy` +when present. + +### Status mapping + +`TrackingStatus` (`units.py`) keys off Amazon v2 event codes: + +| karrio status | Amazon event codes | +|---|---| +| `pending` | `LabelCreated`, `PickedUp`, `Manifested` | +| `in_transit` | `InTransit`, `ArrivedAtCarrierFacility`, `DepartedCarrierFacility`, `ArrivedAtDeliveryStation`, `ArrivedAtLocalFacility` | +| `out_for_delivery` | `OutForDelivery` | +| `delivered` | `Delivered` | +| `on_hold` | `Delayed`, `OnHold`, `PaymentNotReady` | +| `delivery_failed` | `DeliveryAttempted`, `Undeliverable`, `AddressNotFound`, `BusinessClosed`, `CustomerUnavailable`, `UnableToAccess`, `UnableToContactRecipient` | +| `delivery_delayed` | `Delayed`, `WeatherDelay` | +| `return_initiated` | `ReturnInitiated`, `Rejected`, `CancelledByRecipient` | + +`TrackingIncidentReason` maps exception codes to normalized reasons +(`carrier_damaged_parcel`, `consignee_refused`, `consignee_not_home`, +`wrong_address`, `unable_to_access`, `payment_issue`, `hazmat`). + +## Error parsing + +`error.parse_error_response` reads the v2 error envelope: + +``` +response + └─ "errors": [ {code, message, details} ] + for each entry with a code or message: + → Message(code, message, details={**kwargs, note: details?}) +``` + +`kwargs` lets callers attach context — tracking passes +`tracking_number=` so per-number failures are attributable. Rate, +shipment, and cancel parsers all funnel carrier errors through it; the +absence of an `errors` array is treated as success. + +## References + +- **Vendor specs** (`vendor/`): + - `shippingV2.json` — Swagger 2.0 model from `amzn/selling-partner-api-models` + - `openapi.yaml` — developer-docs OpenAPI 3 export + - `postman.json` — published Postman collection +- **Upstream model** — +- **API docs** — +- **LWA token endpoint** — `https://api.amazon.com/auth/o2/token` +- **Ported from** — +- **Generated schemas** — `karrio/schemas/amazon_shipping/*.py` are + generated from `schemas/*.json` via + `kcli ... --no-append-type-suffix --no-nice-property-names` (see + `generate`). Never hand-edit; regenerate with + `./bin/run-generate-on modules/connectors/amazon_shipping`. diff --git a/modules/connectors/amazon_shipping/generate b/modules/connectors/amazon_shipping/generate new file mode 100755 index 0000000000..14d4618d3f --- /dev/null +++ b/modules/connectors/amazon_shipping/generate @@ -0,0 +1,20 @@ +SCHEMAS=./schemas +LIB_MODULES=./karrio/schemas/amazon_shipping +find "${LIB_MODULES}" -name "*.py" -exec rm -r {} \; +touch "${LIB_MODULES}/__init__.py" + +generate_schema() { + echo "Generating $1..." + "${ROOT}/bin/cli" codegen generate "$1" "$2" --no-append-type-suffix --no-nice-property-names +} + +# Amazon Shipping API v2 schemas +generate_schema "${SCHEMAS}/rate_request.json" "${LIB_MODULES}/rate_request.py" +generate_schema "${SCHEMAS}/rate_response.json" "${LIB_MODULES}/rate_response.py" +generate_schema "${SCHEMAS}/one_click_shipment_request.json" "${LIB_MODULES}/one_click_shipment_request.py" +generate_schema "${SCHEMAS}/one_click_shipment_response.json" "${LIB_MODULES}/one_click_shipment_response.py" +generate_schema "${SCHEMAS}/purchase_shipment_request.json" "${LIB_MODULES}/purchase_shipment_request.py" +generate_schema "${SCHEMAS}/purchase_shipment_response.json" "${LIB_MODULES}/purchase_shipment_response.py" +generate_schema "${SCHEMAS}/tracking_response.json" "${LIB_MODULES}/tracking_response.py" +generate_schema "${SCHEMAS}/error_response.json" "${LIB_MODULES}/error_response.py" +generate_schema "${SCHEMAS}/cancel_shipment_response.json" "${LIB_MODULES}/cancel_shipment_response.py" diff --git a/modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/__init__.py b/modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/__init__.py new file mode 100644 index 0000000000..8f2d10c6af --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/__init__.py @@ -0,0 +1,3 @@ +from karrio.mappers.amazon_shipping.mapper import Mapper +from karrio.mappers.amazon_shipping.proxy import Proxy +from karrio.mappers.amazon_shipping.settings import Settings diff --git a/modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/mapper.py b/modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/mapper.py new file mode 100644 index 0000000000..5dd3f39e4b --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/mapper.py @@ -0,0 +1,65 @@ +"""Karrio Amazon Shipping mapper.""" + +import karrio.api.mapper as mapper +import karrio.core.models as models +import karrio.lib as lib +import karrio.mappers.amazon_shipping.settings as provider_settings +import karrio.providers.amazon_shipping as provider + + +class Mapper(mapper.Mapper): + """Amazon Shipping API mapper.""" + + settings: provider_settings.Settings + + # Request Mappers + + def create_rate_request( + self, + payload: models.RateRequest, + ) -> lib.Serializable: + return provider.rate_request(payload, self.settings) + + def create_shipment_request( + self, + payload: models.ShipmentRequest, + ) -> lib.Serializable: + return provider.shipment_request(payload, self.settings) + + def create_cancel_shipment_request( + self, + payload: models.ShipmentCancelRequest, + ) -> lib.Serializable: + return provider.shipment_cancel_request(payload, self.settings) + + def create_tracking_request( + self, + payload: models.TrackingRequest, + ) -> lib.Serializable: + return provider.tracking_request(payload, self.settings) + + # Response Parsers + + def parse_rate_response( + self, + response: lib.Deserializable, + ) -> tuple[list[models.RateDetails], list[models.Message]]: + return provider.parse_rate_response(response, self.settings) + + def parse_shipment_response( + self, + response: lib.Deserializable, + ) -> tuple[models.ShipmentDetails, list[models.Message]]: + return provider.parse_shipment_response(response, self.settings) + + def parse_cancel_shipment_response( + self, + response: lib.Deserializable, + ) -> tuple[models.ConfirmationDetails, list[models.Message]]: + return provider.parse_shipment_cancel_response(response, self.settings) + + def parse_tracking_response( + self, + response: lib.Deserializable, + ) -> tuple[list[models.TrackingDetails], list[models.Message]]: + return provider.parse_tracking_response(response, self.settings) diff --git a/modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/proxy.py b/modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/proxy.py new file mode 100644 index 0000000000..452fd0ae89 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/proxy.py @@ -0,0 +1,178 @@ +"""Karrio Amazon Shipping API proxy.""" + +import datetime + +import karrio.api.proxy as proxy +import karrio.core.errors as errors +import karrio.core.models as models +import karrio.lib as lib +from karrio.mappers.amazon_shipping.settings import Settings + + +class Proxy(proxy.Proxy): + """Amazon Shipping SP-API proxy.""" + + settings: Settings + + def authenticate(self, _=None) -> lib.Deserializable[str]: + """LWA OAuth2 refresh-token flow with a cached access token. See SPECS.md.""" + cache_key = f"{self.settings.carrier_name}|{self.settings.client_id}" + + def get_token(): + result = lib.request( + url=self.settings.token_url, + trace=self.trace_as("json"), + method="POST", + headers={"Content-Type": "application/x-www-form-urlencoded"}, + data=lib.to_query_string( + dict( + grant_type="refresh_token", + refresh_token=self.settings.refresh_token, + client_id=self.settings.client_id, + client_secret=self.settings.client_secret, + ) + ), + max_retries=2, + ) + + response = lib.to_dict(result) + + if "error" in response: + raise errors.ParsedMessagesError( + messages=[ + models.Message( + carrier_name=self.settings.carrier_name, + carrier_id=self.settings.carrier_id, + message=response.get("error_description", response["error"]), + code=response.get("error"), + ) + ] + ) + + access_token = response.get("access_token") + if not access_token: + raise errors.ParsedMessagesError( + messages=[ + models.Message( + carrier_name=self.settings.carrier_name, + carrier_id=self.settings.carrier_id, + message="Authentication failed: No access token received", + code="AUTH_ERROR", + ) + ] + ) + + expiry = datetime.datetime.now() + datetime.timedelta(seconds=float(response.get("expires_in", 3600))) + + return { + **response, + "expiry": lib.fdatetime(expiry), + "access_token": access_token, + } + + token_state = self.settings.connection_cache.thread_safe( + refresh_func=get_token, + cache_key=cache_key, + buffer_minutes=5, + token_field="access_token", + ) + + # Handle both Token object and direct dict from cache + state = token_state.get_state() if hasattr(token_state, "get_state") else token_state + access_token = state.get("access_token") if isinstance(state, dict) else state + + return lib.Deserializable(access_token) + + def get_rates(self, request: lib.Serializable) -> lib.Deserializable: + """Get shipping rates using the v2 getRates API.""" + response = self._send_request( + path="/shipping/v2/shipments/rates", + request=request, + ) + + return lib.Deserializable(response, lib.to_dict) + + def create_shipment(self, request: lib.Serializable) -> lib.Deserializable: + """Create shipment using the oneClickShipment API for combined rate + purchase.""" + response = self._send_request( + path="/shipping/v2/oneClickShipment", + request=request, + ) + + return lib.Deserializable(response, lib.to_dict) + + def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable: + """Cancel shipment using the v2 cancelShipment API.""" + shipment_id = request.serialize() + response = self._send_request( + path=f"/shipping/v2/shipments/{shipment_id}/cancel", + request=None, + method="PUT", + ) + + return lib.Deserializable( + response if response.strip() else "{}", + lib.to_dict, + ) + + def get_tracking(self, request: lib.Serializable) -> lib.Deserializable: + """Get tracking information using the v2 getTracking API.""" + access_token = self.authenticate().deserialize() + tracking_data = request.serialize() + + def track(data: dict) -> tuple[str, str]: + tracking_id = data.get("tracking_id") + carrier_id = data.get("carrier_id", "AMZN_US") + + return ( + tracking_id, + lib.request( + url=f"{self.settings.server_url}/shipping/v2/tracking", + trace=self.trace_as("json"), + method="GET", + headers=self._get_headers(access_token), + params=dict( + trackingId=tracking_id, + carrierId=carrier_id, + ), + ), + ) + + responses: list[tuple[str, str]] = lib.run_asynchronously(track, tracking_data) + + return lib.Deserializable( + responses, + lambda res: [(key, lib.to_dict(response)) for key, response in res], + ) + + def _send_request( + self, + path: str, + request: lib.Serializable = None, + method: str = "POST", + ) -> str: + """Send request to Amazon Shipping API.""" + access_token = self.authenticate().deserialize() + data = dict(data=request.serialize()) if request is not None else {} + + return lib.request( + url=f"{self.settings.server_url}{path}", + trace=self.trace_as("json"), + method=method, + headers=self._get_headers(access_token), + **data, + ) + + def _get_headers(self, access_token: str) -> dict: + """Get request headers with authentication and business ID.""" + headers = { + "Content-Type": "application/json", + "x-amz-access-token": access_token, + } + + # Add shipping business ID if configured + business_id = self.settings.shipping_business_id or self.settings.connection_config.shipping_business_id.state + if business_id: + headers["x-amzn-shipping-business-id"] = business_id + + return headers diff --git a/modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/settings.py b/modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/settings.py new file mode 100644 index 0000000000..22397398f4 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/mappers/amazon_shipping/settings.py @@ -0,0 +1,26 @@ +"""Karrio Amazon Shipping connection settings.""" + +import attr +import karrio.providers.amazon_shipping.utils as provider_utils + + +@attr.s(auto_attribs=True) +class Settings(provider_utils.Settings): + """Amazon Shipping SP-API connection settings. See SPECS.md.""" + + # Required LWA SP-API credentials + client_id: str + client_secret: str + refresh_token: str + + # Optional configuration + aws_region: str = "us-east-1" + shipping_business_id: str = None + + # Standard karrio settings + account_country_code: str = None + carrier_id: str = "amazon_shipping" + test_mode: bool = False + metadata: dict = {} + config: dict = {} + id: str = None diff --git a/modules/connectors/amazon_shipping/karrio/plugins/amazon_shipping/__init__.py b/modules/connectors/amazon_shipping/karrio/plugins/amazon_shipping/__init__.py new file mode 100644 index 0000000000..678851af04 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/plugins/amazon_shipping/__init__.py @@ -0,0 +1,19 @@ +"""Karrio Amazon Shipping plugin.""" + +import karrio.core.metadata as metadata +import karrio.mappers.amazon_shipping as mappers +import karrio.providers.amazon_shipping.units as units + +METADATA = metadata.PluginMetadata( + status="beta", + id="amazon_shipping", + label="Amazon Shipping", + # Integrations + Mapper=mappers.Mapper, + Proxy=mappers.Proxy, + Settings=mappers.Settings, + # Data Units + services=units.ShippingService, + options=units.ShippingOption, + has_intl_accounts=True, +) diff --git a/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/__init__.py b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/__init__.py new file mode 100644 index 0000000000..21caf190c3 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/__init__.py @@ -0,0 +1,11 @@ +from karrio.providers.amazon_shipping.rate import parse_rate_response, rate_request +from karrio.providers.amazon_shipping.shipment import ( + parse_shipment_cancel_response, + parse_shipment_response, + shipment_cancel_request, + shipment_request, +) +from karrio.providers.amazon_shipping.tracking import ( + parse_tracking_response, + tracking_request, +) diff --git a/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/error.py b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/error.py new file mode 100644 index 0000000000..9188659453 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/error.py @@ -0,0 +1,29 @@ +"""Karrio Amazon Shipping error parser.""" + +import karrio.core.models as models +import karrio.providers.amazon_shipping.utils as provider_utils + + +def parse_error_response( + response: dict, + settings: provider_utils.Settings, + **kwargs, +) -> list[models.Message]: + """Parse the v2 ``{"errors": [...]}`` envelope into messages. See SPECS.md.""" + errors = response.get("errors") or [] + + return [ + models.Message( + carrier_id=settings.carrier_id, + carrier_name=settings.carrier_name, + code=error.get("code"), + message=error.get("message"), + details={ + **kwargs, + **({"note": error.get("details")} if error.get("details") else {}), + } + or None, + ) + for error in errors + if error.get("code") or error.get("message") + ] diff --git a/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/rate.py b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/rate.py new file mode 100644 index 0000000000..fc71713f25 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/rate.py @@ -0,0 +1,193 @@ +"""Karrio Amazon Shipping rating implementation.""" + +import karrio.core.models as models +import karrio.lib as lib +import karrio.providers.amazon_shipping.error as error +import karrio.providers.amazon_shipping.units as provider_units +import karrio.providers.amazon_shipping.utils as provider_utils +import karrio.schemas.amazon_shipping.rate_response as amazon + + +def parse_rate_response( + _response: lib.Deserializable[dict], + settings: provider_utils.Settings, +) -> tuple[list[models.RateDetails], list[models.Message]]: + """Parse rate response from Amazon Shipping API. See SPECS.md.""" + response = _response.deserialize() + messages = error.parse_error_response(response, settings) + payload = response.get("payload") or {} + + rates = [_extract_details(rate, settings) for rate in payload.get("rates") or []] + + return rates, messages + + +def _extract_details( + data: dict, + settings: provider_utils.Settings, +) -> models.RateDetails: + """Extract rate details from API response.""" + rate = lib.to_object(amazon.Rate, data) + + # Calculate transit days from delivery window + transit_days = lib.failsafe( + lambda: ( + ( + lib.to_date(rate.promise.deliveryWindow.start).date() + - lib.to_date(rate.promise.pickupWindow.start).date() + ).days + ) + ) + + # Extract rate items as extra charges + extra_charges = [ + models.ChargeDetails( + name=item.rateItemNameLocalization or item.rateItemID, + amount=lib.to_money(item.rateItemCharge.value), + currency=item.rateItemCharge.unit, + ) + for item in rate.rateItemList or [] + ] + + return models.RateDetails( + carrier_id=settings.carrier_id, + carrier_name=settings.carrier_name, + service=provider_units.ShippingService.map(rate.serviceId).name_or_key, + total_charge=lib.to_money(rate.totalCharge.value), + currency=rate.totalCharge.unit, + transit_days=transit_days, + extra_charges=extra_charges, + meta=dict( + rate_id=rate.rateId, + carrier_id=rate.carrierId, + carrier_name=rate.carrierName, + service_id=rate.serviceId, + service_name=rate.serviceName, + ), + ) + + +def rate_request( + payload: models.RateRequest, + settings: provider_utils.Settings, +) -> lib.Serializable: + """Create Amazon Shipping rate request.""" + shipper = lib.to_address(payload.shipper) + recipient = lib.to_address(payload.recipient) + packages = lib.to_packages(payload.parcels, required=["weight"]) + services = lib.to_services(payload.services, provider_units.ShippingService) + options = lib.to_shipping_options( + payload.options, + package_options=packages.options, + initializer=provider_units.shipping_options_initializer, + ) + customs = lib.to_customs_info( + payload.customs, + shipper=payload.shipper, + recipient=payload.recipient, + weight_unit=packages.weight_unit, + ) + customs_declared_value = lib.to_money(customs.duty.declared_value if customs.duty else None) + + label_format = options.amazon_shipping_label_format.state or settings.connection_config.label_format.state or "PNG" + + request = dict( + shipFrom=dict( + name=shipper.company_name or shipper.person_name, + addressLine1=shipper.street, + addressLine2=shipper.address_line2, + addressLine3=None, + companyName=shipper.company_name, + stateOrRegion=shipper.state_code, + city=shipper.city, + countryCode=shipper.country_code, + postalCode=shipper.postal_code, + email=shipper.email, + phoneNumber=shipper.phone_number, + ), + shipTo=dict( + name=recipient.company_name or recipient.person_name, + addressLine1=recipient.street, + addressLine2=recipient.address_line2, + addressLine3=None, + companyName=recipient.company_name, + stateOrRegion=recipient.state_code, + city=recipient.city, + countryCode=recipient.country_code, + postalCode=recipient.postal_code, + email=recipient.email, + phoneNumber=recipient.phone_number, + ), + shipDate=lib.fdatetime( + options.shipment_date.state, + current_format="%Y-%m-%d", + output_format="%Y-%m-%dT%H:%M:%SZ", + ) + if options.shipment_date.state + else None, + packages=[ + dict( + dimensions=dict( + length=package.length.IN, + width=package.width.IN, + height=package.height.IN, + unit="INCH", + ) + if package.has_dimensions + else None, + weight=dict( + value=package.weight.LB, + unit="POUND", + ), + # insuredValue and items are required by the v2 spec (see SPECS.md). + insuredValue=dict( + value=lib.to_money(package.options.declared_value.state or customs_declared_value or 0), + unit=package.options.currency.state or "USD", + ), + items=[ + dict( + quantity=item.quantity or 1, + description=item.description, + itemIdentifier=item.sku or item.hs_code, + weight=dict( + value=item.weight, + unit="POUND", + ) + if item.weight + else None, + itemValue=dict( + value=lib.to_money(item.value_amount), + unit=item.value_currency or "USD", + ) + if item.value_amount + else None, + ) + for item in (package.items or []) + ], + packageClientReferenceId=package.parcel.id or str(index), + ) + for index, package in enumerate(packages, 1) + ], + channelDetails=dict( + channelType=options.amazon_shipping_channel_type.state or "EXTERNAL", + ), + labelSpecifications=dict( + format=label_format, + size=dict( + length=settings.connection_config.label_size_length.state or 6, + width=settings.connection_config.label_size_width.state or 4, + unit=settings.connection_config.label_size_unit.state or "INCH", + ), + dpi=300, + pageLayout="DEFAULT", + needFileJoining=False, + requestedDocumentTypes=["LABEL"], + ), + serviceSelection=dict( + serviceId=list(services) if any(services) else None, + ) + if any(services) + else None, + ) + + return lib.Serializable(request, lib.to_dict) diff --git a/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/shipment/__init__.py b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/shipment/__init__.py new file mode 100644 index 0000000000..f23bb5ddbe --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/shipment/__init__.py @@ -0,0 +1,8 @@ +from karrio.providers.amazon_shipping.shipment.cancel import ( + parse_shipment_cancel_response, + shipment_cancel_request, +) +from karrio.providers.amazon_shipping.shipment.create import ( + parse_shipment_response, + shipment_request, +) diff --git a/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/shipment/cancel.py b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/shipment/cancel.py new file mode 100644 index 0000000000..f340eb8a33 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/shipment/cancel.py @@ -0,0 +1,38 @@ +"""Karrio Amazon Shipping shipment cancellation implementation.""" + +import karrio.core.models as models +import karrio.lib as lib +import karrio.providers.amazon_shipping.error as error +import karrio.providers.amazon_shipping.utils as provider_utils + + +def parse_shipment_cancel_response( + _response: lib.Deserializable[dict], + settings: provider_utils.Settings, +) -> tuple[models.ConfirmationDetails, list[models.Message]]: + """Parse shipment cancellation response (success = no errors). See SPECS.md.""" + response = _response.deserialize() + messages = error.parse_error_response(response, settings) + + success = len(messages) == 0 + + details = ( + models.ConfirmationDetails( + carrier_id=settings.carrier_id, + carrier_name=settings.carrier_name, + success=success, + operation="Cancel Shipment", + ) + if success + else None + ) + + return details, messages + + +def shipment_cancel_request( + payload: models.ShipmentCancelRequest, + settings: provider_utils.Settings, +) -> lib.Serializable: + """Serialize the shipment id for the cancel API path. See SPECS.md.""" + return lib.Serializable(payload.shipment_identifier) diff --git a/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/shipment/create.py b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/shipment/create.py new file mode 100644 index 0000000000..1cfd0d0ee7 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/shipment/create.py @@ -0,0 +1,194 @@ +"""Karrio Amazon Shipping shipment creation implementation.""" + +import karrio.core.models as models +import karrio.lib as lib +import karrio.providers.amazon_shipping.error as error +import karrio.providers.amazon_shipping.units as provider_units +import karrio.providers.amazon_shipping.utils as provider_utils +import karrio.schemas.amazon_shipping.one_click_shipment_response as amazon + + +def parse_shipment_response( + _response: lib.Deserializable[dict], + settings: provider_utils.Settings, +) -> tuple[models.ShipmentDetails, list[models.Message]]: + """Parse shipment creation response from Amazon Shipping API. See SPECS.md.""" + response = _response.deserialize() + messages = error.parse_error_response(response, settings) + payload = response.get("payload") or {} + + shipment = _extract_details(payload, settings) if payload.get("shipmentId") else None + + return shipment, messages + + +def _extract_details( + data: dict, + settings: provider_utils.Settings, +) -> models.ShipmentDetails: + """Extract shipment details from API response payload.""" + result = lib.to_object(amazon.Payload, data) + + package_docs = result.packageDocumentDetails or [] + tracking_ids = [pkg.trackingId for pkg in package_docs if pkg.trackingId] + + labels = [ + doc.contents + for pkg in package_docs + for doc in (pkg.packageDocuments or []) + if doc.type == "LABEL" and doc.contents + ] + + label_format = next( + (doc.format for pkg in package_docs for doc in (pkg.packageDocuments or []) if doc.type == "LABEL"), + "PNG", + ) + label = lib.bundle_base64(labels, label_format) if len(labels) > 1 else next(iter(labels), None) + + # PNG labels are normalised to PDF (see SPECS.md). + if label and label_format == "PNG": + label = lib.image_to_pdf(label) + label_format = "PDF" + + return models.ShipmentDetails( + carrier_id=settings.carrier_id, + carrier_name=settings.carrier_name, + tracking_number=next(iter(tracking_ids), None), + shipment_identifier=result.shipmentId, + label_type=label_format, + docs=models.Documents(label=label), + meta=dict( + shipment_id=result.shipmentId, + tracking_numbers=tracking_ids, + carrier_id=lib.failsafe(lambda: result.carrier.id), + carrier_name=lib.failsafe(lambda: result.carrier.name), + service_id=lib.failsafe(lambda: result.service.id), + service_name=lib.failsafe(lambda: result.service.name), + total_charge=lib.failsafe(lambda: lib.to_money(result.totalCharge.value)), + currency=lib.failsafe(lambda: result.totalCharge.unit), + ), + ) + + +def shipment_request( + payload: models.ShipmentRequest, + settings: provider_utils.Settings, +) -> lib.Serializable: + """Create Amazon Shipping shipment request using oneClickShipment API.""" + shipper = lib.to_address(payload.shipper) + recipient = lib.to_address(payload.recipient) + packages = lib.to_packages(payload.parcels, required=["weight"]) + service = provider_units.ShippingService.map(payload.service).value_or_key + options = lib.to_shipping_options( + payload.options, + package_options=packages.options, + initializer=provider_units.shipping_options_initializer, + ) + + label_format = options.amazon_shipping_label_format.state or settings.connection_config.label_format.state or "PNG" + + request = dict( + shipFrom=dict( + name=shipper.company_name or shipper.person_name, + addressLine1=shipper.street, + addressLine2=shipper.address_line2, + addressLine3=None, + companyName=shipper.company_name, + stateOrRegion=shipper.state_code, + city=shipper.city, + countryCode=shipper.country_code, + postalCode=shipper.postal_code, + email=shipper.email, + phoneNumber=shipper.phone_number, + ), + shipTo=dict( + name=recipient.company_name or recipient.person_name, + addressLine1=recipient.street, + addressLine2=recipient.address_line2, + addressLine3=None, + companyName=recipient.company_name, + stateOrRegion=recipient.state_code, + city=recipient.city, + countryCode=recipient.country_code, + postalCode=recipient.postal_code, + email=recipient.email, + phoneNumber=recipient.phone_number, + ), + shipDate=lib.fdatetime( + options.shipment_date.state, + current_format="%Y-%m-%d", + output_format="%Y-%m-%dT%H:%M:%SZ", + ) + if options.shipment_date.state + else None, + packages=[ + dict( + dimensions=dict( + length=package.length.IN, + width=package.width.IN, + height=package.height.IN, + unit="INCH", + ) + if package.has_dimensions + else None, + weight=dict( + value=package.weight.LB, + unit="POUND", + ), + # insuredValue and items are required by the v2 spec (see SPECS.md). + insuredValue=dict( + value=lib.to_money(package.options.declared_value.state or 0), + unit=package.options.currency.state or "USD", + ), + items=[ + dict( + quantity=item.quantity or 1, + description=item.description, + itemIdentifier=item.sku or item.hs_code, + weight=dict( + value=item.weight, + unit="POUND", + ) + if item.weight + else None, + itemValue=dict( + value=lib.to_money(item.value_amount), + unit=item.value_currency or "USD", + ) + if item.value_amount + else None, + ) + for item in (package.items or []) + ], + packageClientReferenceId=package.parcel.id or str(index), + ) + for index, package in enumerate(packages, 1) + ], + channelDetails=dict( + channelType=options.amazon_shipping_channel_type.state or "EXTERNAL", + ), + labelSpecifications=dict( + format=label_format, + size=dict( + length=settings.connection_config.label_size_length.state or 6, + width=settings.connection_config.label_size_width.state or 4, + unit=settings.connection_config.label_size_unit.state or "INCH", + ), + dpi=300, + pageLayout="DEFAULT", + needFileJoining=len(packages) > 1, + requestedDocumentTypes=["LABEL"], + ), + serviceSelection=dict( + serviceId=[service] if service else None, + ) + if service + else None, + shipperInstruction=dict( + deliveryNotes=options.delivery_instructions.state, + ) + if options.delivery_instructions.state + else None, + ) + + return lib.Serializable(request, lib.to_dict) diff --git a/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/tracking.py b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/tracking.py new file mode 100644 index 0000000000..1bb08f4450 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/tracking.py @@ -0,0 +1,119 @@ +"""Karrio Amazon Shipping tracking implementation.""" + +import karrio.core.models as models +import karrio.lib as lib +import karrio.providers.amazon_shipping.error as error +import karrio.providers.amazon_shipping.units as provider_units +import karrio.providers.amazon_shipping.utils as provider_utils +import karrio.schemas.amazon_shipping.tracking_response as amazon + + +def parse_tracking_response( + _response: lib.Deserializable[list[tuple[str, dict]]], + settings: provider_utils.Settings, +) -> tuple[list[models.TrackingDetails], list[models.Message]]: + """Parse tracking response from Amazon Shipping API.""" + responses = _response.deserialize() + + messages: list[models.Message] = sum( + [ + error.parse_error_response(response, settings, tracking_number=tracking_id) + for tracking_id, response in responses + if response.get("errors") + ], + [], + ) + + trackers = [ + _extract_details(tracking_id, response, settings) + for tracking_id, response in responses + if not response.get("errors") + ] + + return trackers, messages + + +def _extract_details( + tracking_id: str, + data: dict, + settings: provider_utils.Settings, +) -> models.TrackingDetails: + """Extract tracking details from API response payload. See SPECS.md.""" + payload = data.get("payload") or {} + details = lib.to_object(amazon.Payload, payload) + events = details.eventHistory or [] + summary = details.summary + + # Check if delivered based on summary status + delivered = lib.failsafe(lambda: summary.status == "Delivered") or False + + # Get status from the latest event + latest_event = next(iter(events), None) + status = lib.failsafe( + lambda: next( + (s.name for s in list(provider_units.TrackingStatus) if latest_event.eventCode in s.value), + None, + ) + ) + + return models.TrackingDetails( + carrier_id=settings.carrier_id, + carrier_name=settings.carrier_name, + tracking_number=details.trackingId or tracking_id, + delivered=delivered, + status=status, + estimated_delivery=lib.fdate( + details.promisedDeliveryDate, + "%Y-%m-%dT%H:%M:%SZ", + ), + events=[ + models.TrackingEvent( + date=lib.fdate(event.eventTime, "%Y-%m-%dT%H:%M:%SZ"), + time=lib.flocaltime(event.eventTime, "%Y-%m-%dT%H:%M:%SZ"), + code=event.eventCode, + description=event.eventCode, + location=lib.join( + lib.failsafe(lambda: event.location.city), + lib.failsafe(lambda: event.location.stateOrRegion), + lib.failsafe(lambda: event.location.postalCode), + lib.failsafe(lambda: event.location.countryCode), + join=True, + separator=", ", + ), + timestamp=lib.fiso_timestamp( + event.eventTime, + current_format="%Y-%m-%dT%H:%M:%SZ", + ), + status=next( + (s.name for s in list(provider_units.TrackingStatus) if event.eventCode in s.value), + None, + ), + reason=next( + (r.name for r in list(provider_units.TrackingIncidentReason) if event.eventCode in r.value), + None, + ), + ) + for event in events + ], + meta=dict( + carrier_tracking_id=details.trackingId, + alternate_tracking_id=details.alternateLegTrackingId, + received_by=lib.failsafe(lambda: summary.proofOfDelivery.receivedBy), + ), + ) + + +def tracking_request( + payload: models.TrackingRequest, + settings: provider_utils.Settings, +) -> lib.Serializable: + """Create Amazon Shipping tracking request (one entry per number). See SPECS.md.""" + tracking_data = [ + dict( + tracking_id=tracking_number, + carrier_id=payload.options.get("carrier_id", "AMZN_US"), + ) + for tracking_number in payload.tracking_numbers + ] + + return lib.Serializable(tracking_data) diff --git a/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/units.py b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/units.py new file mode 100644 index 0000000000..1d83e3d514 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/units.py @@ -0,0 +1,149 @@ +"""Karrio Amazon Shipping units and enums.""" + +import karrio.lib as lib + + +class WeightUnit(lib.StrEnum): + """Amazon Shipping weight units.""" + + GRAM = "GRAM" + KILOGRAM = "KILOGRAM" + OUNCE = "OUNCE" + POUND = "POUND" + + +class DimensionUnit(lib.StrEnum): + """Amazon Shipping dimension units.""" + + INCH = "INCH" + CENTIMETER = "CENTIMETER" + + +class LabelFormat(lib.StrEnum): + """Amazon Shipping label formats.""" + + PNG = "PNG" + PDF = "PDF" + ZPL = "ZPL" + + +class ShippingBusinessId(lib.StrEnum): + """Amazon Shipping business IDs.""" + + AmazonShipping_US = "AmazonShipping_US" + AmazonShipping_UK = "AmazonShipping_UK" + AmazonShipping_IN = "AmazonShipping_IN" + AmazonShipping_IT = "AmazonShipping_IT" + AmazonShipping_ES = "AmazonShipping_ES" + AmazonShipping_FR = "AmazonShipping_FR" + + +class ShippingService(lib.StrEnum): + """Amazon Shipping services. + + Service IDs are dynamic and returned by the getRates API. + These are common service patterns. + """ + + # US services + amazon_shipping_standard = "AMZN_US_STD" + amazon_shipping_premium = "AMZN_US_PREM" + amazon_shipping_ground = "AMZN_US_GND" + + # UK services + amazon_shipping_uk_standard = "AMZN_UK_STD" + amazon_shipping_uk_premium = "AMZN_UK_PREM" + + # Generic fallback + amazon_shipping = "AMZN" + + +class PackagingType(lib.StrEnum): + """Amazon Shipping packaging types.""" + + PACKAGE = "PACKAGE" + + # Unified packaging type mapping + envelope = PACKAGE + pak = PACKAGE + tube = PACKAGE + pallet = PACKAGE + small_box = PACKAGE + medium_box = PACKAGE + your_packaging = PACKAGE + + +class ChannelType(lib.StrEnum): + """Amazon Shipping channel types.""" + + AMAZON = "AMAZON" + EXTERNAL = "EXTERNAL" + + +class ShippingOption(lib.Enum): + """Amazon Shipping options.""" + + amazon_shipping_channel_type = lib.OptionEnum("channel_type", str) + amazon_shipping_label_format = lib.OptionEnum("label_format", str) + amazon_shipping_label_size = lib.OptionEnum("label_size", str) + + +def shipping_options_initializer( + options: dict, + package_options: lib.units.Options = None, +) -> lib.units.Options: + """Initialize Amazon Shipping options.""" + _options = options.copy() + + if package_options is not None: + _options.update(package_options.content) + + return lib.units.ShippingOptions(_options, ShippingOption) + + +class TrackingStatus(lib.Enum): + """Maps Amazon Shipping tracking event codes to karrio normalized status. + + Amazon Shipping v2 uses specific event codes for tracking. + """ + + pending = ["LabelCreated", "PickedUp", "Manifested"] + in_transit = [ + "InTransit", + "ArrivedAtCarrierFacility", + "DepartedCarrierFacility", + "ArrivedAtDeliveryStation", + "ArrivedAtLocalFacility", + ] + out_for_delivery = ["OutForDelivery"] + delivered = ["Delivered"] + on_hold = ["Delayed", "OnHold", "PaymentNotReady"] + delivery_failed = [ + "DeliveryAttempted", + "Undeliverable", + "AddressNotFound", + "BusinessClosed", + "CustomerUnavailable", + "UnableToAccess", + "UnableToContactRecipient", + ] + delivery_delayed = ["Delayed", "WeatherDelay"] + return_initiated = ["ReturnInitiated", "Rejected", "CancelledByRecipient"] + + +class TrackingIncidentReason(lib.Enum): + """Maps Amazon Shipping exception codes to normalized TrackingIncidentReason.""" + + carrier_damaged_parcel = ["Damaged"] + consignee_refused = [ + "Rejected", + "RejectedByRecipientWithVerification", + "IncorrectItems", + "NotRequired", + ] + consignee_not_home = ["CustomerUnavailable", "BusinessClosed"] + wrong_address = ["AddressNotFound"] + unable_to_access = ["UnableToAccess", "UnableToContactRecipient"] + payment_issue = ["PaymentNotReady", "OtpNotAvailable"] + hazmat = ["HazmatShipment"] + unknown = [] diff --git a/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/utils.py b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/utils.py new file mode 100644 index 0000000000..b29d5b509f --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/providers/amazon_shipping/utils.py @@ -0,0 +1,62 @@ +"""Karrio Amazon Shipping connection settings.""" + +import karrio.core as core +import karrio.lib as lib + + +class Settings(core.Settings): + """Amazon Shipping SP-API connection settings. See SPECS.md.""" + + # LWA SP-API credentials + client_id: str + client_secret: str + refresh_token: str + + # Optional settings + aws_region: str = "us-east-1" + shipping_business_id: str = None + + @property + def carrier_name(self): + return "amazon_shipping" + + @property + def server_url(self): + """Get the SP-API endpoint based on AWS region.""" + region_mapping = { + # North America + "us-east-1": "https://sellingpartnerapi-na.amazon.com", + # Europe + "eu-west-1": "https://sellingpartnerapi-eu.amazon.com", + # Far East + "us-west-2": "https://sellingpartnerapi-fe.amazon.com", + } + base_url = region_mapping.get(self.aws_region, region_mapping["us-east-1"]) + + if self.test_mode: + return base_url.replace("sellingpartnerapi", "sandbox.sellingpartnerapi") + + return base_url + + @property + def token_url(self): + """LWA token endpoint.""" + return "https://api.amazon.com/auth/o2/token" + + @property + def connection_config(self) -> lib.units.Options: + """Additional connection configuration.""" + return lib.to_connection_config( + self.config or {}, + option_type=ConnectionConfig, + ) + + +class ConnectionConfig(lib.Enum): + """Amazon Shipping connection configuration options.""" + + shipping_business_id = lib.OptionEnum("shipping_business_id") + label_format = lib.OptionEnum("label_format", str) + label_size_width = lib.OptionEnum("label_size_width", float) + label_size_length = lib.OptionEnum("label_size_length", float) + label_size_unit = lib.OptionEnum("label_size_unit", str) diff --git a/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/__init__.py b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/cancel_shipment_response.py b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/cancel_shipment_response.py new file mode 100644 index 0000000000..f08ab8f6ee --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/cancel_shipment_response.py @@ -0,0 +1,13 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class Payload: + pass + + +@attr.s(auto_attribs=True) +class CancelShipmentResponse: + payload: typing.Optional[Payload] = jstruct.JStruct[Payload] diff --git a/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/error_response.py b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/error_response.py new file mode 100644 index 0000000000..ce22d6da80 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/error_response.py @@ -0,0 +1,15 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class Error: + code: typing.Optional[str] = None + message: typing.Optional[str] = None + details: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class ErrorResponse: + errors: typing.Optional[typing.List[Error]] = jstruct.JList[Error] diff --git a/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/one_click_shipment_request.py b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/one_click_shipment_request.py new file mode 100644 index 0000000000..c357f3cf2d --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/one_click_shipment_request.py @@ -0,0 +1,61 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class ChannelDetails: + channelType: typing.Optional[str] = None + amazonOrderDetails: typing.Any = None + amazonShipmentDetails: typing.Any = None + + +@attr.s(auto_attribs=True) +class Size: + length: typing.Optional[float] = None + width: typing.Optional[float] = None + unit: typing.Optional[str] = None + height: typing.Optional[float] = None + + +@attr.s(auto_attribs=True) +class LabelSpecifications: + format: typing.Optional[str] = None + size: typing.Optional[Size] = jstruct.JStruct[Size] + dpi: typing.Optional[int] = None + pageLayout: typing.Optional[str] = None + needFileJoining: typing.Optional[bool] = None + requestedDocumentTypes: typing.Optional[typing.List[str]] = None + + +@attr.s(auto_attribs=True) +class InsuredValue: + value: typing.Optional[float] = None + unit: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class Package: + dimensions: typing.Optional[Size] = jstruct.JStruct[Size] + weight: typing.Optional[InsuredValue] = jstruct.JStruct[InsuredValue] + insuredValue: typing.Optional[InsuredValue] = jstruct.JStruct[InsuredValue] + packageClientReferenceId: typing.Optional[str] = None + items: typing.Any = None + + +@attr.s(auto_attribs=True) +class ServiceSelection: + serviceId: typing.Optional[typing.List[str]] = None + + +@attr.s(auto_attribs=True) +class OneClickShipmentRequest: + shipFrom: typing.Optional[typing.Dict[str, typing.Optional[str]]] = None + shipTo: typing.Optional[typing.Dict[str, typing.Optional[str]]] = None + returnTo: typing.Any = None + shipDate: typing.Optional[str] = None + packages: typing.Optional[typing.List[Package]] = jstruct.JList[Package] + channelDetails: typing.Optional[ChannelDetails] = jstruct.JStruct[ChannelDetails] + labelSpecifications: typing.Optional[LabelSpecifications] = jstruct.JStruct[LabelSpecifications] + serviceSelection: typing.Optional[ServiceSelection] = jstruct.JStruct[ServiceSelection] + shipperInstruction: typing.Any = None diff --git a/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/one_click_shipment_response.py b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/one_click_shipment_response.py new file mode 100644 index 0000000000..ff9c82e2ed --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/one_click_shipment_response.py @@ -0,0 +1,65 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class Carrier: + id: typing.Optional[str] = None + name: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class PackageDocument: + type: typing.Optional[str] = None + format: typing.Optional[str] = None + contents: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class PackageDocumentDetail: + packageClientReferenceId: typing.Optional[str] = None + packageDocuments: typing.Optional[typing.List[PackageDocument]] = jstruct.JList[PackageDocument] + trackingId: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class Window: + start: typing.Optional[str] = None + end: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class Promise: + deliveryWindow: typing.Optional[Window] = jstruct.JStruct[Window] + pickupWindow: typing.Optional[Window] = jstruct.JStruct[Window] + + +@attr.s(auto_attribs=True) +class Charge: + value: typing.Optional[float] = None + unit: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class RateItem: + rateItemCharge: typing.Optional[Charge] = jstruct.JStruct[Charge] + rateItemID: typing.Optional[str] = None + rateItemNameLocalization: typing.Optional[str] = None + rateItemType: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class Payload: + shipmentId: typing.Optional[str] = None + packageDocumentDetails: typing.Optional[typing.List[PackageDocumentDetail]] = jstruct.JList[PackageDocumentDetail] + promise: typing.Optional[Promise] = jstruct.JStruct[Promise] + carrier: typing.Optional[Carrier] = jstruct.JStruct[Carrier] + service: typing.Optional[Carrier] = jstruct.JStruct[Carrier] + totalCharge: typing.Optional[Charge] = jstruct.JStruct[Charge] + rateItems: typing.Optional[typing.List[RateItem]] = jstruct.JList[RateItem] + + +@attr.s(auto_attribs=True) +class OneClickShipmentResponse: + payload: typing.Optional[Payload] = jstruct.JStruct[Payload] diff --git a/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/purchase_shipment_request.py b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/purchase_shipment_request.py new file mode 100644 index 0000000000..54cbb353b2 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/purchase_shipment_request.py @@ -0,0 +1,29 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class Size: + length: typing.Optional[int] = None + width: typing.Optional[int] = None + unit: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class RequestedDocumentSpecification: + format: typing.Optional[str] = None + size: typing.Optional[Size] = jstruct.JStruct[Size] + dpi: typing.Optional[int] = None + pageLayout: typing.Optional[str] = None + needFileJoining: typing.Optional[bool] = None + requestedDocumentTypes: typing.Optional[typing.List[str]] = None + + +@attr.s(auto_attribs=True) +class PurchaseShipmentRequest: + requestToken: typing.Optional[str] = None + rateId: typing.Optional[str] = None + requestedDocumentSpecification: typing.Optional[RequestedDocumentSpecification] = jstruct.JStruct[RequestedDocumentSpecification] + requestedValueAddedServices: typing.Any = None + additionalInputs: typing.Any = None diff --git a/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/purchase_shipment_response.py b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/purchase_shipment_response.py new file mode 100644 index 0000000000..e1d7bd71e6 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/purchase_shipment_response.py @@ -0,0 +1,55 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class Benefits: + includedBenefits: typing.Optional[typing.List[str]] = None + excludedBenefits: typing.Optional[typing.List[typing.Any]] = None + + +@attr.s(auto_attribs=True) +class PackageDocument: + type: typing.Optional[str] = None + format: typing.Optional[str] = None + contents: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class PackageDocumentDetail: + packageClientReferenceId: typing.Optional[str] = None + packageDocuments: typing.Optional[typing.List[PackageDocument]] = jstruct.JList[PackageDocument] + trackingId: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class Window: + start: typing.Optional[str] = None + end: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class Promise: + deliveryWindow: typing.Optional[Window] = jstruct.JStruct[Window] + pickupWindow: typing.Optional[Window] = jstruct.JStruct[Window] + + +@attr.s(auto_attribs=True) +class TotalCharge: + value: typing.Optional[float] = None + unit: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class Payload: + shipmentId: typing.Optional[str] = None + packageDocumentDetails: typing.Optional[typing.List[PackageDocumentDetail]] = jstruct.JList[PackageDocumentDetail] + promise: typing.Optional[Promise] = jstruct.JStruct[Promise] + benefits: typing.Optional[Benefits] = jstruct.JStruct[Benefits] + totalCharge: typing.Optional[TotalCharge] = jstruct.JStruct[TotalCharge] + + +@attr.s(auto_attribs=True) +class PurchaseShipmentResponse: + payload: typing.Optional[Payload] = jstruct.JStruct[Payload] diff --git a/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/rate_request.py b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/rate_request.py new file mode 100644 index 0000000000..d7d4872b42 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/rate_request.py @@ -0,0 +1,61 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class ChannelDetails: + channelType: typing.Optional[str] = None + amazonOrderDetails: typing.Any = None + amazonShipmentDetails: typing.Any = None + + +@attr.s(auto_attribs=True) +class Size: + length: typing.Optional[float] = None + width: typing.Optional[float] = None + unit: typing.Optional[str] = None + height: typing.Optional[float] = None + + +@attr.s(auto_attribs=True) +class LabelSpecifications: + format: typing.Optional[str] = None + size: typing.Optional[Size] = jstruct.JStruct[Size] + dpi: typing.Optional[int] = None + pageLayout: typing.Optional[str] = None + needFileJoining: typing.Optional[bool] = None + requestedDocumentTypes: typing.Optional[typing.List[str]] = None + + +@attr.s(auto_attribs=True) +class InsuredValue: + value: typing.Optional[float] = None + unit: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class Package: + dimensions: typing.Optional[Size] = jstruct.JStruct[Size] + weight: typing.Optional[InsuredValue] = jstruct.JStruct[InsuredValue] + insuredValue: typing.Optional[InsuredValue] = jstruct.JStruct[InsuredValue] + packageClientReferenceId: typing.Optional[str] = None + items: typing.Any = None + + +@attr.s(auto_attribs=True) +class ServiceSelection: + serviceId: typing.Optional[typing.List[str]] = None + + +@attr.s(auto_attribs=True) +class RateRequest: + shipFrom: typing.Optional[typing.Dict[str, typing.Optional[str]]] = None + shipTo: typing.Optional[typing.Dict[str, typing.Optional[str]]] = None + returnTo: typing.Any = None + shipDate: typing.Optional[str] = None + packages: typing.Optional[typing.List[Package]] = jstruct.JList[Package] + channelDetails: typing.Optional[ChannelDetails] = jstruct.JStruct[ChannelDetails] + labelSpecifications: typing.Optional[LabelSpecifications] = jstruct.JStruct[LabelSpecifications] + serviceSelection: typing.Optional[ServiceSelection] = jstruct.JStruct[ServiceSelection] + shipperInstruction: typing.Any = None diff --git a/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/rate_response.py b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/rate_response.py new file mode 100644 index 0000000000..63c9a26671 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/rate_response.py @@ -0,0 +1,102 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class IneligibilityReason: + code: typing.Optional[str] = None + message: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class IneligibleRate: + carrierId: typing.Optional[str] = None + carrierName: typing.Optional[str] = None + serviceId: typing.Optional[str] = None + serviceName: typing.Optional[str] = None + ineligibilityReasons: typing.Optional[typing.List[IneligibilityReason]] = jstruct.JList[IneligibilityReason] + + +@attr.s(auto_attribs=True) +class BilledWeight: + value: typing.Optional[float] = None + unit: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class Window: + start: typing.Optional[str] = None + end: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class Promise: + deliveryWindow: typing.Optional[Window] = jstruct.JStruct[Window] + pickupWindow: typing.Optional[Window] = jstruct.JStruct[Window] + + +@attr.s(auto_attribs=True) +class RateItemList: + rateItemCharge: typing.Optional[BilledWeight] = jstruct.JStruct[BilledWeight] + rateItemID: typing.Optional[str] = None + rateItemNameLocalization: typing.Optional[str] = None + rateItemType: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class SupportedDocumentDetail: + name: typing.Optional[str] = None + isMandatory: typing.Optional[bool] = None + + +@attr.s(auto_attribs=True) +class PrintOption: + supportedDPIs: typing.Optional[typing.List[int]] = None + supportedPageLayouts: typing.Optional[typing.List[str]] = None + supportedFileJoiningOptions: typing.Optional[typing.List[bool]] = None + supportedDocumentDetails: typing.Optional[typing.List[SupportedDocumentDetail]] = jstruct.JList[SupportedDocumentDetail] + + +@attr.s(auto_attribs=True) +class Size: + length: typing.Optional[int] = None + width: typing.Optional[int] = None + unit: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class SupportedDocumentSpecification: + format: typing.Optional[str] = None + size: typing.Optional[Size] = jstruct.JStruct[Size] + printOptions: typing.Optional[typing.List[PrintOption]] = jstruct.JList[PrintOption] + + +@attr.s(auto_attribs=True) +class Rate: + rateId: typing.Optional[str] = None + carrierId: typing.Optional[str] = None + carrierName: typing.Optional[str] = None + billedWeight: typing.Optional[BilledWeight] = jstruct.JStruct[BilledWeight] + totalCharge: typing.Optional[BilledWeight] = jstruct.JStruct[BilledWeight] + serviceId: typing.Optional[str] = None + serviceName: typing.Optional[str] = None + promise: typing.Optional[Promise] = jstruct.JStruct[Promise] + supportedDocumentSpecifications: typing.Optional[typing.List[SupportedDocumentSpecification]] = jstruct.JList[SupportedDocumentSpecification] + availableValueAddedServiceGroups: typing.Any = None + requiresAdditionalInputs: typing.Optional[bool] = None + rateItemList: typing.Optional[typing.List[RateItemList]] = jstruct.JList[RateItemList] + paymentType: typing.Optional[str] = None + benefits: typing.Any = None + + +@attr.s(auto_attribs=True) +class Payload: + requestToken: typing.Optional[str] = None + rates: typing.Optional[typing.List[Rate]] = jstruct.JList[Rate] + ineligibleRates: typing.Optional[typing.List[IneligibleRate]] = jstruct.JList[IneligibleRate] + + +@attr.s(auto_attribs=True) +class RateResponse: + payload: typing.Optional[Payload] = jstruct.JStruct[Payload] diff --git a/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/tracking_response.py b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/tracking_response.py new file mode 100644 index 0000000000..fc693abe93 --- /dev/null +++ b/modules/connectors/amazon_shipping/karrio/schemas/amazon_shipping/tracking_response.py @@ -0,0 +1,59 @@ +import attr +import jstruct +import typing + + +@attr.s(auto_attribs=True) +class Location: + city: typing.Optional[str] = None + stateOrRegion: typing.Optional[str] = None + countryCode: typing.Optional[str] = None + postalCode: typing.Optional[int] = None + + +@attr.s(auto_attribs=True) +class EventHistory: + eventCode: typing.Optional[str] = None + location: typing.Optional[Location] = jstruct.JStruct[Location] + eventTime: typing.Optional[str] = None + shipmentType: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class DeliveryLocationCoordinates: + latitude: typing.Optional[float] = None + longitude: typing.Optional[float] = None + + +@attr.s(auto_attribs=True) +class ProofOfDelivery: + deliveryLocationCoordinates: typing.Optional[DeliveryLocationCoordinates] = jstruct.JStruct[DeliveryLocationCoordinates] + deliveryImageURL: typing.Any = None + receivedBy: typing.Optional[str] = None + + +@attr.s(auto_attribs=True) +class TrackingDetailCodes: + forward: typing.Optional[typing.List[str]] = None + returns: typing.Optional[typing.List[typing.Any]] = None + + +@attr.s(auto_attribs=True) +class Summary: + status: typing.Optional[str] = None + trackingDetailCodes: typing.Optional[TrackingDetailCodes] = jstruct.JStruct[TrackingDetailCodes] + proofOfDelivery: typing.Optional[ProofOfDelivery] = jstruct.JStruct[ProofOfDelivery] + + +@attr.s(auto_attribs=True) +class Payload: + trackingId: typing.Optional[str] = None + alternateLegTrackingId: typing.Any = None + eventHistory: typing.Optional[typing.List[EventHistory]] = jstruct.JList[EventHistory] + promisedDeliveryDate: typing.Optional[str] = None + summary: typing.Optional[Summary] = jstruct.JStruct[Summary] + + +@attr.s(auto_attribs=True) +class TrackingResponse: + payload: typing.Optional[Payload] = jstruct.JStruct[Payload] diff --git a/modules/connectors/amazon_shipping/pyproject.toml b/modules/connectors/amazon_shipping/pyproject.toml new file mode 100644 index 0000000000..44066b5275 --- /dev/null +++ b/modules/connectors/amazon_shipping/pyproject.toml @@ -0,0 +1,39 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "karrio_amazon_shipping" +version = "2026.1.29" +description = "Karrio - Amazon Shipping Extension" +readme = "README.md" +requires-python = ">=3.11" +license = "LGPL-3.0" +authors = [ + {name = "karrio", email = "hello@karrio.io"} +] +classifiers = [ + "Intended Audience :: Developers", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", +] +dependencies = [ + "karrio", +] + +[project.urls] +Homepage = "https://github.com/karrioapi/karrio" + +[project.entry-points."karrio.plugins"] +amazon_shipping = "karrio.plugins.amazon_shipping:METADATA" + +[tool.setuptools] +zip-safe = false +include-package-data = true + +[tool.setuptools.package-dir] +"" = "." + +[tool.setuptools.packages.find] +exclude = ["tests.*", "tests"] +namespaces = true diff --git a/modules/connectors/amazon_shipping/schemas/cancel_shipment_response.json b/modules/connectors/amazon_shipping/schemas/cancel_shipment_response.json new file mode 100644 index 0000000000..d29bdbad56 --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/cancel_shipment_response.json @@ -0,0 +1,3 @@ +{ + "payload": {} +} diff --git a/modules/connectors/amazon_shipping/schemas/create_shipment_request.json b/modules/connectors/amazon_shipping/schemas/create_shipment_request.json new file mode 100644 index 0000000000..850527abc6 --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/create_shipment_request.json @@ -0,0 +1,141 @@ +{ + "clientReferenceId": "Lorem id", + "containers": [ + { + "containerReferenceId": "do", + "dimensions": { + "height": -38572150.35065935, + "length": -85750121.68778314, + "unit": "CM", + "width": -54631286.35967256 + }, + "items": [ + { + "quantity": 12603075.859053627, + "title": "veniam ad labore si", + "unitPrice": { + "unit": "sed", + "value": 44025615.037848175 + }, + "unitWeight": { + "unit": { + "value": "" + }, + "value": { + "value": "" + } + } + }, + { + "quantity": 28036120.64922984, + "title": "dolor", + "unitPrice": { + "unit": "Lor", + "value": 49485557.865751475 + }, + "unitWeight": { + "unit": { + "value": "" + }, + "value": { + "value": "" + } + } + } + ], + "value": { + "unit": "vol", + "value": -24786701.7194324 + }, + "weight": { + "unit": "g", + "value": 71836922.63174221 + }, + "containerType": "PACKAGE" + }, + { + "containerReferenceId": "ex elit", + "dimensions": { + "height": 26717441.485329196, + "length": -66384125.902533375, + "unit": "IN", + "width": 34260621.139361024 + }, + "items": [ + { + "quantity": 34332844.918010294, + "title": "", + "unitPrice": { + "unit": "nis", + "value": -17673259.73329337 + }, + "unitWeight": { + "unit": { + "value": "" + }, + "value": { + "value": "" + } + } + }, + { + "quantity": 4902160.586336464, + "title": "do ea laborum dolor id", + "unitPrice": { + "unit": "id ", + "value": -25493299.78321944 + }, + "unitWeight": { + "unit": { + "value": "" + }, + "value": { + "value": "" + } + } + } + ], + "value": { + "unit": "occ", + "value": 25804726.230912417 + }, + "weight": { + "unit": "lb", + "value": -82720999.81444092 + }, + "containerType": "PACKAGE" + } + ], + "shipFrom": { + "addressLine1": "sint adipisicin", + "city": "sunt aute est", + "countryCode": "ve", + "name": "proi", + "postalCode": "proide", + "stateOrRegion": "enim", + "addressLine2": "minim aliqua ea", + "addressLine3": "ipsum amet", + "email": "est reprehenderit", + "copyEmails": [ + "Lorem sint voluptat", + "mollit" + ], + "phoneNumber": "elit tempor" + }, + "shipTo": { + "addressLine1": "veniam laborum Lorem", + "city": "do deserunt anim", + "countryCode": "mo", + "name": "conse", + "postalCode": "mollit incididunt ", + "stateOrRegion": "consectetur", + "addressLine2": "mollit", + "addressLine3": "enim Duis nulla occaecat nostrud", + "email": "dolore", + "copyEmails": [ + "elit ullamco ut laborum eiusmod", + "fugiat exercitation aute" + ], + "phoneNumber": "commodo proident in" + } +} diff --git a/modules/connectors/amazon_shipping/schemas/create_shipment_response.json b/modules/connectors/amazon_shipping/schemas/create_shipment_response.json new file mode 100644 index 0000000000..72a765959c --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/create_shipment_response.json @@ -0,0 +1,28 @@ +{ + "shipmentId": "89108749065090", + "eligibleRates": [ + { + "billedWeight": { + "value": 4, + "unit": "kg" + }, + "totalCharge": { + "value": 3.25, + "unit": "GBP" + }, + "serviceType": "Amazon Shipping Standard", + "promise": { + "deliveryWindow": { + "start": "2018-08-25T20:22:30.737Z", + "end": "2018-08-26T20:22:30.737Z" + }, + "receiveWindow": { + "start": "2018-08-23T09:22:30.737Z", + "end": "2018-08-23T11:22:30.737Z" + } + }, + "rateId": "RI123456", + "expirationTime": "2018-08-22T09:22:30.737Z" + } + ] +} diff --git a/modules/connectors/amazon_shipping/schemas/error_response.json b/modules/connectors/amazon_shipping/schemas/error_response.json new file mode 100644 index 0000000000..7a8b588576 --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/error_response.json @@ -0,0 +1,9 @@ +{ + "errors": [ + { + "code": "string", + "message": "string", + "details": "string" + } + ] +} diff --git a/modules/connectors/amazon_shipping/schemas/one_click_shipment_request.json b/modules/connectors/amazon_shipping/schemas/one_click_shipment_request.json new file mode 100644 index 0000000000..e7e1075140 --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/one_click_shipment_request.json @@ -0,0 +1,71 @@ +{ + "shipFrom": { + "name": "John Doe", + "addressLine1": "123 Main St", + "addressLine2": null, + "addressLine3": null, + "companyName": "Test Company", + "stateOrRegion": "WA", + "city": "Seattle", + "countryCode": "US", + "postalCode": "98101", + "email": "test@example.com", + "phoneNumber": "+12065551234" + }, + "shipTo": { + "name": "Jane Doe", + "addressLine1": "456 Oak Ave", + "addressLine2": null, + "addressLine3": null, + "companyName": null, + "stateOrRegion": "CA", + "city": "Los Angeles", + "countryCode": "US", + "postalCode": "90001", + "email": "recipient@example.com", + "phoneNumber": "+13105551234" + }, + "returnTo": null, + "shipDate": "2024-01-15T10:00:00Z", + "packages": [ + { + "dimensions": { + "length": 10.0, + "width": 8.0, + "height": 6.0, + "unit": "INCH" + }, + "weight": { + "value": 2.5, + "unit": "POUND" + }, + "insuredValue": { + "value": 100.00, + "unit": "USD" + }, + "packageClientReferenceId": "PKG001", + "items": null + } + ], + "channelDetails": { + "channelType": "EXTERNAL", + "amazonOrderDetails": null, + "amazonShipmentDetails": null + }, + "labelSpecifications": { + "format": "PNG", + "size": { + "length": 6, + "width": 4, + "unit": "INCH" + }, + "dpi": 300, + "pageLayout": "DEFAULT", + "needFileJoining": false, + "requestedDocumentTypes": ["LABEL"] + }, + "serviceSelection": { + "serviceId": ["AMZN_US_STD"] + }, + "shipperInstruction": null +} diff --git a/modules/connectors/amazon_shipping/schemas/one_click_shipment_response.json b/modules/connectors/amazon_shipping/schemas/one_click_shipment_response.json new file mode 100644 index 0000000000..b2e5a1aa89 --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/one_click_shipment_response.json @@ -0,0 +1,60 @@ +{ + "payload": { + "shipmentId": "87852211788104", + "packageDocumentDetails": [ + { + "packageClientReferenceId": "PKG001", + "packageDocuments": [ + { + "type": "LABEL", + "format": "PNG", + "contents": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + } + ], + "trackingId": "1578648261977" + } + ], + "promise": { + "deliveryWindow": { + "start": "2024-01-17T08:00:00Z", + "end": "2024-01-17T20:00:00Z" + }, + "pickupWindow": { + "start": "2024-01-15T08:00:00Z", + "end": "2024-01-15T20:00:00Z" + } + }, + "carrier": { + "id": "AMZN_US", + "name": "Amazon Shipping" + }, + "service": { + "id": "AMZN_US_STD", + "name": "Amazon Shipping Standard" + }, + "totalCharge": { + "value": 7.99, + "unit": "USD" + }, + "rateItems": [ + { + "rateItemCharge": { + "unit": "USD", + "value": 6.99 + }, + "rateItemID": "BASE_RATE", + "rateItemNameLocalization": "Base Rate", + "rateItemType": "MANDATORY" + }, + { + "rateItemCharge": { + "unit": "USD", + "value": 1.00 + }, + "rateItemID": "FUEL_SURCHARGE", + "rateItemNameLocalization": "Fuel Surcharge", + "rateItemType": "INCLUDED" + } + ] + } +} diff --git a/modules/connectors/amazon_shipping/schemas/purchase_label_request.json b/modules/connectors/amazon_shipping/schemas/purchase_label_request.json new file mode 100644 index 0000000000..d3d86a17cc --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/purchase_label_request.json @@ -0,0 +1,7 @@ +{ + "labelSpecification": { + "labelFormat": "PNG", + "labelStockSize": "4x6" + }, + "rateId": "ex aliqua eiusmod aliquip" +} diff --git a/modules/connectors/amazon_shipping/schemas/purchase_label_response.json b/modules/connectors/amazon_shipping/schemas/purchase_label_response.json new file mode 100644 index 0000000000..84beedaa1f --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/purchase_label_response.json @@ -0,0 +1,38 @@ +{ + "shipmentId": "89108749065090", + "clientReferenceId": "911-7267646-6348616", + "acceptedRate": { + "billedWeight": { + "value": 4, + "unit": "kg" + }, + "totalCharge": { + "value": 3.5, + "unit": "GBP" + }, + "serviceType": "Amazon Shipping Standard", + "promise": { + "deliveryWindow": { + "start": "2018-08-25T20:22:30.737Z", + "end": "2018-08-26T20:22:30.737Z" + }, + "receiveWindow": { + "start": "2018-08-23T09:22:30.737Z", + "end": "2018-08-23T11:22:30.737Z" + } + } + }, + "labelResults": [ + { + "containerReferenceId": "CRI123456789", + "trackingId": "1512748795322", + "label": { + "labelStream": "iVBORw0KGgo...AAAARK5CYII=(Truncated)", + "labelSpecification": { + "labelFormat": "PNG", + "labelStockSize": "4x6" + } + } + } + ] +} diff --git a/modules/connectors/amazon_shipping/schemas/purchase_shipment_request.json b/modules/connectors/amazon_shipping/schemas/purchase_shipment_request.json new file mode 100644 index 0000000000..5ece466a83 --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/purchase_shipment_request.json @@ -0,0 +1,18 @@ +{ + "requestToken": "6DCCEDD3FF961C15FEB94F342D41C", + "rateId": "F4B68849F969E239FF9FCA9C12E35", + "requestedDocumentSpecification": { + "format": "PNG", + "size": { + "length": 6, + "width": 4, + "unit": "INCH" + }, + "dpi": 300, + "pageLayout": "DEFAULT", + "needFileJoining": false, + "requestedDocumentTypes": ["LABEL"] + }, + "requestedValueAddedServices": null, + "additionalInputs": null +} diff --git a/modules/connectors/amazon_shipping/schemas/purchase_shipment_response.json b/modules/connectors/amazon_shipping/schemas/purchase_shipment_response.json new file mode 100644 index 0000000000..e6a9fcde9e --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/purchase_shipment_response.json @@ -0,0 +1,36 @@ +{ + "payload": { + "shipmentId": "87852211788104", + "packageDocumentDetails": [ + { + "packageClientReferenceId": "PKG001", + "packageDocuments": [ + { + "type": "LABEL", + "format": "PNG", + "contents": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + } + ], + "trackingId": "1578648261977" + } + ], + "promise": { + "deliveryWindow": { + "start": "2024-01-17T08:00:00Z", + "end": "2024-01-17T20:00:00Z" + }, + "pickupWindow": { + "start": "2024-01-15T08:00:00Z", + "end": "2024-01-15T20:00:00Z" + } + }, + "benefits": { + "includedBenefits": ["CLAIMS_PROTECTED"], + "excludedBenefits": [] + }, + "totalCharge": { + "value": 7.99, + "unit": "USD" + } + } +} diff --git a/modules/connectors/amazon_shipping/schemas/rate_request.json b/modules/connectors/amazon_shipping/schemas/rate_request.json new file mode 100644 index 0000000000..e7e1075140 --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/rate_request.json @@ -0,0 +1,71 @@ +{ + "shipFrom": { + "name": "John Doe", + "addressLine1": "123 Main St", + "addressLine2": null, + "addressLine3": null, + "companyName": "Test Company", + "stateOrRegion": "WA", + "city": "Seattle", + "countryCode": "US", + "postalCode": "98101", + "email": "test@example.com", + "phoneNumber": "+12065551234" + }, + "shipTo": { + "name": "Jane Doe", + "addressLine1": "456 Oak Ave", + "addressLine2": null, + "addressLine3": null, + "companyName": null, + "stateOrRegion": "CA", + "city": "Los Angeles", + "countryCode": "US", + "postalCode": "90001", + "email": "recipient@example.com", + "phoneNumber": "+13105551234" + }, + "returnTo": null, + "shipDate": "2024-01-15T10:00:00Z", + "packages": [ + { + "dimensions": { + "length": 10.0, + "width": 8.0, + "height": 6.0, + "unit": "INCH" + }, + "weight": { + "value": 2.5, + "unit": "POUND" + }, + "insuredValue": { + "value": 100.00, + "unit": "USD" + }, + "packageClientReferenceId": "PKG001", + "items": null + } + ], + "channelDetails": { + "channelType": "EXTERNAL", + "amazonOrderDetails": null, + "amazonShipmentDetails": null + }, + "labelSpecifications": { + "format": "PNG", + "size": { + "length": 6, + "width": 4, + "unit": "INCH" + }, + "dpi": 300, + "pageLayout": "DEFAULT", + "needFileJoining": false, + "requestedDocumentTypes": ["LABEL"] + }, + "serviceSelection": { + "serviceId": ["AMZN_US_STD"] + }, + "shipperInstruction": null +} diff --git a/modules/connectors/amazon_shipping/schemas/rate_response.json b/modules/connectors/amazon_shipping/schemas/rate_response.json new file mode 100644 index 0000000000..7e74ebc611 --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/rate_response.json @@ -0,0 +1,93 @@ +{ + "payload": { + "requestToken": "6DCCEDD3FF961C15FEB94F342D41C", + "rates": [ + { + "rateId": "F4B68849F969E239FF9FCA9C12E35", + "carrierId": "AMZN_US", + "carrierName": "Amazon Shipping", + "billedWeight": { + "value": 2.5, + "unit": "POUND" + }, + "totalCharge": { + "value": 7.99, + "unit": "USD" + }, + "serviceId": "AMZN_US_STD", + "serviceName": "Amazon Shipping Standard", + "promise": { + "deliveryWindow": { + "start": "2024-01-17T08:00:00Z", + "end": "2024-01-17T20:00:00Z" + }, + "pickupWindow": { + "start": "2024-01-15T08:00:00Z", + "end": "2024-01-15T20:00:00Z" + } + }, + "supportedDocumentSpecifications": [ + { + "format": "PNG", + "size": { + "length": 6, + "width": 4, + "unit": "INCH" + }, + "printOptions": [ + { + "supportedDPIs": [300, 203], + "supportedPageLayouts": ["DEFAULT"], + "supportedFileJoiningOptions": [true, false], + "supportedDocumentDetails": [ + { + "name": "LABEL", + "isMandatory": true + } + ] + } + ] + } + ], + "availableValueAddedServiceGroups": null, + "requiresAdditionalInputs": false, + "rateItemList": [ + { + "rateItemCharge": { + "unit": "USD", + "value": 6.99 + }, + "rateItemID": "BASE_RATE", + "rateItemNameLocalization": "Base Rate", + "rateItemType": "MANDATORY" + }, + { + "rateItemCharge": { + "unit": "USD", + "value": 1.00 + }, + "rateItemID": "FUEL_SURCHARGE", + "rateItemNameLocalization": "Fuel Surcharge", + "rateItemType": "INCLUDED" + } + ], + "paymentType": "PAY_THROUGH_AMAZON", + "benefits": null + } + ], + "ineligibleRates": [ + { + "carrierId": "AMZN_US", + "carrierName": "Amazon Shipping", + "serviceId": "AMZN_US_PREM", + "serviceName": "Amazon Shipping Premium", + "ineligibilityReasons": [ + { + "code": "NO_COVERAGE", + "message": "Required shipping network coverage doesn't exist for the offering" + } + ] + } + ] + } +} diff --git a/modules/connectors/amazon_shipping/schemas/shipping_label.json b/modules/connectors/amazon_shipping/schemas/shipping_label.json new file mode 100644 index 0000000000..31fa52952c --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/shipping_label.json @@ -0,0 +1,7 @@ +{ + "labelStream": "iVBORw0KGgo...AAAARK5CYII=(Truncated)", + "labelSpecification": { + "labelFormat": "PNG", + "labelStockSize": "4x6" + } +} diff --git a/modules/connectors/amazon_shipping/schemas/tracking_response.json b/modules/connectors/amazon_shipping/schemas/tracking_response.json new file mode 100644 index 0000000000..96237104af --- /dev/null +++ b/modules/connectors/amazon_shipping/schemas/tracking_response.json @@ -0,0 +1,79 @@ +{ + "payload": { + "trackingId": "1578648261977", + "alternateLegTrackingId": null, + "eventHistory": [ + { + "eventCode": "Delivered", + "location": { + "city": "Seattle", + "stateOrRegion": "WA", + "countryCode": "US", + "postalCode": "98101" + }, + "eventTime": "2024-01-17T14:30:00Z", + "shipmentType": "FORWARD" + }, + { + "eventCode": "OutForDelivery", + "location": { + "city": "Seattle", + "stateOrRegion": "WA", + "countryCode": "US", + "postalCode": "98101" + }, + "eventTime": "2024-01-17T08:00:00Z", + "shipmentType": "FORWARD" + }, + { + "eventCode": "ArrivedAtDeliveryStation", + "location": { + "city": "Seattle", + "stateOrRegion": "WA", + "countryCode": "US", + "postalCode": "98101" + }, + "eventTime": "2024-01-16T22:00:00Z", + "shipmentType": "FORWARD" + }, + { + "eventCode": "InTransit", + "location": { + "city": "Portland", + "stateOrRegion": "OR", + "countryCode": "US", + "postalCode": "97201" + }, + "eventTime": "2024-01-16T10:00:00Z", + "shipmentType": "FORWARD" + }, + { + "eventCode": "PickedUp", + "location": { + "city": "Los Angeles", + "stateOrRegion": "CA", + "countryCode": "US", + "postalCode": "90001" + }, + "eventTime": "2024-01-15T15:00:00Z", + "shipmentType": "FORWARD" + } + ], + "promisedDeliveryDate": "2024-01-17T20:00:00Z", + "summary": { + "status": "Delivered", + "trackingDetailCodes": { + "forward": ["Signed"], + "returns": [] + }, + "proofOfDelivery": { + "deliveryLocationCoordinates": { + "latitude": 47.6062, + "longitude": -122.3321 + }, + "deliveryImageURL": null, + "receivedBy": "John Doe" + } + } + } +} diff --git a/modules/connectors/amazon_shipping/tests/__init__.py b/modules/connectors/amazon_shipping/tests/__init__.py new file mode 100644 index 0000000000..2645fc0f4f --- /dev/null +++ b/modules/connectors/amazon_shipping/tests/__init__.py @@ -0,0 +1,4 @@ +from tests.amazon_shipping.test_authentication import * +from tests.amazon_shipping.test_rate import * +from tests.amazon_shipping.test_shipment import * +from tests.amazon_shipping.test_tracking import * diff --git a/modules/connectors/amazon_shipping/tests/amazon_shipping/__init__.py b/modules/connectors/amazon_shipping/tests/amazon_shipping/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/modules/connectors/amazon_shipping/tests/amazon_shipping/fixture.py b/modules/connectors/amazon_shipping/tests/amazon_shipping/fixture.py new file mode 100644 index 0000000000..8a9a91aa5b --- /dev/null +++ b/modules/connectors/amazon_shipping/tests/amazon_shipping/fixture.py @@ -0,0 +1,31 @@ +"""Amazon Shipping test fixtures.""" + +import datetime + +import karrio.lib as lib +import karrio.sdk as karrio + +# Test SP-API credentials +client_id = "amzn1.application-oa2-client.test123" +client_secret = "test-client-secret" +refresh_token = "Atzr|test-refresh-token" + +# Pre-cached auth token to avoid authentication in tests +expiry = datetime.datetime.now() + datetime.timedelta(days=1) +cached_auth = { + f"amazon_shipping|{client_id}": dict( + access_token="Atza|test-access-token", + expiry=lib.fdatetime(expiry), + ) +} + +gateway = karrio.gateway["amazon_shipping"].create( + dict( + client_id=client_id, + client_secret=client_secret, + refresh_token=refresh_token, + aws_region="us-east-1", + shipping_business_id="AmazonShipping_US", + ), + cache=lib.Cache(**cached_auth), +) diff --git a/modules/connectors/amazon_shipping/tests/amazon_shipping/test_authentication.py b/modules/connectors/amazon_shipping/tests/amazon_shipping/test_authentication.py new file mode 100644 index 0000000000..5b8a501a7f --- /dev/null +++ b/modules/connectors/amazon_shipping/tests/amazon_shipping/test_authentication.py @@ -0,0 +1,51 @@ +"""Amazon Shipping authentication tests.""" + +import unittest +from unittest.mock import patch + +import karrio.sdk as karrio + + +class TestAmazonShippingAuthentication(unittest.TestCase): + def setUp(self): + self.maxDiff = None + + def test_authenticate(self): + # Create a fresh gateway without cached auth to test the OAuth2 flow + fresh_gateway = karrio.gateway["amazon_shipping"].create( + dict( + client_id="amzn1.application-oa2-client.test123", + client_secret="test-client-secret", + refresh_token="Atzr|test-refresh-token", + aws_region="us-east-1", + ), + ) + + with patch("karrio.mappers.amazon_shipping.proxy.lib.request") as mock: + mock.return_value = TokenResponse + result = fresh_gateway.proxy.authenticate() + parsed_response = result.deserialize() + + self.assertEqual( + mock.call_args[1]["url"], + "https://api.amazon.com/auth/o2/token", + ) + self.assertEqual(mock.call_args[1]["method"], "POST") + self.assertEqual( + mock.call_args[1]["headers"]["Content-Type"], + "application/x-www-form-urlencoded", + ) + self.assertEqual(parsed_response, "Atza|test-access-token") + + +if __name__ == "__main__": + unittest.main() + + +TokenResponse = """{ + "access_token": "Atza|test-access-token", + "token_type": "bearer", + "expires_in": 3600, + "refresh_token": "Atzr|test-refresh-token" +} +""" diff --git a/modules/connectors/amazon_shipping/tests/amazon_shipping/test_rate.py b/modules/connectors/amazon_shipping/tests/amazon_shipping/test_rate.py new file mode 100644 index 0000000000..d306183b8c --- /dev/null +++ b/modules/connectors/amazon_shipping/tests/amazon_shipping/test_rate.py @@ -0,0 +1,189 @@ +"""Amazon Shipping rating tests.""" + +import unittest +from unittest.mock import patch + +import karrio.core.models as models +import karrio.lib as lib +import karrio.sdk as karrio + +from .fixture import gateway + + +class TestAmazonShippingRating(unittest.TestCase): + def setUp(self): + self.maxDiff = None + self.RateRequest = models.RateRequest(**RATE_PAYLOAD) + + def test_create_rate_request(self): + request = gateway.mapper.create_rate_request(self.RateRequest) + + self.assertDictEqual(request.serialize(), RateRequestJSON) + + def test_get_rate(self): + with patch("karrio.mappers.amazon_shipping.proxy.lib.request") as mock: + mock.return_value = "{}" + karrio.Rating.fetch(self.RateRequest).from_(gateway) + + self.assertEqual( + mock.call_args[1]["url"], + f"{gateway.settings.server_url}/shipping/v2/shipments/rates", + ) + + def test_parse_rate_response(self): + with patch("karrio.mappers.amazon_shipping.proxy.lib.request") as mock: + mock.return_value = RateResponseJSON + parsed_response = karrio.Rating.fetch(self.RateRequest).from_(gateway).parse() + + self.assertListEqual( + lib.to_dict(parsed_response), + ParsedRateResponse, + ) + + +if __name__ == "__main__": + unittest.main() + + +RATE_PAYLOAD = { + "reference": "order #1111", + "recipient": { + "company_name": "AmazonShipping", + "address_line1": "417 Montgomery Street", + "address_line2": "5th Floor", + "city": "San Francisco", + "state_code": "CA", + "postal_code": "94104", + "country_code": "US", + "phone_number": "415-528-7555", + "email": "test@example.com", + }, + "shipper": { + "person_name": "George Costanza", + "company_name": "Vandelay Industries", + "address_line1": "1 E 161st St.", + "city": "Bronx", + "state_code": "NY", + "postal_code": "10451", + "country_code": "US", + }, + "parcels": [{"length": 9.0, "width": 6.0, "height": 2.0, "weight": 10.0}], + "options": {"shipment_date": "2024-01-15"}, +} + +RateRequestJSON = { + "shipFrom": { + "name": "Vandelay Industries", + "addressLine1": "1 E 161st St.", + "companyName": "Vandelay Industries", + "stateOrRegion": "NY", + "city": "Bronx", + "countryCode": "US", + "postalCode": "10451", + }, + "shipTo": { + "name": "AmazonShipping", + "addressLine1": "417 Montgomery Street", + "addressLine2": "5th Floor", + "companyName": "AmazonShipping", + "stateOrRegion": "CA", + "city": "San Francisco", + "countryCode": "US", + "postalCode": "94104", + "email": "test@example.com", + "phoneNumber": "415-528-7555", + }, + "shipDate": "2024-01-15T00:00:00Z", + "packages": [ + { + "dimensions": { + "length": 9.0, + "width": 6.0, + "height": 2.0, + "unit": "INCH", + }, + "weight": { + "value": 10.0, + "unit": "POUND", + }, + "insuredValue": {"value": 0.0, "unit": "USD"}, + "packageClientReferenceId": "1", + } + ], + "channelDetails": { + "channelType": "EXTERNAL", + }, + "labelSpecifications": { + "format": "PNG", + "size": { + "length": 6, + "width": 4, + "unit": "INCH", + }, + "dpi": 300, + "pageLayout": "DEFAULT", + "needFileJoining": False, + "requestedDocumentTypes": ["LABEL"], + }, +} + +ParsedRateResponse = [ + [ + { + "carrier_id": "amazon_shipping", + "carrier_name": "amazon_shipping", + "currency": "USD", + "extra_charges": [{"amount": 5.25, "currency": "USD", "name": "Base Rate"}], + "meta": { + "carrier_id": "AMZN", + "carrier_name": "Amazon", + "rate_id": "rate-12345", + "service_id": "AMZN_US_STD", + "service_name": "Amazon Shipping Standard", + }, + "service": "amazon_shipping_standard", + "total_charge": 5.25, + } + ], + [], +] + + +RateResponseJSON = """{ + "payload": { + "rates": [ + { + "rateId": "rate-12345", + "carrierId": "AMZN", + "carrierName": "Amazon", + "serviceId": "AMZN_US_STD", + "serviceName": "Amazon Shipping Standard", + "totalCharge": { + "value": 5.25, + "unit": "USD" + }, + "rateItemList": [ + { + "rateItemID": "BASE", + "rateItemNameLocalization": "Base Rate", + "rateItemCharge": { + "value": 5.25, + "unit": "USD" + } + } + ], + "promise": { + "deliveryWindow": { + "start": "2024-01-17T18:00:00Z", + "end": "2024-01-17T21:00:00Z" + }, + "pickupWindow": { + "start": "2024-01-15T09:00:00Z", + "end": "2024-01-15T17:00:00Z" + } + } + } + ] + } +} +""" diff --git a/modules/connectors/amazon_shipping/tests/amazon_shipping/test_shipment.py b/modules/connectors/amazon_shipping/tests/amazon_shipping/test_shipment.py new file mode 100644 index 0000000000..8f6d96b75a --- /dev/null +++ b/modules/connectors/amazon_shipping/tests/amazon_shipping/test_shipment.py @@ -0,0 +1,226 @@ +"""Amazon Shipping shipment tests.""" + +import unittest +from unittest.mock import patch + +import karrio.core.models as models +import karrio.lib as lib +import karrio.sdk as karrio + +from .fixture import gateway + + +class TestAmazonShippingShipment(unittest.TestCase): + def setUp(self): + self.maxDiff = None + self.ShipmentRequest = models.ShipmentRequest(**SHIPMENT_PAYLOAD) + self.ShipmentCancelRequest = models.ShipmentCancelRequest(**CANCEL_SHIPMENT_PAYLOAD) + + def test_create_shipment_request(self): + request = gateway.mapper.create_shipment_request(self.ShipmentRequest) + + self.assertDictEqual(request.serialize(), ShipmentRequestJSON) + + def test_create_cancel_shipment_request(self): + request = gateway.mapper.create_cancel_shipment_request(self.ShipmentCancelRequest) + + self.assertEqual(request.serialize(), CancelShipmentRequestJSON) + + def test_create_shipment(self): + with patch("karrio.mappers.amazon_shipping.proxy.lib.request") as mock: + mock.return_value = "{}" + karrio.Shipment.create(self.ShipmentRequest).from_(gateway) + + self.assertEqual( + mock.call_args[1]["url"], + f"{gateway.settings.server_url}/shipping/v2/oneClickShipment", + ) + + def test_cancel_shipment(self): + with patch("karrio.mappers.amazon_shipping.proxy.lib.request") as mock: + mock.return_value = "{}" + karrio.Shipment.cancel(self.ShipmentCancelRequest).from_(gateway) + + self.assertEqual( + mock.call_args[1]["url"], + f"{gateway.settings.server_url}/shipping/v2/shipments/{self.ShipmentCancelRequest.shipment_identifier}/cancel", + ) + + def test_parse_shipment_response(self): + with patch("karrio.mappers.amazon_shipping.proxy.lib.request") as mock: + mock.return_value = ShipmentResponseJSON + response = karrio.Shipment.create(self.ShipmentRequest).from_(gateway) + + with patch("karrio.providers.amazon_shipping.shipment.create.lib.image_to_pdf") as pdf_mock: + pdf_mock.return_value = "base64_pdf_label" + parsed_response = response.parse() + + self.assertListEqual(lib.to_dict(parsed_response), ParsedShipmentResponse) + + def test_parse_cancel_shipment_response(self): + with patch("karrio.mappers.amazon_shipping.proxy.lib.request") as mock: + mock.return_value = "{}" + parsed_response = karrio.Shipment.cancel(self.ShipmentCancelRequest).from_(gateway).parse() + + self.assertListEqual(lib.to_dict(parsed_response), ParsedCancelShipmentResponse) + + +if __name__ == "__main__": + unittest.main() + + +SHIPMENT_PAYLOAD = { + "service": "amazon_shipping_standard", + "reference": "order #1111", + "recipient": { + "company_name": "AmazonShipping", + "address_line1": "417 Montgomery Street", + "address_line2": "5th Floor", + "city": "San Francisco", + "state_code": "CA", + "postal_code": "94104", + "country_code": "US", + "phone_number": "415-528-7555", + "email": "recipient@example.com", + }, + "shipper": { + "person_name": "George Costanza", + "company_name": "Vandelay Industries", + "address_line1": "1 E 161st St.", + "city": "Bronx", + "state_code": "NY", + "postal_code": "10451", + "country_code": "US", + "email": "shipper@example.com", + }, + "parcels": [{"length": 9.0, "width": 6.0, "height": 2.0, "weight": 10.0}], +} + +CANCEL_SHIPMENT_PAYLOAD = { + "shipment_identifier": "shipment-12345", +} + +ShipmentRequestJSON = { + "shipFrom": { + "name": "Vandelay Industries", + "addressLine1": "1 E 161st St.", + "companyName": "Vandelay Industries", + "stateOrRegion": "NY", + "city": "Bronx", + "countryCode": "US", + "postalCode": "10451", + "email": "shipper@example.com", + }, + "shipTo": { + "name": "AmazonShipping", + "addressLine1": "417 Montgomery Street", + "addressLine2": "5th Floor", + "companyName": "AmazonShipping", + "stateOrRegion": "CA", + "city": "San Francisco", + "countryCode": "US", + "postalCode": "94104", + "email": "recipient@example.com", + "phoneNumber": "415-528-7555", + }, + "packages": [ + { + "dimensions": { + "length": 9.0, + "width": 6.0, + "height": 2.0, + "unit": "INCH", + }, + "weight": { + "value": 10.0, + "unit": "POUND", + }, + "insuredValue": {"value": 0.0, "unit": "USD"}, + "packageClientReferenceId": "1", + } + ], + "channelDetails": { + "channelType": "EXTERNAL", + }, + "labelSpecifications": { + "format": "PNG", + "size": { + "length": 6, + "width": 4, + "unit": "INCH", + }, + "dpi": 300, + "pageLayout": "DEFAULT", + "needFileJoining": False, + "requestedDocumentTypes": ["LABEL"], + }, + "serviceSelection": { + "serviceId": ["AMZN_US_STD"], + }, +} + +ParsedShipmentResponse = [ + { + "carrier_id": "amazon_shipping", + "carrier_name": "amazon_shipping", + "docs": {"label": "base64_pdf_label"}, + "label_type": "PDF", + "meta": { + "carrier_id": "AMZN", + "carrier_name": "Amazon", + "currency": "USD", + "service_id": "AMZN_US_STD", + "service_name": "Amazon Shipping Standard", + "shipment_id": "shipment-12345", + "total_charge": 5.25, + "tracking_numbers": ["1Z999AA10123456784"], + }, + "shipment_identifier": "shipment-12345", + "tracking_number": "1Z999AA10123456784", + }, + [], +] + +ParsedCancelShipmentResponse = [ + { + "carrier_id": "amazon_shipping", + "carrier_name": "amazon_shipping", + "operation": "Cancel Shipment", + "success": True, + }, + [], +] + + +CancelShipmentRequestJSON = "shipment-12345" + +ShipmentResponseJSON = """{ + "payload": { + "shipmentId": "shipment-12345", + "carrier": { + "id": "AMZN", + "name": "Amazon" + }, + "service": { + "id": "AMZN_US_STD", + "name": "Amazon Shipping Standard" + }, + "totalCharge": { + "value": 5.25, + "unit": "USD" + }, + "packageDocumentDetails": [ + { + "trackingId": "1Z999AA10123456784", + "packageDocuments": [ + { + "type": "LABEL", + "format": "PNG", + "contents": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + } + ] + } + ] + } +} +""" diff --git a/modules/connectors/amazon_shipping/tests/amazon_shipping/test_tracking.py b/modules/connectors/amazon_shipping/tests/amazon_shipping/test_tracking.py new file mode 100644 index 0000000000..1fe28a9d35 --- /dev/null +++ b/modules/connectors/amazon_shipping/tests/amazon_shipping/test_tracking.py @@ -0,0 +1,192 @@ +"""Amazon Shipping tracking tests.""" + +import unittest +from unittest.mock import patch + +import karrio.core.models as models +import karrio.lib as lib +import karrio.sdk as karrio + +from .fixture import gateway + + +class TestAmazonShippingTracking(unittest.TestCase): + def setUp(self): + self.maxDiff = None + self.TrackingRequest = models.TrackingRequest(**TRACKING_PAYLOAD) + + def test_create_tracking_request(self): + request = gateway.mapper.create_tracking_request(self.TrackingRequest) + + self.assertListEqual(request.serialize(), TrackingRequestJSON) + + def test_get_tracking(self): + with patch("karrio.mappers.amazon_shipping.proxy.lib.request") as mock: + mock.return_value = "{}" + karrio.Tracking.fetch(self.TrackingRequest).from_(gateway) + + self.assertEqual( + mock.call_args[1]["url"], + f"{gateway.settings.server_url}/shipping/v2/tracking", + ) + + def test_parse_tracking_response(self): + with patch("karrio.mappers.amazon_shipping.proxy.lib.request") as mock: + mock.return_value = TrackingResponseJSON + parsed_response = karrio.Tracking.fetch(self.TrackingRequest).from_(gateway).parse() + + self.assertListEqual(lib.to_dict(parsed_response), ParsedTrackingResponse) + + def test_parse_error_response(self): + with patch("karrio.mappers.amazon_shipping.proxy.lib.request") as mock: + mock.return_value = ErrorResponseJSON + parsed_response = karrio.Tracking.fetch(self.TrackingRequest).from_(gateway).parse() + + self.assertListEqual(lib.to_dict(parsed_response), ParsedErrorResponse) + + +if __name__ == "__main__": + unittest.main() + + +TRACKING_PAYLOAD = { + "tracking_numbers": ["89108749065090"], +} + +TrackingRequestJSON = [ + { + "tracking_id": "89108749065090", + "carrier_id": "AMZN_US", + } +] + +ParsedTrackingResponse = [ + [ + { + "carrier_id": "amazon_shipping", + "carrier_name": "amazon_shipping", + "delivered": True, + "estimated_delivery": "2024-01-17", + "events": [ + { + "code": "Delivered", + "date": "2024-01-17", + "description": "Delivered", + "location": "Seattle, WA, 98101, US", + "status": "delivered", + "time": "14:30 PM", + "timestamp": "2024-01-17T14:30:00.000Z", + }, + { + "code": "OutForDelivery", + "date": "2024-01-17", + "description": "OutForDelivery", + "location": "Seattle, WA, 98101, US", + "status": "out_for_delivery", + "time": "08:00 AM", + "timestamp": "2024-01-17T08:00:00.000Z", + }, + { + "code": "InTransit", + "date": "2024-01-16", + "description": "InTransit", + "location": "Portland, OR, 97201, US", + "status": "in_transit", + "time": "10:00 AM", + "timestamp": "2024-01-16T10:00:00.000Z", + }, + ], + "meta": { + "carrier_tracking_id": "89108749065090", + "received_by": "John Doe", + }, + "status": "delivered", + "tracking_number": "89108749065090", + } + ], + [], +] + +ParsedErrorResponse = [ + [], + [ + { + "carrier_id": "amazon_shipping", + "carrier_name": "amazon_shipping", + "code": "InvalidTrackingId", + "details": {"note": "Tracking ID not found", "tracking_number": "89108749065090"}, + "message": "The tracking ID provided is not valid", + } + ], +] + + +TrackingResponseJSON = """{ + "payload": { + "trackingId": "89108749065090", + "alternateLegTrackingId": null, + "eventHistory": [ + { + "eventCode": "Delivered", + "location": { + "city": "Seattle", + "stateOrRegion": "WA", + "countryCode": "US", + "postalCode": "98101" + }, + "eventTime": "2024-01-17T14:30:00Z", + "shipmentType": "FORWARD" + }, + { + "eventCode": "OutForDelivery", + "location": { + "city": "Seattle", + "stateOrRegion": "WA", + "countryCode": "US", + "postalCode": "98101" + }, + "eventTime": "2024-01-17T08:00:00Z", + "shipmentType": "FORWARD" + }, + { + "eventCode": "InTransit", + "location": { + "city": "Portland", + "stateOrRegion": "OR", + "countryCode": "US", + "postalCode": "97201" + }, + "eventTime": "2024-01-16T10:00:00Z", + "shipmentType": "FORWARD" + } + ], + "promisedDeliveryDate": "2024-01-17T20:00:00Z", + "summary": { + "status": "Delivered", + "trackingDetailCodes": { + "forward": ["Signed"], + "returns": [] + }, + "proofOfDelivery": { + "deliveryLocationCoordinates": { + "latitude": 47.6062, + "longitude": -122.3321 + }, + "deliveryImageURL": null, + "receivedBy": "John Doe" + } + } + } +} +""" + +ErrorResponseJSON = """{ + "errors": [ + { + "code": "InvalidTrackingId", + "message": "The tracking ID provided is not valid", + "details": "Tracking ID not found" + } + ] +} +""" diff --git a/modules/connectors/amazon_shipping/vendor/openapi.yaml b/modules/connectors/amazon_shipping/vendor/openapi.yaml new file mode 100644 index 0000000000..7cda552700 --- /dev/null +++ b/modules/connectors/amazon_shipping/vendor/openapi.yaml @@ -0,0 +1,5210 @@ +openapi: 3.0.1 +info: + title: Amazon Shipping APIs + description: "The Amazon Shipping API is designed to support outbound shipping use cases both for orders originating on Amazon-owned marketplaces as well as external channels/marketplaces. With these APIs, you can request shipping rates, create shipments, cancel shipments, and track shipments." + version: v2 + contact: + name: API Integrations Support + url: https://developer-docs.amazon.com/amazon-shipping/docs/api-integrations-support +externalDocs: + description: Find out more about Amazon Shipping + url: https://developer-docs.amazon.com/amazon-shipping +servers: + - url: https://sandbox.sellingpartnerapi-na.amazon.com + - url: https://sandbox.sellingpartnerapi-eu.amazon.com + - url: https://sellingpartnerapi-na.amazon.com + - url: https://sellingpartnerapi-eu.amazon.com +tags: + - name: Shipping Rate API + - name: Label Purchase Via Purchase Shipment API + - name: Label Purchase Via Smart Purchase API + - name: Shipment Tracking API + - name: Regenerate Shipping Label API + - name: Cancel Shipment Label API + - name: Obtain Delivery Counter Location API + - name: Submit Non-Delivery Report API + - name: Provide Additional Input API +paths: + /shipping/v2/shipments/rates: + post: + tags: + - Shipping Rate API + description: |- + Returns the available shipping service offerings. + + **Usage Plan:** + + | Default Quota | Unit | + | ---- | ---- | + | 5 | Requests Per Second | + + The `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api). + operationId: getRates + parameters: + - name: x-amz-access-token + in: header + description: Amazon shipping request token + required: true + schema: + type: string + - name: x-amzn-shipping-business-id + in: header + description: + Amazon shipping business to assume for this request. The default + is AmazonShipping_UK. + schema: + type: string + enum: + - AmazonShipping_US + - AmazonShipping_IN + - AmazonShipping_UK + - AmazonShipping_IT + - AmazonShipping_ES + - AmazonShipping_FR + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/GetRatesRequest" + required: true + responses: + "200": + description: Success. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/GetRatesResponse" + example: + requestToken: 6DCCEDD3FF961C15FEB94F342D41C + rates: + - rateId: F4B68849F969E239FF9FCA9C12E35 + carrierId: FOOSHIPGRD + carrierName: FOO SHIP GRD + billedWeight: + value: 5 + unit: GRAMS + totalCharge: + value: 7 + unit: USD + totalChargeWithAdjustments: + value: 6 + unit: USD + serviceId: FOORSID + serviceName: FOO RS ID + promise: + deliveryWindow: + start: 2018-08-24T08:22:30.737Z + end: 2018-08-24T20:22:30.737Z + pickupWindow: + start: 2018-08-23T08:22:30.737Z + end: 2018-08-23T20:22:30.737Z + supportedDocumentSpecifications: + - format: PNG + size: + length: 6 + width: 4 + unit: INCH + printOptions: + - supportedDPIs: + - 300 + - 203 + supportedPageLayouts: + - LEFT + - RIGHT + supportedFileJoiningOptions: + - true + - false + supportedDocumentDetails: + - name: LABEL + isMandatory: true + - format: ZPL + size: + length: 6 + width: 4 + unit: INCH + printOptions: + - supportedDPIs: + - 300 + - 203 + supportedPageLayouts: + - LEFT + - RIGHT + supportedFileJoiningOptions: + - true + - false + supportedDocumentDetails: + - name: LABEL + isMandatory: true + availableValueAddedServiceGroups: + - groupId: SIG_VERIFICATION + groupDescription: Signature Verification + isRequired: true + valueAddedServices: + - id: CUST_SIG_VERIFICATION + name: Customer Signature Verification + cost: + unit: USD + value: 2 + requiresAdditionalInputs: false + rateItemList: + - rateItemCharge: + unit: INR + value: 2.00 + rateItemID: BASE_RATE + rateItemNameLocalization: Base Rate + rateItemType: MANDATORY + - rateItemCharge: + unit: INR + value: 1.20 + rateItemID: DELIVERY_CONFIRMATION + rateItemNameLocalization: Delivery Confirmation + rateItemType: INCLUDED + - rateItemCharge: + unit: INR + value: 2.00 + rateItemID: VAT + rateItemNameLocalization: VAT + rateItemType: MANDATORY + adjustmentItemList: + - rateItemCharge: + unit: INR + value: 1.00 + rateItemID: FBM_SHIP_PLUS_CREDIT + rateItemType: INCLUDED + rateItemNameLocalization: FBM Ship Plus Credit + paymentType: PAY_THROUGH_AMAZON + benefits: + includedBenefits: + - FBM_SHIP_PLUS + excludedBenefits: + - benefit: CLAIMS_PROTECTED + reasonCodes: + - LATE_DELIVERY_RISK + ineligibleRates: + - carrierId: FOOSTDGRD + serviceId: FOO8420430 + carrierName: FOOSTDGRD + serviceName: FOO8420430 + ineligibilityReasons: + - code: NO_COVERAGE + message: + Required shipping network coverage doesn't exist for + the offering + "400": + description: Request has missing or invalid parameters and cannot be parsed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: Request is missing or has invalid parameters + details: ChannelDetails object cannot be null + "401": + description: + The request's Authorization header is not formatted correctly + or does not contain a valid token. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "403": + description: "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature." + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "404": + description: The resource specified does not exist. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: NotFound + message: The requested resource is invalid or doesn't exist + "413": + description: The request size exceeded the maximum accepted size. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: + The request payload size is greater than maximum accepted + size. + "415": + description: The request payload is in an unsupported format. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: The request payload format is not supported. + "429": + description: The frequency of requests was greater than allowed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: TooManyRequests + message: The total number of requests exceeded your allowed limit. + "500": + description: + An unexpected condition occurred that prevented the server + from fulfilling the request. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InternalError + message: Something went wrong while processing the request. + "503": + description: Temporary overloading or maintenance of the server. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: ServiceUnavailable + message: + Service temporarily unavailable or down for maintenance. + Please try again after sometime. + x-amzn-api-sandbox: + dynamic: {} + x-codegen-request-body-name: body + /shipping/v2/shipments: + post: + tags: + - Label Purchase Via Purchase Shipment API + description: |- + Purchases a shipping service and returns purchase related details and documents. + + Note: You must complete the purchase within **10 minutes** of rate creation by the shipping service provider. If you make the request after the 10 minutes have expired, you will receive an error response with the error code equal to "TOKEN_EXPIRED". If you receive this error response, you must get the rates for the shipment again. + + **Usage Plan:** + + | Default Quota | Unit | + | ---- | ---- | + | 5 | Requests Per Second | + + The `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api). + operationId: purchaseShipment + parameters: + - name: x-amz-access-token + in: header + description: Amazon shipping request token + required: true + schema: + type: string + - name: x-amzn-shipping-business-id + in: header + description: + Amazon shipping business to assume for this request. The default + is AmazonShipping_UK. + schema: + type: string + enum: + - AmazonShipping_US + - AmazonShipping_IN + - AmazonShipping_UK + - AmazonShipping_IT + - AmazonShipping_ES + - AmazonShipping_FR + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/PurchaseShipmentRequest" + required: true + responses: + "200": + description: Success. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/PurchaseShipmentResponse" + example: + shipmentId: "87852211788104" + packageDocumentDetails: + - packageClientReferenceId: abcd + packageDocuments: + - type: LABEL + format: PNG + contents: sdioadaiosfhdodsaiufhouafhoudfhdouahfac== + trackingId: 1578648261977 + promise: + pickupWindow: + start: 2019-12-11T07:09:05.513Z + end: 2019-12-11T09:09:05.513Z + deliveryWindow: + start: 2019-12-13T07:09:05.513Z + end: 2019-12-13T09:09:05.513Z + benefits: + includedBenefits: + - CLAIMS_PROTECTED + excludedBenefits: [] + totalChargeWithAdjustments: + value: 6 + unit: USD + "400": + description: Request has missing or invalid parameters and cannot be parsed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: Request is missing or has invalid parameters + details: RateId cannot be null + "401": + description: + The request's Authorization header is not formatted correctly + or does not contain a valid token. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "403": + description: "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature." + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "404": + description: The resource specified does not exist. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: NotFound + message: The requested resource is invalid or doesn't exist + "413": + description: The request size exceeded the maximum accepted size. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: + The request payload size is greater than maximum accepted + size. + "415": + description: The request payload is in an unsupported format. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: The request payload format is not supported. + "429": + description: The frequency of requests was greater than allowed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: TooManyRequests + message: The total number of requests exceeded your allowed limit. + "500": + description: + An unexpected condition occurred that prevented the server + from fulfilling the request. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InternalError + message: Something went wrong while processing the request. + "503": + description: Temporary overloading or maintenance of the server. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: ServiceUnavailable + message: + Service temporarily unavailable or down for maintenance. + Please try again after sometime. + x-amzn-api-sandbox: + dynamic: {} + x-codegen-request-body-name: body + /shipping/v2/oneClickShipment: + post: + tags: + - Label Purchase Via Smart Purchase API + description: |- + Purchases a shipping service identifier and returns purchase-related details and documents. + + **Usage Plan:** + + | Default Quota | Unit | + | ---- | ---- | + | 5 | Requests Per Second | + + The `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api). + operationId: oneClickShipment + parameters: + - name: x-amz-access-token + in: header + description: Amazon shipping request token + required: true + schema: + type: string + - name: x-amzn-shipping-business-id + in: header + description: + Amazon shipping business to assume for this request. The default + is AmazonShipping_UK. + schema: + type: string + enum: + - AmazonShipping_US + - AmazonShipping_IN + - AmazonShipping_UK + - AmazonShipping_IT + - AmazonShipping_ES + - AmazonShipping_FR + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/OneClickShipmentRequest" + required: true + responses: + "200": + description: Success. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/OneClickShipmentResponse" + example: + shipmentId: "87852211788104" + packageDocumentDetails: + - packageClientReferenceId: "#abcd" + packageDocuments: + - type: LABEL + format: PNG + contents: sdioadaiosfhdodsaiufhouafhoudfhdouahfac== + trackingId: 1578648261977 + promise: + pickupWindow: + start: 2019-12-11T07:09:05.513Z + end: 2019-12-11T09:09:05.513Z + deliveryWindow: + start: 2019-12-13T07:09:05.513Z + end: 2019-12-13T09:09:05.513Z + carrier: + id: AMZN_UK + name: Amazon Shipping + service: + id: SWA-UK-PREM + name: Amazon Shipping One Day + totalCharge: + unit: GBP + value: 4.76 + rateItems: + - rateItemCharge: + unit: GBP + value: 3.90 + rateItemID: BASE_RATE + rateItemNameLocalization: Base Rate + rateItemType: MANDATORY + - rateItemCharge: + unit: GBP + value: 0.86 + rateItemID: VAT + rateItemNameLocalization: VAT + rateItemType: MANDATORY + "400": + description: Request has missing or invalid parameters and cannot be parsed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: Request is missing or has invalid parameters + details: ServiceId cannot be null + "401": + description: + The request's Authorization header is not formatted correctly + or does not contain a valid token. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "403": + description: "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature." + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "404": + description: The resource specified does not exist. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: NotFound + message: The requested resource is invalid or doesn't exist + "413": + description: The request size exceeded the maximum accepted size. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: + The request payload size is greater than maximum accepted + size. + "415": + description: The request payload is in an unsupported format. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: The request payload format is not supported. + "429": + description: The frequency of requests was greater than allowed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: TooManyRequests + message: The total number of requests exceeded your allowed limit. + "500": + description: + An unexpected condition occurred that prevented the server + from fulfilling the request. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InternalError + message: Something went wrong while processing the request. + "503": + description: Temporary overloading or maintenance of the server. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: ServiceUnavailable + message: + Service temporarily unavailable or down for maintenance. + Please try again after sometime. + x-amzn-api-sandbox: + dynamic: {} + x-codegen-request-body-name: body + /shipping/v2/tracking: + get: + tags: + - Shipment Tracking API + description: |- + Returns tracking information for a purchased shipment. + + **Usage Plan:** + + | Default Quota | Unit | + | ---- | ---- | + | 5 | Requests Per Second | + + The `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api). + operationId: getTracking + parameters: + - name: trackingId + in: query + description: + A carrier-generated tracking identifier originally returned by + the purchaseShipment operation. + required: true + schema: + type: string + - name: carrierId + in: query + description: + A carrier identifier originally returned by the getRates operation + for the selected rate. + required: true + schema: + type: string + - name: x-amz-access-token + in: header + description: Amazon shipping request token + required: true + schema: + type: string + - name: x-amzn-shipping-business-id + in: header + description: + Amazon shipping business to assume for this request. The default + is AmazonShipping_UK. + schema: + type: string + enum: + - AmazonShipping_US + - AmazonShipping_IN + - AmazonShipping_UK + - AmazonShipping_IT + - AmazonShipping_ES + - AmazonShipping_FR + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + responses: + "200": + description: Success. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/GetTrackingResponse" + example: + trackingId: 23AA47DE2B3B6 + alternateLegTrackingId: "null" + eventHistory: + - eventCode: Undeliverable + location: + postalCode: EX33 2JL + countryCode: GB + eventTime: 2024-02-11T07:09:05.513Z + shipmentType: FORWARD + - eventCode: ReturnInitiated + location: + postalCode: EX33 2JL + countryCode: GB + eventTime: 2024-02-12T09:09:05.513Z + shipmentType: RETURNS + - eventCode: Delivered + location: + postalCode: EX33 2JL + countryCode: GB + eventTime: 2024-02-13T04:09:05.513Z + shipmentType: RETURNS + promisedDeliveryDate: 2024-02-12T13:09:05.513Z + summary: + status: Delivered + trackingDetailCodes: + forward: + - Rejected + - Damaged + returns: + - Signed + proofOfDelivery: + deliveryLocationCoordinates: + latitude: 51.5074 + longitude: -0.1278 + deliveryImageURL: https://amzn-s3-demo-bucket.xxx.com/key?X-Amz-Algorithm=XXXX&X-Amz-Credential=XXXX&X-Amz-Date=XXX&X-Amz-Expires=XXX&X-Amz-SignedHeaders=host&X-Amz-Signature=XXX + receivedBy: "John Doe" + "400": + description: Request has missing or invalid parameters and cannot be parsed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: Request is missing or has invalid parameters + details: CarrierId is missing in the request + "401": + description: + The request's Authorization header is not formatted correctly + or does not contain a valid token. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "403": + description: "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature." + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "404": + description: The resource specified does not exist. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: NotFound + message: The requested resource is invalid or doesn't exist + "413": + description: The request size exceeded the maximum accepted size. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: + The request payload size is greater than maximum accepted + size. + "415": + description: The request payload is in an unsupported format. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: The request payload format is not supported. + "429": + description: The frequency of requests was greater than allowed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: TooManyRequests + message: The total number of requests exceeded your allowed limit. + "500": + description: + An unexpected condition occurred that prevented the server + from fulfilling the request. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InternalError + message: Something went wrong while processing the request. + "503": + description: Temporary overloading or maintenance of the server. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: ServiceUnavailable + message: + Service temporarily unavailable or down for maintenance. + Please try again after sometime. + x-amzn-api-sandbox: + dynamic: {} + /shipping/v2/shipments/{shipmentId}/documents: + get: + tags: + - Regenerate Shipping Label API + description: |- + Returns the shipping documents associated with a package in a shipment. + + **Usage Plan:** + + | Default Quota | Unit | + | ---- | ---- | + | 5 | Requests Per Second | + + The `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api). + operationId: getShipmentDocuments + parameters: + - name: shipmentId + in: path + description: + The shipment identifier originally returned by the purchaseShipment + operation. + required: true + schema: + type: string + - name: packageClientReferenceId + in: query + description: + The package client reference identifier originally provided in + the request body parameter for the getRates operation. + required: true + schema: + type: string + - name: format + in: query + description: + The file format of the document. Must be one of the supported + formats returned by the getRates operation. + schema: + type: string + - name: dpi + in: query + description: + "The resolution of the document (for example, 300 means 300 dots\ + \ per inch). Must be one of the supported resolutions returned in the response\ + \ to the getRates operation." + schema: + type: number + - name: x-amz-access-token + in: header + description: Amazon shipping request token + required: true + schema: + type: string + - name: x-amzn-shipping-business-id + in: header + description: + Amazon shipping business to assume for this request. The default + is AmazonShipping_UK. + schema: + type: string + enum: + - AmazonShipping_US + - AmazonShipping_IN + - AmazonShipping_UK + - AmazonShipping_IT + - AmazonShipping_ES + - AmazonShipping_FR + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + responses: + "200": + description: Success. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/GetShipmentDocumentsResponse" + example: + shipmentId: 445454-3232-3232 + packageDocumentDetail: + packageClientReferenceId: ASUSDI-45343854 + trackingId: T1234567 + packageDocuments: + - type: LABEL + format: PNG + contents: sdioadaiosfhdodsaiufhouafhoudfhdouahfac== + benefits: + includedBenefits: + - CLAIMS_PROTECTED + excludedBenefits: [] + "400": + description: Request has missing or invalid parameters and cannot be parsed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: Request is missing or has invalid parameters + details: shipmentId is missing in the request + "401": + description: + The request's Authorization header is not formatted correctly + or does not contain a valid token. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "403": + description: "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature." + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "404": + description: The resource specified does not exist. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: NotFound + message: The requested resource is invalid or doesn't exist + "413": + description: The request size exceeded the maximum accepted size. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: + The request payload size is greater than maximum accepted + size. + "415": + description: The request payload is in an unsupported format. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: The request payload format is not supported. + "429": + description: The frequency of requests was greater than allowed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: TooManyRequests + message: The total number of requests exceeded your allowed limit. + "500": + description: + An unexpected condition occurred that prevented the server + from fulfilling the request. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InternalError + message: Something went wrong while processing the request. + "503": + description: Temporary overloading or maintenance of the server. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: ServiceUnavailable + message: + Service temporarily unavailable or down for maintenance. + Please try again after sometime. + x-amzn-api-sandbox: + dynamic: {} + /shipping/v2/shipments/{shipmentId}/cancel: + put: + tags: + - Cancel Shipment Label API + description: |- + Cancels a purchased shipment. Returns an empty object if the shipment is successfully cancelled. + + **Usage Plan:** + + | Default Quota | Unit | + | ---- | ---- | + | 5 | Requests Per Second | + + The `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api). + operationId: cancelShipment + parameters: + - name: shipmentId + in: path + description: + The shipment identifier originally returned by the purchaseShipment + operation. + required: true + schema: + type: string + - name: x-amz-access-token + in: header + description: Amazon shipping request token + required: true + schema: + type: string + - name: x-amzn-shipping-business-id + in: header + description: + Amazon shipping business to assume for this request. The default + is AmazonShipping_UK. + schema: + type: string + enum: + - AmazonShipping_US + - AmazonShipping_IN + - AmazonShipping_UK + - AmazonShipping_IT + - AmazonShipping_ES + - AmazonShipping_FR + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + responses: + "200": + description: Success. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/CancelShipmentResponse" + example: {} + "400": + description: Request has missing or invalid parameters and cannot be parsed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: Request is missing or has invalid parameters + details: ShipmentId cannot be null + "401": + description: + The request's Authorization header is not formatted correctly + or does not contain a valid token. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "403": + description: "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature." + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "404": + description: The resource specified does not exist. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: NotFound + message: The requested resource is invalid or doesn't exist + "413": + description: The request size exceeded the maximum accepted size. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: + The request payload size is greater than maximum accepted + size. + "415": + description: The request payload is in an unsupported format. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: The request payload format is not supported. + "429": + description: The frequency of requests was greater than allowed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: TooManyRequests + message: The total number of requests exceeded your allowed limit. + "500": + description: + An unexpected condition occurred that prevented the server + from fulfilling the request. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InternalError + message: Something went wrong while processing the request. + "503": + description: Temporary overloading or maintenance of the server. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: ServiceUnavailable + message: + Service temporarily unavailable or down for maintenance. + Please try again after sometime. + x-amzn-api-sandbox: + dynamic: {} + /shipping/v2/accessPoints: + get: + tags: + - Obtain Delivery Counter Location API + description: |- + Returns a list of access points in proximity of input postal code. + + **Usage Plan:** + + | Default Quota | Unit | + | ---- | ---- | + | 5 | Requests Per Second | + + The `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api). + operationId: getAccessPoints + parameters: + - name: accessPointTypes + in: query + description: Access point types, only available with "HELIX" option for now. + required: true + style: form + explode: false + schema: + type: array + items: + type: string + enum: + - HELIX + - CAMPUS_LOCKER + - OMNI_LOCKER + - ODIN_LOCKER + - DOBBY_LOCKER + - CORE_LOCKER + - 3P + - CAMPUS_ROOM + x-docgen-enum-table-extension: + - value: HELIX + description: + "Counter, aka Helix, is an assisted access point where customers\ + \ interact with a store associate to collect or return packages. Counter\ + \ stores do not have Locker" + - value: CAMPUS_LOCKER + description: It denotes a physical Locker present at Locker+ stores + - value: OMNI_LOCKER + description: + Omni lockers are similar to core lockers installed at apartment + buildings. Core and Omni will have common base hardware platform + - value: ODIN_LOCKER + description: + "Odin lockers rely on Bluetooth connection as there is no\ + \ screen or scanner on the locker. Drivers, customers, and even technicians\ + \ can interact with the locker using an app (different depending on\ + \ the user)." + - value: DOBBY_LOCKER + description: + Dobby lockers are Odin lockers installed at apartment complexes + and available only to residents. + - value: CORE_LOCKER + description: + "Core lockers are the traditional and initial style of lockers\ + \ when Hub was launched. Core lockers have a screen, keypad and scanner.\ + \ Customer either enter their pickup code using the Locker screen or\ + \ scan pickup code barcode to pick up their package." + - value: 3P + description: + "Networks that are owned end-to-end by commercial carriers\ + \ (CC - e.g. DHL, UPS, DPD and Postal organizations), who operate both\ + \ the transportation leg and the access point aspect. These networks\ + \ offer a range of pickup points including: 3P Lockers, legacy Postal\ + \ Offices, 3P Counters (e.g. Gas Stations and Retail Outlets - supermarkets,\ + \ bookstores, news-stands etc)." + - value: CAMPUS_ROOM + description: + "Locker+, formerly known as Campus, are attended, physical\ + \ stores, where the Locker is used dynamically by the staff to keep\ + \ packages ready for customer pickup. Locker+ store can have multiple\ + \ Lockers installed," + x-docgen-enum-table-extension: + - value: HELIX + description: + "Counter, aka Helix, is an assisted access point where customers\ + \ interact with a store associate to collect or return packages. Counter\ + \ stores do not have Locker" + - value: CAMPUS_LOCKER + description: It denotes a physical Locker present at Locker+ stores + - value: OMNI_LOCKER + description: + Omni lockers are similar to core lockers installed at apartment + buildings. Core and Omni will have common base hardware platform + - value: ODIN_LOCKER + description: + "Odin lockers rely on Bluetooth connection as there is no screen\ + \ or scanner on the locker. Drivers, customers, and even technicians can\ + \ interact with the locker using an app (different depending on the user)." + - value: DOBBY_LOCKER + description: + Dobby lockers are Odin lockers installed at apartment complexes + and available only to residents. + - value: CORE_LOCKER + description: + "Core lockers are the traditional and initial style of lockers\ + \ when Hub was launched. Core lockers have a screen, keypad and scanner.\ + \ Customer either enter their pickup code using the Locker screen or\ + \ scan pickup code barcode to pick up their package." + - value: 3P + description: + "Networks that are owned end-to-end by commercial carriers\ + \ (CC - e.g. DHL, UPS, DPD and Postal organizations), who operate both\ + \ the transportation leg and the access point aspect. These networks offer\ + \ a range of pickup points including: 3P Lockers, legacy Postal Offices,\ + \ 3P Counters (e.g. Gas Stations and Retail Outlets - supermarkets, bookstores,\ + \ news-stands etc)." + - value: CAMPUS_ROOM + description: + "Locker+, formerly known as Campus, are attended, physical\ + \ stores, where the Locker is used dynamically by the staff to keep packages\ + \ ready for customer pickup. Locker+ store can have multiple Lockers installed," + - name: countryCode + in: query + description: Country code for access point + required: true + schema: + type: string + example: UK + - name: postalCode + in: query + description: postal code for access point + required: true + schema: + type: string + example: EX332JL + - name: x-amz-access-token + in: header + description: Amazon shipping request token + required: true + schema: + type: string + - name: x-amzn-shipping-business-id + in: header + description: Amazon shipping business to assume for this request. Only available for the AmazonShipping_UK. + schema: + type: string + enum: + - AmazonShipping_UK + x-docgen-enum-table-extension: + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + x-docgen-enum-table-extension: + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + responses: + "200": + description: Success. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/GetAccessPointsResponse" + example: + HELIX: + accessPointId: 00046c4b-bb0a-4c03-831e-5633e142f16a + name: Amazon Hub Counter - Co-op Braunton + timezone: Europe/London + type: HELIX + accessibilityAttributes: + distance: "21.200000762939453" + driveTime: "0.0" + address: + addressObject: + addressLine1: Exeter Road + city: Braunton + countryCode: GB + zipOrPostalCode: EX33 2JL + geocode: + latitude: "51.1076671" + longitude: "-4.1607301" + exceptionOperatingHours: + - dateRange: + endDate: 1.6932672E9 + startDate: 1.6931808E9 + operatingHours: + closingTime: + hourOfDay: 0 + minuteOfHour: 0 + secondOfMinute: 0 + midDayClosures: [] + openingTime: + hourOfDay: 0 + minuteOfHour: 0 + secondOfMinute: 0 + assistanceType: STAFF_ASSISTED + score: "0.6688068509101868" + standardOperatingHours: + WEDNESDAY: + closingTime: + hourOfDay: 22 + minuteOfHour: 0 + secondOfMinute: 0 + midDayClosures: [] + openingTime: + hourOfDay: 7 + minuteOfHour: 0 + secondOfMinute: 0 + MONDAY: + closingTime: + hourOfDay: 22 + minuteOfHour: 0 + secondOfMinute: 0 + midDayClosures: [] + openingTime: + hourOfDay: 7 + minuteOfHour: 0 + secondOfMinute: 0 + THURSDAY: + closingTime: + hourOfDay: 22 + minuteOfHour: 0 + secondOfMinute: 0 + midDayClosures: [] + openingTime: + hourOfDay: 7 + minuteOfHour: 0 + secondOfMinute: 0 + SUNDAY: + closingTime: + hourOfDay: 22 + minuteOfHour: 0 + secondOfMinute: 0 + midDayClosures: [] + openingTime: + hourOfDay: 7 + minuteOfHour: 0 + secondOfMinute: 0 + TUESDAY: + closingTime: + hourOfDay: 22 + minuteOfHour: 0 + secondOfMinute: 0 + midDayClosures: [] + openingTime: + hourOfDay: 7 + minuteOfHour: 0 + secondOfMinute: 0 + FRIDAY: + closingTime: + hourOfDay: 22 + minuteOfHour: 0 + secondOfMinute: 0 + midDayClosures: [] + openingTime: + hourOfDay: 7 + minuteOfHour: 0 + secondOfMinute: 0 + SATURDAY: + closingTime: + hourOfDay: 22 + minuteOfHour: 0 + secondOfMinute: 0 + midDayClosures: [] + openingTime: + hourOfDay: 7 + minuteOfHour: 0 + secondOfMinute: 0 + "400": + description: Request has missing or invalid parameters and cannot be parsed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: Request is missing or has invalid parameters + details: Access Point types cannot be null + "401": + description: + The request's Authorization header is not formatted correctly + or does not contain a valid token. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "403": + description: "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature." + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "404": + description: The resource specified does not exist. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: NotFound + message: The requested resource is invalid or doesn't exist + "413": + description: The request size exceeded the maximum accepted size. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: + The request payload size is greater than maximum accepted + size. + "415": + description: The request payload is in an unsupported format. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: The request payload format is not supported. + "429": + description: The frequency of requests was greater than allowed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: TooManyRequests + message: The total number of requests exceeded your allowed limit. + "500": + description: + An unexpected condition occurred that prevented the server + from fulfilling the request. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InternalError + message: Something went wrong while processing the request. + "503": + description: Temporary overloading or maintenance of the server. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: ServiceUnavailable + message: + Service temporarily unavailable or down for maintenance. + Please try again after sometime. + x-amzn-api-sandbox: + dynamic: {} + /shipping/v2/ndrFeedback: + post: + tags: + - Submit Non-Delivery Report API + description: |- + This API submits the NDR (Non-delivery Report) Feedback for any eligible shipment. + + **Usage Plan:** + + | Default Quota | Unit | + | ---- | ---- | + | 5 | Requests Per Second | + + The `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](doc:usage-plans-and-rate-limits-in-the-sp-api). + operationId: submitNdrFeedback + parameters: + - name: x-amz-access-token + in: header + description: Amazon shipping request token + required: true + schema: + type: string + - name: x-amzn-shipping-business-id + in: header + description: Amazon shipping business to assume for this request. Available for only AmazonShipping_IN. + schema: + type: string + enum: + - AmazonShipping_IN + x-docgen-enum-table-extension: + - value: AmazonShipping_IN + description: The India Amazon shipping business. + x-docgen-enum-table-extension: + - value: AmazonShipping_IN + description: The India Amazon shipping business. + requestBody: + description: Request body for ndrFeedback operation + content: + application/json: + schema: + $ref: "#/components/schemas/SubmitNdrFeedbackRequest" + required: true + responses: + "204": + description: Success + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: {} + "400": + description: Request has missing or invalid parameters and cannot be parsed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: Request is missing or has invalid parameters + details: RescheduleDate is missing in the request + "401": + description: + The request's Authorization header is not formatted correctly + or does not contain a valid token. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested resource or the + credentials are invalid. + "403": + description: "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature." + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested resource or the + credentials are invalid. + "404": + description: The resource specified does not exist. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: NotFound + message: The requested resource is invalid or doesn't exist + "413": + description: The request size exceeded the maximum accepted size. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: + The request payload size is greater than maximum accepted + size. + "415": + description: The request payload is in an unsupported format. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: The request payload format is not supported. + "429": + description: The frequency of requests was greater than allowed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: TooManyRequests + message: The total number of requests exceeded your allowed limit. + "500": + description: + An unexpected condition occurred that prevented the server + from fulfilling the request. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InternalError + message: Something went wrong while processing the request. + "503": + description: Temporary overloading or maintenance of the server. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: ServiceUnavailable + message: + Service temporarily unavailable or down for maintenance. + Please try again after sometime. + x-amzn-api-sandbox: + dynamic: {} + x-codegen-request-body-name: body + /shipping/v2/shipments/additionalInputs/schema: + get: + tags: + - Provide Additional Input API + description: |- + Returns the JSON schema to use for providing additional inputs when needed to purchase a shipping offering. Call the getAdditionalInputs operation when the response to a previous call to the getRates operation indicates that additional inputs are required for the rate (shipping offering) that you want to purchase. + + **Usage Plan:** + + | Default Quota | Unit | + | ---- | ---- | + | 5 | Requests Per Second | + + The `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api). + operationId: getAdditionalInputs + parameters: + - name: requestToken + in: query + description: The request token returned in the response to the getRates operation. + required: true + schema: + type: string + - name: rateId + in: query + description: + The rate identifier for the shipping offering (rate) returned + in the response to the getRates operation. + required: true + schema: + type: string + - name: x-amz-access-token + in: header + description: Amazon shipping request token + required: true + schema: + type: string + - name: x-amzn-shipping-business-id + in: header + description: + Amazon shipping business to assume for this request. The default + is AmazonShipping_UK. + schema: + type: string + enum: + - AmazonShipping_US + - AmazonShipping_IN + - AmazonShipping_UK + - AmazonShipping_IT + - AmazonShipping_ES + - AmazonShipping_FR + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + x-docgen-enum-table-extension: + - value: AmazonShipping_US + description: The United States Amazon shipping business. + - value: AmazonShipping_IN + description: The India Amazon shipping business. + - value: AmazonShipping_UK + description: The United Kingdom Amazon shipping business. + - value: AmazonShipping_IT + description: The Italy Amazon shipping business. + - value: AmazonShipping_ES + description: The Spain Amazon shipping business. + - value: AmazonShipping_FR + description: The France Amazon shipping business. + responses: + "200": + description: Success. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/GetAdditionalInputsResponse" + example: + payload: + properties: + harmonizedSystemCode: + type: string + description: Harmonized System's commodity code for an item. + packageClientReferenceId: + type: string + description: Unique identifier for the item. + x-amzn-api-sandbox: + static: + - request: + parameters: + requestToken: + value: amzn1.rq.123456789.101 + rateId: + value: "122324234543535321345436534321423423523452345" + response: + payload: {} + "400": + description: Request has missing or invalid parameters and cannot be parsed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: Request is missing or has invalid parameters + details: RequestToken cannot be null + x-amzn-api-sandbox: + static: + - request: + parameters: + requestToken: + value: "null" + rateId: + value: "2314346237423894905834905890346890789075" + response: + errors: + - code: InvalidRequest + message: Request is missing or has invalid parameters + details: RequestToken cannot be null + "401": + description: + The request's Authorization header is not formatted correctly + or does not contain a valid token. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "403": + description: "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature." + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: Unauthorized + message: + You don't have access to the requested response or the + credentials are invalid. + "404": + description: The resource specified does not exist. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: Your rate limit (requests per second) for this operation. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: NotFound + message: The requested resource is invalid or doesn't exist + "413": + description: The request size exceeded the maximum accepted size. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: + The request payload size is greater than maximum accepted + size. + "415": + description: The request payload is in an unsupported format. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InvalidRequest + message: The request payload format is not supported. + "429": + description: The frequency of requests was greater than allowed. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: TooManyRequests + message: The total number of requests exceeded your allowed limit. + "500": + description: + An unexpected condition occurred that prevented the server + from fulfilling the request. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: InternalError + message: Something went wrong while processing the request. + "503": + description: Temporary overloading or maintenance of the server. + headers: + x-amzn-RequestId: + description: Unique request reference identifier. + schema: + type: string + x-amzn-RateLimit-Limit: + description: |- + Your rate limit (requests per second) for this operation. + _Note:_ For this status code, the rate limit header is deprecated and no longer returned. + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorList" + example: + errors: + - code: ServiceUnavailable + message: + Service temporarily unavailable or down for maintenance. + Please try again after sometime. +components: + schemas: + Error: + required: + - code + - message + type: object + properties: + code: + type: string + description: An error code that identifies the type of error that occurred. + message: + type: string + description: A message that describes the error condition. + details: + type: string + description: + Additional details that can help the caller understand or fix + the issue. + description: Error response returned when the request is unsuccessful. + ErrorList: + required: + - errors + type: object + properties: + errors: + type: array + description: Array of Errors + items: + $ref: "#/components/schemas/Error" + description: A list of error responses returned when a request is unsuccessful. + Weight: + required: + - unit + - value + type: object + properties: + unit: + type: string + description: The unit of measurement. + enum: + - GRAM + - KILOGRAM + - OUNCE + - POUND + x-docgen-enum-table-extension: + - value: GRAM + description: Metric unit of mass equal to one thousandth of a kilogram. + - value: KILOGRAM + description: Metric unit of mass. + - value: OUNCE + description: The imperial unit of weight that is one sixteenth of a pound. + - value: POUND + description: The imperial unit of weight. + value: + type: number + description: The measurement value. + description: The weight in the units indicated. + LiquidVolume: + required: + - unit + - value + type: object + properties: + unit: + type: string + description: The unit of measurement. + enum: + - ML + - L + - FL_OZ + - GAL + - PT + - QT + - C + x-docgen-enum-table-extension: + - value: ML + description: Milliliter - Metric unit of volume. + - value: L + description: Liter - Metric unit of volume. + - value: FL_OZ + description: Fluid Ounce - Imperial unit of volume. + - value: GAL + description: Gallon - Imperial unit of volume. + - value: PT + description: Pint - Imperial unit of volume. + - value: QT + description: Quart - Imperial unit of volume. + - value: C + description: Cup - Imperial unit of volume. + value: + type: number + description: The measurement value. + description: Liquid Volume. + InvoiceDetails: + type: object + properties: + invoiceNumber: + type: string + description: The invoice number of the item. + invoiceDate: + type: string + description: The invoice date of the item in ISO 8061 format. + format: date-time + description: + The invoice details for charges associated with the goods in the + package. Only applies to certain regions. + ChargeList: + type: array + description: + A list of charges based on the shipping service charges applied + on a package. + items: + $ref: "#/components/schemas/ChargeComponent" + ChargeComponent: + type: object + properties: + amount: + $ref: "#/components/schemas/Currency" + chargeType: + type: string + description: The type of charge. + enum: + - TAX + - DISCOUNT + x-docgen-enum-table-extension: + - value: TAX + description: A tax imposed on a package. + - value: DISCOUNT + description: A discount deducted from the cost of a package. + description: The type and amount of a charge applied on a package. + Currency: + required: + - unit + - value + type: object + properties: + value: + type: number + description: The monetary value. + unit: + maxLength: 3 + minLength: 3 + type: string + description: The ISO 4217 format 3-character currency code. + description: + "The monetary value in the currency indicated, in ISO 4217 standard\ + \ format." + Dimensions: + required: + - height + - length + - unit + - width + type: object + properties: + length: + type: number + description: The length of the package. + width: + type: number + description: The width of the package. + height: + type: number + description: The height of the package. + unit: + type: string + description: The unit of measurement. + enum: + - INCH + - CENTIMETER + x-docgen-enum-table-extension: + - value: INCH + description: The imperial unit of length equal to one twelfth of a foot. + - value: CENTIMETER + description: "A metric unit of length, equal to one hundredth of a meter." + description: A set of measurements for a three-dimensional object. + RequestToken: + type: string + description: A unique token generated to identify a getRates operation. + RateId: + type: string + description: + An identifier for the rate (shipment offering) provided by a shipping + service provider. + CarrierId: + type: string + description: "The carrier identifier for the offering, provided by the carrier." + CarrierName: + type: string + description: The carrier name for the offering. + PackageClientReferenceId: + type: string + description: + A client provided unique identifier for a package being shipped. + This value should be saved by the client to pass as a parameter to the getShipmentDocuments + operation. + AdditionalPackageInfo: + type: array + description: Additional information about the package provided by Shipper + items: + required: + - name + - value + type: object + properties: + name: + type: string + description: + The name of the information content being sent along with + the package + enum: + - PURCHASE_ORDER_NUMBER + x-docgen-enum-table-extension: + - value: PURCHASE_ORDER_NUMBER + description: Specifies the Purchase Order number. + value: + type: string + description: + The value of the information content being sent along with + the package + ShipmentId: + type: string + description: The unique shipment identifier provided by a shipping service. + DetailCodes: + type: string + description: A list of codes used to provide additional shipment information. + enum: + - BusinessClosed + - CustomerUnavailable + - PaymentNotReady + - OtpNotAvailable + - DeliveryAttempted + - UnableToAccess + - UnableToContactRecipient + - DeliveredToBehindWheelieBin + - DeliveredToPorch + - DeliveredToGarage + - DeliveredToGarden + - DeliveredToGreenhouse + - DeliveredToMailSlot + - DeliveredToMailRoom + - DeliveredToNeighbor + - DeliveredToRearDoor + - DeliveredToReceptionist + - DeliveredToShed + - DeliveredWithOTP + - Signed + - Damaged + - IncorrectItems + - NotRequired + - Rejected + - RejectedByRecipientWithVerification + - CancelledByRecipient + - AddressNotFound + - HazmatShipment + - Undeliverable + - ArrivedAtLocalFacility + x-docgen-enum-table-extension: + - value: BusinessClosed + description: Attempted delivery but the business was closed. + - value: CustomerUnavailable + description: Attempted delivery but the recipient was unavailable. + - value: PaymentNotReady + description: Attempted delivery but cash was not available from recipient. + - value: OtpNotAvailable + description: Attempted delivery but the recipient did not have the PIN available. + - value: DeliveryAttempted + description: Delivery was attempted. + - value: UnableToAccess + description: Attempted delivery but could not access the recipient location. + - value: UnableToContactRecipient + description: + Attempted delivery but unable to contact the recipient to coordinate + delivery. + - value: DeliveredToBehindWheelieBin + description: Shipment delivered behind wheelie bin. + - value: DeliveredToPorch + description: Shipment delivered to front porch or front door. + - value: DeliveredToGarage + description: Shipment delivered to garage. + - value: DeliveredToGarden + description: Shipment delivered to garden. + - value: DeliveredToGreenhouse + description: Shipment delivered to greenhouse. + - value: DeliveredToMailSlot + description: Shipment delivered to letterbox/mail slot. + - value: DeliveredToMailRoom + description: Shipment delivered to mail room. + - value: DeliveredToNeighbor + description: Shipment delivered and signed for by neighbor. + - value: DeliveredToRearDoor + description: Shipment delivered to rear porch or rear door. + - value: DeliveredToReceptionist + description: Shipment delivered to reception. + - value: DeliveredToShed + description: Shipment delivered to shed. + - value: DeliveredWithOTP + description: Shipment delivered with onetime passcode. + - value: Signed + description: Shipment delivered and signature received. + - value: Damaged + description: + Shipment was damaged. When paired with Rejected this indicates + the recipient refused to accept the shipment because the shipment was damaged. + When paired with UNDELIVERABLE it means the shipment was undeliverable due + to being damaged and may be returned to the seller. + - value: IncorrectItems + description: + Recipient refused to accept the shipment because the shipment + contains incorrect item/s. + - value: NotRequired + description: + Recipient refused to accept the shipment because the recipient + did not want the shipment. + - value: Rejected + description: Recipient refused to accept the shipment. + - value: RejectedByRecipientWithVerification + description: Recipient refused to accept the shipment and provided verification. + - value: CancelledByRecipient + description: + Shipment was cancelled by the recipient. This tag is paired with + the rejected shipment status. + - value: AddressNotFound + description: + Shipment is undeliverable and is returning to seller because + the address was missing or incorrect. + - value: HazmatShipment + description: Shipment is non-compliant Hazmat. + - value: Undeliverable + description: Shipment is undeliverable and is returning to the seller. + - value: ArrivedAtLocalFacility + description: Package arrived at the local facility. + TrackingDetailCodes: + required: + - forward + - returns + type: object + properties: + forward: + type: array + description: + Contains detail codes that provide additional details related + to the forward leg of the shipment. + items: + $ref: "#/components/schemas/DetailCodes" + returns: + type: array + description: + Contains detail codes that provide additional details related + to the return leg of the shipment. + items: + $ref: "#/components/schemas/DetailCodes" + description: + Contains detail codes that provide additional details related to + the forward and return leg of the shipment. + TrackingId: + type: string + description: The carrier generated identifier for a package in a purchased shipment. + AlternateLegTrackingId: + type: string + description: + The carrier generated reverse identifier for a returned package + in a purchased shipment. + ServiceId: + type: string + description: An identifier for the shipping service. + ServiceName: + type: string + description: The name of the shipping service. + AdditionalAddressNotes: + maxLength: 256 + type: string + description: Address notes to re-attempt delivery with. + ShipperInstruction: + type: object + properties: + deliveryNotes: + maxLength: 256 + type: string + description: The delivery notes for the shipment + description: The shipper instruction. + Address: + required: + - addressLine1 + - city + - countryCode + - name + - postalCode + - stateOrRegion + type: object + properties: + name: + maxLength: 50 + minLength: 1 + type: string + description: "The name of the person, business or institution at the address." + addressLine1: + maxLength: 60 + minLength: 1 + type: string + description: The first line of the address. + addressLine2: + maxLength: 60 + minLength: 1 + type: string + description: "Additional address information, if required." + addressLine3: + maxLength: 60 + minLength: 1 + type: string + description: "Additional address information, if required." + companyName: + type: string + description: + The name of the business or institution associated with the + address. + stateOrRegion: + $ref: "#/components/schemas/StateOrRegion" + city: + $ref: "#/components/schemas/City" + countryCode: + $ref: "#/components/schemas/CountryCode" + postalCode: + $ref: "#/components/schemas/PostalCode" + email: + maxLength: 64 + type: string + description: The email address of the contact associated with the address. + phoneNumber: + maxLength: 20 + minLength: 1 + type: string + description: + "The phone number of the person, business or institution located\ + \ at that address, including the country calling code." + geocode: + $ref: "#/components/schemas/Geocode" + description: The address. + Geocode: + type: object + properties: + latitude: + type: string + description: + The latitude component of the location in ISO6709:2022 Decimal + Degree format. + longitude: + type: string + description: + The longitude component of the location in ISO6709:2022 Decimal + Degree format. + elevation: + type: string + description: + The height or depth component of the location in ISO6709:2022 + Decimal Degree format. This is only present when the data is available. + description: + A representation of the geographical location. Currently only returns + the geographic coordinate formatted in the ISO6709:2022 Decimal Degrees for + longitude and latitude. + StateOrRegion: + type: string + description: + "The state, county or region where the person, business or institution\ + \ is located." + City: + type: string + description: + "The city or town where the person, business or institution is\ + \ located." + CountryCode: + type: string + description: The two digit country code. Follows ISO 3166-1 alpha-2 format. + PostalCode: + type: string + description: + "The postal code of that address. It contains a series of letters\ + \ or digits or both, sometimes including spaces or punctuation." + Location: + type: object + properties: + stateOrRegion: + $ref: "#/components/schemas/StateOrRegion" + city: + $ref: "#/components/schemas/City" + countryCode: + $ref: "#/components/schemas/CountryCode" + postalCode: + $ref: "#/components/schemas/PostalCode" + description: "The location where the person, business or institution is located." + DocumentFormat: + type: string + description: The file format of the document. + enum: + - PDF + - PNG + - ZPL + x-docgen-enum-table-extension: + - value: PDF + description: + "The Portable Document Format (PDF) file format. Used to present\ + \ documents, including text formatting and images, in a manner independent\ + \ of application software, hardware, and operating systems." + - value: PNG + description: + Portable Network Graphics (PNG) is a raster-graphics file format + that supports lossless data compression. + - value: ZPL + description: + Zebra Programming Language (ZPL) format is from Zebra Technologies. + It's used primarily for labeling applications and can only be used with + ZPL compatible printers. + DocumentType: + type: string + description: The type of shipping document. + enum: + - PACKSLIP + - LABEL + - RECEIPT + - CUSTOM_FORM + x-docgen-enum-table-extension: + - value: PACKSLIP + description: A listing of the items packed within the shipment. + - value: LABEL + description: The shipping label for the specific shipment. + - value: RECEIPT + description: The receipt of the shipment. + - value: CUSTOM_FORM + description: The customs documentation for a cross-border shipment. + Dpi: + type: integer + description: + The dots per inch (DPI) value used in printing. This value represents + a measure of the resolution of the document. + PageLayout: + type: string + description: + Indicates the position of the label on the paper. Should be the + same value as returned in getRates response. + NeedFileJoining: + type: boolean + description: + "When true, files should be stitched together. Otherwise, files\ + \ should be returned separately. Defaults to false." + Contents: + type: string + description: A Base64 encoded string of the file contents. + RequestedLabelCustomization: + type: object + properties: + requestAttributes: + $ref: "#/components/schemas/RequestAttributes" + description: + Object contains customised data requested by a shipper to be printed + on a shipping label. + RequestAttributes: + type: array + description: Specify the type of attributes to be added on a label. + items: + $ref: "#/components/schemas/LabelAttribute" + LabelAttribute: + type: string + description: + Enumerates the attributes supported to be printed on a shipping + label. The values for these attributes are retrieved from GetRates/OneClickShipment + request + enum: + - PACKAGE_CLIENT_REFERENCE_ID + - SELLER_DISPLAY_NAME + - COLLECT_ON_DELIVERY_AMOUNT + x-docgen-enum-table-extension: + - value: PACKAGE_CLIENT_REFERENCE_ID + description: Prints client reference ID on the shipping label + - value: SELLER_DISPLAY_NAME + description: Prints seller name on the shipping label + - value: COLLECT_ON_DELIVERY_AMOUNT + description: Prints Collect On Delivery amount on the shipping label. + PackageDocumentList: + type: array + description: A list of documents related to a package. + items: + $ref: "#/components/schemas/PackageDocument" + PackageDocument: + required: + - contents + - format + - type + type: object + properties: + type: + $ref: "#/components/schemas/DocumentType" + format: + $ref: "#/components/schemas/DocumentFormat" + contents: + $ref: "#/components/schemas/Contents" + description: A document related to a package. + PrintOptionList: + type: array + description: A list of the format options for a label. + items: + $ref: "#/components/schemas/PrintOption" + PrintOption: + required: + - supportedDocumentDetails + - supportedFileJoiningOptions + - supportedPageLayouts + type: object + properties: + supportedDPIs: + type: array + description: A list of the supported DPI options for a document. + items: + $ref: "#/components/schemas/Dpi" + supportedPageLayouts: + type: array + description: A list of the supported page layout options for a document. + items: + $ref: "#/components/schemas/PageLayout" + supportedFileJoiningOptions: + type: array + description: + A list of the supported needFileJoining boolean values for + a document. + items: + $ref: "#/components/schemas/NeedFileJoining" + supportedDocumentDetails: + type: array + description: A list of the supported documented details. + items: + $ref: "#/components/schemas/SupportedDocumentDetail" + description: The format options available for a label. + DocumentSize: + required: + - length + - unit + - width + type: object + properties: + width: + type: number + description: The width of the document measured in the units specified. + length: + type: number + description: The length of the document measured in the units specified. + unit: + type: string + description: The unit of measurement. + enum: + - INCH + - CENTIMETER + x-docgen-enum-table-extension: + - value: INCH + description: The imperial unit of length equal to one twelfth of a foot. + - value: CENTIMETER + description: "A metric unit of length, equal to one hundredth of a meter." + description: The size dimensions of the label. + SupportedDocumentDetail: + required: + - isMandatory + - name + type: object + properties: + name: + $ref: "#/components/schemas/DocumentType" + isMandatory: + type: boolean + description: "When true, the supported document type is required." + description: The supported document types for a service offering. + RequestedDocumentSpecification: + required: + - format + - needFileJoining + - requestedDocumentTypes + - size + type: object + properties: + format: + $ref: "#/components/schemas/DocumentFormat" + size: + $ref: "#/components/schemas/DocumentSize" + dpi: + $ref: "#/components/schemas/Dpi" + pageLayout: + $ref: "#/components/schemas/PageLayout" + needFileJoining: + $ref: "#/components/schemas/NeedFileJoining" + requestedDocumentTypes: + type: array + description: A list of the document types requested. + items: + $ref: "#/components/schemas/DocumentType" + requestedLabelCustomization: + $ref: "#/components/schemas/RequestedLabelCustomization" + description: + "The document specifications requested. For calls to the purchaseShipment\ + \ operation, the shipment purchase fails if the specified document specifications\ + \ are not among those returned in the response to the getRates operation." + SupportedDocumentSpecificationList: + type: array + description: + A list of the document specifications supported for a shipment + service offering. + items: + $ref: "#/components/schemas/SupportedDocumentSpecification" + SupportedDocumentSpecification: + required: + - format + - printOptions + - size + type: object + properties: + format: + $ref: "#/components/schemas/DocumentFormat" + size: + $ref: "#/components/schemas/DocumentSize" + printOptions: + $ref: "#/components/schemas/PrintOptionList" + description: Document specification that is supported for a service offering. + Item: + required: + - quantity + type: object + properties: + itemValue: + $ref: "#/components/schemas/Currency" + description: + type: string + description: + "The product description of the item. Note: It must accurately\ + \ reflect the goods being shipped. For shipments to Northern Ireland,\ + \ this field must also contain a six-digit commodity code (HS code) in\ + \ the following format: HSCODE:XXXXXX (six digits without spaces or special\ + \ characters)." + itemIdentifier: + type: string + description: + "A unique identifier for an item provided by the client. Please\ + \ use the Orders SP api and populate this field with the response parameter\ + \ OrderItemId. Note: This is Required field for Amazon Marketplace Orders\ + \ (ON Amazon type of requests)." + quantity: + type: integer + description: The number of units. This value is required. + weight: + $ref: "#/components/schemas/Weight" + liquidVolume: + $ref: "#/components/schemas/LiquidVolume" + isHazmat: + type: boolean + description: + "When true, the item qualifies as hazardous materials (hazmat).\ + \ Defaults to false." + dangerousGoodsDetails: + $ref: "#/components/schemas/DangerousGoodsDetails" + productType: + type: string + description: The product type of the item. + invoiceDetails: + $ref: "#/components/schemas/InvoiceDetails" + serialNumbers: + type: array + description: + A list of unique serial numbers in an Amazon package that can + be used to guarantee non-fraudulent items. The number of serial numbers + in the list must be less than or equal to the quantity of items being + shipped. Only applicable when channel source is Amazon. + items: + type: string + directFulfillmentItemIdentifiers: + $ref: "#/components/schemas/DirectFulfillmentItemIdentifiers" + description: An item in a package. + ItemList: + type: array + description: A list of items. + items: + $ref: "#/components/schemas/Item" + Package: + required: + - dimensions + - insuredValue + - items + - packageClientReferenceId + - weight + type: object + properties: + dimensions: + $ref: "#/components/schemas/Dimensions" + weight: + $ref: "#/components/schemas/Weight" + insuredValue: + $ref: "#/components/schemas/Currency" + isHazmat: + type: boolean + description: + "When true, the package contains hazardous materials. Defaults\ + \ to false." + sellerDisplayName: + type: string + description: The seller name displayed on the label. + charges: + $ref: "#/components/schemas/ChargeList" + packageClientReferenceId: + $ref: "#/components/schemas/PackageClientReferenceId" + additionalPackageInfo: + $ref: "#/components/schemas/AdditionalPackageInfo" + items: + $ref: "#/components/schemas/ItemList" + description: A package to be shipped through a shipping service offering. + PackageList: + type: array + description: A list of packages to be shipped through a shipping service offering. + items: + $ref: "#/components/schemas/Package" + DirectFulfillmentItemIdentifiers: + required: + - lineItemID + type: object + properties: + lineItemID: + type: string + description: + A unique identifier for an item provided by the client for + a direct fulfillment shipment. This is only populated for direct fulfillment + multi-piece shipments. It is required if a vendor wants to change the + configuration of the packages in which the purchase order is shipped. + pieceNumber: + type: string + description: + A unique identifier for an item provided by the client for + a direct fulfillment shipment. This is only populated if a single line + item has multiple pieces. Defaults to 1. + description: Item identifiers for an item in a direct fulfillment shipment. + PackageDocumentDetail: + required: + - packageClientReferenceId + - packageDocuments + type: object + properties: + packageClientReferenceId: + $ref: "#/components/schemas/PackageClientReferenceId" + packageDocuments: + $ref: "#/components/schemas/PackageDocumentList" + trackingId: + $ref: "#/components/schemas/TrackingId" + description: + The post-purchase details of a package that will be shipped using + a shipping service. + PackageDocumentDetailList: + type: array + description: + A list of post-purchase details about a package that will be shipped + using a shipping service. + items: + $ref: "#/components/schemas/PackageDocumentDetail" + TimeWindow: + type: object + properties: + start: + type: string + description: The start time of the time window. + format: date-time + end: + type: string + description: The end time of the time window. + format: date-time + description: The start and end time that specifies the time interval of an event. + Promise: + type: object + properties: + deliveryWindow: + $ref: "#/components/schemas/TimeWindow" + pickupWindow: + $ref: "#/components/schemas/TimeWindow" + description: The time windows promised for pickup and delivery events. + RequestedValueAddedServiceList: + type: array + description: The value-added services to be added to a shipping service purchase. + items: + $ref: "#/components/schemas/RequestedValueAddedService" + RequestedValueAddedService: + required: + - id + type: object + properties: + id: + type: string + description: + The identifier of the selected value-added service. Must be + among those returned in the response to the getRates operation. + description: A value-added service to be applied to a shipping service purchase. + AvailableValueAddedServiceGroupList: + type: array + description: + A list of value-added services available for a shipping service + offering. + items: + $ref: "#/components/schemas/AvailableValueAddedServiceGroup" + AvailableValueAddedServiceGroup: + required: + - groupDescription + - groupId + - isRequired + type: object + properties: + groupId: + type: string + description: The type of the value-added service group. + groupDescription: + type: string + description: The name of the value-added service group. + isRequired: + type: boolean + description: + "When true, one or more of the value-added services listed\ + \ must be specified." + valueAddedServices: + type: array + description: + A list of optional value-added services available for purchase + with a shipping service offering. + items: + $ref: "#/components/schemas/ValueAddedService" + description: + The value-added services available for purchase with a shipping + service offering. + ValueAddedService: + required: + - cost + - id + - name + type: object + properties: + id: + type: string + description: The identifier for the value-added service. + name: + type: string + description: The name of the value-added service. + cost: + $ref: "#/components/schemas/Currency" + description: + A value-added service available for purchase with a shipment service + offering. + CollectOnDelivery: + required: + - amount + type: object + properties: + amount: + $ref: "#/components/schemas/Currency" + description: The amount to collect on delivery. + SignatureOnDelivery: + type: object + description: + Setting this structure requests a service to confirm that the package + has been delivered via signature at the time of delivery. + OTPOnDelivery: + type: object + description: + Setting this structure requests a service to confirm that the package + has been delivered via One Time Passcode at the time of delivery. + ValueAddedServiceDetails: + type: object + properties: + collectOnDelivery: + $ref: "#/components/schemas/CollectOnDelivery" + signatureOnDelivery: + $ref: "#/components/schemas/SignatureOnDelivery" + otpOnDelivery: + $ref: "#/components/schemas/OTPOnDelivery" + description: A collection of supported value-added services. + DangerousGoodsDetails: + type: object + properties: + unitedNationsRegulatoryId: + pattern: "^(UN)[0-9]{4}$" + type: string + description: The specific UNID of the item being shipped. + transportationRegulatoryClass: + pattern: "^[1-9](\\.[1-9])?$" + type: string + description: The specific regulatory class of the item being shipped. + packingGroup: + type: string + description: The specific packaging group of the item being shipped. + enum: + - I + - II + - III + x-docgen-enum-table-extension: + - value: I + description: Packing group I indicates great danger. + - value: II + description: Packing group II indicates medium danger. + - value: III + description: Packing group III indicates minor danger. + packingInstruction: + type: string + description: The specific packing instruction of the item being shipped. + enum: + - PI965_SECTION_IA + - PI965_SECTION_IB + - PI965_SECTION_II + - PI966_SECTION_I + - PI966_SECTION_II + - PI967_SECTION_I + - PI967_SECTION_II + - PI968_SECTION_IA + - PI968_SECTION_IB + - PI969_SECTION_I + - PI969_SECTION_II + - PI970_SECTION_I + - PI970_SECTION_II + x-docgen-enum-table-extension: + - value: PI965_SECTION_IA + description: Ion PI965 Section IA (LiBa) + - value: PI965_SECTION_IB + description: Ion PI965 Section IB (LiBa) + - value: PI965_SECTION_II + description: Ion PI965 Section II (LiBa) + - value: PI966_SECTION_I + description: Ion PI966 Section I (LiBa with equipment) + - value: PI966_SECTION_II + description: Ion PI966 Section II (LiBa with equipment) + - value: PI967_SECTION_I + description: Ion PI967 Section I (LiBa in equipment) + - value: PI967_SECTION_II + description: Ion PI967 Section II (LiBa in equipment) + - value: PI968_SECTION_IA + description: Metal PI968 Section IA (LiBa) + - value: PI968_SECTION_IB + description: Metal PI968 Section IB (LiBa) + - value: PI969_SECTION_I + description: Metal PI969 Section I (LiBa with equipment) + - value: PI969_SECTION_II + description: Metal PI969 Section II (LiBa with equipment) + - value: PI970_SECTION_I + description: Metal PI970 Section I (LiBa in equipment) + - value: PI970_SECTION_II + description: Metal PI970 Section II (LiBa in equipment) + description: Details related to any dangerous goods/items that are being shipped. + TaxType: + type: string + description: Indicates the type of tax. + enum: + - GST + x-docgen-enum-table-extension: + - value: GST + description: Goods and Services Tax. + TaxDetail: + required: + - taxRegistrationNumber + - taxType + type: object + properties: + taxType: + $ref: "#/components/schemas/TaxType" + taxRegistrationNumber: + type: string + description: + The shipper's tax registration number associated with the shipment + for customs compliance purposes in certain regions. + description: + Indicates the tax specifications associated with the shipment for + customs compliance purposes in certain regions. + TaxDetailList: + type: array + description: A list of tax detail information. + items: + $ref: "#/components/schemas/TaxDetail" + ShipperNumber: + type: string + description: A unique identifier for a shipper’s 'Amazon Shipping' account. + GoodsOwner: + required: + - merchantId + type: object + properties: + merchantId: + $ref: "#/components/schemas/MerchantId" + description: The seller owning the goods before handing them over to the carrier + EventCode: + type: string + description: The tracking event type. + enum: + - ReadyForReceive + - PickupDone + - Delivered + - Departed + - DeliveryAttempted + - Lost + - OutForDelivery + - ArrivedAtCarrierFacility + - Rejected + - Undeliverable + - PickupCancelled + - ReturnInitiated + - AvailableForPickup + - RecipientRequestedAlternateDeliveryTiming + - PackageReceivedByCarrier + x-docgen-enum-table-extension: + - value: ReadyForReceive + description: + Package has been created and is ready for pickup at the shippers + location. This is a pre-transit status event code. + - value: PickupDone + description: Package has been picked up by the service provider. + - value: Delivered + description: Package has been delivered. + - value: Departed + description: Package has departed from a particular location in carrier network. + - value: DeliveryAttempted + description: "Delivery was attempted, but was unsuccessful." + - value: Lost + description: Package is lost. + - value: OutForDelivery + description: Package is out for delivery. + - value: ArrivedAtCarrierFacility + description: Package is in transit and has been received at a carrier location. + - value: Rejected + description: Package was rejected by the recipient. + - value: Undeliverable + description: Package is undeliverable. + - value: PickupCancelled + description: Pickup scheduled for the package was cancelled. + - value: ReturnInitiated + description: Return label has been generated. + - value: AvailableForPickup + description: Package held for recipient pickup. + - value: RecipientRequestedAlternateDeliveryTiming + description: Recipient has requested an update to their delivery date. + - value: PackageReceivedByCarrier + description: Package has been received at a carrier location. + Event: + required: + - eventCode + - eventTime + type: object + properties: + eventCode: + $ref: "#/components/schemas/EventCode" + location: + $ref: "#/components/schemas/Location" + eventTime: + type: string + description: The ISO 8601 formatted timestamp of the event. + format: date-time + shipmentType: + $ref: "#/components/schemas/ShipmentType" + description: A tracking event. + TrackingSummary: + type: object + properties: + status: + $ref: "#/components/schemas/Status" + trackingDetailCodes: + $ref: "#/components/schemas/TrackingDetailCodes" + proofOfDelivery: + $ref: "#/components/schemas/ProofOfDelivery" + description: A package status summary. + Status: + type: string + description: The status of the package being shipped. + enum: + - PreTransit + - InTransit + - Delivered + - Lost + - OutForDelivery + - Rejected + - Undeliverable + - DeliveryAttempted + - PickupCancelled + - AwaitingCustomerPickup + x-docgen-enum-table-extension: + - value: PreTransit + description: Package has been created but has not been picked up. + - value: InTransit + description: Package has been picked up and is in transit. + - value: Delivered + description: Package has has been delivered successfully. + - value: Lost + description: Package is lost. + - value: OutForDelivery + description: Package is out for delivery. + - value: Rejected + description: Package has been rejected by the recipient. + - value: Undeliverable + description: Package was undeliverable. + - value: DeliveryAttempted + description: + "Delivery was attempted to the recipient location, but was not\ + \ delivered." + - value: PickupCancelled + description: Pickup was cancelled for the package. + - value: AwaitingCustomerPickup + description: Package held at facility for the customer pickup. + ProofOfDelivery: + type: object + properties: + deliveryLocationCoordinates: + $ref: "#/components/schemas/Geocode" + deliveryImageURL: + type: string + description: + "This attribute exists only when available. A temporary URL\ + \ that provides access to the image taken at the time of delivery. This\ + \ URL is dynamically generated with each request, and remains valid for\ + \ 72 hours before expiring. If the delivery image is needed again, another\ + \ getTracking request can be made." + signatureImageURL: + type: string + description: + "This attribute exists only when available. A temporary URL\ + \ that provides access to the signature given at the time of delivery.\ + \ This URL is dynamically generated with each request, and remains valid\ + \ for 72 hours before expiring. If the signature image is needed again,\ + \ another getTracking request can be made." + receivedBy: + type: string + description: + This attribute exists only when available. The recorded person + or entity who received the package. + description: + "Contains the proof of delivery information for a package, if available\ + \ and permitted." + AmazonOrderDetails: + required: + - orderId + type: object + properties: + orderId: + type: string + description: + The Amazon order ID associated with the Amazon order fulfilled + by this shipment. + description: + Amazon order information. This is required if the shipment source + channel is Amazon. + AmazonShipmentDetails: + required: + - shipmentId + type: object + properties: + shipmentId: + type: string + description: + This attribute is required only for a Direct Fulfillment shipment. + This is the encrypted shipment ID. + description: Amazon shipment information. + ChannelType: + type: string + description: The shipment source channel type. + enum: + - AMAZON + - EXTERNAL + x-docgen-enum-table-extension: + - value: AMAZON + description: Indicates that the shipment originates from an Amazon order. + - value: EXTERNAL + description: Indicates that the shipment originates from a non-Amazon channel. + ChannelDetails: + required: + - channelType + type: object + properties: + channelType: + $ref: "#/components/schemas/ChannelType" + amazonOrderDetails: + $ref: "#/components/schemas/AmazonOrderDetails" + amazonShipmentDetails: + $ref: "#/components/schemas/AmazonShipmentDetails" + description: Shipment source channel related information. + RateList: + type: array + description: A list of eligible shipping service offerings. + items: + $ref: "#/components/schemas/Rate" + Rate: + required: + - carrierId + - carrierName + - promise + - rateId + - requiresAdditionalInputs + - serviceId + - serviceName + - supportedDocumentSpecifications + - totalCharge + type: object + properties: + rateId: + $ref: "#/components/schemas/RateId" + carrierId: + $ref: "#/components/schemas/CarrierId" + carrierName: + $ref: "#/components/schemas/CarrierName" + serviceId: + $ref: "#/components/schemas/ServiceId" + serviceName: + $ref: "#/components/schemas/ServiceName" + billedWeight: + $ref: "#/components/schemas/Weight" + totalCharge: + $ref: "#/components/schemas/Currency" + totalChargeWithAdjustments: + $ref: "#/components/schemas/Currency" + promise: + $ref: "#/components/schemas/Promise" + supportedDocumentSpecifications: + $ref: "#/components/schemas/SupportedDocumentSpecificationList" + availableValueAddedServiceGroups: + $ref: "#/components/schemas/AvailableValueAddedServiceGroupList" + requiresAdditionalInputs: + type: boolean + description: + "When true, indicates that additional inputs are required to\ + \ purchase this shipment service. You must then call the getAdditionalInputs\ + \ operation to return the JSON schema to use when providing the additional\ + \ inputs to the purchaseShipment operation." + rateItemList: + $ref: "#/components/schemas/RateItemList" + adjustmentItemList: + $ref: "#/components/schemas/AdjustmentItemList" + paymentType: + $ref: "#/components/schemas/PaymentType" + benefits: + $ref: "#/components/schemas/Benefits" + description: The details of a shipping service offering. + IneligibilityReasonCode: + type: string + description: Reasons that make a shipment service offering ineligible. + enum: + - NO_COVERAGE + - PICKUP_SLOT_RESTRICTION + - UNSUPPORTED_VAS + - VAS_COMBINATION_RESTRICTION + - SIZE_RESTRICTIONS + - WEIGHT_RESTRICTIONS + - LATE_DELIVERY + - PROGRAM_CONSTRAINTS + - TERMS_AND_CONDITIONS_NOT_ACCEPTED + - UNKNOWN + x-docgen-enum-table-extension: + - value: NO_COVERAGE + description: + The shipment is ineligible because there is no coverage to that + address. + - value: PICKUP_SLOT_RESTRICTION + description: + The shipment is ineligible because there is an issue with the + pickup slot. + - value: UNSUPPORTED_VAS + description: + The shipment is ineligible because the value-added service is + invalid for this shipment. + - value: VAS_COMBINATION_RESTRICTION + description: + The shipment is ineligible because an invalid combination of + value-added services were chosen. + - value: SIZE_RESTRICTIONS + description: + The shipment is ineligible because the package dimensions are + unsupported. + - value: WEIGHT_RESTRICTIONS + description: The shipment is ineligible because the weight is unsupported. + - value: LATE_DELIVERY + description: The shipment is ineligible because delivery is too late. + - value: PROGRAM_CONSTRAINTS + description: The shipment is ineligible because of program constraints. + - value: TERMS_AND_CONDITIONS_NOT_ACCEPTED + description: + The shipment is ineligible because terms and conditions have + not been accepted by the carrier. + - value: UNKNOWN + description: The ineligibility reason is unknown. + IneligibilityReason: + required: + - code + - message + type: object + properties: + code: + $ref: "#/components/schemas/IneligibilityReasonCode" + message: + type: string + description: The ineligibility reason. + description: The reason why a shipping service offering is ineligible. + IneligibleRate: + required: + - carrierId + - carrierName + - ineligibilityReasons + - serviceId + - serviceName + type: object + properties: + serviceId: + $ref: "#/components/schemas/ServiceId" + serviceName: + $ref: "#/components/schemas/ServiceName" + carrierName: + $ref: "#/components/schemas/CarrierName" + carrierId: + $ref: "#/components/schemas/CarrierId" + ineligibilityReasons: + type: array + description: A list of reasons why a shipping service offering is ineligible. + items: + $ref: "#/components/schemas/IneligibilityReason" + description: Detailed information for an ineligible shipping service offering. + IneligibleRateList: + type: array + description: A list of ineligible shipping service offerings. + items: + $ref: "#/components/schemas/IneligibleRate" + CancelShipmentResult: + type: object + additionalProperties: + type: object + description: The payload for the cancelShipment operation. + CancelShipmentResponse: + type: object + properties: + payload: + $ref: "#/components/schemas/CancelShipmentResult" + description: Response schema for the cancelShipment operation. + GetRatesRequest: + required: + - channelDetails + - packages + - shipFrom + type: object + properties: + shipTo: + $ref: "#/components/schemas/Address" + shipFrom: + $ref: "#/components/schemas/Address" + returnTo: + $ref: "#/components/schemas/Address" + shipDate: + type: string + description: + The ship date and time (the requested pickup). This defaults + to the current date and time. + format: date-time + quoteForShipperNumber: + $ref: "#/components/schemas/ShipperNumber" + shipmentForShipperNumber: + $ref: "#/components/schemas/ShipperNumber" + shipperInstruction: + $ref: "#/components/schemas/ShipperInstruction" + packages: + $ref: "#/components/schemas/PackageList" + valueAddedServices: + $ref: "#/components/schemas/ValueAddedServiceDetails" + taxDetails: + $ref: "#/components/schemas/TaxDetailList" + channelDetails: + $ref: "#/components/schemas/ChannelDetails" + clientReferenceDetails: + $ref: "#/components/schemas/ClientReferenceDetails" + shipmentType: + $ref: "#/components/schemas/ShipmentType" + destinationAccessPointDetails: + $ref: "#/components/schemas/AccessPointDetails" + description: + "The request schema for the getRates operation. When the channelType\ + \ is Amazon, the shipTo address is not required and will be ignored." + AccessPointDetails: + type: object + properties: + accessPointId: + $ref: "#/components/schemas/AccessPointId" + description: AccessPointDetails object + NdrAction: + type: string + description: The type of NDR action shipper wants to take for a particular shipment. + enum: + - RESCHEDULE + - REATTEMPT + - RTO + x-docgen-enum-table-extension: + - value: RESCHEDULE + description: Seller wants to reschedule shipment delivery to a specific date. + - value: REATTEMPT + description: + Seller wants to re-attempt delivery with additional delivery + notes. + - value: RTO + description: Seller wants the shipment to return to the origin. + NdrRequestData: + type: object + properties: + rescheduleDate: + type: string + description: + "The date on which the Seller wants to reschedule shipment\ + \ delivery, in ISO-8601 date/time format" + format: date-time + example: 2023-12-12T05:24:00Z + additionalAddressNotes: + $ref: "#/components/schemas/AdditionalAddressNotes" + description: + "Additional information required for the NDR action that has been\ + \ filed. If the NDR Action is RESCHEDULE, rescheduleDate is a required field.\ + \ Otherwise, if the NDR Action is REATTEMPT, additionalAddressNotes is a required\ + \ field. " + GetRatesResult: + required: + - rates + - requestToken + type: object + properties: + requestToken: + $ref: "#/components/schemas/RequestToken" + rates: + $ref: "#/components/schemas/RateList" + ineligibleRates: + $ref: "#/components/schemas/IneligibleRateList" + description: The payload for the getRates operation. + GetRatesResponse: + type: object + properties: + payload: + $ref: "#/components/schemas/GetRatesResult" + description: The response schema for the getRates operation. + GetShipmentDocumentsResult: + required: + - packageDocumentDetail + - shipmentId + type: object + properties: + shipmentId: + $ref: "#/components/schemas/ShipmentId" + packageDocumentDetail: + $ref: "#/components/schemas/PackageDocumentDetail" + benefits: + $ref: "#/components/schemas/Benefits" + description: The payload for the getShipmentDocuments operation. + GetShipmentDocumentsResponse: + type: object + properties: + payload: + $ref: "#/components/schemas/GetShipmentDocumentsResult" + description: The response schema for the the getShipmentDocuments operation. + GetTrackingResult: + required: + - alternateLegTrackingId + - eventHistory + - promisedDeliveryDate + - summary + - trackingId + type: object + properties: + trackingId: + $ref: "#/components/schemas/TrackingId" + alternateLegTrackingId: + $ref: "#/components/schemas/AlternateLegTrackingId" + eventHistory: + type: array + description: A list of tracking events. + items: + $ref: "#/components/schemas/Event" + promisedDeliveryDate: + type: string + description: The date and time by which the shipment is promised to be delivered. + format: date-time + summary: + $ref: "#/components/schemas/TrackingSummary" + description: The payload for the getTracking operation. + SubmitNdrFeedbackRequest: + required: + - ndrAction + - trackingId + type: object + properties: + trackingId: + $ref: "#/components/schemas/TrackingId" + ndrAction: + $ref: "#/components/schemas/NdrAction" + ndrRequestData: + $ref: "#/components/schemas/NdrRequestData" + description: The request schema for the NdrFeedback operation + GetTrackingResponse: + type: object + properties: + payload: + $ref: "#/components/schemas/GetTrackingResult" + description: The response schema for the getTracking operation. + PurchaseShipmentRequest: + required: + - rateId + - requestToken + - requestedDocumentSpecification + type: object + properties: + requestToken: + $ref: "#/components/schemas/RequestToken" + rateId: + $ref: "#/components/schemas/RateId" + requestedDocumentSpecification: + $ref: "#/components/schemas/RequestedDocumentSpecification" + requestedValueAddedServices: + $ref: "#/components/schemas/RequestedValueAddedServiceList" + additionalInputs: + type: object + additionalProperties: + type: object + description: |- + The additional inputs required to purchase a shipping offering, in JSON format. The JSON provided here must adhere to the JSON schema that is returned in the response to the getAdditionalInputs operation. + + Additional inputs are only required when indicated by the requiresAdditionalInputs property in the response to the getRates operation. + description: The request schema for the purchaseShipment operation. + PurchaseShipmentResult: + required: + - packageDocumentDetails + - promise + - shipmentId + type: object + properties: + shipmentId: + $ref: "#/components/schemas/ShipmentId" + packageDocumentDetails: + $ref: "#/components/schemas/PackageDocumentDetailList" + promise: + $ref: "#/components/schemas/Promise" + benefits: + $ref: "#/components/schemas/Benefits" + totalChargeWithAdjustments: + $ref: "#/components/schemas/Currency" + description: The payload for the purchaseShipment operation. + PurchaseShipmentResponse: + type: object + properties: + payload: + $ref: "#/components/schemas/PurchaseShipmentResult" + description: The response schema for the purchaseShipment operation. + OneClickShipmentRequest: + required: + - channelDetails + - labelSpecifications + - packages + - serviceSelection + - shipFrom + type: object + properties: + shipTo: + $ref: "#/components/schemas/Address" + shipFrom: + $ref: "#/components/schemas/Address" + returnTo: + $ref: "#/components/schemas/Address" + shipDate: + type: string + description: + The ship date and time (the requested pickup). This defaults + to the current date and time. + format: date-time + goodsOwner: + $ref: "#/components/schemas/GoodsOwner" + packages: + $ref: "#/components/schemas/PackageList" + valueAddedServicesDetails: + $ref: "#/components/schemas/OneClickShipmentValueAddedServiceDetails" + valueAddedServices: + $ref: "#/components/schemas/ValueAddedServiceDetails" + taxDetails: + $ref: "#/components/schemas/TaxDetailList" + channelDetails: + $ref: "#/components/schemas/ChannelDetails" + labelSpecifications: + $ref: "#/components/schemas/RequestedDocumentSpecification" + serviceSelection: + $ref: "#/components/schemas/ServiceSelection" + shipperInstruction: + $ref: "#/components/schemas/ShipperInstruction" + destinationAccessPointDetails: + $ref: "#/components/schemas/AccessPointDetails" + description: + "The request schema for the OneClickShipment operation. When the\ + \ channelType is not Amazon, shipTo is required and when channelType is Amazon\ + \ shipTo is ignored." + OneClickShipmentResponse: + type: object + properties: + payload: + $ref: "#/components/schemas/OneClickShipmentResult" + description: The response schema for the OneClickShipment operation. + OneClickShipmentResult: + required: + - carrier + - packageDocumentDetails + - promise + - service + - shipmentId + - totalCharge + type: object + properties: + shipmentId: + $ref: "#/components/schemas/ShipmentId" + packageDocumentDetails: + $ref: "#/components/schemas/PackageDocumentDetailList" + promise: + $ref: "#/components/schemas/Promise" + carrier: + $ref: "#/components/schemas/Carrier" + service: + $ref: "#/components/schemas/Service" + rateItems: + $ref: "#/components/schemas/RateItemList" + totalCharge: + $ref: "#/components/schemas/Currency" + description: The payload for the OneClickShipment API. + GetAccessPointsResponse: + type: object + properties: + payload: + $ref: "#/components/schemas/GetAccessPointsResult" + description: The response schema for the GetAccessPoints operation. + GetAccessPointsResult: + required: + - accessPointsMap + type: object + properties: + accessPointsMap: + $ref: "#/components/schemas/AccessPointsMap" + description: The payload for the GetAccessPoints API. + AccessPointType: + type: string + description: "The type of access point, like counter (HELIX), lockers, etc." + enum: + - HELIX + - CAMPUS_LOCKER + - OMNI_LOCKER + - ODIN_LOCKER + - DOBBY_LOCKER + - CORE_LOCKER + - 3P + - CAMPUS_ROOM + x-docgen-enum-table-extension: + - value: HELIX + description: + "Counter, aka Helix, is an assisted access point where customers\ + \ interact with a store associate to collect or return packages. Counter\ + \ stores do not have Locker" + - value: CAMPUS_LOCKER + description: It denotes a physical Locker present at Locker+ stores + - value: OMNI_LOCKER + description: + Omni lockers are similar to core lockers installed at apartment + buildings. Core and Omni will have common base hardware platform + - value: ODIN_LOCKER + description: + "Odin lockers rely on Bluetooth connection as there is no screen\ + \ or scanner on the locker. Drivers, customers, and even technicians can\ + \ interact with the locker using an app (different depending on the user)." + - value: DOBBY_LOCKER + description: + Dobby lockers are Odin lockers installed at apartment complexes + and available only to residents. + - value: CORE_LOCKER + description: + "Core lockers are the traditional and initial style of lockers\ + \ when Hub was launched. Core lockers have a screen, keypad and scanner.\ + \ Customer either enter their pickup code using the Locker screen or scan\ + \ pickup code barcode to pick up their package." + - value: 3P + description: + "Networks that are owned end-to-end by commercial carriers (CC\ + \ - e.g. DHL, UPS, DPD and Postal organizations), who operate both the transportation\ + \ leg and the access point aspect. These networks offer a range of pickup\ + \ points including: 3P Lockers, legacy Postal Offices, 3P Counters (e.g.\ + \ Gas Stations and Retail Outlets - supermarkets, bookstores, news-stands\ + \ etc)." + - value: CAMPUS_ROOM + description: + "Locker+, formerly known as Campus, are attended, physical stores,\ + \ where the Locker is used dynamically by the staff to keep packages ready\ + \ for customer pickup. Locker+ store can have multiple Lockers installed," + AccessPointsMap: + type: object + additionalProperties: + $ref: "#/components/schemas/AccessPointList" + description: Map of type of access point to list of access points + AccessPointList: + type: array + description: + "List of relevant Access points requested by shipper. These access\ + \ points are sorted by proximity to postal code, and are limited to 40. We\ + \ have internally defined a radius value to render relevant results." + items: + $ref: "#/components/schemas/AccessPoint" + AccessPointId: + type: string + description: Unique identifier for the access point + AccessPoint: + type: object + properties: + accessPointId: + $ref: "#/components/schemas/AccessPointId" + name: + type: string + description: Name of entity (store/hub etc) where this access point is located + timezone: + type: string + description: Timezone of access point + type: + $ref: "#/components/schemas/AccessPointType" + accessibilityAttributes: + $ref: "#/components/schemas/AccessibilityAttributes" + address: + $ref: "#/components/schemas/Address" + exceptionOperatingHours: + type: array + description: Exception operating hours for Access Point + items: + $ref: "#/components/schemas/ExceptionOperatingHours" + assistanceType: + type: string + description: + Assistance type enum for Access point i.e. STAFF_ASSISTED or + SELF_ASSISTED + enum: + - STAFF_ASSISTED + - SELF_ASSISTED + score: + type: string + description: + "The score of access point, based on proximity to postal code\ + \ and sorting preference. This can be used to sort access point results\ + \ on shipper's end." + standardOperatingHours: + $ref: "#/components/schemas/DayOfWeekTimeMap" + description: Access point details + AccessibilityAttributes: + type: object + properties: + distance: + type: string + description: + The approximate distance of access point from input postalCode's + centroid. + driveTime: + type: integer + description: + The approximate (static) drive time from input postal code's + centroid. + description: Defines the accessibility details of the access point. + OperatingHours: + type: object + properties: + closingTime: + $ref: "#/components/schemas/TimeOfDay" + openingTime: + $ref: "#/components/schemas/TimeOfDay" + midDayClosures: + type: array + description: midDayClosures operating hours array + items: + $ref: "#/components/schemas/TimeOfDay" + description: The hours in which the access point shall remain operational + TimeOfDay: + type: object + properties: + hourOfDay: + type: integer + description: + "Denotes hour of the day, used for defining opening or closing\ + \ time of access points" + minuteOfHour: + type: integer + description: + "Denotes minute of the hour, used for defining opening or closing\ + \ time of access points" + secondOfMinute: + type: integer + description: + "Denotes second of the minute, used for defining opening or\ + \ closing time of access points" + description: + "Denotes time of the day, used for defining opening or closing\ + \ time of access points" + DayOfWeekTimeMap: + type: object + additionalProperties: + $ref: "#/components/schemas/OperatingHours" + description: Map of day of the week to operating hours of that day + ExceptionOperatingHours: + type: object + properties: + dateRange: + $ref: "#/components/schemas/DateRange" + operatingHours: + $ref: "#/components/schemas/OperatingHours" + description: + Defines exceptions to standard operating hours for certain date + ranges. + GetAdditionalInputsResult: + type: object + additionalProperties: + type: object + description: + The JSON schema to use to provide additional inputs when required + to purchase a shipping offering. + GetAdditionalInputsResponse: + type: object + properties: + payload: + $ref: "#/components/schemas/GetAdditionalInputsResult" + description: The response schema for the getAdditionalInputs operation. + ClientReferenceDetails: + type: array + description: + "Object to pass additional information about the MCI Integrator\ + \ shipperType: List of ClientReferenceDetail" + items: + $ref: "#/components/schemas/ClientReferenceDetail" + ClientReferenceDetail: + required: + - clientReferenceId + - clientReferenceType + type: object + properties: + clientReferenceType: + type: string + description: Client Reference type. + enum: + - IntegratorShipperId + - IntegratorMerchantId + x-docgen-enum-table-extension: + - value: IntegratorShipperId + description: + The unique identifier assigned to a 3P seller by the shipping + integrator. + - value: IntegratorMerchantId + description: + The unique identifier assigned to a 3P shipping integrator + by Amazon. + clientReferenceId: + type: string + description: The Client Reference Id. + description: Client Reference Details + ShipmentType: + type: string + description: Shipment type. + enum: + - FORWARD + - RETURNS + x-docgen-enum-table-extension: + - value: FORWARD + description: + Additional information about shipment representing package journey + from origin address to destination address in the transportation/shipping + request. + - value: RETURNS + description: + Additional information about shipment representing package journey + from destination address to origin/return address in the original transportation/shipping + request. + DateRange: + type: object + properties: + startDate: + type: string + description: Start Date for query . + format: dateTime + endDate: + type: string + description: end date for query. + format: dateTime + description: Date Range for query the results. + MerchantId: + type: string + description: "merchant Id of provided merchant " + PaymentType: + type: string + description: Payment type of the purchase. + enum: + - PAY_THROUGH_AMAZON + - PAY_DIRECT_TO_CARRIER + x-docgen-enum-table-extension: + - value: PAY_THROUGH_AMAZON + description: Payment Through Amazon. + - value: PAY_DIRECT_TO_CARRIER + description: Payment Direct to Carrier. + AdjustmentItemList: + type: array + description: A list of RateItem + items: + $ref: "#/components/schemas/RateItem" + RateItemList: + type: array + description: A list of RateItem + items: + $ref: "#/components/schemas/RateItem" + RateItem: + type: object + properties: + rateItemID: + $ref: "#/components/schemas/RateItemID" + rateItemType: + $ref: "#/components/schemas/RateItemType" + rateItemCharge: + $ref: "#/components/schemas/Currency" + rateItemNameLocalization: + type: string + description: Used for the localization. + description: + "Rate Item for shipping (base cost, transaction fee, confirmation,\ + \ insurance, etc.) Data source definition: " + RateItemID: + type: string + description: Unique ID for the rateItem. + enum: + - BASE_RATE + - TRANSACTION_FEE + - ADULT_SIGNATURE_CONFIRMATION + - SIGNATURE_CONFIRMATION + - NO_CONFIRMATION + - WAIVE_SIGNATURE + - IMPLIED_LIABILITY + - HIDDEN_POSTAGE + - DECLARED_VALUE + - SUNDAY_HOLIDAY_DELIVERY + - DELIVERY_CONFIRMATION + - IMPORT_DUTY_CHARGE + - VAT + - NO_SATURDAY_DELIVERY + - INSURANCE + - COD + - FUEL_SURCHARGE + - INSPECTION_CHARGE + - DELIVERY_AREA_SURCHARGE + - WAYBILL_CHARGE + - AMAZON_SPONSORED_DISCOUNT + - INTEGRATOR_SPONSORED_DISCOUNT + - OVERSIZE_SURCHARGE + - CONGESTION_CHARGE + - RESIDENTIAL_SURCHARGE + - ADDITIONAL_SURCHARGE + - SURCHARGE + - REBATE + - HIGH_CUBE_SURCHARGE + - HIGH_LENGTH_SURCHARGE + - HIGH_WIDTH_SURCHARGE + - DEMAND_SURCHARGE + - NONSTANDARD_FEE + - NON_CONVEYABLE_SURCHARGE + - OTP_ON_DELIVERY_FEE + - FBM_SHIP_PLUS_CREDIT + x-docgen-enum-table-extension: + - value: BASE_RATE + description: for Base Rate. + - value: TRANSACTION_FEE + description: Charge for TransAction Fee. + - value: ADULT_SIGNATURE_CONFIRMATION + description: Charge for Adult Signature Confirmation. + - value: SIGNATURE_CONFIRMATION + description: Charge for signature confirmation. + - value: NO_CONFIRMATION + description: Charge for No Confirmation. + - value: WAIVE_SIGNATURE + description: Charge for Waive Signature. + - value: IMPLIED_LIABILITY + description: Charge for Implied Liability. + - value: HIDDEN_POSTAGE + description: Hidden Postage Charge. + - value: DECLARED_VALUE + description: Declared Value Coverage Charge. + - value: SUNDAY_HOLIDAY_DELIVERY + description: Sunday Holiday Delivery Charge. + - value: DELIVERY_CONFIRMATION + description: Charge for Delivery Confirmation. + - value: IMPORT_DUTY_CHARGE + description: Import Duty Charge. + - value: VAT + description: Vat Charge. + - value: NO_SATURDAY_DELIVERY + description: Charge for No Saturday Delivery. + - value: INSURANCE + description: Insurance Charge. + - value: COD + description: COD Charge. + - value: AMAZON_SPONSORED_DISCOUNT + description: Amazon Sponsored Discount. + - value: INTEGRATOR_SPONSORED_DISCOUNT + description: Integrator Sponsored Discount. + - value: FUEL_SURCHARGE + description: Fuel Surcharge. + - value: INSPECTION_CHARGE + description: Inspection Charge. + - value: DELIVERY_AREA_SURCHARGE + description: Delivery Area Surcharge. + - value: WAYBILL_CHARGE + description: WayBill Charge. + - value: OVERSIZE_SURCHARGE + description: OverSize Surcharge. + - value: CONGESTION_CHARGE + description: Congestion Charge. + - value: RESIDENTIAL_SURCHARGE + description: Residential Surcharge. + - value: ADDITIONAL_SURCHARGE + description: Additional Surcharge. + - value: SURCHARGE + description: Surcharge. + - value: REBATE + description: Rebate. + - value: HIGH_CUBE_SURCHARGE + description: High Cube Surcharge. + - value: HIGH_LENGTH_SURCHARGE + description: High Length Surcharge. + - value: HIGH_WIDTH_SURCHARGE + description: High Width Surcharge. + - value: DEMAND_SURCHARGE + description: + A fee that is applied during specific periods of increased demand + on the network as specified in the Amazon Shipping Service guide. + - value: NONSTANDARD_FEE + description: + A fee that applies for packages that are outside the standard + size for processing and sorting as specified in the Amazon Shipping Service + guide. + - value: NON_CONVEYABLE_SURCHARGE + description: + "A handling surcharge which applies to a package, as defined\ + \ in the Non-Conveyable Surcharge section of the Amazon Shipping Service\ + \ guide." + - value: OTP_ON_DELIVERY_FEE + description: + A fee for one-time passcode on delivery value added service as + defined in the Amazon Shipping Service Guide. + - value: FBM_SHIP_PLUS_CREDIT + description: Charge adjustment for FBM Ship plus. + RateItemType: + type: string + description: Type of the rateItem. + enum: + - MANDATORY + - OPTIONAL + - INCLUDED + x-docgen-enum-table-extension: + - value: MANDATORY + description: Rate Item is mandatory. + - value: OPTIONAL + description: Rate Item is Optional. + - value: INCLUDED + description: Rate Item is included. + Benefits: + required: + - excludedBenefits + - includedBenefits + type: object + properties: + includedBenefits: + $ref: "#/components/schemas/IncludedBenefits" + excludedBenefits: + $ref: "#/components/schemas/ExcludedBenefits" + description: + "Benefits that are included and excluded for each shipping offer.\ + \ Benefits represents services provided by Amazon (eg. CLAIMS_PROTECTED, etc.)\ + \ when sellers purchase shipping through Amazon. Benefit details will be made\ + \ available for any shipment placed on or after January 1st 2024 00:00 UTC." + IncludedBenefits: + type: array + description: A list of included benefits. + items: + type: string + ExcludedBenefits: + type: array + description: + A list of excluded benefit. Refer to the ExcludeBenefit object + for further documentation + items: + $ref: "#/components/schemas/ExcludedBenefit" + ExcludedBenefit: + required: + - benefit + type: object + properties: + benefit: + type: string + description: benefit + reasonCodes: + $ref: "#/components/schemas/ExcludedBenefitReasonCodes" + description: + Object representing a benefit that is excluded for a shipping offer + or rate. + ExcludedBenefitReasonCodes: + minItems: 1 + type: array + description: + "List of reasons (eg. LATE_DELIVERY_RISK, etc.) indicating why\ + \ a benefit is excluded for a shipping offer." + items: + type: string + ServiceSelection: + required: + - serviceId + type: object + properties: + serviceId: + $ref: "#/components/schemas/ServiceIds" + description: Service Selection Criteria. + ServiceIds: + type: array + description: A list of ServiceId. + items: + type: string + OneClickShipmentValueAddedServiceDetails: + type: array + description: + This field is deprecated. This field only supports the Collect + on Delivery (COD) value added service. + items: + $ref: "#/components/schemas/OneClickShipmentValueAddedService" + OneClickShipmentValueAddedService: + required: + - id + type: object + properties: + id: + type: string + description: The identifier of the selected value-added service. + amount: + $ref: "#/components/schemas/Currency" + description: A value-added service to be applied to a shipping service purchase. + Service: + required: + - id + - name + type: object + properties: + id: + $ref: "#/components/schemas/ServiceId" + name: + $ref: "#/components/schemas/ServiceName" + description: Service Related Info + Carrier: + required: + - id + - name + type: object + properties: + id: + $ref: "#/components/schemas/CarrierId" + name: + $ref: "#/components/schemas/CarrierName" + description: Carrier Related Info diff --git a/modules/connectors/amazon_shipping/vendor/postman.json b/modules/connectors/amazon_shipping/vendor/postman.json new file mode 100644 index 0000000000..625146f46e --- /dev/null +++ b/modules/connectors/amazon_shipping/vendor/postman.json @@ -0,0 +1,927 @@ +{ + "info": { + "_postman_id": "953497f6-4b88-4873-9267-1edd1d77a1e5", + "name": "Amazon Shipping APIs", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "30249867" + }, + "item": [ + { + "name": "Label Purchase Via Get Rates and Purchase Shipment APIs", + "item": [ + { + "name": "1. Get Access Token", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/x-www-form-urlencoded", + "type": "text" + } + ], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "grant_type", + "value": "refresh_token", + "type": "text" + }, + { + "key": "refresh_token", + "value": "YOURREFRESHTOKENHERE", + "type": "text" + }, + { + "key": "client_id", + "value": "YOURCLIENTIDHERE", + "type": "text" + }, + { + "key": "client_secret", + "value": "YOURCLIENTSECRETHERE", + "type": "text" + } + ] + }, + "url": { + "raw": "{{token-host}}/auth/o2/token", + "host": [ + "{{token-host}}" + ], + "path": [ + "auth", + "o2", + "token" + ] + } + }, + "response": [] + }, + { + "name": "2. Get Shipping Rates - OFF-Amazon", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "x-amz-access-token", + "value": "{{x-amz-access-token}}", + "type": "text" + }, + { + "key": "x-amzn-shipping-business-id", + "value": "BUSINESSIDHERE", + "type": "text", + "description": "Please Refer https://developer-docs.amazon.com/amazon-shipping/docs/shipping-api-v2-reference#x-amzn-shipping-business-id" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"shipTo\": {\n \"name\": \"A3\",\n \"addressLine1\": \"SWA Test Account\",\n \"addressLine2\": \"SWA Test Account\",\n \"addressLine3\": \"SWA Test Account\",\n \"stateOrRegion\": \"\",\n \"postalCode\": \"DN1 1QZ\",\n \"city\": \"Doncaster\",\n \"countryCode\": \"GB\",\n \"email\": \"test+test@amazon.com\",\n \"phoneNumber\": \"444-444-4444\"\n },\n \"shipFrom\": {\n \"name\": \"A1\",\n \"addressLine1\": \"4 Neal Street\",\n \"stateOrRegion\": \"\",\n \"postalCode\": \"WC2H 9QL\",\n \"city\": \"London\",\n \"countryCode\": \"GB\",\n \"email\": \"test+test@amazon.com\",\n \"phoneNumber\": \"444-444-4444\"\n },\n \"packages\": [\n {\n \"dimensions\": {\n \"length\": 3.14,\n \"width\": 3.14,\n \"height\": 3.14,\n \"unit\": \"INCH\"\n },\n \"weight\": {\n \"unit\": \"KILOGRAM\",\n \"value\": 3.14159\n },\n \"items\": [\n {\n \"quantity\": 1,\n \"itemIdentifier\": \"V-02\",\n \"description\": \"Sundries\",\n \"isHazmat\": false,\n \"weight\": {\n \"unit\": \"KILOGRAM\",\n \"value\": 1.14159\n }\n },\n {\n \"quantity\": 1,\n \"itemIdentifier\": \"V-01\",\n \"description\": \"Sundries\",\n \"isHazmat\": false,\n \"weight\": {\n \"unit\": \"KILOGRAM\",\n \"value\": 1.14159\n }\n }\n ],\n \"insuredValue\": {\n \"unit\": \"GBP\",\n \"value\": 29.98\n },\n \"packageClientReferenceId\": \"abcd\",\n \"sellerDisplayName\": \"A TEST Seller\"\n }\n ],\n \"channelDetails\": {\n \"channelType\": \"EXTERNAL\"\n }\n}\n" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/shipments/rates", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "shipments", + "rates" + ] + } + }, + "response": [] + }, + { + "name": "2. Get Shipping Rates - ON-Amazon", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "x-amz-access-token", + "value": "{{x-amz-access-token}}", + "type": "text" + }, + { + "key": "x-amzn-shipping-business-id", + "value": "BUSINESSIDHERE", + "type": "text", + "description": "Please Refer https://developer-docs.amazon.com/amazon-shipping/docs/shipping-api-v2-reference#x-amzn-shipping-business-id" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"shipTo\": {\n \"name\": \"A3\",\n \"addressLine1\": \"SWA Test Account\",\n \"addressLine2\": \"SWA Test Account\",\n \"addressLine3\": \"SWA Test Account\",\n \"stateOrRegion\": \"\",\n \"postalCode\": \"DN1 1QZ\",\n \"city\": \"Doncaster\",\n \"countryCode\": \"GB\",\n \"email\": \"test+test@amazon.com\",\n \"phoneNumber\": \"444-444-4444\"\n },\n \"shipFrom\": {\n \"name\": \"A1\",\n \"addressLine1\": \"4 Neal Street\",\n \"stateOrRegion\": \"\",\n \"postalCode\": \"WC2H 9QL\",\n \"city\": \"London\",\n \"countryCode\": \"GB\",\n \"email\": \"test+test@amazon.com\",\n \"phoneNumber\": \"444-444-4444\"\n },\n \"packages\": [\n {\n \"dimensions\": {\n \"length\": 3.14,\n \"width\": 3.14,\n \"height\": 3.14,\n \"unit\": \"INCH\"\n },\n \"weight\": {\n \"unit\": \"KILOGRAM\",\n \"value\": 3.14159\n },\n \"items\": [\n {\n \"quantity\": 1,\n \"itemIdentifier\": \"V-02\",\n \"description\": \"Sundries\",\n \"isHazmat\": false,\n \"weight\": {\n \"unit\": \"KILOGRAM\",\n \"value\": 1.14159\n }\n },\n {\n \"quantity\": 1,\n \"itemIdentifier\": \"V-01\",\n \"description\": \"Sundries\",\n \"isHazmat\": false,\n \"weight\": {\n \"unit\": \"KILOGRAM\",\n \"value\": 1.14159\n }\n }\n ],\n \"insuredValue\": {\n \"unit\": \"GBP\",\n \"value\": 29.98\n },\n \"packageClientReferenceId\": \"abcd\"\n }\n ],\n \"channelDetails\": {\n \"channelType\": \"AMAZON\",\n \"amazonOrderDetails\": {\n \"orderId\": \"113-3080243-4028244\"\n }\n }\n}\n" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/shipments/rates", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "shipments", + "rates" + ] + } + }, + "response": [] + }, + { + "name": "3. Purchase Shipment", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "x-amz-access-token", + "type": "text", + "value": "{{x-amz-access-token}}" + }, + { + "key": "x-amzn-shipping-business-id", + "type": "text", + "value": "BUSINESSIDHERE", + "description": "Please Refer https://developer-docs.amazon.com/amazon-shipping/docs/shipping-api-v2-reference#x-amzn-shipping-business-id" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"requestToken\": \"REQUEST TOKEN VALUE FROM GETRATES\",\n\t\"rateId\": \"RATEID VALUE FROM GETRATES\",\n\t\"requestedDocumentSpecification\": {\n\t\t\"format\": \"PNG\",\n\t\t\"size\": {\n\t\t\t\"width\": 4,\n\t\t\t\"length\": 6,\n\t\t\t\"unit\": \"INCH\"\n\t\t},\n\t\t\"dpi\": 300,\n\t\t\"pageLayout\": \"DEFAULT\",\n\t\t\"needFileJoining\": false,\n\t\t\"requestedDocumentTypes\": [\n\t\t\t\"LABEL\"\n\t\t]\n\t}\n}" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/shipments", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "shipments" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Label Purchase With Deliver To Counter Via Get Rates and Purchase Shipment APIs", + "item": [ + { + "name": "1. Get Access Token", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/x-www-form-urlencoded", + "type": "text" + } + ], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "grant_type", + "value": "refresh_token", + "type": "text" + }, + { + "key": "refresh_token", + "value": "YOURREFRESHTOKENHERE", + "type": "text" + }, + { + "key": "client_id", + "value": "YOURCLIENTIDHERE", + "type": "text" + }, + { + "key": "client_secret", + "value": "YOURCLIENTSECRETHERE", + "type": "text" + } + ] + }, + "url": { + "raw": "{{token-host}}/auth/o2/token", + "host": [ + "{{token-host}}" + ], + "path": [ + "auth", + "o2", + "token" + ] + } + }, + "response": [] + }, + { + "name": "2. Access Points API", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "x-amz-access-token", + "value": "{{x-amz-access-token}}", + "type": "text" + }, + { + "key": "x-amzn-shipping-business-id", + "value": "AmazonShipping_UK", + "type": "text", + "description": "Works only with UK Market for now" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/accessPoints?accessPointTypes=HELIX&countryCode=GB&postalCode=DESTINATIONPOSTCODEHERE", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "accessPoints" + ], + "query": [ + { + "key": "accessPointTypes", + "value": "HELIX" + }, + { + "key": "countryCode", + "value": "GB", + "description": "Works only with GB Market for now" + }, + { + "key": "postalCode", + "value": "DESTINATIONPOSTCODEHERE" + } + ] + } + }, + "response": [] + }, + { + "name": "3. Get Shipping Rates - OFF-Amazon With AccessPointId", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "x-amz-access-token", + "type": "text", + "value": "{{x-amz-access-token}}" + }, + { + "key": "x-amzn-shipping-business-id", + "type": "text", + "value": "BUSINESSIDHERE", + "description": "Please Refer https://developer-docs.amazon.com/amazon-shipping/docs/shipping-api-v2-reference#x-amzn-shipping-business-id" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"shipTo\": {\n \"name\": \"A3\",\n \"addressLine1\": \"SWA Test Account\",\n \"addressLine2\": \"SWA Test Account\",\n \"addressLine3\": \"SWA Test Account\",\n \"stateOrRegion\": \"\",\n \"postalCode\": \"DN1 1QZ\",\n \"city\": \"Doncaster\",\n \"countryCode\": \"GB\",\n \"email\": \"test+test@amazon.com\",\n \"phoneNumber\": \"444-444-4444\"\n },\n \"shipFrom\": {\n \"name\": \"A1\",\n \"addressLine1\": \"4 Neal Street\",\n \"stateOrRegion\": \"\",\n \"postalCode\": \"WC2H 9QL\",\n \"city\": \"London\",\n \"countryCode\": \"GB\",\n \"email\": \"test+test@amazon.com\",\n \"phoneNumber\": \"444-444-4444\"\n },\n \"packages\": [\n {\n \"dimensions\": {\n \"length\": 3.14,\n \"width\": 3.14,\n \"height\": 3.14,\n \"unit\": \"INCH\"\n },\n \"weight\": {\n \"unit\": \"KILOGRAM\",\n \"value\": 3.14159\n },\n \"items\": [\n {\n \"quantity\": 1,\n \"itemIdentifier\": \"V-02\",\n \"description\": \"Sundries\",\n \"isHazmat\": false,\n \"weight\": {\n \"unit\": \"KILOGRAM\",\n \"value\": 1.14159\n }\n },\n {\n \"quantity\": 1,\n \"itemIdentifier\": \"V-01\",\n \"description\": \"Sundries\",\n \"isHazmat\": false,\n \"weight\": {\n \"unit\": \"KILOGRAM\",\n \"value\": 1.14159\n }\n }\n ],\n \"insuredValue\": {\n \"unit\": \"GBP\",\n \"value\": 29.98\n },\n \"packageClientReferenceId\": \"abcd\",\n \"sellerDisplayName\": \"A TEST Seller\"\n }\n ],\n \"channelDetails\": {\n \"channelType\": \"EXTERNAL\"\n },\n \"destinationAccessPointDetails\": {\n \"accessPointId\": \"30df9f2a-5da6-4719-9393-491db2082f77\"\n }\n}" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/shipments/rates", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "shipments", + "rates" + ] + } + }, + "response": [] + }, + { + "name": "4. Purchase Shipment", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + }, + { + "key": "x-amz-access-token", + "type": "text", + "value": "{{x-amz-access-token}}" + }, + { + "key": "x-amzn-shipping-business-id", + "type": "text", + "value": "BUSINESSIDHERE", + "description": "Please Refer https://developer-docs.amazon.com/amazon-shipping/docs/shipping-api-v2-reference#x-amzn-shipping-business-id" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"requestToken\": \"REQUEST TOKEN VALUE FROM GETRATES\",\n\t\"rateId\": \"RATEID VALUE FROM GETRATES\",\n\t\"requestedDocumentSpecification\": {\n\t\t\"format\": \"PNG\",\n\t\t\"size\": {\n\t\t\t\"width\": 4,\n\t\t\t\"length\": 6,\n\t\t\t\"unit\": \"INCH\"\n\t\t},\n\t\t\"dpi\": 300,\n\t\t\"pageLayout\": \"DEFAULT\",\n\t\t\"needFileJoining\": false,\n\t\t\"requestedDocumentTypes\": [\n\t\t\t\"LABEL\"\n\t\t]\n\t}\n}" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/shipments", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "shipments" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Label Purchase Via Smart Purchase API", + "item": [ + { + "name": "1. Get Access Token", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/x-www-form-urlencoded", + "type": "text" + } + ], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "grant_type", + "value": "refresh_token", + "type": "text" + }, + { + "key": "refresh_token", + "value": "YOURREFRESHTOKENHERE", + "type": "text" + }, + { + "key": "client_id", + "value": "YOURCLIENTIDHERE", + "type": "text" + }, + { + "key": "client_secret", + "value": "YOURCLIENTSECRETHERE", + "type": "text" + } + ] + }, + "url": { + "raw": "{{token-host}}/auth/o2/token", + "host": [ + "{{token-host}}" + ], + "path": [ + "auth", + "o2", + "token" + ] + } + }, + "response": [] + }, + { + "name": "2. Smart Purchase Shipment - OFF-Amazon", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "x-amz-access-token", + "value": "{{x-amz-access-token}}", + "type": "text" + }, + { + "key": "x-amzn-shipping-business-id", + "value": "BUSINESSIDHERE", + "type": "text", + "description": "Please Refer https://developer-docs.amazon.com/amazon-shipping/docs/shipping-api-v2-reference#x-amzn-shipping-business-id" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"channelDetails\": {\r\n \"channelType\": \"EXTERNAL\"\r\n },\r\n \"labelSpecifications\": {\r\n \"dpi\": 300,\r\n \"format\": \"PNG\",\r\n \"needFileJoining\": false,\r\n \"pageLayout\": \"DEFAULT\",\r\n \"requestedDocumentTypes\": [\r\n \"LABEL\"\r\n ],\r\n \"size\": {\r\n \"length\": 6.0,\r\n \"unit\": \"INCH\",\r\n \"width\": 4.0\r\n }\r\n },\r\n \"packages\": [\r\n {\r\n \"dimensions\": {\r\n \"length\": 15,\r\n \"width\": 10,\r\n \"height\": 10,\r\n \"unit\": \"CENTIMETER\"\r\n },\r\n \"insuredValue\": {\r\n \"value\": 0.01,\r\n \"unit\": \"GBP\"\r\n },\r\n \"isHazmat\": false,\r\n \"items\": [\r\n {\r\n \"itemValue\": {\r\n \"value\": 0.01,\r\n \"unit\": \"GBP\"\r\n },\r\n \"description\": \"Item\",\r\n \"itemIdentifier\": \"20231003140431o0tyegwsn3o\",\r\n \"quantity\": 1,\r\n \"weight\": {\r\n \"unit\": \"GRAM\",\r\n \"value\": 1900\r\n },\r\n \"isHazmat\": false\r\n }\r\n ],\r\n \"packageClientReferenceId\": \"20231003140431gkuelozg2ys\",\r\n \"weight\": {\r\n \"unit\": \"GRAM\",\r\n \"value\": 1900\r\n }\r\n }\r\n ],\r\n \"serviceSelection\": {\r\n \"serviceId\": [\r\n \"SWA-UK-2D\"\r\n ]\r\n },\r\n \"shipTo\": {\r\n \"name\": \"A3\",\r\n \"addressLine1\": \"SWA Test Account\",\r\n \"addressLine2\": \"SWA Test Account\",\r\n \"addressLine3\": \"SWA Test Account\",\r\n \"stateOrRegion\": \"\",\r\n \"postalCode\": \"DN1 1QZ\",\r\n \"city\": \"Doncaster\",\r\n \"countryCode\": \"GB\",\r\n \"email\": \"test+test@amazon.com\",\r\n \"phoneNumber\": \"444-444-4444\"\r\n },\r\n \"shipFrom\": {\r\n \"name\": \"A1\",\r\n \"addressLine1\": \"4 Neal Street\",\r\n \"stateOrRegion\": \"\",\r\n \"postalCode\": \"WC2H 9QL\",\r\n \"city\": \"London\",\r\n \"countryCode\": \"GB\",\r\n \"email\": \"test+test@amazon.com\",\r\n \"phoneNumber\": \"444-444-4444\"\r\n }\r\n}" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/oneClickShipment", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "oneClickShipment" + ] + } + }, + "response": [] + }, + { + "name": "2. Smart Purchase Shipment - ON-Amazon", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "x-amz-access-token", + "value": "{{x-amz-access-token}}", + "type": "text" + }, + { + "key": "x-amzn-shipping-business-id", + "value": "BUSINESSIDHERE", + "type": "text", + "description": "Please Refer https://developer-docs.amazon.com/amazon-shipping/docs/shipping-api-v2-reference#x-amzn-shipping-business-id" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"channelDetails\": {\r\n \"channelType\": \"AMAZON\",\r\n \"amazonOrderDetails\": {\r\n \"orderId\": \"113-3080243-4028244\"\r\n }\r\n },\r\n \"labelSpecifications\": {\r\n \"dpi\": 300,\r\n \"format\": \"PNG\",\r\n \"needFileJoining\": false,\r\n \"pageLayout\": \"DEFAULT\",\r\n \"requestedDocumentTypes\": [\r\n \"LABEL\"\r\n ],\r\n \"size\": {\r\n \"length\": 6.0,\r\n \"unit\": \"INCH\",\r\n \"width\": 4.0\r\n }\r\n },\r\n \"packages\": [\r\n {\r\n \"dimensions\": {\r\n \"length\": 15,\r\n \"width\": 10,\r\n \"height\": 10,\r\n \"unit\": \"CENTIMETER\"\r\n },\r\n \"insuredValue\": {\r\n \"value\": 0.01,\r\n \"unit\": \"GBP\"\r\n },\r\n \"isHazmat\": false,\r\n \"items\": [\r\n {\r\n \"itemValue\": {\r\n \"value\": 0.01,\r\n \"unit\": \"GBP\"\r\n },\r\n \"description\": \"Item\",\r\n \"itemIdentifier\": \"20231003140431o0tyegwsn3o\",\r\n \"quantity\": 1,\r\n \"weight\": {\r\n \"unit\": \"GRAM\",\r\n \"value\": 1900\r\n },\r\n \"isHazmat\": false\r\n }\r\n ],\r\n \"packageClientReferenceId\": \"20231003140431gkuelozg2ys\",\r\n \"weight\": {\r\n \"unit\": \"GRAM\",\r\n \"value\": 1900\r\n }\r\n }\r\n ],\r\n \"serviceSelection\": {\r\n \"serviceId\": [\r\n \"SWA-UK-2D\"\r\n ]\r\n },\r\n \"shipTo\": {\r\n \"name\": \"A3\",\r\n \"addressLine1\": \"SWA Test Account\",\r\n \"addressLine2\": \"SWA Test Account\",\r\n \"addressLine3\": \"SWA Test Account\",\r\n \"stateOrRegion\": \"\",\r\n \"postalCode\": \"DN1 1QZ\",\r\n \"city\": \"Doncaster\",\r\n \"countryCode\": \"GB\",\r\n \"email\": \"test+test@amazon.com\",\r\n \"phoneNumber\": \"444-444-4444\"\r\n },\r\n \"shipFrom\": {\r\n \"name\": \"A1\",\r\n \"addressLine1\": \"4 Neal Street\",\r\n \"stateOrRegion\": \"\",\r\n \"postalCode\": \"WC2H 9QL\",\r\n \"city\": \"London\",\r\n \"countryCode\": \"GB\",\r\n \"email\": \"test+test@amazon.com\",\r\n \"phoneNumber\": \"444-444-4444\"\r\n }\r\n}" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/oneClickShipment", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "oneClickShipment" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Label Purchase With Deliver To Counter Via Smart Purchase API", + "item": [ + { + "name": "1. Get Access Token", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/x-www-form-urlencoded", + "type": "text" + } + ], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "grant_type", + "value": "refresh_token", + "type": "text" + }, + { + "key": "refresh_token", + "value": "YOURREFRESHTOKENHERE", + "type": "text" + }, + { + "key": "client_id", + "value": "YOURCLIENTIDHERE", + "type": "text" + }, + { + "key": "client_secret", + "value": "YOURCLIENTSECRETHERE", + "type": "text" + } + ] + }, + "url": { + "raw": "{{token-host}}/auth/o2/token", + "host": [ + "{{token-host}}" + ], + "path": [ + "auth", + "o2", + "token" + ] + } + }, + "response": [] + }, + { + "name": "2. Access Points API", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "x-amz-access-token", + "value": "{{x-amz-access-token}}", + "type": "text" + }, + { + "key": "x-amzn-shipping-business-id", + "value": "AmazonShipping_UK", + "type": "text", + "description": "Works only with UK Market for now" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/accessPoints?accessPointTypes=HELIX&countryCode=GB&postalCode=DESTINATIONPOSTCODEHERE", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "accessPoints" + ], + "query": [ + { + "key": "accessPointTypes", + "value": "HELIX" + }, + { + "key": "countryCode", + "value": "GB", + "description": "Works only with GB Market for now" + }, + { + "key": "postalCode", + "value": "DESTINATIONPOSTCODEHERE" + } + ] + } + }, + "response": [] + }, + { + "name": "2. Smart Purchase Shipment - OFF-Amazon", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "x-amz-access-token", + "value": "{{x-amz-access-token}}", + "type": "text" + }, + { + "key": "x-amzn-shipping-business-id", + "value": "AmazonShipping_UK", + "type": "text", + "description": "Please Refer https://developer-docs.amazon.com/amazon-shipping/docs/shipping-api-v2-reference#x-amzn-shipping-business-id" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"channelDetails\": {\r\n \"channelType\": \"EXTERNAL\"\r\n },\r\n \"labelSpecifications\": {\r\n \"dpi\": 300,\r\n \"format\": \"PNG\",\r\n \"needFileJoining\": false,\r\n \"pageLayout\": \"DEFAULT\",\r\n \"requestedDocumentTypes\": [\r\n \"LABEL\"\r\n ],\r\n \"size\": {\r\n \"length\": 6.0,\r\n \"unit\": \"INCH\",\r\n \"width\": 4.0\r\n }\r\n },\r\n \"packages\": [\r\n {\r\n \"dimensions\": {\r\n \"length\": 15,\r\n \"width\": 10,\r\n \"height\": 10,\r\n \"unit\": \"CENTIMETER\"\r\n },\r\n \"insuredValue\": {\r\n \"value\": 0.01,\r\n \"unit\": \"GBP\"\r\n },\r\n \"isHazmat\": false,\r\n \"items\": [\r\n {\r\n \"itemValue\": {\r\n \"value\": 0.01,\r\n \"unit\": \"GBP\"\r\n },\r\n \"description\": \"Item\",\r\n \"itemIdentifier\": \"20231003140431o0tyegwsn3o\",\r\n \"quantity\": 1,\r\n \"weight\": {\r\n \"unit\": \"GRAM\",\r\n \"value\": 1900\r\n },\r\n \"isHazmat\": false\r\n }\r\n ],\r\n \"packageClientReferenceId\": \"20231003140431gkuelozg2ys\",\r\n \"weight\": {\r\n \"unit\": \"GRAM\",\r\n \"value\": 1900\r\n }\r\n }\r\n ],\r\n \"serviceSelection\": {\r\n \"serviceId\": [\r\n \"SWA-UK-2D\"\r\n ]\r\n },\r\n \"shipTo\": {\r\n \"name\": \"A3\",\r\n \"addressLine1\": \"SWA Test Account\",\r\n \"addressLine2\": \"SWA Test Account\",\r\n \"addressLine3\": \"SWA Test Account\",\r\n \"stateOrRegion\": \"\",\r\n \"postalCode\": \"DN1 1QZ\",\r\n \"city\": \"Doncaster\",\r\n \"countryCode\": \"GB\",\r\n \"email\": \"test+test@amazon.com\",\r\n \"phoneNumber\": \"444-444-4444\"\r\n },\r\n \"shipFrom\": {\r\n \"name\": \"A1\",\r\n \"addressLine1\": \"4 Neal Street\",\r\n \"stateOrRegion\": \"\",\r\n \"postalCode\": \"WC2H 9QL\",\r\n \"city\": \"London\",\r\n \"countryCode\": \"GB\",\r\n \"email\": \"test+test@amazon.com\",\r\n \"phoneNumber\": \"444-444-4444\"\r\n },\r\n \"destinationAccessPointDetails\": {\r\n \"accessPointId\": \"30df9f2a-5da6-4719-9393-491db2082f77\"\r\n }\r\n}" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/oneClickShipment", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "oneClickShipment" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Other Important APIs", + "item": [ + { + "name": "Get Shipment Tracking", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "x-amz-access-token", + "type": "text", + "value": "{{x-amz-access-token}}" + }, + { + "key": "Accept", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/tracking?carrierId=CARRIERIDHERE&trackingId=TRACKINGIDHERE", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "tracking" + ], + "query": [ + { + "key": "carrierId", + "value": "CARRIERIDHERE" + }, + { + "key": "trackingId", + "value": "TRACKINGIDHERE" + } + ] + } + }, + "response": [] + }, + { + "name": "Get Shipment Documents", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "x-amz-access-token", + "value": "{{x-amz-access-token}}", + "type": "text" + }, + { + "key": "x-amzn-shipping-business-id", + "value": "BUSINESSIDHERE", + "type": "text", + "description": "Please Refer https://developer-docs.amazon.com/amazon-shipping/docs/shipping-api-v2-reference#x-amzn-shipping-business-id" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/shipments/SHIPMENTID/documents?packageClientReferenceId=CLIENTREFID&format=DOCFORMAT", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "shipments", + "SHIPMENTID", + "documents" + ], + "query": [ + { + "key": "packageClientReferenceId", + "value": "CLIENTREFID" + }, + { + "key": "format", + "value": "DOCFORMAT" + } + ] + } + }, + "response": [] + }, + { + "name": "Cancel Shipment", + "request": { + "auth": { + "type": "noauth" + }, + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "x-amz-access-token", + "value": "{{x-amz-access-token}}", + "type": "text" + }, + { + "key": "x-amzn-shipping-business-id", + "value": "BUSINESSIDHERE", + "type": "text", + "description": "Please Refer https://developer-docs.amazon.com/amazon-shipping/docs/shipping-api-v2-reference#x-amzn-shipping-business-id" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "{{api-host}}/shipping/v2/shipments/SHIPMENTIDHERE/cancel", + "host": [ + "{{api-host}}" + ], + "path": [ + "shipping", + "v2", + "shipments", + "SHIPMENTIDHERE", + "cancel" + ] + } + }, + "response": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/modules/connectors/amazon_shipping/vendor/shippingV2.json b/modules/connectors/amazon_shipping/vendor/shippingV2.json new file mode 100644 index 0000000000..3385cb09e4 --- /dev/null +++ b/modules/connectors/amazon_shipping/vendor/shippingV2.json @@ -0,0 +1,10496 @@ +{ + "swagger": "2.0", + "info": { + "description": "The Amazon Shipping API is designed to support outbound shipping use cases both for orders originating on Amazon-owned marketplaces as well as external channels/marketplaces. With these APIs, you can request shipping rates, create shipments, cancel shipments, and track shipments.", + "version": "v2", + "title": "Amazon Shipping API", + "contact": { + "name": "Amazon Shipping API Support", + "email": "swa-api-core@amazon.com" + }, + "license": { + "name": "Amazon Software License", + "url": "https://aws.amazon.com/asl/" + } + }, + "host": "sellingpartnerapi-eu.amazon.com", + "schemes": [ + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/shipping/v2/shipments/rates": { + "post": { + "tags": [ + "shipping" + ], + "description": "Returns the available shipping service offerings.\n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "getRates", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "GetRatesRequest body", + "required": true, + "schema": { + "$ref": "#/definitions/GetRatesRequest" + } + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetRatesResponse" + }, + "examples": { + "application/json": { + "requestToken": "6DCCEDD3FF961C15FEB94F342D41C", + "rates": [ + { + "rateId": "F4B68849F969E239FF9FCA9C12E35", + "carrierId": "FOOSHIPGRD", + "carrierName": "FOO SHIP GRD", + "billedWeight": { + "value": 5, + "unit": "GRAMS" + }, + "totalCharge": { + "value": 7, + "unit": "USD" + }, + "serviceId": "FOORSID", + "serviceName": "FOO RS ID", + "promise": { + "deliveryWindow": { + "start": "2018-08-24T08:22:30.737Z", + "end": "2018-08-24T20:22:30.737Z" + }, + "pickupWindow": { + "start": "2018-08-23T08:22:30.737Z", + "end": "2018-08-23T20:22:30.737Z" + } + }, + "supportedDocumentSpecifications": [ + { + "format": "PNG", + "size": { + "length": 6, + "width": 4, + "unit": "INCH" + }, + "printOptions": [ + { + "supportedDPIs": [ + 300, + 203 + ], + "supportedPageLayouts": [ + "LEFT", + "RIGHT" + ], + "supportedFileJoiningOptions": [ + true, + false + ], + "supportedDocumentDetails": [ + { + "name": "LABEL", + "isMandatory": true + } + ] + } + ] + }, + { + "format": "ZPL", + "size": { + "length": 6, + "width": 4, + "unit": "INCH" + }, + "printOptions": [ + { + "supportedDPIs": [ + 300, + 203 + ], + "supportedPageLayouts": [ + "LEFT", + "RIGHT" + ], + "supportedFileJoiningOptions": [ + true, + false + ], + "supportedDocumentDetails": [ + { + "name": "LABEL", + "isMandatory": true + } + ] + } + ] + } + ], + "availableValueAddedServiceGroups": [ + { + "groupId": "SIG_VERIFICATION", + "groupDescription": "Signature Verification", + "isRequired": true, + "valueAddedServices": [ + { + "id": "CUST_SIG_VERIFICATION", + "name": "Customer Signature Verification", + "cost": { + "unit": "USD", + "value": 2 + } + } + ] + } + ], + "requiresAdditionalInputs": false, + "rateItemList": [ + { + "rateItemCharge": { + "unit": "INR", + "value": "2.00000" + }, + "rateItemID": "BASE_RATE", + "rateItemNameLocalization": "Base Rate", + "rateItemType": "MANDATORY" + }, + { + "rateItemCharge": { + "unit": "INR", + "value": "1.20000" + }, + "rateItemID": "DELIVERY_CONFIRMATION", + "rateItemNameLocalization": "Delivery Confirmation", + "rateItemType": "INCLUDED" + }, + { + "rateItemCharge": { + "unit": "INR", + "value": "2.00" + }, + "rateItemID": "VAT", + "rateItemNameLocalization": "VAT", + "rateItemType": "MANDATORY" + } + ], + "paymentType": "PAY_THROUGH_AMAZON", + "benefits": { + "includedBenefits": [], + "excludedBenefits": [ + { + "benefit": "CLAIMS_PROTECTED", + "reasonCodes": [ + "LATE_DELIVERY_RISK" + ] + } + ] + } + } + ], + "ineligibleRates": [ + { + "carrierId": "FOOSTDGRD", + "serviceId": "FOO8420430", + "carrierName": "FOOSTDGRD", + "serviceName": "FOO8420430", + "ineligibilityReasons": [ + { + "code": "NO_COVERAGE", + "message": "Required shipping network coverage doesn't exist for the offering" + } + ] + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "ChannelDetails object cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + }, + "x-amzn-api-sandbox": { + "dynamic": {} + } + }, + "parameters": [] + }, + "/shipping/v2/shipments/directPurchase": { + "post": { + "tags": [ + "shipping" + ], + "description": "Purchases the shipping service for a shipment using the best fit service offering. Returns purchase related details and documents.\n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "directPurchaseShipment", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "DirectPurchaseRequest body", + "required": true, + "schema": { + "$ref": "#/definitions/DirectPurchaseRequest" + } + }, + { + "name": "x-amzn-IdempotencyKey", + "description": "A unique value which the server uses to recognize subsequent retries of the same request.", + "in": "header", + "required": false, + "type": "string" + }, + { + "name": "locale", + "description": "The IETF Language Tag. Note that this only supports the primary language subtag with one secondary language subtag (i.e. en-US, fr-CA).\nThe secondary language subtag is almost always a regional designation.\nThis does not support additional subtags beyond the primary and secondary language subtags.\n", + "in": "header", + "required": false, + "type": "string" + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/DirectPurchaseResponse" + }, + "examples": { + "application/json": { + "shipmentId": "445454-3232-3232", + "packageDocumentDetailList": [ + { + "packageClientReferenceId": "ASUSDI-45343854", + "trackingId": "T1234567", + "packageDocuments": [ + { + "type": "LABEL", + "format": "PNG", + "contents": "iVBORw0KGgoAAAANSUhEUgAAAywAAATCCAMAAABouZLTAAAABlBMVEUAAAD///+l2Z/dAAAACXBIWXMAAB84AAAfOAGTPyf1AAAfLklEQVR42u3dCXajShZAQb2u2v+WX3+XNTBkMgkQoIhz+ndZlsCWucpEA0TcgCn+5y4AsYBYQCwgFhALiAUQC4gFxAJiAbGAWEAsgFhALCAWEAuIBcQCiAXEAmIBsYBYQCwgFkAsIBYQC4gFxAJiAbEAYgGxgFhALCAWEAsgFhALiAXEAmIBsYBYALGAWEAsIBYQC1zaX3fBeaW7YCVhZAHTMDANY4vpw6cninGGH9LIAqZhIBYQC4gFxAKIBcQCYgGxgFhALCAWQCwgFhALiAXEAmIB9vkM/s9nnGPuDTq3KFw0/4doLODt5SGWFft4bI358/85Z8OM/jEEnhflwm28s8xw0C0OEku8Nur0CL6rvDUeqCrXiCmjf94aS8qxha71w0f1Qbd2te4kofOgeLJpmBnPrqnct+0s3uc5XFhh8P1dSmz/mJe1X+aWnd8le9fJ3GMLO90OfoTuxh+Worj1ZdZaiQmL3fR+L/1ozz6j/th7v07scixbB9m7aDJZaCBuy7epjM0Tz0orE0ejjLPHku1f+zGwZ2ucb06YC89aFean2Xr+4HlXx6256NcfIIbm5r3Lvm4va8pmtvs9ku+Vd8ZYor3pxXPrbXSSjT9YtobU3yfSGvPR1u0fs+loXbOxzMfFr60h+3uRvXWcfxJ2rEFjhVaisnufrX2qzX+PA0zD8reO1zZ/n0Q8voqcdNe2rpnN0SeetWR/tJ++DrqbcuMpgE2eLTtaxAfYwY9bc5YWE4fkZw1ZeqIkynd1DAzxeb3nEKb9GvMfkH92xuP3OajmBRG3zP2TXWXmdvod/On7DtVrRm3f9+r7J9O3m/FWMor7eJHd3cYVB+gs78L2Z9F7+nvav/l9cpUxfM1ccbs6VyprbVKjNeXWf/6j7FAePJZ485o54Sn4uGAo03+tCSlk1NvYdKc6F75+stVZYU78ruMo/CEXT+SuI+J3n2va7sOkjT2nb6Dr7+HH3MHr/vtvsP90uFiysA8/eu3lL+DGNU9jOnsLW6uWDeYVnd9l0pxsm1z+95keKr9GzLrjY9lYOzrcZOa31JKxQi1xxLttixnF/z7xSwy+BPjfff74E76eHp48QOTguJH9Vqav44K15L97O++beXlrj8KA3/ni9U6to92J6/48f2LLHzIKL1ZFv/t4Xiua343nFDj6Edyv0B53e2uM5/caT222X0R73fKEO/2x7Psx4/pR/Fb70ojBRcfC3yiKk46Yc4NY53789XfjP2IMzkQL14ry7WPyPRu1lUXt54odRvAzj1FZuCDb7+irvUa83kqnXOkL3u7CAQamoY9+R3Yq+FdLdX5wXWK5liy+p/rNx9xuLb3PYsXmtUT1B8voxL7dS5iO7nLZalacl7R38ncZQSqfvZlZQoiFvTeVmFxJrP7DD84Q62veIOk/9mavlkMUtrDobEIxY6rT/F71A74xtFVPfPdz58qNjwvXm3hdqfB6wPT3E0y7ok+0X2oH5dZ/b1hWRoYYuE372q1Xq6K0nOr70Sa/Syurg0rnk7IxYcI5871hKZZvjWWX3aGYuFGu/ZbGTX7jybHYZ2HJUwcb7D4f398v+ePG+GXj3+L22bcFffiDLX8/cF/v/AuXjtQ6cPRWB3Z962HoykzDmPvYc7u/Y3L/Vj68h32YaVhudXQFNhmpv3G2+nf/u/qNibAJ0nEGly9kGgYfHVnah2VtHFC1fNjWzjFeO0dg7V3t8ejWW1gUDgdbGJkq77Wdfc4lxLLutDZ/9sryVjq88fO9rJ1jvJaOwPo8MGs0N/rszaG7h4PttlI9mOu1juLKyfZZ2sdKzd6hIZqHba3cPCcfXrWx+KHlVg/m6iiufHKfJXpzn+J74IZuHpN37hvXnnwci+LhMeTCp3bw353VrPoaZlQ/5Oxcfnx0GrbqswXbL8uQwvljWfM9qwMH1TOy8OFp2MGGKEVw3Vh2OrxqmIbx8Vg2ezTPlY5/aLjhALHkjE1xcFfidXjVaLyBb/4LiEMHc73qUVxZ0YYHrIj2qYULB7eJxkFUm/vY7SOwNo+VEKWr9xYfrQV0V1I4mGtzNZucHXHD6aOfcrefcKtPCJjd7PKMRRz+JzxBLD6DD2fbwYfL2PAt+t7Bi1iust8JpmEgFhALiAXEAmIBxAJiAbGAWOB0nIAVvEUfTMPgM+OPaRgYWUAsIBYQC4gFxAKIBcQCYgGxgFhALCAWQCwgFhALiAXEAmIBxAJiAbGAWEAsIBYQCyAWEAuIBcQCYgGxgFgAsYBYQCwgFhALiAUQC4gFxAJiAbGAWEAsgFhALCAWEAuIBcQCiAXEAmIBsYBYQCwgFkAsIBYQC4gFxAJiAbEAYgGxgFhALCAWEAsgFhALiAXEAmIBsYBYALHA0WJJdyrX9HfrFfy2E+5pTi9i/Tii9WX0LwTTsFKN7mPEUp11ZakYuzKIZXgkMbAglsrAEkYRxAJi2YFnwxBLfxZW2pvXClew+YuSv+FohfNb9UXJjMpIYmjBNGxqkp4hw8jSm3C98jC2YJ9laAB5pJHSwDQMxLLW7r19FMTy7n4MiKU7sPSHFvv3iGXa0KIVLmCtp47br9Pnc3xp/hPEAqZhgFhALCAWEAuIBcQCYgHEAmIBsYBY4Iy2O27Y8/MsUbw8Zi2jyVs/uUIszRDi31evLfvx4bC49Y9mUT+8Re9IZKt99NIhNfhgLPn4BEvec/GZYuyzDLVSmSblwJiQG31UPwe/94mUPXyIpT1nipGtY6cJUB5uy0ytiKWWRbx161vxc5wrfrTTpsvnRpaFG+cndrXt3vPRHfyhEWXvR/F8/Te2Xku0/53l+2Cfn4izxVJ6CH89NdZ9UP85D+WCgyN3t8ns7jcNHX+pss7edl654LWWbDxCZPfCVhnhiFBiGZrbbPfk8eNp6uy8EvO6YMkyW898Fy+4b/TPS5q/4fPfkY2uUiD2WUpzrxx6JB+ai809OHLjaersXjDjB26u85FZVC/4t5K4P8mQU37EcKQpscyvZfVd7fbT1DMnOOV19p757lyQ3TnVyDAVSx4G+IpYHtOgkRFjjeehsrAbscq++npPSRhTxDL+aF965S3rG+C9ndUegKdspOV1RncRYcNns1juW1Xl5cmNX0187+XxCQNLdmZoZlffZu2njv89b7pkpjXz2ePs3XrB807hdH58MJbf7fa1Cfa2x87WuXRjLT9JPS0XgXCQWDq1NPcRSg/kudJWHLfpuaRyOEYsc16KzBWf1XrkEjuuEzv468rbTltn9fkFOEMs3efB6jsw059gql9zwpO85XX2FtlfR+sSb/MSyzvjR04KZ6PBy3DCWUeWWHijXPuak5c0YWgZmWYKViwblrP8mah4bZuZnZ2hGFxtdZ3NRdYv6M3CGiedjU0eQjiMP7F+FdnZSYnivytbT0zb0gq7QVHYlSgcd6m6zuobKFsXPN4uGr3lRfOnaK0nhn8iTmK9tz09t6DuQ2zrqdru07a9D29VP2PY+052Cq1cJXq3qa+z9znG8gW1hQ48IxC1n4hvjMUp7xELsNcOPogFxAKIBcQCYgGxgFhALCAWQCwgFtjXuqec+PnP5Hdmbv6xDp8b4bCxPE5nEiMf+xjtrckGz1Gsez7TeD2gNz6zmFF6qC8fHyWj/c03RgcHYOGo+yyf2zY/d6AIh6gQy9pbVBamVLnWtlY7qEpuvjE7nItY3p7ZPf6bg4/DMXDjVXZZzMFY1xZHd+l/3Ttcyv0kk0Mb9MChJxrnEK4e5qKwgtLRKspftg9B0fzH6+dIUX6bzU/t/TsdireXEf/NeponkLwfkT8m7yvl40hgMf5l/zTdcf89niuesWZMw/ojQQ4OERlvtfLccB9fLRr4ovxlPr8c2g/JCddBLBNryQnxLNnsorTXc5uzXefQ/pO9IHbewS+e2/t332Hd8z3+ri1mLiMaN6t+adhgl1j+bXmTnk999xTFufUKYONY2oNLrHg+uvzRXIcG2N3qZyvO0r5Ddt/pkgsibK9j8KD1aUDh+LFMO1124xWTRacBH30++s0VwPbTsPrOxboP9RE3uyNcKJbXCxntVziydeKTXL7wkd17u/gcNJbcMrrWMwX5/jJqX3pRnn1HlpHNbeEpimdku+IKDEtsF0v5rcXxbn2tE0fmbf7riM+XgLL25a1zqsjRN7WZ4n2ZVU+TF915zGtby+Z54rrvH+62VLg0m5cMvn24voK8Fb5Vud3z/cX3i/tvrCzdBrHMGlRsO4gF7LMAYgGxgFhALCAWEAsgFhALiAXEAifhzF/wgVic+YtLc+Yv2H2fxZm/EMv7W5QzfyGW8szu8V9n/uJSnPmr9KUzf1HgzF/O/MXu0zBn/kIsc2px5i/EMnVbcuYvxDKpFmf+QixLBhdn/uJanPkLPhSLM39hGvbuvrkzfyGW4Yd3Z/5CLAODyAbROfMX1xxZnPkLsczfupz5i+tw5q/y7Zz5iw1jceYvxAJstYMPYgGxAGIBsYBYQCwgFhALIBYQC4gFPmu1A1a0Ptnh3Zlc0AZn/mof8R5Mw4YD3OwjhD6byLX2WTarxZEoudwOvo+nYwd/8cyp8/H59n7N+KfoXx+iz/bCWh+GdyQjTraD39vdbx3I4udf2bukcWbw9lbf/jLbFcXrkMd5PwayWDjZNKwXTWte1jvTUPuIldE76l1UFxytxSqFM8dS2aPpztLeWIwzoXKNfZbXsfBqx7NvHi2vuslHeUdowvXg1CPLgucH4PIjy7t1/Oy0185bpCK+PJbs7PvXT/NlzsVVpmGxzg0jjCJcOpZc8YZO84Ud/O0HKTh8LP2zp1TfiTL8NrLqOye9+4xrxOI9Wohl0g5HNlvpnVOrOEa8TsIVrfN73eqn+eqeDgy23huIVTsp7mP03nVcPoNW732Vjbcax8DC4lY7gxgcMhYwDQPEAmIBsYBYQCwgFhALIBYQC4gFxAJn8/cUP2XrXcnwGRu/RX/4+N9RXVZ0Fh231kdi2h9hERHniqV25q/O4cKbn18pbeWF7xSOCp5xKy8fzrXP0j3zVxauUYu0/51sHtg1q4XCOXfwVzyaROEjylrhQrEUPyu/7uLhIrFsNMyYhHHBWDqnKsp1cnCwMK49smy3jRtYuFwsJmGI5QC7LXCxkWWLWgwsXHMaZmxBLPZYEMsag8mGQ4tWuEosudJErH0rhfAd07BVxxbZcJVYittybLYuOG0sKz7uN8ej7JzDBc4bS+HMX28PLa1TgWmFz9nhY8X1T0oWAsryQnysmAvFsilnwUMsM5NRC2LZ/XkDuHYsDiCGWIwuiAWuw7GOQSwgFhALiAXEAt/jwCcz6r4J8/gLxsjyIZu9BOS1JT47srTeGDzw2P3GK/CNVTTe9uIlfc4WSzS32miem+u2wSq2WQEcdRr2zofntcL1d/BjjVSaragGO/jGFb5oZHns52d/zz8fX70OQVE9B/hQK+0FRnlBOfTMQ/Oi3kLS0MVrErPBqb0b2/PPZRnFA7Nk95AU2TweRdQ27OiOK60Fttf1u8j2gS6il0fvx3supLM0TMP2mDINH43yd4OMziGPah1nTF3Xb37R7Sr6lzR/vOdCJvzkiGWTPfoJV8qp8U1aV94WPZkQnjrgsDv4s85tn2udzLV1ifGDc8Qyd6CyWfMRB3kj5YztP9Izx3xzLHO2frVgGrb2TMyr/Hx9LDNqyZujvvLVscypJbXChWLpbPpTSphWS2b8qN/oeYnX6dk1ltY5utbY9PLNWnLJguF4I0tjc28+wL++HA5u6tjy61ZZ8OMSAwsTNtk1N5J8vle3dYKvKPzj+e3WG12aZ/bqv5WyeePHvwdX0T+NWGHB/Z+i+gMjlnVzOc6mleX3/8MyK78oeaRN8jWuhJ0UTr3PAmI55nTTn5qD7bMcSjqyGGKZudsiFcQC9llALCAWEAsgFhALiAXEAmIBsYBYALGAWEAsIBYQC4gFxAKIBcQCYgGxgFhALIBYQCwgFhALiAXEAmIBxAJiAbGAWEAsIBZALCAWEAuIBcQCYgGxAGIBsYBYQCwgFgAAgJVEuA/ADj6IBcQCYgGxgFgAsYBYQCwgFhALiAXEAogFxAJiAbGAWEAsgFhALCAWEAuIBcQCYgHEAmKBzf11F7CpvP9/dC6Y+/X0GxhZuFg83a9z5HqzFyAWsM8CYgGxgFgAsYBYOIuofB0j15u9gA1+dGcrZlO5dl1ZiyO3rsbIAmIBsYBYQCwgFhALbCsq/7/agjf4kb3Owqa6r37k2Eaeu238RhYQC4gFxAJiAbEAYuGYTvTahYPs8Vk5tx6fZwHTMBALiAUQC4gFxMLVeZ0FJkbSe51l7PMs1Rtunp2RBcQCYgGxgFhALCAWoMNxw9jW2PlZJn8spfb6y36vtxhZQCwgFhALiAXEAmIBOnyehX2Mnp+lKyd+P7K9AiMLjESTYgH7LGCf5UNjcYzNeCvvNYr6yD727qT2NbK6qrGfZeAgvcsW+sYK21fyzsGrxZJv3Sbrm2FO31qy+9WSG3ZvtnihS1f47r0qlsuF8rhZPP6R/QEiSt8aE42faOINs3O7/s0WLPS9FWrlkvssme+2cn9gze634vGfORtNtKY2uex2udJCF69QK5cdWWLJnzUb20rkyEYy+dH8cb3IJZvu83bt9S1d6OIVbtbK6H7SY0cxpi4gR74WS++vXt+Mxu71QhHtb0V/JjbjL7H0duss9N0VxqfGlly2m7hgAd81Dds0wv1vPjgviu1+z8Jo6mmwK07DNq5m8k5+HDDaN2ZMWhHL0pH9sIPYJivUyvdNw2JGGLHxtr37wPPGCj0RZp9l943ivDMzA4tYuttCPv8Tq2aVW2yquVcbJmH2Wfr77a/t4iBjQG6x0Lkr3KyVqS+PRO2nG13A5pH/uczDSMz4RgxfYcEFvU0wlm7ssepC566w00qs//fJytej6xxdwOab8heMLKV3C0b53bwx/H6p4WWOb9WL3wy5fKEzV2jn/itjab3pqz+3aNQykEbOWWb93fQjP8tQEcsWunSFaef+i3fwI8qvUrfegZmjpzWctMzBJY7fLsemErMXumSFWvneWG7l93RkZ38wi/PizDnL/Nk2n5tn5bYxtGNd3Ed9b6EzV+iJsG+PpbDB3D8DGDFQy7+3/8esWtrPyczesIdWt3ihc1aoFbEM7IlE3AaeUln4XOSiz57kyOrW/UBLcYV27sd8wVPH3WfwW3P16N38sQk1N6UYW+a73xvfs958hTn/jl3095n6/4sXsJ1vfSPlq43+J3bj3c1l8jPQ01tZsNB3V7jZeD78dc5dUPUFGNOw1f9ocYgfKa69Qvssl5+/7TSNzw/EiWnY3jWttenG3q3E6G+Zt5vxx8hy7GmhccXI8vndgHj3djO/N+1nGXoAX7rQxSvk60eWLGw2k97rmgsfnnPRIqf9FnMXahwxsqy455HD2+AWH97tvnUg11hq7L3CN/8eOfz/B9zFvHAsGeVtvnPIlpx6u6nfK40BQ7eb9EvMXejiFe73oFV/uXThGOnDX9MnWeUneAobWeuvVHoDYwxsZjEwI2od7zWm3C7H/+jzF/rmCkdnfQd54N9/yz11LDn5Huxsf5Pfl754Dhax/I8cKy90/gpzw43yxLFcdRrWPAZyDH679zheHaqGlhmDH9PKZX/hpQtdvEJuCx8BYYPhf7UTwuy/5XpREsQCYoGP8EZKdto7HtuHGb3C1H0dn2cB0zAQC4gFxAKIBcQCe/E6C/sYPapX7bwrtcvfPrybkQXEAmIBsYBYQCyAWOBdf3wGn4+K1Y+Z6fMsXNSJji8rFhALiAXEAmIBscD3cKxjMLKAWEAsIBYQC4gFEAuIBcQCYgGxgFhALIBYQCwgFhALiAXEAogFxAJiAbGAWEAsIBZALCAWEAuIBcQCYgGxAGKBFf1dYyHpfuQ4Njvzo5EF9hxZOP6j4tfYcJpjZAGxgFhALCAWuIBTPRtWeKooZ10hhm/k9SIGt781nqvMnXvJ1maes64QpR84Sr9EfCIeTx2/vzFudh/+OdtfJ3pfxrwrRGmT7IYREXGEX48j3Ydnf1Hy53FkcAgoXyHS9sr37eDn2PadMwPIf2waXC+W8T2LNHwglunbfnTjUQvfGMvsocUci2/cwZ+yx96/QsaEW3ydbI/D2bvjWhfXri6WK1JLb6ramqD++zKqF1cvNw274oOo3ZbRXb8Yurh6uVgOvunPfPbYqDJlDy/GL65eLpaLP5AaWpqbf8bIxdXLxWIi9r2PH+Hh5qtHFrWMTMR6I0Xl4urlYrnk3JzyzGr04urlYrHb8kX3SMaUi6uXi8VE7GvumuLGX7m4erlYTMRMxL5zIuYz+IaW4j0SUy+uXi6W844SaSK2wv2Z3zxIXyKWWHwFtajFNMxuyxv3Sfn4A5WLq5eL5VKzMLst5Y2/eI9VLq5eLpaLDi1qKT18eEbsgrHEW1cwESuNFDHx4urlYjlsK/nWFejPqjp3WuXi6uViueYei47m3jXf+ozY+WOJsT/S6BXU0h8ppn20pXr5Nf09fyrvz8HSDr6hZIKzHeu4vTsZ/VFj7ApRfSfHwGIO9GTFxq1E70eJ6sXVy4+wjWyy4DMdRX+tU07cColldSH5LbG0DmaU5Z+scTdVrv7pWlIs38KE8MCxeNcxiAXEAmIBsYBYQCyAWEAsIBYQC4gFxAJiAcQCYgGxgFhALHB5f90Fh+JwBkYWEAuIBRALiAW2tMqzYQ6juALPgxlZQCwgFkAsIBYQC4gFxAJiAbEAYgGxgFhALCAWEAsgFhALiAXEAmIBsYBYALGAWEAsIBYQC4gFEAuIBcQCYgGxgFhALIBYQCwgFhALiAXEAmIBxAJiAbGAWEAsIBZALCAWEAuIBcQCYgEAANhFhPvgGNJdsNY2bQcfjCxwDkYWEAuIBcQCYgGxgFgAsYBYYFN/V1nK7/ua4t8/4nFJVK5w6135ef3n1W69hRRv2Lx+58KBKw//DMM//MRV3Hq/1+gNo3c/3Hq/flQW2F9j/x4buKOmXGHuzzDl1yzet7fSMvt/sv5KjSxgGgZiAbGAWACxgFhALCAWEAuIBRALiAXEAmIBsYBYQCyAWEAsIBYQC4gFxAKIBcQCYgGxgFhALCAWQCwgFhALiAXEAmIBsQBiAbGAWEAsIBYQCyAWEAuIBcQCYgGxgFgAsYBYQCwgFhALiAXE4i4AsYBYQCwgFhALiAUQC4gFxAJiAbGAWEAsgFhALCAWEAuIBcQCiAXEAmIBsYBYQCwgFkAsIBYQC4gFxAJiAbEAYgGxgFhALCAWEAsgFhALiAXEAmIBsYBYALGAWEAsIBYQC4gFEAuIBcQCYgGxgFhALIBYQCwgFhALiAXEAmIBxAJiAbGAWEAsIBZALCAWEAuIBcQCYgGxAGIBsYBYQCwgFhALiMVdAGIBsYBYQCwgFhALIBYQC4gFxAJiAbGAWACxgFhALCAWEAuIBRALiAXEAmIBsYBYQCxASYT7AIwsIBYQC4gFxAJiAcQCYgGxgFhALCAWEAsgFhALiAXEAmf1112wjfz5j4+hiuV7tvafzT1fl8VYDc8b/fe/XLLKGI4tWxdm90cS56Z8Bn9ow71vva//e26bzW+Xb9T/7qS1xtCmn/8WH5WvjGT2WT7XSmfji9cw8/x2NkeU0o26o860AW0gpXits/FVpr+ZWI41Dpc35ilb6uStOce/k/2vwhRBLGecpb653eZgrI2RKzyHsK8/7uzhTbP972hcHJ2xJno3uk+SmtfJ1sWNJwTuF0VnZZXd/+h8dSveorQCjCxbhDLyEJ/37TXiMQ3q3yjvM7DndSJa86iIRmXPL1bc/+ivALFsUksOjzq956u6N8oo7kvk76W5JFPEctBJWM7cvejcKMrX60zm7s9nRXUutc6TEl6FEcu2O/dZzaQcU+FGsXjzRiwX2HHJekxzp1H3NDKGx6y3hxYDi1i2n4qVW6nvjMeGkSKW08jiE8orPPJnjNYSrX/G9J+isALEsmIVudeNirOuGLha5YX8mU9FIJZtZ2LZf4Ir5tyovoqYUEvlpfuY9DsYWMSy6Yyr+3ic3Zfps7UL3b9RtvZvcnGkz8uy8cbk9lfs8MDpvh6auHR2HqI7r+m+P775KZR8vbvldZ1HXtla4H2Hov0ZltJeRvXzLDk8zthjEcuXJDv7yYRlt8I0DIwsYGQBsYBYQCyAWEAsIBYQC4gFxAJiAcQCYgGxgFhALCAWEAsgFhALiAXEAmIBsQBiAbGAWEAsIBYQC4gFEAuIBcQCYgGxgFgAsYBYQCwgFhALiAXEAogFxAJiAbGAWEAsIBZALCAWEAuIBcQCYgHEAmIBsYBYQCwgFhALIBYQC4gFxAJiAbEAYgGxgFhALCAWEAuIBRALiAXEAmIBsYBYQCyAWEAsIBYQC4gFxAKIBcQCYgGxgFhALCAWQCwgFhALiAXEAmIBsQBiAbGAWEAsIBYQCyAWEAuIBcQCYgGxgFgAsYBYQCwgFhALiAUQC4gFxAJiAbGAWEAsgFhALCAWEAuIBcQCYgHEAmIBsYBYQCwgFkAsIBYQC4gFxAJiAbEAYgGxgFhALCAWEAsgFhALiAXEAmIBsYBYALGAWEAsIBYQC4gFxAKIBcQCYgGxgFhALIBYQCwgFhALiAXEAmIByv4PwWms3AV97VoAAAAASUVORK5CYII=" + } + ] + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + }, + "x-amzn-IdempotencyKey": { + "type": "string", + "description": "A unique value which the server uses to recognize subsequent retries of the same request." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "ChannelDetails object cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + }, + "x-amzn-api-sandbox": { + "dynamic": {} + } + }, + "parameters": [] + }, + "/shipping/v2/shipments": { + "post": { + "tags": [ + "shipping" + ], + "description": "Purchases a shipping service and returns purchase related details and documents.\n\nNote: You must complete the purchase within 10 minutes of rate creation by the shipping service provider. If you make the request after the 10 minutes have expired, you will receive an error response with the error code equal to \"TOKEN_EXPIRED\". If you receive this error response, you must get the rates for the shipment again.\n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "purchaseShipment", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "PurchaseShipmentRequest body", + "required": true, + "schema": { + "$ref": "#/definitions/PurchaseShipmentRequest" + } + }, + { + "name": "x-amzn-IdempotencyKey", + "description": "A unique value which the server uses to recognize subsequent retries of the same request.", + "in": "header", + "required": false, + "type": "string" + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/PurchaseShipmentResponse" + }, + "examples": { + "application/json": { + "shipmentId": "87852211788104", + "packageDocumentDetails": [ + { + "packageClientReferenceId": "abcd", + "packageDocuments": [ + { + "type": "LABEL", + "format": "PNG", + "contents": "sdioadaiosfhdodsaiufhouafhoudfhdouahfac==" + } + ], + "trackingId": 1578648261977 + } + ], + "promise": { + "pickupWindow": { + "start": "2019-12-11T07:09:05.513Z", + "end": "2019-12-11T09:09:05.513Z" + }, + "deliveryWindow": { + "start": "2019-12-13T07:09:05.513Z", + "end": "2019-12-13T09:09:05.513Z" + } + }, + "benefits": { + "includedBenefits": [ + "CLAIMS_PROTECTED" + ], + "excludedBenefits": [] + } + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "RateId cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + }, + "x-amzn-api-sandbox": { + "dynamic": {} + } + }, + "parameters": [] + }, + "/shipping/v2/oneClickShipment": { + "post": { + "tags": [ + "shipping" + ], + "description": "Purchases a shipping service identifier and returns purchase-related details and documents.\n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "oneClickShipment", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "OneClickShipmentRequest body", + "required": true, + "schema": { + "$ref": "#/definitions/OneClickShipmentRequest" + } + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/OneClickShipmentResponse" + }, + "examples": { + "application/json": { + "shipmentId": "87852211788104", + "packageDocumentDetails": [ + { + "packageClientReferenceId": "#abcd", + "packageDocuments": [ + { + "type": "LABEL", + "format": "PNG", + "contents": "sdioadaiosfhdodsaiufhouafhoudfhdouahfac==" + } + ], + "trackingId": 1578648261977 + } + ], + "promise": { + "pickupWindow": { + "start": "2019-12-11T07:09:05.513Z", + "end": "2019-12-11T09:09:05.513Z" + }, + "deliveryWindow": { + "start": "2019-12-13T07:09:05.513Z", + "end": "2019-12-13T09:09:05.513Z" + } + }, + "carrier": { + "id": "AMZN_UK", + "name": "Amazon Shipping" + }, + "service": { + "id": "SWA-UK-PREM", + "name": "Amazon Shipping One Day" + }, + "totalCharge": { + "unit": "GBP", + "value": 4.76 + } + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "ServiceId cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + }, + "x-amzn-api-sandbox": { + "dynamic": {} + } + }, + "parameters": [] + }, + "/shipping/v2/tracking": { + "get": { + "tags": [ + "shipping" + ], + "description": "Returns tracking information for a purchased shipment.\n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "getTracking", + "parameters": [ + { + "name": "trackingId", + "in": "query", + "description": "A carrier-generated tracking identifier originally returned by the purchaseShipment operation.", + "required": true, + "type": "string" + }, + { + "name": "carrierId", + "in": "query", + "description": "A carrier identifier originally returned by the getRates operation for the selected rate.", + "required": true, + "type": "string" + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetTrackingResponse" + }, + "examples": { + "application/json": { + "trackingId": "23AA47DE2B3B6", + "alternateLegTrackingId": "null", + "eventHistory": [ + { + "eventCode": "Undeliverable", + "location": { + "postalCode": "EX33 2JL", + "countryCode": "GB" + }, + "eventTime": "2024-02-11T07:09:05.513Z", + "shipmentType": "FORWARD" + }, + { + "eventCode": "ReturnInitiated", + "location": { + "postalCode": "EX33 2JL", + "countryCode": "GB" + }, + "eventTime": "2024-02-12T09:09:05.513Z", + "shipmentType": "RETURNS" + }, + { + "eventCode": "Delivered", + "location": { + "postalCode": "EX33 2JL", + "countryCode": "GB" + }, + "eventTime": "2024-02-13T04:09:05.513Z", + "shipmentType": "RETURNS" + } + ], + "promisedDeliveryDate": "2024-02-12T13:09:05.513Z", + "summary": { + "status": "Delivered", + "trackingDetailCodes": { + "forward": [ + "Rejected", + "Damaged" + ], + "returns": [ + "Signed" + ] + } + } + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "CarrierId is missing in the request" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + }, + "x-amzn-api-sandbox": { + "dynamic": {} + } + }, + "parameters": [] + }, + "/shipping/v2/shipments/{shipmentId}/documents": { + "get": { + "tags": [ + "shipping" + ], + "description": "Returns the shipping documents associated with a package in a shipment.\n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "getShipmentDocuments", + "parameters": [ + { + "name": "shipmentId", + "description": "The shipment identifier originally returned by the purchaseShipment operation.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "packageClientReferenceId", + "in": "query", + "description": "The package client reference identifier originally provided in the request body parameter for the getRates operation.", + "required": true, + "type": "string" + }, + { + "name": "format", + "in": "query", + "description": "The file format of the document. Must be one of the supported formats returned by the getRates operation.", + "required": false, + "type": "string" + }, + { + "name": "dpi", + "in": "query", + "description": "The resolution of the document (for example, 300 means 300 dots per inch). Must be one of the supported resolutions returned in the response to the getRates operation.", + "required": false, + "type": "number" + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetShipmentDocumentsResponse" + }, + "examples": { + "application/json": { + "shipmentId": "445454-3232-3232", + "packageDocumentDetail": { + "packageClientReferenceId": "ASUSDI-45343854", + "trackingId": "T1234567", + "packageDocuments": [ + { + "type": "LABEL", + "format": "PNG", + "contents": "sdioadaiosfhdodsaiufhouafhoudfhdouahfac==" + } + ], + "benefits": { + "includedBenefits": [ + "CLAIMS_PROTECTED" + ], + "excludedBenefits": [] + } + } + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "shipmentId is missing in the request" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + }, + "x-amzn-api-sandbox": { + "dynamic": {} + } + }, + "parameters": [] + }, + "/shipping/v2/shipments/{shipmentId}/cancel": { + "put": { + "tags": [ + "shipping" + ], + "description": "Cancels a purchased shipment. Returns an empty object if the shipment is successfully cancelled.\n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "cancelShipment", + "parameters": [ + { + "name": "shipmentId", + "description": "The shipment identifier originally returned by the purchaseShipment operation.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/CancelShipmentResponse" + }, + "examples": { + "application/json": {} + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "ShipmentId cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + }, + "x-amzn-api-sandbox": { + "dynamic": {} + } + }, + "parameters": [] + }, + "/shipping/v2/shipments/additionalInputs/schema": { + "get": { + "tags": [ + "shipping" + ], + "description": "Returns the JSON schema to use for providing additional inputs when needed to purchase a shipping offering. Call the getAdditionalInputs operation when the response to a previous call to the getRates operation indicates that additional inputs are required for the rate (shipping offering) that you want to purchase.\n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "getAdditionalInputs", + "parameters": [ + { + "name": "requestToken", + "in": "query", + "description": "The request token returned in the response to the getRates operation.", + "required": true, + "type": "string" + }, + { + "name": "rateId", + "in": "query", + "description": "The rate identifier for the shipping offering (rate) returned in the response to the getRates operation.", + "required": true, + "type": "string" + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetAdditionalInputsResponse" + }, + "examples": { + "application/json": { + "payload": { + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Additional inputs for Shipping Offering", + "type": "object", + "properties": { + "harmonizedSystemCode": { + "type": "string", + "description": "Harmonized System's commodity code for an item." + }, + "packageClientReferenceId": { + "type": "string", + "description": "Unique identifier for the item." + } + } + } + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + }, + "x-amzn-api-sandbox": { + "static": [ + { + "request": { + "parameters": { + "requestToken": { + "value": "amzn1.rq.123456789.101" + }, + "rateId": { + "value": "122324234543535321345436534321423423523452345" + } + } + }, + "response": { + "payload": {} + } + } + ] + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "RequestToken cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + }, + "x-amzn-api-sandbox": { + "static": [ + { + "request": { + "parameters": { + "requestToken": { + "value": "null" + }, + "rateId": { + "value": "2314346237423894905834905890346890789075" + } + } + }, + "response": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "RequestToken cannot be null" + } + ] + } + } + ] + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + } + }, + "parameters": [] + }, + "/shipping/v2/carrierAccountFormInputs": { + "get": { + "tags": [ + "shipping" + ], + "description": "This API will return a list of input schema required to register a shipper account with the carrier. \n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "getCarrierAccountFormInputs", + "parameters": [ + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetCarrierAccountFormInputsResponse" + }, + "examples": { + "application/json": { + "linkableCarriers": [ + { + "carrierId": "FedEx", + "linkableAccountTypes": [ + { + "accountTypeId": "", + "carrierAccountInputs": [ + { + "descriptionLocalizationKey": "accountNumberDescriptionString", + "inputType": "TEXTBOX", + "isConfidential": true, + "isHidden": false, + "isMandatory": true, + "name": "ACCOUNT_NUMBER", + "validationMetadata": [ + { + "errorMessage": "stringId4", + "validationStrategy": "MIN_LENGTH", + "value": "5" + }, + { + "errorMessage": "stringId5", + "validationStrategy": "MAX_LENGTH", + "value": "35" + } + ] + } + ] + } + ] + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "RequestToken cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + } + }, + "parameters": [] + }, + "/shipping/v2/carrierAccounts": { + "put": { + "tags": [ + "shipping" + ], + "description": "This API will return Get all carrier accounts for a merchant. \n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "getCarrierAccounts", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "GetCarrierAccountsRequest body", + "required": true, + "schema": { + "$ref": "#/definitions/GetCarrierAccountsRequest" + } + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetCarrierAccountsResponse" + }, + "examples": { + "application/json": { + "activeAccounts": [ + { + "accountId": "90613e4-dcf6-4c3e-bc57-45005580", + "carrierId": "FedEx" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "RequestToken cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + } + }, + "parameters": [] + }, + "/shipping/v2/carrierAccounts/{carrierId}": { + "put": { + "tags": [ + "shipping" + ], + "description": "This API associates/links the specified carrier account with the merchant. \n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "linkCarrierAccount", + "parameters": [ + { + "name": "carrierId", + "description": "An identifier for the carrier with which the seller's account is being linked.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "LinkCarrierAccountRequest body", + "required": true, + "schema": { + "$ref": "#/definitions/LinkCarrierAccountRequest" + } + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Espain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/LinkCarrierAccountResponse" + }, + "examples": { + "application/json": { + "registrationStatus": "ACTIVE", + "accountId": "90613e4-dcf6-4c3e-bc57-45005580" + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "Provided input is invalid. Please verify." + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + } + }, + "post": { + "tags": [ + "shipping" + ], + "description": "This API associates/links the specified carrier account with the merchant. \n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 5 | 10 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](doc:usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "linkCarrierAccount", + "parameters": [ + { + "name": "carrierId", + "description": "An identifier for the carrier with which the seller's account is being linked.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "LinkCarrierAccountRequest body", + "required": true, + "schema": { + "$ref": "#/definitions/LinkCarrierAccountRequest" + } + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/LinkCarrierAccountResponse" + }, + "examples": { + "application/json": { + "registrationStatus": "ACTIVE", + "accountId": "90613e4-dcf6-4c3e-bc57-45005580" + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "Provided input is invalid. Please verify." + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + }, + "x-amzn-api-sandbox": { + "dynamic": {} + } + }, + "parameters": [] + }, + "/shipping/v2/carrierAccounts/{carrierId}/unlink": { + "put": { + "tags": [ + "shipping" + ], + "description": "This API Unlink the specified carrier account with the merchant. \n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "unlinkCarrierAccount", + "parameters": [ + { + "name": "carrierId", + "description": "carrier Id to unlink with merchant.", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "description": "UnlinkCarrierAccountRequest body", + "required": true, + "schema": { + "$ref": "#/definitions/UnlinkCarrierAccountRequest" + } + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/UnlinkCarrierAccountResponse" + }, + "examples": { + "application/json": { + "merchantId": "", + "isUnlinked": true + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "RequestToken cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + } + }, + "parameters": [] + }, + "/shipping/v2/collectionForms": { + "post": { + "tags": [ + "shipping" + ], + "description": "This API Call to generate the collection form. \n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "generateCollectionForm", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "GenerateCollectionFormRequest body", + "required": true, + "schema": { + "$ref": "#/definitions/GenerateCollectionFormRequest" + } + }, + { + "name": "x-amzn-IdempotencyKey", + "description": "A unique value which the server uses to recognize subsequent retries of the same request.", + "in": "header", + "required": false, + "type": "string" + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GenerateCollectionFormResponse" + }, + "examples": { + "application/json": { + "collectionsFormDocument": { + "base64EncodedContent": "VGhpcyBpcyBzb21lIHRleHQgdG8gY29udmVydCB2aWEgQ3J5cHQu", + "documentFormat": "application/pdf" + } + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "RequestToken cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + } + }, + "parameters": [] + }, + "/shipping/v2/collectionForms/history": { + "put": { + "tags": [ + "shipping" + ], + "description": "This API Call to get the history of the previously generated collection forms. \n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "getCollectionFormHistory", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "GetCollectionFormHistoryRequest body", + "required": true, + "schema": { + "$ref": "#/definitions/GetCollectionFormHistoryRequest" + } + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetCollectionFormHistoryResponse" + }, + "examples": { + "application/json": { + "collectionFormsHistoryRecordList": [ + { + "carrierName": "USPS", + "creationDate": 1650163712052, + "generationStatus": "COMPLETED", + "collectionFormId": "2c631e32-2538-3431-889e-008b6f5a6f11", + "shipFromAddress": "<**-redacted-**>" + } + ], + "lastRefreshedDate": "April 17 2022 06:20 PM PDT" + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "RequestToken cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + } + }, + "parameters": [] + }, + "/shipping/v2/unmanifestedShipments": { + "put": { + "tags": [ + "shipping" + ], + "description": "This API Get all unmanifested carriers with shipment locations. Any locations which has unmanifested shipments\n with an eligible carrier for manifesting shall be returned. \n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "getUnmanifestedShipments", + "parameters": [ + { + "name": "body", + "in": "body", + "description": "GetUmanifestedShipmentsRequest body", + "required": true, + "schema": { + "$ref": "#/definitions/GetUnmanifestedShipmentsRequest" + } + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetUnmanifestedShipmentsResponse" + }, + "examples": { + "application/json": { + "unmanifestedCarrierInformationList": [ + { + "carrierId": "USPS", + "carrierName": "USPS", + "unmanifestedShipmentLocationList": [ + { + "address": "<**-redacted-**>", + "lastManifestDate": 1650163712052 + } + ] + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "RequestToken cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + } + }, + "parameters": [] + }, + "/shipping/v2/collectionForms/{collectionFormId}": { + "get": { + "tags": [ + "shipping" + ], + "description": "This API reprint a collection form. \n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "getCollectionForm", + "parameters": [ + { + "name": "collectionFormId", + "in": "path", + "description": "collection form Id to reprint a collection.", + "required": true, + "type": "string" + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetCollectionFormResponse" + }, + "examples": { + "application/json": { + "collectionsFormDocument": { + "base64EncodedContent": "VGhpcyBpcyBzb21lIHRleHQgdG8gY29udmVydCB2aWEgQ3J5cHQu", + "documentFormat": "application/pdf" + } + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "RequestToken cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + } + }, + "parameters": [] + }, + "/shipping/v2/accessPoints": { + "get": { + "tags": [ + "shipping" + ], + "description": "Returns a list of access points in proximity of input postal code.\n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](https://developer-docs.amazon.com/sp-api/docs/usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "getAccessPoints", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "parameters": [ + { + "in": "query", + "name": "accessPointTypes", + "description": "Access point types", + "required": true, + "type": "array", + "items": { + "type": "string", + "enum": [ + "HELIX", + "CAMPUS_LOCKER", + "OMNI_LOCKER", + "ODIN_LOCKER", + "DOBBY_LOCKER", + "CORE_LOCKER", + "3P", + "CAMPUS_ROOM" + ] + }, + "x-docgen-enum-table-extension": [ + { + "value": "HELIX", + "description": "Counter, aka Helix, is an assisted access point where customers interact with a store associate to collect or return packages. Counter stores do not have Locker" + }, + { + "value": "CAMPUS_LOCKER", + "description": "It denotes a physical Locker present at Locker+ stores" + }, + { + "value": "OMNI_LOCKER", + "description": "Omni lockers are similar to core lockers installed at apartment buildings. Core and Omni will have common base hardware platform" + }, + { + "value": "ODIN_LOCKER", + "description": "Odin lockers rely on Bluetooth connection as there is no screen or scanner on the locker. Drivers, customers, and even technicians can interact with the locker using an app (different depending on the user)." + }, + { + "value": "DOBBY_LOCKER", + "description": "Dobby lockers are Odin lockers installed at apartment complexes and available only to residents." + }, + { + "value": "CORE_LOCKER", + "description": "Core lockers are the traditional and initial style of lockers when Hub was launched. Core lockers have a screen, keypad and scanner. Customer either enter their pickup code using the Locker screen or scan pickup code barcode to pick up their package." + }, + { + "value": "3P", + "description": "Networks that are owned end-to-end by commercial carriers (CC - e.g. DHL, UPS, DPD and Postal organizations), who operate both the transportation leg and the access point aspect. These networks offer a range of pickup points including: 3P Lockers, legacy Postal Offices, 3P Counters (e.g. Gas Stations and Retail Outlets - supermarkets, bookstores, news-stands etc)." + }, + { + "value": "CAMPUS_ROOM", + "description": "Locker+, formerly known as Campus, are attended, physical stores, where the Locker is used dynamically by the staff to keep packages ready for customer pickup. Locker+ store can have multiple Lockers installed," + } + ], + "collectionFormat": "csv", + "x-example": "HELIX" + }, + { + "in": "query", + "name": "countryCode", + "description": "Country code for access point", + "required": true, + "type": "string", + "collectionFormat": "csv", + "x-example": "US" + }, + { + "in": "query", + "name": "postalCode", + "description": "postal code for access point", + "required": true, + "type": "string", + "collectionFormat": "csv", + "x-example": "EX332JL" + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "200": { + "description": "Success.", + "schema": { + "$ref": "#/definitions/GetAccessPointsResponse" + }, + "examples": { + "application/json": { + "HELIX": { + "accessPointId": "00046c4b-bb0a-4c03-831e-5633e142f16a", + "name": "Amazon Hub Counter - Co-op Braunton", + "timezone": "Europe/London", + "type": "HELIX", + "accessibilityAttributes": { + "distance": "21.200000762939453", + "driveTime": "0.0" + }, + "address": { + "addressObject": { + "addressLine1": "Exeter Road", + "city": "Braunton", + "countryCode": "GB", + "zipOrPostalCode": "EX33 2JL" + }, + "geocode": { + "latitude": "51.1076671", + "longitude": "-4.1607301" + } + }, + "exceptionOperatingHours": [ + { + "dateRange": { + "endDate": "1.6932672E9", + "startDate": "1.6931808E9" + }, + "operatingHours": { + "closingTime": { + "hourOfDay": 0, + "minuteOfHour": 0, + "secondOfMinute": 0 + }, + "midDayClosures": [], + "openingTime": { + "hourOfDay": 0, + "minuteOfHour": 0, + "secondOfMinute": 0 + } + } + } + ], + "assistanceType": "STAFF_ASSISTED", + "score": "0.6688068509101868", + "standardOperatingHours": { + "WEDNESDAY": { + "closingTime": { + "hourOfDay": 22, + "minuteOfHour": 0, + "secondOfMinute": 0 + }, + "midDayClosures": [], + "openingTime": { + "hourOfDay": 7, + "minuteOfHour": 0, + "secondOfMinute": 0 + } + }, + "MONDAY": { + "closingTime": { + "hourOfDay": 22, + "minuteOfHour": 0, + "secondOfMinute": 0 + }, + "midDayClosures": [], + "openingTime": { + "hourOfDay": 7, + "minuteOfHour": 0, + "secondOfMinute": 0 + } + }, + "THURSDAY": { + "closingTime": { + "hourOfDay": 22, + "minuteOfHour": 0, + "secondOfMinute": 0 + }, + "midDayClosures": [], + "openingTime": { + "hourOfDay": 7, + "minuteOfHour": 0, + "secondOfMinute": 0 + } + }, + "SUNDAY": { + "closingTime": { + "hourOfDay": 22, + "minuteOfHour": 0, + "secondOfMinute": 0 + }, + "midDayClosures": [], + "openingTime": { + "hourOfDay": 7, + "minuteOfHour": 0, + "secondOfMinute": 0 + } + }, + "TUESDAY": { + "closingTime": { + "hourOfDay": 22, + "minuteOfHour": 0, + "secondOfMinute": 0 + }, + "midDayClosures": [], + "openingTime": { + "hourOfDay": 7, + "minuteOfHour": 0, + "secondOfMinute": 0 + } + }, + "FRIDAY": { + "closingTime": { + "hourOfDay": 22, + "minuteOfHour": 0, + "secondOfMinute": 0 + }, + "midDayClosures": [], + "openingTime": { + "hourOfDay": 7, + "minuteOfHour": 0, + "secondOfMinute": 0 + } + }, + "SATURDAY": { + "closingTime": { + "hourOfDay": 22, + "minuteOfHour": 0, + "secondOfMinute": 0 + }, + "midDayClosures": [], + "openingTime": { + "hourOfDay": 7, + "minuteOfHour": 0, + "secondOfMinute": 0 + } + } + } + } + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "Access Point types cannot be null" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested response or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + }, + "x-amzn-api-sandbox": { + "dynamic": {} + } + }, + "parameters": [] + }, + "/shipping/v2/ndrFeedback": { + "post": { + "tags": [ + "shipping" + ], + "description": "This API submits the NDR (Non-delivery Report) Feedback for any eligible shipment.\n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](doc:usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "submitNdrFeedback", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Request body for ndrFeedback operation", + "required": true, + "schema": { + "$ref": "#/definitions/SubmitNdrFeedbackRequest" + } + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "204": { + "description": "Success", + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "Request is missing or has invalid parameters", + "details": "RescheduleDate is missing in the request" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested resource or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested resource or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or doesn't exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Something went wrong while processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + }, + "x-amzn-api-sandbox": { + "dynamic": {} + } + }, + "parameters": [] + }, + "/shipping/v2/claims": { + "post": { + "tags": [ + "shipping" + ], + "description": "This API will be used to create claim for single eligible shipment.\n\n**Usage Plan:**\n\n| Rate (requests per second) | Burst |\n| ---- | ---- |\n| 80 | 100 |\n\nThe `x-amzn-RateLimit-Limit` response header returns the usage plan rate limits that were applied to the requested operation, when available. The table above indicates the default rate and burst values for this operation. Selling partners whose business demands require higher throughput may see higher rate and burst values then those shown here. For more information, see [Usage Plans and Rate Limits in the Selling Partner API](doc:usage-plans-and-rate-limits-in-the-sp-api).", + "operationId": "createClaim", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Request body for the createClaim operation", + "required": true, + "schema": { + "$ref": "#/definitions/CreateClaimRequest" + } + }, + { + "name": "x-amzn-shipping-business-id", + "in": "header", + "description": "Amazon shipping business to assume for this request. The default is AmazonShipping_UK.", + "required": false, + "type": "string", + "enum": [ + "AmazonShipping_US", + "AmazonShipping_IN", + "AmazonShipping_UK", + "AmazonShipping_UAE", + "AmazonShipping_SA", + "AmazonShipping_EG", + "AmazonShipping_IT", + "AmazonShipping_ES", + "AmazonShipping_FR", + "AmazonShipping_JP" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AmazonShipping_US", + "description": "The United States Amazon shipping business." + }, + { + "value": "AmazonShipping_IN", + "description": "The India Amazon shipping business." + }, + { + "value": "AmazonShipping_UK", + "description": "The United Kingdom Amazon shipping business." + }, + { + "value": "AmazonShipping_UAE", + "description": "The United Arab Emirates Amazon shipping business." + }, + { + "value": "AmazonShipping_SA", + "description": "The Saudi Arabia Amazon shipping business." + }, + { + "value": "AmazonShipping_EG", + "description": "The Egypt Amazon shipping business." + }, + { + "value": "AmazonShipping_IT", + "description": "The Italy Amazon shipping business." + }, + { + "value": "AmazonShipping_ES", + "description": "The Spain Amazon shipping business." + }, + { + "value": "AmazonShipping_FR", + "description": "The France Amazon shipping business." + }, + { + "value": "AmazonShipping_JP", + "description": "The Japan Amazon shipping business." + } + ] + } + ], + "responses": { + "201": { + "description": "Success", + "schema": { + "$ref": "#/definitions/CreateClaimResponse" + }, + "examples": { + "application/json": { + "claimId": "1318160977650" + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "400": { + "description": "Request has missing or invalid parameters and cannot be parsed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidInputException", + "message": "Request is missing or has invalid parameters", + "details": "Claim already exists for the trackingId" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "401": { + "description": "The request's Authorization header is not formatted correctly or does not contain a valid token.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You don't have access to the requested resource or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "403": { + "description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "Unauthorized", + "message": "You do not have access to the requested resource or the credentials are invalid." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "404": { + "description": "The resource specified does not exist.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "NotFound", + "message": "The requested resource is invalid or does not exist" + } + ] + } + }, + "headers": { + "x-amzn-RateLimit-Limit": { + "type": "string", + "description": "Your rate limit (requests per second) for this operation." + }, + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "413": { + "description": "The request size exceeded the maximum accepted size.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload size is greater than maximum accepted size." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "415": { + "description": "The request payload is in an unsupported format.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InvalidRequest", + "message": "The request payload format is not supported." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "429": { + "description": "The frequency of requests was greater than allowed.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "TooManyRequests", + "message": "The total number of requests exceeded your allowed limit." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "500": { + "description": "An unexpected condition occurred that prevented the server from fulfilling the request.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "InternalError", + "message": "Unknown error in processing the request." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + }, + "503": { + "description": "Temporary overloading or maintenance of the server.", + "schema": { + "$ref": "#/definitions/ErrorList" + }, + "examples": { + "application/json": { + "errors": [ + { + "code": "ServiceUnavailable", + "message": "Service temporarily unavailable or down for maintenance. Please try again after sometime." + } + ] + } + }, + "headers": { + "x-amzn-RequestId": { + "type": "string", + "description": "Unique request reference identifier." + } + } + } + }, + "x-amzn-api-sandbox": { + "dynamic": {} + } + }, + "parameters": [] + } + }, + "definitions": { + "Error": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "An error code that identifies the type of error that occurred." + }, + "message": { + "type": "string", + "description": "A message that describes the error condition." + }, + "details": { + "type": "string", + "description": "Additional details that can help the caller understand or fix the issue." + } + }, + "description": "Error response returned when the request is unsuccessful." + }, + "ErrorList": { + "type": "object", + "description": "A list of error responses returned when a request is unsuccessful.", + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "description": "Array of Errors", + "items": { + "$ref": "#/definitions/Error" + } + } + } + }, + "Weight": { + "description": "The weight in the units indicated.", + "type": "object", + "required": [ + "unit", + "value" + ], + "properties": { + "unit": { + "type": "string", + "description": "The unit of measurement.", + "enum": [ + "GRAM", + "KILOGRAM", + "OUNCE", + "POUND" + ], + "x-docgen-enum-table-extension": [ + { + "value": "GRAM", + "description": "Metric unit of mass equal to one thousandth of a kilogram." + }, + { + "value": "KILOGRAM", + "description": "Metric unit of mass." + }, + { + "value": "OUNCE", + "description": "The imperial unit of weight that is one sixteenth of a pound." + }, + { + "value": "POUND", + "description": "The imperial unit of weight." + } + ] + }, + "value": { + "type": "number", + "description": "The measurement value." + } + } + }, + "LiquidVolume": { + "type": "object", + "required": [ + "unit", + "value" + ], + "properties": { + "unit": { + "type": "string", + "description": "The unit of measurement.", + "enum": [ + "ML", + "L", + "FL_OZ", + "GAL", + "PT", + "QT", + "C" + ], + "x-docgen-enum-table-extension": [ + { + "value": "ML", + "description": "Milliliter - Metric unit of volume." + }, + { + "value": "L", + "description": "Liter - Metric unit of volume." + }, + { + "value": "FL_OZ", + "description": "Fluid Ounce - Imperial unit of volume." + }, + { + "value": "GAL", + "description": "Gallon - Imperial unit of volume." + }, + { + "value": "PT", + "description": "Pint - Imperial unit of volume." + }, + { + "value": "QT", + "description": "Quart - Imperial unit of volume." + }, + { + "value": "C", + "description": "Cup - Imperial unit of volume." + } + ] + }, + "value": { + "type": "number", + "description": "The measurement value." + } + }, + "description": "Liquid Volume." + }, + "InvoiceDetails": { + "description": "The invoice details for charges associated with the goods in the package. Only applies to certain regions.", + "type": "object", + "properties": { + "invoiceNumber": { + "type": "string", + "description": "The invoice number of the item." + }, + "invoiceDate": { + "type": "string", + "format": "date-time", + "description": "The invoice date of the item in ISO 8061 format." + } + } + }, + "ChargeList": { + "type": "array", + "description": "A list of charges based on the shipping service charges applied on a package.", + "items": { + "$ref": "#/definitions/ChargeComponent" + } + }, + "ChargeComponent": { + "type": "object", + "description": "The type and amount of a charge applied on a package.", + "properties": { + "amount": { + "$ref": "#/definitions/Currency" + }, + "chargeType": { + "type": "string", + "description": "The type of charge.", + "enum": [ + "TAX", + "DISCOUNT" + ], + "x-docgen-enum-table-extension": [ + { + "value": "TAX", + "description": "A tax imposed on a package." + }, + { + "value": "DISCOUNT", + "description": "A discount deducted from the cost of a package." + } + ] + } + } + }, + "Currency": { + "description": "The monetary value in the currency indicated, in ISO 4217 standard format.", + "type": "object", + "required": [ + "unit", + "value" + ], + "properties": { + "value": { + "type": "number", + "description": "The monetary value." + }, + "unit": { + "type": "string", + "description": "The ISO 4217 format 3-character currency code.", + "minLength": 3, + "maxLength": 3 + } + } + }, + "Dimensions": { + "type": "object", + "required": [ + "height", + "length", + "unit", + "width" + ], + "properties": { + "length": { + "type": "number", + "description": "The length of the package." + }, + "width": { + "type": "number", + "description": "The width of the package." + }, + "height": { + "type": "number", + "description": "The height of the package." + }, + "unit": { + "type": "string", + "description": "The unit of measurement.", + "enum": [ + "INCH", + "CENTIMETER" + ], + "x-docgen-enum-table-extension": [ + { + "value": "INCH", + "description": "The imperial unit of length equal to one twelfth of a foot." + }, + { + "value": "CENTIMETER", + "description": "A metric unit of length, equal to one hundredth of a meter." + } + ] + } + }, + "description": "A set of measurements for a three-dimensional object." + }, + "RequestToken": { + "type": "string", + "description": "A unique token generated to identify a getRates operation." + }, + "RateId": { + "type": "string", + "description": "An identifier for the rate (shipment offering) provided by a shipping service provider." + }, + "CarrierId": { + "type": "string", + "description": "The carrier identifier for the offering, provided by the carrier." + }, + "AccountId": { + "type": "string", + "description": "Identifier for the seller's carrier account." + }, + "CarrierName": { + "type": "string", + "description": "The carrier name for the offering." + }, + "PackageClientReferenceId": { + "type": "string", + "description": "A client provided unique identifier for a package being shipped. This value should be saved by the client to pass as a parameter to the getShipmentDocuments operation." + }, + "ShipmentId": { + "type": "string", + "description": "The unique shipment identifier provided by a shipping service." + }, + "DetailCodes": { + "type": "string", + "description": "A list of codes used to provide additional shipment information.", + "enum": [ + "BusinessClosed", + "CustomerUnavailable", + "PaymentNotReady", + "OtpNotAvailable", + "DeliveryAttempted", + "UnableToAccess", + "UnableToContactRecipient", + "DeliveredToBehindWheelieBin", + "DeliveredToPorch", + "DeliveredToGarage", + "DeliveredToGarden", + "DeliveredToGreenhouse", + "DeliveredToMailSlot", + "DeliveredToMailRoom", + "DeliveredToNeighbor", + "DeliveredToRearDoor", + "DeliveredToReceptionist", + "DeliveredToShed", + "DeliveredWithOTP", + "Signed", + "Damaged", + "IncorrectItems", + "NotRequired", + "Rejected", + "RejectedByRecipientWithVerification", + "CancelledByRecipient", + "AddressNotFound", + "HazmatShipment", + "Undeliverable", + "ArrivedAtLocalFacility" + ], + "x-docgen-enum-table-extension": [ + { + "value": "BusinessClosed", + "description": "Attempted delivery but the business was closed." + }, + { + "value": "CustomerUnavailable", + "description": "Attempted delivery but the recipient was unavailable." + }, + { + "value": "PaymentNotReady", + "description": "Attempted delivery but cash was not available from recipient." + }, + { + "value": "OtpNotAvailable", + "description": "Attempted delivery but the recipient did not have the PIN available." + }, + { + "value": "DeliveryAttempted", + "description": "Delivery was attempted." + }, + { + "value": "UnableToAccess", + "description": "Attempted delivery but could not access the recipient location." + }, + { + "value": "UnableToContactRecipient", + "description": "Attempted delivery but unable to contact the recipient to coordinate delivery." + }, + { + "value": "DeliveredToBehindWheelieBin", + "description": "Shipment delivered behind wheelie bin." + }, + { + "value": "DeliveredToPorch", + "description": "Shipment delivered to front porch or front door." + }, + { + "value": "DeliveredToGarage", + "description": "Shipment delivered to garage." + }, + { + "value": "DeliveredToGarden", + "description": "Shipment delivered to garden." + }, + { + "value": "DeliveredToGreenhouse", + "description": "Shipment delivered to greenhouse." + }, + { + "value": "DeliveredToMailSlot", + "description": "Shipment delivered to letterbox/mail slot." + }, + { + "value": "DeliveredToMailRoom", + "description": "Shipment delivered to mailroom." + }, + { + "value": "DeliveredToNeighbor", + "description": "Shipment delivered and signed for by neighbor." + }, + { + "value": "DeliveredToRearDoor", + "description": "Shipment delivered to rear porch or rear door." + }, + { + "value": "DeliveredToReceptionist", + "description": "Shipment delivered to reception." + }, + { + "value": "DeliveredToShed", + "description": "Shipment delivered to shed." + }, + { + "value": "DeliveredWithOTP", + "description": "Shipment delivered with onetime passcode." + }, + { + "value": "Signed", + "description": "Shipment delivered and signature received." + }, + { + "value": "Damaged", + "description": "Shipment was damaged. When paired with Rejected this indicates the recipient refused to accept the shipment because the shipment was damaged. When paired with UNDELIVERABLE it means the shipment was undeliverable due to being damaged and may be returned to the seller." + }, + { + "value": "IncorrectItems", + "description": "Recipient refused to accept the shipment because the shipment contains incorrect item/s." + }, + { + "value": "NotRequired", + "description": "Recipient refused to accept the shipment because the recipient did not want the shipment." + }, + { + "value": "Rejected", + "description": "Recipient refused to accept the shipment." + }, + { + "value": "RejectedByRecipientWithVerification", + "description": "Recipient refused to accept the shipment and provided verification." + }, + { + "value": "CancelledByRecipient", + "description": "Shipment was cancelled by the recipient. This tag is paired with the rejected shipment status." + }, + { + "value": "AddressNotFound", + "description": "Shipment is undeliverable and is returning to seller because the address was missing or incorrect." + }, + { + "value": "HazmatShipment", + "description": "Shipment is non-compliant Hazmat." + }, + { + "value": "Undeliverable", + "description": "Shipment is undeliverable and is returning to the seller." + }, + { + "value": "ArrivedAtLocalFacility", + "description": "Package arrived at the local facility." + } + ] + }, + "TrackingDetailCodes": { + "type": "object", + "description": "Contains detail codes that provide additional details related to the forward and return leg of the shipment.", + "required": [ + "forward", + "returns" + ], + "properties": { + "forward": { + "type": "array", + "description": "Contains detail codes that provide additional details related to the forward leg of the shipment.", + "items": { + "$ref": "#/definitions/DetailCodes" + } + }, + "returns": { + "type": "array", + "description": "Contains detail codes that provide additional details related to the return leg of the shipment.", + "items": { + "$ref": "#/definitions/DetailCodes" + } + } + } + }, + "TrackingId": { + "type": "string", + "description": "The carrier generated identifier for a package in a purchased shipment." + }, + "ClaimId": { + "type": "string", + "description": "The claim identifier originally returned by the createClaim operation." + }, + "AlternateLegTrackingId": { + "type": "string", + "description": "The carrier generated reverse identifier for a returned package in a purchased shipment." + }, + "ServiceId": { + "type": "string", + "description": "An identifier for the shipping service." + }, + "ServiceName": { + "type": "string", + "description": "The name of the shipping service." + }, + "AdditionalAddressNotes": { + "type": "string", + "maxLength": 256, + "description": "Address notes to re-attempt delivery with." + }, + "ShipperInstruction": { + "type": "object", + "properties": { + "deliveryNotes": { + "type": "string", + "description": "The delivery notes for the shipment", + "maxLength": 256 + } + }, + "description": "The shipper instruction." + }, + "Address": { + "type": "object", + "required": [ + "addressLine1", + "city", + "countryCode", + "name", + "postalCode", + "stateOrRegion" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the person, business or institution at the address.", + "minLength": 1, + "maxLength": 50 + }, + "addressLine1": { + "type": "string", + "description": "The first line of the address.", + "minLength": 1, + "maxLength": 60 + }, + "addressLine2": { + "type": "string", + "description": "Additional address information, if required.", + "minLength": 1, + "maxLength": 60 + }, + "addressLine3": { + "type": "string", + "description": "Additional address information, if required.", + "minLength": 1, + "maxLength": 60 + }, + "companyName": { + "type": "string", + "description": "The name of the business or institution associated with the address." + }, + "stateOrRegion": { + "$ref": "#/definitions/StateOrRegion" + }, + "city": { + "$ref": "#/definitions/City" + }, + "countryCode": { + "$ref": "#/definitions/CountryCode" + }, + "postalCode": { + "$ref": "#/definitions/PostalCode" + }, + "email": { + "type": "string", + "description": "The email address of the contact associated with the address.", + "maxLength": 64 + }, + "phoneNumber": { + "type": "string", + "description": "The phone number of the person, business or institution located at that address, including the country calling code.", + "minLength": 1, + "maxLength": 20 + }, + "geocode": { + "$ref": "#/definitions/Geocode" + } + }, + "description": "The address." + }, + "Geocode": { + "type": "object", + "description": "Defines the latitude and longitude of the access point.", + "properties": { + "latitude": { + "description": "The latitude of access point.", + "type": "string" + }, + "longitude": { + "description": "The longitude of access point.", + "type": "string" + } + } + }, + "StateOrRegion": { + "type": "string", + "description": "The state, county or region where the person, business or institution is located." + }, + "City": { + "type": "string", + "description": "The city or town where the person, business or institution is located." + }, + "CountryCode": { + "type": "string", + "description": "The two digit country code. Follows ISO 3166-1 alpha-2 format." + }, + "PostalCode": { + "type": "string", + "description": "The postal code of that address. It contains a series of letters or digits or both, sometimes including spaces or punctuation." + }, + "Location": { + "type": "object", + "properties": { + "stateOrRegion": { + "$ref": "#/definitions/StateOrRegion" + }, + "city": { + "$ref": "#/definitions/City" + }, + "countryCode": { + "$ref": "#/definitions/CountryCode" + }, + "postalCode": { + "$ref": "#/definitions/PostalCode" + } + }, + "description": "The location where the person, business or institution is located." + }, + "DocumentFormat": { + "type": "string", + "description": "The file format of the document.", + "enum": [ + "PDF", + "PNG", + "ZPL" + ], + "x-docgen-enum-table-extension": [ + { + "value": "PDF", + "description": "The Portable Document Format (PDF) file format. Used to present documents, including text formatting and images, in a manner independent of application software, hardware, and operating systems." + }, + { + "value": "PNG", + "description": "Portable Network Graphics (PNG) is a raster-graphics file format that supports lossless data compression." + }, + { + "value": "ZPL", + "description": "Zebra Programming Language (ZPL) format is from Zebra Technologies. It's used primarily for labeling applications and can only be used with ZPL compatible printers." + } + ] + }, + "DocumentType": { + "type": "string", + "description": "The type of shipping document.", + "enum": [ + "PACKSLIP", + "LABEL", + "RECEIPT", + "CUSTOM_FORM" + ], + "x-docgen-enum-table-extension": [ + { + "value": "PACKSLIP", + "description": "A listing of the items packed within the shipment." + }, + { + "value": "LABEL", + "description": "The shipping label for the specific shipment." + }, + { + "value": "RECEIPT", + "description": "The receipt of the shipment." + }, + { + "value": "CUSTOM_FORM", + "description": "The customs documentation for a cross-border shipment." + } + ] + }, + "Dpi": { + "type": "integer", + "description": "The dots per inch (DPI) value used in printing. This value represents a measure of the resolution of the document." + }, + "PageLayout": { + "type": "string", + "description": "Indicates the position of the label on the paper. Should be the same value as returned in getRates response." + }, + "NeedFileJoining": { + "type": "boolean", + "description": "When true, files should be stitched together. Otherwise, files should be returned separately. Defaults to false." + }, + "Contents": { + "type": "string", + "description": "A Base64 encoded string of the file contents." + }, + "RequestedLabelCustomization": { + "type": "object", + "description": "Object contains customised data requested by a shipper to be printed on a shipping label.", + "properties": { + "requestAttributes": { + "$ref": "#/definitions/RequestAttributes" + } + } + }, + "RequestAttributes": { + "type": "array", + "description": "Specify the type of attributes to be added on a label.", + "items": { + "$ref": "#/definitions/LabelAttribute" + } + }, + "LabelAttribute": { + "type": "string", + "description": "Enumerates the attributes supported to be printed on a shipping label. The values for these attributes are retrieved from GetRates/OneClickShipment request", + "enum": [ + "PACKAGE_CLIENT_REFERENCE_ID", + "SELLER_DISPLAY_NAME", + "COLLECT_ON_DELIVERY_AMOUNT" + ], + "x-docgen-enum-table-extension": [ + { + "value": "PACKAGE_CLIENT_REFERENCE_ID", + "description": "Prints client reference ID on the shipping label" + }, + { + "value": "SELLER_DISPLAY_NAME", + "description": "Prints seller name on the shipping label" + }, + { + "value": "COLLECT_ON_DELIVERY_AMOUNT", + "description": "Prints Collect On Delivery amount on the shipping label." + } + ] + }, + "PackageDocumentList": { + "type": "array", + "description": "A list of documents related to a package.", + "items": { + "$ref": "#/definitions/PackageDocument" + } + }, + "PackageDocument": { + "type": "object", + "required": [ + "contents", + "format", + "type" + ], + "properties": { + "type": { + "$ref": "#/definitions/DocumentType" + }, + "format": { + "$ref": "#/definitions/DocumentFormat" + }, + "contents": { + "$ref": "#/definitions/Contents" + } + }, + "description": "A document related to a package." + }, + "PrintOptionList": { + "type": "array", + "description": "A list of the format options for a label.", + "items": { + "$ref": "#/definitions/PrintOption" + } + }, + "PrintOption": { + "type": "object", + "required": [ + "supportedDocumentDetails", + "supportedFileJoiningOptions", + "supportedPageLayouts" + ], + "properties": { + "supportedDPIs": { + "type": "array", + "description": "A list of the supported DPI options for a document.", + "items": { + "$ref": "#/definitions/Dpi" + } + }, + "supportedPageLayouts": { + "type": "array", + "description": "A list of the supported page layout options for a document.", + "items": { + "$ref": "#/definitions/PageLayout" + } + }, + "supportedFileJoiningOptions": { + "type": "array", + "description": "A list of the supported needFileJoining boolean values for a document.", + "items": { + "$ref": "#/definitions/NeedFileJoining" + } + }, + "supportedDocumentDetails": { + "description": "A list of the supported documented details.", + "type": "array", + "items": { + "$ref": "#/definitions/SupportedDocumentDetail" + } + } + }, + "description": "The format options available for a label." + }, + "DocumentSize": { + "type": "object", + "required": [ + "length", + "unit", + "width" + ], + "properties": { + "width": { + "description": "The width of the document measured in the units specified.", + "type": "number" + }, + "length": { + "description": "The length of the document measured in the units specified.", + "type": "number" + }, + "unit": { + "type": "string", + "description": "The unit of measurement.", + "enum": [ + "INCH", + "CENTIMETER" + ], + "x-docgen-enum-table-extension": [ + { + "value": "INCH", + "description": "The imperial unit of length equal to one twelfth of a foot." + }, + { + "value": "CENTIMETER", + "description": "A metric unit of length, equal to one hundredth of a meter." + } + ] + } + }, + "description": "The size dimensions of the label." + }, + "SupportedDocumentDetail": { + "type": "object", + "required": [ + "isMandatory", + "name" + ], + "properties": { + "name": { + "$ref": "#/definitions/DocumentType" + }, + "isMandatory": { + "type": "boolean", + "description": "When true, the supported document type is required." + } + }, + "description": "The supported document types for a service offering." + }, + "RequestedDocumentSpecification": { + "type": "object", + "required": [ + "format", + "needFileJoining", + "requestedDocumentTypes", + "size" + ], + "properties": { + "format": { + "$ref": "#/definitions/DocumentFormat" + }, + "size": { + "$ref": "#/definitions/DocumentSize" + }, + "dpi": { + "$ref": "#/definitions/Dpi" + }, + "pageLayout": { + "$ref": "#/definitions/PageLayout" + }, + "needFileJoining": { + "$ref": "#/definitions/NeedFileJoining" + }, + "requestedDocumentTypes": { + "description": "A list of the document types requested.", + "type": "array", + "items": { + "$ref": "#/definitions/DocumentType" + } + }, + "requestedLabelCustomization": { + "$ref": "#/definitions/RequestedLabelCustomization" + } + }, + "description": "The document specifications requested. For calls to the purchaseShipment operation, the shipment purchase fails if the specified document specifications are not among those returned in the response to the getRates operation." + }, + "SupportedDocumentSpecificationList": { + "type": "array", + "description": "A list of the document specifications supported for a shipment service offering.", + "items": { + "$ref": "#/definitions/SupportedDocumentSpecification" + } + }, + "SupportedDocumentSpecification": { + "type": "object", + "required": [ + "format", + "printOptions", + "size" + ], + "properties": { + "format": { + "$ref": "#/definitions/DocumentFormat" + }, + "size": { + "$ref": "#/definitions/DocumentSize" + }, + "printOptions": { + "$ref": "#/definitions/PrintOptionList" + } + }, + "description": "Document specification that is supported for a service offering." + }, + "Item": { + "type": "object", + "required": [ + "quantity" + ], + "properties": { + "itemValue": { + "$ref": "#/definitions/Currency" + }, + "description": { + "type": "string", + "description": "The product description of the item." + }, + "itemIdentifier": { + "type": "string", + "description": "A unique identifier for an item provided by the client." + }, + "quantity": { + "type": "integer", + "description": "The number of units. This value is required." + }, + "weight": { + "$ref": "#/definitions/Weight" + }, + "liquidVolume": { + "$ref": "#/definitions/LiquidVolume" + }, + "isHazmat": { + "type": "boolean", + "description": "When true, the item qualifies as hazardous materials (hazmat). Defaults to false." + }, + "dangerousGoodsDetails": { + "$ref": "#/definitions/DangerousGoodsDetails" + }, + "productType": { + "type": "string", + "description": "The product type of the item." + }, + "invoiceDetails": { + "$ref": "#/definitions/InvoiceDetails" + }, + "serialNumbers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of unique serial numbers in an Amazon package that can be used to guarantee non-fraudulent items. The number of serial numbers in the list must be less than or equal to the quantity of items being shipped. Only applicable when channel source is Amazon." + }, + "directFulfillmentItemIdentifiers": { + "$ref": "#/definitions/DirectFulfillmentItemIdentifiers" + } + }, + "description": "An item in a package." + }, + "ItemList": { + "type": "array", + "description": "A list of items.", + "items": { + "$ref": "#/definitions/Item" + } + }, + "Package": { + "type": "object", + "required": [ + "dimensions", + "insuredValue", + "items", + "packageClientReferenceId", + "weight" + ], + "properties": { + "dimensions": { + "$ref": "#/definitions/Dimensions" + }, + "weight": { + "$ref": "#/definitions/Weight" + }, + "insuredValue": { + "$ref": "#/definitions/Currency" + }, + "isHazmat": { + "type": "boolean", + "description": "When true, the package contains hazardous materials. Defaults to false." + }, + "sellerDisplayName": { + "type": "string", + "description": "The seller name displayed on the label." + }, + "charges": { + "$ref": "#/definitions/ChargeList" + }, + "packageClientReferenceId": { + "$ref": "#/definitions/PackageClientReferenceId" + }, + "items": { + "$ref": "#/definitions/ItemList" + } + }, + "description": "A package to be shipped through a shipping service offering." + }, + "PackageList": { + "type": "array", + "description": "A list of packages to be shipped through a shipping service offering.", + "items": { + "$ref": "#/definitions/Package" + } + }, + "DirectFulfillmentItemIdentifiers": { + "type": "object", + "required": [ + "lineItemID" + ], + "properties": { + "lineItemID": { + "type": "string", + "description": "A unique identifier for an item provided by the client for a direct fulfillment shipment. This is only populated for direct fulfillment multi-piece shipments. It is required if a vendor wants to change the configuration of the packages in which the purchase order is shipped." + }, + "pieceNumber": { + "type": "string", + "description": "A unique identifier for an item provided by the client for a direct fulfillment shipment. This is only populated if a single line item has multiple pieces. Defaults to 1." + } + }, + "description": "Item identifiers for an item in a direct fulfillment shipment." + }, + "PackageDocumentDetail": { + "type": "object", + "required": [ + "packageClientReferenceId", + "packageDocuments" + ], + "properties": { + "packageClientReferenceId": { + "$ref": "#/definitions/PackageClientReferenceId" + }, + "packageDocuments": { + "$ref": "#/definitions/PackageDocumentList" + }, + "trackingId": { + "$ref": "#/definitions/TrackingId" + } + }, + "description": "The post-purchase details of a package that will be shipped using a shipping service." + }, + "PackageDocumentDetailList": { + "type": "array", + "description": "A list of post-purchase details about a package that will be shipped using a shipping service.", + "items": { + "$ref": "#/definitions/PackageDocumentDetail" + } + }, + "TimeWindow": { + "type": "object", + "properties": { + "start": { + "type": "string", + "format": "date-time", + "description": "The start time of the time window." + }, + "end": { + "type": "string", + "format": "date-time", + "description": "The end time of the time window." + } + }, + "description": "The start and end time that specifies the time interval of an event." + }, + "Promise": { + "type": "object", + "properties": { + "deliveryWindow": { + "$ref": "#/definitions/TimeWindow" + }, + "pickupWindow": { + "$ref": "#/definitions/TimeWindow" + } + }, + "description": "The time windows promised for pickup and delivery events." + }, + "RequestedValueAddedServiceList": { + "type": "array", + "description": "The value-added services to be added to a shipping service purchase.", + "items": { + "$ref": "#/definitions/RequestedValueAddedService" + } + }, + "RequestedValueAddedService": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "description": "The identifier of the selected value-added service. Must be among those returned in the response to the getRates operation." + } + }, + "description": "A value-added service to be applied to a shipping service purchase." + }, + "AvailableValueAddedServiceGroupList": { + "type": "array", + "description": "A list of value-added services available for a shipping service offering.", + "items": { + "$ref": "#/definitions/AvailableValueAddedServiceGroup" + } + }, + "AvailableValueAddedServiceGroup": { + "type": "object", + "required": [ + "groupDescription", + "groupId", + "isRequired" + ], + "properties": { + "groupId": { + "type": "string", + "description": "The type of the value-added service group." + }, + "groupDescription": { + "type": "string", + "description": "The name of the value-added service group." + }, + "isRequired": { + "type": "boolean", + "description": "When true, one or more of the value-added services listed must be specified." + }, + "valueAddedServices": { + "type": "array", + "description": "A list of optional value-added services available for purchase with a shipping service offering.", + "items": { + "$ref": "#/definitions/ValueAddedService" + } + } + }, + "description": "The value-added services available for purchase with a shipping service offering." + }, + "ValueAddedService": { + "type": "object", + "required": [ + "cost", + "id", + "name" + ], + "properties": { + "id": { + "type": "string", + "description": "The identifier for the value-added service." + }, + "name": { + "type": "string", + "description": "The name of the value-added service." + }, + "cost": { + "description": "The cost of the value-added service.", + "$ref": "#/definitions/Currency" + } + }, + "description": "A value-added service available for purchase with a shipment service offering." + }, + "CollectOnDelivery": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Currency" + } + }, + "description": "The amount to collect on delivery." + }, + "ValueAddedServiceDetails": { + "type": "object", + "properties": { + "collectOnDelivery": { + "$ref": "#/definitions/CollectOnDelivery" + } + }, + "description": "A collection of supported value-added services." + }, + "DangerousGoodsDetails": { + "type": "object", + "properties": { + "unitedNationsRegulatoryId": { + "type": "string", + "description": "The specific UNID of the item being shipped.", + "pattern": "^(UN|ID|NA)[0-9]{4}$" + }, + "transportationRegulatoryClass": { + "type": "string", + "description": "The specific regulatory class of the item being shipped.", + "pattern": "^[1-9](\\.[1-9])?$" + }, + "packingGroup": { + "type": "string", + "description": "The specific packaging group of the item being shipped.", + "enum": [ + "I", + "II", + "III" + ], + "x-docgen-enum-table-extension": [ + { + "value": "I", + "description": "Packing group I indicates great danger." + }, + { + "value": "II", + "description": "Packing group II indicates medium danger." + }, + { + "value": "III", + "description": "Packing group III indicates minor danger." + } + ] + }, + "packingInstruction": { + "type": "string", + "description": "The specific packing instruction of the item being shipped.", + "enum": [ + "PI965_SECTION_IA", + "PI965_SECTION_IB", + "PI965_SECTION_II", + "PI966_SECTION_I", + "PI966_SECTION_II", + "PI967_SECTION_I", + "PI967_SECTION_II", + "PI968_SECTION_IA", + "PI968_SECTION_IB", + "PI969_SECTION_I", + "PI969_SECTION_II", + "PI970_SECTION_I", + "PI970_SECTION_II" + ], + "x-docgen-enum-table-extension": [ + { + "value": "PI965_SECTION_IA", + "description": "Ion PI965 Section IA (LiBa)" + }, + { + "value": "PI965_SECTION_IB", + "description": "Ion PI965 Section IB (LiBa)" + }, + { + "value": "PI965_SECTION_II", + "description": "Ion PI965 Section II (LiBa)" + }, + { + "value": "PI966_SECTION_I", + "description": "Ion PI966 Section I (LiBa with equipment)" + }, + { + "value": "PI966_SECTION_II", + "description": "Ion PI966 Section II (LiBa with equipment)" + }, + { + "value": "PI967_SECTION_I", + "description": "Ion PI967 Section I (LiBa in equipment)" + }, + { + "value": "PI967_SECTION_II", + "description": "Ion PI967 Section II (LiBa in equipment)" + }, + { + "value": "PI968_SECTION_IA", + "description": "Metal PI968 Section IA (LiBa)" + }, + { + "value": "PI968_SECTION_IB", + "description": "Metal PI968 Section IB (LiBa)" + }, + { + "value": "PI969_SECTION_I", + "description": "Metal PI969 Section I (LiBa with equipment)" + }, + { + "value": "PI969_SECTION_II", + "description": "Metal PI969 Section II (LiBa with equipment)" + }, + { + "value": "PI970_SECTION_I", + "description": "Metal PI970 Section I (LiBa in equipment)" + }, + { + "value": "PI970_SECTION_II", + "description": "Metal PI970 Section II (LiBa in equipment)" + } + ] + } + }, + "description": "Details related to any dangerous goods/items that are being shipped." + }, + "TaxType": { + "type": "string", + "description": "Indicates the type of tax.", + "enum": [ + "GST" + ], + "x-docgen-enum-table-extension": [ + { + "value": "GST", + "description": "Goods and Services Tax." + } + ] + }, + "TaxDetail": { + "description": "Indicates the tax specifications associated with the shipment for customs compliance purposes in certain regions.", + "type": "object", + "required": [ + "taxRegistrationNumber", + "taxType" + ], + "properties": { + "taxType": { + "$ref": "#/definitions/TaxType" + }, + "taxRegistrationNumber": { + "description": "The shipper's tax registration number associated with the shipment for customs compliance purposes in certain regions.", + "type": "string" + } + } + }, + "TaxDetailList": { + "type": "array", + "description": "A list of tax detail information.", + "items": { + "$ref": "#/definitions/TaxDetail" + } + }, + "GoodsOwner": { + "type": "object", + "description": "The seller owning the goods before handing them over to the carrier", + "required": [ + "merchantId" + ], + "properties": { + "merchantId": { + "description": "The Amazon Merchant Identifier (merchantId) of the seller owning the goods before handing them over to the carrier", + "$ref": "#/definitions/MerchantId" + } + } + }, + "EventCode": { + "type": "string", + "description": "The tracking event type.", + "enum": [ + "ReadyForReceive", + "PickupDone", + "Delivered", + "Departed", + "DeliveryAttempted", + "Lost", + "OutForDelivery", + "ArrivedAtCarrierFacility", + "Rejected", + "Undeliverable", + "PickupCancelled", + "ReturnInitiated", + "AvailableForPickup", + "RecipientRequestedAlternateDeliveryTiming" + ], + "x-docgen-enum-table-extension": [ + { + "value": "ReadyForReceive", + "description": "Package has been created and is ready for pickup at the shippers location. This is a pre-transit status event code." + }, + { + "value": "PickupDone", + "description": "Package has been picked up by the service provider." + }, + { + "value": "Delivered", + "description": "Package has been delivered." + }, + { + "value": "Departed", + "description": "Package has departed from a particular location in carrier network." + }, + { + "value": "DeliveryAttempted", + "description": "Delivery was attempted, but was unsuccessful." + }, + { + "value": "Lost", + "description": "Package is lost." + }, + { + "value": "OutForDelivery", + "description": "Package is out for delivery." + }, + { + "value": "ArrivedAtCarrierFacility", + "description": "Package is in transit and has been received at a carrier location." + }, + { + "value": "Rejected", + "description": "Package was rejected by the recipient." + }, + { + "value": "Undeliverable", + "description": "Package is undeliverable." + }, + { + "value": "PickupCancelled", + "description": "Pickup scheduled for the package was cancelled." + }, + { + "value": "ReturnInitiated", + "description": "Return label has been generated." + }, + { + "value": "AvailableForPickup", + "description": "Package held for recipient pickup." + }, + { + "value": "RecipientRequestedAlternateDeliveryTiming", + "description": "Recipient has requested an update to their delivery date." + } + ] + }, + "Event": { + "type": "object", + "required": [ + "eventCode", + "eventTime" + ], + "properties": { + "eventCode": { + "$ref": "#/definitions/EventCode" + }, + "location": { + "$ref": "#/definitions/Location" + }, + "eventTime": { + "type": "string", + "format": "date-time", + "description": "The ISO 8601 formatted timestamp of the event." + }, + "shipmentType": { + "$ref": "#/definitions/ShipmentType" + } + }, + "description": "A tracking event." + }, + "TrackingSummary": { + "type": "object", + "properties": { + "status": { + "$ref": "#/definitions/Status" + }, + "trackingDetailCodes": { + "$ref": "#/definitions/TrackingDetailCodes" + } + }, + "description": "A package status summary." + }, + "Status": { + "type": "string", + "description": "The status of the package being shipped.", + "enum": [ + "PreTransit", + "InTransit", + "Delivered", + "Lost", + "OutForDelivery", + "Rejected", + "Undeliverable", + "DeliveryAttempted", + "PickupCancelled", + "AwaitingCustomerPickup" + ], + "x-docgen-enum-table-extension": [ + { + "value": "PreTransit", + "description": "Package has been created but has not been picked up." + }, + { + "value": "InTransit", + "description": "Package has been picked up and is in transit." + }, + { + "value": "Delivered", + "description": "Package has has been delivered successfully." + }, + { + "value": "Lost", + "description": "Package is lost." + }, + { + "value": "OutForDelivery", + "description": "Package is out for delivery." + }, + { + "value": "Rejected", + "description": "Package has been rejected by the recipient." + }, + { + "value": "Undeliverable", + "description": "Package was undeliverable." + }, + { + "value": "DeliveryAttempted", + "description": "Delivery was attempted to the recipient location, but was not delivered." + }, + { + "value": "PickupCancelled", + "description": "Pickup was cancelled for the package." + }, + { + "value": "AwaitingCustomerPickup", + "description": "Package held at facility for the customer pickup." + } + ] + }, + "AmazonOrderDetails": { + "type": "object", + "required": [ + "orderId" + ], + "properties": { + "orderId": { + "type": "string", + "description": "The Amazon order ID associated with the Amazon order fulfilled by this shipment." + } + }, + "description": "Amazon order information. This is required if the shipment source channel is Amazon." + }, + "AmazonShipmentDetails": { + "type": "object", + "required": [ + "shipmentId" + ], + "properties": { + "shipmentId": { + "type": "string", + "description": "This attribute is required only for a Direct Fulfillment shipment. This is the encrypted shipment ID." + } + }, + "description": "Amazon shipment information." + }, + "ChannelType": { + "type": "string", + "description": "The shipment source channel type.", + "enum": [ + "AMAZON", + "EXTERNAL" + ], + "x-docgen-enum-table-extension": [ + { + "value": "AMAZON", + "description": "Indicates that the shipment originates from an Amazon order." + }, + { + "value": "EXTERNAL", + "description": "Indicates that the shipment originates from a non-Amazon channel." + } + ] + }, + "ChannelDetails": { + "type": "object", + "required": [ + "channelType" + ], + "properties": { + "channelType": { + "$ref": "#/definitions/ChannelType" + }, + "amazonOrderDetails": { + "$ref": "#/definitions/AmazonOrderDetails" + }, + "amazonShipmentDetails": { + "$ref": "#/definitions/AmazonShipmentDetails" + } + }, + "description": "Shipment source channel related information." + }, + "RateList": { + "type": "array", + "description": "A list of eligible shipping service offerings.", + "items": { + "$ref": "#/definitions/Rate" + } + }, + "Rate": { + "type": "object", + "required": [ + "carrierId", + "carrierName", + "promise", + "rateId", + "requiresAdditionalInputs", + "serviceId", + "serviceName", + "supportedDocumentSpecifications", + "totalCharge" + ], + "properties": { + "rateId": { + "$ref": "#/definitions/RateId" + }, + "carrierId": { + "$ref": "#/definitions/CarrierId" + }, + "carrierName": { + "$ref": "#/definitions/CarrierName" + }, + "serviceId": { + "$ref": "#/definitions/ServiceId" + }, + "serviceName": { + "$ref": "#/definitions/ServiceName" + }, + "billedWeight": { + "$ref": "#/definitions/Weight" + }, + "totalCharge": { + "$ref": "#/definitions/Currency" + }, + "promise": { + "$ref": "#/definitions/Promise" + }, + "supportedDocumentSpecifications": { + "$ref": "#/definitions/SupportedDocumentSpecificationList" + }, + "availableValueAddedServiceGroups": { + "$ref": "#/definitions/AvailableValueAddedServiceGroupList" + }, + "requiresAdditionalInputs": { + "type": "boolean", + "description": "When true, indicates that additional inputs are required to purchase this shipment service. You must then call the getAdditionalInputs operation to return the JSON schema to use when providing the additional inputs to the purchaseShipment operation." + }, + "rateItemList": { + "$ref": "#/definitions/RateItemList" + }, + "paymentType": { + "$ref": "#/definitions/PaymentType" + }, + "benefits": { + "$ref": "#/definitions/Benefits" + } + }, + "description": "The details of a shipping service offering." + }, + "IneligibilityReasonCode": { + "type": "string", + "description": "Reasons that make a shipment service offering ineligible.", + "enum": [ + "NO_COVERAGE", + "PICKUP_SLOT_RESTRICTION", + "UNSUPPORTED_VAS", + "VAS_COMBINATION_RESTRICTION", + "SIZE_RESTRICTIONS", + "WEIGHT_RESTRICTIONS", + "LATE_DELIVERY", + "PROGRAM_CONSTRAINTS", + "TERMS_AND_CONDITIONS_NOT_ACCEPTED", + "UNKNOWN" + ], + "x-docgen-enum-table-extension": [ + { + "value": "NO_COVERAGE", + "description": "The shipment is ineligible because there is no coverage to that address." + }, + { + "value": "PICKUP_SLOT_RESTRICTION", + "description": "The shipment is ineligible because there is an issue with the pickup slot." + }, + { + "value": "UNSUPPORTED_VAS", + "description": "The shipment is ineligible because the value-added service is invalid for this shipment." + }, + { + "value": "VAS_COMBINATION_RESTRICTION", + "description": "The shipment is ineligible because an invalid combination of value-added services were chosen." + }, + { + "value": "SIZE_RESTRICTIONS", + "description": "The shipment is ineligible because the package dimensions are unsupported." + }, + { + "value": "WEIGHT_RESTRICTIONS", + "description": "The shipment is ineligible because the weight is unsupported." + }, + { + "value": "LATE_DELIVERY", + "description": "The shipment is ineligible because delivery is too late." + }, + { + "value": "PROGRAM_CONSTRAINTS", + "description": "The shipment is ineligible because of program constraints." + }, + { + "value": "TERMS_AND_CONDITIONS_NOT_ACCEPTED", + "description": "The shipment is ineligible because terms and conditions have not been accepted by the carrier." + }, + { + "value": "UNKNOWN", + "description": "The ineligibility reason is unknown." + } + ] + }, + "IneligibilityReason": { + "description": "The reason why a shipping service offering is ineligible.", + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "$ref": "#/definitions/IneligibilityReasonCode" + }, + "message": { + "type": "string", + "description": "The ineligibility reason." + } + } + }, + "IneligibleRate": { + "description": "Detailed information for an ineligible shipping service offering.", + "type": "object", + "required": [ + "carrierId", + "carrierName", + "ineligibilityReasons", + "serviceId", + "serviceName" + ], + "properties": { + "serviceId": { + "$ref": "#/definitions/ServiceId" + }, + "serviceName": { + "$ref": "#/definitions/ServiceName" + }, + "carrierName": { + "$ref": "#/definitions/CarrierName" + }, + "carrierId": { + "$ref": "#/definitions/CarrierId" + }, + "ineligibilityReasons": { + "type": "array", + "description": "A list of reasons why a shipping service offering is ineligible.", + "items": { + "$ref": "#/definitions/IneligibilityReason" + } + } + } + }, + "IneligibleRateList": { + "type": "array", + "description": "A list of ineligible shipping service offerings.", + "items": { + "$ref": "#/definitions/IneligibleRate" + } + }, + "CancelShipmentResult": { + "type": "object", + "description": "The payload for the cancelShipment operation.", + "additionalProperties": {} + }, + "CancelShipmentResponse": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/definitions/CancelShipmentResult" + } + }, + "description": "Response schema for the cancelShipment operation." + }, + "GetRatesRequest": { + "description": "The request schema for the getRates operation. When the channelType is Amazon, the shipTo address is not required and will be ignored.", + "type": "object", + "required": [ + "channelDetails", + "packages", + "shipFrom" + ], + "properties": { + "shipTo": { + "description": "The ship to address.", + "$ref": "#/definitions/Address" + }, + "shipFrom": { + "description": "The ship from address.", + "$ref": "#/definitions/Address" + }, + "returnTo": { + "description": "The return to address.", + "$ref": "#/definitions/Address" + }, + "shipDate": { + "type": "string", + "format": "date-time", + "description": "The ship date and time (the requested pickup). This defaults to the current date and time." + }, + "shipperInstruction": { + "$ref": "#/definitions/ShipperInstruction", + "description": "This field describe shipper instruction." + }, + "packages": { + "$ref": "#/definitions/PackageList" + }, + "valueAddedServices": { + "$ref": "#/definitions/ValueAddedServiceDetails" + }, + "taxDetails": { + "$ref": "#/definitions/TaxDetailList" + }, + "channelDetails": { + "$ref": "#/definitions/ChannelDetails" + }, + "clientReferenceDetails": { + "$ref": "#/definitions/ClientReferenceDetails" + }, + "shipmentType": { + "$ref": "#/definitions/ShipmentType" + }, + "destinationAccessPointDetails": { + "$ref": "#/definitions/AccessPointDetails" + }, + "carrierAccounts": { + "$ref": "#/definitions/CarrierAccounts" + } + } + }, + "AccessPointDetails": { + "type": "object", + "description": "AccessPointDetails object", + "properties": { + "accessPointId": { + "$ref": "#/definitions/AccessPointId" + } + } + }, + "NdrAction": { + "type": "string", + "description": "The type of NDR action shipper wants to take for a particular shipment.", + "enum": [ + "RESCHEDULE", + "REATTEMPT", + "RTO" + ], + "x-docgen-enum-table-extension": [ + { + "value": "RESCHEDULE", + "description": "Seller wants to reschedule shipment delivery to a specific date." + }, + { + "value": "REATTEMPT", + "description": "Seller wants to re-attempt delivery with additional delivery notes." + }, + { + "value": "RTO", + "description": "Seller wants the shipment to return to the origin." + } + ] + }, + "NdrRequestData": { + "type": "object", + "description": "Additional information required for the NDR action that has been filed. If the NDR Action is RESCHEDULE, rescheduleDate is a required field. Otherwise, if the NDR Action is REATTEMPT, additionalAddressNotes is a required field. ", + "properties": { + "rescheduleDate": { + "type": "string", + "format": "date-time", + "description": "The date on which the Seller wants to reschedule shipment delivery, in ISO-8601 date/time format", + "example": "2023-12-12T05:24:00.00Z" + }, + "additionalAddressNotes": { + "$ref": "#/definitions/AdditionalAddressNotes" + } + } + }, + "ClaimReason": { + "type": "string", + "description": "The reason for which shipper is filing the claim for a particular shipment.", + "enum": [ + "LOST_IN_TRANSIT", + "DAMAGED_IN_TRANSIT", + "DELIVERED_NOT_RECEIVED", + "ITEM_MISSING_SWITCHEROO", + "COD_ABUSE" + ], + "x-docgen-enum-table-extension": [ + { + "value": "LOST_IN_TRANSIT", + "description": "Shipment was lost in-transit" + }, + { + "value": "DAMAGED_IN_TRANSIT", + "description": "Shipment was received in damaged condition" + }, + { + "value": "DELIVERED_NOT_RECEIVED", + "description": "Shipment was delivered but is not received" + }, + { + "value": "ITEM_MISSING_SWITCHEROO", + "description": "Shipment was delivered but the content is missing/different" + }, + { + "value": "COD_ABUSE", + "description": "Cash was collected from customer but delivery agent marked it rejected" + } + ] + }, + "ClaimProofURLs": { + "type": "array", + "description": "A list of proof URLs for a claim. Basic URL validation will happen for each URLs present in the list", + "items": { + "type": "string" + } + }, + "SettlementType": { + "type": "string", + "description": "Type of settlement the shipper wants to receive for a particular shipment.", + "default": "REFUND", + "enum": [ + "REFUND", + "CERTIFICATE_OF_FACT" + ], + "x-docgen-enum-table-extension": [ + { + "value": "REFUND", + "description": "File a claim for item value to get refund." + }, + { + "value": "CERTIFICATE_OF_FACT", + "description": "File a claim to request for a COF[Certificate Of Fact]." + } + ] + }, + "GetRatesResult": { + "type": "object", + "required": [ + "rates", + "requestToken" + ], + "properties": { + "requestToken": { + "$ref": "#/definitions/RequestToken" + }, + "rates": { + "$ref": "#/definitions/RateList" + }, + "ineligibleRates": { + "$ref": "#/definitions/IneligibleRateList" + } + }, + "description": "The payload for the getRates operation." + }, + "GetRatesResponse": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/definitions/GetRatesResult" + } + }, + "description": "The response schema for the getRates operation." + }, + "DirectPurchaseRequest": { + "description": "The request schema for the directPurchaseShipment operation. When the channel type is Amazon, the shipTo address is not required and will be ignored.", + "type": "object", + "required": [ + "channelDetails" + ], + "properties": { + "shipTo": { + "description": "The address where the shipment will be delivered. For vendor orders, shipTo information is pulled directly from the Amazon order.", + "$ref": "#/definitions/Address" + }, + "shipFrom": { + "description": "The address where the package will be picked up.", + "$ref": "#/definitions/Address" + }, + "returnTo": { + "description": "The address where the package will be returned if it cannot be delivered.", + "$ref": "#/definitions/Address" + }, + "packages": { + "$ref": "#/definitions/PackageList" + }, + "channelDetails": { + "$ref": "#/definitions/ChannelDetails" + }, + "labelSpecifications": { + "description": "The document (label) specifications requested. The default label returned is PNG DPI 203 4x6 if no label specification is provided. Requesting an invalid file format results in a failure.", + "$ref": "#/definitions/RequestedDocumentSpecification" + } + } + }, + "DirectPurchaseResult": { + "type": "object", + "required": [ + "shipmentId" + ], + "properties": { + "shipmentId": { + "$ref": "#/definitions/ShipmentId" + }, + "packageDocumentDetailList": { + "$ref": "#/definitions/PackageDocumentDetailList" + } + }, + "description": "The payload for the directPurchaseShipment operation." + }, + "DirectPurchaseResponse": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/definitions/DirectPurchaseResult" + } + }, + "description": "The response schema for the directPurchaseShipment operation." + }, + "GetShipmentDocumentsResult": { + "type": "object", + "required": [ + "packageDocumentDetail", + "shipmentId" + ], + "properties": { + "shipmentId": { + "$ref": "#/definitions/ShipmentId" + }, + "packageDocumentDetail": { + "$ref": "#/definitions/PackageDocumentDetail" + }, + "benefits": { + "$ref": "#/definitions/Benefits" + } + }, + "description": "The payload for the getShipmentDocuments operation." + }, + "GetShipmentDocumentsResponse": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/definitions/GetShipmentDocumentsResult" + } + }, + "description": "The response schema for the the getShipmentDocuments operation." + }, + "GetTrackingResult": { + "type": "object", + "required": [ + "eventHistory", + "promisedDeliveryDate", + "summary", + "trackingId", + "alternateLegTrackingId" + ], + "properties": { + "trackingId": { + "$ref": "#/definitions/TrackingId" + }, + "alternateLegTrackingId": { + "$ref": "#/definitions/AlternateLegTrackingId" + }, + "eventHistory": { + "type": "array", + "description": "A list of tracking events.", + "items": { + "$ref": "#/definitions/Event" + } + }, + "promisedDeliveryDate": { + "type": "string", + "format": "date-time", + "description": "The date and time by which the shipment is promised to be delivered." + }, + "summary": { + "$ref": "#/definitions/TrackingSummary" + } + }, + "description": "The payload for the getTracking operation." + }, + "SubmitNdrFeedbackRequest": { + "type": "object", + "description": "The request schema for the NdrFeedback operation", + "required": [ + "trackingId", + "ndrAction" + ], + "properties": { + "trackingId": { + "$ref": "#/definitions/TrackingId" + }, + "ndrAction": { + "$ref": "#/definitions/NdrAction" + }, + "ndrRequestData": { + "$ref": "#/definitions/NdrRequestData" + } + } + }, + "CreateClaimRequest": { + "type": "object", + "description": "The request schema for the CreateClaim operation", + "required": [ + "trackingId", + "claimReason", + "settlementType" + ], + "properties": { + "trackingId": { + "$ref": "#/definitions/TrackingId" + }, + "declaredValue": { + "$ref": "#/definitions/Currency", + "description": "This is required for off-Amazon shipments to determine value of shipments" + }, + "claimReason": { + "$ref": "#/definitions/ClaimReason" + }, + "isReplacementPackageSent": { + "type": "boolean", + "description": "Applicable for only On Amazon shipments to identify if replacement was sent" + }, + "proofs": { + "$ref": "#/definitions/ClaimProofURLs" + }, + "settlementType": { + "$ref": "#/definitions/SettlementType" + } + } + }, + "GetTrackingResponse": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/definitions/GetTrackingResult" + } + }, + "description": "The response schema for the getTracking operation." + }, + "CreateClaimResponse": { + "type": "object", + "properties": { + "claimId": { + "$ref": "#/definitions/ClaimId" + } + }, + "description": "The response schema for the createClaim operation." + }, + "PurchaseShipmentRequest": { + "description": "The request schema for the purchaseShipment operation.", + "type": "object", + "required": [ + "rateId", + "requestedDocumentSpecification", + "requestToken" + ], + "properties": { + "requestToken": { + "$ref": "#/definitions/RequestToken" + }, + "rateId": { + "$ref": "#/definitions/RateId" + }, + "requestedDocumentSpecification": { + "$ref": "#/definitions/RequestedDocumentSpecification" + }, + "requestedValueAddedServices": { + "$ref": "#/definitions/RequestedValueAddedServiceList" + }, + "additionalInputs": { + "type": "object", + "additionalProperties": {}, + "description": "The additional inputs required to purchase a shipping offering, in JSON format. The JSON provided here must adhere to the JSON schema that is returned in the response to the getAdditionalInputs operation.\n\nAdditional inputs are only required when indicated by the requiresAdditionalInputs property in the response to the getRates operation." + } + } + }, + "PurchaseShipmentResult": { + "type": "object", + "required": [ + "packageDocumentDetails", + "promise", + "shipmentId" + ], + "properties": { + "shipmentId": { + "$ref": "#/definitions/ShipmentId" + }, + "packageDocumentDetails": { + "$ref": "#/definitions/PackageDocumentDetailList" + }, + "promise": { + "$ref": "#/definitions/Promise" + }, + "benefits": { + "$ref": "#/definitions/Benefits" + } + }, + "description": "The payload for the purchaseShipment operation." + }, + "PurchaseShipmentResponse": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/definitions/PurchaseShipmentResult" + } + }, + "description": "The response schema for the purchaseShipment operation." + }, + "OneClickShipmentRequest": { + "description": "The request schema for the OneClickShipment operation. When the channelType is not Amazon, shipTo is required and when channelType is Amazon shipTo is ignored.", + "type": "object", + "required": [ + "channelDetails", + "packages", + "shipFrom", + "labelSpecifications", + "serviceSelection" + ], + "properties": { + "shipTo": { + "description": "The ship to address.", + "$ref": "#/definitions/Address" + }, + "shipFrom": { + "description": "The ship from address.", + "$ref": "#/definitions/Address" + }, + "returnTo": { + "description": "The return to address.", + "$ref": "#/definitions/Address" + }, + "shipDate": { + "type": "string", + "format": "date-time", + "description": "The ship date and time (the requested pickup). This defaults to the current date and time." + }, + "goodsOwner": { + "description": "The seller owning the goods before handing them over to the carrier", + "$ref": "#/definitions/GoodsOwner" + }, + "packages": { + "$ref": "#/definitions/PackageList" + }, + "valueAddedServicesDetails": { + "$ref": "#/definitions/OneClickShipmentValueAddedServiceDetails" + }, + "taxDetails": { + "$ref": "#/definitions/TaxDetailList" + }, + "channelDetails": { + "$ref": "#/definitions/ChannelDetails" + }, + "labelSpecifications": { + "$ref": "#/definitions/RequestedDocumentSpecification" + }, + "serviceSelection": { + "$ref": "#/definitions/ServiceSelection" + }, + "shipperInstruction": { + "$ref": "#/definitions/ShipperInstruction", + "description": "Optional field for shipper instruction." + }, + "destinationAccessPointDetails": { + "$ref": "#/definitions/AccessPointDetails" + } + } + }, + "OneClickShipmentResponse": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/definitions/OneClickShipmentResult" + } + }, + "description": "The response schema for the OneClickShipment operation." + }, + "OneClickShipmentResult": { + "type": "object", + "required": [ + "packageDocumentDetails", + "promise", + "shipmentId", + "service", + "carrier", + "totalCharge" + ], + "properties": { + "shipmentId": { + "$ref": "#/definitions/ShipmentId" + }, + "packageDocumentDetails": { + "$ref": "#/definitions/PackageDocumentDetailList" + }, + "promise": { + "$ref": "#/definitions/Promise" + }, + "carrier": { + "$ref": "#/definitions/Carrier" + }, + "service": { + "$ref": "#/definitions/Service" + }, + "totalCharge": { + "$ref": "#/definitions/Currency" + } + }, + "description": "The payload for the OneClickShipment API." + }, + "GetAccessPointsResponse": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/definitions/GetAccessPointsResult" + } + }, + "description": "The response schema for the GetAccessPoints operation." + }, + "GetAccessPointsResult": { + "type": "object", + "required": [ + "accessPointsMap" + ], + "properties": { + "accessPointsMap": { + "$ref": "#/definitions/AccessPointsMap" + } + }, + "description": "The payload for the GetAccessPoints API." + }, + "AccessPointType": { + "description": "The type of access point, like counter (HELIX), lockers, etc.", + "type": "string", + "enum": [ + "HELIX", + "CAMPUS_LOCKER", + "OMNI_LOCKER", + "ODIN_LOCKER", + "DOBBY_LOCKER", + "CORE_LOCKER", + "3P", + "CAMPUS_ROOM" + ], + "x-docgen-enum-table-extension": [ + { + "value": "HELIX", + "description": "Counter, aka Helix, is an assisted access point where customers interact with a store associate to collect or return packages. Counter stores do not have Locker" + }, + { + "value": "CAMPUS_LOCKER", + "description": "It denotes a physical Locker present at Locker+ stores" + }, + { + "value": "OMNI_LOCKER", + "description": "Omni lockers are similar to core lockers installed at apartment buildings. Core and Omni will have common base hardware platform" + }, + { + "value": "ODIN_LOCKER", + "description": "Odin lockers rely on Bluetooth connection as there is no screen or scanner on the locker. Drivers, customers, and even technicians can interact with the locker using an app (different depending on the user)." + }, + { + "value": "DOBBY_LOCKER", + "description": "Dobby lockers are Odin lockers installed at apartment complexes and available only to residents." + }, + { + "value": "CORE_LOCKER", + "description": "Core lockers are the traditional and initial style of lockers when Hub was launched. Core lockers have a screen, keypad and scanner. Customer either enter their pickup code using the Locker screen or scan pickup code barcode to pick up their package." + }, + { + "value": "3P", + "description": "Networks that are owned end-to-end by commercial carriers (CC - e.g. DHL, UPS, DPD and Postal organizations), who operate both the transportation leg and the access point aspect. These networks offer a range of pickup points including: 3P Lockers, legacy Postal Offices, 3P Counters (e.g. Gas Stations and Retail Outlets - supermarkets, bookstores, news-stands etc)." + }, + { + "value": "CAMPUS_ROOM", + "description": "Locker+, formerly known as Campus, are attended, physical stores, where the Locker is used dynamically by the staff to keep packages ready for customer pickup. Locker+ store can have multiple Lockers installed," + } + ] + }, + "AccessPointsMap": { + "description": "Map of type of access point to list of access points", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/AccessPointList" + } + }, + "AccessPointList": { + "description": "List of relevant Access points requested by shipper. These access points are sorted by proximity to postal code, and are limited to 40. We have internally defined a radius value to render relevant results.", + "type": "array", + "items": { + "$ref": "#/definitions/AccessPoint" + } + }, + "AccessPointId": { + "description": "Unique identifier for the access point", + "type": "string" + }, + "AccessPoint": { + "type": "object", + "description": "Access point details", + "properties": { + "accessPointId": { + "$ref": "#/definitions/AccessPointId" + }, + "name": { + "description": "Name of entity (store/hub etc) where this access point is located", + "type": "string" + }, + "timezone": { + "description": "Timezone of access point", + "type": "string" + }, + "type": { + "$ref": "#/definitions/AccessPointType" + }, + "accessibilityAttributes": { + "$ref": "#/definitions/AccessibilityAttributes" + }, + "address": { + "$ref": "#/definitions/Address" + }, + "exceptionOperatingHours": { + "type": "array", + "description": "Exception operating hours for Access Point", + "items": { + "$ref": "#/definitions/ExceptionOperatingHours" + } + }, + "assistanceType": { + "type": "string", + "description": "Assistance type enum for Access point i.e. STAFF_ASSISTED or SELF_ASSISTED", + "enum": [ + "STAFF_ASSISTED", + "SELF_ASSISTED" + ] + }, + "score": { + "type": "string", + "description": "The score of access point, based on proximity to postal code and sorting preference. This can be used to sort access point results on shipper's end." + }, + "standardOperatingHours": { + "$ref": "#/definitions/DayOfWeekTimeMap" + } + } + }, + "AccessibilityAttributes": { + "type": "object", + "description": "Defines the accessibility details of the access point.", + "properties": { + "distance": { + "description": "The approximate distance of access point from input postalCode's centroid.", + "type": "string" + }, + "driveTime": { + "description": "The approximate (static) drive time from input postal code's centroid.", + "type": "integer" + } + } + }, + "OperatingHours": { + "type": "object", + "description": "The hours in which the access point shall remain operational", + "properties": { + "closingTime": { + "$ref": "#/definitions/TimeOfDay" + }, + "openingTime": { + "$ref": "#/definitions/TimeOfDay" + }, + "midDayClosures": { + "type": "array", + "description": "midDayClosures operating hours array", + "items": { + "$ref": "#/definitions/TimeOfDay" + } + } + } + }, + "TimeOfDay": { + "type": "object", + "description": "Denotes time of the day, used for defining opening or closing time of access points", + "properties": { + "hourOfDay": { + "type": "integer", + "description": "Denotes hour of the day, used for defining opening or closing time of access points" + }, + "minuteOfHour": { + "type": "integer", + "description": "Denotes minute of the hour, used for defining opening or closing time of access points" + }, + "secondOfMinute": { + "type": "integer", + "description": "Denotes second of the minute, used for defining opening or closing time of access points" + } + } + }, + "DayOfWeekTimeMap": { + "description": "Map of day of the week to operating hours of that day", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/OperatingHours" + } + }, + "ExceptionOperatingHours": { + "type": "object", + "description": "Defines exceptions to standard operating hours for certain date ranges.", + "properties": { + "dateRange": { + "$ref": "#/definitions/DateRange" + }, + "operatingHours": { + "$ref": "#/definitions/OperatingHours" + } + } + }, + "GetAdditionalInputsResult": { + "type": "object", + "description": "The JSON schema to use to provide additional inputs when required to purchase a shipping offering.", + "additionalProperties": {} + }, + "GetAdditionalInputsResponse": { + "type": "object", + "properties": { + "payload": { + "$ref": "#/definitions/GetAdditionalInputsResult" + } + }, + "description": "The response schema for the getAdditionalInputs operation." + }, + "GetCarrierAccountsRequest": { + "description": "The request schema for the GetCarrierAccounts operation.", + "type": "object", + "properties": { + "clientReferenceDetails": { + "$ref": "#/definitions/ClientReferenceDetails" + } + } + }, + "LinkCarrierAccountRequest": { + "description": "The request schema for verify and add the merchant's account with a certain carrier.", + "type": "object", + "required": [ + "carrierAccountAttributes", + "carrierAccountType" + ], + "properties": { + "clientReferenceDetails": { + "$ref": "#/definitions/ClientReferenceDetails" + }, + "carrierAccountType": { + "$ref": "#/definitions/CarrierAccountType" + }, + "carrierAccountAttributes": { + "$ref": "#/definitions/CarrierAccountAttributes" + }, + "encryptedCarrierAccountAttributes": { + "$ref": "#/definitions/CarrierAccountAttributes" + } + } + }, + "UnlinkCarrierAccountRequest": { + "description": "The request schema for remove the Carrier Account associated with the provided merchant.", + "type": "object", + "properties": { + "clientReferenceDetails": { + "$ref": "#/definitions/ClientReferenceDetails" + }, + "accountId": { + "$ref": "#/definitions/AccountId" + } + } + }, + "GenerateCollectionFormRequest": { + "description": "The request schema Call to generate the collection form.", + "type": "object", + "required": [ + "carrierId" + ], + "properties": { + "clientReferenceDetails": { + "$ref": "#/definitions/ClientReferenceDetails" + }, + "carrierId": { + "$ref": "#/definitions/CarrierId" + }, + "shipFromAddress": { + "$ref": "#/definitions/Address" + } + } + }, + "GetCollectionFormHistoryRequest": { + "description": "The request schema to get query collections form history API .", + "type": "object", + "properties": { + "clientReferenceDetails": { + "$ref": "#/definitions/ClientReferenceDetails" + }, + "maxResults": { + "type": "integer", + "description": "max Number of Results for query ." + }, + "carrierId": { + "$ref": "#/definitions/CarrierId" + }, + "shipFromAddress": { + "$ref": "#/definitions/Address" + }, + "dateRange": { + "$ref": "#/definitions/DateRange" + } + } + }, + "GetUnmanifestedShipmentsRequest": { + "description": "The request schema for the GetUnmanifestedShipmentsRequest operation.", + "type": "object", + "properties": { + "clientReferenceDetails": { + "$ref": "#/definitions/ClientReferenceDetails" + } + } + }, + "GetCarrierAccountFormInputsResponse": { + "description": "The Response for the GetCarrierAccountFormInputsResponse operation.", + "type": "object", + "properties": { + "linkableCarriersList": { + "$ref": "#/definitions/LinkableCarriersList" + } + } + }, + "GetCarrierAccountsResponse": { + "description": "The Response for the GetCarrierAccountsResponse operation.", + "type": "object", + "required": [ + "activeAccounts" + ], + "properties": { + "activeAccounts": { + "$ref": "#/definitions/ActiveAccounts" + } + } + }, + "LinkCarrierAccountResponse": { + "description": "The Response for the LinkCarrierAccount operation.", + "type": "object", + "properties": { + "registrationStatus": { + "$ref": "#/definitions/AccountStatus" + }, + "accountId": { + "$ref": "#/definitions/AccountId" + } + } + }, + "UnlinkCarrierAccountResponse": { + "description": "The Response for the UnlinkCarrierAccountResponse operation.", + "type": "object", + "properties": { + "isUnlinked": { + "type": "boolean", + "description": "Is Carrier unlinked from Merchant" + } + } + }, + "GenerateCollectionFormResponse": { + "description": "The Response for the GenerateCollectionFormResponse operation.", + "type": "object", + "properties": { + "collectionsFormDocument": { + "$ref": "#/definitions/CollectionsFormDocument" + } + } + }, + "GetCollectionFormHistoryResponse": { + "description": "The Response for the GetCollectionFormHistoryResponse operation.", + "type": "object", + "properties": { + "collectionFormsHistoryRecordList": { + "$ref": "#/definitions/CollectionFormsHistoryRecordList" + }, + "lastRefreshedDate": { + "type": "string", + "description": "Last Refereshed Date of collection" + } + } + }, + "GetUnmanifestedShipmentsResponse": { + "description": "The Response for the GetUnmanifestedShipmentsResponse operation.", + "type": "object", + "properties": { + "unmanifestedCarrierInformationList": { + "$ref": "#/definitions/UnmanifestedCarrierInformationList" + } + } + }, + "GetCollectionFormResponse": { + "description": "The Response for the GetCollectionFormResponse operation.", + "type": "object", + "properties": { + "collectionsFormDocument": { + "$ref": "#/definitions/CollectionsFormDocument" + } + } + }, + "ClientReferenceDetails": { + "type": "array", + "description": "Object to pass additional information about the MCI Integrator shipperType: List of ClientReferenceDetail", + "items": { + "$ref": "#/definitions/ClientReferenceDetail" + } + }, + "ClientReferenceDetail": { + "type": "object", + "required": [ + "clientReferenceType", + "clientReferenceId" + ], + "properties": { + "clientReferenceType": { + "type": "string", + "description": "Client Reference type.", + "enum": [ + "IntegratorShipperId", + "IntegratorMerchantId" + ], + "x-docgen-enum-table-extension": [ + { + "value": "IntegratorShipperId", + "description": "The unique identifier assigned to a 3P seller by the shipping integrator." + }, + { + "value": "IntegratorMerchantId", + "description": "The unique identifier assigned to a 3P shipping integrator by Amazon." + } + ] + }, + "clientReferenceId": { + "type": "string", + "description": "The Client Reference Id." + } + }, + "description": "Client Reference Details" + }, + "CollectionFormId": { + "type": "string", + "description": "Collection Form Id for Reprint ." + }, + "ShipmentType": { + "type": "string", + "description": "Shipment type.", + "enum": [ + "FORWARD", + "RETURNS" + ], + "x-docgen-enum-table-extension": [ + { + "value": "FORWARD", + "description": "Additional information about shipment representing package journey from origin address to destination address in the transportation/shipping request." + }, + { + "value": "RETURNS", + "description": "Additional information about shipment representing package journey from destination address to origin/return address in the original transportation/shipping request." + } + ] + }, + "CarrierAccounts": { + "type": "array", + "description": "A list of CarrierAccounts", + "items": { + "$ref": "#/definitions/CarrierAccount" + } + }, + "CarrierAccount": { + "type": "object", + "required": [ + "carrierAccountId", + "carrierId" + ], + "properties": { + "carrierAccountId": { + "$ref": "#/definitions/AccountId" + }, + "carrierId": { + "$ref": "#/definitions/CarrierId" + } + }, + "description": "Carrier Account details used to fetch rates." + }, + "ActiveAccounts": { + "type": "array", + "description": "A list of ActiveAccount", + "items": { + "$ref": "#/definitions/ActiveAccount" + } + }, + "ActiveAccount": { + "type": "object", + "properties": { + "accountId": { + "$ref": "#/definitions/AccountId" + }, + "carrierId": { + "$ref": "#/definitions/CarrierId" + } + }, + "description": "Active Account Details" + }, + "DateRange": { + "type": "object", + "properties": { + "startDate": { + "type": "string", + "format": "dateTime", + "description": "Start Date for query ." + }, + "endDate": { + "type": "string", + "format": "dateTime", + "description": "end date for query." + } + }, + "description": "Date Range for query the results." + }, + "CarrierAccountAttributes": { + "type": "array", + "description": "A list of all attributes required by the carrier in order to successfully link the merchant's account", + "items": { + "$ref": "#/definitions/CarrierAccountAttribute" + } + }, + "CarrierAccountAttribute": { + "type": "object", + "properties": { + "attributeName": { + "type": "string", + "description": "Attribute Name ." + }, + "propertyGroup": { + "type": "string", + "description": "Property Group." + }, + "value": { + "type": "string", + "description": "Value ." + } + }, + "description": "Attribute Properties required by carrier" + }, + "CarrierAccountType": { + "type": "string", + "description": "CarrierAccountType associated with account." + }, + "MerchantId": { + "type": "string", + "description": "merchant Id of provided merchant " + }, + "AccountStatus": { + "type": "string", + "description": "Account Status.", + "enum": [ + "ACTIVE", + "INACTIVE", + "PENDING", + "SUSPENDED" + ], + "x-docgen-enum-table-extension": [ + { + "value": "ACTIVE", + "description": "Seller Account is Active." + }, + { + "value": "INACTIVE", + "description": "Seller Account is InActive." + }, + { + "value": "PENDING", + "description": "Seller Account is Pending." + }, + { + "value": "SUSPENDED", + "description": "Seller Account is Suspended." + } + ] + }, + "CollectionsFormDocument": { + "type": "object", + "properties": { + "base64EncodedContent": { + "type": "string", + "description": "Base64 document Value of Collection." + }, + "documentFormat": { + "type": "string", + "format": "application/pdf", + "description": "Collection Document format is PDF." + } + }, + "description": "Collection Form Document Details" + }, + "CollectionFormsHistoryRecordList": { + "type": "array", + "description": "A list of CollectionFormsHistoryRecord", + "items": { + "$ref": "#/definitions/CollectionFormsHistoryRecord" + } + }, + "CollectionFormsHistoryRecord": { + "type": "object", + "properties": { + "carrierName": { + "$ref": "#/definitions/CarrierName" + }, + "creationDate": { + "type": "string", + "format": "dateTime", + "description": "Creation Time for this account." + }, + "generationStatus": { + "$ref": "#/definitions/GenerationStatus" + }, + "collectionFormId": { + "$ref": "#/definitions/CollectionFormId" + }, + "shipFromAddress": { + "$ref": "#/definitions/Address" + } + }, + "description": "Active Account Details" + }, + "UnmanifestedCarrierInformationList": { + "type": "array", + "description": "A list of UnmanifestedCarrierInformation", + "items": { + "$ref": "#/definitions/UnmanifestedCarrierInformation" + } + }, + "UnmanifestedCarrierInformation": { + "type": "object", + "properties": { + "carrierId": { + "$ref": "#/definitions/CarrierId" + }, + "carrierName": { + "$ref": "#/definitions/CarrierName" + }, + "unmanifestedShipmentLocationList": { + "$ref": "#/definitions/UnmanifestedShipmentLocationList" + } + }, + "description": "UnmanifestedCarrierInformation like carrierId CarrierName and Location" + }, + "UnmanifestedShipmentLocationList": { + "type": "array", + "description": "A list of UnmanifestedShipmentLocation", + "items": { + "$ref": "#/definitions/UnmanifestedShipmentLocation" + } + }, + "UnmanifestedShipmentLocation": { + "type": "object", + "properties": { + "address": { + "$ref": "#/definitions/Address" + }, + "lastManifestDate": { + "type": "string", + "format": "dateTime", + "description": "Its Last Manifest Date." + } + }, + "description": "UnmanifestedShipmentLocation info " + }, + "LinkableCarriersList": { + "type": "array", + "description": "A list of LinkableCarrier", + "items": { + "$ref": "#/definitions/LinkableCarrier" + } + }, + "LinkableCarrier": { + "type": "object", + "properties": { + "carrierId": { + "$ref": "#/definitions/CarrierId" + }, + "linkableAccountTypes": { + "$ref": "#/definitions/LinkableAccountTypeList" + } + }, + "description": "Info About Linkable Carrier" + }, + "LinkableAccountTypeList": { + "type": "array", + "description": "A list of LinkableAccountType", + "items": { + "$ref": "#/definitions/LinkableAccountType" + } + }, + "LinkableAccountType": { + "type": "object", + "properties": { + "accountType": { + "$ref": "#/definitions/AccountType" + }, + "carrierAccountInputs": { + "$ref": "#/definitions/CarrierAccountInputsList" + } + }, + "description": "Info About Linkable Account Type" + }, + "CarrierAccountInputsList": { + "type": "array", + "description": "A list of CarrierAccountInput", + "items": { + "$ref": "#/definitions/CarrierAccountInput" + } + }, + "CarrierAccountInput": { + "type": "object", + "properties": { + "descriptionLocalizationKey": { + "type": "string", + "description": "descriptionLocalizationKey value ." + }, + "name": { + "type": "string", + "description": "name value ." + }, + "groupName": { + "type": "string", + "description": "groupName value ." + }, + "inputType": { + "$ref": "#/definitions/InputType" + }, + "isMandatory": { + "type": "boolean", + "description": "mandatory or not value ." + }, + "isConfidential": { + "type": "boolean", + "description": "is value is Confidential ." + }, + "isHidden": { + "type": "boolean", + "description": "is value is hidden ." + }, + "validationMetadata": { + "$ref": "#/definitions/ValidationMetadataList" + } + }, + "description": "Info About CarrierAccountInput" + }, + "ValidationMetadataList": { + "type": "array", + "description": "A list of ValidationMetadata", + "items": { + "$ref": "#/definitions/ValidationMetadata" + } + }, + "ValidationMetadata": { + "type": "object", + "properties": { + "errorMessage": { + "type": "string", + "description": "errorMessage for the error." + }, + "validationStrategy": { + "type": "string", + "description": "validationStrategy for the error." + }, + "value": { + "type": "string", + "description": "Value." + } + }, + "description": "ValidationMetadata Details" + }, + "GenerationStatus": { + "type": "string", + "description": "Generation Status.", + "enum": [ + "Completed", + "InProgress" + ], + "x-docgen-enum-table-extension": [ + { + "value": "Completed", + "description": "Genertaion is succesfully completed ." + }, + { + "value": "InProgress", + "description": "Genertaion is in Progress." + } + ] + }, + "AccountType": { + "type": "string", + "description": "Shipper Account Type.", + "enum": [ + "SHIPPER_ACCOUNT", + "SHIPPER_ACCOUNT_WITH_INVOICE", + "AMAZON_ACCOUNT" + ], + "x-docgen-enum-table-extension": [ + { + "value": "SHIPPER_ACCOUNT", + "description": "Shipper Account in case of others carriers like : FEDEX, ROYLA_MAIL, DHL etc." + }, + { + "value": "SHIPPER_ACCOUNT_WITH_INVOICE", + "description": "Shipper Account only for UPS ." + }, + { + "value": "AMAZON_ACCOUNT", + "description": "this for default account and will be used if seller dont have any active linked account." + } + ] + }, + "InputType": { + "type": "string", + "description": "Type of Input.", + "enum": [ + "TEXTBOX", + "PASSWORD" + ], + "x-docgen-enum-table-extension": [ + { + "value": "TEXTBOX", + "description": "Input Type is Textbox." + }, + { + "value": "PASSWORD", + "description": "Input Type is Password." + } + ] + }, + "PaymentType": { + "type": "string", + "description": "Payment type of the purchase.", + "enum": [ + "PAY_THROUGH_AMAZON", + "PAY_DIRECT_TO_CARRIER" + ], + "x-docgen-enum-table-extension": [ + { + "value": "PAY_THROUGH_AMAZON", + "description": "Payment Through Amazon." + }, + { + "value": "PAY_DIRECT_TO_CARRIER", + "description": "Payment Direct to Carrier." + } + ] + }, + "RateItemList": { + "type": "array", + "description": "A list of RateItem", + "items": { + "$ref": "#/definitions/RateItem" + } + }, + "RateItem": { + "type": "object", + "properties": { + "rateItemID": { + "$ref": "#/definitions/RateItemID" + }, + "rateItemType": { + "$ref": "#/definitions/RateItemType" + }, + "rateItemCharge": { + "$ref": "#/definitions/Currency" + }, + "rateItemNameLocalization": { + "type": "string", + "description": "Used for the localization." + } + }, + "description": "Rate Item for shipping (base cost, transaction fee, confirmation, insurance, etc.) Data source definition: " + }, + "RateItemID": { + "type": "string", + "description": "Unique ID for the rateItem.", + "enum": [ + "BASE_RATE", + "TRANSACTION_FEE", + "ADULT_SIGNATURE_CONFIRMATION", + "SIGNATURE_CONFIRMATION", + "NO_CONFIRMATION", + "WAIVE_SIGNATURE", + "IMPLIED_LIABILITY", + "HIDDEN_POSTAGE", + "DECLARED_VALUE", + "SUNDAY_HOLIDAY_DELIVERY", + "DELIVERY_CONFIRMATION", + "IMPORT_DUTY_CHARGE", + "VAT", + "NO_SATURDAY_DELIVERY", + "INSURANCE", + "COD", + "FUEL_SURCHARGE", + "INSPECTION_CHARGE", + "DELIVERY_AREA_SURCHARGE", + "WAYBILL_CHARGE", + "AMAZON_SPONSORED_DISCOUNT", + "INTEGRATOR_SPONSORED_DISCOUNT", + "OVERSIZE_SURCHARGE", + "CONGESTION_CHARGE", + "RESIDENTIAL_SURCHARGE", + "ADDITIONAL_SURCHARGE", + "SURCHARGE", + "REBATE", + "HIGH_CUBE_SURCHARGE", + "HIGH_LENGTH_SURCHARGE", + "HIGH_WIDTH_SURCHARGE", + "DEMAND_SURCHARGE", + "NONSTANDARD_FEE" + ], + "x-docgen-enum-table-extension": [ + { + "value": "BASE_RATE", + "description": "for Base Rate." + }, + { + "value": "TRANSACTION_FEE", + "description": "Charge for TransAction Fee." + }, + { + "value": "ADULT_SIGNATURE_CONFIRMATION", + "description": "Charge for Adult Signature Confirmation." + }, + { + "value": "SIGNATURE_CONFIRMATION", + "description": "Charge for signature confirmation." + }, + { + "value": "NO_CONFIRMATION", + "description": "Charge for No Confirmation." + }, + { + "value": "WAIVE_SIGNATURE", + "description": "Charge for Waive Signature." + }, + { + "value": "IMPLIED_LIABILITY", + "description": "Charge for Implied Liability." + }, + { + "value": "HIDDEN_POSTAGE", + "description": "Hidden Postage Charge." + }, + { + "value": "DECLARED_VALUE", + "description": "Declared Valure Coverage Charge." + }, + { + "value": "SUNDAY_HOLIDAY_DELIVERY", + "description": "Sunday Holiday Delivery Charge." + }, + { + "value": "DELIVERY_CONFIRMATION", + "description": "Charge for Delivery Confirmation." + }, + { + "value": "IMPORT_DUTY_CHARGE", + "description": "Import Duty Charge." + }, + { + "value": "VAT", + "description": "Vat Charge." + }, + { + "value": "NO_SATURDAY_DELIVERY", + "description": "Charge for No Satuarday Delivery." + }, + { + "value": "INSURANCE", + "description": "Insurance Charge." + }, + { + "value": "COD", + "description": "COD Charge." + }, + { + "value": "AMAZON_SPONSORED_DISCOUNT", + "description": "Amazon Sponsored Discount." + }, + { + "value": "INTEGRATOR_SPONSORED_DISCOUNT", + "description": "Integrator Sponsored Discount." + }, + { + "value": "FUEL_SURCHARGE", + "description": "Fuel Surcharge." + }, + { + "value": "INSPECTION_CHARGE", + "description": "Inspection Charge." + }, + { + "value": "DELIVERY_AREA_SURCHARGE", + "description": "Delivery Area Surcharge." + }, + { + "value": "WAYBILL_CHARGE", + "description": "WayBill Charge." + }, + { + "value": "OVERSIZE_SURCHARGE", + "description": "OverSize Surcharge." + }, + { + "value": "CONGESTION_CHARGE", + "description": "Congestion Charge." + }, + { + "value": "RESIDENTIAL_SURCHARGE", + "description": "Residential Surcharge." + }, + { + "value": "ADDITIONAL_SURCHARGE", + "description": "Additional Surcharge." + }, + { + "value": "SURCHARGE", + "description": "Surcharge." + }, + { + "value": "REBATE", + "description": "Rebate." + }, + { + "value": "HIGH_CUBE_SURCHARGE", + "description": "High Cube Surcharge." + }, + { + "value": "HIGH_LENGTH_SURCHARGE", + "description": "High Length Surcharge." + }, + { + "value": "HIGH_WIDTH_SURCHARGE", + "description": "High Width Surcharge." + }, + { + "value": "DEMAND_SURCHARGE", + "description": "A fee that is applied during specific periods of increased demand on the network as specified in the Amazon Shipping Service guide." + }, + { + "value": "NONSTANDARD_FEE", + "description": "A fee that applies for packages that are outside the standard size for processing and sorting as specified in the Amazon Shipping Service guide." + } + ] + }, + "RateItemType": { + "type": "string", + "description": "Type of the rateItem.", + "enum": [ + "MANDATORY", + "OPTIONAL", + "INCLUDED" + ], + "x-docgen-enum-table-extension": [ + { + "value": "MANDATORY", + "description": "Rate Item is mandatory." + }, + { + "value": "OPTIONAL", + "description": "Rate Item is Optional." + }, + { + "value": "INCLUDED", + "description": "Rate Item is included." + } + ] + }, + "Benefits": { + "type": "object", + "description": "Benefits that are included and excluded for each shipping offer. Benefits represents services provided by Amazon (eg. CLAIMS_PROTECTED, etc.) when sellers purchase shipping through Amazon. Benefit details will be made available for any shipment placed on or after January 1st 2024 00:00 UTC.", + "required": [ + "includedBenefits", + "excludedBenefits" + ], + "properties": { + "includedBenefits": { + "$ref": "#/definitions/IncludedBenefits" + }, + "excludedBenefits": { + "$ref": "#/definitions/ExcludedBenefits" + } + } + }, + "IncludedBenefits": { + "type": "array", + "description": "A list of included benefits.", + "items": { + "type": "string" + } + }, + "ExcludedBenefits": { + "type": "array", + "description": "A list of excluded benefit. Refer to the ExcludeBenefit object for further documentation", + "items": { + "$ref": "#/definitions/ExcludedBenefit" + } + }, + "ExcludedBenefit": { + "type": "object", + "description": "Object representing a benefit that is excluded for a shipping offer or rate.", + "required": [ + "benefit" + ], + "properties": { + "benefit": { + "type": "string", + "description": "benefit" + }, + "reasonCodes": { + "$ref": "#/definitions/ExcludedBenefitReasonCodes" + } + } + }, + "ExcludedBenefitReasonCodes": { + "type": "array", + "description": "List of reasons (eg. LATE_DELIVERY_RISK, etc.) indicating why a benefit is excluded for a shipping offer.", + "minItems": 1, + "items": { + "type": "string" + } + }, + "ServiceSelection": { + "description": "Service Selection Criteria.", + "type": "object", + "required": [ + "serviceId" + ], + "properties": { + "serviceId": { + "$ref": "#/definitions/ServiceIds" + } + } + }, + "ServiceIds": { + "type": "array", + "description": "A list of ServiceId.", + "items": { + "type": "string" + } + }, + "OneClickShipmentValueAddedServiceDetails": { + "type": "array", + "description": "The value-added services to be added to a shipping service purchase.", + "items": { + "$ref": "#/definitions/OneClickShipmentValueAddedService" + } + }, + "OneClickShipmentValueAddedService": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "description": "The identifier of the selected value-added service." + }, + "amount": { + "$ref": "#/definitions/Currency" + } + }, + "description": "A value-added service to be applied to a shipping service purchase." + }, + "Service": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "$ref": "#/definitions/ServiceId" + }, + "name": { + "$ref": "#/definitions/ServiceName" + } + }, + "description": "Service Related Info" + }, + "Carrier": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "$ref": "#/definitions/CarrierId" + }, + "name": { + "$ref": "#/definitions/CarrierName" + } + }, + "description": "Carrier Related Info" + } + } +} \ No newline at end of file diff --git a/modules/connectors/asendia/SPECS.md b/modules/connectors/asendia/SPECS.md new file mode 100644 index 0000000000..b19b1f6bdf --- /dev/null +++ b/modules/connectors/asendia/SPECS.md @@ -0,0 +1,644 @@ +# Asendia integration — specification + +Reference for the Asendia connector. Asendia is an international +e-commerce parcel carrier; the connector talks to the **Asendia Sync** +JSON REST API (JWT-bearer auth, single production environment at +`https://www.asendia-sync.com`). It supports shipment create / cancel, +return shipments, tracking, and manifest close. There is **no native +rating API** — rates are served from a local rate sheet +(`services.csv`) via karrio's universal `RatingMixinProxy`. + +The **vendor source of truth** is the Asendia Sync OpenAPI 3.0.1 spec +(, Swagger UI at +), mirrored under +`vendor/openapi.json` / `vendor/openapi.yaml`. + +## Table of contents + +1. [Architecture overview](#architecture-overview) +2. [Data flow](#data-flow) +3. [Endpoints](#endpoints) +4. [Authentication](#authentication) +5. [Supported operations](#supported-operations) +6. [Services & options](#services--options) +7. [Data mapping](#data-mapping) +8. [Per-package fan-out & two-tier identifiers](#per-package-fan-out--two-tier-identifiers) +9. [Returns](#returns) +10. [Rating (rate sheet only)](#rating-rate-sheet-only) +11. [Tracking](#tracking) +12. [Error parsing](#error-parsing) +13. [References](#references) + +--- + +## Architecture overview + +``` +┌─────────────────────────┐ +│ Unified shipping model │ karrio ShipmentRequest / ShipmentCancelRequest / +│ (karrio core) │ TrackingRequest / ManifestRequest / RateRequest +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ providers/asendia │ Pure data transforms. +│ shipment/create.py │ Unified model → typed Asendia request, +│ shipment/cancel.py │ typed Asendia response → unified model. +│ shipment/return_*.py │ No HTTP, no side effects. +│ tracking.py │ +│ manifest.py │ ShippingService, ServiceCode, PackagingType, +│ error.py │ ShippingOption, TrackingStatus, +│ units.py │ TrackingIncidentReason, LabelType, ... +│ utils.py (Settings) │ JWT login + token cache lives here. +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ mappers/asendia/proxy │ HTTP transport only. +│ - create_shipment │ - JWT bearer on every call +│ - cancel_shipment │ - per-package async create + per-parcel +│ - get_tracking │ label fetch +│ - create_manifest │ - concurrent tracking fetch +│ - get_label / etc. │ +│ rate via RatingMixin │ - rating served locally (no Asendia call) +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ Asendia Sync API │ +│ ───────────────────── │ +│ POST /api/authenticate │ JWT id_token (≈24h) +│ POST /api/parcels │ create one parcel per package +│ GET .../label │ base64 label per parcel +│ DELETE /api/parcels/id │ cancel (HTTP 204) +│ GET .../tracking/{tn} │ tracking events +│ POST /api/manifests │ close manifest +└─────────────────────────┘ +``` + +**Key architectural choices:** + +- **Per-package fan-out (Pattern B).** `shipment_request` returns a + *list* of `ShipmentRequestType`, one per parcel. The proxy creates + each parcel asynchronously, then fires a second async call per parcel + to fetch its label, and the parser folds the N responses into one + `ShipmentDetails` via `lib.to_multi_piece_shipment`. (Same shape the + Canada Post connector uses — noted inline in `proxy.create_shipment`.) +- **JWT bearer with a cached token.** No OAuth client-credentials — + `POST /api/authenticate` with `{username, password}` returns an + `id_token`. Cached per `asendia||` and treated as + valid for 23h (refreshed when within 30 min of expiry). +- **No rating API.** Asendia has no quote endpoint; the connector + registers via `RatingMixinProxy`/`RatingMixinSettings` and serves + rates from `services.csv` (loaded into `DEFAULT_SERVICES`). +- **Single environment.** `server_url` is always + `https://www.asendia-sync.com` — there is no sandbox host; + `test_mode` does not switch the URL. +- **Generated schemas** — `karrio/schemas/asendia/*.py` is generated + from `schemas/*.json` (kcli). Don't hand-edit; regenerate with + `./bin/run-generate-on modules/connectors/asendia`. + +## Data flow + +### Shipment create (2 HTTP calls per parcel) + +``` +ShipmentRequest Asendia Sync + │ │ + │ shipment_request → [ShipmentRequestType, ...] │ + ├─► to_address(shipper/recipient) │ + │ to_packages(parcels) │ + │ to_shipping_options() │ + │ map service → product (EPAQ*) │ + │ resolve service modifier (CUP / RETPP / …) │ + │ to_customs_info() → items[] │ + │ │ + │ for each package (run_asynchronously): │ + │ ─── POST /api/parcels ────────────────────────►│ validate + │ ◄── {id, trackingNumber, labelLocation, …} ───│ create parcel + │ │ + │ for each parcel id (run_asynchronously): │ + │ ─── GET /api/parcels/{id}/label ──────────────►│ render label + │ ◄── base64 (PDF/PNG/ZPL) ─────────────────────│ + │ │ + ├─► parse: [(parcel_dict, label_b64), ...] │ + │ trackingNumber → tracking_number │ + │ id → shipment_identifier │ + │ label_b64 → docs.label │ + │ lib.to_multi_piece_shipment(...) │ + ▼ ▼ +ShipmentDetails (one, aggregating N parcels) +``` + +If a parcel create returns no `id`, the proxy passes `label=None` for +that parcel (it skips the label GET). The parser only emits parcel +details for responses that carry a `trackingNumber`. + +## Endpoints + +Single host: `https://www.asendia-sync.com` (no test/prod split). + +| Purpose | Method | Path | +|---|---|---| +| Authenticate (JWT) | POST | `/api/authenticate` | +| Create parcel (shipment) | POST | `/api/parcels` | +| Fetch label | GET | `/api/parcels/{parcel_id}/label` | +| Fetch return label | GET | `/api/parcels/{parcel_id}/return-label` | +| Cancel parcel | DELETE | `/api/parcels/{parcel_id}` | +| Tracking | GET | `/api/customers/{customer_id}/tracking/{tracking_number}` | +| Create manifest | POST | `/api/manifests` | +| Manifest document | GET | `/api/manifests/{manifest_id}/document` | + +Notes: +- DELETE returns HTTP 204 with an empty body; the proxy substitutes + `"{}"` so the parser has valid JSON to deserialize. +- The label GET uses `decoder=lib.encode_base64` in the create flow so + the binary body is base64-encoded into `docs.label`. The standalone + `get_label` / `get_return_label` / `get_manifest_document` helpers + return the raw body (`decoder=lambda r: r`). +- The label `Accept` header is driven by `LABEL_MIME` (see below); it + defaults to `application/pdf` for unknown label types. + +## Authentication + +JWT bearer. There is no OAuth grant — credentials are a plain +`username` / `password` pair (`customer_id` is a separate field used in +the tracking URL path, not for auth). + +``` +access_token property ┌──────────────────┐ + │ │ connection_cache │ + ▼ │ (per conn) │ +┌──────────────┐ miss / expired │ │ +│ token cached?│◄────────────────────────│ key: │ +│ + expiry │ │ asendia|| │ +│ > now+30min │ cache hit │ │ +└──────┬───────┘────────────────────────►└──────────────────┘ + │ miss + ▼ POST /api/authenticate + ┌─────────────────────────────────────────┐ + │ Content-Type: application/json │ + │ Body: {username, password} │ + │ ◄── {id_token} │ + └─────────────────────────────────────────┘ + │ + ▼ cached with expiry = now + 23h + every shipment / cancel / tracking / manifest call carries + Authorization: Bearer +``` + +`login()` parses the auth response through `error.parse_error_response` +and raises `errors.ParsedMessagesError` if the API returned an error, +so bad credentials surface as a normal parsed message rather than a +raw exception. The expiry is stored as `now + 23h` (the JWT is assumed +to live ≈24h); the `access_token` property refreshes when the cached +expiry is within 30 minutes. + +Settings fields (`mappers/asendia/settings.py`): + +| Field | Required | Purpose | +|---|---|---| +| `username` | yes | auth | +| `password` | yes | auth | +| `customer_id` | no | tracking URL path segment | +| `account_country_code` | no | generic karrio field | +| `services` | no | rate-sheet override (defaults to `DEFAULT_SERVICES`) | +| `config` | no | `ConnectionConfig` blob | + +## Supported operations + +| Operation | Wired? | Provider entry point | Proxy method | +|---|---|---|---| +| Rate | yes (local sheet) | `universal_provider.rate_request` | `get_rates` (RatingMixin) | +| Shipment create | yes | `shipment_request` / `parse_shipment_response` | `create_shipment` | +| Shipment cancel | yes | `shipment_cancel_request` / `parse_shipment_cancel_response` | `cancel_shipment` | +| Return shipment | yes | `return_shipment_request` / `parse_return_shipment_response` | `create_return_shipment` (→ `create_shipment`) | +| Tracking | yes | `tracking_request` / `parse_tracking_response` | `get_tracking` | +| Manifest | yes | `manifest_request` / `parse_manifest_response` | `create_manifest` | +| Pickup | no | — | — | + +Plugin metadata (`plugins/asendia/__init__.py`): `id="asendia"`, +`label="Asendia"`, `status="beta"`, `is_hub=False`. + +## Services & options + +### Shipping services — `asendiaService.product` (`ShippingService`) + +| karrio service code | Wire `product` | +|---|---| +| `asendia_epaq_standard` | `EPAQSTD` | +| `asendia_epaq_plus` | `EPAQPLS` | +| `asendia_epaq_select` | `EPAQSCT` | +| `asendia_epaq_elite` | `EPAQELT` | +| `asendia_epaq_go` | `EPAQGO` | +| `asendia_epaq_returns_domestic` | `EPAQRETDOM` | +| `asendia_epaq_returns_international` | `EPAQRETINT` | + +Default product when `payload.service` is unset: `EPAQSTD`. + +### Service modifier — `asendiaService.service` (`ServiceCode`, mandatory) + +`asendiaService.service` is a **mandatory** API field. The user may +override it via the `asendia_service_type` option; otherwise the +connector defaults based on product family: `RETPP` when the product +starts with `EPAQRET`, else `CUP`. + +| code | meaning | +|---|---| +| `CUP` | Customs unpaid / paid at destination (outbound default) | +| `CPPR` | Customs prepaid by retailer | +| `RETPP` | Prepaid return (return default) | +| `RETPAP` | Partially paid return | + +### Packaging / format — `asendiaService.format` (`PackagingType`) + +| karrio packaging | Wire `format` | +|---|---| +| `small_box` / `medium_box` / `your_packaging` | `B` (boxable) | +| `envelope` / `pak` | `N` (non-boxable) | +| `tube` | `L` (large) | +| `pallet` | `XL` (extra-large) | + +Default packaging when unset: `your_packaging` → `B`. + +`units.resolve_format(product, packaging_type, smartgate)` then clamps that mapped +code to the value set the account has a quote for — which is **product-specific**: + +| Product / service | Allowed `format` | Behaviour | +|---|---|---| +| SmartGate Flex / Direct (`SGF` / `SGD`) | `N` or `L` | coerce `B` / `XL` → `N`; keep `N` / `L` | +| e-PAQ Elite (`EPAQELT`) | `L` or `XL` | coerce `B` / `N` → `L`; keep `L` / `XL` | +| all other e-PAQ products (Standard, Plus, Select, …) | `B` or `N` | coerce `L` / `XL` → `N`; keep `B` / `N` | + +`asendiaService.format` is **always sent** — the Asendia API returns +`VE-004 "asendiaService.format: Mandatory field"` if it is omitted or empty (verified +live; Asendia's written examples showed Select with no format and Elite with `""`, but +the API rejects both). The accepted value is **quote-driven**: a product/lane the +account has no rate for returns `"There is no valid quote"`, so the per-product clamp +above targets the set each product is rated for (Elite quotes are L/XL, Standard US +quotes are N, etc.). A mismatch surfaces as a clean `no valid quote` from Asendia. + +### Label types — `labelType` (`LabelType` / `LABEL_MIME`) + +| karrio label type | Wire `labelType` value | `Accept` MIME | +|---|---|---| +| `PDF` | `PDF` | `application/pdf` | +| `PNG` | `PNG` | `image/png` | +| `ZPL` | `Zebra` | `text/plain` | + +Note the ZPL value on the wire is `Zebra`, not `ZPL`. Default `PDF`. + +### Options (`ShippingOption`) + +| Option | Wire field | Type | Notes | +|---|---|---|---| +| `asendia_insurance` (= unified `insurance`) | `asendiaService.insurance` | str | values from `InsuranceOption`: `EL45`, `EL150`, `EL500` | +| `asendia_return_label` | `asendiaService.returnLabelOption.enabled` | bool | `service_level=True`, category `RETURN` | +| `asendia_return_label_type` | `returnLabelOption.type` | str | default `EPAQRETDOM`/`EPAQRETINT` by domesticity | +| `asendia_return_label_payment` | `returnLabelOption.payment` | str | default `RETPP` | +| `asendia_sender_eori` | `senderEORI` | str | | +| `asendia_seller_eori` | `sellerEORI` | str | | +| `asendia_sender_tax_id` | `senderTaxId` | str | | +| `asendia_receiver_tax_id` | `receiverTaxId` | str | | +| `asendia_service_type` | `asendiaService.service` | str | overrides the mandatory service modifier; `service_level=True` | + +The standard `declared_value` option maps to `shippingCost`. + +### Product / service feature flags — `asendiaService.options` + +Boolean feature flags ride the `asendiaService.options` wire array. Each +flag's wire code is the first `OptionEnum` argument (read off +`option.code`, never duplicated). Set flags are collected in +`SHIPMENT_FLAG_OPTIONS` order; an empty array is stripped by +`lib.to_dict`. + +| Option | Wire code | +|---|---| +| `asendia_economy` | `ECO` | +| `asendia_signature` | `SIG` | +| `asendia_pudo` | `PUDO` | +| `asendia_mailbox` | `MBX` | +| `asendia_dangerous_goods` | `DG` | +| `asendia_personal_delivery` | `PD` | +| `asendia_smartgate_flex` | `SGF` | +| `asendia_smartgate_direct` | `SGD` | +| `asendia_direct_access` | `DA` | +| `asendia_printed_matter` | `PM` | + +### SmartGate Flex (Switzerland) — shipping-cost customs line + +Asendia's dedicated Swiss services **SmartGate Flex** (`SGF`) and +**SmartGate Direct** (`SGD`) require the shipping cost to be declared as +an **extra customs line** in `customsInfo.items`, alongside the +customer's article items. When `asendia_smartgate_flex` is set, +`create.py` appends this line inline in the request builder: + +```json +{ + "articleNumber": "MCGSHP", + "articleDescription": "Shipping costs", + "unitValue": 9.99, + "currency": "EUR", + "harmonizationCode": "00000000", + "originCountry": "DE", + "unitWeight": 0.01, + "quantity": 1 +} +``` + +Every field is fixed except: + +- `currency` — matches the customer's article currency (the resolved + `customsInfo.currency`). +- `unitValue` — the shipment's shipping cost (`declared_value` → + `shippingCost`). +- `originCountry` — the shipper's country code. + +Mandatory customs fields per Asendia: `articleDescription`, +`articleNumber`, `unitValue`, `currency`, `unitWeight`, +`harmonizationCode`, `originCountry`, `quantity`. + +**Format / routing.** SmartGate Flex / Direct require `asendiaService.format` of +`N` or `L` (see the per-product format table above for the full rules). Asendia routes +SmartGate as `EPAQSCT | CPPR | SGF` (or `SGD`); the product and `CUP`/`CPPR` modifier +remain caller-selected (routing is per-subsidiary), and the service is +**onboarding-gated** on Asendia's side before it can be booked. Only **SGF** carries +the `MCGSHP` shipping-cost line — **SGD** does not. + +### Connection config (`ConnectionConfig`) + +| Key | Type | +|---|---| +| `label_type` | str | +| `shipping_options` | list | +| `shipping_services` | list | + +## Data mapping + +### Address — karrio `Address` → Asendia `ImporterType` + +Both `addresses.sender` and `addresses.receiver` use the same +`ImporterType` shape. + +``` +karrio Address Asendia ImporterType +───────────────── ──────────────────── +person_name ──► name (lib.text max 50) +company_name ──► company (lib.text max 50) +address_line1 ──► address1 (lib.text max 50) +address_line2 ──► address2 +city ──► city +state_code ──► province +postal_code ──► postalCode +country_code ──► country +email ──► email +phone_number ──► phone +``` + +(The schema also carries `address3`, `mobile`, and an `importer` / +`seller` / `pudoAddress` block, plus `AddressesType.importer` / +`.seller` — none are populated by the connector.) + +### Shipment-level fields — `ShipmentRequestType` + +``` +settings.customer_id ──► customerId +label type (resolved) ──► labelType +payload.reference / order_id / uuid4 ──► referencenumber +package.weight.KG ──► weight (always KG) +options.declared_value ──► shippingCost +options.asendia_sender_eori ──► senderEORI +options.asendia_seller_eori ──► sellerEORI +options.asendia_sender_tax_id ──► senderTaxId +options.asendia_receiver_tax_id ──► receiverTaxId +``` + +`referencenumber` falls back through `reference` → `order_id` → +`uuid4().hex` so it is never empty. + +### Customs — `CustomsInfo` → `customsInfo` + +`customsInfo` is built via `lib.to_customs_info(weight_unit=KG)` by the +`_customs_items()` helper, and is populated when the package has line +items, the customs payload carries commodities, **or** SmartGate Flex is +enabled. The item source is `package.items` if present, else +`customs.commodities`; the SmartGate Flex shipping-cost line (`MCGSHP`) +is appended last when `asendia_smartgate_flex` is set. + +``` +customs.duty.currency (or "EUR") ──► customsInfo.currency +commodity i ──► customsInfo.items[i] { + description / title (max 150) ──► articleDescription + sku ──► articleNumber + value_amount ──► unitValue + value_currency (or customs currency) ──► currency + hs_code ──► harmonizationCode + origin_country ──► originCountry + weight ──► unitWeight + quantity (or 1) ──► quantity +} +``` + +### Shipment response → `ShipmentDetails` + +``` +ShipmentResponseType ShipmentDetails +──────────────────── ─────────────── +trackingNumber ──► tracking_number +id ──► shipment_identifier +label (fetched by proxy)──► docs.label + label_type (from ctx) +trackingNumber ──► meta.carrier_tracking_link + (tracking.asendia.com/tracking/{tn}) +labelLocation ──► meta.label_location +returnLabelLocation ──► meta.return_label_location +returnTrackingNumber ──► meta.return_tracking_number +customsDocumentLocation ──► meta.customs_document_location +commercialInvoiceLocation ─► meta.commercial_invoice_location +``` + +`tracking_url` (for `carrier_tracking_link`) is +`https://tracking.asendia.com/tracking/{}` — distinct from the API +host. + +## Per-package fan-out & two-tier identifiers + +Asendia is one-parcel-per-request. The connector fans out N requests +for an N-parcel shipment and aggregates with +`lib.to_multi_piece_shipment`, keyed by 1-based index. + +Each created parcel returns two identifiers, surfaced on different +fields: + +| Asendia field | Meaning | Where surfaced | +|---|---|---| +| `trackingNumber` | customer-facing tracking number | `tracking_number` | +| `id` | parcel handle (Asendia internal) | `shipment_identifier` | + +`shipment_identifier` (the parcel `id`) is what cancel and manifest +consume: +- **Cancel** — `shipment_cancel_request` returns + `payload.shipment_identifier` verbatim; the proxy puts it in + `DELETE /api/parcels/{parcel_id}`. +- **Manifest** — `manifest_request` returns + `payload.shipment_identifiers` (the list of parcel ids) and POSTs + them to `/api/manifests`. + +## Returns + +Two ways to get a return label: + +1. **Return-label option on an outbound shipment.** Setting + `asendia_return_label = True` adds a `returnLabelOption` block to the + outbound parcel: + - `type` ← `asendia_return_label_type`, else `EPAQRETDOM` (domestic) + / `EPAQRETINT` (international), chosen by + `shipper.country_code == recipient.country_code`. + - `payment` ← `asendia_return_label_payment`, else `RETPP`. + +2. **Dedicated return shipment.** `return_shipment_request` + (`shipment/return_shipment.py`) is a thin wrapper that forces + `asendia_return_label = True` into the options and delegates to the + standard `shipment_request`. The proxy's `create_return_shipment` + simply calls `create_shipment`, and `parse_return_shipment_response` + delegates to `parse_shipment_response`. + +Return products are `EPAQRETDOM` / `EPAQRETINT` (`ShippingService`); +return payment codes are `RETPP` / `RETPAP` (`ServiceCode`). The +response carries `returnTrackingNumber` / `returnLabelLocation` +(surfaced in `meta`); the dedicated return-label fetch endpoint is +`GET /api/parcels/{id}/return-label`. + +## Rating (rate sheet only) + +There is **no Asendia rating endpoint**. The connector mixes in +`RatingMixinSettings` / `RatingMixinProxy` and `get_rates` delegates to +the universal rating provider, which evaluates the rate sheet in +`Settings.shipping_services` (defaults to `DEFAULT_SERVICES`). + +`DEFAULT_SERVICES` is built by `load_services_from_csv()` from +`karrio/providers/asendia/services.csv` at import time. The CSV columns +are: + +``` +service_code, service_name, zone_label, country_codes, min_weight, +max_weight, max_length, max_width, max_height, rate, currency, +transit_days, domicile, international +``` + +Each row's `service_code` is mapped to its karrio name via +`ShippingService.map(...).name_or_key`; rows sharing a service code are +grouped into one `ServiceLevel` with multiple `ServiceZone`s. The +shipped CSV defines all seven e-PAQ products with weight band +`0.01–31.5 KG`, `EUR`, `rate=0.0` (placeholder — operators are expected +to supply real rates via a rate sheet / `services` override). If the +CSV is missing, a single hardcoded `asendia_epaq_standard` fallback +`ServiceLevel` is returned. + +## Tracking + +`GET /api/customers/{customer_id}/tracking/{tracking_number}`, fired +concurrently (`lib.run_concurently`) for each requested number. The +proxy returns `[(tracking_number, response_dict), ...]` and drops +entries whose body is whitespace-only. + +The API returns a **bare JSON array** of tracking-event objects (not a +wrapped object); the tracking number comes from the proxy tuple, not +the body. The parser only builds details for responses that are a +non-empty list. Events are sorted most-recent-first by `time`. + +### Event → `TrackingEvent` + +``` +TrackingEventType TrackingEvent +───────────────── ───────────── +time (%Y-%m-%dT%H:%M:%SZ) ──► date, time, timestamp (ISO 8601) +carrierEventDescription ──► description +code ──► code; also drives status + reason +locationName, locationCountry ──► location (joined ", ") +``` + +Overall status = status of the latest event, falling back to +`in_transit` when no event maps. `delivered` is `True` when the +resolved status is `delivered`. + +### Status mapping (`TrackingStatus`) — code matched against value lists + +| karrio status | Asendia codes | +|---|---| +| `pending` | `PENDING`, `CREATED`, `ACCEPTED`, `LABEL_PRINTED` | +| `picked_up` | `PICKED_UP`, `COLLECTED`, `COLLECTION`, `PU` | +| `in_transit` | `IN_TRANSIT`, `IT`, `TRANSIT`, `DEPARTED`, `ARRIVED`, `PROCESSED`, `CUSTOMS`, `CLEARED` | +| `out_for_delivery` | `OUT_FOR_DELIVERY`, `OFD`, `WITH_COURIER` | +| `delivered` | `DELIVERED`, `DL`, `DELIVERY_CONFIRMED` | +| `ready_for_pickup` | `READY_FOR_PICKUP`, `PICKUP`, `AT_LOCATION` | +| `on_hold` | `ON_HOLD`, `HELD`, `AWAITING` | +| `delivery_delayed` | `DELAYED`, `DELAY`, `RESCHEDULED` | +| `delivery_failed` | `DELIVERY_FAILED`, `FAILED`, `UNDELIVERABLE`, `RETURNED`, `RTS` | + +### Incident reason mapping (`TrackingIncidentReason`) + +The event `code` is also matched against `TrackingIncidentReason` and +attached as `event.reason`. Examples: `carrier_damaged_parcel` +(`DAMAGED`/`DMG`), `consignee_refused` (`REFUSED`/`RJ`), `customs_delay` +(`CUSTOMS_DELAY`/`CD`), `weather_delay` (`WEATHER`/`WE`); full +carrier/consignee/customs/weather taxonomy lives in `units.py`. + +## Error parsing + +Asendia surfaces errors in two distinct shapes, both handled by +`error.parse_error_response`: + +1. **API-level (RFC 7807 Problem Details).** Keys: `type`, `title`, + `status`, `detail`, `path`, `message`, `fieldErrors[]` + (`{objectName, field, message}`). Treated as an error when + `status >= 400`, or `title` is present, or `fieldErrors` is + non-empty. One `Message` per field error + (`"{field}: {message}"`), else a single general message + (`detail` → `message` → `title`). + +2. **Parcel-level.** A successful-looking response body may carry + `errorMessages: [{field, message}]`. Each becomes a `Message` with + code `PARCEL_ERROR` and text `"{field}: {message}"`. + +``` +response + │ + ├─ None ─────────────────► [] (no errors) + ├─ list (e.g. tracking) ──► [] (bare arrays carry no errors) + │ + ├─ status is numeric ≥400 ─┐ + ├─ title present ├──► API-level Message(s) + ├─ fieldErrors non-empty ─┘ (per-field or general) + │ + └─ errorMessages[] present ───► PARCEL_ERROR Message(s) +``` + +Quirk: `status` is only treated as an HTTP error code when it is +numeric (or a numeric string). Non-numeric `status` values — e.g. +manifest responses returning `status: "CREATED"` — are explicitly +**not** errors. Manifest also adds its own `MANIFEST_ERROR` +(`manifest.errorMessage`) and per-parcel `PARCEL_ERROR` +(`manifest.errorParcelIds`) messages in `parse_manifest_response`. + +Shipment parsing aggregates errors across all per-parcel responses +(`sum([parse_error_response(parcel, …) for parcel, _ in responses])`), +so a partial failure surfaces alongside the parcels that succeeded. + +## References + +- **OpenAPI (authoritative)** — Asendia Sync API + (Swagger UI: + ), mirrored at + `vendor/openapi.json` / `vendor/openapi.yaml`. +- **Vendor notes** — `vendor/README.md` (endpoint table, enum summary, + spec-refresh command). +- **Rate sheet** — `karrio/providers/asendia/services.csv` (the only + source of service/weight-band/zone/rate data — not in the OpenAPI). +- **Generated schemas** — `karrio/schemas/asendia/*.py` is generated + from `schemas/*.json` via the connector's `generate` script (kcli, + `--no-nice-property-names` for the camelCase Asendia fields, + `--nice-property-names` only for `auth_response` so `id_token` keeps + its snake_case). Regenerate with + `./bin/run-generate-on modules/connectors/asendia` — never hand-edit + the generated `.py` files. diff --git a/modules/connectors/asendia/karrio/mappers/asendia/__init__.py b/modules/connectors/asendia/karrio/mappers/asendia/__init__.py index 4445eacdef..99b22c5e28 100644 --- a/modules/connectors/asendia/karrio/mappers/asendia/__init__.py +++ b/modules/connectors/asendia/karrio/mappers/asendia/__init__.py @@ -1,3 +1,3 @@ from karrio.mappers.asendia.mapper import Mapper from karrio.mappers.asendia.proxy import Proxy -from karrio.mappers.asendia.settings import Settings \ No newline at end of file +from karrio.mappers.asendia.settings import Settings diff --git a/modules/connectors/asendia/karrio/mappers/asendia/mapper.py b/modules/connectors/asendia/karrio/mappers/asendia/mapper.py index aa4e5c8d97..ce29e06761 100644 --- a/modules/connectors/asendia/karrio/mappers/asendia/mapper.py +++ b/modules/connectors/asendia/karrio/mappers/asendia/mapper.py @@ -1,62 +1,60 @@ """Karrio Asendia client mapper.""" -import typing -import karrio.lib as lib import karrio.api.mapper as mapper import karrio.core.models as models -import karrio.providers.asendia as provider +import karrio.lib as lib import karrio.mappers.asendia.settings as provider_settings +import karrio.providers.asendia as provider +import karrio.universal.providers.rating as universal_provider class Mapper(mapper.Mapper): settings: provider_settings.Settings - def create_tracking_request( - self, payload: models.TrackingRequest - ) -> lib.Serializable: + + def create_rate_request(self, payload: models.RateRequest) -> lib.Serializable: + return universal_provider.rate_request(payload, self.settings) + + def parse_rate_response( + self, response: lib.Deserializable + ) -> tuple[list[models.RateDetails], list[models.Message]]: + return universal_provider.parse_rate_response(response, self.settings) + + def create_tracking_request(self, payload: models.TrackingRequest) -> lib.Serializable: return provider.tracking_request(payload, self.settings) - - def create_shipment_request( - self, payload: models.ShipmentRequest - ) -> lib.Serializable: + + def create_shipment_request(self, payload: models.ShipmentRequest) -> lib.Serializable: return provider.shipment_request(payload, self.settings) - - def create_cancel_shipment_request( - self, payload: models.ShipmentCancelRequest - ) -> lib.Serializable[str]: + + def create_cancel_shipment_request(self, payload: models.ShipmentCancelRequest) -> lib.Serializable[str]: return provider.shipment_cancel_request(payload, self.settings) - - def create_manifest_request( - self, payload: models.ManifestRequest - ) -> lib.Serializable: + + def create_manifest_request(self, payload: models.ManifestRequest) -> lib.Serializable: return provider.manifest_request(payload, self.settings) - - + def parse_cancel_shipment_response( self, response: lib.Deserializable[str] - ) -> typing.Tuple[models.ConfirmationDetails, typing.List[models.Message]]: + ) -> tuple[models.ConfirmationDetails, list[models.Message]]: return provider.parse_shipment_cancel_response(response, self.settings) - + def parse_shipment_response( self, response: lib.Deserializable[str] - ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]: + ) -> tuple[models.ShipmentDetails, list[models.Message]]: return provider.parse_shipment_response(response, self.settings) - + def parse_tracking_response( self, response: lib.Deserializable[str] - ) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]: + ) -> tuple[list[models.TrackingDetails], list[models.Message]]: return provider.parse_tracking_response(response, self.settings) - + def parse_manifest_response( self, response: lib.Deserializable[str] - ) -> typing.Tuple[models.ManifestDetails, typing.List[models.Message]]: + ) -> tuple[models.ManifestDetails, list[models.Message]]: return provider.parse_manifest_response(response, self.settings) - def create_return_shipment_request( - self, payload: models.ShipmentRequest - ) -> lib.Serializable: + def create_return_shipment_request(self, payload: models.ShipmentRequest) -> lib.Serializable: return provider.return_shipment_request(payload, self.settings) def parse_return_shipment_response( self, response: lib.Deserializable - ) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]: + ) -> tuple[models.ShipmentDetails, list[models.Message]]: return provider.parse_return_shipment_response(response, self.settings) diff --git a/modules/connectors/asendia/karrio/mappers/asendia/proxy.py b/modules/connectors/asendia/karrio/mappers/asendia/proxy.py index 88cc49730b..48edfbc3e2 100644 --- a/modules/connectors/asendia/karrio/mappers/asendia/proxy.py +++ b/modules/connectors/asendia/karrio/mappers/asendia/proxy.py @@ -1,13 +1,18 @@ """Karrio Asendia client proxy.""" -import karrio.lib as lib import karrio.api.proxy as proxy +import karrio.lib as lib import karrio.mappers.asendia.settings as provider_settings +import karrio.providers.asendia.units as provider_units +import karrio.universal.mappers.rating_proxy as rating_proxy -class Proxy(proxy.Proxy): +class Proxy(rating_proxy.RatingMixinProxy, proxy.Proxy): settings: provider_settings.Settings + def get_rates(self, request: lib.Serializable) -> lib.Deserializable: + return super().get_rates(request) + def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]: """Create parcels and retrieve labels. @@ -43,7 +48,7 @@ def create_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]: trace=self.trace_as("json"), method="GET", headers={ - "Accept": f"application/{label_type.lower()}", + "Accept": provider_units.LABEL_MIME.get(label_type, "application/pdf"), "Authorization": f"Bearer {self.settings.access_token}", }, decoder=lib.encode_base64, @@ -80,7 +85,7 @@ def cancel_shipment(self, request: lib.Serializable) -> lib.Deserializable[str]: }, ) - return lib.Deserializable(response, lib.to_dict) + return lib.Deserializable(response or "{}", lib.to_dict) def get_tracking(self, request: lib.Serializable) -> lib.Deserializable[str]: """Get tracking information for tracking numbers.""" @@ -124,7 +129,7 @@ def get_label(self, parcel_id: str, label_type: str = "PDF") -> str: trace=self.trace_as("json"), method="GET", headers={ - "Accept": f"application/{label_type.lower()}", + "Accept": provider_units.LABEL_MIME.get(label_type, "application/pdf"), "Authorization": f"Bearer {self.settings.access_token}", }, decoder=lambda r: r, @@ -137,7 +142,7 @@ def get_return_label(self, parcel_id: str, label_type: str = "PDF") -> str: trace=self.trace_as("json"), method="GET", headers={ - "Accept": f"application/{label_type.lower()}", + "Accept": provider_units.LABEL_MIME.get(label_type, "application/pdf"), "Authorization": f"Bearer {self.settings.access_token}", }, decoder=lambda r: r, diff --git a/modules/connectors/asendia/karrio/mappers/asendia/settings.py b/modules/connectors/asendia/karrio/mappers/asendia/settings.py index 6a23aa042c..4e9814e709 100644 --- a/modules/connectors/asendia/karrio/mappers/asendia/settings.py +++ b/modules/connectors/asendia/karrio/mappers/asendia/settings.py @@ -1,11 +1,15 @@ """Karrio Asendia client settings.""" import attr +import jstruct +import karrio.core.models as models +import karrio.providers.asendia.units as provider_units import karrio.providers.asendia.utils as provider_utils +from karrio.universal.mappers.rating_proxy import RatingMixinSettings @attr.s(auto_attribs=True) -class Settings(provider_utils.Settings): +class Settings(provider_utils.Settings, RatingMixinSettings): """Asendia connection settings.""" # Asendia API credentials (required) @@ -23,3 +27,14 @@ class Settings(provider_utils.Settings): account_country_code: str = None metadata: dict = {} config: dict = {} + + services: list[models.ServiceLevel] = jstruct.JList[ + models.ServiceLevel, False, dict(default=provider_units.DEFAULT_SERVICES) + ] # type: ignore + + @property + def shipping_services(self) -> list[models.ServiceLevel]: + if any(self.services or []): + return self.services + + return provider_units.DEFAULT_SERVICES diff --git a/modules/connectors/asendia/karrio/plugins/asendia/__init__.py b/modules/connectors/asendia/karrio/plugins/asendia/__init__.py index 86be455292..703d5afcf7 100644 --- a/modules/connectors/asendia/karrio/plugins/asendia/__init__.py +++ b/modules/connectors/asendia/karrio/plugins/asendia/__init__.py @@ -1,11 +1,9 @@ +import karrio.providers.asendia.units as units +import karrio.providers.asendia.utils as utils from karrio.core.metadata import PluginMetadata - from karrio.mappers.asendia.mapper import Mapper from karrio.mappers.asendia.proxy import Proxy from karrio.mappers.asendia.settings import Settings -import karrio.providers.asendia.units as units -import karrio.providers.asendia.utils as utils - METADATA = PluginMetadata( id="asendia", diff --git a/modules/connectors/asendia/karrio/providers/asendia/__init__.py b/modules/connectors/asendia/karrio/providers/asendia/__init__.py index 1eb31f51b7..f7a0ef14ba 100644 --- a/modules/connectors/asendia/karrio/providers/asendia/__init__.py +++ b/modules/connectors/asendia/karrio/providers/asendia/__init__.py @@ -1,19 +1,19 @@ """Karrio Asendia provider imports.""" -from karrio.providers.asendia.utils import Settings + +from karrio.providers.asendia.manifest import ( + manifest_request, + parse_manifest_response, +) from karrio.providers.asendia.shipment import ( + parse_return_shipment_response, parse_shipment_cancel_response, parse_shipment_response, + return_shipment_request, shipment_cancel_request, shipment_request, - - parse_return_shipment_response, - return_shipment_request, ) from karrio.providers.asendia.tracking import ( parse_tracking_response, tracking_request, ) -from karrio.providers.asendia.manifest import ( - parse_manifest_response, - manifest_request, -) \ No newline at end of file +from karrio.providers.asendia.utils import Settings diff --git a/modules/connectors/asendia/karrio/providers/asendia/error.py b/modules/connectors/asendia/karrio/providers/asendia/error.py index 61d293972c..a4feb9a08a 100644 --- a/modules/connectors/asendia/karrio/providers/asendia/error.py +++ b/modules/connectors/asendia/karrio/providers/asendia/error.py @@ -1,8 +1,7 @@ """Karrio Asendia error parser.""" -import typing -import karrio.lib as lib import karrio.core.models as models +import karrio.lib as lib import karrio.providers.asendia.utils as provider_utils import karrio.schemas.asendia.error_response as asendia @@ -11,7 +10,7 @@ def parse_error_response( response: dict, settings: provider_utils.Settings, **kwargs, -) -> typing.List[models.Message]: +) -> list[models.Message]: """Parse Asendia error response into karrio Message objects. Asendia has two error formats: @@ -34,12 +33,16 @@ def parse_error_response( "errorMessages": [{"field": "...", "message": "..."}] } """ - errors: typing.List[models.Message] = [] + errors: list[models.Message] = [] # Check if response contains error indicators if response is None: return errors + # Bare list responses (e.g. tracking success) carry no errors + if isinstance(response, list): + return errors + # Get status code from response - must be a numeric value (HTTP status code) # Non-numeric status values (like "CREATED" in manifest responses) are not error codes raw_status = response.get("status") diff --git a/modules/connectors/asendia/karrio/providers/asendia/manifest.py b/modules/connectors/asendia/karrio/providers/asendia/manifest.py index b513a63249..71ddcdd13d 100644 --- a/modules/connectors/asendia/karrio/providers/asendia/manifest.py +++ b/modules/connectors/asendia/karrio/providers/asendia/manifest.py @@ -1,18 +1,16 @@ """Karrio Asendia manifest creation implementation.""" -import typing -import karrio.lib as lib import karrio.core.models as models +import karrio.lib as lib import karrio.providers.asendia.error as error import karrio.providers.asendia.utils as provider_utils -import karrio.schemas.asendia.manifest_request as asendia_req import karrio.schemas.asendia.manifest_response as asendia_res def parse_manifest_response( _response: lib.Deserializable[dict], settings: provider_utils.Settings, -) -> typing.Tuple[typing.Optional[models.ManifestDetails], typing.List[models.Message]]: +) -> tuple[models.ManifestDetails | None, list[models.Message]]: """Parse manifest creation response from Asendia API.""" response = _response.deserialize() messages = error.parse_error_response(response, settings) @@ -79,8 +77,4 @@ def manifest_request( Asendia uses POST /api/manifests with parcel IDs. The shipment_identifiers should be the parcel IDs returned from create shipment. """ - request = asendia_req.ManifestRequestType( - parcelIds=payload.shipment_identifiers, - ) - - return lib.Serializable(request, lib.to_dict) + return lib.Serializable(payload.shipment_identifiers, lib.to_dict) diff --git a/modules/connectors/asendia/karrio/providers/asendia/services.csv b/modules/connectors/asendia/karrio/providers/asendia/services.csv index ae7a6dbc93..3f4a7da088 100644 --- a/modules/connectors/asendia/karrio/providers/asendia/services.csv +++ b/modules/connectors/asendia/karrio/providers/asendia/services.csv @@ -1,13 +1,8 @@ service_code,service_name,zone_label,country_codes,min_weight,max_weight,max_length,max_width,max_height,rate,currency,transit_days,domicile,international EPAQSTD,e-PAQ Standard,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true -EPAQSTD_CUP,e-PAQ Standard Collection,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true -EPAQPLUS,e-PAQ Plus,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true -EPAQPLUS_CUP,e-PAQ Plus Collection,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true -EPAQELITE,e-PAQ Elite,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true -EPAQELITE_CUP,e-PAQ Elite Collection,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true -EPAQRET,e-PAQ Returns,EU Returns,"AT,BE,CH,CZ,DE,DK,EE,ES,FI,FR,GB,GR,HU,HR,IE,IT,LT,LU,LV,MT,NL,PL,PT,RO,SE,SI,SK",0.01,31.5,,,,0.0,EUR,,false,true -EPAQRETDOM,e-PAQ Returns Domestic,Domestic,,0.01,31.5,,,,0.0,EUR,,true,false -CROAD,Country Road,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true -CROADPLUS,Country Road Plus,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true -PRIORITY,Priority,Worldwide,,0.01,2.0,,,,0.0,EUR,,false,true -PRIORITYTRK,Priority Tracked,Worldwide,,0.01,2.0,,,,0.0,EUR,,false,true +EPAQPLS,e-PAQ Plus,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true +EPAQSCT,e-PAQ Select,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true +EPAQELT,e-PAQ Elite,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true +EPAQGO,e-PAQ GO,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true +EPAQRETDOM,e-PAQ Domestic Returns,Domestic,,0.01,31.5,,,,0.0,EUR,,true,false +EPAQRETINT,e-PAQ International Returns,Worldwide,,0.01,31.5,,,,0.0,EUR,,false,true diff --git a/modules/connectors/asendia/karrio/providers/asendia/shipment/__init__.py b/modules/connectors/asendia/karrio/providers/asendia/shipment/__init__.py index 8b92281406..3755f188e1 100644 --- a/modules/connectors/asendia/karrio/providers/asendia/shipment/__init__.py +++ b/modules/connectors/asendia/karrio/providers/asendia/shipment/__init__.py @@ -1,12 +1,11 @@ - -from karrio.providers.asendia.shipment.create import ( - parse_shipment_response, - shipment_request, -) from karrio.providers.asendia.shipment.cancel import ( parse_shipment_cancel_response, shipment_cancel_request, ) +from karrio.providers.asendia.shipment.create import ( + parse_shipment_response, + shipment_request, +) from karrio.providers.asendia.shipment.return_shipment import ( parse_return_shipment_response, return_shipment_request, diff --git a/modules/connectors/asendia/karrio/providers/asendia/shipment/cancel.py b/modules/connectors/asendia/karrio/providers/asendia/shipment/cancel.py index f4965db08d..3d80fde14c 100644 --- a/modules/connectors/asendia/karrio/providers/asendia/shipment/cancel.py +++ b/modules/connectors/asendia/karrio/providers/asendia/shipment/cancel.py @@ -1,8 +1,7 @@ """Karrio Asendia shipment cancellation API implementation.""" -import typing -import karrio.lib as lib import karrio.core.models as models +import karrio.lib as lib import karrio.providers.asendia.error as error import karrio.providers.asendia.utils as provider_utils @@ -10,7 +9,7 @@ def parse_shipment_cancel_response( _response: lib.Deserializable[dict], settings: provider_utils.Settings, -) -> typing.Tuple[typing.Optional[models.ConfirmationDetails], typing.List[models.Message]]: +) -> tuple[models.ConfirmationDetails | None, list[models.Message]]: """Parse shipment cancellation response from Asendia API.""" response = _response.deserialize() messages = error.parse_error_response(response, settings) diff --git a/modules/connectors/asendia/karrio/providers/asendia/shipment/create.py b/modules/connectors/asendia/karrio/providers/asendia/shipment/create.py index 707a0cedaa..21eda258c8 100644 --- a/modules/connectors/asendia/karrio/providers/asendia/shipment/create.py +++ b/modules/connectors/asendia/karrio/providers/asendia/shipment/create.py @@ -1,20 +1,21 @@ """Karrio Asendia shipment API implementation.""" -import typing -import karrio.lib as lib -import karrio.core.units as units +import uuid + import karrio.core.models as models +import karrio.core.units as units +import karrio.lib as lib import karrio.providers.asendia.error as error -import karrio.providers.asendia.utils as provider_utils import karrio.providers.asendia.units as provider_units +import karrio.providers.asendia.utils as provider_utils import karrio.schemas.asendia.shipment_request as asendia_req import karrio.schemas.asendia.shipment_response as asendia_res def parse_shipment_response( - _response: lib.Deserializable[typing.List[typing.Tuple[dict, str]]], + _response: lib.Deserializable[list[tuple[dict, str]]], settings: provider_utils.Settings, -) -> typing.Tuple[typing.Optional[models.ShipmentDetails], typing.List[models.Message]]: +) -> tuple[models.ShipmentDetails | None, list[models.Message]]: """Parse shipment response from Asendia API. Asendia uses per-package requests (Pattern B), like Canada Post. @@ -24,7 +25,7 @@ def parse_shipment_response( responses = _response.deserialize() # Aggregate errors from all responses using sum() - messages: typing.List[models.Message] = sum( + messages: list[models.Message] = sum( [error.parse_error_response(parcel, settings) for parcel, _ in responses], start=[], ) @@ -46,7 +47,7 @@ def parse_shipment_response( def _extract_details( data: dict, - label: typing.Optional[str], + label: str | None, settings: provider_utils.Settings, ctx: dict = None, ) -> models.ShipmentDetails: @@ -95,21 +96,21 @@ def shipment_request( initializer=provider_units.shipping_options_initializer, ) - # Map service to Asendia product code using ShippingService enum - # The value will be in format "EPAQSTD" or "EPAQSTD_CUP" - service_code = provider_units.ShippingService.map( - payload.service or "EPAQSTD" - ).value_or_key + # Map karrio service to Asendia product code (e.g. "asendia_epaq_standard" -> "EPAQSTD") + product_code = provider_units.ShippingService.map(payload.service or "EPAQSTD").value_or_key - # Parse product and service modifier from mapped code - service_parts = service_code.split("_") if "_" in service_code else [service_code] - product_code = service_parts[0] - service_modifier = service_parts[1] if len(service_parts) > 1 else None + # AsendiaService.service is a mandatory API field (CUP/CPPR for outbound, RETPP/RETPAP for returns). + # User may override via asendia_service_type option; otherwise default based on product family. + service_modifier = options.asendia_service_type.state or ("RETPP" if product_code.startswith("EPAQRET") else "CUP") # Determine label type - label_type = provider_units.LabelType.map( - payload.label_type or "PDF" - ).value_or_key + label_type = provider_units.LabelType.map(payload.label_type or "PDF").value_or_key + + service_options = [ + getattr(options, key).code for key in provider_units.SHIPMENT_FLAG_OPTIONS if getattr(options, key).state + ] + shipping_cost = options.declared_value.state + smartgate = options.asendia_smartgate_flex.state or options.asendia_smartgate_direct.state # Build customs info for international shipments using lib.to_customs_info customs = lib.to_customs_info( @@ -118,12 +119,15 @@ def shipment_request( recipient=payload.recipient, weight_unit=units.WeightUnit.KG.name, ) + customs_currency = lib.failsafe(lambda: customs.duty.currency) or "EUR" # Build return label option if requested + is_domestic = shipper.country_code == recipient.country_code + default_return_type = "EPAQRETDOM" if is_domestic else "EPAQRETINT" return_label_option = lib.identity( asendia_req.ReturnLabelOptionType( enabled=True, - type=options.asendia_return_label_type.state or "EPAQRETDOM", + type=options.asendia_return_label_type.state or default_return_type, payment=options.asendia_return_label_payment.state or "RETPP", ) if options.asendia_return_label.state @@ -135,27 +139,26 @@ def shipment_request( asendia_req.ShipmentRequestType( customerId=settings.customer_id, labelType=label_type, - referencenumber=payload.reference, + referencenumber=(payload.reference or payload.order_id or uuid.uuid4().hex), weight=package.weight.KG, - shippingCost=options.declared_value.state if options.declared_value.state else None, + shippingCost=shipping_cost if shipping_cost else None, senderEORI=options.asendia_sender_eori.state, sellerEORI=options.asendia_seller_eori.state, senderTaxId=options.asendia_sender_tax_id.state, receiverTaxId=options.asendia_receiver_tax_id.state, asendiaService=asendia_req.AsendiaServiceType( - format=provider_units.PackagingType.map( - package.packaging_type or "your_packaging" - ).value_or_key, + format=provider_units.resolve_format(product_code, package.packaging_type, smartgate), product=product_code, service=service_modifier, + options=service_options, insurance=options.asendia_insurance.state, returnLabelOption=return_label_option, ), addresses=asendia_req.AddressesType( sender=asendia_req.ImporterType( - name=shipper.person_name, - company=shipper.company_name, - address1=shipper.address_line1, + name=lib.text(shipper.person_name, max=50), + company=lib.text(shipper.company_name, max=50), + address1=lib.text(shipper.address_line1, max=50), address2=shipper.address_line2, city=shipper.city, province=shipper.state_code, @@ -165,9 +168,9 @@ def shipment_request( phone=shipper.phone_number, ), receiver=asendia_req.ImporterType( - name=recipient.person_name, - company=recipient.company_name, - address1=recipient.address_line1, + name=lib.text(recipient.person_name, max=50), + company=lib.text(recipient.company_name, max=50), + address1=lib.text(recipient.address_line1, max=50), address2=recipient.address_line2, city=recipient.city, province=recipient.state_code, @@ -179,26 +182,38 @@ def shipment_request( ), customsInfo=lib.identity( asendia_req.CustomsInfoType( - currency=lib.failsafe(lambda: customs.duty.currency) or "USD", + currency=customs_currency, items=[ asendia_req.ItemType( - articleDescription=lib.text( - item.description or item.title, max=200 - ), + articleDescription=lib.text(item.description or item.title, max=150), articleNumber=item.sku, unitValue=item.value_amount, - currency=item.value_currency or "USD", + currency=item.value_currency or customs_currency, harmonizationCode=item.hs_code, originCountry=item.origin_country, unitWeight=item.weight, quantity=item.quantity or 1, ) - for item in ( - package.items if any(package.items) else customs.commodities - ) - ], + for item in (package.items if any(package.items) else customs.commodities) + ] + + ( + [ + asendia_req.ItemType( + articleNumber="MCGSHP", + articleDescription="Shipping costs", + unitValue=shipping_cost or 0.0, + currency=customs_currency, + harmonizationCode="00000000", + originCountry=shipper.country_code, + unitWeight=0.01, + quantity=1, + ) + ] + if options.asendia_smartgate_flex.state + else [] + ), ) - if any(package.items) or customs.commodities + if any(package.items) or customs.commodities or options.asendia_smartgate_flex.state else None ), ) diff --git a/modules/connectors/asendia/karrio/providers/asendia/shipment/return_shipment.py b/modules/connectors/asendia/karrio/providers/asendia/shipment/return_shipment.py index 8733a247dd..c3175a4bb8 100644 --- a/modules/connectors/asendia/karrio/providers/asendia/shipment/return_shipment.py +++ b/modules/connectors/asendia/karrio/providers/asendia/shipment/return_shipment.py @@ -3,9 +3,8 @@ Delegates to the standard shipment creation with the return option forced on. """ -import typing -import karrio.lib as lib import karrio.core.models as models +import karrio.lib as lib import karrio.providers.asendia.shipment.create as create import karrio.providers.asendia.utils as provider_utils @@ -13,7 +12,7 @@ def parse_return_shipment_response( _response: lib.Deserializable, settings: provider_utils.Settings, -) -> typing.Tuple[models.ShipmentDetails, typing.List[models.Message]]: +) -> tuple[models.ShipmentDetails, list[models.Message]]: return create.parse_shipment_response(_response, settings) diff --git a/modules/connectors/asendia/karrio/providers/asendia/tracking.py b/modules/connectors/asendia/karrio/providers/asendia/tracking.py index de74d9b4c2..517d4e9663 100644 --- a/modules/connectors/asendia/karrio/providers/asendia/tracking.py +++ b/modules/connectors/asendia/karrio/providers/asendia/tracking.py @@ -1,15 +1,14 @@ """Karrio Asendia tracking API implementation.""" -import typing -import karrio.lib as lib import karrio.core.models as models +import karrio.lib as lib import karrio.providers.asendia.error as error -import karrio.providers.asendia.utils as provider_utils import karrio.providers.asendia.units as provider_units +import karrio.providers.asendia.utils as provider_utils import karrio.schemas.asendia.tracking_response as asendia_res -def _match_status(code: str) -> typing.Optional[str]: +def _match_status(code: str) -> str | None: """Match code against TrackingStatus enum values.""" if not code: return None @@ -19,7 +18,7 @@ def _match_status(code: str) -> typing.Optional[str]: return None -def _match_reason(code: str) -> typing.Optional[str]: +def _match_reason(code: str) -> str | None: """Match code against TrackingIncidentReason enum values.""" if not code: return None @@ -30,13 +29,13 @@ def _match_reason(code: str) -> typing.Optional[str]: def parse_tracking_response( - _response: lib.Deserializable[typing.List[typing.Tuple[str, dict]]], + _response: lib.Deserializable[list[tuple[str, dict]]], settings: provider_utils.Settings, -) -> typing.Tuple[typing.List[models.TrackingDetails], typing.List[models.Message]]: +) -> tuple[list[models.TrackingDetails], list[models.Message]]: """Parse tracking response from Asendia API.""" responses = _response.deserialize() - messages: typing.List[models.Message] = sum( + messages: list[models.Message] = sum( [ error.parse_error_response(response, settings, tracking_number=tracking_number) for tracking_number, response in responses @@ -45,26 +44,26 @@ def parse_tracking_response( ) tracking_details = [ - _extract_details(details, settings, tracking_number) - for tracking_number, details in responses - if details.get("trackingNumber") or details.get("trackingEvents") + _extract_details(response, settings, tracking_number) + for tracking_number, response in responses + if isinstance(response, list) and len(response) > 0 ] return tracking_details, messages def _extract_details( - data: dict, + events_list: list, settings: provider_utils.Settings, tracking_number: str = None, ) -> models.TrackingDetails: - """Extract tracking details from Asendia response.""" - tracking = lib.to_object(asendia_res.TrackingResponseType, data) - number = tracking.trackingNumber or tracking_number + """Extract tracking details from Asendia bare-array response.""" + # API returns a bare list of TrackingEvent objects; tracking_number comes from the proxy tuple + raw_events = [lib.to_object(asendia_res.TrackingEventType, e) for e in events_list] # Sort events (most recent first) sorted_events = sorted( - tracking.trackingEvents or [], + raw_events, key=lambda e: e.time or "", reverse=True, ) @@ -103,7 +102,7 @@ def _extract_details( return models.TrackingDetails( carrier_id=settings.carrier_id, carrier_name=settings.carrier_name, - tracking_number=number, + tracking_number=tracking_number, events=events, delivered=status == "delivered", status=status, diff --git a/modules/connectors/asendia/karrio/providers/asendia/units.py b/modules/connectors/asendia/karrio/providers/asendia/units.py index 9474365b52..3e3b3c715e 100644 --- a/modules/connectors/asendia/karrio/providers/asendia/units.py +++ b/modules/connectors/asendia/karrio/providers/asendia/units.py @@ -2,9 +2,10 @@ import csv import pathlib -import karrio.lib as lib -import karrio.core.units as units + import karrio.core.models as models +import karrio.core.units as units +import karrio.lib as lib class ConnectionConfig(lib.Enum): @@ -16,92 +17,91 @@ class ConnectionConfig(lib.Enum): class LabelType(lib.StrEnum): - """Asendia label format types.""" + """Asendia label format types (values sent in POST /api/parcels body).""" PDF = "PDF" PNG = "PNG" ZPL = "Zebra" -class ProductCode(lib.StrEnum): - """Asendia product codes.""" - - # e-PAQ products - asendia_epaq_standard = "EPAQSTD" - asendia_epaq_plus = "EPAQPLUS" - asendia_epaq_elite = "EPAQELITE" - asendia_epaq_returns = "EPAQRET" - - # Country Road products - asendia_country_road = "CROAD" - asendia_country_road_plus = "CROADPLUS" - - # Priority products - asendia_priority = "PRIORITY" - asendia_priority_tracked = "PRIORITYTRK" +LABEL_MIME = { + "PDF": "application/pdf", + "PNG": "image/png", + "Zebra": "text/plain", +} class ServiceCode(lib.StrEnum): - """Asendia service codes.""" + """Asendia service modifier codes (AsendiaService.service — mandatory API field).""" - # Common service codes - asendia_cup = "CUP" # Collection/Pickup - asendia_std = "STD" # Standard - asendia_exp = "EXP" # Express + # Standard service modifiers + asendia_cup = "CUP" # Customs unpaid / Customs paid at destination + asendia_cppr = "CPPR" # Customs prepaid by retailer + + # Return-specific service modifiers + asendia_retpp = "RETPP" # Prepaid return + asendia_retpap = "RETPAP" # Partially paid return class ShippingService(lib.StrEnum): - """Asendia shipping services (product + service combination).""" + """Asendia shipping services (product codes from AsendiaService.product).""" - # e-PAQ Standard services + # e-PAQ products asendia_epaq_standard = "EPAQSTD" - asendia_epaq_standard_cup = "EPAQSTD_CUP" - - # e-PAQ Plus services - asendia_epaq_plus = "EPAQPLUS" - asendia_epaq_plus_cup = "EPAQPLUS_CUP" - - # e-PAQ Elite services - asendia_epaq_elite = "EPAQELITE" - asendia_epaq_elite_cup = "EPAQELITE_CUP" + asendia_epaq_plus = "EPAQPLS" + asendia_epaq_select = "EPAQSCT" + asendia_epaq_elite = "EPAQELT" + asendia_epaq_go = "EPAQGO" # e-PAQ Returns - asendia_epaq_returns = "EPAQRET" asendia_epaq_returns_domestic = "EPAQRETDOM" - - # Country Road services - asendia_country_road = "CROAD" - asendia_country_road_plus = "CROADPLUS" - - # Priority services - asendia_priority = "PRIORITY" - asendia_priority_tracked = "PRIORITYTRK" + asendia_epaq_returns_international = "EPAQRETINT" class PackagingType(lib.StrEnum): - """Asendia format/packaging types.""" + """Asendia format/packaging types (AsendiaService.format).""" # Asendia format codes - asendia_packet = "B" # Standard packet format - asendia_parcel = "P" # Parcel format + asendia_boxable = "B" # Boxable + asendia_non_boxable = "N" # Non boxable + asendia_large = "L" # Large + asendia_extra_large = "XL" # Extra Large # Unified Packaging type mapping - envelope = asendia_packet - pak = asendia_packet - tube = asendia_parcel - pallet = asendia_parcel - small_box = asendia_packet - medium_box = asendia_parcel - your_packaging = asendia_packet + envelope = asendia_non_boxable + pak = asendia_non_boxable + tube = asendia_large + pallet = asendia_extra_large + small_box = asendia_boxable + medium_box = asendia_boxable + your_packaging = asendia_boxable + + +def resolve_format(product: str, packaging_type: str, smartgate: bool) -> str: + """Resolve ``asendiaService.format`` — always required by the API (VE-004), and + clamped to the value set the account has a quote for, which is product-specific: + + - SmartGate (SGF/SGD) → N or L (coerce others → N) + - e-PAQ Elite → L or XL (coerce B/N → L) + - all other e-PAQ products → B or N (coerce L/XL → N) + + The packaging-mapped code picks within the allowed set; out-of-set falls back to + the set's default. See SPECS for the table. + """ + code = PackagingType.map(packaging_type or "your_packaging").value_or_key + if smartgate: + return code if code in ("N", "L") else "N" + if product == ShippingService.asendia_epaq_elite.value: + return code if code in ("L", "XL") else "L" + return code if code in ("B", "N") else "N" class InsuranceOption(lib.StrEnum): """Asendia insurance options.""" + asendia_el45 = "EL45" asendia_el150 = "EL150" asendia_el500 = "EL500" - asendia_el1000 = "EL1000" - asendia_el2500 = "EL2500" class ReturnLabelType(lib.StrEnum): @@ -117,22 +117,62 @@ class ReturnPaymentType(lib.StrEnum): class ShippingOption(lib.Enum): - """Asendia shipping options.""" + """Asendia shipping options. + + The flag options below ride ``asendiaService.options`` (a list of wire + codes). Their wire code is the first ``OptionEnum`` argument — read it + from ``option.code``, never duplicate it in a parallel dict. + """ + + # Product / service feature flags (ride asendiaService.options as wire codes) + asendia_economy = lib.OptionEnum("ECO", bool, meta=dict(configurable=True, service_level=True)) + asendia_signature = lib.OptionEnum("SIG", bool, meta=dict(configurable=True, service_level=True)) + asendia_pudo = lib.OptionEnum("PUDO", bool, meta=dict(configurable=True, service_level=True)) + asendia_mailbox = lib.OptionEnum("MBX", bool, meta=dict(configurable=True, service_level=True)) + asendia_dangerous_goods = lib.OptionEnum("DG", bool, meta=dict(configurable=True, service_level=True)) + asendia_personal_delivery = lib.OptionEnum("PD", bool, meta=dict(configurable=True, service_level=True)) + asendia_smartgate_flex = lib.OptionEnum("SGF", bool, meta=dict(configurable=True, service_level=True)) + asendia_smartgate_direct = lib.OptionEnum("SGD", bool, meta=dict(configurable=True, service_level=True)) + asendia_direct_access = lib.OptionEnum("DA", bool, meta=dict(configurable=True, service_level=True)) + asendia_printed_matter = lib.OptionEnum("PM", bool, meta=dict(configurable=True, service_level=True)) # Asendia specific options - asendia_insurance = lib.OptionEnum("insurance", str) - asendia_return_label = lib.OptionEnum("return_label", bool) - asendia_return_label_type = lib.OptionEnum("return_label_type", str) - asendia_return_label_payment = lib.OptionEnum("return_label_payment", str) - asendia_sender_eori = lib.OptionEnum("sender_eori", str) - asendia_seller_eori = lib.OptionEnum("seller_eori", str) - asendia_sender_tax_id = lib.OptionEnum("sender_tax_id", str) - asendia_receiver_tax_id = lib.OptionEnum("receiver_tax_id", str) + asendia_insurance = lib.OptionEnum("insurance", str, meta=dict(configurable=True, service_level=False)) + asendia_return_label = lib.OptionEnum( + "return_label", bool, meta=dict(category="RETURN", configurable=True, service_level=True) + ) + asendia_return_label_type = lib.OptionEnum( + "return_label_type", str, meta=dict(category="RETURN", configurable=True, service_level=False) + ) + asendia_return_label_payment = lib.OptionEnum( + "return_label_payment", str, meta=dict(category="RETURN", configurable=True, service_level=False) + ) + asendia_sender_eori = lib.OptionEnum("sender_eori", str, meta=dict(configurable=True, service_level=False)) + asendia_seller_eori = lib.OptionEnum("seller_eori", str, meta=dict(configurable=True, service_level=False)) + asendia_sender_tax_id = lib.OptionEnum("sender_tax_id", str, meta=dict(configurable=True, service_level=False)) + asendia_receiver_tax_id = lib.OptionEnum("receiver_tax_id", str, meta=dict(configurable=True, service_level=False)) + asendia_service_type = lib.OptionEnum("service_type", str, meta=dict(configurable=True, service_level=True)) # Unified Option type mapping insurance = asendia_insurance +# Boolean feature flags serialized into asendiaService.options (wire codes read +# off option.code). Order is preserved on the wire. +SHIPMENT_FLAG_OPTIONS = ( + "asendia_economy", + "asendia_signature", + "asendia_pudo", + "asendia_mailbox", + "asendia_dangerous_goods", + "asendia_personal_delivery", + "asendia_smartgate_flex", + "asendia_smartgate_direct", + "asendia_direct_access", + "asendia_printed_matter", +) + + def shipping_options_initializer( options: dict, package_options: units.ShippingOptions = None, @@ -243,7 +283,7 @@ def load_services_from_csv() -> list: # Group zones by service services_dict: dict[str, dict] = {} - with open(csv_path, "r", encoding="utf-8") as f: + with open(csv_path, encoding="utf-8") as f: reader = csv.DictReader(f) for row in reader: service_code = row["service_code"] @@ -258,53 +298,33 @@ def load_services_from_csv() -> list: "service_name": service_name, "service_code": karrio_service_code, "currency": row.get("currency", "EUR"), - "min_weight": ( - float(row["min_weight"]) if row.get("min_weight") else None - ), - "max_weight": ( - float(row["max_weight"]) if row.get("max_weight") else None - ), - "max_length": ( - float(row["max_length"]) if row.get("max_length") else None - ), - "max_width": ( - float(row["max_width"]) if row.get("max_width") else None - ), - "max_height": ( - float(row["max_height"]) if row.get("max_height") else None - ), + "min_weight": (float(row["min_weight"]) if row.get("min_weight") else None), + "max_weight": (float(row["max_weight"]) if row.get("max_weight") else None), + "max_length": (float(row["max_length"]) if row.get("max_length") else None), + "max_width": (float(row["max_width"]) if row.get("max_width") else None), + "max_height": (float(row["max_height"]) if row.get("max_height") else None), "weight_unit": "KG", "dimension_unit": "CM", "domicile": (row.get("domicile") or "").lower() == "true", - "international": ( - True if (row.get("international") or "").lower() == "true" else None - ), + "international": (True if (row.get("international") or "").lower() == "true" else None), "zones": [], } # Parse country codes - country_codes = [ - c.strip() for c in row.get("country_codes", "").split(",") if c.strip() - ] + country_codes = [c.strip() for c in row.get("country_codes", "").split(",") if c.strip()] # Create zone zone = models.ServiceZone( label=row.get("zone_label", "Default Zone"), rate=float(row.get("rate", 0.0)), - transit_days=( - int(row["transit_days"]) if row.get("transit_days") else None - ), + transit_days=(int(row["transit_days"]) if row.get("transit_days") else None), country_codes=country_codes if country_codes else None, ) services_dict[karrio_service_code]["zones"].append(zone) # Convert to ServiceLevel objects - return [ - models.ServiceLevel(**service_data) for service_data in services_dict.values() - ] + return [models.ServiceLevel(**service_data) for service_data in services_dict.values()] DEFAULT_SERVICES = load_services_from_csv() - - diff --git a/modules/connectors/asendia/karrio/providers/asendia/utils.py b/modules/connectors/asendia/karrio/providers/asendia/utils.py index 502a6477b5..697201a74e 100644 --- a/modules/connectors/asendia/karrio/providers/asendia/utils.py +++ b/modules/connectors/asendia/karrio/providers/asendia/utils.py @@ -1,9 +1,10 @@ """Karrio Asendia provider utilities.""" import datetime -import karrio.lib as lib + import karrio.core as core import karrio.core.errors as errors +import karrio.lib as lib class Settings(core.Settings): @@ -63,6 +64,7 @@ def login(settings: Settings) -> dict: result = lib.request( url=f"{settings.server_url}/api/authenticate", + trace=settings.trace_as("json"), method="POST", headers={"Content-Type": "application/json"}, data=lib.to_json( diff --git a/modules/connectors/asendia/tests/__init__.py b/modules/connectors/asendia/tests/__init__.py index 4cf982856d..d1f575291a 100644 --- a/modules/connectors/asendia/tests/__init__.py +++ b/modules/connectors/asendia/tests/__init__.py @@ -1,4 +1,3 @@ - -from asendia.test_tracking import * -from asendia.test_shipment import * from asendia.test_manifest import * +from asendia.test_shipment import * +from asendia.test_tracking import * diff --git a/modules/connectors/asendia/tests/asendia/__init__.py b/modules/connectors/asendia/tests/asendia/__init__.py index 54814c8a8a..a2557731d7 100644 --- a/modules/connectors/asendia/tests/asendia/__init__.py +++ b/modules/connectors/asendia/tests/asendia/__init__.py @@ -1 +1 @@ -from .fixture import gateway \ No newline at end of file +from .fixture import gateway diff --git a/modules/connectors/asendia/tests/asendia/fixture.py b/modules/connectors/asendia/tests/asendia/fixture.py index 7aa3f2c990..fa7e15bd19 100644 --- a/modules/connectors/asendia/tests/asendia/fixture.py +++ b/modules/connectors/asendia/tests/asendia/fixture.py @@ -2,7 +2,6 @@ import karrio.sdk as karrio - gateway = karrio.gateway["asendia"].create( dict( id="asendia", diff --git a/modules/connectors/asendia/tests/asendia/test_manifest.py b/modules/connectors/asendia/tests/asendia/test_manifest.py index 4b2aabc632..0381435868 100644 --- a/modules/connectors/asendia/tests/asendia/test_manifest.py +++ b/modules/connectors/asendia/tests/asendia/test_manifest.py @@ -1,12 +1,14 @@ """Asendia carrier manifest tests.""" -import unittest -from unittest.mock import patch, ANY -from .fixture import gateway import logging -import karrio.sdk as karrio -import karrio.lib as lib +import unittest +from unittest.mock import patch + import karrio.core.models as models +import karrio.lib as lib +import karrio.sdk as karrio + +from .fixture import gateway logger = logging.getLogger(__name__) @@ -21,44 +23,44 @@ def test_create_manifest_request(self): self.assertEqual(lib.to_dict(request.serialize()), ManifestRequest) def test_create_manifest(self): - with patch("karrio.mappers.asendia.proxy.lib.request") as mock: - with patch( + with ( + patch("karrio.mappers.asendia.proxy.lib.request") as mock, + patch( "karrio.providers.asendia.utils.Settings.access_token", new_callable=lambda: property(lambda self: "test_token"), - ): - mock.return_value = "{}" - karrio.Manifest.create(self.ManifestRequest).from_(gateway) - self.assertEqual( - mock.call_args[1]["url"], - f"{gateway.settings.server_url}/api/manifests", - ) + ), + ): + mock.return_value = "{}" + karrio.Manifest.create(self.ManifestRequest).from_(gateway) + self.assertEqual( + mock.call_args[1]["url"], + f"{gateway.settings.server_url}/api/manifests", + ) def test_parse_manifest_response(self): - with patch("karrio.mappers.asendia.proxy.lib.request") as mock: - with patch( + with ( + patch("karrio.mappers.asendia.proxy.lib.request") as mock, + patch( "karrio.providers.asendia.utils.Settings.access_token", new_callable=lambda: property(lambda self: "test_token"), - ): - mock.return_value = ManifestResponse - parsed_response = ( - karrio.Manifest.create(self.ManifestRequest).from_(gateway).parse() - ) - self.assertListEqual( - lib.to_dict(parsed_response), ParsedManifestResponse - ) + ), + ): + mock.return_value = ManifestResponse + parsed_response = karrio.Manifest.create(self.ManifestRequest).from_(gateway).parse() + self.assertListEqual(lib.to_dict(parsed_response), ParsedManifestResponse) def test_parse_error_response(self): - with patch("karrio.mappers.asendia.proxy.lib.request") as mock: - with patch( + with ( + patch("karrio.mappers.asendia.proxy.lib.request") as mock, + patch( "karrio.providers.asendia.utils.Settings.access_token", new_callable=lambda: property(lambda self: "test_token"), - ): - mock.return_value = ErrorResponse - parsed_response = ( - karrio.Manifest.create(self.ManifestRequest).from_(gateway).parse() - ) + ), + ): + mock.return_value = ErrorResponse + parsed_response = karrio.Manifest.create(self.ManifestRequest).from_(gateway).parse() - self.assertListEqual(lib.to_dict(parsed_response), ParsedErrorResponse) + self.assertListEqual(lib.to_dict(parsed_response), ParsedErrorResponse) if __name__ == "__main__": @@ -76,12 +78,10 @@ def test_parse_error_response(self): }, } -ManifestRequest = { - "parcelIds": [ - "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "4fa85f64-5717-4562-b3fc-2c963f66afa7", - ], -} +ManifestRequest = [ + "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "4fa85f64-5717-4562-b3fc-2c963f66afa7", +] ManifestResponse = """{ "id": "manifest-123", diff --git a/modules/connectors/asendia/tests/asendia/test_rate.py b/modules/connectors/asendia/tests/asendia/test_rate.py new file mode 100644 index 0000000000..e9ee35950b --- /dev/null +++ b/modules/connectors/asendia/tests/asendia/test_rate.py @@ -0,0 +1,221 @@ +"""Asendia carrier rate tests.""" + +import unittest + +import karrio.lib as lib +import karrio.sdk as karrio +from karrio.core.models import RateRequest +from karrio.providers.asendia import units + +from .fixture import gateway + + +class TestAsendiaRating(unittest.TestCase): + def setUp(self): + self.maxDiff = None + self.RateRequest = RateRequest(**rate_request_data) + + def test_parse_rate_response(self): + parsed_response = karrio.Rating.fetch(self.RateRequest).from_(gateway).parse() + + self.assertListEqual(lib.to_dict(parsed_response), ParsedRateResponse) + + +class TestAsendiaServiceFiltering(unittest.TestCase): + """Test service filtering for domestic vs international shipments.""" + + maxDiff = None + + def setUp(self): + self.services = units.DEFAULT_SERVICES + + def test_default_services_loaded(self): + self.assertGreater(len(self.services), 0, "Services should be loaded") + + service_codes = [s.service_code for s in self.services] + self.assertIn("asendia_epaq_standard", service_codes) + self.assertIn("asendia_epaq_returns_domestic", service_codes) + self.assertIn("asendia_epaq_returns_international", service_codes) + + def test_domestic_service_marked_correctly(self): + domestic_services = [s for s in self.services if s.service_code == "asendia_epaq_returns_domestic"] + + self.assertEqual(len(domestic_services), 1) + self.assertTrue(domestic_services[0].domicile, "Domestic service should have domicile=True") + + def test_international_services_marked_correctly(self): + international_services = [s for s in self.services if s.service_code == "asendia_epaq_standard"] + + self.assertEqual(len(international_services), 1) + self.assertTrue( + international_services[0].international, + "International service should have international=True", + ) + self.assertFalse( + international_services[0].domicile, + "International service should not have domicile=True", + ) + + def test_domestic_request_returns_only_domestic_services(self): + domestic_request = RateRequest( + **{ + "shipper": {"postal_code": "10115", "country_code": "DE"}, + "recipient": {"postal_code": "20095", "country_code": "DE"}, + "parcels": [ + { + "weight": 5.0, + "weight_unit": "KG", + } + ], + } + ) + + parsed_response = karrio.Rating.fetch(domestic_request).from_(gateway).parse() + rates = parsed_response[0] + + service_codes = [rate.service for rate in rates] + self.assertIn("asendia_epaq_returns_domestic", service_codes) + self.assertNotIn( + "asendia_epaq_standard", + service_codes, + "International services should not be returned for domestic requests", + ) + + def test_international_request_returns_only_international_services(self): + international_request = RateRequest( + **{ + "shipper": {"postal_code": "10115", "country_code": "DE"}, + "recipient": {"postal_code": "SW1A 1AA", "country_code": "GB"}, + "parcels": [ + { + "weight": 5.0, + "weight_unit": "KG", + } + ], + } + ) + + parsed_response = karrio.Rating.fetch(international_request).from_(gateway).parse() + rates = parsed_response[0] + + service_codes = [rate.service for rate in rates] + self.assertIn("asendia_epaq_standard", service_codes) + self.assertNotIn( + "asendia_epaq_returns_domestic", + service_codes, + "Domestic services should not be returned for international requests", + ) + + def test_weight_limits_respected(self): + epaq_standard = next((s for s in self.services if s.service_code == "asendia_epaq_standard"), None) + + self.assertIsNotNone(epaq_standard, "EPAQSTD service should be loaded") + self.assertEqual(epaq_standard.min_weight, 0.01) + self.assertEqual(epaq_standard.max_weight, 31.5) + self.assertEqual(epaq_standard.weight_unit, "KG") + + +if __name__ == "__main__": + unittest.main() + + +rate_request_data = { + "shipper": {"postal_code": "10115", "country_code": "DE"}, + "recipient": {"postal_code": "SW1A 1AA", "country_code": "GB"}, + "parcels": [ + { + "height": 3.0, + "length": 5.0, + "width": 3.0, + "weight": 4.0, + "dimension_unit": "CM", + "weight_unit": "KG", + } + ], + "services": [], +} + + +ParsedRateResponse = [ + [ + { + "carrier_id": "asendia", + "carrier_name": "asendia", + "currency": "EUR", + "extra_charges": [{"amount": 0.0, "currency": "EUR", "name": "Base Charge"}], + "meta": { + "service_name": "e-PAQ Standard", + "shipping_charges": 0.0, + "shipping_currency": "EUR", + }, + "service": "asendia_epaq_standard", + "total_charge": 0.0, + }, + { + "carrier_id": "asendia", + "carrier_name": "asendia", + "currency": "EUR", + "extra_charges": [{"amount": 0.0, "currency": "EUR", "name": "Base Charge"}], + "meta": { + "service_name": "e-PAQ Plus", + "shipping_charges": 0.0, + "shipping_currency": "EUR", + }, + "service": "asendia_epaq_plus", + "total_charge": 0.0, + }, + { + "carrier_id": "asendia", + "carrier_name": "asendia", + "currency": "EUR", + "extra_charges": [{"amount": 0.0, "currency": "EUR", "name": "Base Charge"}], + "meta": { + "service_name": "e-PAQ Select", + "shipping_charges": 0.0, + "shipping_currency": "EUR", + }, + "service": "asendia_epaq_select", + "total_charge": 0.0, + }, + { + "carrier_id": "asendia", + "carrier_name": "asendia", + "currency": "EUR", + "extra_charges": [{"amount": 0.0, "currency": "EUR", "name": "Base Charge"}], + "meta": { + "service_name": "e-PAQ Elite", + "shipping_charges": 0.0, + "shipping_currency": "EUR", + }, + "service": "asendia_epaq_elite", + "total_charge": 0.0, + }, + { + "carrier_id": "asendia", + "carrier_name": "asendia", + "currency": "EUR", + "extra_charges": [{"amount": 0.0, "currency": "EUR", "name": "Base Charge"}], + "meta": { + "service_name": "e-PAQ GO", + "shipping_charges": 0.0, + "shipping_currency": "EUR", + }, + "service": "asendia_epaq_go", + "total_charge": 0.0, + }, + { + "carrier_id": "asendia", + "carrier_name": "asendia", + "currency": "EUR", + "extra_charges": [{"amount": 0.0, "currency": "EUR", "name": "Base Charge"}], + "meta": { + "service_name": "e-PAQ International Returns", + "shipping_charges": 0.0, + "shipping_currency": "EUR", + }, + "service": "asendia_epaq_returns_international", + "total_charge": 0.0, + }, + ], + [], +] diff --git a/modules/connectors/asendia/tests/asendia/test_shipment.py b/modules/connectors/asendia/tests/asendia/test_shipment.py index 2d5786eef4..586c05e7ba 100644 --- a/modules/connectors/asendia/tests/asendia/test_shipment.py +++ b/modules/connectors/asendia/tests/asendia/test_shipment.py @@ -1,12 +1,14 @@ """Asendia carrier shipment tests.""" -import unittest -from unittest.mock import patch, ANY -from .fixture import gateway import logging -import karrio.sdk as karrio -import karrio.lib as lib +import unittest +from unittest.mock import patch + import karrio.core.models as models +import karrio.lib as lib +import karrio.sdk as karrio + +from .fixture import gateway logger = logging.getLogger(__name__) @@ -15,12 +17,8 @@ class TestAsendiaShipment(unittest.TestCase): def setUp(self): self.maxDiff = None self.ShipmentRequest = models.ShipmentRequest(**ShipmentPayload) - self.ShipmentCancelRequest = models.ShipmentCancelRequest( - **ShipmentCancelPayload - ) - self.MultiPieceShipmentRequest = models.ShipmentRequest( - **MultiPieceShipmentPayload - ) + self.ShipmentCancelRequest = models.ShipmentCancelRequest(**ShipmentCancelPayload) + self.MultiPieceShipmentRequest = models.ShipmentRequest(**MultiPieceShipmentPayload) def test_create_shipment_request(self): request = gateway.mapper.create_shipment_request(self.ShipmentRequest) @@ -28,82 +26,171 @@ def test_create_shipment_request(self): self.assertEqual(lib.to_dict(request.serialize()), ShipmentRequest) def test_create_shipment(self): - with patch("karrio.mappers.asendia.proxy.lib.run_asynchronously") as mock_async: - with patch( + with ( + patch("karrio.mappers.asendia.proxy.lib.run_asynchronously") as mock_async, + patch( "karrio.providers.asendia.utils.Settings.access_token", new_callable=lambda: property(lambda self: "test_token"), - ): - # Mock both async calls: 1) create parcels, 2) fetch labels - mock_async.side_effect = [ - [ShipmentResponse], # First call: create parcel - [ - { - "parcel": lib.to_dict(ShipmentResponse), - "label": MockLabelBase64, - } - ], # Second call: fetch label - ] - karrio.Shipment.create(self.ShipmentRequest).from_(gateway) - # Verify the async function was called twice (create + fetch labels) - self.assertEqual(mock_async.call_count, 2) + ), + ): + # Mock both async calls: 1) create parcels, 2) fetch labels + mock_async.side_effect = [ + [ShipmentResponse], # First call: create parcel + [ + { + "parcel": lib.to_dict(ShipmentResponse), + "label": MockLabelBase64, + } + ], # Second call: fetch label + ] + karrio.Shipment.create(self.ShipmentRequest).from_(gateway) + # Verify the async function was called twice (create + fetch labels) + self.assertEqual(mock_async.call_count, 2) def test_parse_shipment_response(self): - with patch("karrio.mappers.asendia.proxy.lib.run_asynchronously") as mock_async: - with patch( + with ( + patch("karrio.mappers.asendia.proxy.lib.run_asynchronously") as mock_async, + patch( "karrio.providers.asendia.utils.Settings.access_token", new_callable=lambda: property(lambda self: "test_token"), - ): - # Mock both async calls: 1) create parcels, 2) fetch labels - mock_async.side_effect = [ - [ShipmentResponse], # First call: create parcel - [ - { - "parcel": lib.to_dict(ShipmentResponse), - "label": MockLabelBase64, - } - ], # Second call: fetch label - ] - parsed_response = ( - karrio.Shipment.create(self.ShipmentRequest).from_(gateway).parse() - ) - self.assertListEqual( - lib.to_dict(parsed_response), ParsedShipmentResponse - ) + ), + ): + # Mock both async calls: 1) create parcels, 2) fetch labels + mock_async.side_effect = [ + [ShipmentResponse], # First call: create parcel + [ + { + "parcel": lib.to_dict(ShipmentResponse), + "label": MockLabelBase64, + } + ], # Second call: fetch label + ] + parsed_response = karrio.Shipment.create(self.ShipmentRequest).from_(gateway).parse() + self.assertListEqual(lib.to_dict(parsed_response), ParsedShipmentResponse) def test_create_cancel_request(self): - request = gateway.mapper.create_cancel_shipment_request( - self.ShipmentCancelRequest - ) + request = gateway.mapper.create_cancel_shipment_request(self.ShipmentCancelRequest) self.assertEqual(request.serialize(), ShipmentCancelRequest) def test_cancel_shipment(self): - with patch("karrio.mappers.asendia.proxy.lib.request") as mock: - with patch( + with ( + patch("karrio.mappers.asendia.proxy.lib.request") as mock, + patch( "karrio.providers.asendia.utils.Settings.access_token", new_callable=lambda: property(lambda self: "test_token"), - ): - mock.return_value = "{}" - karrio.Shipment.cancel(self.ShipmentCancelRequest).from_(gateway) - self.assertEqual( - mock.call_args[1]["url"], - f"{gateway.settings.server_url}/api/parcels/3fa85f64-5717-4562-b3fc-2c963f66afa6", - ) + ), + ): + mock.return_value = "{}" + karrio.Shipment.cancel(self.ShipmentCancelRequest).from_(gateway) + self.assertEqual( + mock.call_args[1]["url"], + f"{gateway.settings.server_url}/api/parcels/3fa85f64-5717-4562-b3fc-2c963f66afa6", + ) def test_parse_cancel_response(self): - with patch("karrio.mappers.asendia.proxy.lib.request") as mock: - with patch( + with ( + patch("karrio.mappers.asendia.proxy.lib.request") as mock, + patch( "karrio.providers.asendia.utils.Settings.access_token", new_callable=lambda: property(lambda self: "test_token"), - ): - mock.return_value = "{}" - parsed_response = ( - karrio.Shipment.cancel(self.ShipmentCancelRequest) - .from_(gateway) - .parse() - ) - self.assertListEqual( - lib.to_dict(parsed_response), ParsedShipmentCancelResponse - ) + ), + ): + mock.return_value = "{}" + parsed_response = karrio.Shipment.cancel(self.ShipmentCancelRequest).from_(gateway).parse() + self.assertListEqual(lib.to_dict(parsed_response), ParsedShipmentCancelResponse) + + def test_cancel_empty_body_204(self): + """HTTP 204 returns empty body — must not raise JSONDecodeError.""" + with ( + patch("karrio.mappers.asendia.proxy.lib.request") as mock, + patch( + "karrio.providers.asendia.utils.Settings.access_token", + new_callable=lambda: property(lambda self: "test_token"), + ), + ): + mock.return_value = "" + parsed_response = karrio.Shipment.cancel(self.ShipmentCancelRequest).from_(gateway).parse() + self.assertListEqual(lib.to_dict(parsed_response), ParsedShipmentCancelResponse) + + def test_international_return_label_defaults_to_epaqretint(self): + """International shipment with return_label=True should default type to EPAQRETINT.""" + intl_payload = { + **ShipmentPayload, + "options": {"asendia_return_label": True}, + } + request = gateway.mapper.create_shipment_request(models.ShipmentRequest(**intl_payload)) + serialized = lib.to_dict(request.serialize()) + return_label_option = serialized[0].get("asendiaService", {}).get("returnLabelOption", {}) + self.assertEqual(return_label_option.get("type"), "EPAQRETINT") + + def test_create_shipment_with_service_options(self): + """Carrier feature flags serialize to asendiaService.options as wire codes, in order.""" + payload = { + **ShipmentPayload, + "options": { + "asendia_economy": True, + "asendia_signature": True, + "asendia_pudo": True, + }, + } + request = gateway.mapper.create_shipment_request(models.ShipmentRequest(**payload)) + serialized = lib.to_dict(request.serialize()) + self.assertEqual(serialized[0]["asendiaService"]["options"], ["ECO", "SIG", "PUDO"]) + + def test_create_shipment_with_cppr_service_modifier(self): + """Service modifier is configurable per subsidiary (e.g. EPAQSCT | CPPR for Italy).""" + payload = { + **ShipmentPayload, + "service": "asendia_epaq_select", + "options": {"asendia_service_type": "CPPR"}, + } + request = gateway.mapper.create_shipment_request(models.ShipmentRequest(**payload)) + serialized = lib.to_dict(request.serialize()) + self.assertEqual(serialized[0]["asendiaService"]["product"], "EPAQSCT") + self.assertEqual(serialized[0]["asendiaService"]["service"], "CPPR") + + def test_create_smartgate_flex_shipment_request(self): + """SmartGate Flex (CH) adds a shipping-cost customs line and the SGF wire option.""" + request = gateway.mapper.create_shipment_request(models.ShipmentRequest(**SmartGateFlexPayload)) + self.assertEqual(lib.to_dict(request.serialize()), SmartGateFlexRequest) + + def test_smartgate_coerces_boxable_format_to_n(self): + """SmartGate (SGF/SGD) only accepts format N or L — a boxable default coerces to N.""" + payload = {**ShipmentPayload, "options": {"asendia_smartgate_direct": True}} + serialized = lib.to_dict(gateway.mapper.create_shipment_request(models.ShipmentRequest(**payload)).serialize()) + self.assertEqual(serialized[0]["asendiaService"]["format"], "N") + + def test_smartgate_keeps_large_format(self): + """SmartGate keeps an already-valid format (tube → L) instead of coercing.""" + payload = { + **ShipmentPayload, + "parcels": [{"weight": 1.5, "weight_unit": "KG", "packaging_type": "tube"}], + "options": {"asendia_smartgate_flex": True}, + } + serialized = lib.to_dict(gateway.mapper.create_shipment_request(models.ShipmentRequest(**payload)).serialize()) + self.assertEqual(serialized[0]["asendiaService"]["format"], "L") + + def test_select_sends_format_b(self): + """e-PAQ Select still sends a format — the Asendia API rejects a missing one (VE-004).""" + payload = {**ShipmentPayload, "service": "asendia_epaq_select"} + serialized = lib.to_dict(gateway.mapper.create_shipment_request(models.ShipmentRequest(**payload)).serialize()) + self.assertEqual(serialized[0]["asendiaService"]["format"], "B") + + def test_elite_coerces_to_large_format(self): + """e-PAQ Elite quotes carry L/XL only — a boxable default coerces to L.""" + payload = {**ShipmentPayload, "service": "asendia_epaq_elite"} + serialized = lib.to_dict(gateway.mapper.create_shipment_request(models.ShipmentRequest(**payload)).serialize()) + self.assertEqual(serialized[0]["asendiaService"]["format"], "L") + + def test_standard_coerces_large_format_to_n(self): + """e-PAQ Standard/Plus only accept B or N — a tube (L) coerces to N.""" + payload = { + **ShipmentPayload, + "service": "asendia_epaq_plus", + "parcels": [{"weight": 1.5, "weight_unit": "KG", "packaging_type": "tube"}], + } + serialized = lib.to_dict(gateway.mapper.create_shipment_request(models.ShipmentRequest(**payload)).serialize()) + self.assertEqual(serialized[0]["asendiaService"]["format"], "N") def test_create_multi_piece_shipment_request(self): """Test that multi-piece shipments create one request per package.""" @@ -115,66 +202,56 @@ def test_create_multi_piece_shipment_request(self): def test_parse_multi_piece_shipment_response(self): """Test that multi-piece responses are aggregated correctly using lib.to_multi_piece_shipment().""" - with patch("karrio.mappers.asendia.proxy.lib.run_asynchronously") as mock_async: - with patch( + with ( + patch("karrio.mappers.asendia.proxy.lib.run_asynchronously") as mock_async, + patch( "karrio.providers.asendia.utils.Settings.access_token", new_callable=lambda: property(lambda self: "test_token"), - ): - with patch( - "karrio.core.utils.transformer.utils.bundle_base64" - ) as mock_bundle: - # Mock bundle_base64 to return the first label (skip actual PDF merging) - mock_bundle.return_value = MockLabelBase64 - # Mock both async calls: 1) create parcels, 2) fetch labels - mock_async.side_effect = [ - [ - MultiPieceShipmentResponse1, - MultiPieceShipmentResponse2, - ], # First call: create parcels - [ - { - "parcel": lib.to_dict(MultiPieceShipmentResponse1), - "label": MockLabelBase64, - }, - { - "parcel": lib.to_dict(MultiPieceShipmentResponse2), - "label": MockLabelBase64, - }, - ], # Second call: fetch labels - ] - parsed_response = ( - karrio.Shipment.create(self.MultiPieceShipmentRequest) - .from_(gateway) - .parse() - ) - result = lib.to_dict(parsed_response) - # Sort the lists in meta for comparison since lib.to_multi_piece_shipment() uses sets - result[0]["meta"]["shipment_identifiers"] = sorted( - result[0]["meta"]["shipment_identifiers"] - ) - result[0]["meta"]["tracking_numbers"] = sorted( - result[0]["meta"]["tracking_numbers"] - ) - self.assertListEqual(result, ParsedMultiPieceShipmentResponse) + ), + patch("karrio.core.utils.transformer.utils.bundle_base64") as mock_bundle, + ): + # Mock bundle_base64 to return the first label (skip actual PDF merging) + mock_bundle.return_value = MockLabelBase64 + # Mock both async calls: 1) create parcels, 2) fetch labels + mock_async.side_effect = [ + [ + MultiPieceShipmentResponse1, + MultiPieceShipmentResponse2, + ], # First call: create parcels + [ + { + "parcel": lib.to_dict(MultiPieceShipmentResponse1), + "label": MockLabelBase64, + }, + { + "parcel": lib.to_dict(MultiPieceShipmentResponse2), + "label": MockLabelBase64, + }, + ], # Second call: fetch labels + ] + parsed_response = karrio.Shipment.create(self.MultiPieceShipmentRequest).from_(gateway).parse() + result = lib.to_dict(parsed_response) + # Sort the lists in meta for comparison since lib.to_multi_piece_shipment() uses sets + result[0]["meta"]["shipment_identifiers"] = sorted(result[0]["meta"]["shipment_identifiers"]) + result[0]["meta"]["tracking_numbers"] = sorted(result[0]["meta"]["tracking_numbers"]) + self.assertListEqual(result, ParsedMultiPieceShipmentResponse) def test_parse_error_response(self): - with patch("karrio.mappers.asendia.proxy.lib.run_asynchronously") as mock_async: - with patch( + with ( + patch("karrio.mappers.asendia.proxy.lib.run_asynchronously") as mock_async, + patch( "karrio.providers.asendia.utils.Settings.access_token", new_callable=lambda: property(lambda self: "test_token"), - ): - # Mock error response from create parcel - error_dict = lib.to_dict(ErrorResponse) - mock_async.side_effect = [ - [ErrorResponse], # First call: create parcel returns error - [ - {"parcel": error_dict, "label": None} - ], # Second call: return error with no label - ] - parsed_response = ( - karrio.Shipment.create(self.ShipmentRequest).from_(gateway).parse() - ) - self.assertListEqual(lib.to_dict(parsed_response), ParsedErrorResponse) + ), + ): + # Mock error response from create parcel + error_dict = lib.to_dict(ErrorResponse) + mock_async.side_effect = [ + [ErrorResponse], # First call: create parcel returns error + [{"parcel": error_dict, "label": None}], # Second call: return error with no label + ] + parsed_response = karrio.Shipment.create(self.ShipmentRequest).from_(gateway).parse() + self.assertListEqual(lib.to_dict(parsed_response), ParsedErrorResponse) if __name__ == "__main__": @@ -213,6 +290,54 @@ def test_parse_error_response(self): "reference": "REF-123456", } +SmartGateFlexPayload = { + "shipper": { + "address_line1": "Hauptstrasse 5", + "city": "Frankfurt", + "postal_code": "60311", + "country_code": "DE", + "person_name": "John Sender", + "company_name": "Sender Company", + "phone_number": "+4969123456", + "email": "sender@example.com", + }, + "recipient": { + "address_line1": "Bahnhofstrasse 1", + "city": "Zurich", + "postal_code": "8001", + "country_code": "CH", + "person_name": "Jane Receiver", + "company_name": "Receiver AG", + "phone_number": "+41441234567", + "email": "receiver@example.com", + }, + "parcels": [ + { + "weight": 1.5, + "weight_unit": "KG", + "items": [ + { + "description": "Cotton T-Shirt", + "sku": "SKU-1", + "value_amount": 25.0, + "value_currency": "EUR", + "hs_code": "61091000", + "origin_country": "DE", + "weight": 0.3, + "weight_unit": "KG", + "quantity": 2, + } + ], + } + ], + "service": "asendia_epaq_select", + "reference": "REF-SGF-1", + "options": { + "asendia_smartgate_flex": True, + "declared_value": 9.99, + }, +} + ShipmentCancelPayload = {"shipment_identifier": "3fa85f64-5717-4562-b3fc-2c963f66afa6"} # Request is now a list (one per package) for Pattern B: Per-Package Request @@ -244,6 +369,7 @@ def test_parse_error_response(self): "asendiaService": { "format": "B", "product": "EPAQSTD", + "service": "CUP", }, "customerId": "CUST123", "labelType": "PDF", @@ -252,6 +378,69 @@ def test_parse_error_response(self): } ] +SmartGateFlexRequest = [ + { + "addresses": { + "receiver": { + "address1": "Bahnhofstrasse 1", + "city": "Zurich", + "company": "Receiver AG", + "country": "CH", + "email": "receiver@example.com", + "name": "Jane Receiver", + "phone": "+41441234567", + "postalCode": "8001", + }, + "sender": { + "address1": "Hauptstrasse 5", + "city": "Frankfurt", + "company": "Sender Company", + "country": "DE", + "email": "sender@example.com", + "name": "John Sender", + "phone": "+4969123456", + "postalCode": "60311", + }, + }, + "asendiaService": { + "format": "N", + "options": ["SGF"], + "product": "EPAQSCT", + "service": "CUP", + }, + "customerId": "CUST123", + "customsInfo": { + "currency": "EUR", + "items": [ + { + "articleDescription": "Cotton T-Shirt", + "articleNumber": "SKU-1", + "currency": "EUR", + "harmonizationCode": "61091000", + "originCountry": "DE", + "quantity": 2, + "unitValue": 25.0, + "unitWeight": 0.3, + }, + { + "articleDescription": "Shipping costs", + "articleNumber": "MCGSHP", + "currency": "EUR", + "harmonizationCode": "00000000", + "originCountry": "DE", + "quantity": 1, + "unitValue": 9.99, + "unitWeight": 0.01, + }, + ], + }, + "labelType": "PDF", + "referencenumber": "REF-SGF-1", + "shippingCost": 9.99, + "weight": 1.5, + } +] + ShipmentCancelRequest = "3fa85f64-5717-4562-b3fc-2c963f66afa6" ShipmentResponse = """{ @@ -267,9 +456,7 @@ def test_parse_error_response(self): }""" # Mock base64-encoded label (simple PDF header for testing) -MockLabelBase64 = ( - "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PgplbmRvYmoK" -) +MockLabelBase64 = "JVBERi0xLjQKJeLjz9MKMSAwIG9iago8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PgplbmRvYmoK" ParsedShipmentResponse = [ { @@ -354,7 +541,7 @@ def test_parse_error_response(self): "postalCode": "3030", }, }, - "asendiaService": {"format": "B", "product": "EPAQSTD"}, + "asendiaService": {"format": "B", "product": "EPAQSTD", "service": "CUP"}, "customerId": "CUST123", "labelType": "PDF", "referencenumber": "REF-MULTI-001", @@ -384,7 +571,7 @@ def test_parse_error_response(self): "postalCode": "3030", }, }, - "asendiaService": {"format": "B", "product": "EPAQSTD"}, + "asendiaService": {"format": "B", "product": "EPAQSTD", "service": "CUP"}, "customerId": "CUST123", "labelType": "PDF", "referencenumber": "REF-MULTI-001", @@ -420,9 +607,7 @@ def test_parse_error_response(self): { "carrier_id": "asendia", "carrier_name": "asendia", - "docs": { - "label": MockLabelBase64 - }, # lib.to_multi_piece_shipment() bundles labels + "docs": {"label": MockLabelBase64}, # lib.to_multi_piece_shipment() bundles labels "label_type": "PDF", "meta": { "carrier_tracking_link": "https://tracking.asendia.com/tracking/ASENDIA111111111", diff --git a/modules/connectors/asendia/tests/asendia/test_tracking.py b/modules/connectors/asendia/tests/asendia/test_tracking.py index 8a2ce1ec0b..a8cb3e69bf 100644 --- a/modules/connectors/asendia/tests/asendia/test_tracking.py +++ b/modules/connectors/asendia/tests/asendia/test_tracking.py @@ -1,12 +1,13 @@ """Asendia carrier tracking tests.""" import unittest -from unittest.mock import patch, ANY -from .fixture import gateway +from unittest.mock import patch -import karrio.sdk as karrio -import karrio.lib as lib import karrio.core.models as models +import karrio.lib as lib +import karrio.sdk as karrio + +from .fixture import gateway class TestAsendiaTracking(unittest.TestCase): @@ -19,43 +20,43 @@ def test_create_tracking_request(self): self.assertEqual(request.serialize(), TrackingRequest) def test_get_tracking(self): - with patch("karrio.mappers.asendia.proxy.lib.request") as mock: - with patch( + with ( + patch("karrio.mappers.asendia.proxy.lib.request") as mock, + patch( "karrio.providers.asendia.utils.Settings.access_token", new_callable=lambda: property(lambda self: "test_token"), - ): - mock.return_value = TrackingResponse - karrio.Tracking.fetch(self.TrackingRequest).from_(gateway) - self.assertEqual( - mock.call_args[1]["url"], - f"{gateway.settings.server_url}/api/customers/CUST123/tracking/ASENDIA123456789", - ) + ), + ): + mock.return_value = TrackingResponse + karrio.Tracking.fetch(self.TrackingRequest).from_(gateway) + self.assertEqual( + mock.call_args[1]["url"], + f"{gateway.settings.server_url}/api/customers/CUST123/tracking/ASENDIA123456789", + ) def test_parse_tracking_response(self): - with patch("karrio.mappers.asendia.proxy.lib.request") as mock: - with patch( + with ( + patch("karrio.mappers.asendia.proxy.lib.request") as mock, + patch( "karrio.providers.asendia.utils.Settings.access_token", new_callable=lambda: property(lambda self: "test_token"), - ): - mock.return_value = TrackingResponse - parsed_response = ( - karrio.Tracking.fetch(self.TrackingRequest).from_(gateway).parse() - ) - self.assertListEqual( - lib.to_dict(parsed_response), ParsedTrackingResponse - ) + ), + ): + mock.return_value = TrackingResponse + parsed_response = karrio.Tracking.fetch(self.TrackingRequest).from_(gateway).parse() + self.assertListEqual(lib.to_dict(parsed_response), ParsedTrackingResponse) def test_parse_error_response(self): - with patch("karrio.mappers.asendia.proxy.lib.request") as mock: - with patch( + with ( + patch("karrio.mappers.asendia.proxy.lib.request") as mock, + patch( "karrio.providers.asendia.utils.Settings.access_token", new_callable=lambda: property(lambda self: "test_token"), - ): - mock.return_value = ErrorResponse - parsed_response = ( - karrio.Tracking.fetch(self.TrackingRequest).from_(gateway).parse() - ) - self.assertListEqual(lib.to_dict(parsed_response), ParsedErrorResponse) + ), + ): + mock.return_value = ErrorResponse + parsed_response = karrio.Tracking.fetch(self.TrackingRequest).from_(gateway).parse() + self.assertListEqual(lib.to_dict(parsed_response), ParsedErrorResponse) if __name__ == "__main__": @@ -68,35 +69,32 @@ def test_parse_error_response(self): TrackingRequest = ["ASENDIA123456789"] -TrackingResponse = """{ - "trackingNumber": "ASENDIA123456789", - "trackingEvents": [ - { - "id": 1, - "code": "PU", - "time": "2024-04-12T14:30:00Z", - "locationName": "Bern", - "carrierEventDescription": "Package picked up", - "locationCountry": "CH" - }, - { - "id": 2, - "code": "IT", - "time": "2024-04-13T09:15:00Z", - "locationName": "Zurich Airport", - "carrierEventDescription": "In transit to destination", - "locationCountry": "CH" - }, - { - "id": 3, - "code": "DL", - "time": "2024-04-15T11:00:00Z", - "locationName": "New York", - "carrierEventDescription": "Delivered", - "locationCountry": "US" - } - ] -}""" +TrackingResponse = """[ + { + "id": 1, + "code": "PU", + "time": "2024-04-12T14:30:00Z", + "locationName": "Bern", + "carrierEventDescription": "Package picked up", + "locationCountry": "CH" + }, + { + "id": 2, + "code": "IT", + "time": "2024-04-13T09:15:00Z", + "locationName": "Zurich Airport", + "carrierEventDescription": "In transit to destination", + "locationCountry": "CH" + }, + { + "id": 3, + "code": "DL", + "time": "2024-04-15T11:00:00Z", + "locationName": "New York", + "carrierEventDescription": "Delivered", + "locationCountry": "US" + } +]""" ParsedTrackingResponse = [ [ diff --git a/modules/connectors/asendia/vendor/README.md b/modules/connectors/asendia/vendor/README.md new file mode 100644 index 0000000000..5208ada384 --- /dev/null +++ b/modules/connectors/asendia/vendor/README.md @@ -0,0 +1,61 @@ +# Asendia Vendor Documentation + +This directory contains the Asendia Sync API specification used by the Asendia connector. + +## API Overview + +- **Source**: Asendia Sync API — https://www.asendia-sync.com/swagger-ui/index.html +- **Spec URL**: https://www.asendia-sync.com/v3/api-docs +- **Authentication**: JWT Bearer — obtain token via `POST /api/authenticate` with `{username, password}` + +## Endpoints Used + +| Method | Path | Purpose | +|--------|------|---------| +| POST | `/api/authenticate` | Obtain JWT access token | +| POST | `/api/parcels` | Create shipment (one request per package) | +| DELETE | `/api/parcels/{id}` | Cancel shipment (HTTP 204, empty body) | +| GET | `/api/parcels/{id}/label` | Fetch label (base64 PDF/PNG/ZPL) | +| GET | `/api/parcels/{id}/return-label` | Fetch return label | +| GET | `/api/customers/{customerId}/tracking/{trackingNumber}` | Get tracking events | +| POST | `/api/manifests` | Close manifest | +| GET | `/api/manifests/{id}/document` | Fetch manifest document | + +## Rate Handling + +No native rating API. The connector uses `RatingMixinProxy` with a rate sheet loaded from `services.csv`. +Service definitions, weight limits, and zone rates live in that CSV — not in the OpenAPI spec. + +## Enums (see `karrio/providers/asendia/units.py`) + +| Enum class | Field | Values | +|------------|-------|--------| +| `ShippingService` | `asendiaService.product` | `EPAQSTD`, `EPAQPLS`, `EPAQSCT`, `EPAQELT`, `EPAQGO`, `EPAQRETDOM`, `EPAQRETINT` | +| `ServiceCode` | `asendiaService.service` | `CUP`, `CPPR`, `CPPS`, `RETPP`, `RETPAP` | +| `PackagingType` | `asendiaService.format` | `B` (boxable), `N` (non-boxable), `L` (large), `XL` (extra-large) | +| `InsuranceOption` | insurance option | `EL45`, `EL150`, `EL500` | + +## Returns + +- **Domestic return product**: `EPAQRETDOM` — used when shipper and recipient are in the same country +- **International return product**: `EPAQRETINT` — used when countries differ +- **Payment options**: `RETPP` (prepaid), `RETPAP` (partially prepaid) + +The connector auto-selects the correct default based on `shipper.country_code == recipient.country_code`. + +## Directory Contents + +``` +vendor/ +├── README.md # This file +├── openapi.json # OpenAPI 3.0.1 spec (machine-readable) +└── openapi.yaml # OpenAPI 3.0.1 spec (human-readable) +``` + +## Refreshing the Spec + +```bash +cd karrio/modules/connectors/asendia +curl -sL https://www.asendia-sync.com/v3/api-docs -o vendor/openapi.json +python3 -c "import json,yaml; yaml.safe_dump(json.load(open('vendor/openapi.json')), open('vendor/openapi.yaml','w'), sort_keys=False)" +``` diff --git a/modules/connectors/asendia/vendor/openapi.json b/modules/connectors/asendia/vendor/openapi.json new file mode 100644 index 0000000000..5556b276b9 --- /dev/null +++ b/modules/connectors/asendia/vendor/openapi.json @@ -0,0 +1 @@ +{"openapi":"3.0.1","info":{"title":"Asendia Sync API","description":"API documentation","termsOfService":"https://www.asendia.de/allgemeine-geschaeftsbedingungen","contact":{"name":"","url":"","email":""},"license":{"name":"","url":""},"version":"1.0.0"},"servers":[{"url":"https://www.asendia-sync.com","description":"Generated server url"}],"tags":[{"name":"user-jwt-controller","description":"the user-jwt-controller API"},{"name":"pudo","description":"the pudo API"},{"name":"invoice","description":"Everything about your invoices"},{"name":"parcels","description":"Everything about your parcels"},{"name":"customers","description":"Everything about your customers"},{"name":"documents","description":"Everything about your documents"},{"name":"manifests","description":"Everything about your manifests"}],"paths":{"/api/parcels/{parcelId}":{"get":{"tags":["parcels"],"summary":"Returns parcel details","description":"Returns parcel details","operationId":"getParcelDetails","parameters":[{"name":"parcelId","in":"path","description":"ID of parcel","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParcelResponse"}}}},"400":{"description":"Invalid IDs supplied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParcelResponse"}}}},"404":{"description":"Parcel not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParcelResponse"}}}}}},"put":{"tags":["parcels"],"summary":"Update a parcel","description":"Update a parcel","operationId":"updateParcel","parameters":[{"name":"parcelId","in":"path","description":"ID of parcel","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParcelRequest"}}},"required":true},"responses":{"202":{"description":"Successfully accepted the request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParcelResponse"}}}}}},"delete":{"tags":["parcels"],"summary":"Delete a parcel","operationId":"deleteParcel","parameters":[{"name":"parcelId","in":"path","description":"ID of parcel","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Parcel deletion successful, no content returned."},"400":{"description":"Invalid ID supplied"},"404":{"description":"Parcel not found"}}}},"/api/manifests/{manifestId}":{"get":{"tags":["manifests"],"summary":"Get manifest by ID","description":"Get manifest by ID","operationId":"getManifestDetail","parameters":[{"name":"manifestId","in":"path","description":"ID of manifest","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"successful operation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManifestResponse"}}}},"400":{"description":"Invalid ID supplied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManifestResponse"}}}},"404":{"description":"Manifest not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManifestResponse"}}}}}},"put":{"tags":["manifests"],"summary":"Recreate the manifest","description":"Recreate the manifest","operationId":"recreateManifest","parameters":[{"name":"manifestId","in":"path","description":"ID of manifest","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Successfully accepted request to recreate the manifest","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManifestResponse"}}}},"400":{"description":"Invalid ID supplied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManifestResponse"}}}},"404":{"description":"Manifest not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManifestResponse"}}}}}}},"/api/pudo/points-by-country/{countryCode}":{"post":{"tags":["pudo"],"summary":"Get PUDO points by country","operationId":"pudoPointsByCountryCountryCodePost","parameters":[{"name":"countryCode","in":"path","description":"2-letter ISO country code","required":true,"schema":{"maxLength":2,"minLength":2,"type":"string"}},{"name":"pageSize","in":"query","description":"Number of results per page","required":false,"schema":{"minimum":1,"type":"integer","format":"int32","default":5}},{"name":"pageNumber","in":"query","description":"Page number to retrieve","required":false,"schema":{"minimum":1,"type":"integer","format":"int32","default":1}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PudoPointsByCountryCountryCodePostRequest"}}},"required":true},"responses":{"200":{"description":"Successful response with list of PUDO points","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PudoPointsResponse"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PudoPointsResponse"}}}},"404":{"description":"Country not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PudoPointsResponse"}}}}}}},"/api/pudo/points-by-address":{"post":{"tags":["pudo"],"summary":"Get PUDO points by address","operationId":"pudoPointsByAddressPost","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PudoPointsByAddressPostRequest"}}},"required":true},"responses":{"200":{"description":"Successful response with list of PUDO points","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PudoPointsResponse"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PudoPointsResponse"}}}},"404":{"description":"No PUDO points found for the given address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PudoPointsResponse"}}}}}}},"/api/parcels":{"post":{"tags":["parcels"],"summary":"Create a new parcel","description":"Create a new parcel","operationId":"addParcel","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParcelRequest"}}},"required":true},"responses":{"202":{"description":"Successfully accepted the request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParcelResponse"}}}}}}},"/api/manifests":{"post":{"tags":["manifests"],"summary":"Create a manifest","description":"Create a manifest","operationId":"createManifest","requestBody":{"content":{"application/json":{"schema":{"type":"array","items":{"type":"string","format":"uuid"}}}},"required":true},"responses":{"202":{"description":"Successfully accepted request to create a manifest","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManifestResponse"}}}}}}},"/api/customs-documents":{"post":{"tags":["documents"],"summary":"Upload customs document for a customer","description":"Uploads a customs document for a customer identified by CRMID.","operationId":"uploadCustomsDocument","parameters":[{"name":"crmId","in":"query","description":"CRMID of the customer","required":false,"schema":{"type":"string"}},{"name":"filename","in":"query","description":"Short display name for the file","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","description":"The PDF file","format":"binary"}}}}}},"responses":{"201":{"description":"Successfully uploaded"},"400":{"description":"Invalid request"},"403":{"description":"Forbidden"},"404":{"description":"Customer not found"}}}},"/api/customers/{customerId}/tracking":{"post":{"tags":["customers","tracking"],"summary":"Get tracking information for multiple tracking numbers","description":"Retrieve tracking information for multiple tracking numbers associated with a customer.","operationId":"getCustomerMultipleTrackingInformation","parameters":[{"name":"customerId","in":"path","description":"ID of the customer","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"array","description":"List of tracking numbers for which to retrieve information","items":{"type":"string"}}}},"required":true},"responses":{"200":{"description":"Successful operation","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TrackingResponse"}}}}},"400":{"description":"Invalid ID supplied","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TrackingResponse"}}}}},"404":{"description":"One or more tracking numbers not found","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TrackingResponse"}}}}}}}},"/api/commercial-invoice":{"post":{"tags":["invoice"],"summary":"Create a commercial invoice","description":"Create a commercial invoice based on the provided InvoiceRest object","operationId":"createCommercialInvoice","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommercialInvoiceRequest"}}},"required":true},"responses":{"200":{"description":"successful","content":{"application/pdf":{"schema":{"type":"string","format":"byte"}}}}}}},"/api/authenticate":{"post":{"tags":["user-jwt-controller"],"operationId":"authorize","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginVM"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JWTToken"}}}}}}},"/api/parcels/{parcelId}/return-label":{"get":{"tags":["documents","parcels"],"summary":"Get a parcel return label by ID","description":"Returns a parcel return label","operationId":"getParcelReturnLabel","parameters":[{"name":"parcelId","in":"path","description":"ID of parcel","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"successful","content":{"application/pdf":{"schema":{"type":"string","format":"byte"}},"image/*":{"schema":{"type":"string","format":"byte"}},"text/plain":{"schema":{"type":"string","format":"byte"}}}}}}},"/api/parcels/{parcelId}/label":{"get":{"tags":["documents","parcels"],"summary":"Get a parcel label by ID","description":"Returns a parcel label","operationId":"getParcelLabel","parameters":[{"name":"parcelId","in":"path","description":"ID of parcel","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"successful","content":{"application/pdf":{"schema":{"type":"string","format":"byte"}},"image/*":{"schema":{"type":"string","format":"byte"}},"text/plain":{"schema":{"type":"string","format":"byte"}}}}}}},"/api/parcels/{parcelId}/customs-document":{"get":{"tags":["documents","parcels"],"summary":"Get a parcel customs document by ID","description":"Returns a parcel customs document","operationId":"getParcelCustomsDocument","parameters":[{"name":"parcelId","in":"path","description":"ID of parcel","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"successful","content":{"application/pdf":{"schema":{"type":"string","format":"byte"}}}}}}},"/api/parcels/{parcelId}/commercial-invoice-document":{"get":{"tags":["documents","parcels"],"summary":"Get a parcel commercial invoice document by ID","description":"Returns a parcel commercial invoice document","operationId":"getParcelCommercialInvoiceDocument","parameters":[{"name":"parcelId","in":"path","description":"ID of parcel","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"successful","content":{"application/pdf":{"schema":{"type":"string","format":"byte"}}}}}}},"/api/manifests/{manifestId}/parcels":{"get":{"tags":["manifests"],"summary":"Returns list of parcels","description":"Returns list of parcels","operationId":"getManifestParcels","parameters":[{"name":"manifestId","in":"path","description":"ID of manifest","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"pageable","in":"query","required":true,"schema":{"$ref":"#/components/schemas/ApiPageable"}}],"responses":{"200":{"description":"successful operation","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ParcelResponse"}}}}},"400":{"description":"Invalid IDs supplied","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ParcelResponse"}}}}},"404":{"description":"Manifest not found","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ParcelResponse"}}}}}}}},"/api/manifests/{manifestId}/document":{"get":{"tags":["manifests"],"summary":"Get a manifest document by ID","description":"Get a manifest document by ID","operationId":"getManifestDocument","parameters":[{"name":"manifestId","in":"path","description":"ID of manifest","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"successful","content":{"application/pdf":{"schema":{"type":"string","format":"byte"}}}},"400":{"description":"Invalid ID supplied","content":{"application/pdf":{"schema":{"type":"string","format":"byte"}}}},"404":{"description":"Manifest not found","content":{"application/pdf":{"schema":{"type":"string","format":"byte"}}}}}}},"/api/customers":{"get":{"tags":["customers"],"operationId":"getAllCustomers","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Customer"}}}}}}}},"/api/customers/{customerId}/tracking/{trackingNumber}":{"get":{"tags":["customers","tracking"],"summary":"Get tracking information by tracking number","description":"Get tracking information by tracking number","operationId":"getCustomerTrackingInformation","parameters":[{"name":"customerId","in":"path","description":"ID of customer","required":true,"schema":{"type":"string"}},{"name":"trackingNumber","in":"path","description":"Tracking number","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"successful operation","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TrackingEvent"}}}}},"400":{"description":"Invalid ID supplied","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TrackingEvent"}}}}},"404":{"description":"Tracking not found","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TrackingEvent"}}}}}}}},"/api/customers/{customerId}/parcels":{"get":{"tags":["customers"],"summary":"Returns a list of parcels","description":"Returns a list of parcels for a customer","operationId":"getCustomerParcels","parameters":[{"name":"customerId","in":"path","description":"ID of customer","required":true,"schema":{"type":"string"}},{"name":"showManifested","in":"query","required":false,"schema":{"type":"boolean","default":false}},{"name":"pageable","in":"query","required":true,"schema":{"$ref":"#/components/schemas/ApiPageable"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ParcelResponse"}}}}}}}},"/api/customers/{customerId}/manifests":{"get":{"tags":["customers"],"summary":"Returns a list of manifests","description":"Returns a list of manifest for a customer","operationId":"getCustomerManifests","parameters":[{"name":"customerId","in":"path","description":"ID of customer","required":true,"schema":{"type":"string"}},{"name":"pageable","in":"query","required":true,"schema":{"$ref":"#/components/schemas/ApiPageable"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ManifestResponse"}}}}}}}}},"components":{"schemas":{"ParcelError":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}}},"ParcelResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"trackingNumber":{"type":"string"},"returnTrackingNumber":{"type":"string"},"errorMessages":{"type":"array","items":{"$ref":"#/components/schemas/ParcelError"}},"errorMessage":{"type":"string","deprecated":true},"labelLocation":{"type":"string","example":"/parcels/4711/label"},"returnLabelLocation":{"type":"string","example":"/parcels/4711/return-label"},"customsDocumentLocation":{"type":"string","example":"/parcels/4711/customs-document"},"manifestLocation":{"type":"string","example":"/parcels/4711/manifest"},"commercialInvoiceLocation":{"type":"string","example":"/parcels/4711/commercial-invoice-document"}}},"Address":{"required":["address1","city","country","name"],"type":"object","properties":{"name":{"maxLength":50,"minLength":0,"type":"string","description":"Name and Surname","example":"Peter Baer"},"company":{"maxLength":50,"minLength":0,"type":"string","description":"Company name","example":"ACME GmbH"},"address1":{"maxLength":50,"minLength":0,"type":"string","description":"Street address including house number","example":"High Street 1"},"address2":{"maxLength":50,"minLength":0,"type":"string","description":"Optional: address line 2","example":"Apt. B1"},"address3":{"maxLength":50,"minLength":0,"type":"string","description":"Optional: address line 3","example":"2nd Floor"},"postalCode":{"maxLength":20,"minLength":0,"type":"string","description":"Enter the postal code (zipcode) in the format of the destination country","example":"3030"},"city":{"maxLength":30,"minLength":0,"type":"string","description":"Name of city","example":"Bern"},"province":{"maxLength":50,"minLength":0,"type":"string","description":"State code or name of province"},"country":{"maxLength":2,"minLength":0,"type":"string","description":"Country code of shipping country in ISO 3166-1 alpha-2 Code format","example":"CH"},"email":{"maxLength":70,"minLength":0,"type":"string","description":"Contact email address","example":"pbaer@acme.ch"},"mobile":{"maxLength":25,"minLength":0,"type":"string","description":"Contact mobile number","example":"+41123456789"},"phone":{"maxLength":25,"minLength":0,"type":"string","description":"Contact phone number","example":"+41123456789"}}},"Addresses":{"required":["receiver"],"type":"object","properties":{"sender":{"$ref":"#/components/schemas/Address"},"receiver":{"$ref":"#/components/schemas/Address"},"importer":{"$ref":"#/components/schemas/Address"},"seller":{"$ref":"#/components/schemas/Address"},"pudoAddress":{"$ref":"#/components/schemas/PudoAddress"}}},"AsendiaService":{"required":["format","product","service"],"type":"object","properties":{"format":{"maxLength":3,"minLength":0,"type":"string","description":"

Parcel format code. The available formats depend on the product/service/country combination.

Allowed values:

  • B - Boxable
  • N - Non boxable
  • L - Large
  • XL - Extra Large

Note: For e-PAQ Select (EPAQSCT) and e-PAQ Elite (EPAQELT), format may be omitted unless instructed otherwise.

","example":"B"},"product":{"maxLength":50,"minLength":0,"type":"string","description":"

Product code identifying the shipping product.

Allowed values:

  • EPAQSTD - e-PAQ Standard
  • EPAQPLS - e-PAQ Plus
  • EPAQSCT - e-PAQ Select
  • EPAQELT - e-PAQ Elite
  • EPAQGO - e-PAQ GO

Return-specific products (used only with return label functionality):

  • EPAQRETDOM - e-PAQ Domestic Returns
  • EPAQRETINT - e-PAQ International Returns
  • RETDOM - Domestic Returns (deprecated)
  • RETINT - International Returns (deprecated)
","example":"EPAQSTD"},"service":{"maxLength":50,"minLength":0,"type":"string","description":"

Service code defining the service level for the shipment.

Allowed values:

  • CUP - Customs unpaid / Customs paid at destination
  • CPPR - Customs prepaid by retailer
  • CPPS - Customs prepaid by shopper

Return-specific services (used only with return shipments):

  • RETPP - Prepaid return
  • RETPAP - Partially paid return
","example":"CUP"},"options":{"type":"array","description":"

List of additional option codes for the shipment. Multiple options can be combined.

Allowed values:

  • PM - Printed Matter
  • ECO - Economy
  • POX - Post office box
  • PD - Personal Delivery
  • SIG - Signature
  • PUDO - Pick-up / Drop-off
  • MBX - Mailbox Plus
  • DG - Dangerous Goods
  • SAT - Saturday Delivery
  • DA - Direct Access
  • SGF - Smartgate Flex
  • SGD - Smartgate Direct
  • OPT1 - Option1
  • OPT2 - Option2
","items":{"type":"string","description":"

List of additional option codes for the shipment. Multiple options can be combined.

Allowed values:

  • PM - Printed Matter
  • ECO - Economy
  • POX - Post office box
  • PD - Personal Delivery
  • SIG - Signature
  • PUDO - Pick-up / Drop-off
  • MBX - Mailbox Plus
  • DG - Dangerous Goods
  • SAT - Saturday Delivery
  • DA - Direct Access
  • SGF - Smartgate Flex
  • SGD - Smartgate Direct
  • OPT1 - Option1
  • OPT2 - Option2
"}},"insurance":{"maxLength":255,"minLength":0,"type":"string","description":"

Insurance code specifying the coverage level for the shipment.

Allowed values:

  • EL45 - Enhanced liability up to 45 EUR
  • EL150 - Enhanced liability up to 150 EUR
  • EL500 - Enhanced liability up to 500 EUR

Note: For product EPAQPLS (e-PAQ Plus), only EL45 and EL150 are available. For all other products, EL45, EL150, and EL500 are available.

","example":"EL150"},"returnLabelOption":{"$ref":"#/components/schemas/ReturnLabelOption"}}},"CustomsInfo":{"type":"object","properties":{"currency":{"maxLength":3,"minLength":0,"type":"string","description":"The currency (ISO 3 characters) of Value","example":"EUR"},"items":{"type":"array","items":{"$ref":"#/components/schemas/CustomsInfoItem"}}}},"CustomsInfoItem":{"type":"object","properties":{"articleDescription":{"maxLength":150,"minLength":0,"type":"string","description":"Description of article. If alcohol, please add liters and the percentage of alcohol in the description.","example":"Blue Watch"},"articleUrl":{"maxLength":50,"minLength":0,"type":"string","description":"Additional description of article.","example":"Blue Watch"},"articleNumber":{"type":"string","description":"Article number","example":"ACBLWATCH001"},"articleComposition":{"maxLength":200,"minLength":0,"type":"string","description":"Article composition","example":"50% cotton 50% polyester"},"unitValue":{"type":"number","description":"Value of article to be declared to customs","example":1000},"currency":{"maxLength":3,"minLength":0,"type":"string","description":"The currency (ISO 3 characters) of Value","example":"EUR"},"harmonizationCode":{"maxLength":20,"minLength":0,"type":"string","description":"Harmonised European Tarif code of article used to declare to customs","example":"910310"},"originCountry":{"maxLength":2,"minLength":0,"type":"string","description":"Country of Origin of article used to declare to customs (ISO-2)","example":"CN"},"unitWeight":{"type":"number","description":"Weight of article used to declare customs","example":1},"quantity":{"type":"number","description":"Quantity of items being shipped","example":1},"volatileOrganicCompoundInGrams":{"minimum":0,"type":"integer","description":"Weight in grams of Volatile Organic Components (VOC) in the item. Optional field.","format":"int32","example":150},"destinationTariffCode":{"maxLength":20,"minLength":0,"type":"string","description":"Destination tariff code used for import clearance purposes. VAT codes for VAT exemptions or reduced VAT can be filled here.","example":"6VTF"}}},"ParcelRequest":{"required":["addresses","asendiaService","customsInfo","labelType","referencenumber","weight"],"type":"object","properties":{"customerId":{"maxLength":255,"minLength":0,"type":"string","description":"The customer ID is a unique identifier assigned to each customer. The first 2 characters of the customer ID are the country code of the customer.","example":"DE12345678"},"labelType":{"maxLength":10,"minLength":0,"type":"string","description":"Supported LabelType: Zebra, PDF, PNG","example":"PDF"},"labelRotation":{"type":"string","description":"Label orientation for labels. If not provided, the customer profile setting is used.","example":"PORTRAIT","enum":["PORTRAIT","LANDSCAPE_90","PORTRAIT_180","LANDSCAPE_270"]},"referencenumber":{"maxLength":50,"minLength":0,"type":"string","description":"Reference number","example":"1234"},"sequencenumber":{"maxLength":15,"minLength":0,"type":"string","description":"This field is only used by customers with their own tracking number range. If there is no own tracking number range, do not fill in. Must be unique.","example":"1234567"},"senderEORI":{"maxLength":19,"minLength":0,"type":"string","description":"Identifies the party dispatching or sending the goods. (EORI = Economic Operators Registration and Identification number)"},"sellerEORI":{"maxLength":19,"minLength":0,"type":"string","description":"Identifies the party that owns or sells the goods being dispatched or shipped. (EORI = Economic Operators Registration and Identification number)"},"euDDSReferenceNumber":{"maxLength":19,"minLength":0,"type":"string","description":"Identifies the party that owns or sells the goods being dispatched or shipped. (EORI = Economic Operators Registration and Identification number)"},"senderTaxId":{"type":"string","description":"Import One Stop Shop (IOSS) reference or VAT number of the sender in parcel destination country."},"receiverTaxId":{"type":"string","description":"Import One Stop Shop (IOSS) reference or VAT number of the receiver in parcel destination country."},"weight":{"type":"number","description":"Total weight (in kg) of the orderline/position including pro-rata packaging. Maximum weight depends on product/service/country combination.","example":1},"shippingCost":{"type":"number","description":"Total shipping cost"},"asendiaService":{"$ref":"#/components/schemas/AsendiaService"},"addresses":{"$ref":"#/components/schemas/Addresses"},"customsInfo":{"$ref":"#/components/schemas/CustomsInfo"}}},"PudoAddress":{"type":"object","properties":{"address1":{"type":"string","description":"First line of the street address","example":"123 Main St"},"address2":{"type":"string","description":"Second line of the street address (apartment, suite, etc.)","example":"Apt 4B"},"address3":{"type":"string","description":"Third line of the address (optional)","example":"Building C"},"city":{"type":"string","description":"City name","example":"New York"},"state":{"type":"string","description":"State, province, or region","example":"NY"},"zipCode":{"type":"string","description":"Postal or ZIP code","example":"10001"},"countryCode":{"type":"string","description":"Two-letter ISO country code","example":"US"},"pudoId":{"maxLength":20,"minLength":0,"type":"string","description":"Unique identifier for the PUDO (Pick-Up Drop-Off) location","example":"PUDO12345"}}},"ReturnLabelOption":{"required":["enabled"],"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether a return label should be generated for this shipment."},"type":{"type":"string","description":"

Return label product type defining the return shipping product.

Allowed values:

  • EPAQRETDOM - e-PAQ Domestic Returns
  • EPAQRETINT - e-PAQ International Returns
  • RETDOM - Domestic Returns (deprecated)
  • RETINT - International Returns (deprecated)
","example":"EPAQRETDOM"},"payment":{"type":"string","description":"

Payment method for the return label, defining who bears the return shipping cost.

Allowed values:

  • RETPP - Prepaid return (seller/retailer pays the return shipping cost)
  • RETPAP - Partially paid return (receiver/buyer pays the return shipping cost)
","example":"RETPP"}},"description":"Configuration for generating a return label alongside the outbound shipment."},"ManifestResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"createdAt":{"type":"string","format":"date-time"},"errorMessage":{"type":"string"},"errorParcelIds":{"type":"array","items":{"type":"string","format":"uuid"}},"status":{"type":"string"},"manifestDocumentLocation":{"type":"string","example":"/manifests/4711/document"},"parcelsLocation":{"type":"string","example":"/manifests/4711/parcels"},"manifestLocation":{"type":"string","example":"/manifests/4711"}}},"PudoPointsResponse":{"type":"object","properties":{"requestedLocation":{"$ref":"#/components/schemas/PudoPointsResponseRequestedLocation"},"pudoPoints":{"type":"array","items":{"$ref":"#/components/schemas/PudoPointsResponsePudoPointsInner"}},"status":{"type":"string","example":"Success"},"code":{"type":"integer","format":"int32","example":200},"transactionKey":{"type":"string","example":"TKVKGKJQPKLD6GU93"},"additionalData":{"type":"object","additionalProperties":{"type":"object"}}}},"PudoPointsResponsePudoPointsInner":{"type":"object","properties":{"asendiaPudoPointId":{"type":"string"},"supplierId":{"type":"string"},"title":{"type":"string"},"pudoType":{"type":"string"},"mappedType":{"type":"string"},"pudoId":{"type":"string"},"addressDetail":{"$ref":"#/components/schemas/PudoPointsResponsePudoPointsInnerAddressDetail"},"workHours":{"$ref":"#/components/schemas/PudoPointsResponsePudoPointsInnerWorkHours"},"distance":{"type":"number"}}},"PudoPointsResponsePudoPointsInnerAddressDetail":{"type":"object","properties":{"countryCode":{"type":"string"},"state":{"type":"string"},"zipCode":{"type":"string"},"city":{"type":"string"},"address1":{"type":"string"},"address2":{"type":"string"},"address3":{"type":"string"},"suburb":{"type":"string"},"streetNumber":{"type":"string"},"latitude":{"type":"number"},"longitude":{"type":"number"},"radius":{"type":"integer","format":"int32"}}},"PudoPointsResponsePudoPointsInnerWorkHours":{"type":"object","properties":{"mo":{"type":"array","items":{"type":"string"}},"tu":{"type":"array","items":{"type":"string"}},"we":{"type":"array","items":{"type":"string"}},"th":{"type":"array","items":{"type":"string"}},"fr":{"type":"array","items":{"type":"string"}},"sa":{"type":"array","items":{"type":"string"}},"su":{"type":"array","items":{"type":"string"}}}},"PudoPointsResponseRequestedLocation":{"type":"object","properties":{"countryCode":{"type":"string"},"zipCode":{"type":"string"},"city":{"type":"string"},"address1":{"type":"string"},"address2":{"type":"string"},"address3":{"type":"string"},"streetNumber":{"type":"string"},"latitude":{"type":"number"},"longitude":{"type":"number"},"radius":{"type":"integer","format":"int32"}}},"PudoPointsByCountryCountryCodePostRequest":{"type":"object","properties":{"servicesList":{"type":"array","items":{"$ref":"#/components/schemas/PudoPointsByCountryCountryCodePostRequestServicesListInner"}}}},"PudoPointsByCountryCountryCodePostRequestServicesListInner":{"required":["supplierId"],"type":"object","properties":{"serviceCode":{"type":"string","description":"Optional service code","example":"SS0000944"},"supplierId":{"type":"string","description":"Supplier ID","example":"SUP00138"}}},"JsonNullableBigDecimal":{"type":"object","properties":{"present":{"type":"boolean"}},"description":"Longitude coordinates of the address","example":53.46305},"PudoPointsByAddressPostRequest":{"type":"object","properties":{"address":{"$ref":"#/components/schemas/PudoPointsByAddressPostRequestAddress"},"servicesList":{"type":"array","items":{"$ref":"#/components/schemas/PudoPointsByCountryCountryCodePostRequestServicesListInner"}}}},"PudoPointsByAddressPostRequestAddress":{"required":["city","countryCode","radius","street","streetNumber","zipCode"],"type":"object","properties":{"countryCode":{"type":"string","example":"FR"},"state":{"type":"string","example":"Paris"},"zipCode":{"type":"string","example":"75007"},"city":{"type":"string","example":"Paris"},"street":{"type":"string","example":"Av. de la Bourdonnais"},"address2":{"type":"string"},"address3":{"type":"string"},"suburb":{"type":"string"},"streetNumber":{"type":"string","example":"14"},"latitude":{"$ref":"#/components/schemas/JsonNullableBigDecimal"},"longitude":{"$ref":"#/components/schemas/JsonNullableBigDecimal"},"radius":{"type":"integer","format":"int32","example":5}}},"TrackingEvent":{"type":"object","properties":{"id":{"type":"integer","format":"int32"},"code":{"type":"string"},"time":{"type":"string","format":"date-time"},"locationName":{"type":"string"},"carrierEventDescription":{"type":"string"},"locationCountry":{"type":"string"}}},"TrackingResponse":{"type":"object","properties":{"trackingNumber":{"type":"string","description":"Tracking number of the parcel"},"trackingEvents":{"type":"array","items":{"$ref":"#/components/schemas/TrackingEvent"}}}},"CommercialInvoiceAddress":{"required":["address1","city","country","name"],"type":"object","properties":{"name":{"maxLength":50,"minLength":0,"type":"string","description":"Name and Surname","example":"Peter Baer"},"address1":{"maxLength":50,"minLength":0,"type":"string","description":"Street address including house number","example":"High Street 1"},"address2":{"maxLength":50,"minLength":0,"type":"string","description":"Optional: address line 2","example":"Apt. B1"},"address3":{"maxLength":50,"minLength":0,"type":"string","description":"Optional: address line 3","example":"2nd Floor"},"postalCode":{"maxLength":20,"minLength":0,"type":"string","description":"Enter the postal code (zipcode) in the format of the destination country","example":"3030"},"city":{"maxLength":30,"minLength":0,"type":"string","description":"Name of city","example":"Bern"},"province":{"maxLength":255,"minLength":0,"type":"string","description":"State code or name of province"},"country":{"maxLength":2,"minLength":0,"type":"string","description":"Country code of shipping country in ISO 3166-1 alpha-2 Code format","example":"CH"},"email":{"maxLength":70,"minLength":0,"type":"string","description":"Contact email address","example":"pbaer@acme.ch"},"phone":{"maxLength":25,"minLength":0,"type":"string","description":"Contact phone number","example":"+41123456789"}}},"CommercialInvoiceBulkDetails":{"required":["importer","receiver","seller","sender"],"type":"object","properties":{"senderTaxId":{"type":"string","description":"Import One Stop Shop (IOSS) reference or VAT number of the sender in parcel destination country."},"zazAccountNumber":{"type":"string","description":"ZAZ account number"},"uidChTaxNumber":{"type":"string","description":"UID CH Tax number"},"sender":{"$ref":"#/components/schemas/CommercialInvoiceAddress"},"receiver":{"$ref":"#/components/schemas/CommercialInvoiceAddress"},"seller":{"$ref":"#/components/schemas/CommercialInvoiceAddress"},"importer":{"$ref":"#/components/schemas/CommercialInvoiceAddress"}}},"CommercialInvoiceRequest":{"required":["incoterm","invoiceNumber","parcelIds","reasonOfExport","sellerEORI"],"type":"object","properties":{"proformaInvoice":{"type":"boolean","description":"If true, the invoice will be created as a proforma invoice. If false, the invoice will be created as a commercial invoice."},"invoiceNumber":{"maxLength":50,"minLength":0,"type":"string","description":"Invoice number has to be unique","example":"A-1234"},"sellerEORI":{"maxLength":19,"minLength":0,"type":"string","description":"Identifies the party that owns or sells the goods being dispatched or shipped. (EORI = Economic Operators Registration and Identification number)","example":"CH12345678901234567"},"poNumber":{"maxLength":50,"minLength":0,"type":"string","example":"PO-1234"},"incoterm":{"type":"string","enum":["DAP","DDP","DTP"]},"reasonOfExport":{"type":"string","enum":["DOCS","SALE_OF_GOODS_PERMANENT","RETURNED_GOODS","SAMPLES","GIFT_PERMANENT","OTHERS"]},"discountRebate":{"type":"number","description":"Discount or rebate on the invoice. Values are recognized as amounts, not as percentages.","format":"double"},"freightPostage":{"type":"number","description":"Freight or postage on the invoice. Values are recognized as amounts, not as percentages.","format":"double"},"additionalComments":{"type":"string","description":"Additional comments on the invoice"},"declarationStatement":{"type":"string","description":"Declaration statement on the invoice"},"vat":{"type":"number","description":"Value added tax on the invoice. If incoterm is DAP, field is not needed. Values are recognized as amounts, not as percentages.","format":"double"},"tobaccoTax":{"type":"number","description":"Tobacco tax on the invoice. If incoterm is DAP, field is not needed. Values are recognized as amounts, not as percentages.","format":"double"},"duty":{"type":"number","description":"Duty on the invoice. If incoterm is DAP, field is not needed. Values are recognized as amounts, not as percentages.","format":"double"},"bulk":{"type":"boolean","description":"If true, the invoice will be created for multiple parcels and bulk addresses will be used for the invoice. If false, the invoice will be created and grouped by receiver address.","example":false},"bulkDetails":{"$ref":"#/components/schemas/CommercialInvoiceBulkDetails"},"parcelIds":{"type":"array","description":"List of parcel ids. The parcels have to be manifested before creating an invoice.","items":{"type":"string","description":"List of parcel ids. The parcels have to be manifested before creating an invoice.","format":"uuid"}}}},"JWTToken":{"type":"object","properties":{"id_token":{"type":"string"}}},"LoginVM":{"required":["password","username"],"type":"object","properties":{"username":{"maxLength":50,"minLength":1,"type":"string"},"password":{"maxLength":100,"minLength":4,"type":"string"}}},"ApiPageable":{"type":"object","properties":{"page":{"type":"integer","format":"int32"},"size":{"maximum":500,"minimum":1,"type":"integer","format":"int32","example":10}},"description":"Pageable object"},"Customer":{"type":"object","properties":{"id":{"maxLength":255,"minLength":0,"type":"string","example":"DE12345678"},"name":{"maxLength":255,"minLength":0,"type":"string","example":"ACME GmbH"}}}}}} \ No newline at end of file diff --git a/modules/connectors/asendia/vendor/openapi.yaml b/modules/connectors/asendia/vendor/openapi.yaml new file mode 100644 index 0000000000..90396217ba --- /dev/null +++ b/modules/connectors/asendia/vendor/openapi.yaml @@ -0,0 +1,1705 @@ +openapi: 3.0.1 +info: + title: Asendia Sync API + description: API documentation + termsOfService: https://www.asendia.de/allgemeine-geschaeftsbedingungen + contact: + name: '' + url: '' + email: '' + license: + name: '' + url: '' + version: 1.0.0 +servers: +- url: https://www.asendia-sync.com + description: Generated server url +tags: +- name: user-jwt-controller + description: the user-jwt-controller API +- name: pudo + description: the pudo API +- name: invoice + description: Everything about your invoices +- name: parcels + description: Everything about your parcels +- name: customers + description: Everything about your customers +- name: documents + description: Everything about your documents +- name: manifests + description: Everything about your manifests +paths: + /api/parcels/{parcelId}: + get: + tags: + - parcels + summary: Returns parcel details + description: Returns parcel details + operationId: getParcelDetails + parameters: + - name: parcelId + in: path + description: ID of parcel + required: true + schema: + type: string + format: uuid + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ParcelResponse' + '400': + description: Invalid IDs supplied + content: + application/json: + schema: + $ref: '#/components/schemas/ParcelResponse' + '404': + description: Parcel not found + content: + application/json: + schema: + $ref: '#/components/schemas/ParcelResponse' + put: + tags: + - parcels + summary: Update a parcel + description: Update a parcel + operationId: updateParcel + parameters: + - name: parcelId + in: path + description: ID of parcel + required: true + schema: + type: string + format: uuid + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ParcelRequest' + required: true + responses: + '202': + description: Successfully accepted the request + content: + application/json: + schema: + $ref: '#/components/schemas/ParcelResponse' + delete: + tags: + - parcels + summary: Delete a parcel + operationId: deleteParcel + parameters: + - name: parcelId + in: path + description: ID of parcel + required: true + schema: + type: string + format: uuid + responses: + '204': + description: Parcel deletion successful, no content returned. + '400': + description: Invalid ID supplied + '404': + description: Parcel not found + /api/manifests/{manifestId}: + get: + tags: + - manifests + summary: Get manifest by ID + description: Get manifest by ID + operationId: getManifestDetail + parameters: + - name: manifestId + in: path + description: ID of manifest + required: true + schema: + type: string + format: uuid + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ManifestResponse' + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: '#/components/schemas/ManifestResponse' + '404': + description: Manifest not found + content: + application/json: + schema: + $ref: '#/components/schemas/ManifestResponse' + put: + tags: + - manifests + summary: Recreate the manifest + description: Recreate the manifest + operationId: recreateManifest + parameters: + - name: manifestId + in: path + description: ID of manifest + required: true + schema: + type: string + format: uuid + responses: + '202': + description: Successfully accepted request to recreate the manifest + content: + application/json: + schema: + $ref: '#/components/schemas/ManifestResponse' + '400': + description: Invalid ID supplied + content: + application/json: + schema: + $ref: '#/components/schemas/ManifestResponse' + '404': + description: Manifest not found + content: + application/json: + schema: + $ref: '#/components/schemas/ManifestResponse' + /api/pudo/points-by-country/{countryCode}: + post: + tags: + - pudo + summary: Get PUDO points by country + operationId: pudoPointsByCountryCountryCodePost + parameters: + - name: countryCode + in: path + description: 2-letter ISO country code + required: true + schema: + maxLength: 2 + minLength: 2 + type: string + - name: pageSize + in: query + description: Number of results per page + required: false + schema: + minimum: 1 + type: integer + format: int32 + default: 5 + - name: pageNumber + in: query + description: Page number to retrieve + required: false + schema: + minimum: 1 + type: integer + format: int32 + default: 1 + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PudoPointsByCountryCountryCodePostRequest' + required: true + responses: + '200': + description: Successful response with list of PUDO points + content: + application/json: + schema: + $ref: '#/components/schemas/PudoPointsResponse' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/PudoPointsResponse' + '404': + description: Country not found + content: + application/json: + schema: + $ref: '#/components/schemas/PudoPointsResponse' + /api/pudo/points-by-address: + post: + tags: + - pudo + summary: Get PUDO points by address + operationId: pudoPointsByAddressPost + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PudoPointsByAddressPostRequest' + required: true + responses: + '200': + description: Successful response with list of PUDO points + content: + application/json: + schema: + $ref: '#/components/schemas/PudoPointsResponse' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/PudoPointsResponse' + '404': + description: No PUDO points found for the given address + content: + application/json: + schema: + $ref: '#/components/schemas/PudoPointsResponse' + /api/parcels: + post: + tags: + - parcels + summary: Create a new parcel + description: Create a new parcel + operationId: addParcel + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ParcelRequest' + required: true + responses: + '202': + description: Successfully accepted the request + content: + application/json: + schema: + $ref: '#/components/schemas/ParcelResponse' + /api/manifests: + post: + tags: + - manifests + summary: Create a manifest + description: Create a manifest + operationId: createManifest + requestBody: + content: + application/json: + schema: + type: array + items: + type: string + format: uuid + required: true + responses: + '202': + description: Successfully accepted request to create a manifest + content: + application/json: + schema: + $ref: '#/components/schemas/ManifestResponse' + /api/customs-documents: + post: + tags: + - documents + summary: Upload customs document for a customer + description: Uploads a customs document for a customer identified by CRMID. + operationId: uploadCustomsDocument + parameters: + - name: crmId + in: query + description: CRMID of the customer + required: false + schema: + type: string + - name: filename + in: query + description: Short display name for the file + required: false + schema: + type: string + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + description: The PDF file + format: binary + responses: + '201': + description: Successfully uploaded + '400': + description: Invalid request + '403': + description: Forbidden + '404': + description: Customer not found + /api/customers/{customerId}/tracking: + post: + tags: + - customers + - tracking + summary: Get tracking information for multiple tracking numbers + description: Retrieve tracking information for multiple tracking numbers associated + with a customer. + operationId: getCustomerMultipleTrackingInformation + parameters: + - name: customerId + in: path + description: ID of the customer + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: array + description: List of tracking numbers for which to retrieve information + items: + type: string + required: true + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TrackingResponse' + '400': + description: Invalid ID supplied + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TrackingResponse' + '404': + description: One or more tracking numbers not found + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TrackingResponse' + /api/commercial-invoice: + post: + tags: + - invoice + summary: Create a commercial invoice + description: Create a commercial invoice based on the provided InvoiceRest object + operationId: createCommercialInvoice + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CommercialInvoiceRequest' + required: true + responses: + '200': + description: successful + content: + application/pdf: + schema: + type: string + format: byte + /api/authenticate: + post: + tags: + - user-jwt-controller + operationId: authorize + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LoginVM' + required: true + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/JWTToken' + /api/parcels/{parcelId}/return-label: + get: + tags: + - documents + - parcels + summary: Get a parcel return label by ID + description: Returns a parcel return label + operationId: getParcelReturnLabel + parameters: + - name: parcelId + in: path + description: ID of parcel + required: true + schema: + type: string + format: uuid + responses: + '200': + description: successful + content: + application/pdf: + schema: + type: string + format: byte + image/*: + schema: + type: string + format: byte + text/plain: + schema: + type: string + format: byte + /api/parcels/{parcelId}/label: + get: + tags: + - documents + - parcels + summary: Get a parcel label by ID + description: Returns a parcel label + operationId: getParcelLabel + parameters: + - name: parcelId + in: path + description: ID of parcel + required: true + schema: + type: string + format: uuid + responses: + '200': + description: successful + content: + application/pdf: + schema: + type: string + format: byte + image/*: + schema: + type: string + format: byte + text/plain: + schema: + type: string + format: byte + /api/parcels/{parcelId}/customs-document: + get: + tags: + - documents + - parcels + summary: Get a parcel customs document by ID + description: Returns a parcel customs document + operationId: getParcelCustomsDocument + parameters: + - name: parcelId + in: path + description: ID of parcel + required: true + schema: + type: string + format: uuid + responses: + '200': + description: successful + content: + application/pdf: + schema: + type: string + format: byte + /api/parcels/{parcelId}/commercial-invoice-document: + get: + tags: + - documents + - parcels + summary: Get a parcel commercial invoice document by ID + description: Returns a parcel commercial invoice document + operationId: getParcelCommercialInvoiceDocument + parameters: + - name: parcelId + in: path + description: ID of parcel + required: true + schema: + type: string + format: uuid + responses: + '200': + description: successful + content: + application/pdf: + schema: + type: string + format: byte + /api/manifests/{manifestId}/parcels: + get: + tags: + - manifests + summary: Returns list of parcels + description: Returns list of parcels + operationId: getManifestParcels + parameters: + - name: manifestId + in: path + description: ID of manifest + required: true + schema: + type: string + format: uuid + - name: pageable + in: query + required: true + schema: + $ref: '#/components/schemas/ApiPageable' + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ParcelResponse' + '400': + description: Invalid IDs supplied + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ParcelResponse' + '404': + description: Manifest not found + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ParcelResponse' + /api/manifests/{manifestId}/document: + get: + tags: + - manifests + summary: Get a manifest document by ID + description: Get a manifest document by ID + operationId: getManifestDocument + parameters: + - name: manifestId + in: path + description: ID of manifest + required: true + schema: + type: string + format: uuid + responses: + '200': + description: successful + content: + application/pdf: + schema: + type: string + format: byte + '400': + description: Invalid ID supplied + content: + application/pdf: + schema: + type: string + format: byte + '404': + description: Manifest not found + content: + application/pdf: + schema: + type: string + format: byte + /api/customers: + get: + tags: + - customers + operationId: getAllCustomers + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Customer' + /api/customers/{customerId}/tracking/{trackingNumber}: + get: + tags: + - customers + - tracking + summary: Get tracking information by tracking number + description: Get tracking information by tracking number + operationId: getCustomerTrackingInformation + parameters: + - name: customerId + in: path + description: ID of customer + required: true + schema: + type: string + - name: trackingNumber + in: path + description: Tracking number + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TrackingEvent' + '400': + description: Invalid ID supplied + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TrackingEvent' + '404': + description: Tracking not found + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TrackingEvent' + /api/customers/{customerId}/parcels: + get: + tags: + - customers + summary: Returns a list of parcels + description: Returns a list of parcels for a customer + operationId: getCustomerParcels + parameters: + - name: customerId + in: path + description: ID of customer + required: true + schema: + type: string + - name: showManifested + in: query + required: false + schema: + type: boolean + default: false + - name: pageable + in: query + required: true + schema: + $ref: '#/components/schemas/ApiPageable' + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ParcelResponse' + /api/customers/{customerId}/manifests: + get: + tags: + - customers + summary: Returns a list of manifests + description: Returns a list of manifest for a customer + operationId: getCustomerManifests + parameters: + - name: customerId + in: path + description: ID of customer + required: true + schema: + type: string + - name: pageable + in: query + required: true + schema: + $ref: '#/components/schemas/ApiPageable' + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ManifestResponse' +components: + schemas: + ParcelError: + type: object + properties: + field: + type: string + message: + type: string + ParcelResponse: + type: object + properties: + id: + type: string + format: uuid + trackingNumber: + type: string + returnTrackingNumber: + type: string + errorMessages: + type: array + items: + $ref: '#/components/schemas/ParcelError' + errorMessage: + type: string + deprecated: true + labelLocation: + type: string + example: /parcels/4711/label + returnLabelLocation: + type: string + example: /parcels/4711/return-label + customsDocumentLocation: + type: string + example: /parcels/4711/customs-document + manifestLocation: + type: string + example: /parcels/4711/manifest + commercialInvoiceLocation: + type: string + example: /parcels/4711/commercial-invoice-document + Address: + required: + - address1 + - city + - country + - name + type: object + properties: + name: + maxLength: 50 + minLength: 0 + type: string + description: Name and Surname + example: Peter Baer + company: + maxLength: 50 + minLength: 0 + type: string + description: Company name + example: ACME GmbH + address1: + maxLength: 50 + minLength: 0 + type: string + description: Street address including house number + example: High Street 1 + address2: + maxLength: 50 + minLength: 0 + type: string + description: 'Optional: address line 2' + example: Apt. B1 + address3: + maxLength: 50 + minLength: 0 + type: string + description: 'Optional: address line 3' + example: 2nd Floor + postalCode: + maxLength: 20 + minLength: 0 + type: string + description: Enter the postal code (zipcode) in the format of the destination + country + example: '3030' + city: + maxLength: 30 + minLength: 0 + type: string + description: Name of city + example: Bern + province: + maxLength: 50 + minLength: 0 + type: string + description: State code or name of province + country: + maxLength: 2 + minLength: 0 + type: string + description: Country code of shipping country in ISO 3166-1 alpha-2 Code + format + example: CH + email: + maxLength: 70 + minLength: 0 + type: string + description: Contact email address + example: pbaer@acme.ch + mobile: + maxLength: 25 + minLength: 0 + type: string + description: Contact mobile number + example: '+41123456789' + phone: + maxLength: 25 + minLength: 0 + type: string + description: Contact phone number + example: '+41123456789' + Addresses: + required: + - receiver + type: object + properties: + sender: + $ref: '#/components/schemas/Address' + receiver: + $ref: '#/components/schemas/Address' + importer: + $ref: '#/components/schemas/Address' + seller: + $ref: '#/components/schemas/Address' + pudoAddress: + $ref: '#/components/schemas/PudoAddress' + AsendiaService: + required: + - format + - product + - service + type: object + properties: + format: + maxLength: 3 + minLength: 0 + type: string + description:

Parcel format code. The available formats depend on the + product/service/country combination.

Allowed values:

+
  • B - Boxable
  • N - Non boxable
  • L + - Large
  • XL - Extra Large

Note: + For e-PAQ Select (EPAQSCT) and e-PAQ Elite (EPAQELT), + format may be omitted unless instructed otherwise.

+ example: B + product: + maxLength: 50 + minLength: 0 + type: string + description:

Product code identifying the shipping product.

Allowed + values:

  • EPAQSTD - e-PAQ Standard
  • EPAQPLS + - e-PAQ Plus
  • EPAQSCT - e-PAQ Select
  • EPAQELT + - e-PAQ Elite
  • EPAQGO - e-PAQ GO

Return-specific + products (used only with return label functionality):

  • EPAQRETDOM + - e-PAQ Domestic Returns
  • EPAQRETINT - e-PAQ International + Returns
  • RETDOM - Domestic Returns (deprecated)
  • RETINT + - International Returns (deprecated)
+ example: EPAQSTD + service: + maxLength: 50 + minLength: 0 + type: string + description:

Service code defining the service level for the shipment.

+

Allowed values:

  • CUP - + Customs unpaid / Customs paid at destination
  • CPPR + - Customs prepaid by retailer
  • CPPS - Customs prepaid + by shopper

Return-specific services (used only with + return shipments):

  • RETPP - Prepaid + return
  • RETPAP - Partially paid return
+ example: CUP + options: + type: array + description:

List of additional option codes for the shipment. Multiple + options can be combined.

Allowed values:

  • PM + - Printed Matter
  • ECO - Economy
  • POX + - Post office box
  • PD - Personal Delivery
  • SIG + - Signature
  • PUDO - Pick-up / Drop-off
  • MBX + - Mailbox Plus
  • DG - Dangerous Goods
  • SAT + - Saturday Delivery
  • DA - Direct Access
  • SGF + - Smartgate Flex
  • SGD - Smartgate Direct
  • OPT1 + - Option1
  • OPT2 - Option2
+ items: + type: string + description:

List of additional option codes for the shipment. Multiple + options can be combined.

Allowed values:

+
  • PM - Printed Matter
  • ECO + - Economy
  • POX - Post office box
  • PD + - Personal Delivery
  • SIG - Signature
  • PUDO + - Pick-up / Drop-off
  • MBX - Mailbox Plus
  • DG + - Dangerous Goods
  • SAT - Saturday Delivery
  • DA + - Direct Access
  • SGF - Smartgate Flex
  • SGD + - Smartgate Direct
  • OPT1 - Option1
  • OPT2 + - Option2
+ insurance: + maxLength: 255 + minLength: 0 + type: string + description:

Insurance code specifying the coverage level for the shipment.

+

Allowed values:

  • EL45 - + Enhanced liability up to 45 EUR
  • EL150 - Enhanced + liability up to 150 EUR
  • EL500 - Enhanced liability + up to 500 EUR

Note: For product EPAQPLS + (e-PAQ Plus), only EL45 and EL150 are available. + For all other products, EL45, EL150, and EL500 + are available.

+ example: EL150 + returnLabelOption: + $ref: '#/components/schemas/ReturnLabelOption' + CustomsInfo: + type: object + properties: + currency: + maxLength: 3 + minLength: 0 + type: string + description: The currency (ISO 3 characters) of Value + example: EUR + items: + type: array + items: + $ref: '#/components/schemas/CustomsInfoItem' + CustomsInfoItem: + type: object + properties: + articleDescription: + maxLength: 150 + minLength: 0 + type: string + description: Description of article. If alcohol, please add liters and the + percentage of alcohol in the description. + example: Blue Watch + articleUrl: + maxLength: 50 + minLength: 0 + type: string + description: Additional description of article. + example: Blue Watch + articleNumber: + type: string + description: Article number + example: ACBLWATCH001 + articleComposition: + maxLength: 200 + minLength: 0 + type: string + description: Article composition + example: 50% cotton 50% polyester + unitValue: + type: number + description: Value of article to be declared to customs + example: 1000 + currency: + maxLength: 3 + minLength: 0 + type: string + description: The currency (ISO 3 characters) of Value + example: EUR + harmonizationCode: + maxLength: 20 + minLength: 0 + type: string + description: Harmonised European Tarif code of article used to declare to + customs + example: '910310' + originCountry: + maxLength: 2 + minLength: 0 + type: string + description: Country of Origin of article used to declare to customs (ISO-2) + example: CN + unitWeight: + type: number + description: Weight of article used to declare customs + example: 1 + quantity: + type: number + description: Quantity of items being shipped + example: 1 + volatileOrganicCompoundInGrams: + minimum: 0 + type: integer + description: Weight in grams of Volatile Organic Components (VOC) in the + item. Optional field. + format: int32 + example: 150 + destinationTariffCode: + maxLength: 20 + minLength: 0 + type: string + description: Destination tariff code used for import clearance purposes. + VAT codes for VAT exemptions or reduced VAT can be filled here. + example: 6VTF + ParcelRequest: + required: + - addresses + - asendiaService + - customsInfo + - labelType + - referencenumber + - weight + type: object + properties: + customerId: + maxLength: 255 + minLength: 0 + type: string + description: The customer ID is a unique identifier assigned to each customer. + The first 2 characters of the customer ID are the country code of the + customer. + example: DE12345678 + labelType: + maxLength: 10 + minLength: 0 + type: string + description: 'Supported LabelType: Zebra, PDF, PNG' + example: PDF + labelRotation: + type: string + description: Label orientation for labels. If not provided, the customer + profile setting is used. + example: PORTRAIT + enum: + - PORTRAIT + - LANDSCAPE_90 + - PORTRAIT_180 + - LANDSCAPE_270 + referencenumber: + maxLength: 50 + minLength: 0 + type: string + description: Reference number + example: '1234' + sequencenumber: + maxLength: 15 + minLength: 0 + type: string + description: This field is only used by customers with their own tracking + number range. If there is no own tracking number range, do not fill in. + Must be unique. + example: '1234567' + senderEORI: + maxLength: 19 + minLength: 0 + type: string + description: Identifies the party dispatching or sending the goods. (EORI + = Economic Operators Registration and Identification number) + sellerEORI: + maxLength: 19 + minLength: 0 + type: string + description: Identifies the party that owns or sells the goods being dispatched + or shipped. (EORI = Economic Operators Registration and Identification + number) + euDDSReferenceNumber: + maxLength: 19 + minLength: 0 + type: string + description: Identifies the party that owns or sells the goods being dispatched + or shipped. (EORI = Economic Operators Registration and Identification + number) + senderTaxId: + type: string + description: Import One Stop Shop (IOSS) reference or VAT number of the + sender in parcel destination country. + receiverTaxId: + type: string + description: Import One Stop Shop (IOSS) reference or VAT number of the + receiver in parcel destination country. + weight: + type: number + description: Total weight (in kg) of the orderline/position including pro-rata + packaging. Maximum weight depends on product/service/country combination. + example: 1 + shippingCost: + type: number + description: Total shipping cost + asendiaService: + $ref: '#/components/schemas/AsendiaService' + addresses: + $ref: '#/components/schemas/Addresses' + customsInfo: + $ref: '#/components/schemas/CustomsInfo' + PudoAddress: + type: object + properties: + address1: + type: string + description: First line of the street address + example: 123 Main St + address2: + type: string + description: Second line of the street address (apartment, suite, etc.) + example: Apt 4B + address3: + type: string + description: Third line of the address (optional) + example: Building C + city: + type: string + description: City name + example: New York + state: + type: string + description: State, province, or region + example: NY + zipCode: + type: string + description: Postal or ZIP code + example: '10001' + countryCode: + type: string + description: Two-letter ISO country code + example: US + pudoId: + maxLength: 20 + minLength: 0 + type: string + description: Unique identifier for the PUDO (Pick-Up Drop-Off) location + example: PUDO12345 + ReturnLabelOption: + required: + - enabled + type: object + properties: + enabled: + type: boolean + description: Whether a return label should be generated for this shipment. + type: + type: string + description:

Return label product type defining the return shipping product.

+

Allowed values:

  • EPAQRETDOM + - e-PAQ Domestic Returns
  • EPAQRETINT - e-PAQ International + Returns
  • RETDOM - Domestic Returns (deprecated)
  • RETINT + - International Returns (deprecated)
+ example: EPAQRETDOM + payment: + type: string + description:

Payment method for the return label, defining who bears + the return shipping cost.

Allowed values:

+
  • RETPP - Prepaid return (seller/retailer pays the + return shipping cost)
  • RETPAP - Partially paid + return (receiver/buyer pays the return shipping cost)
+ example: RETPP + description: Configuration for generating a return label alongside the outbound + shipment. + ManifestResponse: + type: object + properties: + id: + type: string + format: uuid + createdAt: + type: string + format: date-time + errorMessage: + type: string + errorParcelIds: + type: array + items: + type: string + format: uuid + status: + type: string + manifestDocumentLocation: + type: string + example: /manifests/4711/document + parcelsLocation: + type: string + example: /manifests/4711/parcels + manifestLocation: + type: string + example: /manifests/4711 + PudoPointsResponse: + type: object + properties: + requestedLocation: + $ref: '#/components/schemas/PudoPointsResponseRequestedLocation' + pudoPoints: + type: array + items: + $ref: '#/components/schemas/PudoPointsResponsePudoPointsInner' + status: + type: string + example: Success + code: + type: integer + format: int32 + example: 200 + transactionKey: + type: string + example: TKVKGKJQPKLD6GU93 + additionalData: + type: object + additionalProperties: + type: object + PudoPointsResponsePudoPointsInner: + type: object + properties: + asendiaPudoPointId: + type: string + supplierId: + type: string + title: + type: string + pudoType: + type: string + mappedType: + type: string + pudoId: + type: string + addressDetail: + $ref: '#/components/schemas/PudoPointsResponsePudoPointsInnerAddressDetail' + workHours: + $ref: '#/components/schemas/PudoPointsResponsePudoPointsInnerWorkHours' + distance: + type: number + PudoPointsResponsePudoPointsInnerAddressDetail: + type: object + properties: + countryCode: + type: string + state: + type: string + zipCode: + type: string + city: + type: string + address1: + type: string + address2: + type: string + address3: + type: string + suburb: + type: string + streetNumber: + type: string + latitude: + type: number + longitude: + type: number + radius: + type: integer + format: int32 + PudoPointsResponsePudoPointsInnerWorkHours: + type: object + properties: + mo: + type: array + items: + type: string + tu: + type: array + items: + type: string + we: + type: array + items: + type: string + th: + type: array + items: + type: string + fr: + type: array + items: + type: string + sa: + type: array + items: + type: string + su: + type: array + items: + type: string + PudoPointsResponseRequestedLocation: + type: object + properties: + countryCode: + type: string + zipCode: + type: string + city: + type: string + address1: + type: string + address2: + type: string + address3: + type: string + streetNumber: + type: string + latitude: + type: number + longitude: + type: number + radius: + type: integer + format: int32 + PudoPointsByCountryCountryCodePostRequest: + type: object + properties: + servicesList: + type: array + items: + $ref: '#/components/schemas/PudoPointsByCountryCountryCodePostRequestServicesListInner' + PudoPointsByCountryCountryCodePostRequestServicesListInner: + required: + - supplierId + type: object + properties: + serviceCode: + type: string + description: Optional service code + example: SS0000944 + supplierId: + type: string + description: Supplier ID + example: SUP00138 + JsonNullableBigDecimal: + type: object + properties: + present: + type: boolean + description: Longitude coordinates of the address + example: 53.46305 + PudoPointsByAddressPostRequest: + type: object + properties: + address: + $ref: '#/components/schemas/PudoPointsByAddressPostRequestAddress' + servicesList: + type: array + items: + $ref: '#/components/schemas/PudoPointsByCountryCountryCodePostRequestServicesListInner' + PudoPointsByAddressPostRequestAddress: + required: + - city + - countryCode + - radius + - street + - streetNumber + - zipCode + type: object + properties: + countryCode: + type: string + example: FR + state: + type: string + example: Paris + zipCode: + type: string + example: '75007' + city: + type: string + example: Paris + street: + type: string + example: Av. de la Bourdonnais + address2: + type: string + address3: + type: string + suburb: + type: string + streetNumber: + type: string + example: '14' + latitude: + $ref: '#/components/schemas/JsonNullableBigDecimal' + longitude: + $ref: '#/components/schemas/JsonNullableBigDecimal' + radius: + type: integer + format: int32 + example: 5 + TrackingEvent: + type: object + properties: + id: + type: integer + format: int32 + code: + type: string + time: + type: string + format: date-time + locationName: + type: string + carrierEventDescription: + type: string + locationCountry: + type: string + TrackingResponse: + type: object + properties: + trackingNumber: + type: string + description: Tracking number of the parcel + trackingEvents: + type: array + items: + $ref: '#/components/schemas/TrackingEvent' + CommercialInvoiceAddress: + required: + - address1 + - city + - country + - name + type: object + properties: + name: + maxLength: 50 + minLength: 0 + type: string + description: Name and Surname + example: Peter Baer + address1: + maxLength: 50 + minLength: 0 + type: string + description: Street address including house number + example: High Street 1 + address2: + maxLength: 50 + minLength: 0 + type: string + description: 'Optional: address line 2' + example: Apt. B1 + address3: + maxLength: 50 + minLength: 0 + type: string + description: 'Optional: address line 3' + example: 2nd Floor + postalCode: + maxLength: 20 + minLength: 0 + type: string + description: Enter the postal code (zipcode) in the format of the destination + country + example: '3030' + city: + maxLength: 30 + minLength: 0 + type: string + description: Name of city + example: Bern + province: + maxLength: 255 + minLength: 0 + type: string + description: State code or name of province + country: + maxLength: 2 + minLength: 0 + type: string + description: Country code of shipping country in ISO 3166-1 alpha-2 Code + format + example: CH + email: + maxLength: 70 + minLength: 0 + type: string + description: Contact email address + example: pbaer@acme.ch + phone: + maxLength: 25 + minLength: 0 + type: string + description: Contact phone number + example: '+41123456789' + CommercialInvoiceBulkDetails: + required: + - importer + - receiver + - seller + - sender + type: object + properties: + senderTaxId: + type: string + description: Import One Stop Shop (IOSS) reference or VAT number of the + sender in parcel destination country. + zazAccountNumber: + type: string + description: ZAZ account number + uidChTaxNumber: + type: string + description: UID CH Tax number + sender: + $ref: '#/components/schemas/CommercialInvoiceAddress' + receiver: + $ref: '#/components/schemas/CommercialInvoiceAddress' + seller: + $ref: '#/components/schemas/CommercialInvoiceAddress' + importer: + $ref: '#/components/schemas/CommercialInvoiceAddress' + CommercialInvoiceRequest: + required: + - incoterm + - invoiceNumber + - parcelIds + - reasonOfExport + - sellerEORI + type: object + properties: + proformaInvoice: + type: boolean + description: If true, the invoice will be created as a proforma invoice. + If false, the invoice will be created as a commercial invoice. + invoiceNumber: + maxLength: 50 + minLength: 0 + type: string + description: Invoice number has to be unique + example: A-1234 + sellerEORI: + maxLength: 19 + minLength: 0 + type: string + description: Identifies the party that owns or sells the goods being dispatched + or shipped. (EORI = Economic Operators Registration and Identification + number) + example: CH12345678901234567 + poNumber: + maxLength: 50 + minLength: 0 + type: string + example: PO-1234 + incoterm: + type: string + enum: + - DAP + - DDP + - DTP + reasonOfExport: + type: string + enum: + - DOCS + - SALE_OF_GOODS_PERMANENT + - RETURNED_GOODS + - SAMPLES + - GIFT_PERMANENT + - OTHERS + discountRebate: + type: number + description: Discount or rebate on the invoice. Values are recognized as + amounts, not as percentages. + format: double + freightPostage: + type: number + description: Freight or postage on the invoice. Values are recognized as + amounts, not as percentages. + format: double + additionalComments: + type: string + description: Additional comments on the invoice + declarationStatement: + type: string + description: Declaration statement on the invoice + vat: + type: number + description: Value added tax on the invoice. If incoterm is DAP, field + is not needed. Values are recognized as amounts, not as percentages. + format: double + tobaccoTax: + type: number + description: Tobacco tax on the invoice. If incoterm is DAP, field is not + needed. Values are recognized as amounts, not as percentages. + format: double + duty: + type: number + description: Duty on the invoice. If incoterm is DAP, field is not needed. + Values are recognized as amounts, not as percentages. + format: double + bulk: + type: boolean + description: If true, the invoice will be created for multiple parcels and + bulk addresses will be used for the invoice. If false, the invoice will + be created and grouped by receiver address. + example: false + bulkDetails: + $ref: '#/components/schemas/CommercialInvoiceBulkDetails' + parcelIds: + type: array + description: List of parcel ids. The parcels have to be manifested before + creating an invoice. + items: + type: string + description: List of parcel ids. The parcels have to be manifested before + creating an invoice. + format: uuid + JWTToken: + type: object + properties: + id_token: + type: string + LoginVM: + required: + - password + - username + type: object + properties: + username: + maxLength: 50 + minLength: 1 + type: string + password: + maxLength: 100 + minLength: 4 + type: string + ApiPageable: + type: object + properties: + page: + type: integer + format: int32 + size: + maximum: 500 + minimum: 1 + type: integer + format: int32 + example: 10 + description: Pageable object + Customer: + type: object + properties: + id: + maxLength: 255 + minLength: 0 + type: string + example: DE12345678 + name: + maxLength: 255 + minLength: 0 + type: string + example: ACME GmbH diff --git a/modules/connectors/australiapost/SPECS.md b/modules/connectors/australiapost/SPECS.md new file mode 100644 index 0000000000..e77cf80414 --- /dev/null +++ b/modules/connectors/australiapost/SPECS.md @@ -0,0 +1,617 @@ +# Australia Post integration — specification + +Reference for the Australia Post connector. It talks to the Australia +Post **Shipping & Tracking REST API** (`digitalapi.auspost.com.au`, +JSON over HTTPS, HTTP Basic auth + an `Account-Number` header). The +connector supports rating, shipment creation (label download), +shipment cancellation, return shipments, tracking, and order/manifest +creation. + +The **vendor source of truth** is the Australia Post developer portal: +. +Plugin status is `beta` (`karrio/plugins/australiapost/__init__.py`). + +## Table of contents + +1. [Architecture overview](#architecture-overview) +2. [Data flow](#data-flow) +3. [Endpoints](#endpoints) +4. [Authentication](#authentication) +5. [Supported operations](#supported-operations) +6. [Services & options](#services--options) +7. [Data mapping](#data-mapping) +8. [Identifiers](#identifiers) +9. [Connector-specific invariants](#connector-specific-invariants) +10. [Error parsing](#error-parsing) +11. [References](#references) + +--- + +## Architecture overview + +``` +┌─────────────────────────┐ +│ Unified shipping model │ karrio RateRequest / ShipmentRequest / +│ (karrio core) │ ShipmentCancelRequest / TrackingRequest / +└───────────┬─────────────┘ ManifestRequest + │ + ▼ +┌─────────────────────────┐ +│ providers/australiapost│ Pure data transforms. +│ rate.py │ Unified model → typed AusPost request, +│ shipment/create.py │ typed AusPost response → unified model. +│ shipment/cancel.py │ No HTTP, no side effects. +│ shipment/return_…py │ +│ tracking.py │ +│ manifest.py │ +│ error.py │ +│ units.py │ ShippingService, ShippingOption, +│ utils.py (Settings) │ PackagingType, LabelType, TrackingStatus, +│ │ CustomsContentType, ServiceLabelGroup +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ mappers/australiapost/ │ HTTP transport only. +│ proxy.py │ - Basic auth + Account-Number header +│ │ - shipment = 3-call chain +│ │ - manifest = 2-call chain +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ Australia Post API │ digitalapi.auspost.com.au +│ /shipping/v1/prices │ rate +│ /shipping/v1/shipments │ create / cancel shipment +│ /shipping/v1/labels │ request labels +│ /shipping/v1/track │ tracking +│ /shipping/v1/orders │ order / manifest +└─────────────────────────┘ +``` + +**Key architectural choices:** + +- **Multi-call shipment creation.** Creating a shipment is a chain of + up to three HTTP calls in `proxy.create_shipment`: (1) `POST + /shipments` to create the shipment, (2) `POST /labels` to request + the label PDF/ZPL, (3) `GET