Skip to content

click-and-drop-api

CI Tests PyPI version PyPI Downloads Ruff

This is an inofficial Python client for the Royal Mail Click & Drop API. The Click & Drop API allows you to import your orders, retrieve your orders and generate labels.

Read about Royal Mail's Click & Drop API:

Links for this package:

Installation

pip install click_and_drop_api

API Reference

This package has extensive documentation for the API Reference, which can be found here.

API Key

You need an API key to use the Click & Drop API. You can get this key here:

  1. Register an account at parcel.royalmail.com
  2. Login at auth.parcel.royalmail.com
  3. Go to SettingsIntegrationsAdd a new integrationClick & Drop API
  4. Fill out the details and save/update them.
  5. Copy the Click & Drop API authorisation key
export API_KEY="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"

The API key will be used on the examples below to authenticate the requests.

OBA

Some endpoints like generating labels require an OBA (Online Business Account). Label generation is available for OBA accounts only:

  • Private accounts pay as the label is generated.
  • OBA accounts pay monthly via invoice.

This only OBA can generate labels on request.

  1. Apply for a Royal Mail Business Account (OBA).
  2. Link your OBA to Click & Drop.
  3. Fill in required account information and make sure you follow the law.

Examples

This sections guides you through some examples. Export the API_KEY of your account to run the examples. The examples use the simple API that is based on the generated API and reduces the amount of code.

Retrieve the version

Version API Documentation

Retrieving the version is useful to understand if you can use the API without authentication.

#!/usr/bin/env python
from click_and_drop_api.simple import ClickAndDrop
import os

# navigate to https://business.parcel.royalmail.com/settings/channels/
# Configure API key authorization: Bearer
API_KEY = os.environ["API_KEY"]

api = ClickAndDrop(API_KEY)

version = api.get_version()

# https://api.parcel.royalmail.com/#tag/Version
print("commit:", version.commit)
print("build:", version.build)
print("release:", version.release)
print("release date:", version.release_date)

Output:

commit: 9236a82ff38de629d7b987a4d291d81c34fdd531
build: 20260107.6
release: Release-20260107.3
release date: 2026-01-22 10:20:54+00:00

View specific orders

Orders API Documentation

The image below shows orders that were created as examples.

Example orders for the script to retrieve

The script below retrieves information about these orders, by id (int) and by reference (str).

#!/usr/bin/env python
from click_and_drop_api.simple import ClickAndDrop
import os

# navigate to https://business.parcel.royalmail.com/settings/channels/
# Configure API key authorization: Bearer
API_KEY = os.environ["API_KEY"]

api = ClickAndDrop(API_KEY)

example_order_id = 1002
example_order_reference = "my-ref-9999"

orders = api.get_orders([example_order_id, example_order_reference])

for order in orders:
    print("Order Identifier:", order.order_identifier)
    print("Order Reference:", order.order_reference)
    print("Order Date:", order.order_date)
    print("Order Printed On:", order.printed_on)
    print("Order Manifested On:", order.manifested_on)
    print("Order Shipped On:", order.shipped_on)
    for package in order.packages:
        print("\tPackage Number:", package.package_number)
        print("\tPackage Tracking Number:", package.tracking_number)

Output:

Order Identifier: 1002
Order Reference: example-order-20260213211828
Order Date: 2026-02-13 21:18:28.936246
Order Printed On: None
Order Manifested On: None
Order Shipped On: None
    Package Number: 1
    Package Tracking Number: None
Order Identifier: 1003
Order Reference: my-ref-9999
Order Date: 2026-02-09 20:09:19.587738
Order Printed On: None
Order Manifested On: None
Order Shipped On: None
    Package Number: 1
    Package Tracking Number: None

Create and delete orders

Orders API Documentation

The script below creates a new order and then deletes it.

#!/usr/bin/env python
"""Create an order and download it.

On CI, also delete the order again.
"""

from pprint import pprint
from click_and_drop_api.simple import (
    ClickAndDrop,
    CreateOrder,
    RecipientDetails,
    Address,
    PackageSize,
)
import os
from datetime import datetime, UTC

# navigate to https://business.parcel.royalmail.com/settings/channels/
# Configure API key authorization: Bearer
API_KEY = os.environ["API_KEY"]

api = ClickAndDrop(API_KEY)

# choose a new reference or else the API will reject the order
REFERENCE = "example-order-{now}".format(now=datetime.now(UTC).strftime("%Y%m%d%H%M%S"))

package = PackageSize.get("letter")  # send a letter
service = package.get_shipping_option("OLP2")  # with 2nd class delivery

new_order = CreateOrder(
    order_reference=REFERENCE,
    is_recipient_a_business=False,
    recipient=RecipientDetails(
        address=Address(
            full_name="Nicco Kunzmann",
            company_name="",
            address_line1="Wernlas",
            address_line2="Talley",
            address_line3="",
            city="Llandeilo",
            county="United Kingdom",
            postcode="SA19 7EE",
            country_code="GB",
        ),
        phone_number="07726640000",
        email_address="niccokunzmann" + "@" + "rambler.ru",
    ),
    order_date=datetime.now(UTC),
    subtotal=float(12),  # 12 pounds
    shipping_cost_charged=float(service.gross),  # charge the same as Royal Mail
    total=float(12 + service.gross),
    currency_code="GBP",
    postage_details=service.as_postage_details(),
    packages=[package.as_package_request(weight_in_grams=80)],
    # Label generation is only possible for OBA customers
    # label = LabelGeneration(
    #     include_label_in_response=True,
    #     include_cn=False,
    #     include_returns_label=False,
    # )
)

response = api.create_orders(new_order)

print(f"Orders created: {response.created_orders}")
print(f"Errors: {response.errors_count}")

print("Getting the order from the API.")
order = api.get_order(REFERENCE)

print(f"Order Reference: {order.order_reference}")
print(f"Order Identifier: {order.order_identifier}")
pprint(order.to_dict())

# Delete the order when run in CI test
if "CI" in os.environ:
    print("Deleting order.")

    deleted_orders = api.delete_orders([REFERENCE])

    print(f"Orders deleted: {deleted_orders.deleted_orders}")
    print(f"Errors: {deleted_orders.errors}")

Output:

Orders created: [CreateOrderResponse(order_identifier=1012, order_reference='example-order-from-python-api', created_on=datetime.datetime(2026, 2, 11, 17, 32, 26, 763419, tzinfo=TzInfo(0)), order_date=datetime.datetime(2026, 2, 11, 17, 32, 26, 549854, tzinfo=TzInfo(0)), printed_on=None, manifested_on=None, shipped_on=None, tracking_number=None, packages=[CreatePackagesResponse(package_number=1, tracking_number=None)], label=None, label_errors=[], generated_documents=[])]
Errors: 0
Getting the order from the API.
Order Reference: example-order-from-python-api
Order Identifier: 1012
Deleting order.
Orders deleted: [DeletedOrderInfo(order_identifier=1012, order_reference='example-order-from-python-api', order_info=None)]
Errors: []

Package sizes and their shipping options

Several shipping options are available for each package size. There is no API for this, so the price and delivery speed is hard coded. You can view the table when you apply postage to an order. If the values are outdated, you are welcome to update them with a pull request and a screenshot of the table on the website.

This example prints all the available package sizes and their shipping options.

#!/usr/bin/env python
"""Print package size and shipping cost with options."""

from click_and_drop_api.simple import packages_sizes

for package_size in packages_sizes:
    print("Package Code:", package_size.code)
    print("Package Name:", package_size.name)
    print("Package Max. Weight (grams):", package_size.weight_grams)
    print("Package Max. Height (mm):", package_size.height_mm)
    print("Package Max. Width (mm):", package_size.width_mm)
    print("Package Max. Length (mm):", package_size.length_mm)
    for shipping_option in package_size.shipping_options:
        print(
            f"\t{shipping_option.brand.ljust(14)} {shipping_option.service_code.ljust(10)} £{shipping_option.gross} \t{shipping_option.delivery_speed}"
        )

Output:

Package Code: letter
Package Name: Letter
Package Max. Weight (grams): 100
Package Max. Height (mm): 5
Package Max. Width (mm): 165
Package Max. Length (mm): 240
    Royal Mail     OLP1       £1.70     24 hour (next working day)
    Royal Mail     OLP1SF     £3.60     24 hour (next working day)
    Royal Mail     OLP2       £0.87     48 hour (2 working days)
    Royal Mail     OLP2SF     £2.77     48 hour (2 working days)
    Royal Mail     SD1OLP     £8.75     Guaranteed by 1pm next working day
    Royal Mail     SD2OLP     £11.75    Guaranteed by 1pm next working day
    Royal Mail     SD3OLP     £18.75    Guaranteed by 1pm next working day
Package Code: largeLetter
Package Name: Large letter
Package Max. Weight (grams): 1000
Package Max. Height (mm): 25
Package Max. Width (mm): 250
Package Max. Length (mm): 353
    Royal Mail     OLP1       £1.70     24 hour (next working day)
    Royal Mail     OLP1SF     £3.60     24 hour (next working day)
    Royal Mail     OLP2       £0.87     48 hour (2 working days)
    Royal Mail     OLP2SF     £2.77     48 hour (2 working days)
    Royal Mail     SD1OLP     £8.75     Guaranteed by 1pm next working day
    Royal Mail     SD2OLP     £11.75    Guaranteed by 1pm next working day
    Royal Mail     SD3OLP     £18.75    Guaranteed by 1pm next working day
    Royal Mail     TOLP24     £3.65     24 hour (next working day)
    Royal Mail     TOLP24SF   £5.55     24 hour (next working day)
    Royal Mail     TOLP48     £2.75     48 hour (2 working days)
    Royal Mail     TOLP48SF   £4.65     48 hour (2 working days)
Package Code: smallParcel
Package Name: Small parcel
Package Max. Weight (grams): 2000
Package Max. Height (mm): 160
Package Max. Width (mm): 350
Package Max. Length (mm): 450
    Royal Mail     OLP1       £1.70     24 hour (next working day)
    Royal Mail     OLP1SF     £3.60     24 hour (next working day)
    Royal Mail     OLP2       £0.87     48 hour (2 working days)
    Royal Mail     OLP2SF     £2.77     48 hour (2 working days)
    Royal Mail     SD1OLP     £8.75     Guaranteed by 1pm next working day
    Royal Mail     SD2OLP     £11.75    Guaranteed by 1pm next working day
    Royal Mail     SD3OLP     £18.75    Guaranteed by 1pm next working day
    Royal Mail     TOLP24     £3.65     24 hour (next working day)
    Royal Mail     TOLP24SF   £5.55     24 hour (next working day)
    Royal Mail     TOLP24SFA  £7.33     24 hour (next working day)
    Royal Mail     TOLP48     £2.75     48 hour (2 working days)
    Royal Mail     TOLP48SF   £4.65     48 hour (2 working days)
    Royal Mail     TOLP48SFA  £6.43     48 hour (2 working days)
Package Code: mediumParcel
Package Name: Medium parcel
Package Max. Weight (grams): 20000
Package Max. Height (mm): 460
Package Max. Width (mm): 460
Package Max. Length (mm): 610
    Royal Mail     OLP1       £1.70     24 hour (next working day)
    Royal Mail     OLP1SF     £3.60     24 hour (next working day)
    Royal Mail     OLP2       £0.87     48 hour (2 working days)
    Royal Mail     OLP2SF     £2.77     48 hour (2 working days)
    Royal Mail     SD1OLP     £8.75     Guaranteed by 1pm next working day
    Royal Mail     SD2OLP     £11.75    Guaranteed by 1pm next working day
    Royal Mail     SD3OLP     £18.75    Guaranteed by 1pm next working day
    Royal Mail     TOLP24     £3.65     24 hour (next working day)
    Royal Mail     TOLP24SF   £5.55     24 hour (next working day)
    Royal Mail     TOLP24SFA  £7.33     24 hour (next working day)
    Royal Mail     TOLP48     £2.75     48 hour (2 working days)
    Royal Mail     TOLP48SF   £4.65     48 hour (2 working days)
    Royal Mail     TOLP48SFA  £6.43     48 hour (2 working days)
    Parcel Force   PFE10SF    £93.25    Guaranteed by 10am next working day
    Parcel Force   PFE24      £76.90    24 hour (next working day)
    Parcel Force   PFE24SF    £78.40    24 hour (next working day)
    Parcel Force   PFE48      £76.35    48 hour (2 working days)
    Parcel Force   PFE48SF    £77.85    48 hour (2 working days)
    Parcel Force   PFEAM      £80.70    Guaranteed by 12pm next working day
    Parcel Force   PFEAMSF    £82.20    Guaranteed by 12pm next working day
Package Code: largeParcel
Package Name: Large parcel
Package Max. Weight (grams): 30000
Package Max. Height (mm): 3000
Package Max. Width (mm): 3000
Package Max. Length (mm): 1500

Create postage labels

Labels API Documentation

! Labels are only available for OBA accounts !

The script below takes an order id or reference as an argument and generates a postage label for it.

#!/usr/bin/env python
"""Generate a label for an order number.

The order has to be created first.
You can use create_order.py to create an order.

View your orders: https://business.parcel.royalmail.com/orders/

Note: Label generation is only possible for OBA customers
"""

from click_and_drop_api.simple import ClickAndDrop
import os
import sys
from pathlib import Path

if len(sys.argv) < 2:
    print(
        f"""{Path(__file__).name} [ORDER_NUMBER_OR_REFERENCE]

    ORDER_NUMBER_OR_REFERENCE: The order number or reference.   
    """
    )
    print(__doc__)
    exit(0)

# navigate to https://business.parcel.royalmail.com/settings/channels/
# Configure API key authorization: Bearer
API_KEY = os.environ["API_KEY"]

api = ClickAndDrop(API_KEY)

order_id = sys.argv[1]
if order_id.isdigit():
    order_id = int(order_id)

label = api.get_label(order_id, "postageLabel", include_returns_label=False)

(Path(__file__).parent / "label.pdf").write_bytes(label)