shipzil

Errors and exclusions

Partial rating failures, validation errors and purchase safety.

shipzil reports three kinds of failure:

  1. A source-level ShipzilError in quote.errors.
  2. An Exclusion for a carrier, service or local preflight rule.
  3. A synchronous exception from configuration, model construction or purchase.

Partial rating failures

Gateway.get_rates() catches ShipzilError from each source. Successful sources still contribute rates:

quote = gateway.get_rates(shipment)

for error in quote.errors:
    log.warning("rating source failed: %s", error)

if not quote:
    raise NoShippingOption(quote.explain())

Errors that are not ShipzilError propagate. They indicate a programming error in shipzil or a custom adapter and are not converted into provider downtime.

Exclusions

for exclusion in quote.excluded:
    print(exclusion.code, exclusion.source, exclusion.message)
FieldMeaning
codenormalized ExclusionCode
messageprovider text or shipzil preflight explanation
carrieraffected carrier when known
serviceaffected service when known
sourceprovider when the failure came from provider output; shipzil for local validation or filtering

The code may be inferred from provider prose. source records where the failure originated, not whether the normalized code was structured or inferred.

shipzil reports rates it removes through local carrier or service filters, and retains exclusions supplied by providers.

It also reports a rate list shortened by a transient carrier failure. A provider can return some rates while one carrier is rate limited, which would otherwise look like a normal result. That case arrives as RATE_LIMITED alongside the rates that did come back:

quote = gateway.get_rates(shipment)

if any(e.code is z.ExclusionCode.RATE_LIMITED for e in quote.excluded):
    # Fewer services than usual. Retrying later may return more.
    log.info("rate list may be incomplete: %s", quote.explain())

Permanent account and lane messages are not promoted to exclusions, because they are true on every call for that account and would bury the transient case. They remain available in quote.messages.

shipzil cannot report a service a provider omits with no message at all. Detecting that needs a baseline of what the account usually returns.

Exclusion codes

CodeMeaning
MULTIPACKAGE_NOT_SUPPORTEDcarrier cannot rate the full parcel set
SERVICE_UNAVAILABLEno service was available for the request
CARRIER_ACCOUNT_MISCONFIGUREDprovider account or carrier connection needs configuration
DIMENSIONS_REQUIREDprovider needs dimensions that were not supplied
ITEM_CLASSIFICATION_REQUIREDprovider needs item category or HS classification
ADDRESS_UNSUPPORTEDaddress or lane is unsupported
CUSTOMS_DECLARATION_REQUIREDcross-border item data, EEI data or provider support is missing
DUTIES_UNSUPPORTEDselected duty liability is not transmitted by this adapter
HAZMAT_DETAIL_UNSUPPORTEDdeclared dangerous-goods fields would be dropped
RATE_LIMITEDprovider throttled the request
FILTERED_BY_REQUESTcaller filter removed the rate
SERVICE_NOT_ADDRESSABLEprovider rate could not be assigned a ServiceKey
UNKNOWNprovider text did not match a known code

UNKNOWN retains the original message. Branch on the code for known cases and log the message for diagnosis.

Exceptions

Provider, Gateway and purchase failures inherit ShipzilError:

ExceptionMeaning
ConfigurationErrormissing credentials, unknown source or conflicting options
AuthenticationErrorprovider rejected credentials
ValidationErrorprovider rejected the request data
CapabilityErrorrequested operation cannot be performed or emulated
RateLimitErrorprovider throttled or exhausted quota
ProviderErrornetwork/provider failure not classified more specifically
LabelPurchaseErrorprovider returned a known purchase failure
AmbiguousPurchaseErrorpurchase request may have succeeded
SpendLimitExceededlocal max_spend check stopped the purchase

Invalid local model values, units and service-key strings raise ValueError. They do not inherit ShipzilError.

Purchase safety

Purchases are sent once. They are not automatically retried or redirected to a different source.

A ProviderError raised after Gateway.buy() dispatches the request is converted to AmbiguousPurchaseError. Easyship also raises it when a confirmed shipment does not settle before the polling deadline.

try:
    label = gateway.buy(shipment, rate)
except z.AmbiguousPurchaseError as error:
    log.error("purchase outcome unknown: %s", error)
    # Reconcile recent labels/shipments with rate.source before another attempt.
    raise
except z.LabelPurchaseError as error:
    log.error("provider reported purchase failure: %s", error)

No supported purchase path documents a caller-supplied idempotency key. A second request can buy postage twice.

Rate limits

Provider status codes are not uniform:

  • Shippo can return HTTP 201 with no rates and a message containing "Too Many Requests". shipzil turns that message into a RATE_LIMITED exclusion.
  • Easyship can return HTTP 403 with "API usage limit exceeded". shipzil raises RateLimitError, not AuthenticationError.

Safe rating and read requests retry 429, 502, 503 and 504 responses with backoff. Retry-After is honored and capped at 30 seconds. Purchase, cancel and refund requests pass retries=0.

Empty result example

quote = gateway.get_rates(shipment)

if not quote:
    for error in quote.errors:
        log.warning("source failed: %s", error)
    for exclusion in quote.excluded:
        log.warning(
            "excluded %s: %s",
            exclusion.code.value,
            exclusion.message,
        )
    for message in quote.messages:
        log.warning("provider warning: %s", message)
    raise NoShippingOption(quote.explain())

On this page