API for Developers

Sections
    Add a header to begin generating the table of contents

    Introduction

    If you are using Shopify,  Magento 2 or WooCommerce we can provide integrations for you. This API is for customers on other platforms who want to connect to our systems directly.

    The API is REST and the following examples are in Python but should be very easy to translate to any other language.

    There are two endpoints:

    When a call is successful the API returns status code 200 OK (if you are viewing something or changing something) or 201 CREATED (if you have just created something new).

    If a call fails you may get 401 UNAUTHORIZED (which means your secret is wrong), 404 NOT FOUND (if you are trying to view something that doesn’t exist) or 400 BAD REQUEST (for all other expected errors).

    A 500 status code means there has been an unexpected error and Move Fresh will have been notified to investigate.

    Whenever there is an error you will be sent a JSON response with the error code and a message like this:

    {
        "error": 10,
        "message": "Invalid or missing secret"
    } 

    A full list of errors appears at the end of this document.

    Ping

    Just to make sure everything is working you should start with a ping:

    import requests
    
    response = requests.get('https://app.movefresh.com/api/ping/',
       headers={'Authorization': 'Bearer <<YOUR SECRET>>'}
    )
    
    if response.status_code == 200:
        print(response.json())
    
    elif response.status_code == 401:
        print('Your secret is wrong.')

    You should get the message “PONG” coming back.

    Create Order

    We recommend that orders are pushed to us quite frequently, either by a queue or perhaps every 10 minutes through a scheduled job.

    Here is some simple code to create an order:

    import requests
    
    response = requests.post('https://app.movefresh.com/api/order/create/',
       headers={'Authorization': 'Bearer <<YOUR SECRET>>'},
       json={
           'order_number': '1001',
           'delivery_name': 'Bob Test',
           'delivery_address_1': '2 Rennie Square',
           'delivery_town': 'Livingston',
           'delivery_postcode': 'EH2 3BU',
           'delivery_country': 'GB',
           'lines': [
               {'sku': 'SK1010', 'quantity': 10},
               {'sku': 'SK1082', 'quantity': 10}
           ]
       }
    )

    This more complex example shows all of the possible features:

    import base64
    import requests
    
    # The following two lines are optional.
    with open('document.pdf', 'rb') as f:
        document = base64.b64encode(f.read()).decode()
    
    response = requests.post('https://app.movefresh.com/api/order/create/',
       headers={'Authorization': 'Bearer <<YOUR SECRET>>'},
       json={
           'order_number': '1001',
           'reference': 'R123',  # Optional
           'ship_date': '2023-09-05',  # Optional
           'courier': 'dhl_parcel',  # Optional
           'courier_service': '210',  # Required if courier has been specified
           'customer_type': 'consumer',  # Optional
           'channel': 'amazon',  # Optional
           'delivery_name': 'Bob Test',
           'delivery_organisation': 'Move Fresh Ltd', # Optional
           'delivery_address_1': '2 Rennie Square',
           'delivery_address_2': 'Brucefield Ind Estate', # Optional
           'delivery_town': 'Livingston',
           'delivery_county': 'West Lothian', # Optional
           'delivery_postcode': 'EH2 3BU',
           'delivery_country': 'GB',
           'delivery_email': 'bob@example.com',  # Optional
           'delivery_phone': '447123456789',  # Optional
           'delivery_instructions': 'Leave in porch if out',  # Optional
           'insurance_value': 500,  # Optional
           'message': 'You now have 100 loyalty points!',  # Optional
           'priority': 'high',  # Optional
           'lines': [
               {'sku': 'SK1010', 'quantity': 10},
               {'sku': 'SK1082', 'quantity': 10}
           ],
           'documents': [document]  # Optional
       }
    )
    
    if response.status_code == 201:
        print('Order created successfully.')
    
    elif response.status_code == 400:
        print('Error:', response.json()['message'])
    
    elif response.status_code == 500:
        print('Unhandled error please contact Move Fresh.')

    The fields are as follows:

    • order_number – is the Order Number on your store and must be unique and as it is frequently used we would recommend something easily human readable (max 20 characters including brand prefix)
    • reference – is an additional reference which is shown on the order page and printed on the Delivery Note, it could be used as your internal reference number or a purchase order number for B2B orders (max 20 characters) [optional]
    • ship_date – is for orders that you want to hold in the Deferred state and  ship in the future. The earliest it can be set to is tomorrow. The format is ‘YYYY-MM-DD’. On the day the order is due to be shipped it will be moved out of the Deferred state round about 1AM.
    • courier – is ‘collection’, ‘dpd’, ‘dhl_parcel’, ‘dhl_parcel_international’, ‘hermes’, ‘parcelforce’, ‘parcelforcepallet’, ‘royalmail’ or ‘yodel’. Set ‘collection’ if you do not want a courier but are planning to arrange a collection from the warehouse yourself.
    • courier_service – see Courier Services at bottom of page
    • customer_type – is ‘business’ for B2B, or ‘consumer’ for B2C which is the default [optional]
    • channel – is ‘amazon’, ‘ebay’, ‘etsy’ or ‘direct’ (the default) and is displayed on the order page and can be used for IOSS declarations and for setting priority [optional]
    • delivery_name – person’s name (max 60 characters)
    • delivery_organisation – company or organisation (max 60 characters) [optional]
    • delivery_address_1 – first line of address (max 60 characters)
    • delivery_address_2 – second line of address (max 60 characters) [optional]
    • delivery_town – town or city (max 60 characters)
    • delivery_county – county, province or state  (max 60 characters) [optional but state required in USA]
    • postcode – postal code or Zip code, see Supported Countries for validation rules, not required for Hong Kong or the United Arab Emirates (max 10 characters)
    • delivery_country – ISO-3166 country code (max 2 characters) but note that only countries we ship to are supported as shown below
    • delivery_email – an email address for the courier (max 254 characters)[optional]
    • delivery_phone – a phone number for the courier (include country code)
    • delivery_instructions – a safe place parcels can be left (max 60 characters)[optional]
    • insurance_value – a value in GBP that the shipment should be insured for, insurance  may be rounded up to the nearest band, do not use before discussing price, terms and limitations with your account manager (max 2500, whole numbers only)[optional]
    • message – a message for the customer to be printed on the delivery note (max 128 characters) [optional]
    • priority – is ‘low’, ‘normal’ (default if not supplied), ‘high’ or ‘urgent’; it’s recommended that urgent is reserved for manual use to expedite specific orders [optional]
    • documents – a list of base64 encoded PDF’s that are to be printed and sent out with the order [optional]

    Note that multiple order lines for the same product will be consolidated. So 10 x SKU1 and 20 x SKU1 will be consolidated to 30 x SKU1.

    If the courier service is not available it will be adjusted if possible. For example attempting to send to the Scottish Highlands on a next day service will usually be downgraded to a 48 hour service.

    Delivery email and phone are used to generate Pre Delivery Notifications (PDN’s) which allow the customer to reschedule deliveries and give them a timed delivery window. The fields are optional but are important for getting first time deliveries. A low rate of first time delivery may result in you paying a higher shipping charge.

    The optional documents list allows you to add one or more PDF’s to the order which will be printed out and put in the parcel along with the goods. These might be a personal promotion, a nutrition plan or an invoice.

    If successful, the status code 201 CREATED will be returned along with the origin and the shipping deadline. The origin is your order number with a brand prefix. The brand prefix for “Test Brand” would be “TB” so in this example the order number was 1001 so the origin would be “TB-1001”.

    The JSON returned will look something like this:

    {
        "origin": "TB-100"
    }

    The maximum length of the origin is 20 characters, so in this case the maximum length of the order_number would be 17 characters as the brand prefix “TB-” is three characters.

    We make sure that all of our clients have a different brand prefix to avoid confusion.

    Linking to Order Page

    You may want to link directly to orders from your CRM or other web application. This could be done for an order with the origin TB-1001 by linking to:

    https://app.movefresh.com/order/origin/TB-1001/

    Cancel Order

    If a customer changes their mind or fails a fraud check you may want to cancel the order before it is shipped. The following code would cancel an order with origin TB-1001.

    import requests
    
    response = requests.post('https://app.movefresh.com/api/order/TB-1001/cancel/',
        headers={'Authorization': 'Bearer <<YOUR SECRET>>'}
    )
    
    if response.status_code == 200:
        print('Order successfully cancelled.')
    
    elif response.status_code == 404:
        print('Order not found.')
    
    elif response.status_code == 400:
        print('Order cannot be cancelled')
        print(response.json()['message'])

    Orders can be cancelled even while being picked but can no longer be cancelled once the pick is complete.

    Set Order Priority

    After sending an order to Move Fresh it is possible to adjust the priority. This might happen in response to a customer service contact where it is necessary to ship the order as fast as possible.

    The possible priorities are ‘low’, ‘normal’, ‘high’ or ‘urgent’. An attempt to set a priority outside of these will generate an error 50.

    If an order status is In Process, Done or Cancelled, then it is not possible to update the priority and an error 80 will be returned.

    import requests
    
    response = requests.post('https://app.movefresh.com/api/order/TB-1001/priority/',
        headers={'Authorization': 'Bearer <<YOUR SECRET>>'},
        json={'priority': 'high'}
    )
    
    if response.status_code == 200:
        print('Order priority updated.')
    
    elif response.status_code == 400:
        print(response.json()['message'])
    
    elif response.status_code == 404:
        print('Order not found.')

    Get Order Status

    You may want to check the order status at the end of each day to get the courier tracking details to email to your customers.

    In this example we are checking on the order TB-1001 that we created in the first example:

    import requests
    
    response = requests.get('https://app.movefresh.com/api/order/TB-1001/',
       headers={'Authorization': 'Bearer <<YOUR SECRET>>'}
    )
    
    if response.status_code == 200:
        result = response.json()
        print(result)
    
    elif response.status_code == 404:
        print('Order not found.')

    The JSON returned will look something like this:

    {
        "status": "ready",
        "message": "Ready to be picked and dispatched",
        "deadline": "2023-06-06T17:00:00+00:00"
    }

    The following statuses may be returned:

    • pending – order has just been received but not processed
    • waiting – order is waiting for stock to be available
    • ready – stock has been allocated and the order is ready to pick
    • in_process – is being picked and packed
    • packed – the order has been packed and is waiting for a courier label
    • done – the order is complete
    • on_hold – the order has been put on hold by a member of the team
    • deferred – certain orders are deferred to be dispatched at a particular time
    • cancelled – order has been cancelled and will not dispatched

    The deadline is the date and time that the order should be dispatched according to the SLA and will be in the UTC timezone.

    If the order is done and has been shipped the deadline will no longer be available but there will be some extra courier information:

    {
        "status": "done",
        "message": "1 parcel dispatched by DHL Parcel (Next Day YYY/Safe)",
        "parcels": 1,
        "courier": "dhl_parcel",
        "courier_service": "210",
        "courier_reference": "123456789",
        "courier_link": "https://track.example.com/123456789"
    }

    Note that the courier link is not always available, depending on how the order was shipped.

    Order Webhooks

    Webhooks can be used to notify your application when an event happens. At the moment webhooks are only available for changes in order status.

    A webhook is created by completing the form in the web admin with the URL on your server that you want to be called every time an order is completed.

    That URL will get a post containing details of the order in JSON. It will look something like this:

    { 
        "topic": "order/in_process",
        "date": "2023-04-03T10:13:27.575900+00:00",
        "text": "Order TB-1001 is In Process",
        "order": {
            "order_number": "1001",
            "origin": "TB-1001",
            "status": "in_process"
        }
    }

    In the event the order has the status “done” then it will have some extra fields and look like this:

    { 
        "topic": "order/done",
        "date": "2023-04-03T10:13:27.575900+00:00",
        "text": "Order TB-1001 dispatched by DHL Parcel reference 123456789",
        "order": {
            "order_number": "1001",
            "origin": "TB-1001",
            "status": "done",
            "parcels": 1,
            "courier": "dhl_parcel",
            "courier_service": "210",
            "courier_reference": "123456789",
            "courier_link": "https://track.example.com/123456789"
        }
    }

    The fields are:

    • topic – the topic will either be “order/<< status >>” if the webhook has been setup for that particular status change or “order/any” if the webhook is for all status changes.
    • date – is when the webhook was generated in ISO 8601 format. It will always be in UTC. If this is more than a few seconds ago, it indicates that the webhook has been delayed.
    • text – a summary useful if you want to post the webhook to Slack, Microsoft Teams or similar.
    • order_number – what was originally supplied when the order was created.
    • origin – the order number with your brand prefix added to make it unique in our system.
    • status – the current status of the order which is useful if the webhook has been set for all status changes using the “order/any” topic. It is possible for this not to match the topic, if the order status changes as the webhook is being triggered.
    • parcels – will usually be one but may be more if a large number of items have been ordered which cannot fit into one box.
    • courier and courier_service – will usually be what was specified when the order was created but can sometimes be changed, for example, a next day service may be downgraded to a 48 hour service if the delivery address is in a remote area.
    • courier_link – will go to the courier’s website to give up to date tracking information. This link is usually not live until the parcel has arrived in the courier’s depot and been scanned.

    It is important to verify the post is genuine. All webhook posts are signed using hash-based message authentication code (HMAC) with SHA-256 using your API secret. The signature is in an HTTP header called X-Hmac-Sha256 which should match the HMAC signature of the body of the post.

    Here is a simplified example in Python showing a webhook being received in the Django web framework:

    import hashlib
    import hmac
    import json
    from django.views.decorators.csrf import csrf_exempt
    
    @csrf_exempt
    def webhook(request):
        header = request.headers['X-Hmac-Sha256']
    
        signature = hmac.new(
            '<< YOUR SECRET >>'.encode('utf-8'),
            msg=request.body,
            digestmod=hashlib.sha256
        ).hexdigest()
    
        if header == signature:
            # Signature verified, safe to process.
            payload = json.loads(request.body)

    Note that the comparison at the end could theoretically expose a timing attack, as the speed of the == operator might reveal how many characters in the header were correct (the more characters that are correct, the slower it will be). It would be better to rewrite with a secure compare method:

        if hmac.compare_digest(header, signature): 

    This method is guaranteed to take exactly the same time to process irrespective of how many characters in the header and signature match.

    Once the webhook has been processed your application should return a 200 status code or any other 2xx status code to indicate success. Any content returned will be ignored.

    It is important to return the success code quickly. If you need to do slow processing then this should be done after sending the 200 response. The timeout is set to 20 seconds but we would recommend you respond within five seconds.

    If there are 20 errors without any successes, then the webhook will be stopped. Any successful 2xx response will reset the error count.

    Webhooks are not designed to be completely reliable so your code should check the status of orders that have not been completed once a day to make sure nothing has been missed.

    It is also possible that this webhook will be called more than once. Usually this will be because Move Fresh staff have decided to cancel the courier job and create a new one to correct an error.

    The User-Agent of the webhook is “Move Fresh Webhook/1.0”. This should not be relied on for security but may be useful for searching web server logs.

    Stock Webhooks (Beta)

    Webhooks are also available for three stock events:

    • Stock -> Goods Received which triggers when goods for a purchase order are received in the warehouse.
    • Stock -> Adjustment for manual stock adjustments
    • Stock -> Quarantine for stock that is being moved into our out of quarantine
    • Stock -> Any all three of the above stock events

    All of these webhooks will post similar JSON like this:

    {
        "topic": "stock/adjustment",
        "date": "2023-04-03T10:13:27.575900+00:00",
        "text": "Inventory Adjustment:\n5 x [SKU101] Cola",
        "stock": {
            "id": 12345,
            "movement": "adjustment",
            "reason": "stocktake",
            "lines": [
                {
                    "sku": "SKU101",
                    "change": 5
                }
            ]
        }
    }

    The id in the stock section is the id of the Goods Received Note, Inventory Adjustment or Internal Transfer that created the stock event.

    There are three possible movements:

    • received – for the “stock/received” topic
    • adjustment – for the “stock/adjustment” topic
    • quarantine – for the “stock/quarantine” topic

    The movement is mainly useful for the “stock/any” topic, as this topic could be any one of the three possible movements.

    If the movement is an adjustment then there will also be a reason. There are currently eight possible reasons which are:

    • client – Client requisition
    • correction – Correcting an error
    • customer – Customer return
    • packaging – Quality: packaging
    • product – Quality: product
    • shelf_life – Shelf life expired
    • stocktake – Stocktake adjustment
    • other – Other reason

    Goods Received will be like the example above except without a reason and with a “purchase_order” field which will be the Purchase Order number the goods have been received against.

    Finally, the lines contain the SKU’s that have a stock movement. The change can be either a positive or a negative number.

    Products and Stock

    It is good practice to check stock available once a day and update your store. We would recommend running this at night or during a quiet period when it is unlikely that stock movements will occur.

    import requests
    
    response = requests.get('https://app.movefresh.com/api/product/',
       headers={'Authorization': 'Bearer <<YOUR SECRET>>'}
    )
    
    if response.status_code == 200:
        results = response.json()
        for product in results['products']:
            print(product)
    
    else:
        print('Error:', response.status_code)

    The JSON returned will be like this:

    {'products': [
        "name": "Test Product",
        "sku": "TESTSKU",
        "temperature": "ambient",
        "available": 999,
        "is_active": true
    ]}

    Temperature can be ‘ambient’, ‘chilled’ or ‘frozen’.

    Note that available stock can be negative if we hold more orders than stock.

    Create Product

    Products can be created through the API which is useful if you have a very large range or regularly add new products.

    Here is the simplest possible call:

    import requests
    
    response = requests.post('https://app.movefresh.com/api/product/create/',
       headers={'Authorization': 'Bearer <<YOUR SECRET>>'},
       json={
           'name': 'Chicken Pie',
           'sku': 'CHICKPIE',
           'barcode': '900123456',
           'category': 'Pies'
        }
    )
    

    And a more complex call showing all possible options:

    import requests
    
    response = requests.post('https://app.movefresh.com/api/product/create/',
       headers={'Authorization': 'Bearer <<YOUR SECRET>>'},
       json={
           'name': 'Chicken Pie',
           'sku': 'CHICKPIE',
           'purchase_sku': 'CHICKPIE',  # Optional
           'barcode': '900123456',
           'category': 'Pies',
           'temperature': 'chilled',  # Optional
           'purchase_units': 1,  # Optional
           'sell_units': 1,  # Optional
           'track_lots': True,  # Optional
           'minimum_life_on_receipt': 30,  # Optional       'label_and_ship': False,  # Optional       'custom_product': False,  # Optional
           'hide_from_delivery_note': False,  # Optional
           'weight': 500,  # Optional
           'volume': 200,  # Optional
    
           # The following are required to ship outside the UK, otherwise optional.
           'customs_value': 3.95,
           'hs_code': '16023230',
           'country_of_origin': 'GB'
       }
    )
    
    if response.status_code == 201:
        print('Product created successfully.')

    The fields are:

    • name – is the product name printed on the packaging (max 128 characters)
    • sku – a unique Stock Keeping Unit which should match your e-commerce shop, limited to letters, numbers and a hyphen, we recommend you use capital letters (max 20 characters)
    • purchase_sku – the SKU used by your supplier, if omitted will be set to the sku (max 20 characters) [optional]
    • barcode – the barcode that has been printed on the product (max 20 characters)
    • category – the product category, if it does not exist it will be created (max 100 characters)
    • temperature – can be one of ‘ambient’, ‘chilled’ or ‘frozen’, use ‘ambient’ if no temperature control is required which is the default [optional]
    • purchase_units – the units used to buy from the supplier, for example 18 if you buy in cases of 18, the default is one [optional]
    • sell_units – the units used to sell to your customs, for example 10 if you sell in boxes of 10, the default is one [optional]
    • track_lots – track Lots and Best Before if the product has a shelf life, default is false [optional]
    • minimum_life_on_receipt – minimum days of shelf life required when products are received in the warehouse, 0 for no minimum which is the default [optional]
    • label_and_ship – no outer box required, product can be labelled and shipped as is, default is false [optional]
    • custom_product – engraved, personal label or other customisation, must be setup on your account before you can use, default if false [optional]
    • hide_from_delivery_note – used for products that should not be printed on the delivery note, default is false [optional]
    • weight – weight in g including packaging used to calculate courier rates and for customs declarations [optional, if left blank Move Fresh staff will weigh the product for you]
    • volume – in ml or cm³ used to calculate the best box to use [optional, if left blank Move Fresh staff will measure the product for you]
    • customs_value – the unit value in GBP for customs declarations (max 9999.99) [optional unless shipping outside the UK]
    • hs_code – the Harmonised System tariff code for customs declarations (max 10 characters) [optional unless shipping outside the UK]
    • country_of_origin – ISO-3166 country code (max 2 characters) [optional unless shipping outside the UK]

    Harmonised System tariff codes can be looked up at https://www.trade-tariff.service.gov.uk/browse. If you need help finding the right code, working out the customs value or understanding the rules of origin please get in touch with your account manager.

    Create Purchase Order (Beta)

    Purchase Orders are required for our Goods In process to let the distribution centre know what products are due to be received into the warehouse. There is a Xero integration for Purchase Orders. Purchase Orders are required for any form of goods in, even when a PO has not been sent to a supplier.

    Here is example code to create a Purchase Order:

    import requests
    
    response = requests.post('/api/po/create/',
        headers={'Authorization': 'Bearer <<YOUR SECRET>>'},
        json={
            'origin': 'PO-1234',
            'supplier': 'Test Food Ltd',
            'delivery_date': '2023-09-10',
            'lines': [
                {'sku': 'SK1010', 'quantity': 10},
                {'sku': 'SK1082', 'quantity': 10}
            ]
        }
    )
    

    The fields are:

    • origin – the Purchase Order number (max 20 characters)
    • supplier – the name of the supplier (max 100 characters)
    • delivery_date – the expected delivery date in the format YYYY-MM-DD.

    Delete Purchase Order

    Purchase Orders can also be deleted but only if they have a status of Waiting for Stock, Error or Cancelled.

    To delete a Purchase Order simply  call the DELETE method. This code would delete the Purchase Order created in the previous section.

    import requests
    
    requests.delete('/api/po/PO-1234/',
        headers={'Authorization': 'Bearer <<YOUR SECRET>>'}
    )
    

    Changes

    There are two API change processes to balance between delivering features quickly that are requested by clients while not breaking existing integrations.

    For non-breaking changes, which should require no action, we will simply email with at least two days of notice. The following four types of changes will use this process:

    • Changes to features that are in beta
    • Adding a new method to the API
    • Add new optional parameters to a method
    • Adding extra fields to a JSON return

    Any other change will go through a full deprecation process where at least 60 days of notice will be given along with a minimum of three emails over that period. We may also introduce a new version of the API if the change is significant.

    Courier Services

    Courier Courier Service Description
    Collection PARCEL Parcel collection from warehouse by client
    Collection PALLET Pallet collection from warehouse by client
    DPD 1^12 Next Day
    DPD 1^11 Two Day
    DPD 1^16 Saturday
    DPD 1^01 Sunday
    DPD 1^19 DPD Classic (EU)
    DHL Parcel 1 Next Day YYY
    DHL Parcel 2 Next Day 12:00 YYY
    DHL Parcel 4 Saturday YYY
    DHL Parcel 9 Next Day 10:30 YYY
    DHL Parcel 48 48 Hour**
    DHL Parcel 72 72 Hour**
    DHL Parcel 210 Next Day YYY/Safe
    DHL Parcel 211 Next Day 12:00 YYY/Safe
    DHL Parcel 212 Next Day 10:30 YYY/Safe
    DHL Parcel 215 Saturday YYY/Safe
    DHL Parcel 220 Next Day NYN
    DHL Parcel 221 Next Day 12:00 NYN
    DHL Parcel 222 Next Day 10:30 NYN
    DHL Parcel 225 Saturday NYN
    DHL Parcel International 204 International Road
    Hermes UK24 24 Hour
    Hermes UK24SIG 24 Hour Signature
    Hermes UK48 48 Hour
    Hermes UK48SIG 48 Hour Signature
    Parcelforce S09 Express 9AM
    Parcelforce S10 Express 10AM
    Parcelforce S12 Express AM
    Parcelforce SND Express 24
    Parcelforce SNDSAT Express 24 Saturday
    Parcelforce SCDP Express Sunday
    Parcelforce SUP Express 48
    Parcelforce Pallet PALLETS24 Pallets 24 Hour
    Royal Mail 1_CRL_F Royal Mail 24 Large Letter*
    Royal Mail 1_CRL_P Royal Mail 24 Parcel*
    Royal Mail 2_CRL_F Royal Mail 48 Large Letter*
    Royal Mail 2_CRL_P Royal Mail 48 Parcel*
    Royal Mail I_IE1_E Zone Sort Priority Parcel
    Royal Mail I_IE1_G Zone Sort Priority Large Letter
    Royal Mail I_PS9_E Max Sort Priority Parcel
    Royal Mail I_PG9_G Max Sort Priority Large Letter
    Royal Mail T_TPN_F Royal Mail Tracked 24 Large Letter
    Royal Mail T_TPN_P Royal Mail Tracked 24 Parcel
    Royal Mail T_TPS_F Royal Mail Tracked 48 Large Letter
    Royal Mail T_TPS_P Royal Mail Tracked 48 Parcel
    Yodel 2CMN Packet 48 UK
    Yodel 2VMP Medium Parcel 48 Pod UK
    Yodel 2VP Large Parcel 48 Pod UK
    Yodel 72POD Large Parcel 72 Pod Channel Islands

    * Replaced with improved services and should no longer by used.

    ** To be used in remote areas only. Next day services will be automatically downgraded.

    Error List

    HTTP Code HTTP Status Error Messsage
    401 Unauthorized 10 Invalid or missing secret.
    400 Bad Request 15 [Missing|Invalid] order number.
    400 Bad Request 20 Order <<origin>> has already been imported.
    400 Bad Request 30 No order lines.
    400 Bad Request 31 Invalid SKU: <<sku>>
    400 Bad Request 32 Invalid quantity: <<quantity>>
    400 Bad Request 40 Invalid courier.
    400 Bad Request 41 [Standard|Chilled] courier account does not exist.
    400 Bad Request 42 Courier service <<code>> does not exist.
    400 Bad Request 50 Invalid priority.
    400 Bad Request 55 Invalid temperature.
    400 Bad Request 60 Invalid customer type.
    400 Bad Request 70 Invalid or missing: <<address line>>
    400 Bad Request 80 Order is <<status>> and cannot be updated.
    404 Not Found 90 Order not found.
    405 Method Not Allowed 91 HTTP method <<method>> not allowed use <<method>>.
    400 Bad Request 97 Invalid PDF.
    400 Bad Request 98 Invalid base64 encoding.
    400 Bad Request 99 Invalid JSON.
    500 Internal Server Error An unhandled error has occurred and no error message will be returned. Move Fresh’s IT team have been notified.
    503 Service Unavailable Service unavailable due to maintenance. Try again later.

    Supported Countries

    British and Canadian postal codes are converted to uppercase and have an appropriate space added (if missing) before they are validated.

    Code Country Postal Code Validation
    GB United Kingdom ^([A-Z][A-HJ-Y]?\d[A-Z\d]? ?\d[A-Z]{2}|GIR ?0A{2})$
    AL Albania 4-digit
    AU Australia 4-digit
    AT Austria 4-digit
    BE Belgium 4-digit
    CA Canada ^[ABCEGHJ-NPRSTVXY]\d[ABCEGHJ-NPRSTV-Z] \d[ABCEGHJ-NPRSTV-Z]\d$
    CN China 6-digit
    DK Denmark Not validated
    FI Finland 4-digit
    FR France 5-digit
    DE Germany 5-digit
    GG Guernsey Not validated
    HK Hong Kong No postal codes
    HU Hungary 4-digit
    IS Iceland Not validated
    IE Ireland Not validated
    IM Isle of Man Not validated
    IT Italy 5-digit
    JE Jersey Not valdidated
    LV Latvia 4-digit
    LU Luxembourg 4-digit
    MC Monaco 98000
    NL Netherlands Not validated
    NZ New Zealand 4-digit
    NO Norway 4-digit
    PL Poland Not validated
    PT Portugal 4-digit
    SG Singapore 6-digit
    SI Slovenia 4-digit
    ZA South Africa 4-digit
    KR South Korea 5-digit
    ES Spain 5-digit
    SE Sweden 5-digit
    CH Switzerland 4-digit
    AE United Arab Emirates No postal codes
    US United States ^\d{5}(?:[-\s]\d{4})?$