Documentation
Please note, this documentation is not exhaustive, and other optional values may be returned in responses.
If you have questions or found errors, use your integration Slack channel with Porterbuddy, or get in touch at integration@instabee.com.
Overview
Porterbuddy is a last mile delivery service platform. Customers of Porterbuddy can place delivery orders using this api.
The following describes the necessary steps to do a delivery using Porterbuddy:
- Your retailer solution: Fetch delivery
availability
- End user: Choose delivery time
- Your retailer solution: Place delivery
order
- Porterbuddy: Deliver the parcels at the requested time
Time
All timestamps requires timezone to avoid ambiguity.
Availability
An availability
object is an entity that combines pickup address, delivery address, and delivery time. To be able to guarantee our ability to fulfill an order, you first need to obtain an availability
entity before submitting an order for the requested time.
Order
The order
contains all information needed to deliver to the end user. This includes addresses and delivery time, but also the availability for the package for pick up and requirements for identification, etc.
Testing the API
If you want to test the API and already have a partner account in the test environment with Porterbuddy, the API key can be found in the profile section of the partner portal on https://retailers.porterbuddy-test.com/profile. If you don't have a Porterbuddy test account yet, please checkout https://porterbuddy.com/business, or you can send a request to integration@instabee.com to receive a test account invitation.
Get an API key
To get an API key for use in production, you first have to register as a user and customer at Porterbuddy. Registering a user account is done by an invitation system, so please checkout https://porterbuddy.com/business for further details. After the account and user login have been created, the API key can be found at https://retailers.porterbuddy.com/profile.
Implementation Hints
We try to keep this API as stable as possible. However, implementing new services or features sometimes make changes necessary, for example adding new properties to API objects.
To lower the risk of such a change breaking your integration, we advise to only define the properties of API responses your integration needs
and always ignore unknown properties in received json objects. This is the default behaviour in Javascript
(due to Javascript not being typed anyway) and Typescript when representing received API responses via interfaces. When using Jackson to implement
JSON APIs in Java, the easiest way to achieve this is to use the annotation @JsonIgnoreProperties(ignoreUnknown=true)
on classes representing API response data.
Authentication
An authenticated request includes the
x-api-key
header:
curl "https://api.porterbuddy.com/..."
-H "x-api-key: <your_api_key>"
Make sure to replace
<your_api_key>
with your API key.
Porterbuddy expects the API key to be included in all API requests to the server in a header that looks like the following:
x-api-key: <your_api_key>
Availability check API
To verify that PorterBuddy can deliver according to your preferences you need to do an availability request. This will verify address details and return possible delivery windows. Note that even when the endpoint returns statuscode OK (200), the delivery windows array could be empty due to no delivery being possible with the specified pickup windows, or no more capacity being available.
POST https://api.porterbuddy.com/availability
Example Request:
curl \
-X POST 'https://api.porterbuddy.com/availability' \
-H 'Content-Type: application/json' \
-H 'x-api-key: <your_api_key>' \
-d '{
"pickupWindows": [
{"start": "2025-02-13T10:00+01:00", "end": "2025-02-13T18:00+01:00"},
{"start": "2025-02-14T10:00+01:00", "end": "2025-02-14T18:00+01:00"}
],
"originAddress": {
"streetName": "Keysers Gate",
"streetNumber": "3",
"postalCode": "0165",
"city": "Oslo",
"country": "Norway"
},
"destinationAddress": {
"streetName": "Høyenhallveien",
"streetNumber": "25",
"postalCode": "0678",
"city": "Oslo",
"country": "Norway"
},
"recipient": {
"email": "testemail+recipient@porterbuddy.com",
"phoneCountryCode": "+47",
"phoneNumber": "65127865"
},
"products": [ "delivery" ],
"parcels": [
{
"widthCm": 30,
"heightCm": 25,
"depthCm": 45,
"weightGrams": 2000
}
],
"items": [
{
"name":"Fancy Sneakers",
"sku":"FANCYSNEAKER43",
"weightGrams":"600",
"widthCm":20,
"heightCm":10,
"depthCm":35,
"description":"Fancy sneakers (red/blue) in size 43",
"category":"shoes",
"brand":"Fancy Footwear Corp.",
"imageUrl":"https://awesomewebshop.com/images/5fd71d6f-b0be-4480-900f-f3d008a0bc62.png",
"price": {
"fractionalDenomination":"79900",
"currency":"NOK"
},
"barCode": {
"value":"123-456-789",
"type":"CODE128"
}
},
{
"name":"Heavy Boots",
"sku":"HEAVYBOOTS48",
"weightGrams":"1400",
"widthCm":30,
"heightCm":12,
"depthCm":45,
"description":"Heavy military boots, size 48",
"category":"shoes",
"brand":"Heavy Industries Inc.",
"imageUrl":"https://awesomewebshop.com/images/613c37e8-64be-4483-a216-9fb1b7f7e848.png",
"price": {
"fractionalDenomination":"134900",
"currency":"NOK"
},
"barCode": {
"value":"999-888-765",
"type":"CODE128"
}
}
]
}'
Example response:
{
"originResolvedAddress": {
"streetName": "Keysers Gate",
"streetNumber": "3",
"postalCode": "0165",
"city": "Oslo",
"country": "Norway",
"location": {
"latitude": 59.9169844,
"longitude": 10.7411912
}
},
"destinationResolvedAddress": {
"streetName": "Høyenhallveien",
"streetNumber": "25",
"postalCode": "0678",
"city": "Oslo",
"country": "Norway",
"location": {
"latitude": 59.9032646,
"longitude": 10.811598
}
},
"deliveryWindows": [
{
"product": "delivery",
"start": "2025-02-13T17:30:00+01:00",
"end": "2025-02-13T19:30:00+01:00",
"price": {
"fractionalDenomination": 14900,
"currency": "NOK"
},
"expiresAt": "2025-02-13T13:00:13+01:00",
"token": "20hRsgz8AovrLjeOldJ2Wg==:yhr85il4/swdgiEP/DG2wg==:2hBoFcmyTNLp/CTfX3sTGslOJr9sXAMxHggqq/h6tGmUuCEB2Vfy8uyNIWfg3qf6d7nj84Aj2sbwMLK2hETe14L4qgnlZHVSkBcktYPc6VCp9vEZhXErpQS3HoSyRU+mVcF2SNGP4s5TI5x7S6oq4Q==",
"consolidated": false
},
{
"product": "delivery",
"start": "2025-02-13T19:30:00+01:00",
"end": "2025-02-13T21:30:00+01:00",
"price": {
"fractionalDenomination": 14900,
"currency": "NOK"
},
"expiresAt": "2025-02-13T13:00:11+01:00",
"token": "lDQ+BXxkKR9qHwv5naf6CQ==:zpR8F/PUK3YlhUkVspoQ/w==:eV5fTEQmv/6We+KaK32ji31FHHHKaG4/GFQO8s/BX9dLKQ4QlV6gLZYtXOCQU+WfbYe+x9pJOfdjaXRfC2M4oNq+bHtNHSQKY7rfjAV3IoG2DSqvT9a+z4Lp8yuFOvonEUlzWlEiT2inTcnju+JlYQ==",
"consolidated": false
},
{
"product": "delivery",
"start": "2025-02-14T17:30:00+01:00",
"end": "2025-02-14T19:30:00+01:00",
"price": {
"fractionalDenomination": 14900,
"currency": "NOK"
},
"expiresAt": "2025-02-14T13:00:27+01:00",
"token": "SW23FPWVYkyV+xJuFfJ76w==:wZl92j141aX8vBtU/bzgtQ==:kBZeBgu+JoYMaasgk+Y+F4Bsd6ntn3Q8hkbGX9/ysfnppPTmqe24ZZeBdtDwh2364DMUhc+hpyK2Aau8jW7FC/gl4E3Q84j+7OnX3CNYuRzoNlYsXI749XGr86YbVU6wt8olw8pNkkLsyF17oNDA3g==",
"consolidated": false
},
{
"product": "delivery",
"start": "2025-02-14T19:30:00+01:00",
"end": "2025-02-14T21:30:00+01:00",
"price": {
"fractionalDenomination": 14900,
"currency": "NOK"
},
"expiresAt": "2025-02-14T13:00:04+01:00",
"token": "Kj1q+VlVFG4LX3ZAwbMPPg==:t6BkqFpGFzVnpqoTqEyKgA==:GGrsm1gUTetg8ux9sTs0/+X7mQiG/9a4y67BZoXPRjB3g8F+OK4NVyaYZAdIsLcdbO36jNta8hEyyCDxVee2qi7+95RJXBP/7wuH4Ik/S2/fq+8gHBQhISm0ba9TyO4ujytN5eAlXmoZgSQSXFG2tg==",
"consolidated": false
}
],
"flags": ["CONSOLIDATION_ENABLED"]
}
Arguments
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
originAddress | Yes | Address | none | The pick up location for the merchandise |
destinationAddress | Yes | AvailabilityDestinationAddress | none | The destination for the merchandise |
recipient | No | AvailabilityRecipient | none | The contact details of the delivery recipient |
pickupWindows | No | List<Window> | none | The windows when the package is available for pick-up. Specifying this allows for fine-grained control of the number of delivery windows returned, or the earliest possible delivery option, e.g. in case of preorders. If not set, pickup windows for 7 days including the current day will be generated on the backend side. We recommend sending wide windows and for multiple days. Ex: If the package is ready for pick up from 3pm on monday and the store closes at 7pm, the first window will be 3pm-7pm the first day, and the following windows should probably be the opening hours for the store the next days. |
products | No | List |
[ delivery ] | The name of the product needed. Currently, the products "delivery" and "large" are available. The applicable products will be influenced by the destination address and the specified parcels or items, so the returned delivery window list may not contain windows for all specified products. Each window has a 'product' attribute which signals which product this is for. |
parcels | (*) | List<Parcel> | none | The list of parcels that you want to transport. This will affect which couriers can fulfill the delivery. A smaller car cannot drive packages that won't fit in it's trunk. |
additionalInfo | no | Object | null | Object reserved for additional information. This argument is intended to be provided by the checkout widget and needs to be passed-through as is, without further modifications. |
items | (*) | List<Item> | none | A list of items you want to transport. Will be used to determine size and weight if no parcels are specified. |
(*) At least one of the properties "items" or "parcels" must be specified for a valid request. If only items are specified, each item must have the "weightGrams" property specified as >= 0.
Availability Destination Address
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
streetName | No | String | none | Name of the street |
streetNumber | No | String | none | Number of the street. Including any letters. Ex: 10d |
postalCode | Yes | String | none | The postal code of the address |
city | No | String | none | The city of the address |
country | Yes | String | none | The country of the address |
Availability Recipient
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
No | String | none | ||
phoneCountryCode | No | String | +47 | Country code for the phone number |
phoneNumber | No | String | none | Phone number |
Response
Parameter | Type | Description |
---|---|---|
originResolvedAddress | Resolved Address | Resolved origin. We will resolve the origin against Google\'s APIs. Note: For historic reasons, the origin address is being replaced with the address of our hub that handles the last-mile delivery. This behaviour can safely be ignored. |
destinationResolvedAddress | Resolved Address | Resolved destination. Like with the origin, we will try to resolve the exact details. The result will be returned here. |
deliveryWindows | List<DeliveryWindow> | The available windows for the destination. This is tied to the pick up windows given in the request, so any changes to those will affect these windows as well. |
consolidatedWindow | ConsolidatedDeliveryWindow | Available window for the destination. Present if the recipient field was present in the request and the customer has already placed one or more orders within one of the delivery windows. |
flags | List |
A list of flags indicating if certain features are enabled in the shop configuration. Mainly used for our own checkout widgets. This property will be omitted if the list would be empty. |
Resolved Address
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
streetName | Yes | String | none | Name of the street |
streetNumber | Yes | String | none | Number of the street. Including any letters. Ex: 10d |
postalCode | Yes | String | none | The postal code of the address |
city | Yes | String | none | The city of the address |
country | Yes | String | none | The country of the address |
location | Yes | location (lat/long) | none | The exact location of the address in longitude/latitude |
Delivery Window
Parameter | Type | Description |
---|---|---|
start | DateTime | The start of the window |
end | DateTime | The end of the window |
product | String | The product type this window is for. Example: "delivery" or "large" |
price | Price | The price given for the specific window |
displayPrice | Price | The display price given for the specific window, calculated based the store's configured base display price. Used to show the end customer a price different from the actual price. Only returned when a base display price is configured. |
expiresAt | DateTime | After this time this window should not be shown as an option. Either remove the window or do a full refresh of the availability. |
token | String | The token is to be sent back when placing and order. It makes sure that the window and price that was offered for availability at that specific point in time is what is registered on the order. |
consolidated | Boolean | If true, there is another order present for the same customer with that window, so the orders can be consolidated. |
Consolidated Delivery Window
Parameter | Type | Description |
---|---|---|
start | DateTime | The start of the window, may be omitted if anonymous consolidation is used. |
end | DateTime | The end of the window, may be omitted if anonymous consolidation is used. |
product | String | The product type this window is for. Example: "delivery" or "large"" |
price | Price | The price given for the specific window |
displayPrice | Price | The display price given for the specific window, calculated based the store's configured base display price. Used to show the end customer a price different from the actual price. Only returned when a base display price is configured. |
expiresAt | DateTime | After this time this window should not be shown as an option. Either remove the window or do a full refresh of the availability. |
token | String | The token is to be sent back when placing and order. It makes sure that the window and price that was offered for availability at that specific point in time is what is registered on the order. |
consolidated | Boolean | If true, there is another order present for the same customer with that window, so the orders can be consolidated. |
Price
Parameter | Type | Description |
---|---|---|
fractionalDenomination | Integer | The price in the lowest denominator. Ex: 1 USD = 100 fractionalDenomination (cents) |
currency | String | The currency specified. Usually "NOK". |
Order API
Testing hints
The test environment on api.porterbuddy-test.com
is used for both testing partner integrations and for internal testing within Porterbuddy. For this reason,
short downtimes can occur, and features might behave unexpectedly for short periods of time. For short-term answers, best practice is to establish an integration
slack channel with Porterbuddy.
⚠ The test environment is configured to send out emails to specified sender and recipient email addresses. To avoid confusion, do not use email addresses of real customers, instead, rather use your own email address if you want to receive tracking links, or a non-existing one. The test environment does not send out SMS, so any phone number can be specified without consequences. ⚠
Place an order
To place an order you need to know the destination, origin as well as the times chosen by the user and the times the package is available for pickup.
POST https://api.porterbuddy.com/order
Example request:
curl -X POST \
https://api.porterbuddy-test.com/order \
-H 'x-api-key: <your_api_key>' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: <your idempotency key>' \
-d '{
"origin": {
"name": "Nils Johansen (Sender)",
"address": {
"streetName": "Keysers Gate",
"streetNumber": "3",
"postalCode": "0165",
"city": "Oslo",
"country": "Norway"
},
"email": "testemail+sender@porterbuddy.com",
"phoneCountryCode": "+47",
"phoneNumber": "65127865"
},
"destination": {
"name": "Roger Olsen (Recipient)",
"address": {
"streetName": "Høyenhallveien",
"streetNumber": "25",
"postalCode": "0678",
"city": "Oslo",
"country": "Norway"
},
"email": "testemail+recipient@porterbuddy.com",
"phoneCountryCode": "+47",
"phoneNumber": "65789832",
"deliveryWindow":{
"start": "2025-02-13T17:30:00+01:00",
"end": "2025-02-13T19:30:00+01:00",
"token": "20hRsgz8AovrLjeOldJ2Wg==:yhr85il4/swdgiEP/DG2wg==:2hBoFcmyTNLp/CTfX3sTGslOJr9sXAMxHggqq/h6tGmUuCEB2Vfy8uyNIWfg3qf6d7nj84Aj2sbwMLK2hETe14L4qgnlZHVSkBcktYPc6VCp9vEZhXErpQS3HoSyRU+mVcF2SNGP4s5TI5x7S6oq4Q=="
},
"verifications": {
"minimumAgeCheck": null,
"requireSignature": false,
"deliveryVerification":"CONTACTLESS",
"confirmCustomerReceipt": false
}
},
"parcels" : [
{
"description": "Shoes",
"widthCm": 30,
"heightCm": 25,
"depthCm": 45,
"weightGrams": 2000,
"parcelShipmentIdentifier": "12345",
"constraints": {
"temperature": {
"maximumCelsius": 25.0,
"minimumCelsius": 8.0
}
}
}
],
"items": [
{
"name":"Fancy Sneakers",
"sku":"FANCYSNEAKER43",
"weightGrams":"600",
"widthCm":20,
"heightCm":10,
"depthCm":35,
"description":"Fancy sneakers (red/blue) in size 43",
"category":"shoes",
"brand":"Fancy Footwear Corp.",
"imageUrl":"https://awesomewebshop.com/images/5fd71d6f-b0be-4480-900f-f3d008a0bc62.png",
"price": {
"fractionalDenomination":"79900",
"currency":"NOK"
},
"barCode": {
"value":"123-456-789",
"type":"CODE128"
}
},
{
"name":"Heavy Boots",
"sku":"HEAVYBOOTS48",
"weightGrams":"1400",
"widthCm":30,
"heightCm":12,
"depthCm":45,
"description":"Heavy military boots, size 48",
"category":"shoes",
"brand":"Heavy Industries Inc.",
"imageUrl":"https://awesomewebshop.com/images/613c37e8-64be-4483-a216-9fb1b7f7e848.png",
"price": {
"fractionalDenomination":"134900",
"currency":"NOK"
},
"barCode": {
"value":"999-888-765",
"type":"CODE128"
}
}
],
"product": "delivery",
"courierInstructions": "Test",
"orderReference": "NJ12345"
}'
Example response:
{
"orderId": "1702918291025",
"pickupDate": "2025-02-13",
"pickupTime": "2025-02-13T15:00:00+02:00",
"_links": {
"self": {
"href": "https://api.porterbuddy-test.com/order/1702918291025"
},
"labelInfo": {
"href": "https://api.porterbuddy-test.com/order/1702918291025/label"
},
"status": {
"href": "https://api.porterbuddy-test.com/order/1702918291025/status"
},
"userInformation": {
"href": "https://www.porterbuddy-test.com/orders/recipient_tracking/aDpa9ytqp86no9XATds6r2gf"
}
}
}
Arguments
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
origin | Yes | Origin | none | Information about the origin location. |
destination | Yes | Destination | none | Information about the destination. |
parcels | (*) | List<Parcel> | none | Information about the parcels. |
product | Yes | String | none | The product associated with the window chosen. Should be taken from the delivery window's properties. |
orderReference | No | String | none | Your internal order reference. |
tmsReference | No | String | none | Your internal tms reference. |
courierInstructions | No | String | "" | A message to the courier about the delivery. |
shipmentIdentifier | No | Digits[5-20] | none | Shipping identifier. The 5 last digits will be used as a pickup pin if this is specified. |
items | (*) | List<Item> | none | Information about the items (goods) in the order. |
statusWebhookUrl | No | URL | none | Url of a webhook that gets called when the pickup date or order status changes. See Order Status Webhook |
(*) At least one of the properties "items" or "parcels" must be specified for a valid request. If only items are specified, each item must have the "weightGrams" property specified as >= 0.
Origin
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
name | Yes | String | none | Full name |
address | Yes | Address | none | The address where the package will be collected |
Yes | String | none | ||
phoneCountryCode | No | String | +47 | Country code for the phone number |
phoneNumber | Yes | String | none | Phone number |
pickupWindows | No | List<Window> | none | The available windows to pick up the package, only relevant if destination.bestAvailableWindow is set to true . If not specified in the best available case, the backend will try to chose a delivery window within the next 10 days. If a delivery window or consolidated window is specified, this property will be ignored. |
Destination
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
name | Yes | String | none | Full name |
address | Yes | Address | none | The address where the package will be delivered |
Yes | String | none | ||
phoneCountryCode | No | String | +47 | Country code for the phone number |
phoneNumber | Yes | String | none | Phone number |
bestAvailableWindow | No | Boolean | false | IF this is true, find the "best available" window. This might be today or tomorrow, or even the next day if tomorrow is a holiday. To have some idea about when something can be delivered it is always best to verify availability through API. If a delivery window or consolidated delivery window is specified, this property is ignored. |
deliveryWindow | Yes* | Window | none | The agreed upon window to deliver within. This should be a window fetched from the /availability request. * This is not required if bestAvailableWindow or consolidatedWindow is used. |
consolidatedWindow | No | ConsolidatedWindow | none | if the user chose the consolidated delivery window, this field needs to be filled with the consolidated window data from the availability request. |
verifications | Yes | Verifications | none | The verifications to be performed by the courier. |
Address
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
streetName | Yes | String | none | Name of the street |
streetNumber | Yes | String | none | Number of the street. Including any letters. Ex: 10d |
postalCode | Yes | String | none | The postal code of the address |
city | Yes | String | none | The city of the address |
country | Yes | String | none | The country of the address |
Verifications
Some of the deprecated verification properties will affect each other. In that case, the couriers will check the strictest union of the requirements to make sure nothing is delivered to the wrong person.
The deprecated properties are only evaluated if deliveryVerification
is not provided.
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
No | Boolean | false | Deprecated, use deliveryVerifications property instead |
|
requireSignature | No | Boolean | null | Should the recipient sign for the package? Note: Taking signatures is currently replaced with confirmation via recipient pincode. |
No | Boolean | null | Deprecated, use deliveryVerifications property instead. Former usage: should the courier check the real identity of the recipient? |
|
No | Boolean | null | Deprecated, use deliveryVerifications property instead. Former usage: should the courier only deliver to the named recipient? This option implies an ID check. |
|
minimumAgeCheck | No | Integer | null | Should the courier verify the age of the recipient is equal to or above this value? Package will not be delivered if recipient is younger than the age provided. |
confirmCustomerReceipt | No | Boolean | false | Should the recipient be asked to confirm that the parcel is received and taken inside (for contactless delivery)? |
deliveryVerification | No | DeliveryVerification | CONTACTLESS* | Enum to select the delivery verification mode. |
* If requireSignature
is set to true or minimumAgeCheck
has a positive value, the default value for deliveryVerification
is REQUIRE_PIN
. If there is no contractual agreement in place to cover additional cost for pin code checks at the door,
these requirements will be ignored.
DeliveryVerification
Value | Description |
---|---|
CONTACTLESS | Delivery is performed contactless by placing parcels on the doorstep. Customers can choose to switch to personal delivery on their own accounts. |
ALLOW_CONTACTLESS | Delivery is performed in person with pin verification. Customers can change to contactless delivery on their own accounts and responsibility |
REQUIRE_PIN | Delivery is performed in person with pin verification. Customers are not allowed to change to contactless delivery |
REQUIRE_ID | Delivery is performed in person with id verification. The recipient is only required to show an id, but does not need to be the same person as the recipient named in order data. |
ONLY_TO_RECIPIENT | Delivery is performed in person with id verification. The recipient must be the same person as the named recipient in the order data. |
Enum for the way the delivery is verified. In case of a consolidated delivery, the strictest delivery verification is performed for the whole delivery.
Window
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
start | (*) | DateTime | none | The start of the window |
end | (*) | DateTime | none | The end of the window |
token | (*) | String | none | The token from the availability response. If only a token is specified, the start and end times will be extracted from that token. |
(*) Either start
and end
or token
must be present for the request to be handled. If all properties are null, the request will be rejected. If start and end time as well as token are provided, a check will be performed to make sure token and times match up.
ConsolidatedWindow
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
token | Yes | String | none | The token from the availability response. Only used when placing an order. |
Parcel
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
description | Yes | String | none | Description of the parcel and contents |
widthCm | No | Int | none | The width of the parcel in cm |
heightCm | No | Int | none | The height of the parcel in cm |
depthCm | No | Int | none | The depth of the parcel in cm |
weightGrams | Yes | Int | none | The weight of the package in grams |
parcelShipmentIdentifier | No | Digits[5-20] | none | The shipping identifier for the package. This is usually the number for the bar code. |
constraints | No* | ParcelConstraints | none | Constraints for parcel delivery. * Some api calls like /update-shipment-details will override the existing parcel data, including constraints. Make sure not to change the constraints after the labels for the order are printed. |
ParcelConstraints
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
temperature | no | TemperatureConstraints | none | Temperature constraints for the parcel. |
TemperatureConstraints
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
maximumCelsius | yes | double | none | maximum storage temperature in degrees Celsius |
minimumCelsius | yes | double | none | minimum storage temperature in degrees Celsius |
Temperature control requirements are specified per parcel. The information can either be passed during order creation, or when updating the shipment details / adding parcels.
Take care not to overwrite parcel constraints unintentionally. If the constraints are changed purposefully they should be done so as early as possible, and should not be changed
after the label(s) are printed. Supported min/max combinations are: {min: 8.0, max: 25.0}
, {min: 5.0, max: 20.0}
, {"min": 2.0, max: 25.0}
Item
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
weightGrams | (*) | Int | none | Weight of the item in grams. Required if only items and no parcels are specified in the request. |
widthCm | no | Int | none | Width of the item in cm |
heightCm | no | Int | none | Height of the item in cm |
depthCm | no | Int | none | Depth of the item in cm |
name | no | String | none | The item's name |
sku | no | String | none | SKU for the item |
price | no | Price | none | Sales price per unit of the item |
cost | no | Price | none | Cost per unit of the item |
description | no | String | none | Description of the item |
category | no | String | none | A product category for the item. |
brand | no | String | none | Brand name the item is sold under |
imageUrl | no | String | none | A URL linking to an image of the product |
barCode | no | BarCode | none | A unique barcode identifying the product |
storageTemperature | no | Int | none | Temperature the item should be stored at. Note: For reference purposes only, if items need to be handled at specific temperatures, it needs to be specified as parcel restrictions and the delivery agreement with Porterbuddy must include temperature-controlled handling. |
(*) If items are specified instead of parcels in an Availability- or OrderRequest, the weightGrams property must be specified as >= 0. It can be omitted if a list of parcels is specified in the requests additionally.
BarCode
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
value | yes | String | none | Value encoded in the barcode. Represented as string to support different barcode formats. |
type | yes | String | none | Barcode type, e.g. Code128, UPC, ... |
Response
Parameter | Type | Description |
---|---|---|
orderId | String | The id of the created order. Can be up to 13 char |
Deprecated, use pickupTime instead! Calculated date the order needs to be picked up to be delivered on time. | ||
pickupTime | DateTime | Calculated date and time the order needs to be picked up to be delivered on time. |
_links | OrderResponseLinks | Links for this resource. |
Order response links
Parameter | Type | Description |
---|---|---|
self | HrefLink | Link to the created order. |
labelInfo | HrefLink | Link to information about the labels for this order. WARNING: The contents from this URL should not be stored. The addresses have limited existense because of security concerns. |
status | HrefLink | Link to fetch the status of the order. Returns a Order status on a GET request. |
userInformation | HrefLink | Address of a wep page that displays the status of the order. This link is intended for the use of the end user and is sensitive as it also gives access to modify some elements of the order. |
Order status
Parameter | Type | Description |
---|---|---|
orderId | String | The id of the order. Can be up to 13 char |
orderStatus | String Examples: [received, delivered, cancelled] | The status of the order. This list is non exhaustive so please take care to display/handle any new statuses that is introduced. |
Deprecated, use pickupTime instead! The date when the order parcels will be picked up by Porterbuddy. Depending on the delivery area, the parcels may be picked up one or more days before the delivery date. | ||
pickupTime | DateTime | Calculated date and time the order needs to be picked up to be delivered on time. |
statusUpdatedAt | DateTime | The timestamp of the last order status change, as ISO timestamp with time zone. |
orderReference | String | Order reference as specified in order request / shipment details update |
tmsReference | String | TMS reference as specified in order request |
Href Link
Parameter | Type | Description |
---|---|---|
href | String | Link for the given role. |
Implementation hints
Idempotency Keys
Requests to create orders use an idempotency key specified as header to identify duplicate requests. This is intended to prevent orders to be created twice in our system by an accidentally repeated request, e.g. in case the request was handled successfully on Porterbuddy's side, but too slow, so it would trigger a timeout on the integrator's side. Thus, the idempotency key should be chosen in a way that it corresponds to the order to be created with the request, e.g. your unique order reference, a hash value of the body data, or similar, the only requirement is that if the data is for the same order on your side, the idempotency key must be the same. If an order request is handled with an idempotency key already used on an existing order, the response returned contains the dataset for the existing order.
Phone Number Validation
Phone numbers are validated using libphonenumber. The validation rules applied to the national number depend on the specified country code, for that reason, a documentation of the validation rules is not provided by the libphonenumber developers. Some hints for passing validation are these:
* country codes can be specified both in 00XX
as well as in +XX
form
* it is advised to remove white space in phone numbers
* length validation depends on country. For Norway, national numbers are expected to be 8 digits long. For other countries, other rules apply.
* Phone numbers consisting only of zeros will fail validation.
Update shipment details
This update is supposed to be used whenever you need to update the packages and shipment information after placing the order. Note that the parcel list needs to be complete, including constraints, as any content here overwrites the existing information on the order.
PUT https://api.porterbuddy.com/order/<orderId>/update-shipment-details
Example request:
curl -X PUT \
https://api.porterbuddy-test.com/order/<orderId>/update-shipment-details \
-H 'x-api-key: <your_api_key>' \
-H 'Content-Type: application/json' \
-d '{
"parcels" : [
{
"description": "Shoes",
"widthCm": 1,
"heightCm": 40,
"depthCm": 1,
"weightGrams": 2000,
"parcelShipmentIdentifier": "12345",
"constraints": {
"temperature": {
"maximumCelsius": 25.0,
"minimumCelsius": 8.0
}
}
}
],
"shipmentIdentifier": "12345",
"orderReference": "order-#12345678",
"verifications": {
"minimumAgeCheck": null,
"requireSignature": false,
"deliveryVerification":"CONTACTLESS",
"confirmCustomerReceipt": false
}
}'
Example response:
{
"_links": {
"self": {
"href": "https://api.porterbuddy-test.com/order/1702918291025"
},
"labelInfo": {
"href": "https://api.porterbuddy-test.com/order/1702918291025/label"
}
}
}
Arguments
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
parcels | Yes | List<Parcel> | none | Information about the parcels. |
shipmentIdentifier | No | Digits[5-20] | none | Shipping identifier. |
orderReference | No | String | none | Your internal order reference. If this parameter is not set, the current order reference will not be removed. |
verifications | No | VerificationsUpdate | none | Update for the verifications to be performed by the courier. Only non-null properties in this object will be updated. |
VerificationsUpdate
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
minimumAgeCheck | No | Integer | null | Should the courier verify the age of the recipient is equal to or above this value? Package will not be delivered if recipient is younger than the age provided. |
requireSignature | No | Boolean | null | Should the recipient sign for the package? Note: Taking signatures is currently replaced with confirmation via recipient pincode. |
confirmCustomerReceipt | No | Boolean | null | Should the recipient be asked to confirm that the parcel is received and taken inside (for contactless delivery)? |
deliveryVerification | No | DeliveryVerification | null | Enum to select the delivery verification mode. |
Response
Parameter | Type | Description |
---|---|---|
_links | OrderResponseLinks | Links for this resource. |
Set parcel details
Used to append information to a parcel. It will ignore missing keys and null values, and can thus only add or change information on a parcel.
If you need to remove parcel information use /update-shipment-details
. A prerequisite for using this endpoint is that the parcel has a parcelShipmentIdentifier.
This can either be set at order creation, or via /update-shipment-details
.
PUT https://api.porterbuddy.com/order/<orderId>/set-parcel-details/<parcelShipmentIdentifier>
Example request:
curl -X PUT \
https://api.porterbuddy.com/order/<orderId>/set-parcel-details/<parcelShipmentIdentifier> \
-H 'x-api-key: <your_api_key>' \
-H 'Content-Type: application/json' \
-d '{
"description": "Shoes",
"widthCm": 1,
"heightCm": 40,
"depthCm": 1,
"weightGrams": 2000,
"constraints": {
"temperature": {
"maximumCelsius": 25.0,
"minimumCelsius": 8.0
}
}
}'
Arguments
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
description | No | String | none | Description of the parcel and contents |
widthCm | No | Int | none | The width of the parcel in cm |
heightCm | No | Int | none | The height of the parcel in cm |
depthCm | No | Int | none | The depth of the parcel in cm |
weightGrams | No | Int | none | The weight of the package in grams |
constraints | No | ParcelConstraints | none | Constraints for parcel delivery. * Make sure not to change the constraints after the labels for the order are printed. |
Response
200 OK if the request was successful.
404 Not Found if no parcel is found for combination of orderId and parcelShipmentIdentifier.
Fetch labels
The order creation and update shipment information endpoints contain a URL to fetch label information from. This endpoint can be called to receive URLs for downloading label files.
GET https://api.porterbuddy-test.com/order/<orderId>/label
Example Request
curl -X GET \
https://api.porterbuddy-test.com/order/1702918291025/label \
-H 'x-api-key: <your_api_key>' \
-H 'Accept: application/json'
Example response
{
"shipmentLabelUrl": "https://api.porterbuddy-test.com/order/1702918291025/label/4ZvwE4kloM3vyQN4tuO5UC",
"packages": [{
"labelUrl": "https://api.porterbuddy-test.com/order/1702918291025/label/4ZvwE4kloM3vyQN4tuO5UC/5f6e4f5f-7ce4-4568-b7d1-5fc8c8c6af10",
"parcelShipmentIdentifier": "12345"
}]
}
Response
Parameter | Type | Description |
---|---|---|
shipmentLabelUrl | URL | Label document containing all labels for the order, one for each specified parcel |
packages | PackageLabelInfo | List of label urls for each parcel |
PackageLabelInfo
Parameter | Type | Description |
---|---|---|
labelUrl | URL | Label document for the parcel. This document contains exactly 1 label. |
parcelShipmentIdentifier | String | Shipment Identifier for the individual parcel. |
The label documents can be retrieved by fetching the document from the URLs in this response. The label document endpoints do not require authentication, but contain a unique (per order) token as part of the URL to ensure they can only be used by authorized parties.
Label File Format
Labels can be retrieved in various format. The default format is PDF, and we recommend using PDFs for the labels due to the superior behavior when printing. Other label formats can be requested by setting the Accept
header of the request to fetch the label content.
- PDF: The default format, can also be explicitly required by setting
Accept
toapplication/pdf
. - PNG: Can be requested by setting
Accept
toimage/png
. The PNG image is created by internally converting the PDF label, so generating it will take longer than when requesting a PDF. - ZPL: Label in Zebra Printer Language, can be requested by setting
Accept
toapplication/zpl
. The label will be returned as ZPL source in utf-8 encoding. The printer needs to be configured for the label size (see below) and 203 dpi print resolution.
Label Size
The default size for Porterbuddy labels is 102mm*192mm (width*height). Labels must be printed in 100% size. If smaller labels are necessary, Porterbuddy offers the following options:
- Porterbuddy can set a default smaller label size of 102mm*102mm to be returned.
- The label size for each label can be influenced by adding a query parameter
size
to the request to fetch the label document.size=REGULAR
will force the label(s) to be generated in the regular format of 102*192,size=SMALL
will return the 102*102 sized version. Calls to the label URLs without the query parameter will return the default size as configured.
Fetch order status
The order status can be retrieved using the URL returned from the Create Order call. The order status contains the pickup date, which is updated in case the recipient changes the delivery day, so it should be fetched regularly.
GET https://api.porterbuddy.com/order/<orderId>/status
Example Request
curl -X GET \
https://api.porterbuddy-test.com/order/<orderId>/status \
-H 'x-api-key: <your_api_key>' \
-H 'Accept: application/json'
Example Response
{
"orderId": "1702918291025",
"orderStatus": "ready",
"pickupDate": "2025-02-13",
"pickupTime": "2025-02-13T15:00:00+02:00",
"statusUpdatedAt": "2021-03-25T10:30:35.742857Z",
"orderReference": "order-#12345678",
"tmsReference": "order-#12345678"
}
Fetch order status with order reference
The order status can also be retrieved using order reference. The order status contains the pickup date, which is updated in case the recipient changes the delivery day, so it should be fetched regularly.
GET https://api.porterbuddy.com/order/order-reference/<order-reference>/status
Example Request
curl -X GET \
https://api.porterbuddy-test.com/order/order-reference/<order-reference>/status \
-H 'x-api-key: <your_api_key>' \
-H 'Accept: application/json'
Example Response
{
"orderId": "1702918291025",
"orderStatus": "ready",
"pickupDate": "2025-02-13",
"pickupTime": "2025-02-13T15:00:00+02:00",
"statusUpdatedAt": "2021-03-25T10:30:35.742857Z",
"orderReference": "order-#12345678",
"tmsReference": "order-#12345678"
}
Fetch order status with TMS reference
The order status can also be retrieved using TMS reference. The order status contains the pickup date, which is updated in case the recipient changes the delivery day, so it should be fetched regularly.
GET https://api.porterbuddy.com/order/tms-reference/<tms-reference>/status
Example Request
curl -X GET \
https://api.porterbuddy-test.com/order/tms-reference/<tms-reference>/status \
-H 'x-api-key: <your_api_key>' \
-H 'Accept: application/json'
Example Response
{
"orderId": "1702918291025",
"orderStatus": "ready",
"pickupDate": "2025-02-13",
"pickupTime": "2025-02-13T15:00:00+02:00",
"statusUpdatedAt": "2021-03-25T10:30:35.742857Z",
"orderReference": "order-#12345678",
"tmsReference": "order-#12345678"
}
Response
see Order Status
Confirmed Parcel Scan
Note: We provide a solution to scan outgoing parcels on our partner portal. This endpoint is only intended to be used when integrating scanning of outgoing parcels into an own solution.
This endpoint confirms that the parcels with the specified parcel shipment identifiers belonging to the specified order have been scanned and are confirmed to be sent. The list of parcel shipment identifiers is matched agains the parcel shipment identifiers specified in the parcel data of the order. In case unknown shipment identifiers are specified, the response contains a list of all unknown identifiers, and the HTTP Response code is 422.
PUT https://api.porterbuddy.com/order/<orderId>/confirmed-scan
Example Request
https://api.porterbuddy-test.com/order/<orderId>/confirmed-scan \
-H 'x-api-key: <your_api_key>' \
-H 'Content-Type: application/json' \
-d '{
"parcelShipmentIds": ["12345", "67890"]
}
Example Response
{
"unknownIds": []
}
Arguments
Parameter | Required | Type | Default | Description |
---|---|---|---|---|
parcelShipmentIds | Yes | List<String> | none | parcel shipment identifiers to confirm to have been scanned |
Place a consolidated order
Example request:
curl -X POST \
https://api.porterbuddy-test.com/order \
-H 'x-api-key: <your_api_key>' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: <your idempotency key>' \
-d '{
"origin": {
"name": "Nils Johansen (Sender)",
"address": {
"streetName": "Keysers Gate",
"streetNumber": "3",
"postalCode": "0165",
"city": "Oslo",
"country": "Norway"
},
"email": "testemail+sender@porterbuddy.com",
"phoneCountryCode": "+47",
"phoneNumber": "65127865"
},
"destination": {
"name": "Roger Olsen (Recipient)",
"address": {
"streetName": "Høyenhallveien",
"streetNumber": "25",
"postalCode": "0678",
"city": "Oslo",
"country": "Norway"
},
"email": "testemail+recipient@porterbuddy.com",
"phoneCountryCode": "+47",
"phoneNumber": "65789832",
"consolidatedWindow":{
"token": "20hRsgz8AovrLjeOldJ2Wg==:yhr85il4/swdgiEP/DG2wg==:2hBoFcmyTNLp/CTfX3sTGslOJr9sXAMxHggqq/h6tGmUuCEB2Vfy8uyNIWfg3qf6d7nj84Aj2sbwMLK2hETe14L4qgnlZHVSkBcktYPc6VCp9vEZhXErpQS3HoSyRU+mVcF2SNGP4s5TI5x7S6oq4Q=="
},
"verifications": {
"minimumAgeCheck": null,
"requireSignature": false,
"deliveryVerification":"CONTACTLESS",
"confirmCustomerReceipt": false
}
},
"parcels" : [
{
"description": "Shoes",
"widthCm": 1,
"heightCm": 40,
"depthCm": 1,
"weightGrams": 2000,
"parcelShipmentIdentifier": "12345"
}
],
"product": "delivery",
"courierInstructions": "Test",
"orderReference": "NJ12345"
}'
Consolidated orders (the "Samlevert" product) are created via the same request as regular orders, by using the token from the consolidated window returned in the availability call to fill the data for the "consolidatedWindow" property in the create order request. This should be done if the customer has chosen the consolidated delivery option in the checkout. The data model for request and response is explained in the section Place Order, so this section only contains the different example request, the response has the same structure as for the non-consolidated case.
Note: The example request will not work as is, as to create an actually consolidated order, several conditions have to be met:
- the customer needs to already have an orders placed
- the cutoff for the delivery window chosen in the existing order is still in the future
- the consolidated window token is not expired yet.
If the consolidated window token has expired or is invalid, the order is created using the "best-available" approach.
POST https://api.porterbuddy.com/order
Fetching Order Data
Data for an existing order can be fetched from the endpoint /order/<orderId>/self
. This endpoint will return a basic data set
for placed orders that also includes links to the label endpoint and customer tracking page. The API for this endpoint can be
considered stable, so it's preferable to use this endpoint instead of /order/<orderId>
to retrieve order information.
Example Request
curl -X GET \
https://api.porterbuddy-test.com/order/<orderId>/self \
-H 'x-api-key: <your_api_key>' \
-H 'Accept: application/json'
Example Response
{
"id": "13218",
"created": "2021-05-31T07:13:41.525648Z",
"deliverFrom": "2021-06-10T10:00:00Z",
"deliverUntil": "2021-06-10T14:00:00Z",
"pickupAddress": {
"streetName": "Keysers Gate",
"streetNumber": "3",
"postalCode": "0165",
"city": "Oslo",
"country": "Norway",
"location": {
"latitude": 59.91698083192638,
"longitude": 10.743384544175152
},
"addressType": "NORMAL",
"addressValidity": "VALID",
"fullAddressString": "Keysers Gate 3, 0165 Oslo, Norway"
},
"destinationAddress": {
"streetName": "Høyenhallveien",
"streetNumber": "25",
"postalCode": "0678",
"city": "Oslo",
"country": "Norway",
"location": {
"latitude": 59.90325905025783,
"longitude": 10.81379348938312
},
"addressType": "NORMAL",
"addressValidity": "VALID",
"fullAddressString": "Høyenhallveien 25, 0678 Oslo, Norway"
},
"pickupRegion": "OSLO",
"destinationRegion": "OSLO",
"recipientName": "Roger Olsen (Recipient)",
"recipientPhoneNumber": "65789832",
"recipientPhoneCountryCode": "+47",
"recipientEmail": "testemail+recipient_ves4ab9qjc@porterbuddy.com",
"pickupName": "Nils Johansen (Sender)",
"pickupPhoneNumber": "65127865",
"pickupPhoneCountryCode": "+47",
"pickupEmail": "testemail+sender@porterbuddy.com",
"pickupPin": "02766",
"deliveryType": "SCHEDULED",
"status": "ready",
"xxl": false,
"token": "1VgOBLIHOQSNNxaiS6AaVt",
"parcels": [{
"description": "Parcel description",
"widthCm": 1,
"heightCm": 40,
"depthCm": 1,
"weightGrams": 2000,
"volumeCm3": null,
"parcelShipmentIdentifier": null,
"parcelId": "49883d8a-4319-4f7d-a081-132e0a7d1233",
"locationToken": null,
"originAddress": null,
"constraints": null,
"volume": 40
}],
"verifications": {
"minimumAgeCheck": 16,
"leaveAtDoorstep": false,
"idCheck": true,
"requireSignature": false,
"onlyToRecipient": true,
"deliveryVerification":"REQUIRE_PIN",
"confirmCustomerReceipt": false
},
"orderReference": "c46e068c-a3bb-4429-ac12-f81ee3ce9e86",
"tmsReference": "511bd245-8204-4330-98a3-5e58be8ba1eb",
"statusUpdatedAt": "2021-05-31T07:13:41.525648Z",
"pickupDate": "2021-06-10",
"pickedAt": null,
"scannedAtPartner": null,
"scannedAtHub": null,
"deliveredAt": null,
"links": {
"labelInfo": "https://api.porterbuddy.com/order/13218/label",
"userInformation": "https://tracking.porterbuddy.com/74nBxcSLzQI0FIjJuZZHRQ"
}
}
Response
Parameter | Type | Description |
---|---|---|
id | String | The Porterbuddy order id |
created | DateTime | Order creation data |
deliverFrom | DateTime | Start of the delivery window |
deliverUntil | DateTime | End of the delivery window |
pickupAddress | Resolved Address | Specified origin address for the order, usually the retailer's warehouse address |
destinationAddress | Resolved Address | Destination address for the order (recipient address) |
pickupRegion | String | Region the order will be picked up (e.g. "OSLO", "BERGEN", ...). The list is non-exhaustive, so make sure to handle new values properly. |
destinationRegion | String | Region the order will be delivered to |
recipientName | String | Name of the recipient |
recipientPhoneNumber | String | Recipient's phone number |
recipientPhoneCountryCode | String | Country code for the recipient's phone number |
recipientEmail | String | The recipient's email address |
pickupName | String | Pickup name (the sender name specified in the order create request and printed on the label) |
pickupPhoneNumber | String | Sender's phone number |
pickupPhoneCountryCode | String | Country code for the sender's phone number |
pickupEmail | String | Sender email as specified in the order create request - used for sender receipts if enabled |
pickupPin | String | 5-digit pin code from the parcel label |
deliveryType | String | Type of the delivery - currently always "SCHEDULED" |
status | String | Current order status, e.g. [received, delivered, cancelled]. The list is non-exhaustive, so make sure to handle new values properly. |
xxl | Boolean | Indicates that the order is oversized/overweight |
token | String | Order token |
parcels | List<Parcel> | Current parcel specification |
verifications | Verifications | Verifications to perform on delivery |
orderReference | String | External order reference as specified |
tmsReference | String | External tms reference as specified |
statusUpdatedAt | DateTime | Timestamp of the last status update |
pickupDate | Date | Calculated pickup date, based on the specified delivery date |
pickedAt | DateTime | Timestamp the order was picked up by the courier |
scannedAtPartner | DateTime | Timestamp the order was scanned by the partner (retailer) |
scannedAtHub | DateTime | Timestamp of the first time the order was scanned at a Porterbuddy hub |
deliveredAt | DateTime | Timestamp the order was delivered at |
links | OrderInfoLinks | Links for customer tracking page and label endpoint |
Order Info Links
Parameter | Type | Description |
---|---|---|
labelInfo | URL | Link to the label info endpoint |
userInformation | URL | Link to the recipient tracking page |
Order Status Webhook
Example Order Request
{
"origin": {
"name": "Nils Johansen (Sender)",
"address": {
"streetName": "Keysers Gate",
"streetNumber": "3",
"postalCode": "0165",
"city": "Oslo",
"country": "Norway"
},
"email": "testemail+sender@porterbuddy.com",
"phoneCountryCode": "+47",
"phoneNumber": "65127865",
"pickupWindows":[{"start":"2025-02-13T10:00+02:00", "end":"2025-02-13T20:00+02:00"}]
},
"destination": {
"name": "Roger Olsen (Recipient)",
"address": {
"streetName": "Høyenhallveien",
"streetNumber": "25",
"postalCode": "0678",
"city": "Oslo",
"country": "Norway"
},
"email": "testemail+recipient@porterbuddy.com",
"phoneCountryCode": "+47",
"phoneNumber": "65789832",
"deliveryWindow":{
"start": "2025-02-13T17:30:00+01:00",
"end": "2025-02-13T19:30:00+01:00",
"token": "20hRsgz8AovrLjeOldJ2Wg==:yhr85il4/swdgiEP/DG2wg==:2hBoFcmyTNLp/CTfX3sTGslOJr9sXAMxHggqq/h6tGmUuCEB2Vfy8uyNIWfg3qf6d7nj84Aj2sbwMLK2hETe14L4qgnlZHVSkBcktYPc6VCp9vEZhXErpQS3HoSyRU+mVcF2SNGP4s5TI5x7S6oq4Q=="
},
"verifications": {
"minimumAgeCheck": null,
"requireSignature": false,
"deliveryVerification":"CONTACTLESS",
"confirmCustomerReceipt": false
}
},
"parcels" : [
{
"description": "Shoes",
"widthCm": 20,
"heightCm": 10,
"depthCm": 35,
"weightGrams": 600,
"parcelShipmentIdentifier": "12345",
"constraints": {
"temperature": {
"maximumCelsius": 25.0,
"minimumCelsius": 8.0
}
}
}
],
"items": [
{
"name":"Fancy Sneakers",
"sku":"FANCYSNEAKER43",
"weightGrams":"600",
"widthCm":20,
"heightCm":10,
"depthCm":35,
"description":"Fancy sneakers (red/blue) in size 43",
"category":"shoes",
"brand":"Fancy Footwear Corp.",
"imageUrl":"https://awesomewebshop.com/images/5fd71d6f-b0be-4480-900f-f3d008a0bc62.png",
"price": {
"fractionalDenomination":"79900",
"currency":"NOK"
},
"barCode": {
"value":"123-456-789",
"type":"CODE128"
}
}
],
"product": "delivery",
"courierInstructions": "Test",
"orderReference": "order-12345",
"tmsReference": "order-12345",
"statusWebhookUrl": "https://api.myshopbackend.com/statusUpdated"
}
To receive notifications when the order status or pickup date changes, a webhook URL can be specified in the order request as property statusWebhookUrl
.
This URL gets called with HTTP POST every time the status or pickup date changes, and contains the same body as the Order Status Endpoint.
The target endpoint must be reachable from the internet without authentication and respond within 10 seconds. If a status outside the [200..299] range is returned,
or the endpoint is unreachable, the call will be retried 2 times with a rising interval. The response body will be ignored by our backend.
Webhook call body
Example Body
{
"orderId": "1702918291025",
"orderStatus": "canceled",
"pickupDate": "2025-02-13",
"pickupTime": "2025-02-13T15:00:00+02:00",
"statusUpdatedAt": "2021-06-08T12:16:40.32538Z",
"orderReference": "order-12345",
"tmsReference": "order-12345"
}
See Order Status
Verifying Webhook Authenticity
To verify that a call to the status webhook was originated by Porterbuddy, we add a header x-porterbuddy-hmac-SHA256
to the webhook calls.
This header contains a base64-encoded HMAC Digest of the call body, using the sender's API key as encryption secret. Authenticity can be verified by
calculating the HMAC digest of the request body on the recipient side, and comparing it to the header value.
Confirm order
This endpoint is a workaround for retailers using Klarna checkout, with the shipping assistant and Vipps payment. Confirming an order with this endpoint essentially tells Porterbuddy the order is considered paid for, and should be processed. The earlier this call is made the better, as it avoids delayed cancellations. In addition, it will trigger the sending of a confirmation and tracking link to the end user, in the cases where the order was not confirmed through the Klarna API (i.e Vipps payments).
PUT https://api.porterbuddy.com/order/<orderId>/confirm
Returns an empty 200 OK
if the call succeeded.
curl -X PUT \
https://api.porterbuddy-test.com/order/<orderId>/confirm \
-H 'x-api-key: <your_api_key>'
Errors
Http errors
Http response code | Reason |
---|---|
400 Invalid data | The request did not pass correctly formatted JSON |
401 Unauthenticated | Invalid or missing API key |
403 Forbidden | Your api key is invalid or the x-api-key header is missing |
422 Invalid schema | The JSON could not be parsed with valid structure and values |
500 Server error | Unknown server error :( |