Setting Up the GLS API Sandbox: First Label

Step-by-step guide to GLS parcel API sandbox setup: get MyGLS credentials, call CreateLabel, and generate your first test label.

Setting Up the GLS API Sandbox: First Label

GLS doesn't run one global parcel API. It runs at least four, tied to separate developer portals for Germany, the Netherlands, the CEE region, and North America, each with its own sandbox, its own MyGLS credential pool, and its own quirks. If you're setting up a GLS API sandbox for the first time expecting a single unified onboarding flow like EasyPost or ShipEngine, you'll lose an afternoon before you even send your first request. This walkthrough covers the Netherlands/Germany-style Shipping API (the REST flavor most EU integrators hit first), from portal registration to a rendered test label, including the one field-level bug that trips up almost everyone with more than one customer account.

Why GLS deserves its own setup guide

Because "the GLS API" isn't one thing. GLS's central developer portal covers customs and parcel processing for the group, while GLS Netherlands runs its own portal with a separate Shipping API spec. The GLS API developer Portal will provide access to the API test and production environment including code examples and the possibility to interact directly with the API from the online API documentation. But that's the NL portal's test environment, not the group-wide one, and not the CEE SOAP-based variant used in Hungary, Romania, Slovakia, Czechia, Slovenia, and Croatia. If you're evaluating GLS alongside DHL, DPD, or DB Schenker for EU parcel coverage, budget time to figure out which portal and which API version actually applies to the country pair you ship.

This matters for anyone building a multi-carrier abstraction layer too: GLS's fragmentation is exactly the kind of thing that makes direct-API integration across five countries meaningfully more work than plugging into one normalized platform.

What you need before starting

You can't self-serve your way to a production GLS account, but the sandbox side is more open than you'd expect. Here's the checklist before you touch a single endpoint:

  • A MyGLS username and password. For the NL/DE Shipping API, this typically comes with your shipper agreement, though the group's developer portal FAQ confirms self-registration is available for portal access itself — to create an account, click on the "Sign In" button on the portal's homepage, fill in the required information such as name, email, company and password, and then verify your email address.
  • The correct country portal URL. For the Netherlands/Germany REST flavor it's api-portal.gls.nl; for group-level customs and other APIs it's dev-portal.gls-group.net.
  • Confirmation of your operational customer number(s) from your GLS account manager. You'll need this later, and getting it wrong is the failure mode below.
  • An HTTP client (curl or Postman is fine) and somewhere to store sandbox vs. production base URLs separately, because mixing them across regional portals is a second, quieter failure mode.

Step-by-step: from registration to first label

This is the sequence we ran against the GLS Netherlands Shipping API sandbox. Endpoint and field names below are lifted directly from the GLS Shipping API v0.8 spec.

  1. Register on the country-specific developer portal. Go to the portal for your market (e.g. api-portal.gls.nl), create an account, and verify your email. This gets you into the docs and the interactive test console, not necessarily a live MyGLS account.
  2. Confirm which module you're testing. The API can be used to create Parcel shipments ("ShipType"="P") as well as Freight shipments ("ShipType"="F"), and the field requirements differ between the two. Decide up front — most first integrations are Parcel.
  3. Call ValidateLogin first, not CreateLabel. ValidateLogin: Operator to validate the MyGLS username and password. This is your cheapest sanity check — if it fails, don't bother debugging CreateLabel payloads yet.
  4. Separate your sandbox and production base URLs in code, not just in a notes file. The pattern shows up across GLS's own SDKs: the netresearch GLS Parcel Processing SDK takes a boolean $sandbox flag directly in the service factory call — $service = $serviceFactory->createShipmentService('basicAuthUser', 'basicAuthPass', $logger, $sandbox = true); Hardcode this as a config toggle from day one.
  5. Build the CreateLabel request body. Minimum viable payload needs your shipper account, a recipient address, and at least one parcel weight. The SDK example shows the shape clearly: $requestBuilder->setShipperAccount($shipperId = '98765 43210'); $requestBuilder->setRecipientAddress( $country = 'DE', $postalCode = '36286', $city = 'Neuenstein', $street = 'GLS-Germany-Straße 1 - 7', $name = 'Jane Doe' ); $requestBuilder->addParcel($parcelWeightA = 0.95); Note the weight ceiling: the maximum weight of a parcel is 32kg, so anything heavier needs to go through the Freight ShipType instead.
  6. Parse the response for the unit number and label payload. The CreateLabel endpoint is used to generate a shipment and corresponding shipping labels. The unit numbers are generated by the API and returned in the API response together with the label(s). Labels come back as ZPL or base64-encoded PDF depending on the LabelType field you set.
  7. Render the label and cross-check the barcode. Decode the PDF or push the ZPL to a test printer, then confirm the printed/rendered parcel number matches what the API returned. If it does, you're done — first label complete.

Send the request over Basic Auth to the sandbox CreateLabel endpoint. A raw curl call looks roughly like this:

curl -X POST https://api-sandbox.gls.nl/CreateLabel \
  -u "myglsUsername:myglsPassword" \
  -H "Content-Type: application/json" \
  -d '{
    "ShipperAccount": {"CustomerNo": "98765430"},
    "ShipmentDate": "2026-08-20",
    "ShipmentUnit": [{"Weight": 0.95}],
    "Consignee": {
      "Name1": "Jane Doe",
      "Street": "Testlaan 1",
      "City": "Amsterdam",
      "ZipCode": "1000AA",
      "CountryCode": "NL"
    }
  }'

Swap in the actual sandbox host from your country portal's products section — GLS doesn't publish one universal sandbox domain across markets.

How you know it worked

Success looks like an HTTP 200 or 201 with a populated unit number and a non-empty label field in the JSON body — no unit number means the shipment wasn't actually created even if the status code looks fine. As a second check, feed that unit number into GLS's tracking lookup in the same sandbox environment and confirm it resolves. If tracking comes back empty, don't assume your label call failed; sandbox tracking-event simulation on some GLS portals lags behind label creation, which is worth flagging as a known limitation rather than a bug in your code.

Failure mode: the ambiguous customer number

Here's the one that actually costs people a support ticket. The spec is blunt about it: this field should only be used when there is more than one operational customer number linked to the MyGLS API account. If only one operational customer number is linked then this field may be omitted.

Sounds harmless until your account has two or three operational customer numbers (common if you ship both domestic parcel and freight, or have separate numbers per depot) and you omit CustomerNo because "it's optional." GLS's API then has to guess which customer number to bill and route against, and in practice that guess is wrong often enough to produce misrouted or rejected shipments rather than a clean error message. The fix is simple and asymmetric in risk: always pass an explicit customer number, even when you currently have only one. Accounts change — a second warehouse, a new contract tier — and the day someone adds a second operational number to your MyGLS account is the day your "it worked in testing" integration starts silently misbehaving in production.

A second, quieter failure mode: don't reuse a Netherlands sandbox username against the Germany or group-level portal, or vice versa. Each regional portal issues its own credentials and base URLs, and a 401 from crossed environments looks identical to a genuinely wrong password, which wastes time you don't need to lose.

Where GLS's raw API fits vs. abstraction layers

If GLS is your only carrier, the SDKs above (netresearch's gls-sdk-api-parcel-processing for PHP, or enbit's gls-web-api-sdk) get you to a working integration reasonably fast. If GLS is one of six or eight carriers you're juggling alongside DHL, DPD, UPS, and DB Schenker, maintaining a separate credential set, base URL, and quirk list per regional GLS portal starts to look like the wrong use of engineering time. That's the case multi-carrier platforms like nShift, Sendcloud, ShipEngine, EasyPost, Shipmondo, and Cargoson make: one API surface instead of four GLS portals plus whatever DHL and Schenker require on top. Worth weighing against the control you get from talking to GLS directly, especially if you need freight ShipType support that some abstraction layers don't expose cleanly.

Follow-ups and open questions

What we haven't verified yet: how faithfully the GLS sandbox simulates tracking-event webhooks versus just label generation, and whether sandbox stability differs meaningfully between the NL and DE portals versus the CEE SOAP variant. That's a natural next benchmark — pushing test parcels through CreateLabel and timing how long tracking events take to appear in the sandbox versus production, the same way we've benchmarked webhook latency elsewhere on this blog. If you've run that test on your own GLS integration, we'd genuinely like to compare notes.