universal-parcel-scraper 0.36.0 → 0.37.0-main.390

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/data/catalog.json CHANGED
@@ -4431,7 +4431,8 @@
4431
4431
  "help": "Optional. DPD uses it to unlock verified scans and the delivery window.",
4432
4432
  "validator": "germanyPostcode"
4433
4433
  }
4434
- ]
4434
+ ],
4435
+ "recognitionRank": 65
4435
4436
  },
4436
4437
  "canaryUrl": "https://tracking.dpd.de/status/en_US/parcel/",
4437
4438
  "trackingUrlTemplate": "https://tracking.dpd.de/status/en_US/parcel/{trackingNumber}",
@@ -47,6 +47,26 @@
47
47
  "ctt-express"
48
48
  ]
49
49
  },
50
+ {
51
+ "input": "0000100000104002537106",
52
+ "carrier": "unknown",
53
+ "confidence": "low",
54
+ "candidates": [
55
+ "tipsa",
56
+ "austrian-post",
57
+ "ctt-express",
58
+ "usps",
59
+ "estafeta"
60
+ ],
61
+ "preferred": [
62
+ "tipsa"
63
+ ],
64
+ "asked": [
65
+ "tipsa",
66
+ "austrian-post",
67
+ "ctt-express"
68
+ ]
69
+ },
50
70
  {
51
71
  "input": "000123450000000027",
52
72
  "carrier": "unknown",
@@ -248,10 +268,10 @@
248
268
  ],
249
269
  "asked": [
250
270
  "dpd",
271
+ "dpd-de",
251
272
  "seur",
252
273
  "brt",
253
- "hermes-de",
254
- "relais-colis"
274
+ "hermes-de"
255
275
  ]
256
276
  },
257
277
  {
@@ -375,10 +395,10 @@
375
395
  ],
376
396
  "asked": [
377
397
  "dpd",
398
+ "dpd-de",
378
399
  "seur",
379
400
  "brt",
380
- "hermes-de",
381
- "relais-colis"
401
+ "hermes-de"
382
402
  ]
383
403
  },
384
404
  {
@@ -451,10 +471,10 @@
451
471
  ],
452
472
  "asked": [
453
473
  "dpd",
474
+ "dpd-de",
454
475
  "seur",
455
476
  "brt",
456
- "hermes-de",
457
- "relais-colis"
477
+ "hermes-de"
458
478
  ]
459
479
  },
460
480
  {
@@ -469,6 +489,7 @@
469
489
  ],
470
490
  "asked": [
471
491
  "dpd",
492
+ "dpd-de",
472
493
  "chronopost"
473
494
  ]
474
495
  },
@@ -490,10 +511,10 @@
490
511
  ],
491
512
  "asked": [
492
513
  "dpd",
514
+ "dpd-de",
493
515
  "seur",
494
516
  "brt",
495
- "relais-colis",
496
- "ciblex"
517
+ "relais-colis"
497
518
  ]
498
519
  },
499
520
  {
@@ -514,10 +535,10 @@
514
535
  ],
515
536
  "asked": [
516
537
  "dpd",
538
+ "dpd-de",
517
539
  "seur",
518
540
  "brt",
519
- "relais-colis",
520
- "ciblex"
541
+ "relais-colis"
521
542
  ]
522
543
  },
523
544
  {
@@ -538,10 +559,10 @@
538
559
  ],
539
560
  "asked": [
540
561
  "dpd",
562
+ "dpd-de",
541
563
  "seur",
542
564
  "brt",
543
- "relais-colis",
544
- "ciblex"
565
+ "relais-colis"
545
566
  ]
546
567
  },
547
568
  {
@@ -562,10 +583,10 @@
562
583
  ],
563
584
  "asked": [
564
585
  "dpd",
586
+ "dpd-de",
565
587
  "seur",
566
588
  "brt",
567
- "relais-colis",
568
- "ciblex"
589
+ "relais-colis"
569
590
  ]
570
591
  },
571
592
  {
@@ -586,10 +607,10 @@
586
607
  ],
587
608
  "asked": [
588
609
  "dpd",
610
+ "dpd-de",
589
611
  "seur",
590
612
  "brt",
591
- "relais-colis",
592
- "ciblex"
613
+ "relais-colis"
593
614
  ]
594
615
  },
595
616
  {
@@ -619,10 +640,10 @@
619
640
  ],
620
641
  "asked": [
621
642
  "dpd",
643
+ "dpd-de",
622
644
  "seur",
623
645
  "brt",
624
- "hermes-de",
625
- "relais-colis"
646
+ "hermes-de"
626
647
  ]
627
648
  },
628
649
  {
@@ -644,10 +665,10 @@
644
665
  ],
645
666
  "asked": [
646
667
  "dpd",
668
+ "dpd-de",
647
669
  "seur",
648
670
  "brt",
649
- "hermes-de",
650
- "relais-colis"
671
+ "hermes-de"
651
672
  ]
652
673
  },
653
674
  {
@@ -669,10 +690,10 @@
669
690
  ],
670
691
  "asked": [
671
692
  "dpd",
693
+ "dpd-de",
672
694
  "seur",
673
695
  "brt",
674
- "hermes-de",
675
- "relais-colis"
696
+ "hermes-de"
676
697
  ]
677
698
  },
678
699
  {
@@ -694,10 +715,10 @@
694
715
  ],
695
716
  "asked": [
696
717
  "dpd",
718
+ "dpd-de",
697
719
  "seur",
698
720
  "brt",
699
- "hermes-de",
700
- "relais-colis"
721
+ "hermes-de"
701
722
  ]
702
723
  },
703
724
  {
@@ -788,6 +809,24 @@
788
809
  "austrian-post"
789
810
  ]
790
811
  },
812
+ {
813
+ "input": "0280400280406520139001",
814
+ "carrier": "unknown",
815
+ "confidence": "low",
816
+ "candidates": [
817
+ "tipsa",
818
+ "austrian-post",
819
+ "usps",
820
+ "estafeta"
821
+ ],
822
+ "preferred": [
823
+ "tipsa"
824
+ ],
825
+ "asked": [
826
+ "tipsa",
827
+ "austrian-post"
828
+ ]
829
+ },
791
830
  {
792
831
  "input": "0301006785462006320995",
793
832
  "carrier": "unknown",
@@ -855,6 +894,24 @@
855
894
  "purolator"
856
895
  ]
857
896
  },
897
+ {
898
+ "input": "0410230410230001262324",
899
+ "carrier": "unknown",
900
+ "confidence": "low",
901
+ "candidates": [
902
+ "tipsa",
903
+ "austrian-post",
904
+ "usps",
905
+ "estafeta"
906
+ ],
907
+ "preferred": [
908
+ "tipsa"
909
+ ],
910
+ "asked": [
911
+ "tipsa",
912
+ "austrian-post"
913
+ ]
914
+ },
858
915
  {
859
916
  "input": "041441760228964",
860
917
  "carrier": "unknown",
@@ -899,10 +956,10 @@
899
956
  ],
900
957
  "asked": [
901
958
  "dpd",
959
+ "dpd-de",
902
960
  "seur",
903
961
  "brt",
904
- "relais-colis",
905
- "ciblex"
962
+ "relais-colis"
906
963
  ]
907
964
  },
908
965
  {
@@ -923,6 +980,60 @@
923
980
  "austrian-post"
924
981
  ]
925
982
  },
983
+ {
984
+ "input": "0460040460048104747942",
985
+ "carrier": "unknown",
986
+ "confidence": "low",
987
+ "candidates": [
988
+ "tipsa",
989
+ "austrian-post",
990
+ "usps",
991
+ "estafeta"
992
+ ],
993
+ "preferred": [
994
+ "tipsa"
995
+ ],
996
+ "asked": [
997
+ "tipsa",
998
+ "austrian-post"
999
+ ]
1000
+ },
1001
+ {
1002
+ "input": "0460040460048104754422",
1003
+ "carrier": "unknown",
1004
+ "confidence": "low",
1005
+ "candidates": [
1006
+ "tipsa",
1007
+ "austrian-post",
1008
+ "usps",
1009
+ "estafeta"
1010
+ ],
1011
+ "preferred": [
1012
+ "tipsa"
1013
+ ],
1014
+ "asked": [
1015
+ "tipsa",
1016
+ "austrian-post"
1017
+ ]
1018
+ },
1019
+ {
1020
+ "input": "0460040460048104791791",
1021
+ "carrier": "unknown",
1022
+ "confidence": "low",
1023
+ "candidates": [
1024
+ "tipsa",
1025
+ "austrian-post",
1026
+ "usps",
1027
+ "estafeta"
1028
+ ],
1029
+ "preferred": [
1030
+ "tipsa"
1031
+ ],
1032
+ "asked": [
1033
+ "tipsa",
1034
+ "austrian-post"
1035
+ ]
1036
+ },
926
1037
  {
927
1038
  "input": "0460040460048105123537",
928
1039
  "carrier": "unknown",
@@ -1008,10 +1119,10 @@
1008
1119
  ],
1009
1120
  "asked": [
1010
1121
  "dpd",
1122
+ "dpd-de",
1011
1123
  "seur",
1012
1124
  "brt",
1013
- "relais-colis",
1014
- "ciblex"
1125
+ "relais-colis"
1015
1126
  ]
1016
1127
  },
1017
1128
  {
@@ -1035,10 +1146,10 @@
1035
1146
  ],
1036
1147
  "asked": [
1037
1148
  "dpd",
1149
+ "dpd-de",
1038
1150
  "seur",
1039
1151
  "brt",
1040
- "relais-colis",
1041
- "ciblex"
1152
+ "relais-colis"
1042
1153
  ]
1043
1154
  },
1044
1155
  {
@@ -1067,10 +1178,10 @@
1067
1178
  ],
1068
1179
  "asked": [
1069
1180
  "dpd",
1181
+ "dpd-de",
1070
1182
  "seur",
1071
1183
  "brt",
1072
- "relais-colis",
1073
- "ciblex"
1184
+ "relais-colis"
1074
1185
  ]
1075
1186
  },
1076
1187
  {
@@ -1175,10 +1286,10 @@
1175
1286
  ],
1176
1287
  "asked": [
1177
1288
  "dpd",
1289
+ "dpd-de",
1178
1290
  "seur",
1179
1291
  "brt",
1180
- "relais-colis",
1181
- "ciblex"
1292
+ "relais-colis"
1182
1293
  ]
1183
1294
  },
1184
1295
  {
@@ -1211,10 +1322,10 @@
1211
1322
  ],
1212
1323
  "asked": [
1213
1324
  "dpd",
1325
+ "dpd-de",
1214
1326
  "seur",
1215
1327
  "brt",
1216
- "relais-colis",
1217
- "ciblex"
1328
+ "relais-colis"
1218
1329
  ]
1219
1330
  },
1220
1331
  {
@@ -1516,10 +1627,10 @@
1516
1627
  ],
1517
1628
  "asked": [
1518
1629
  "dpd",
1630
+ "dpd-de",
1519
1631
  "seur",
1520
1632
  "brt",
1521
- "hermes-de",
1522
- "relais-colis"
1633
+ "hermes-de"
1523
1634
  ]
1524
1635
  },
1525
1636
  {
@@ -1792,10 +1903,10 @@
1792
1903
  ],
1793
1904
  "asked": [
1794
1905
  "dpd",
1906
+ "dpd-de",
1795
1907
  "seur",
1796
1908
  "brt",
1797
- "relais-colis",
1798
- "ciblex"
1909
+ "relais-colis"
1799
1910
  ]
1800
1911
  },
1801
1912
  {
@@ -1974,10 +2085,10 @@
1974
2085
  ],
1975
2086
  "asked": [
1976
2087
  "dpd",
2088
+ "dpd-de",
1977
2089
  "seur",
1978
2090
  "brt",
1979
- "relais-colis",
1980
- "ciblex"
2091
+ "relais-colis"
1981
2092
  ]
1982
2093
  },
1983
2094
  {
@@ -1999,10 +2110,10 @@
1999
2110
  ],
2000
2111
  "asked": [
2001
2112
  "dpd",
2113
+ "dpd-de",
2002
2114
  "seur",
2003
2115
  "brt",
2004
- "relais-colis",
2005
- "ciblex"
2116
+ "relais-colis"
2006
2117
  ]
2007
2118
  },
2008
2119
  {
@@ -2024,10 +2135,10 @@
2024
2135
  ],
2025
2136
  "asked": [
2026
2137
  "dpd",
2138
+ "dpd-de",
2027
2139
  "seur",
2028
2140
  "brt",
2029
- "relais-colis",
2030
- "ciblex"
2141
+ "relais-colis"
2031
2142
  ]
2032
2143
  },
2033
2144
  {
@@ -3115,10 +3226,10 @@
3115
3226
  ],
3116
3227
  "asked": [
3117
3228
  "dpd",
3229
+ "dpd-de",
3118
3230
  "seur",
3119
3231
  "brt",
3120
- "relais-colis",
3121
- "ciblex"
3232
+ "relais-colis"
3122
3233
  ]
3123
3234
  },
3124
3235
  {
@@ -3140,10 +3251,10 @@
3140
3251
  ],
3141
3252
  "asked": [
3142
3253
  "dpd",
3254
+ "dpd-de",
3143
3255
  "seur",
3144
3256
  "brt",
3145
- "hermes-de",
3146
- "relais-colis"
3257
+ "hermes-de"
3147
3258
  ]
3148
3259
  },
3149
3260
  {
@@ -3165,10 +3276,10 @@
3165
3276
  ],
3166
3277
  "asked": [
3167
3278
  "dpd",
3279
+ "dpd-de",
3168
3280
  "seur",
3169
3281
  "brt",
3170
- "hermes-de",
3171
- "relais-colis"
3282
+ "hermes-de"
3172
3283
  ]
3173
3284
  },
3174
3285
  {
@@ -3221,10 +3332,10 @@
3221
3332
  ],
3222
3333
  "asked": [
3223
3334
  "dpd",
3335
+ "dpd-de",
3224
3336
  "seur",
3225
3337
  "brt",
3226
- "relais-colis",
3227
- "ciblex"
3338
+ "relais-colis"
3228
3339
  ]
3229
3340
  },
3230
3341
  {
@@ -3562,10 +3673,10 @@
3562
3673
  ],
3563
3674
  "asked": [
3564
3675
  "dpd",
3676
+ "dpd-de",
3565
3677
  "seur",
3566
3678
  "brt",
3567
- "hermes-de",
3568
- "relais-colis"
3679
+ "hermes-de"
3569
3680
  ]
3570
3681
  },
3571
3682
  {
@@ -3651,10 +3762,10 @@
3651
3762
  ],
3652
3763
  "asked": [
3653
3764
  "dpd",
3765
+ "dpd-de",
3654
3766
  "seur",
3655
3767
  "brt",
3656
- "relais-colis",
3657
- "ciblex"
3768
+ "relais-colis"
3658
3769
  ]
3659
3770
  },
3660
3771
  {
@@ -3674,10 +3785,10 @@
3674
3785
  ],
3675
3786
  "asked": [
3676
3787
  "dpd",
3788
+ "dpd-de",
3677
3789
  "seur",
3678
3790
  "brt",
3679
- "relais-colis",
3680
- "ciblex"
3791
+ "relais-colis"
3681
3792
  ]
3682
3793
  },
3683
3794
  {
@@ -3737,10 +3848,10 @@
3737
3848
  ],
3738
3849
  "asked": [
3739
3850
  "dpd",
3851
+ "dpd-de",
3740
3852
  "seur",
3741
3853
  "brt",
3742
- "relais-colis",
3743
- "ciblex"
3854
+ "relais-colis"
3744
3855
  ]
3745
3856
  },
3746
3857
  {
@@ -4154,10 +4265,10 @@
4154
4265
  ],
4155
4266
  "asked": [
4156
4267
  "dpd",
4268
+ "dpd-de",
4157
4269
  "seur",
4158
4270
  "brt",
4159
- "relais-colis",
4160
- "ciblex"
4271
+ "relais-colis"
4161
4272
  ]
4162
4273
  },
4163
4274
  {
@@ -4177,10 +4288,10 @@
4177
4288
  ],
4178
4289
  "asked": [
4179
4290
  "dpd",
4291
+ "dpd-de",
4180
4292
  "seur",
4181
4293
  "brt",
4182
- "relais-colis",
4183
- "ciblex"
4294
+ "relais-colis"
4184
4295
  ]
4185
4296
  },
4186
4297
  {
@@ -4326,10 +4437,10 @@
4326
4437
  ],
4327
4438
  "asked": [
4328
4439
  "dpd",
4440
+ "dpd-de",
4329
4441
  "seur",
4330
4442
  "brt",
4331
- "hermes-de",
4332
- "relais-colis"
4443
+ "hermes-de"
4333
4444
  ]
4334
4445
  },
4335
4446
  {
@@ -4349,10 +4460,10 @@
4349
4460
  ],
4350
4461
  "asked": [
4351
4462
  "dpd",
4463
+ "dpd-de",
4352
4464
  "seur",
4353
4465
  "brt",
4354
- "relais-colis",
4355
- "ciblex"
4466
+ "relais-colis"
4356
4467
  ]
4357
4468
  },
4358
4469
  {
@@ -4564,10 +4675,10 @@
4564
4675
  ],
4565
4676
  "asked": [
4566
4677
  "dpd",
4678
+ "dpd-de",
4567
4679
  "seur",
4568
4680
  "brt",
4569
- "hermes-de",
4570
- "relais-colis"
4681
+ "hermes-de"
4571
4682
  ]
4572
4683
  },
4573
4684
  {
@@ -5002,10 +5113,10 @@
5002
5113
  ],
5003
5114
  "asked": [
5004
5115
  "dpd",
5116
+ "dpd-de",
5005
5117
  "seur",
5006
5118
  "brt",
5007
- "hermes-de",
5008
- "relais-colis"
5119
+ "hermes-de"
5009
5120
  ]
5010
5121
  },
5011
5122
  {
@@ -5266,10 +5377,10 @@
5266
5377
  ],
5267
5378
  "asked": [
5268
5379
  "dpd",
5380
+ "dpd-de",
5269
5381
  "seur",
5270
5382
  "brt",
5271
- "relais-colis",
5272
- "ciblex"
5383
+ "relais-colis"
5273
5384
  ]
5274
5385
  },
5275
5386
  {
@@ -5289,10 +5400,10 @@
5289
5400
  ],
5290
5401
  "asked": [
5291
5402
  "dpd",
5403
+ "dpd-de",
5292
5404
  "seur",
5293
5405
  "brt",
5294
- "relais-colis",
5295
- "ciblex"
5406
+ "relais-colis"
5296
5407
  ]
5297
5408
  },
5298
5409
  {
@@ -5346,10 +5457,10 @@
5346
5457
  ],
5347
5458
  "asked": [
5348
5459
  "dpd",
5460
+ "dpd-de",
5349
5461
  "seur",
5350
5462
  "brt",
5351
- "relais-colis",
5352
- "ciblex"
5463
+ "relais-colis"
5353
5464
  ]
5354
5465
  },
5355
5466
  {
@@ -5429,10 +5540,10 @@
5429
5540
  ],
5430
5541
  "asked": [
5431
5542
  "dpd",
5543
+ "dpd-de",
5432
5544
  "seur",
5433
5545
  "brt",
5434
- "relais-colis",
5435
- "ciblex"
5546
+ "relais-colis"
5436
5547
  ]
5437
5548
  },
5438
5549
  {
@@ -5453,10 +5564,10 @@
5453
5564
  ],
5454
5565
  "asked": [
5455
5566
  "dpd",
5567
+ "dpd-de",
5456
5568
  "seur",
5457
5569
  "brt",
5458
- "relais-colis",
5459
- "ciblex"
5570
+ "relais-colis"
5460
5571
  ]
5461
5572
  },
5462
5573
  {
@@ -5507,10 +5618,10 @@
5507
5618
  ],
5508
5619
  "asked": [
5509
5620
  "dpd",
5621
+ "dpd-de",
5510
5622
  "seur",
5511
5623
  "brt",
5512
- "relais-colis",
5513
- "ciblex"
5624
+ "relais-colis"
5514
5625
  ]
5515
5626
  },
5516
5627
  {
@@ -5530,10 +5641,10 @@
5530
5641
  ],
5531
5642
  "asked": [
5532
5643
  "dpd",
5644
+ "dpd-de",
5533
5645
  "seur",
5534
5646
  "brt",
5535
- "relais-colis",
5536
- "ciblex"
5647
+ "relais-colis"
5537
5648
  ]
5538
5649
  },
5539
5650
  {
@@ -5553,10 +5664,10 @@
5553
5664
  ],
5554
5665
  "asked": [
5555
5666
  "dpd",
5667
+ "dpd-de",
5556
5668
  "seur",
5557
5669
  "brt",
5558
- "relais-colis",
5559
- "ciblex"
5670
+ "relais-colis"
5560
5671
  ]
5561
5672
  },
5562
5673
  {
@@ -5697,10 +5808,10 @@
5697
5808
  ],
5698
5809
  "asked": [
5699
5810
  "dpd",
5811
+ "dpd-de",
5700
5812
  "seur",
5701
5813
  "brt",
5702
- "relais-colis",
5703
- "ciblex"
5814
+ "relais-colis"
5704
5815
  ]
5705
5816
  },
5706
5817
  {
@@ -5844,10 +5955,10 @@
5844
5955
  ],
5845
5956
  "asked": [
5846
5957
  "dpd",
5958
+ "dpd-de",
5847
5959
  "seur",
5848
5960
  "brt",
5849
- "relais-colis",
5850
- "ciblex"
5961
+ "relais-colis"
5851
5962
  ]
5852
5963
  },
5853
5964
  {
@@ -6959,10 +7070,10 @@
6959
7070
  ],
6960
7071
  "asked": [
6961
7072
  "dpd",
7073
+ "dpd-de",
6962
7074
  "seur",
6963
7075
  "brt",
6964
- "relais-colis",
6965
- "ciblex"
7076
+ "relais-colis"
6966
7077
  ]
6967
7078
  },
6968
7079
  {
@@ -7356,10 +7467,10 @@
7356
7467
  ],
7357
7468
  "asked": [
7358
7469
  "dpd",
7470
+ "dpd-de",
7359
7471
  "seur",
7360
7472
  "brt",
7361
- "relais-colis",
7362
- "ciblex"
7473
+ "relais-colis"
7363
7474
  ]
7364
7475
  },
7365
7476
  {
@@ -3441,7 +3441,8 @@ var ce = {
3441
3441
  autoComplete: "postal-code",
3442
3442
  help: "Optional. DPD uses it to unlock verified scans and the delivery window.",
3443
3443
  validator: "germanyPostcode"
3444
- }]
3444
+ }],
3445
+ recognitionRank: 65
3445
3446
  },
3446
3447
  canaryUrl: "https://tracking.dpd.de/status/en_US/parcel/",
3447
3448
  trackingUrlTemplate: "https://tracking.dpd.de/status/en_US/parcel/{trackingNumber}",
@@ -8224,6 +8225,7 @@ var ts = {
8224
8225
  "dhl-ecommerce-uk": 17,
8225
8226
  "dhl-express": 75,
8226
8227
  dpd: 70,
8228
+ "dpd-de": 65,
8227
8229
  "four-px": 13,
8228
8230
  "gls-ch": 60,
8229
8231
  "gls-de": 50,
@@ -39,11 +39,12 @@ export interface DPDTrackerOptions {
39
39
  app?: DPDAppTier;
40
40
  }
41
41
  export interface DPDAppTier {
42
- /** `leads` when the guest tier is still to come and needs time left. */
42
+ /** `leads` when the guest tier is still to come and needs time left. `postcode` is empty when none was given. */
43
43
  run(number: string, context: {
44
44
  signal: AbortSignal;
45
45
  timeoutMs: number;
46
46
  leads: boolean;
47
+ postcode: string;
47
48
  }): Promise<CarrierResult>;
48
49
  /** The failures of either tier the other may answer after. */
49
50
  recovers(error: unknown): boolean;
@@ -65,9 +66,10 @@ export declare class DPDTracker {
65
66
  private browserService;
66
67
  fetch(raw: string, postcode?: string, context?: TrackingContext): Promise<CarrierResult>;
67
68
  /**
68
- * Whether the guest reply establishes Swiss activity for a 14-digit number.
69
- * The group-wide identity match alone must not promote another country's
70
- * parcel to the Swiss carrier id. Missing country evidence is inconclusive.
69
+ * Whether the guest reply establishes activity in this tracker's country for
70
+ * a 14-digit number. The group-wide identity match alone must not promote
71
+ * another country's parcel to this carrier id. Missing country evidence is
72
+ * inconclusive.
71
73
  * Guest API only, never the page tier. A positive not-found (404) and a
72
74
  * details lookup DPD refuses without the postcode (400, seen for old
73
75
  * parcels) are false; any other failure, a 400 from a token step included,
@@ -558,7 +558,9 @@ export class DPDTracker {
558
558
  const appStep = (leads) => ({
559
559
  id: 'app',
560
560
  ...(leads ? {} : { recovers: appRecovers }),
561
- run: ({ signal, remainingMs }) => app.run(trackingNumber, { signal, timeoutMs: remainingMs, leads }),
561
+ run: ({ signal, remainingMs }) => app.run(trackingNumber, {
562
+ signal, timeoutMs: remainingMs, leads, postcode: resolvedPostcode,
563
+ }),
562
564
  });
563
565
  const result = await runSteps({
564
566
  carrier, budgetMs: lookup.budgetMs, signal: lookup.signal, recorder: this.recorder,
@@ -589,9 +591,10 @@ export class DPDTracker {
589
591
  return result;
590
592
  }
591
593
  /**
592
- * Whether the guest reply establishes Swiss activity for a 14-digit number.
593
- * The group-wide identity match alone must not promote another country's
594
- * parcel to the Swiss carrier id. Missing country evidence is inconclusive.
594
+ * Whether the guest reply establishes activity in this tracker's country for
595
+ * a 14-digit number. The group-wide identity match alone must not promote
596
+ * another country's parcel to this carrier id. Missing country evidence is
597
+ * inconclusive.
595
598
  * Guest API only, never the page tier. A positive not-found (404) and a
596
599
  * details lookup DPD refuses without the postcode (400, seen for old
597
600
  * parcels) are false; any other failure, a 400 from a token step included,
@@ -601,17 +604,20 @@ export class DPDTracker {
601
604
  const trackingNumber = dpdParcelNumber(raw);
602
605
  if (!trackingNumber)
603
606
  return false;
604
- const lookup = lookupBudget(context, this.budgetMs, 'dpd');
607
+ const lookup = lookupBudget(context, this.budgetMs, this.country === 'DE' ? 'dpd-de' : 'dpd');
605
608
  try {
606
609
  const payload = await this.detailsWithFreshToken(trackingNumber, undefined, lookup);
607
610
  if (clean(payload.parcelNumber ?? payload.shipmentId) !== trackingNumber)
608
611
  return false;
609
612
  const current = isRecord(payload.status) ? payload.status : {};
610
613
  const country = known(current.countryCode).toUpperCase();
611
- if (/^[A-Z]{2}$/.test(country) && country !== 'CH')
614
+ if (/^[A-Z]{2}$/.test(country) && country !== this.country)
612
615
  return false;
613
- if (country !== 'CH')
614
- throw new IndeterminateError('DPD Switzerland', 'DPD returned no Swiss country evidence');
616
+ if (country !== this.country) {
617
+ throw this.country === 'DE'
618
+ ? new IndeterminateError('DPD Germany', 'DPD returned no German country evidence')
619
+ : new IndeterminateError('DPD Switzerland', 'DPD returned no Swiss country evidence');
620
+ }
615
621
  requireGuestActivity(payload, parseDPDTrackingApi(payload, trackingNumber));
616
622
  return true;
617
623
  }
@@ -13,8 +13,8 @@ export const adapter = (environment) => {
13
13
  recorder: environment.recorder,
14
14
  userAgent: environment.userAgent,
15
15
  app: {
16
- run: (number, { signal, timeoutMs, leads }) => app.track(number, {
17
- signal, timeoutMs, sessionWaitMs: leads ? Math.max(0, timeoutMs - GUEST_RESERVE_MS) : timeoutMs,
16
+ run: (number, { signal, timeoutMs, leads, postcode }) => app.track(number, {
17
+ signal, timeoutMs, postcode, sessionWaitMs: leads ? Math.max(0, timeoutMs - GUEST_RESERVE_MS) : timeoutMs,
18
18
  }),
19
19
  // Each service has its own host, limits and outages, so either answers when
20
20
  // the other fails. Both read the same parcel: neither can place another
@@ -26,5 +26,7 @@ export const adapter = (environment) => {
26
26
  return {
27
27
  id: 'dpd-de', recordsSteps: true, steps: ['app', 'direct'],
28
28
  track: (input, context) => tracker.fetch(input.number, input.postcode ?? '', context),
29
+ // The guest API's current country, as for Switzerland: the app's session takes too long to open.
30
+ recognize: async (number, context) => ({ known: await tracker.recognizes(number, context) }),
29
31
  };
30
32
  };
@@ -30,11 +30,16 @@ export declare class DpdDeAppClient {
30
30
  userAgent?: string;
31
31
  now?: () => number;
32
32
  });
33
- /** `sessionWaitMs` bounds the wait for a session still opening; the whole `timeoutMs` by default. */
33
+ /**
34
+ * `sessionWaitMs` bounds the wait for a session still opening; the whole `timeoutMs` by default.
35
+ * DPD checks a `postcode` against the recipient's: a rejected one gets one lookup without it, and
36
+ * the result says which, as on the guest API.
37
+ */
34
38
  track(number: string, options: {
35
39
  signal: AbortSignal;
36
40
  timeoutMs: number;
37
41
  sessionWaitMs?: number;
42
+ postcode?: string;
38
43
  }): Promise<CarrierResult>;
39
44
  /**
40
45
  * One session for every lookup. The opening runs while a lookup that asked for it is still
@@ -56,6 +56,13 @@ const elements = (fields) => Object.entries(fields)
56
56
  .map(([name, value]) => `<${name}>${typeof value === 'string' ? escaped(value) : elements(value)}</${name}>`).join('');
57
57
  class SessionExpired extends Error {
58
58
  }
59
+ class PostcodeRejected extends Error {
60
+ }
61
+ /** A two-letter country, where the service puts a placeholder three-letter one for an unknown depot. */
62
+ function countryOf(node) {
63
+ const country = node ? scalar(node, 'Country', 8) : '';
64
+ return /^[A-Z]{2}$/.test(country) ? country : '';
65
+ }
59
66
  function statusOf(stage) {
60
67
  if (stage === 'delivered' || stage === 'out_for_delivery' || stage === 'exception')
61
68
  return stage;
@@ -68,16 +75,12 @@ export function parseDpdDeApp(data, scans, number) {
68
75
  const tracking = one(data, 'TrackingData');
69
76
  if (scalar(tracking, 'ParcelNo', 40) !== number)
70
77
  throw new SchemaError('DPD Germany', 'DPD Germany returned a different parcel');
71
- // The service also answers for parcels delivered elsewhere in the group.
72
- const destination = scalar(one(tracking, 'ShipAddress'), 'Country', 8);
73
- if (/^[A-Z]{2}$/.test(destination) && destination !== 'DE') {
74
- throw new IndeterminateError('DPD Germany', 'DPD returned activity in another country', { reason: DPD_DE_OTHER_COUNTRY });
75
- }
76
78
  const rows = children(one(scans, 'TrackingScanList'), 'TrackingScan');
77
79
  if (rows.length > 500)
78
80
  invalid();
79
81
  const events = [];
80
82
  const seen = new Set();
83
+ let placed = '';
81
84
  for (const row of rows) {
82
85
  const wording = scalar(row, 'StatusText', 500);
83
86
  const day = scalar(row, 'ScanDate', 16);
@@ -88,6 +91,8 @@ export function parseDpdDeApp(data, scans, number) {
88
91
  if (/^Your parcel is estimated to be delivered\b/i.test(wording))
89
92
  continue;
90
93
  const place = /^(.{1,120}) \(([A-Z]{2})\)$/.exec(scalar(row, 'Location'));
94
+ if (place)
95
+ placed = place[2];
91
96
  // German facilities keep German civil time; a clock elsewhere has no established zone.
92
97
  const clock = zonedTime(`${day} ${time}`, 'dd.MM.yyyy HH:mm', place?.[2] === 'DE' ? 'Europe/Berlin' : 'UTC');
93
98
  if (!clock)
@@ -108,6 +113,14 @@ export function parseDpdDeApp(data, scans, number) {
108
113
  }
109
114
  if (!events.length)
110
115
  throw new IndeterminateError('DPD Germany', 'DPD Germany returned no parcel scans');
116
+ // The service also answers for parcels elsewhere in the group, and its recipient address reads
117
+ // Germany for them too. Where the parcel is now, as the guest API's current country: the newest
118
+ // placed scan, else the depot of the last status.
119
+ const depot = children(tracking, 'LastStatusInfo').flatMap(info => children(info, 'DepotData')).flatMap(data => children(data, 'Address'))[0];
120
+ const country = placed || countryOf(depot);
121
+ if (country && country !== 'DE') {
122
+ throw new IndeterminateError('DPD Germany', 'DPD returned activity in another country', { reason: DPD_DE_OTHER_COUNTRY });
123
+ }
111
124
  events.reverse();
112
125
  const current = events[0];
113
126
  const rail = DPD_DE_APP_RAIL[scalar(one(tracking, 'LastStatusInfo'), 'StatusID', 64)];
@@ -140,20 +153,46 @@ export class DpdDeAppClient {
140
153
  this.#userAgent = userAgentOf(options.userAgent);
141
154
  this.#now = options.now ?? Date.now;
142
155
  }
143
- /** `sessionWaitMs` bounds the wait for a session still opening; the whole `timeoutMs` by default. */
156
+ /**
157
+ * `sessionWaitMs` bounds the wait for a session still opening; the whole `timeoutMs` by default.
158
+ * DPD checks a `postcode` against the recipient's: a rejected one gets one lookup without it, and
159
+ * the result says which, as on the guest API.
160
+ */
144
161
  async track(number, options) {
145
162
  if (!/^\d{14}$/.test(number))
146
163
  throw new TypeError('DPD Germany app tracking takes 14 digits');
164
+ const postcode = options.postcode ?? '';
165
+ if (postcode && !/^\d{5}$/.test(postcode))
166
+ throw new TypeError('DPD Germany app tracking takes a 5-digit postcode');
147
167
  const deadline = performance.now() + options.timeoutMs;
148
168
  const left = () => Math.max(1, Math.floor(deadline - performance.now()));
149
169
  try {
150
170
  for (let attempt = 0;; attempt += 1) {
151
171
  const session = await this.session(options.signal, Math.min(left(), options.sessionWaitMs ?? Infinity));
172
+ // Read-only: the parcel is neither added to the session nor redirected.
173
+ const tracking = (zip) => this.call('getTrackingData', { SessionToken: session, ParcelNo: number, DeliveryZipCode: zip,
174
+ UpdateNewDeliveryData: 'false', addParcelIfNoTrackingdataAvailable: 'false', ParcelFlowTypeID: 'receiving' }, options.signal, left());
152
175
  try {
153
- const data = await this.call('getTrackingData', { SessionToken: session, ParcelNo: number, DeliveryZipCode: '',
154
- UpdateNewDeliveryData: 'false', addParcelIfNoTrackingdataAvailable: 'false', ParcelFlowTypeID: 'receiving' }, options.signal, left());
155
- const scans = await this.call('getTrackingScanList', { SessionToken: session, ParcelNo: number, DeliveryZipCode: '' }, options.signal, left());
156
- return parseDpdDeApp(data, scans, number);
176
+ let data;
177
+ let verified;
178
+ try {
179
+ data = await tracking(postcode);
180
+ if (postcode)
181
+ verified = ['DeliveryZipCode_isValid', 'Owner'].includes(scalar(one(data, 'TrackingData'), 'DataViewStatus', 40));
182
+ }
183
+ catch (error) {
184
+ // Also the answer for an unknown parcel, which the lookup without it then reports.
185
+ if (!(error instanceof PostcodeRejected))
186
+ throw error;
187
+ data = await tracking('');
188
+ verified = false;
189
+ }
190
+ const scans = await this.call('getTrackingScanList', { SessionToken: session, ParcelNo: number,
191
+ DeliveryZipCode: verified ? postcode : '' }, options.signal, left());
192
+ const result = parseDpdDeApp(data, scans, number);
193
+ if (verified !== undefined)
194
+ result.dpd_postcode_verified = verified;
195
+ return result;
157
196
  }
158
197
  catch (error) {
159
198
  if (!(error instanceof SessionExpired) || attempt)
@@ -255,6 +294,8 @@ export class DpdDeAppClient {
255
294
  }
256
295
  if (codes.includes('ERROR_SESSION_NOT_VALID'))
257
296
  throw new SessionExpired();
297
+ if (codes.includes('ERROR_TRACKING_DELIVERYZIPCODE_NOT_VALID'))
298
+ throw new PostcodeRejected();
258
299
  // "No tracking data" also answers for parcels the scan list still knows: it proves no absence.
259
300
  if (scalar(result, 'Ack', 8) !== 'true')
260
301
  throw new IndeterminateError('DPD Germany', 'DPD Germany returned no confirmed parcel');
@@ -21,6 +21,9 @@
21
21
  "app",
22
22
  "direct"
23
23
  ],
24
+ "recognition": {
25
+ "rank": 65
26
+ },
24
27
  "requirements": [
25
28
  {
26
29
  "field": "postcode",
@@ -22,6 +22,32 @@
22
22
  },
23
23
  "note": "Publicly reported shipment number; the attribution was not independently verified. The source attributes it to TIPSA. CTT Express numbers can share the 000010 prefix, so it stays ambiguous and recognition asks TIPSA first."
24
24
  },
25
+ {
26
+ "number": "0000100000104002537106",
27
+ "role": "shipment",
28
+ "evidence": "public_shipment_report",
29
+ "source": {
30
+ "url": "https://web.archive.org/web/20260420103951/https://aplicaciones.tip-sa.com/cliente/datos_env.php?id=0000100000104002537106",
31
+ "date": "2026-04-20",
32
+ "dateKind": "capture",
33
+ "access": "full_page"
34
+ },
35
+ "context": {
36
+ "assessment": "confirmed"
37
+ },
38
+ "expect": {
39
+ "carrier": "unknown",
40
+ "confidence": "low",
41
+ "candidates": [
42
+ "tipsa",
43
+ "austrian-post",
44
+ "ctt-express",
45
+ "usps",
46
+ "estafeta"
47
+ ]
48
+ },
49
+ "note": "A TIPSA tracking link archived by the Wayback Machine. TIPSA's tracker returns its history. CTT Express numbers can share the 000010 prefix, so it stays ambiguous and recognition asks TIPSA first."
50
+ },
25
51
  {
26
52
  "number": "0280010280017024953690",
27
53
  "role": "shipment",
@@ -42,6 +68,48 @@
42
68
  },
43
69
  "note": "Publicly reported shipment number; the attribution was not independently verified. Portuguese shipment."
44
70
  },
71
+ {
72
+ "number": "0280400280406520139001",
73
+ "role": "shipment",
74
+ "evidence": "open_source_example",
75
+ "source": {
76
+ "url": "https://github.com/CarlosAdrianM/NestoAPI/blob/88934ca46092ad77d1b1dd66b6b60c9811659b21/NestoAPI.Tests/Models/UltimoEnvioClienteDTOTests.cs",
77
+ "date": "2026-06-22",
78
+ "dateKind": "publication",
79
+ "access": "full_page"
80
+ },
81
+ "expect": {
82
+ "carrier": "unknown",
83
+ "confidence": "low",
84
+ "candidates": [
85
+ "tipsa",
86
+ "austrian-post",
87
+ "usps",
88
+ "estafeta"
89
+ ]
90
+ },
91
+ "note": "Expected tracking link in an open-source TIPSA integration's test, built from the sender's fixed agency codes and a waybill. TIPSA's tracker does not find it, so the waybill may be invented."
92
+ },
93
+ {
94
+ "number": "0410230410230001262324",
95
+ "role": "shipment",
96
+ "evidence": "public_shipment_report",
97
+ "source": {
98
+ "url": "https://portaldaqueixa.com/brands/tipsa/complaints/tipsa-nao-entrega-1-146881425",
99
+ "date": "2025-12-24"
100
+ },
101
+ "expect": {
102
+ "carrier": "unknown",
103
+ "confidence": "low",
104
+ "candidates": [
105
+ "tipsa",
106
+ "austrian-post",
107
+ "usps",
108
+ "estafeta"
109
+ ]
110
+ },
111
+ "note": "Publicly reported shipment number; the attribution was not independently verified. Portuguese shipment; TIPSA's tracker no longer finds it."
112
+ },
45
113
  {
46
114
  "number": "0460040240008103140542",
47
115
  "role": "shipment",
@@ -62,6 +130,66 @@
62
130
  },
63
131
  "note": "Publicly reported shipment number; the attribution was not independently verified. Its two agency codes differ."
64
132
  },
133
+ {
134
+ "number": "0460040460048104747942",
135
+ "role": "shipment",
136
+ "evidence": "public_shipment_report",
137
+ "source": {
138
+ "url": "https://portaldaqueixa.com/brands/tipsa/complaints/tipsa-produto-nao-foi-entregue-142779725",
139
+ "date": "2025-10-23"
140
+ },
141
+ "expect": {
142
+ "carrier": "unknown",
143
+ "confidence": "low",
144
+ "candidates": [
145
+ "tipsa",
146
+ "austrian-post",
147
+ "usps",
148
+ "estafeta"
149
+ ]
150
+ },
151
+ "note": "Publicly reported shipment number; the attribution was not independently verified. Portuguese shipment; TIPSA's tracker no longer finds it."
152
+ },
153
+ {
154
+ "number": "0460040460048104754422",
155
+ "role": "shipment",
156
+ "evidence": "public_shipment_report",
157
+ "source": {
158
+ "url": "https://portaldaqueixa.com/brands/tipsa/complaints/tipsa-entrega-nao-efetuada-e-falsas-tentativas-registadas-pela-tipsa-142905725",
159
+ "date": "2025-10-26"
160
+ },
161
+ "expect": {
162
+ "carrier": "unknown",
163
+ "confidence": "low",
164
+ "candidates": [
165
+ "tipsa",
166
+ "austrian-post",
167
+ "usps",
168
+ "estafeta"
169
+ ]
170
+ },
171
+ "note": "Publicly reported shipment number; the attribution was not independently verified. Portuguese shipment; TIPSA's tracker no longer finds it."
172
+ },
173
+ {
174
+ "number": "0460040460048104791791",
175
+ "role": "shipment",
176
+ "evidence": "public_shipment_report",
177
+ "source": {
178
+ "url": "https://portaldaqueixa.com/brands/tipsa/complaints/tipsa-encomenda-por-entregar-desde-3010-tipsa-144629825",
179
+ "date": "2025-11-27"
180
+ },
181
+ "expect": {
182
+ "carrier": "unknown",
183
+ "confidence": "low",
184
+ "candidates": [
185
+ "tipsa",
186
+ "austrian-post",
187
+ "usps",
188
+ "estafeta"
189
+ ]
190
+ },
191
+ "note": "Publicly reported shipment number; the attribution was not independently verified. Portuguese shipment; TIPSA's tracker no longer finds it."
192
+ },
65
193
  {
66
194
  "number": "0460040460048105123537",
67
195
  "role": "shipment",
@@ -4431,7 +4431,8 @@
4431
4431
  "help": "Optional. DPD uses it to unlock verified scans and the delivery window.",
4432
4432
  "validator": "germanyPostcode"
4433
4433
  }
4434
- ]
4434
+ ],
4435
+ "recognitionRank": 65
4435
4436
  },
4436
4437
  "canaryUrl": "https://tracking.dpd.de/status/en_US/parcel/",
4437
4438
  "trackingUrlTemplate": "https://tracking.dpd.de/status/en_US/parcel/{trackingNumber}",
@@ -2980,6 +2980,7 @@ export declare const CARRIER_CATALOG: {
2980
2980
  readonly help: "Optional. DPD uses it to unlock verified scans and the delivery window.";
2981
2981
  readonly validator: "germanyPostcode";
2982
2982
  }];
2983
+ readonly recognitionRank: 65;
2983
2984
  };
2984
2985
  readonly canaryUrl: "https://tracking.dpd.de/status/en_US/parcel/";
2985
2986
  readonly trackingUrlTemplate: "https://tracking.dpd.de/status/en_US/parcel/{trackingNumber}";
@@ -4432,7 +4432,8 @@ export const CARRIER_CATALOG = {
4432
4432
  "help": "Optional. DPD uses it to unlock verified scans and the delivery window.",
4433
4433
  "validator": "germanyPostcode"
4434
4434
  }
4435
- ]
4435
+ ],
4436
+ "recognitionRank": 65
4436
4437
  },
4437
4438
  "canaryUrl": "https://tracking.dpd.de/status/en_US/parcel/",
4438
4439
  "trackingUrlTemplate": "https://tracking.dpd.de/status/en_US/parcel/{trackingNumber}",
@@ -20,6 +20,7 @@ export const CARRIER_RECOGNITION_RANKS = {
20
20
  "dhl-ecommerce-uk": 17,
21
21
  "dhl-express": 75,
22
22
  "dpd": 70,
23
+ "dpd-de": 65,
23
24
  "four-px": 13,
24
25
  "gls-ch": 60,
25
26
  "gls-de": 50,
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Universal Parcel Scraper",
5
- "version": "0.36.0"
5
+ "version": "0.37.0"
6
6
  },
7
7
  "paths": {
8
8
  "/health": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-parcel-scraper",
3
- "version": "0.36.0",
3
+ "version": "0.37.0-main.390",
4
4
  "description": "Self-hosted parcel tracking: carrier detection, dedicated scrapers and optional universal providers.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",