fleet-costs-sdk
Official Python SDK for the Fleet Costs landed-cost / duty / VAT / freight pricing API.
pip install fleet-costs-sdk
from fleet import FleetClient
client = FleetClient(
base_url="https://api.getfleet.dev",
api_key="ck_live_...",
)
quote = client.create_quote({
"origin": "US",
"dest": "DE",
"itemValue": {"amount": 100, "currency": "USD"},
"dimsCm": {"l": 20, "w": 15, "h": 5},
"weightKg": 0.5,
"categoryKey": "electronics.smartphone",
"mode": "air",
"hs6": "851712", # optional: supply a 6-, 8- or 10-digit commodity code
})
print(quote["total"])
Features
- Synchronous client built on httpx.
- Automatic retries on transient failures (429, 500, 502, 503, 504) with exponential backoff.
- Idempotency key support on mutating endpoints — safe to retry writes.
- Context manager support (
with FleetClient(...) as client: ...). - Typed method surface covering quotes, classify, manifests, and lookups.
Documentation
- API reference: https://docs.getfleet.dev
- Python SDK guide: https://docs.getfleet.dev/guides/python-sdk
- Support: https://getfleet.dev/contact or support@getfleet.dev
License
MIT — see the LICENSE file included in this package.
Collection assessment
Quote methods preserve the API's assessment dictionary. Before collecting import charges,
require quote["assessment"]["collection"]["eligible"] is True. Its outcome is
complete, estimated, or refused; display the reasons and their message and action
alongside any estimate. A maximum or confidence score is not collection permission.
Category-only quotes remain estimates. Missing assessment on an older saved response requires
a fresh quote with a new idempotency key before collection.
Retry and recovery keys
Mutating methods return a FleetResponse: the usual response dictionary, plus an
idempotency_key attribute containing the key actually sent. The attribute is SDK
metadata and is not added to the API JSON fields.
import httpx
from fleet import FleetAPIError
try:
quote = client.create_quote(body)
recovery_key = quote.idempotency_key
except FleetAPIError as error:
recovery_key = error.idempotency_key
server_code = error.code
except httpx.RequestError as error:
recovery_key = error.idempotency_key
except ValueError as error:
recovery_key = error.idempotency_key
For a timed-out quote, use client.get_quote_by_key(recovery_key) to retrieve a
result that may have completed server-side. A missing result is not evidence that
it is safe to create the same order under another key.
The SDK keeps the same key on network errors, ordinary transient HTTP failures,
and 409 IDEMPOTENCY_KEY_PROCESSING. It generates a replacement only after
409 IDEMPOTENCY_KEY_FAILED explicitly confirms rollback, and only when the SDK
created the key. A caller's idempotency_key is never replaced. Unrelated conflicts
are not retried. Retries share the configured max_retries budget; HTTP and
transport failures expose the final attempted key even when that budget is zero.
Invalid JSON and HTTPX response-decoding failures also retain the key on the
original exception; non-transport decoding failures are not retried.
create_quote and create_quote_batch require decoded JSON success responses to
be objects. JSON null, arrays and scalars raise ValueError with the final
idempotency_key, without retrying the write. This checks only the top-level
shape: empty objects and nested nullable fields are retained unchanged. Invalid
JSON still raises its original parsing exception with the final recovery key.
Complete consignments and batch quotes
Use client.create_quote_batch(items) for 1–50 quote inputs. All items are sent in
one request, in their original order, including non-adjacent members of a declared
consignment. Supply the complete membership and the canonical declaration facts;
the SDK does not invent grouping, split oversized batches, or change delivery and
freight inputs. An explicit zero deliveryCharge is different from an omitted value.
insurance is the premium actually paid and not already in itemValue. It is handled as
freight is: part of components.CIF and total everywhere, and of the duty base only
where duty is valued CIF (not the US). For a CN destination, omit it when no insurance
was paid and the API applies the
customs presumption of 3 per mille of goods plus freight (cn_insurance_presumed); send
{"amount": 0, ...} when the insurance is already included in itemValue. In the
response, insurance is the declared premium inside components.CIF and total, and
presumedInsurance is the CN presumption: duty and VAT are computed on it, but it is not
part of components.CIF or total, since nobody pays it. The SDK sends whichever of the
three states you give it unchanged.
Inspect results, succeeded and failed: an HTTP success may contain per-item
errors or estimates, and status == "ok" does not grant collection permission.
Assessments and allocation details are retained unchanged.
The batch response exposes result.idempotency_key. To recover a timed-out batch,
repeat create_quote_batch with the identical full items and that final key.
get_quote_by_key reads single quotes and cannot replay a batch key.
Dispatch and customs status
Quote inputs are dictionaries. Single and batch methods preserve the canonical
declaration.movement object without adding defaults or validating customs facts:
body = {
"origin": "CN", # country of manufacture
"dispatchCountry": "DE", # actual ship-from country
"dest": "FR",
"itemValue": {"amount": 200, "currency": "EUR"},
"dimsCm": {"l": 10, "w": 10, "h": 10},
"weightKg": 2,
"categoryKey": "apparel.tshirt",
"hs6": "610910",
"mode": "air",
"freight": {"amount": 25, "currency": "EUR"},
"deliveryCharge": {"amount": 0, "currency": "EUR"},
"declaration": {"movement": {"customsStatus": "free_circulation"}},
}
quote = client.create_quote(body)
The status describes the goods at this sale in the dispatch customs territory.
free_circulation means qualifying goods are already in free circulation before
the sale, with no release/entry lodged to fulfil this order. Use
not_in_free_circulation when a customs debt or conditional procedure remains,
including bonded stock that will be released to fulfil this order. This is the
merchant's assertion from their records; Fleet does not verify release evidence.
Do not derive it from manufacture, warehouse country, IOSS registration or an unset
Boolean. Supply actual dispatchCountry with a known status; omit movement when
unknown. An empty movement object or a made-up status is not an unknown value.
Movement is per line: items sharing a complete consignment may have different
statuses. Preserve those facts; do not split a consignment to change its treatment.
On a no-border lane, a required release produces customs_release_not_modelled
and a refused assessment with a null maximum. Other qualifications remain
independent, including vat_buyer_status_unresolved. Neither declared free
circulation nor a successful batch item establishes collection permission, buyer
VAT entitlement, seller registration or the correct remittance jurisdiction.
explainability.duty.customsStatusSource records request for a merchant declaration
and assumed for an inferred status on applicable no-border quotes. It is absent
on border quotes and can be absent on historical records, including records with
no explainability block. Preserve that absence; never fill it with assumed or
interpret request as verified. The response dictionary retains complete reasons,
warnings, missing components and nullable ceilings alongside this provenance.
Distance-sales attestation
A seller established in an EU member state, with no OSS registration, can attest
that it is under that state's distance-selling threshold (EUR 10,000 in the euro
area, the national figure elsewhere). State the total in the threshold's currency;
client.get_distance_sales_threshold("PL") says which. Quotes whose
request declares dispatchCountry equal to the attested state can then price
consumer sales at the dispatch state's rate:
profile = client.set_distance_sales_attestation({
"statementVersion": 1,
"establishmentCountry": "NL",
"sellsOnOwnAccount": True,
"establishedOnlyThere": True,
"dispatchesOnlyFromThere": True,
"noDestinationTaxationOption": True,
"noSmallEnterpriseExemption": True,
"priorYearWithinThreshold": True,
"currentYearTotal": {"amount": 4200, "currency": "EUR"},
"currentYearTotalAsOf": "2026-09-20",
"exclusionsAcknowledged": True,
})
profile["distanceSalesAttestation"]["inEffect"]
# As soon as the current-year total passes the threshold:
client.withdraw_distance_sales_attestation()
Every statement is a literal True: send the attestation only if the seller can
affirm each one. It needs a live key with self:write; Test-mode keys are refused,
and the API does not check who holds the key.
explainability.vat.supplyVatSchemeSource names what decided where a consumer sale is taxed (oss_registration,
destination_registration, declared_destination, attestation or assumed) on a cross-border intra-EU supply
priced OSS or domestic. declared_destination means the seller states destination taxation with no OSS or
destination VAT registration on file; such an OSS-priced quote cannot be collected.
It is absent on reverse charge, domestic movements, imports and older saved quotes.
explainability.vat.thresholdAttestation is present only where the engine consulted an attestation on file (no OSS
registration, a domestic-scheme price): applied, or not_applied with a reason.
Preserve both absences; do not fill them in.
Buyer VAT numbers
Send the buyer's VAT number, country prefix included, as
declaration.buyer.vatNumber, and declaration.buyer.taxStatus: "vat_registered_business" only where your checkout captured it. Never derive the
status from the presence of a number.
Where an EU number could decide an intra-EU supply, Fleet checks it against VIES and
reports the result in explainability.vat.buyerVatCheck: status (valid, invalid, unavailable, or
not_checked with a reason), numberCountry, requesterCountry, checkedAt and
requestIdentifier, the VIES consultation number to keep with the invoice. The
number itself is never echoed.
A check needs your VAT registration for the dispatch state recorded in Fleet as the
requester. Without it the result is not_checked with reason
no_requester_registration, test keys included. Live keys in production are not
checked until the switch-on (#2369). Test keys get a simulator that sends nothing
anywhere: national part 100 is valid and 200 invalid. GB numbers are not checked
yet.
What the result changes depends on the lane, so read each quote's warnings rather than these notes:
- A number from another member state (an exempt supply). A
validnumber besidevat_registered_businessreplacesvat_reverse_charge_conditionalwith the information codevat_intra_eu_exemption_number_verified, and the quote can complete. VIES confirms a registration, not who is buying, so a valid number without that status does not. VAT confidence stays capped atestimatedandguaranteedMaxkeeps its reserve either way, because the exemption also rests on your own recapitulative statement.unavailableandnot_checkedleave the quote as it would be without a check. - A number from the dispatch state never makes the supply exempt. A valid one
beside
vat_registered_businessprices dispatch-state VAT and discloses it withvat_reverse_charge_not_available. - An
invalidnumber, from either, is priced as absent. It blocks collection withvat_buyer_vat_number_invalidonly where that could change the rate; where it cannot, such as equal rates or an applied distance-sales attestation, the quote discloses it withvat_reverse_charge_not_available.
On a GB business import of GBP 135 or less, a GB number gives a conditional zero
(vat_reverse_charge_conditional) with no guaranteedMax. Rows from export_quotes carry the same buyerVatCheck, so
the consultation number stays linked to its quote.
For the separate organization data export,
use GET /v1/data-retention/export with self:read scope as an organization owner or admin.
Retained consultation records appear in data.viesConsultations, newest first, up to 10,000.
completeness.viesConsultationsIncluded gives the returned count;
completeness.viesConsultationsTruncated says whether more records exist.
These records are separate from per-quote buyerVatCheck. This organization export excludes
single-quote history, manifest quotes and manifest documents; it is not a complete quote-history export.
Great Britain and Northern Ireland
For quote and manifest requests with destination GB (or UK), explicitly send
gbDeliveryTerritory: "great_britain" or "northern_ireland" for the actual delivery.
No SDK supplies a default. Omission returns HTTP 422
GB_DELIVERY_TERRITORY_REQUIRED; Northern Ireland returns HTTP 422
TERRITORY_NOT_SUPPORTED. Do not derive Great Britain from the country code alone.
Gateway headers
Use the public extra_headers option when your chosen API gateway requires
additional headers. For example, a Cloudflare Access protected origin can use:
import os
from fleet import FleetClient
with FleetClient(
base_url=os.environ["FLEET_API_URL"],
api_key=os.environ["FLEET_API_KEY"],
extra_headers={
"CF-Access-Client-Id": os.environ["CF_ACCESS_CLIENT_ID"],
"CF-Access-Client-Secret": os.environ["CF_ACCESS_CLIENT_SECRET"],
},
) as client:
quote = client.create_quote(body)
The mapping is copied at construction. Header names are case-insensitive;
invalid/duplicate names, non-ASCII/control-character values and SDK-controlled
authentication, idempotency, host, framing and hop-by-hop headers raise ValueError.
Keep the API key in api_key and operation keys in idempotency_key.
Redirects are not followed, so gateway credentials are not forwarded to a
redirect destination. Use a trusted HTTPS origin for real credentials.
Metadata
Release files for fleet-costs-sdk 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fleet_costs_sdk-0.4.0.tar.gz | 14.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fleet_costs_sdk-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 30.4 kB
Release files / fleet_costs_sdk-0.4.0.tar.gz
| Download URL | fleet_costs_sdk-0.4.0.tar.gz |
|---|---|
| Size | 14.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
831f986ffdde90f836a3a3d2db1c81b6e44785c2927c00883c340086e9137579
|
|
BLAKE2b-256 checksum How to use checksums |
81a452727da3170922e7bc91d17570cb17e1c39f6bd064b49109687fb96928cd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / fleet_costs_sdk-0.4.0-py3-none-any.whl
| Download URL | fleet_costs_sdk-0.4.0-py3-none-any.whl |
|---|---|
| Size | 15.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0ce11d94c54639fc6d3ff9b71edb2f380884245155b4252d47345736209ab68f
|
|
BLAKE2b-256 checksum How to use checksums |
516bccd66d042d240dedbff3b0e0252961b82d530c76c2cace92b2429e392736
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log