Developer notes
Part of the Bring Shipping documentation. Previous: Troubleshooting.
Sources
Section titled “Sources”Only the official Bring documents were used (read 2026-11-08):
- developer.bring.com: “Getting started with Bring APIs”, “Bring API testing resources”, “Service portfolio”, Shipping Guide API (and its pages “Price”, “Lead time”, “Using Shipping Guide API”), Booking API (and “Label and label-free solutions”, “Free return of parcels”, “Checking prices and availability”, “Authorization”, “Delivery to parcel locker”), Pickup Point API (“Filtering results”), Tracking API (versioning, “Events Description”, language support), Customer Info API.
- The OpenAPI documents those pages link to:
https://api.bring.com/shippingguide/api-docs,https://api.bring.com/booking/api-docs,https://api.bring.com/pickuppoint/openapi,https://api.bring.com/tracking/api-docs,https://api.bring.com/customer-info/api-docs, andhttps://api.bring.com/booking/api/errors.
Copies of the schemas and the official examples used by the tests are in tests/fixtures/official/ (schemas-*.json, examples named after their source).
Host, authentication, test mode
Section titled “Host, authentication, test mode”| Item | Value | Source |
|---|---|---|
| Host | https://api.bring.com (one host for test and live) |
“servers” of every OpenAPI document |
| Authentication | headers X-Mybring-API-Uid (e-mail address of the Mybring user), X-Mybring-API-Key (the user’s API key), X-Bring-Client-URL (URL of the service using the API) on every call |
Getting started, “Authentication headers”: “the following three HTTP header parameters are always required” |
| Rights | the Mybring user needs access to the customer number, the booking right for bookings and the financial right for agreement prices | Getting started, “User, number and rights”; Booking, “Authorization”; Shipping Guide withPrice (“the authenticated user requires the financial role with the provided customer number”) |
| Test mode | header X-Bring-Test-Indicator: true on bookings (the body field testIndicator is “Not in use. Use HTTP header instead”) |
Booking OpenAPI, parameter X-Bring-Test-Indicator; API testing, “Test indicator”: “without creating actual bookings or getting invoiced” |
| Test customer numbers | 5 (Parcel Norway domestic), 6 (Parcel Sweden, Denmark, Finland and Norway cross-border), 7 (Cargo Norway domestic) | API testing, “Test customer numbers” |
| Refused credentials | 401 “Unauthorized” | Tracking API updates (13 May 2024) |
| Client URL missing | 400 “Client URL must be set … with a ‘clientUrl’ request parameter or by setting a ‘X-Bring-Client-URL’ header” | GET /booking/api/errors |
The add-on sends X-Bring-Client-URL = the store’s https location. The API key is sealed with AES-256-GCM in the database, never shown again and never logged (Log::mask()); headers are stripped of line breaks before they are sent.
Endpoints used
Section titled “Endpoints used”| Use | Endpoint | Fields used | Code |
|---|---|---|---|
| Connection test | GET /customer-info/customers |
answer customers[] {name, customerNumber, countryCode, products[]} (schema DtoCustomerToProductMapping); the customer number of the mode must be one of them (test numbers 5, 6, 7 are accepted) |
Api::customers(), Api::ping() |
Prices and delivery dates (agreement price; without the financial right Bring answers errorCode CUSTOMER_NO_FINANCIAL_ACCESS and the add-on asks again without customerNumber, which gives the list price: “Otherwise list (gross) prices is returned”) |
POST /shippingguide/api/v2/products |
body Shipping_guide_request: consignments[] {id, fromCountryCode, fromPostalCode, toCountryCode, toPostalCode, shippingDate {year, month, day}, products[] {id, customerNumber}, packages[] {id, grossWeight (grams)}, additionalServices[] {id}}, language, withPrice, withExpectedDelivery, withGuiInformation, edi; answer consignments[].products[] {id, price {netPrice, listPrice {priceWithAdditionalServices {amountWithoutVAT, vat, amountWithVAT}, currencyCode}}, expectedDelivery {workingDays, formattedExpectedDeliveryDate}, errors[] {code, description}}; 400 Errors {fieldErrors[] {code, message, field}} |
Api::shippingGuide(), RateCalculator::live(), fromProduct(), etaText() |
| Pickup points near the address | GET /pickuppoint/api/pickuppoint/{countryCode}/postalCode/{postalCode} |
countryCode NO, DK, SE, FI; query street, streetNumber (“Use this param to get more accurate result”), numberOfResponses; header Accept: application/json (the default is XML); answer ListDto {pickupPoint[]}: id, name, address, postalCode, city, countryCode, visitingAddress, visitingPostalCode, visitingCity, openingHoursEnglish, latitude, longitude, distanceInKm, pickupPointType (MANNED, LOCKER), status (ACTIVE, INACTIVE, TEMPORARILY_DISABLED); 400 ClientErrors {code, parameterErrors[] {parameter, error}}; 429 “Request rate cannot exceed 30 requests per 1 second” |
Api::pickupPointsNear(), ServicePoints::normalise() |
| Check of the chosen point | GET /pickuppoint/api/pickuppoint/{countryCode}/id/{id} |
same record | Api::pickupPointById(), ServicePoints::find() |
| Booking | POST /booking/api/create |
body DtoBookingRequest {consignments[1]} (below); answer DtoBookingResponse: consignments[].confirmation {consignmentNumber, packages[] {packageNumber}, links {labels, labelArchiveUrl, tracking}, returnConsignmentNumber, returnLinks {labels, tracking}, dateAndTimes.expectedDelivery}; errors consignments[].errors[] {uniqueId, code "BOOK-...", messages[] {lang, message, details}} (400; documented as an array of DtoBookingResponse) |
Api::book(), Shipments::payload(), bookingFrom() |
| Label | the links.labels URL of the booking answer |
“A URL to a PDF label is returned in responses”; “A GET request to the label URL will result in a HTTP 302 redirect to the concrete storage facility (e.g. redirect to Amazon S3). Ensure that your client follows these redirects.” | Api::label(), BaseClient::download() |
| Tracking and lookup | GET /tracking/api/v2/tracking.json?q=...&lang=en |
q “the tracking number or reference string”; answer consignmentSet[] {consignmentId, senderReference, packageSet[] {packageNumber, recipientAddress.postalCode, eventSet[] {status, description, dateIso, city, countryCode}}}; an entry error {code 404, message "No shipments found"} means nothing found; error {code 429} = rate limit |
Api::track(), Shipments::itemsFrom(), lookup(), historyFrom() |
The customer’s tracking link is the links.tracking URL of the booking answer (official example https://sporing.posten.no/sporing/70438101263797410). When a booking is adopted by a lookup (no booking answer), the add-on builds the same address from the shipment number. The CS-Cart carrier link uses the same pattern.
The booking (schema consignment)
Section titled “The booking (schema consignment)”shippingDateTime (date-time with the sender’s offset), correlationId (= sender reference), and:
parties.sender {name, addressLine (35), addressLine2 (35), postalCode, city (35), countryCode, reference (35), contact {name, email (60), phoneNumber}}: the shop. “Sender phone number is a mandatory field for all outgoing services. Exceptions to this rule are Pakke hjem i postkassen (Home mailbox Parcel) and all return services.” The reference is the order id (with the optional prefix): “Please avoid using any personal data here since sender’s reference is accessible in open tracking.”parties.recipient {name, addressLine, addressLine2, postalCode, city, countryCode, additionalAddressInfo (70, optional order notes), contact {name, email, phoneNumber}}: the customer.parties.pickupPoint {id, countryCode}for the Pickup Parcel service.product {id, customerNumber (35), additionalServices[] {id}}.returnProduct {id}for a free return (below).packages[] {weightInKg, goodsDescription (35), correlationId}.
Names carry no maxLength in the schema; the add-on cuts them at 70 characters. All text stays UTF-8 and is cut by characters (mb_substr), so Nordic letters are never broken. Every booking and Shipping Guide request the add-on builds is validated against the official schemas in tests/unit.php (required fields, unknown fields, maxLength, types); the mock server validates them again.
Services
Section titled “Services”From the Service portfolio: inside Norway 5800 Pickup parcel, 5600 Parcel home plus, 3584 Home mailbox parcel, 5000 Business parcel; from Sweden or Denmark, or from Norway across borders, 0340 PickUp Parcel, 0349 Home Delivery Parcel, 0330 Business Parcel. The sender countries offered on the Connection page are Norway, Sweden and Denmark (0340 and 0330 list “Norway, Sweden, Denmark” as senders); Finland is a destination. Returns: 9350 Return parcel to business (Norway) and 0341 PickUp Parcel Return. A shipping method can override the service id with any other id of the merchant’s agreement.
Returns
Section titled “Returns”- Free return (“Free return of parcels”): “For Parcel Norway domestic services, it is possible to book the return shipment at the same time as the outgoing shipment. By setting the element returnProduct … you will both get the regular label and the return label in the response.” Outgoing 3584, 4850, 5000, 5600, 5800; return 9000, 9300, 9600, 9350. “You will be invoiced for the return only if your customer uses it.”
- Return on demand: a separate booking with the return service, the customer as sender and the shop’s return address as recipient, shaped like the official request example “Return Parcels Service”.
Labels
Section titled “Labels”The Booking API returns PDF labels. ZPL is produced only for the RFID service 3570 (“If you opt for tracking, we create ZPL labels instead”); the add-on stores and serves whatever Bring returns (PDF, or ZPL for 3570) and checks the file type. The Mybring headers are sent only to Bring hosts; the redirect to the storage host gets no credentials.
Cancelling
Section titled “Cancelling”The Booking API documents no cancel or delete operation. “Void” removes the booking and its label in the store only and is allowed only before Bring has registered any movement of the parcel. The merchant then does not hand the parcel over and can check unused bookings in Mybring.
Duplicate protection and states
Section titled “Duplicate protection and states”The Booking API takes no idempotency key. The shipment row with its reference is written before the call under a per-order lock (GET_LOCK). A timeout or 5xx leaves the row “unknown”: the queue looks the reference up with the Tracking API every 10 minutes and adopts the shipment found (matching senderReference and the recipient postal code); it never books again. “Book again” is an explicit admin action, allowed 30 minutes after the first attempt (at once for test bookings, which Bring never stores) and only after a lookup found nothing. A 4xx answer or errors[] deletes the pending row. A reference held by another sender’s shipment closes the attempt (new reference on the next booking). Returns use the same guard (per-shipment lock, pending state, lookup by reference R<reference>). Tracking states never move backwards (Shipments::rank()).
Event mapping (Tracking API “Events Description”): PRE_NOTIFIED = booked; HANDED_IN, COLLECTED, IN_TRANSIT, TERMINAL, INTERNATIONAL, CUSTOMS, TRANSPORT_TO_RECIPIENT, DELIVERY_ORDERED, DELIVERY_CHANGED, NOTIFICATION_SENT, ATTEMPTED_DELIVERY = in transit; READY_FOR_PICKUP = ready for pick-up; DELIVERED = delivered; RETURN, DELIVERED_SENDER = returned; DEVIATION, DELIVERY_CANCELLED = needs attention; UNKNOWN and anything new = kept as text only.
Times and money
Section titled “Times and money”shippingDateTimeand the Shipping GuideshippingDateuse the sender country’s zone (Oslo, Stockholm, Copenhagen, Helsinki). TrackingdateIsocarries an offset; a time without one is read in the sender country’s zone. Dates shown in the store use the shop time zone.- Shipping Guide prices come in the answer’s
currencyCodeand are converted with the store’s own currency rates (?:currencies.coefficient); a currency the store does not have gives no Bring price (the method falls back to its own price or is hidden).
Multi-Vendor
Section titled “Multi-Vendor”Marketplace owner mode (default on): every booking uses the owner’s Mybring user and customer number; vendors see the bookings of their own orders but cannot book, void, create returns, retry or discard jobs, or open the Connection page. Owner mode off: each vendor connects their own Mybring user and customer number (Connection page in the vendor panel), and every vendor action checks the record’s company_id. The queue run, cron and backfill are admin-only (schemas/permissions/vendor_multivendor.post.php).
Hooks, tables, jobs
Section titled “Hooks, tables, jobs”- PHP hooks:
change_order_status_post(book at a status),get_order_info,place_order,place_suborders,delete_order,template_document_order_context_init,init_templater_post. - Tables:
kv_bring_accounts,_shipments,_parcels,_points,_spcache,_quotes,_jobs,_log(all dropped on uninstall; no legal records are kept). - Jobs:
create_label,reconcile,track. Cron:php admin.php --dispatch=kv_bring.cronor the secret URL shown in the settings. - Test hook:
$config['kv_bring_base_url'] = 'http://127.0.0.1:<port>';inconfig.local.phppoints every call at the local mock (tests/mock-server/server.php). The e2e scripts refuse to run when it points anywhere else.
Bring and Posten are trademarks of Posten Bring AS. Kavio Labs is not affiliated with or endorsed by Posten Bring AS.