Skip to main content

1) Get an API key

Create an API key in the HandTextAI dashboard and copy the value (it starts with htext_).

2) Generate a PNG

For the most predictable production output, start with font_id: 1 or 2 and validate your final settings with /preview before you ship.
curl -sS https://api.handtextai.com/api/v1/generate \
  -H "Authorization: Bearer htext_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Hello from HandTextAI!",
    "font_id": 1,
    "font_size": "auto",
    "page_size": "a4",
    "dpi": 300
  }' \
  | python3 - <<'PY'
import sys, json, base64
payload = json.load(sys.stdin)
img = base64.b64decode(payload["image_base64"])
open("output.png", "wb").write(img)
print("Saved output.png (request_id:", payload.get("request_id"), ")")
PY

3) Preview before you generate (free)

Use /preview to iterate on margins/font sizing quickly. Previews are watermarked and free.
curl -sS https://api.handtextai.com/api/v1/preview \
  -H "Authorization: Bearer htext_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello!", "font_id": 1, "font_size": "auto", "page_size": "a4"}'

4) Generate a PDF

PDF responses are binary, not JSON.
curl -sS https://api.handtextai.com/api/v1/generate \
  -H "Authorization: Bearer htext_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Hello from HandTextAI!",
    "font_id": 1,
    "font_size": "auto",
    "page_size": "letter",
    "output_format": "pdf"
  }' \
  -o output.pdf

5) Handle rate limits

When you hit rate limits, use the Retry-After header:
import time

def generate_with_retry(request_data, max_retries=3):
    for attempt in range(max_retries):
        response = requests.post(
            "https://api.handtextai.com/api/v1/generate",
            headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
            json=request_data,
        )

        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 60))
            print(f"Rate limited, waiting {retry_after}s...")
            time.sleep(retry_after)
            continue

        return response

    raise Exception("Max retries exceeded")

Need to generate many outputs?

Use the dashboard’s Batch Generation for CSV-based bulk processing.

Troubleshooting

ErrorCauseSolution
401Invalid API keyCheck Authorization: Bearer htext_... format
422Validation errorCheck details array in response for field errors
429Rate/quota limitWait for Retry-After seconds, then retry
See Errors & Rate Limits for complete error reference.