Map each sign-up from your event platform to a saved ticket design, then create a print-ready PDF with a tear-off stub and a QR code. This guide uses one sample registration record and the gallery's Event ticket design. Your event platform keeps control of sign-ups and payment. Each successful ticket PDF costs one credit, and a failed render costs nothing.
Start from the Event ticket template
The Event ticket template is a landscape DL ticket, 794 by 374 pixels, with a tear-off stub. The stub shows the ticket number, seat, price, and a QR code built from the ticket number.

The saved schema requires all 13 fields below and rejects any field it does not name:
| Field | Rule | Sample value |
|---|---|---|
organiser | 1 to 50 characters | Fernbank Print Rooms |
event_name | 1 to 60 characters | Makers Night: Print and Paper |
tagline | 1 to 120 characters | An evening of letterpress demos, live poster printing, and good coffee with the people who make things. |
event_date | date, YYYY-MM-DD | 2026-10-16 |
doors_open | 1 to 20 characters | 6:30 pm |
venue_name | 1 to 40 characters | The Old Foundry |
venue_address | 1 to 60 characters | 22 Anchor Street, Bristol |
ticket_type | 1 to 18 characters | General entry |
holder_name | 1 to 60 characters | Rosa Delgado |
ticket_number | 1 to 14 characters | MN-016-0342 |
seat | 1 to 8 characters | B14 |
price | number, 0 to 100000 | 18 |
stub_note | 1 to 120 characters | Show this QR code at the door. Keep this stub for the raffle at 9 pm. |
ticket_number also feeds the QR code: the design places a QR code element bound to that field, so every ticket gets its own scannable code with no extra work from you. thirds.ai does not generate 1D barcodes, only QR codes.
Map a registration to the ticket fields
Most event platforms and ticketing tools send a webhook or an export row when someone registers or pays. This example uses Python 3 and curl on macOS or Linux. The script uses only the standard library. Set THIRDS_API_KEY through your secret store, and set THIRDS_TEMPLATE_ID and THIRDS_TEMPLATE_VERSION to your published copy of the Event ticket template.
Your platform's export or webhook must supply the shape below. Save it as registration.json in a private folder and set THIRDS_WORK to that folder.
{
"organiser_name": "Fernbank Print Rooms",
"title": "Makers Night: Print and Paper",
"tagline": "An evening of letterpress demos, live poster printing, and good coffee with the people who make things.",
"session_date": "2026-10-16",
"doors_time": "6:30 pm",
"location_name": "The Old Foundry",
"location_address": "22 Anchor Street, Bristol",
"tier": "General entry",
"first_name": "Rosa",
"last_name": "Delgado",
"confirmation_code": "MN-016-0342",
"seat_assignment": "B14",
"amount_paid": 18,
"check_in_note": "Show this QR code at the door. Keep this stub for the raffle at 9 pm."
}
Save this script as prepare-ticket.py. It checks each field against the template's own limits before it writes a request, so a bad export fails here instead of at the API.
"""Map one registration record to the saved Event ticket template."""
import json
import sys
from datetime import date
from pathlib import Path
LIMITS = {
"organiser": 50, "event_name": 60, "tagline": 120, "doors_open": 20,
"venue_name": 40, "venue_address": 60, "ticket_type": 18,
"holder_name": 60, "ticket_number": 14, "seat": 8, "stub_note": 120,
}
fixture_path, template_id, version, output_path = sys.argv[1:]
try:
reg = json.loads(Path(fixture_path).read_text())
holder_name = f"{reg['first_name']} {reg['last_name']}".strip()
data = {
"organiser": reg["organiser_name"],
"event_name": reg["title"],
"tagline": reg["tagline"],
"event_date": date.fromisoformat(reg["session_date"]).isoformat(),
"doors_open": reg["doors_time"],
"venue_name": reg["location_name"],
"venue_address": reg["location_address"],
"ticket_type": reg["tier"],
"holder_name": holder_name,
"ticket_number": str(reg["confirmation_code"]),
"seat": str(reg["seat_assignment"]),
"price": float(reg["amount_paid"]),
"stub_note": reg["check_in_note"],
}
for name, limit in LIMITS.items():
if not 1 <= len(data[name]) <= limit:
raise SystemExit(f"field_too_long: {name}")
if not 0 <= data["price"] <= 100000:
raise SystemExit("invalid_price")
request = {"template_id": template_id, "version": int(version),
"data": data, "wait": False}
Path(output_path).write_text(json.dumps(request, indent=2, allow_nan=False) + "\n")
except (KeyError, TypeError, ValueError, OSError):
raise SystemExit("invalid_input") from None
Make the file
The mapping command writes a local request. The first curl command creates one PDF and costs one credit on success. Use the confirmation code as the Idempotency-Key, so a repeated webhook delivery never makes a second ticket:
umask 077
python3 "$THIRDS_WORK/prepare-ticket.py" \
"$THIRDS_WORK/registration.json" \
"$THIRDS_TEMPLATE_ID" "$THIRDS_TEMPLATE_VERSION" \
"$THIRDS_WORK/request.json"
curl --fail-with-body --silent --show-error https://thirds.ai/v1/pdf \
-H "Authorization: Bearer $THIRDS_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: ticket-MN-016-0342' \
--data @"$THIRDS_WORK/request.json"
Save the id from the response with your registration record. Read the job until status is succeeded. Replace the sample ID with yours.
curl --fail-with-body --silent --show-error \
https://thirds.ai/v1/pdf/pdf_00000000000000000000000000000000 \
-H "Authorization: Bearer $THIRDS_API_KEY"
Copy download.url from that response and add https://thirds.ai before it. The download needs no API key.
curl --fail --silent --show-error \
'https://thirds.ai/v1/downloads/YOUR_SIGNED_DOWNLOAD_TOKEN' \
--output "$THIRDS_WORK/ticket.pdf"
shasum -a 256 "$THIRDS_WORK/ticket.pdf"
The hash must match artifact.sha256 in the job, and the file size must match artifact.byte_size.
Open ticket.pdf and check the name, seat, and QR code before you email or print it. Keep one folder and one key per confirmation code. If a webhook delivery retries, send the same request with the same key, and you get back the same ticket instead of a duplicate.
Resolve errors
| Code or state | Next step |
|---|---|
field_too_long or invalid_price | Fix the source field named in the error before you retry. |
invalid_input | Check the registration record for a missing or malformed field. |
template_data_invalid | Check the field names and limits against your published template version. |
idempotency_conflict | Use the original request with its key. A changed registration needs a new key. |
For polling deadlines, download links, and file retention, read Retry requests and download files.
Customize the ticket layout
You can copy the Event ticket template and change its branding, colours, or wording:
- Open the Event ticket template and click Use this template to save a copy to your account.
- In the editor, change the colours, fonts, and copy to match your event.
- Choose a saved brand kit if you want the same colours and logo across tickets, badges, and receipts.
- Click Save. Each save creates a new template version, so you can pin the exact version your integration renders.
Once saved, send your copy's template_id and version with each request. Read Use templates with your data for the full field syntax and how versions work.
Credit usage and plans
Every thirds.ai plan gives you every tool, so you only choose how many PDFs and images you make each month. 1 credit makes 1 PDF or image, and a failed render costs nothing.
- One ticket PDF costs 1 credit.
- Registering many attendees at once? Upload a CSV of rows to a saved template with CSV batch renders instead of calling the API once per row.
You start free with 50 credits a month, and you don't need a card. For larger venues, pricing starts with Starter at 2,500 credits a month.



