CarrierAPI — Self-hosted multi-carrier verzendplatform

Eén REST API voor verzendlabels, tarieven, tracking en batch-verzending over meerdere vervoerders — self-hosted, als alternatief voor een SaaS-tool als SendCloud, met volledige controle over de eigen carrier-contracten.

Multi-carrier shipping
FastAPI / Python
NetSuite & Shopify sync
Self-hosted

Rol & scope

Een e-commercebedrijf met een eigen ERP (NetSuite) verzond via een SaaS-verzendtool, met de bijbehorende kosten en beperkte controle over carrier-contracten die daarbij horen. CarrierAPI is de self-hosted vervanger: één API die labels aanmaakt, tarieven vergelijkt, zendingen volgt en batches verwerkt over meerdere vervoerders, met dezelfde aansluitpunten als de vorige tool zodat de overstap vanuit NetSuite een drop-in vervanging kon zijn in plaats van een herbouw.

  • Adapter-architectuur per vervoerder (PostNL, DHL Express en UPS als kernset; DHL Parcel, FedEx, GLS en SendCloud zelf als extra adapters), zodat een nieuwe vervoerder toevoegen geen wijziging in de rest van het systeem vraagt.
  • Drop-in vervanging van de vorige SaaS-tool binnen NetSuite: zelfde custom-fieldnamen, minimale wijzigingen aan de bestaande SuiteScript-integratie.
  • Directe koppeling op Item Fulfillment-records in NetSuite, plus synchronisatie met Shopify en een Amazon-koppeling naast de vervoerders.
  • Twee manieren om carrier-credentials te beheren: via de UI (database-backed, voorkeur) of via environment-variabelen (legacy-pad voor bestaande deployments).

Impact

  • Verzendkosten en carrier-contracten in eigen hand in plaats van vastzitten aan de tarieven en voorwaarden van een SaaS-tussenlaag.
  • Adresherkenning gerepareerd voor een klasse adressen die eerder verkeerd werd geparsed (huisnummer-extractie pakte per ongeluk het laatste getal in plaats van het eerste na de straatnaam — fout bij bijvoorbeeld vakantieparkadressen met een huisnummer én een chaletnummer).
  • Facturatie-matching met UPS bleef werken nadat UPS twee keer het exportformaat van hun facturen wijzigde, door kolomdetectie op inhoud te baseren in plaats van op vaste posities, gecontroleerd tegen het totaalbedrag dat de factuur zelf claimt.
  • Een sessie-lek gedicht waarbij verwijderde gebruikers tot 24 uur lang nog toegang hadden, omdat alleen de handtekening en vervaldatum van hun sessietoken werden gecontroleerd — niet of het account nog bestond.

Architectuur

Elke vervoerder is een adapter achter een gedeelde interface (aanmaken en annuleren van zendingen, tarieven opvragen, tracking, beschikbare diensten) — nieuwe vervoerders aansluiten raakt de rest van het systeem niet. De backend is FastAPI (Python, async) met PostgreSQL als primaire database en een Redis-gebaseerde achtergrondwerker (arq) voor batchverwerking van labels, zodat het aanmaken van honderden labels tegelijk de API niet blokkeert. Labels zelf worden opgeslagen in MinIO (S3-compatible), zodat opslag niet aan één cloudleverancier vastzit. De frontend is een Next.js/React-dashboard voor het beheren van zendingen, carrier-instellingen en credentials. Het geheel draait self-hosted via Docker Compose achter Traefik, uitgerold met Coolify.

Modelkeuze

Geen AI/LLM — dit is bewust een deterministisch systeem: verzendlabels, tarieven en tracking moeten voorspelbaar en controleerbaar zijn, niet gegenereerd. De belangrijkste keuze hier was self-hosted versus SaaS: een SaaS-verzendtool is sneller op te zetten, maar een self-hosted platform geeft controle over carrier-contracten, geen per-label of per-zending vergoeding aan een tussenlaag, en de mogelijkheid om functionaliteit (zoals de directe NetSuite-koppeling) precies op maat te bouwen in plaats van te wachten op wat een SaaS-leverancier wel of niet ondersteunt.

Kosten & latency

  • Zelf gehoste infrastructuur (Docker Compose, PostgreSQL, Redis, MinIO) in plaats van een doorlopende per-zending SaaS-vergoeding.
  • Batch-labelgeneratie loopt asynchroon via een Redis-wachtrij (arq), zodat honderden labels tegelijk aanmaken de API responsief houdt in plaats van de aanvrager te laten wachten.
  • Facturatie-matching tegen echte UPS-facturen gecontroleerd: onder meer een factuur van 1.275 regels/€4.855,72 en een export van 2.275 regels, beide correct verwerkt na de fix op kolomdetectie.

Lessons learned

Adresparsing voor Nederlandse adressen ging fout bij een specifieke klasse adressen (vakantieparken met een huisnummer én een apart chaletnummer, zoals "Hessenweg 83 41") doordat de parser het laatste getal in de straatregel als huisnummer las in plaats van het eerste na de straatnaam. Opgelost met een gedeelde parser, geverifieerd tegen een echte case. UPS-facturatie-import brak twee keer doordat UPS het exportformaat van hun facturen wijzigde (kolomaantal en -volgorde verschoven); de vaste-kolomposities-aanpak is vervangen door contentgebaseerde kolomdetectie die het resultaat kruist met het totaalbedrag dat de factuur zelf claimt. Adresvalidatie-waarschuwingen blokkeerden aanvankelijk het aanmaken van een zending volledig; teruggedraaid naar alleen het automatisch kopen van een label blokkeren, zodat een gemarkeerde zending zichtbaar en opnieuw te proberen blijft in plaats van vast te lopen. En een sessie-kwetsbaarheid: verwijderde gebruikers behielden tot 24 uur toegang omdat tokens alleen op handtekening en vervaldatum werden gecontroleerd, niet op het bestaan van het account — opgelost door bij elk verzoek te verifiëren dat het account nog bestaat.