Developer notes
How GLS Shipping Labels & Parcel Shops (kv_gls) talks to GLS, with the source of every endpoint, field, status and label format. Items that could not be verified without a GLS test account are flagged Unverified.
Which GLS API and why
Section titled “Which GLS API and why”GLS runs country-specific APIs. Two were evaluated (2026-11-07):
| API | Countries | Public documentation | Test system | Decision |
|---|---|---|---|---|
| MyGLS API (GLS Hungary, Czech Republic, Slovakia, Romania, Slovenia, Croatia) | 6 EU countries (+ Serbia, not used in 1.0) | Yes: “MyGLS API for system integration” ver. 25.12.11, api.mygls.hu/docs/MyGLS_API.pdf, sample clients mygls_api_20260331.zip | Yes: api.test.mygls.<country> (“testing and development”) |
Used |
| GLS Group developer portal, ShipIT-Farm API (dev-portal.gls-group.net) | DE, AT, BE, LU, DK, NL, PL, IT and others (by GLS country) | No: the portal lists no API documents to anonymous visitors (“API documentation is available within the Developer Portal. After logging in …”, portal FAQ) | Sandbox exists (third-party integrations name api-sandbox.gls-group.net) |
Not used in 1.0: nothing could be cited without an account; a second driver is planned once the owner registers |
Serbia (api.mygls.rs) is not offered in 1.0: its parcels need SenderIdentityCardNumber (“Only in Serbia! ID card number or PIB”, error 44), Content (error 48) and parcel properties, which this version does not collect, and the public point list has no RS.
The MyGLS API is the only GLS API whose full contract (endpoints, classes, error codes, statuses, label formats) is public and has an official test system, so every field below is cited from it.
Endpoints (MyGLS API, “Country domain API URLs” and the method chapters)
Section titled “Endpoints (MyGLS API, “Country domain API URLs” and the method chapters)”Base: https://api.mygls.<hr|cz|hu|ro|si|sk> (live) or https://api.test.mygls.<...> (test). REST format: /{service name}.svc/{format}/{methodName}, service ParcelService, format json, HTTP POST (method tables “Request / Method / URL examples”). The sample client sends Content-Type: application/json.
| Add-on action | Method | Document section | Notes |
|---|---|---|---|
| Create label, step 1 | PrepareLabels |
“PrepareLabels: Validates parcel data for labels and adds valid parcel data to database” | Returns ParcelInfoList[{ClientReference, ParcelId}], PrepareLabelsError[] |
| Create label, step 2; reprint | GetPrintedLabels |
“Generates parcel numbers and PDF document contains labels in byte array format” | ParcelIdList (max. 99), PrintPosition, ShowPrintDialog, TypeOfPrinter; returns Labels (byte[]), PrintDataInfoList, GetPrintedLabelsErrorList |
| Cancel | DeleteLabels |
“Set DELETED state for labels/parcels with specific database record ID” | ParcelIdList (max. 50); returns SuccessfullyDeletedList[{ParcelId, SubParcelIdList}], DeleteLabelsErrorList |
| Check at GLS (lost answer), Test connection | GetParcelList |
“Get parcel(s) information by date ranges” | PickupDateFrom/To, PrintDateFrom/To; returns PrintDataInfoList, GetParcelListErrors |
| Tracking | GetParcelStatuses |
“Get parcel statuses with/or without POD” | ParcelNumber, ReturnPOD (false), LanguageIsoCode left out (“Default: EN”) |
Not used: PrintLabels (prepare + print in one call; the two-step flow keeps the ParcelId before the number is generated, see Idempotency), GetPrintData, ModifyCOD, GetParcelListStatuses (its LanguageIsoCode is REQUIRED and EN is not in its list), MasterDataService (needs credentials; the public point list below covers the checkout).
Authentication (APIRequestBase)
Section titled “Authentication (APIRequestBase)”Every request body carries Username (the MyGLS e-mail) and Password: “Password in string representation must be encrypted with SHA512 algorithm to byte array”. The JSON form follows the official PHP sample: "[" . implode(',', unpack('C*', hash('sha512', $pwd, true))) . "]" (a JSON array of 64 integers, Client\Api::passwordBytes). ClientNumberList: “do not use” (not sent). WebshopEngine (“Webshop engine of the website”, REQUIRED, NOT EMPTY, change 17) is sent as CS-Cart on PrepareLabels.
A wrong user is answered with HTTP 401 in the sample client (if http_code == "401" die("Unauthorized")); Appendix A codes 14 (“User not exists”) and 27 (“User is not authorized to access to Client”) are treated the same way. Unverified: which of the two a wrong password produces on the test system.
Parcel object (Parcel, Address, Service classes)
Section titled “Parcel object (Parcel, Address, Service classes)”| Field | Value sent | Source |
|---|---|---|
ClientNumber |
GLS client number of the Connection page | “Unique client number provided by GLS company” |
ClientReference |
CS<order id>-<record id> (-R for returns), written to the database before the call |
“Client custom tag identifying parcel”, REQUIRED (error 33) |
Count |
1 to 99 (order page), 1 with INS | “maximum 99”, error 29, error 28 “The Count must be 1 because of the INS service” |
Content |
“Order |
“Parcel info printed on label” (error 34 when required by the user right) |
PickupDate |
today 23:59:59 in the shop’s time zone + offset, "\/Date(<ms>)\/" |
sample client "\/Date(".(strtotime("2019-12-14 23:59:59") * 1000).")\/" with date_default_timezone_set("Europe/Budapest"); end of day so the UTC instant stays on the same date |
PickupAddress |
sender of the Connection page (or company data) | “The address of place where courier pick up the shipment” |
DeliveryAddress |
customer; for returns the shop | “The address of destination place” |
FinalDeliveryAddress |
customer address on PSD parcels | change 30: “Backup delivery address (recipient’s own address) when using PSD service” Unverified: for PSD the add-on sends the customer address as DeliveryAddress too, as the official PHP sample does (PSD with a street address); the GLS Czech PSD guide asks own-label printers to put the point address there. To be confirmed on the test system. |
CODAmount, CODCurrency, CODReference |
order total in the COD currency, order id | Parcel class; error 8 / 1000 |
ServiceList |
PSD, FDS, FSS, SM2, INS, 24H, T09, T10, T12, SAT, TGS, SRS / PRS | Appendix B + E |
ParcelPropertyList |
[{Content, Weight, Length, Width, Height}] when enabled |
change 19, “Appendix E: Parcel property list examples”; PackageType is not sent (Unverified whether some accounts require it) |
Address: Name, Street, City, ZipCode, CountryIsoCode REQUIRED; HouseNumber “ONLY NUMBER” (change 6, error 23 “cannot be 0”), so the add-on splits “Váci út 12/b” into Street “Váci út”, HouseNumber “12”, HouseNumberInfo “/b” (Parcel::street). ContactName/Phone/Email are sent for PSD (mandatory there), when a service needs them (FDS e-mail, FSS / SM2 / SAT phone) or when “Send e-mail and phone number” is on.
Field lengths: the document publishes none. The add-on cuts by characters (mb_substr, never inside a UTF-8 sequence) to house caps (Name / Street 80, City 60, HouseNumberInfo 40, contact 60, Content 80, reference 40); these are NOT GLS limits. GLS answers an over-long or invalid field with error 13 “Parcel validation issue”, shown on the order page.
Service parameters (Appendix E): {"Code":"PSD","PSDParameter":{"StringValue":"2351-CSOMAGPONT"}}, {"Code":"FDS","FDSParameter":{"Value":"<e-mail>"}}, {"Code":"FSS","FSSParameter":{"Value":"<phone>"}} (error 30: not without FDS), {"Code":"SM2","SM2Parameter":{"Value":"<phone>"}}, {"Code":"INS","INSParameter":{"Value":<value>}}, {"Code":"SAT","Value":"<phone>"} (change 28), value-less 24H, T09, T10, T12, TGS, SRS, PRS. PSD “requires additional attributes … ContactName, ContactPhone, ContactEmail”. The GLS PSD guide says PSD “cannot be combined with other services, such as FDS … or FSS”: FDS / FSS are left out on point deliveries. Unverified: the GLS Czech guide states this for the CSV import; the API may accept the combination.
Return service: SRS (ShopReturnService) and PRS (Pick & Return Service, ContactPhone required per change 29). The document gives no further semantics: the merchant enables it only when the contract includes it. Unverified: whether a return parcel without SRS / PRS (plain swapped addresses) is accepted by every GLS country.
Label formats (TypeOfPrinter)
Section titled “Label formats (TypeOfPrinter)”“Possible values: A4_2x2, A4_4x1, Connect, Thermo, ThermoZPL, ShipItThermoPdf, ThermoZPL_300DPI, ShipItThermoZpl. Note: ThermoZPL uses 203 DPI (8 dpmm), ThermoZPL_300DPI uses 300 DPI” (GetPrintedLabels; changes 14, 18, 21, 22, 26). PrintPosition “Number of page quarter”, “ACCEPTED ONLY FOR A4-FORMAT”. Labels is “PDF document in byte array” (JSON: array of integers, sample implode(array_map('chr', $labels))). For the ZPL types the add-on stores the bytes as .zpl when they contain ^XA (Shipments::format). Unverified: the exact file type returned for Connect, Thermo and ShipItThermoPdf (treated as PDF when the bytes start with %PDF).
Identifiers and tracking
Section titled “Identifiers and tracking”ParcelId: “Label/Parcel database record ID” (PrepareLabels) = the id for GetPrintedLabels / DeleteLabels.- Reprint:
GetPrintedLabelsis called again with the sameParcelId. Unverified: the document also lists error 18 “Parcel label is already generated”; if MyGLS refuses a second print, the add-on (first print after a lost answer) takes the parcel number over fromGetParcelListand asks the merchant to print the label in MyGLS; a refused reprint shows the GLS message. ParcelNumber(PrintDataInfo: “Parcel number without check digit”) = the tracking number stored and sent toGetParcelStatuses(“ParcelNumber Long … REQUIRED”);ParcelNumberWithCheckdigitis kept as a second number.- Public tracking page:
https://gls-group.com/<country>/en/parcel-tracking/?match=<number>; the GLS page script reads thematchquery parameter (queryParams.get('match')on gls-group.com/HU/en/parcel-tracking/, checked 2026-11-07; page saved with the build notes). The core carrier link (customer order page, shipment e-mails) useshttps://gls-group.com/GROUP/en/parcel-tracking/?match=%swith a single%sthat CS-Cart fills; there is no[tracking_number]placeholder. Unverified: whether the page finds a parcel by the number without check digit as well as with it (GLS shows both on labels). - Statuses: Appendix G “GLS Status Codes” mapped in
Shipments::STATUS_MAP: created 51, 52, 83, 84; delivered 5, 58, 59 (“Parcelshop pickup”), 92, 93 (“Signature confirmed”); waiting at a point 54, 55, 56, 97; returned 23, 40; problem 11-20, 28-31, 33-36, 38, 39, 42-44, 57, 60, 62, 66, 68-75, 88, 89, 401-404, 420; every other code = in transit. Codes arrive as strings (“05”): compared as numbers. A delivered or returned parcel is never downgraded by a later answer.
Error codes (Appendix A)
Section titled “Error codes (Appendix A)”Errors come in the response lists (PrepareLabelsError, GetPrintedLabelsErrorList, DeleteLabelsErrorList, GetParcelListErrors, GetParcelStatusErrors) as ErrorInfo {ErrorCode, ErrorDescription, ClientReferenceList, ParcelIdList}. Client\Api::raise turns the first one into an exception (status 422, 401 for 14 / 27, 500 for 1001). Code 31 (“Same request sent 5 times within last 5 minutes”) is never retried.
Idempotency (no double parcels)
Section titled “Idempotency (no double parcels)”- Per-order MySQL lock (
Idempotency::lock('label-<order>')), an existing active shipment is returned instead. - A
pendingrow and the deterministicClientReferenceare written beforePrepareLabels. - The
ParcelIdis saved beforeGetPrintedLabels; a print failure leaves a “prepared” row that prints again safely. - No answer (timeout, 5xx, error 1001) = “result unknown”: never retried automatically. “Check at GLS” runs
GetParcelListby print date and by pickup date around the request and adopts the parcel with the sameClientReference; if nothing is found, the merchant confirms in MyGLS and closes the row. Return labels: lock,pendingmarker and-Rreference before the call,returnParcelIdsaved before printing; a dead or unanswered request becomes “unknown” and is never repeated (there is no automatic lookup for returns: the merchant checks MyGLS by the-Rreference and confirms). Print, cancel, Check at GLS and confirm share the per-order lock; every state change is an atomic claim (UPDATE ... WHERE status = <read state>) whose result is checked, and tracking answers never overwrite a state changed meanwhile. Cancelling a shipment also deletes its return parcel; a never printed (prepared) record that GLS refuses to delete (Appendix A 6) is closed in the store only. Multi-Vendor parent orders never get a label. Unverified: whetherGetParcelListby pickup date lists parcels that were prepared but not printed (the mock does; if MyGLS does not, Check at GLS reports “nothing found” and the merchant checks MyGLS by hand).
GLS points at the checkout
Section titled “GLS points at the checkout”Source: GLS Czech Republic, “How to implement ShopDeliveryService” (last update 28.07.2026): “The information on all collection points is available in an XML file … updated once a day in the night. https://ps-maps.gls-czech.com/getDropoffPoints.php?ctrcode=CZ”, for AT, BE, BG, CZ, DE, DK, ES, FI, FR, GR, HU, HR, IT, LU, NL, PL, PT, RO, SK, SI; “To order the service, you only need to know the ParcelShop ID” (attribute ID, e.g. 2351-CSOMAGPONT, the same form as the PSD sample of the MyGLS document). Attributes used: ID, Name, Address, CtrCode, ZipCode, CityName, IsCODHandler, IsParcelLocker, GeoLat, GeoLng, Openings/Openings@Day|OpenHours|MidBreak. The add-on downloads the list once a day per configured country (points_sync job), keeps it in ?:kv_gls_dropoff (a download with less than half of the stored points is not applied), searches it near a postal code or town (centre of the matching points, nearest first) and validates the customer’s choice against it on the server. The XML is parsed with LIBXML_NONET (no external entities).
Data model
Section titled “Data model”?:kv_gls_accounts (encrypted password, AES-256-GCM), ?:kv_gls_shipments (one row per label: parcel_id, client_ref, parcel_count, status W pending, A prepared, U unknown, C created, T transit, K at point, D delivered, P problem, R returned, X cancelled), ?:kv_gls_points (point chosen per order), ?:kv_gls_dropoff (GLS point list), ?:kv_gls_jobs, ?:kv_gls_log.
Hooks and extension points
Section titled “Hooks and extension points”PHP hooks: change_order_status_post (label at a status), get_order_info, place_order, place_suborders (Multi-Vendor), delete_order, template_document_order_context_init (point on invoices). Template hooks: checkout:shipping_method (picker), orders:totals_shipping (customer order page), orders:tabs_extra (admin tab), index:scripts / index:styles. Shipping service class Tygh\Shippings\Services\KvGls (rates are the merchant’s prices; MyGLS has no rate operation). Full page cache: the picker is only on the checkout (not cached); the order page block reads the order, no session data is printed in cacheable dispatches.
php81 tests/unit.php/php83 tests/unit.php: every request body checked againsttests/fixtures/official/contract.json(classes of the document and of the official C# contract), the official Java sample request, a slice of the real HU point list, error mapping, status mapping, payloads (home, PSD, COD, services, returns), defects checks.node tests/mock-server/server.js 21853: MyGLS mock that follows the documented contract (unknown fields refused, Appendix A validation, 401 on a wrong password) plus test controls.node tests/e2e/e2e.mjs(admin, storefront, labels, tracking, queue) andnode tests/e2e/vendor-smoke.mjs(Multi-Vendor vendor panel viavendor.php).
GLS, MyGLS and ParcelShop are trademarks of their owner. This add-on is independent software and is not affiliated with, endorsed by or certified by GLS.