Skip to main content

Official Python SDK for the Handwrytten API — send real handwritten notes at scale

Project description

Handwrytten Python SDK

The official Python SDK for the Handwrytten API — send real handwritten notes at scale using robots with real pens.

Installation

pip install handwrytten

Quick Start

from handwrytten import Handwrytten

client = Handwrytten("your_api_key")

# Browse available cards and fonts
cards = client.cards.list()
fonts = client.fonts.list()

# Send a handwritten note in one call
result = client.orders.send(
    card_id=cards[0].id,
    font=fonts[0].id,
    message="Thanks for being an amazing customer!",
    wishes="Best,\nThe Handwrytten Team",
    sender={
        "firstName": "David",
        "lastName": "Wachs",
        "street1": "100 S Mill Ave",
        "city": "Tempe",
        "state": "AZ",
        "zip": "85281",
    },
    recipient={
        "firstName": "Jane",
        "lastName": "Doe",
        "street1": "123 Main Street",
        "city": "Phoenix",
        "state": "AZ",
        "zip": "85001",
    },
)

Usage

Send a Single Note

result = client.orders.send(
    card_id="12345",
    font="hwDavid",
    message="Thank you for your business!",
    wishes="Best,\nThe Team",
    sender={
        "firstName": "David",
        "lastName": "Wachs",
        "street1": "100 S Mill Ave",
        "city": "Tempe",
        "state": "AZ",
        "zip": "85281",
    },
    recipient={
        "firstName": "Jane",
        "lastName": "Doe",
        "street1": "123 Main St",
        "city": "Phoenix",
        "state": "AZ",
        "zip": "85001",
    },
)

Send Bulk — Multiple Recipients with Per-Recipient Overrides

Each recipient can have its own message, wishes, and sender. Top-level values serve as defaults for any recipient that doesn't specify its own.

result = client.orders.send(
    card_id="12345",
    font="hwDavid",
    sender={"firstName": "David", "lastName": "Wachs",
            "street1": "100 S Mill Ave", "city": "Tempe",
            "state": "AZ", "zip": "85281"},
    recipient=[
        {
            "firstName": "Jane",
            "lastName": "Doe",
            "street1": "123 Main St",
            "city": "Phoenix",
            "state": "AZ",
            "zip": "85001",
            "message": "Thanks for your loyalty, Jane!",
            "wishes": "Warmly,\nThe Team",
        },
        {
            "firstName": "John",
            "lastName": "Smith",
            "street1": "456 Oak Ave",
            "city": "Tempe",
            "state": "AZ",
            "zip": "85281",
            "message": "Great working with you, John!",
            "sender": {"firstName": "Other", "lastName": "Person",
                       "street1": "789 Elm St", "city": "Mesa",
                       "state": "AZ", "zip": "85201"},
        },
    ],
)

Use Saved Address IDs

If you have addresses saved in your Handwrytten account, pass their IDs directly:

result = client.orders.send(
    card_id="12345",
    font="hwDavid",
    message="Thank you!",
    sender=98765,    # saved return-address ID
    recipient=67890, # saved recipient address ID
)

# Mix saved IDs and inline addresses in a bulk send
result = client.orders.send(
    card_id="12345",
    font="hwDavid",
    message="Hello!",
    sender=98765,
    recipient=[
        67890,  # saved address ID
        {"firstName": "Jane", "lastName": "Doe",
         "street1": "123 Main St", "city": "Phoenix",
         "state": "AZ", "zip": "85001"},
    ],
)

Use Typed Models

from handwrytten import Recipient, Sender

sender = Sender(
    first_name="David",
    last_name="Wachs",
    street1="100 S Mill Ave",
    city="Tempe",
    state="AZ",
    zip="85281",
)

recipient = Recipient(
    first_name="Jane",
    last_name="Doe",
    street1="123 Main Street",
    city="Phoenix",
    state="AZ",
    zip="85001",
)

result = client.orders.send(
    card_id="12345",
    font="hwDavid",
    message="Welcome aboard!",
    sender=sender,
    recipient=recipient,
)

Custom Cards

Create custom cards with your own cover images and logos.

# 1. Get available card dimensions
dims = client.custom_cards.dimensions()
for d in dims:
    print(d.id, d)  # e.g. "1 7.000x5.000 flat (landscape)"

# Filter by format and/or orientation
flat_dims = client.custom_cards.dimensions(format="flat")
landscape = client.custom_cards.dimensions(format="flat", orientation="landscape")

# 2. Upload a full-bleed cover image (front of card)
cover = client.custom_cards.upload_image(
    url="https://example.com/cover.jpg",
    image_type="cover",
)

# 3. Upload a logo (appears on the writing side)
logo = client.custom_cards.upload_image(
    url="https://example.com/logo.png",
    image_type="logo",
)

# Or upload from a local file
logo = client.custom_cards.upload_image(
    file_path="/path/to/logo.png",
    image_type="logo",
)

# 4. Check image quality (optional)
check = client.custom_cards.check_image(image_id=logo.id)

# 5. Create the custom card
card = client.custom_cards.create(
    name="My Custom Card",
    dimension_id=dims[0].id,           # card dimension
    cover_id=cover.id,                 # front cover image
    header_logo_id=logo.id,            # logo on writing side
    header_logo_size_percent=80,
)

# 6. Use the new card to send orders
client.orders.send(
    card_id=str(card.card_id),
    font="hwDavid",
    message="Hello from our custom card!",
    recipient={...},
)

Custom cards support text and logos in multiple zones:

Zone Logo field Text field Font field
Header (top of writing side) header_logo_id header_text header_font_id
Main (center, folded cards) main_logo_id main_text main_font_id
Footer (bottom of writing side) footer_logo_id footer_text footer_font_id
Back back_logo_id back_text back_font_id
Front cover cover_id
Back cover back_cover_id

Font IDs for text zones come from client.fonts.list_for_customizer() (printed/typeset fonts), which are different from the handwriting fonts used in client.fonts.list().

Manage Custom Images

# List all uploaded images
images = client.custom_cards.list_images()
for img in images:
    print(img.id, img.image_type, img.image_url)

# Filter by type
covers = client.custom_cards.list_images(image_type="cover")
logos = client.custom_cards.list_images(image_type="logo")

# Delete an image
client.custom_cards.delete_image(image_id=123)

# Delete a custom card
client.custom_cards.delete(card_id=456)

Browse Cards and Fonts

# Card templates
cards = client.cards.list()
card = client.cards.get("12345")
categories = client.cards.categories()

# Handwriting fonts (for orders)
fonts = client.fonts.list()
for font in fonts:
    print(f"{font.id}: {font.label}")

# Customizer fonts (for custom card text zones)
customizer_fonts = client.fonts.list_for_customizer()

Gift Cards and Inserts

gift_cards = client.gift_cards.list()
inserts = client.inserts.list()

QR Codes

Create QR codes and attach them to custom cards.

from handwrytten import QRCodeLocation

# Create a QR code
qr = client.qr_codes.create(name="Website Link", url="https://example.com")

# List existing QR codes
qr_codes = client.qr_codes.list()

# Browse available frames (decorative borders around the QR code)
frames = client.qr_codes.frames()

# Attach a QR code to a custom card
card = client.custom_cards.create(
    name="Card with QR",
    dimension_id=dims[0].id,
    cover_id=cover.id,
    qr_code_id=int(qr.id),
    qr_code_location=QRCodeLocation.FOOTER,  # HEADER, FOOTER, or MAIN
    qr_code_size_percent=30,
    qr_code_align="right",
)

# Delete a QR code
client.qr_codes.delete(qr_code_id=int(qr.id))

Address Book

Save and manage recipient and sender addresses, then use their IDs when sending orders.

# Save a sender (return address)
sender_id = client.address_book.add_sender(
    first_name="David",
    last_name="Wachs",
    street1="100 S Mill Ave",
    city="Tempe",
    state="AZ",
    zip="85281",
)

# Save a recipient
recipient_id = client.address_book.add_recipient(
    first_name="Jane",
    last_name="Doe",
    street1="123 Main St",
    city="Phoenix",
    state="AZ",
    zip="85001",
)

# Send using saved IDs
client.orders.send(
    card_id="12345",
    font="hwDavid",
    message="Hello!",
    sender=sender_id,
    recipient=recipient_id,
)

# Update a recipient
client.address_book.update_recipient(
    address_id=recipient_id,
    street1="456 New St",
    city="Scottsdale",
)

# List saved addresses
senders = client.address_book.list_senders()
recipients = client.address_book.list_recipients()
for r in recipients:
    print(r.id, r)  # e.g. "123 Jane Doe, 456 New St, Scottsdale, AZ 85001"

# Countries and states
countries = client.address_book.countries()
states = client.address_book.states("US")

Two-Step Basket Workflow

For finer control, use client.basket directly instead of client.orders.send():

# Step 1: Add order(s) to the basket
client.basket.add_order(
    card_id="12345",
    font="hwDavid",
    addresses=[{
        "firstName": "Jane",
        "lastName": "Doe",
        "street1": "123 Main St",
        "city": "Phoenix",
        "state": "AZ",
        "zip": "85001",
        "message": "Hello!",
    }],
)

# Step 2: Submit the basket
result = client.basket.send()

Error Handling

from handwrytten import (
    HandwryttenError,
    AuthenticationError,
    BadRequestError,
    RateLimitError,
)

try:
    result = client.orders.send(...)
except AuthenticationError:
    print("Check your API key")
except BadRequestError as e:
    print(f"Invalid request: {e.message}")
    print(f"Details: {e.response_body}")
except RateLimitError as e:
    print(f"Rate limited — retry after {e.retry_after}s")
except HandwryttenError as e:
    print(f"API error: {e}")

API Resources

Resource Methods
client.auth get_user(), login()
client.cards list(), get(id), categories()
client.custom_cards dimensions(), upload_image(), check_image(), list_images(), delete_image(), create(), delete()
client.fonts list(), list_for_customizer()
client.gift_cards list()
client.inserts list()
client.qr_codes list(), create(), delete(), frames()
client.address_book list_recipients(), add_recipient(), update_recipient(), list_senders(), add_sender(), countries(), states(country)
client.orders send(), get(id), list()
client.basket add_order(), send()
client.prospecting calculate_targets(zip, radius)

Configuration

client = Handwrytten(
    api_key="your_key",
    timeout=60,          # seconds
    max_retries=5,       # automatic retries with exponential backoff
)

Full Example

See example.py for a complete working demo that exercises every resource: listing cards/fonts, sending single and bulk orders, uploading custom images, creating custom cards, and cleanup.

Requirements

  • Python 3.8+
  • requests

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

handwrytten-1.0.0.tar.gz (47.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

handwrytten-1.0.0-py3-none-any.whl (37.9 kB view details)

Uploaded Python 3

File details

Details for the file handwrytten-1.0.0.tar.gz.

File metadata

  • Download URL: handwrytten-1.0.0.tar.gz
  • Upload date:
  • Size: 47.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for handwrytten-1.0.0.tar.gz
Algorithm Hash digest
SHA256 5e00516ef27566f31a21c43c78bb2c92763317c039a94ca440632f45767477ef
MD5 8c68668da784e8d8c81a371fb2b663e9
BLAKE2b-256 32bf9c4fd32c5de442ffc2ef3218683e240832dd0ecd9e8217b513d81b9f45e1

See more details on using hashes here.

File details

Details for the file handwrytten-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: handwrytten-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 37.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for handwrytten-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f847e473b5e11e08bef3bcc61ceaeb3f794d8105d8b921497f8d91140936736a
MD5 93c8111ae8e4f619c46bc0fb71bcc2d4
BLAKE2b-256 0be34f356003f8285129b05f894033892b5382a0e77cb571f4fa06203d7b72b0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page