Build a Serverless Barcode Reading API with Python and AWS Lambda

In the previous article, we built a simple barcode reader server in Python and deployed it on Vercel. In this article, we are going to deploy it on AWS Lambda to provide a serverless barcode reading interface.

What you’ll build: A serverless REST API on AWS Lambda that accepts a base64-encoded image and returns decoded barcode data (format, text, coordinates) using the Dynamsoft Barcode Reader Python SDK.

Key Takeaways

  • AWS Lambda can run the Dynamsoft Barcode Reader Python SDK as a serverless function with no persistent infrastructure.
  • Lambda Layers package the SDK wheel so the handler stays lightweight and deploys cleanly.
  • The /tmp directory is the only writable path in Lambda — license cache must be directed there.
  • An API Gateway trigger exposes the Lambda as a standard HTTPS endpoint that any client can call.
  • The bundle ships manylinux wheels for Python 3.10-3.14, so the identical package runs on your machine and in the Lambda runtime.

Common Developer Questions

How do I read barcodes in AWS Lambda using Python?

Deploy a Python Lambda function that accepts a base64-encoded image, decodes it with the Dynamsoft Barcode Reader Python SDK, and returns barcode text, format, and coordinates as JSON. The article exposes that function through API Gateway so any HTTPS client can call it.

How do I package a Python barcode SDK as an AWS Lambda Layer?

Download the SDK wheel, extract it into a top-level python folder, and zip that folder as a Lambda Layer. After the layer is attached to the function, the handler stays small and only contains the barcode-reading logic.

Why does the Dynamsoft license cache fail in AWS Lambda and how do I fix it?

AWS Lambda only allows writes under /tmp, so the SDK license cache cannot use a normal application directory. Point the cache location to /tmp before initialization and keep the rest of the function stateless.

Prerequisites

  • An AWS account with permissions to create Lambda functions, layers, and API Gateway resources
  • Python 3.10+ (matching your Lambda runtime — dynamsoft-barcode-reader-bundle 11.6.5000 ships CPython wheels for 3.10 through 3.14)
  • A Dynamsoft Barcode Reader license key — Get a 30-day free trial license
  • The dynamsoft-barcode-reader-bundle wheel file from PyPI

Step 1: Create a New Lambda Function

Create a new AWS Lambda function. Here, we do this in the AWS console.

New function

Step 2: Add the Barcode SDK as a Lambda Layer

A Lambda layer is a .zip file archive that contains supplementary code or data. Layers usually contain library dependencies, a custom runtime, or configuration files.

Here, we need to add the package of Dynamsoft Barcode Reader in the layer.

  1. Install the bundle into a folder named python. Run this on Linux or inside a manylinux container, so pip resolves the Linux wheel instead of your host’s macOS or Windows one:

    python -m pip install --target python dynamsoft-barcode-reader-bundle==11.6.5000
    
  2. Zip the folder for uploading as a layer:

    zip -r dynamsoft-dbr-layer.zip python
    

The bundle publishes manylinux2014_x86_64 wheels for x86 functions and manylinux_2_27_aarch64 wheels for arm64 functions, so build one layer per architecture. If you prefer to assemble the layer by hand, download the matching wheel from PyPI and unzip it into the python folder instead.

New layer

Remember to use the layer for the function:

Use layer

Step 3: Write the Barcode Reading Handler

Next, modify the handler to use Dynamsoft Barcode Reader.

The handler receives a request like the following:

{
  "base64":"<base64-encoded-image>"
}

Then outputs the following:

{"results": 
  [
    {
      "barcodeFormat": "QR_CODE", 
      "barcodeText": "https://www.dynamsoft.com",
      "barcodeBytes": "aHR0cHM6Ly93d3cuZHluYW1zb2Z0LmNvbQ==",
      "confidence": 82,
      "x1": 7,
      "y1": 7,
      "x2": 93,
      "y2": 6,
      "x3": 94,
      "y3": 94,
      "x4": 6,
      "y4": 93
    }
  ]
}
  1. Import the bundle and initialize the license. The SDK caches the validated license on disk so it does not have to reach the license server again on every invocation. Lambda only allows writes under /tmp, so point the cache there — tempfile.gettempdir() returns /tmp on Lambda and the platform temp directory locally, which keeps the same file runnable outside AWS.

    import os
    import tempfile
    
    from dynamsoft_barcode_reader_bundle import (
        CaptureVisionRouter,
        EnumErrorCode,
        EnumPresetTemplate,
        LicenseManager,
    )
    
    LICENSE_KEY = os.environ.get("DYNAMSOFT_LICENSE_KEY", "<your license key>")
    
    cache_dir = os.path.join(tempfile.gettempdir(), "dynamsoft")
    os.makedirs(cache_dir, exist_ok=True)
    # Sets a directory path for saving the license cache.
    LicenseManager.set_license_cache_path(cache_dir)
    errorCode, errorMsg = LicenseManager.init_license(LICENSE_KEY)
    

    You can apply for a trial license here.

  2. Build one router at module scope and reuse it across invocations, then read barcodes from the base64 field. CaptureVisionRouter.capture() accepts file bytes directly, so the image never has to be written to disk.

    import base64
    
    router = None
    
    def get_router():
        global router
        if router is None:
            router = CaptureVisionRouter()
        return router
    
    def lambda_handler(event, context):
        if errorCode not in (EnumErrorCode.EC_OK, EnumErrorCode.EC_LICENSE_WARNING, EnumErrorCode.EC_LICENSE_CACHE_USED):
            return response(500, {"error": "License error: " + errorMsg})
    
        try:
            image_bytes = decode_image(event)
        except ValueError as error:
            return response(400, {"error": str(error)})
    
        result = get_router().capture(image_bytes, EnumPresetTemplate.PT_READ_BARCODES.value)
        if result.get_error_code() != EnumErrorCode.EC_OK:
            return response(422, {"error": result.get_error_string()})
    
        barcode_result = result.get_decoded_barcodes_result()
        items = barcode_result.get_items() if barcode_result is not None else []
        results = []
        for br in items:
            points = br.get_location().points
            item = {
                "barcodeFormat": br.get_format_string(),
                "barcodeText": br.get_text(),
                "barcodeBytes": base64.b64encode(br.get_bytes()).decode("ascii"),
                "confidence": br.get_confidence(),
            }
            for index, point in enumerate(points[:4], start=1):
                item[f"x{index}"] = point.x
                item[f"y{index}"] = point.y
            results.append(item)
        return response(200, {"results": results})
    

    response() and decode_image() are small helpers in lambda_function.py: the first wraps the JSON body and status code, the second unwraps every request shape you hit in practice — a plain dict when the handler is invoked locally, a JSON string in the API Gateway proxy event, a base64-encoded body when isBase64Encoded is true, and a data:image/png;base64, prefix on the image field.

Step 4: Expose the Function via API Gateway

We can add an API gateway as the trigger of the function.

New trigger

Then, we can call the Lambda function through HTTP.

Here is the Python script to test it:

import base64
import json
import requests

endpoint = ""  # like https://*****.execute-api.us-east-2.amazonaws.com/default/BarcodeReader

def get_picture_base64_data(image_path):
    with open(image_path, 'rb') as image_file:
        base64_data = base64.b64encode(image_file.read())
    return base64_data.decode('utf-8')

def decode():
    body = {"base64": get_picture_base64_data("./sample_qr.png")}
    r = requests.post(endpoint, data=json.dumps(body), headers={'Content-type': 'application/json'})
    print(json.dumps(r.json(), indent=2))

if __name__ == "__main__":
    decode()

The repository version of this script also takes --endpoint / --image arguments and a --local flag that calls lambda_handler in-process, so the handler can be tested before anything is deployed:

python test.py --local

Running it against sample_qr.png returns the same payload shown in Step 3 — QR_CODE, https://www.dynamsoft.com, confidence 82.

Common Issues and Edge Cases

  • License cache write failure: AWS Lambda only allows writing to /tmp. If you don’t call LicenseManager.set_license_cache_path() with a /tmp path, the SDK will fail silently or throw a permission error on every cold start.
  • No matching wheel: dynamsoft-barcode-reader-bundle 11.6.5000 publishes CPython wheels for 3.10-3.14 only. Pinning the Lambda runtime to python3.9 fails at layer build time with “No matching distribution found”.
  • Layer size limit exceeded: AWS Lambda layers have a 250 MB unzipped limit. If the Dynamsoft wheel plus dependencies exceed this, extract only the required platform-specific binaries (Linux x86_64) before zipping.
  • Cold start latency: The first invocation after deployment or idle timeout loads the SDK into memory, which can add 3–8 seconds. Subsequent warm invocations are significantly faster. Use provisioned concurrency if latency matters.

Source Code

The source code of the project is available here: https://github.com/yushulx/python-barcode-qrcode-sdk/tree/main/examples/official/aws_lambda