Roll & omfattning
Ett e-handelsföretag med eget ERP (NetSuite) skickade via ett SaaS-fraktverktyg, med de kostnader och den begränsade kontrollen över transportöravtal som följer med. CarrierAPI är den självhostade ersättaren: ett API som skapar etiketter, jämför priser, spårar försändelser och behandlar batchar hos flera transportörer, med samma anslutningspunkter som det tidigare verktyget så att bytet från NetSuite kunde bli en drop-in-ersättning i stället för en ombyggnad.
- En adapterarkitektur per transportör (PostNL, DHL Express och UPS som kärnuppsättning; DHL Parcel, FedEx, GLS och SendCloud själv som extra adaptrar), så att en ny transportör inte kräver några ändringar i resten av systemet.
- Drop-in-ersättning för det tidigare SaaS-verktyget inuti NetSuite: identiska namn på anpassade fält, minimala ändringar i den befintliga SuiteScript-integrationen.
- Direkt koppling mot Item Fulfillment-poster i NetSuite, plus synkronisering med Shopify och en Amazon-integration vid sidan av transportörerna.
- Två sätt att hantera transportörsuppgifter: via gränssnittet (databaslagrat, det föredragna) eller via miljövariabler (legacy-väg för befintliga installationer).
Effekt
- Fraktkostnader och transportöravtal tillbaka i egna händer i stället för att sitta fast i priser och villkor hos ett SaaS-mellanlager.
- Adressigenkänningen lagades för en klass adresser som tidigare tolkades fel (husnummerextraheringen tog av misstag det sista talet i stället för det första efter gatunamnet — fel vid exempelvis semesterparksadresser med både husnummer och stugnummer).
- Fakturamatchningen mot UPS fortsatte fungera efter att UPS två gånger ändrat exportformatet på sina fakturor, genom att basera kolumndetektering på innehåll i stället för fasta positioner, kontrollerat mot den totalsumma fakturan själv anger.
- En sessionsläcka täpptes till där borttagna användare hade kvar åtkomst i upp till 24 timmar, eftersom bara signatur och giltighetstid på deras token kontrollerades — inte om kontot fortfarande fanns.
Arkitektur
Varje transportör är en adapter bakom ett gemensamt gränssnitt (skapa och annullera försändelser, hämta priser, spårning, tillgängliga tjänster) — att koppla in en ny transportör rör inte resten av systemet. Backend är FastAPI (Python, async) med PostgreSQL som primär databas och en Redis-baserad bakgrundsworker (arq) för batchbehandling av etiketter, så att hundratals etiketter på en gång inte blockerar API:et. Etiketterna själva lagras i MinIO (S3-kompatibelt), så att lagringen inte är låst till en enda molnleverantör. Frontend är en Next.js/React-dashboard för att hantera försändelser, transportörsinställningar och autentiseringsuppgifter. Allt körs självhostat via Docker Compose bakom Traefik, utrullat med Coolify.
Modellval
Ingen AI/LLM — det här är medvetet ett deterministiskt system: fraktetiketter, priser och spårning måste vara förutsägbara och kontrollerbara, inte genererade. Det avgörande valet här var självhostat kontra SaaS: ett SaaS-fraktverktyg går snabbare att sätta upp, men en självhostad plattform ger kontroll över transportöravtalen, ingen avgift per etikett eller försändelse till ett mellanlager, och möjligheten att bygga funktionalitet (som den direkta NetSuite-kopplingen) exakt efter behov i stället för att vänta på vad en SaaS-leverantör stödjer eller inte.
Kostnad & latens
- Självhostad infrastruktur (Docker Compose, PostgreSQL, Redis, MinIO) i stället för en löpande SaaS-avgift per försändelse.
- Batchgenerering av etiketter körs asynkront via en Redis-kö (arq), så att hundratals etiketter på en gång håller API:et responsivt i stället för att låta beställaren vänta.
- Fakturamatchningen är verifierad mot riktiga UPS-fakturor: bland annat en faktura på 1 275 rader à 4 855,72 € och en export på 2 275 rader, båda korrekt behandlade efter fixen på kolumndetekteringen.
Lärdomar
Adresstolkningen för nederländska adresser gick fel på en särskild klass adresser (semesterparker med både husnummer och ett separat stugnummer, som ”Hessenweg 83 41”) eftersom parsern läste det sista talet på gaturaden som husnummer i stället för det första efter gatunamnet. Löst med en gemensam parser, verifierad mot ett verkligt fall. UPS-fakturaimporten gick sönder två gånger för att UPS ändrade exportformatet på sina fakturor (antal och ordning på kolumner förflyttades); metoden med fasta kolumnpositioner ersattes av innehållsbaserad kolumndetektering vars resultat stäms av mot den totalsumma fakturan själv anger. Varningar från adressvalideringen blockerade från början helt att en försändelse kunde skapas; det rullades tillbaka till att bara blockera det automatiska etikettköpet, så att en flaggad försändelse förblir synlig och går att försöka igen i stället för att fastna. Och en sessionssårbarhet: borttagna användare behöll åtkomst i upp till 24 timmar eftersom tokens bara kontrollerades på signatur och giltighetstid, inte på att kontot fanns — löst genom att vid varje anrop verifiera att kontot fortfarande existerar.