PPQ APIppq.com.au ↗
Check Queensland personalised plate availability, get pricing by design and range, and retrieve similar combination suggestions via the PPQ API.
What is the PPQ API?
The PPQ API exposes 3 endpoints that cover personalised number plate availability, pricing, and suggestions on ppq.com.au. The check_availability endpoint returns a 10-field response including availability status, default design buy price, compatible design IDs, and flags for inappropriate or over-width combinations. All endpoints accept a plate combination of up to 7 alphanumeric characters and an optional vehicle type ID.
curl -X POST 'https://api.parse.bot/scraper/38c41957-822c-4b80-8444-25c0c70930b7/check_availability' \
-H 'X-API-Key: $PARSE_API_KEY' \
-H 'Content-Type: application/json' \
-d '{}'Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace ppq-com-au-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""
PPQ (Personalised Plates Queensland) API Client
Get your API key from: https://parse.bot/settings
This module provides a client for checking personalised plate availability,
getting pricing information, and finding similar plate suggestions.
"""
import os
import requests
from typing import Optional, Any, Dict, List
class ParseClient:
"""Client for interacting with the PPQ API via Parse."""
def __init__(self, api_key: Optional[str] = None):
"""
Initialize the Parse client.
Args:
api_key: API key for Parse. If not provided, reads from PARSE_API_KEY env var.
"""
self.base_url = "https://api.parse.bot"
self.scraper_id = "38c41957-822c-4b80-8444-25c0c70930b7"
self.api_key = api_key or os.getenv("PARSE_API_KEY")
if not self.api_key:
raise ValueError("API key must be provided or set in PARSE_API_KEY environment variable")
def _call(self, endpoint: str, method: str = "POST", **params) -> Dict[str, Any]:
"""
Make a request to the Parse API.
Args:
endpoint: The endpoint name (e.g., 'check_availability')
method: HTTP method ('GET' or 'POST')
**params: Query/body parameters
Returns:
Response data from the API
Raises:
requests.RequestException: If the API request fails
"""
url = f"{self.base_url}/scraper/{self.scraper_id}/{endpoint}"
headers = {
"X-API-Key": self.api_key,
"Content-Type": "application/json"
}
try:
if method == "GET":
response = requests.get(url, headers=headers, params=params, timeout=30)
else: # POST
response = requests.post(url, headers=headers, json=params, timeout=30)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
raise requests.RequestException(f"API request failed: {e}")
def check_availability(
self,
combination: str,
vehicle_type_id: str = "1"
) -> Dict[str, Any]:
"""
Check whether a specific plate combination is available for purchase.
Args:
combination: The plate combination (1-7 alphanumeric characters)
vehicle_type_id: Vehicle type ID (1=Motor Vehicle, 2=Trailer, 3=Motorcycle, 4=Small Trailer)
Returns:
Availability status, pricing, design options, and compatible design IDs
"""
return self._call(
"check_availability",
method="POST",
combination=combination,
vehicle_type_id=vehicle_type_id
)
def get_plate_ranges(
self,
combination: str,
vehicle_type_id: str = "1"
) -> Dict[str, Any]:
"""
Get available plate ranges with pricing for a given combination.
Faster than check_availability as it doesn't require reCAPTCHA solving.
Args:
combination: The plate combination (1-7 alphanumeric characters)
vehicle_type_id: Vehicle type ID (1=Motor Vehicle, 2=Trailer, 3=Motorcycle, 4=Small Trailer)
Returns:
Available plate ranges, pricing by format, and vehicle type information
"""
return self._call(
"get_plate_ranges",
method="POST",
combination=combination,
vehicle_type_id=vehicle_type_id
)
def get_suggestions(
self,
combination: str,
vehicle_type_id: str = "1"
) -> Dict[str, Any]:
"""
Get alternative plate combination suggestions similar to the input.
Args:
combination: The plate combination to find suggestions for (1-7 alphanumeric)
vehicle_type_id: Vehicle type ID (1=Motor Vehicle, 2=Trailer, 3=Motorcycle, 4=Small Trailer)
Returns:
List of suggested combinations with availability and pricing
"""
return self._call(
"get_suggestions",
method="POST",
combination=combination,
vehicle_type_id=vehicle_type_id
)
def print_availability_details(client: ParseClient, plate: str) -> None:
"""Print detailed availability and pricing information for a plate."""
print(f"\n{'='*60}")
print(f"Checking plate: {plate}")
print(f"{'='*60}")
result = client.check_availability(plate)
data = result.get("data", {})
if data.get("available"):
print(f"✓ {plate} is AVAILABLE!")
design = data.get("default_design", {})
print(f" Default design: {design.get('name')}")
print(f" Price: ${design.get('buy_price', 0) / 100:.2f}")
print(f" Delivery: {design.get('delivery_days')} days")
colours = design.get("foreground_colours", [])
if colours:
print(f" Available colours: {', '.join([c.get('name') for c in colours])}")
design_ids = data.get("available_design_ids", [])
print(f" Compatible designs: {len(design_ids)} available")
else:
print(f"✗ {plate} is NOT available")
reason = data.get("reason", "Unknown reason")
print(f" Reason: {reason}")
def explore_pricing_options(client: ParseClient, plate: str) -> None:
"""Explore pricing across different plate ranges and formats."""
print(f"\n{'='*60}")
print(f"Plate ranges and pricing for: {plate}")
print(f"{'='*60}")
result = client.get_plate_ranges(plate)
data = result.get("data", {})
# Display available plate ranges
ranges = data.get("plate_ranges", [])
if ranges:
print("\nPlate Ranges:")
for range_item in ranges:
status = "✓" if range_item.get("available") else "✗"
price = range_item.get("purchase_from_price", 0)
count = range_item.get("plate_design_count", 0)
print(f" {status} {range_item.get('name')}: from ${price} ({count} designs)")
# Display format options with pricing
formats = data.get("combination_formats", [])
if formats:
print("\nFormat Options:")
for fmt in formats:
price = fmt.get("price_from", 0)
max_chars = fmt.get("max_chars", 0)
print(f" {fmt.get('name')} ({fmt.get('code')}): ${price} (up to {max_chars} chars)")
def find_alternatives(client: ParseClient, plate: str) -> None:
"""Find and display alternative plate suggestions."""
print(f"\n{'='*60}")
print(f"Alternative suggestions for: {plate}")
print(f"{'='*60}")
result = client.get_suggestions(plate)
data = result.get("data", {})
suggestions = data.get("suggestions", [])
if not suggestions:
print("No suggestions found.")
return
print(f"\nFound {len(suggestions)} suggestions:\n")
for i, suggestion in enumerate(suggestions[:5], 1): # Show top 5
combo = suggestion.get("combination")
available = "✓" if suggestion.get("available") else "✗"
price = suggestion.get("priced_from", 0)
score = suggestion.get("score", 0)
distance = suggestion.get("levenshtein_distance", 0)
format_name = suggestion.get("format_name", "Unknown")
print(f"{i}. {available} {combo}")
print(f" Format: {format_name}")
print(f" Price from: ${price}")
print(f" Similarity score: {score} (distance: {distance})")
print()
if __name__ == "__main__":
# Initialize client
client = ParseClient()
# Practical workflow: Search for a plate, check options, and find alternatives
target_plate = "PYTHON"
# Step 1: Check availability and get detailed pricing
print_availability_details(client, target_plate)
# Step 2: Explore all pricing options across ranges and formats
explore_pricing_options(client, target_plate)
# Step 3: If the target plate isn't available, find alternatives
availability = client.check_availability(target_plate)
if not availability.get("data", {}).get("available"):
find_alternatives(client, target_plate)
# Step 4: Try an alternative and check its details
print("\n" + "="*60)
print("Workflow complete! Example of checking an alternative...")
print("="*60)
alt_plate = "PYTHON1"
print_availability_details(client, alt_plate)Check whether a specific plate combination is available for purchase in Queensland. Returns availability status, the default plate design with its buy price, available colour options, and a list of all compatible design IDs.
| Param | Type | Description |
|---|---|---|
| combinationrequired | string | The plate combination to check, 1-7 alphanumeric characters (letters and numbers only). Automatically converted to uppercase. |
| vehicle_type_id | string | Vehicle type ID. Accepts: 1 (Motor Vehicle), 2 (Trailer), 3 (Motor Cycle), 4 (Small Trailer). |
{
"type": "object",
"fields": {
"format": "string",
"reason": "string",
"too_wide": "boolean",
"available": "boolean",
"combination": "string",
"on_p2p_hold": "boolean",
"inappropriate": "boolean",
"default_design": "object containing plate design details including id, name, buy_price, delivery_days, foreground_colours, and combination_formats",
"availability_status": "integer",
"available_design_ids": "array of integer design IDs compatible with this combination",
"vehicle_type_suitable": "boolean"
},
"sample": {
"data": {
"format": "5",
"reason": "",
"too_wide": false,
"available": true,
"combination": "TEST1",
"on_p2p_hold": false,
"inappropriate": false,
"default_design": {
"id": 171,
"name": "Framed Prestige - Black",
"buy_price": 2500,
"delivery_days": 10,
"can_have_space": true,
"can_have_caption": false,
"can_change_colour": true,
"can_have_separator": false,
"foreground_colours": [
{
"name": "White",
"hex_value": "FFFFFF",
"colour_code": "WH"
}
],
"combination_formats": [
{
"id": 9,
"code": "6",
"tooltip": "This design allows 6 letters or numbers.",
"max_chars": 6
}
],
"background_colour_code": "BK"
},
"availability_status": 1,
"available_design_ids": [
171,
141,
147,
173,
174,
176
],
"vehicle_type_suitable": true
},
"status": "success"
}
}About the PPQ API
Availability and Design Data
The check_availability endpoint accepts a combination string (1–7 alphanumeric characters, automatically uppercased) and an optional vehicle_type_id (1 = Motor Vehicle, 2 = Trailer, 3 = Motor Cycle, 4 = Small Trailer). It returns an available boolean, an availability_status integer, and a default_design object containing the plate's buy_price, delivery_days, foreground_colours, and combination_formats. It also surfaces on_p2p_hold, inappropriate, and too_wide flags — useful for pre-validating combinations before presenting them to a user.
Plate Ranges and Pricing
get_plate_ranges returns the full set of product categories available for a given combination. Each entry in the plate_ranges array includes id, name, purchase_from_price, available, plate_design_count, and a tooltip. The endpoint also returns a combination_formats array with per-format pricing (price_from) and max_chars, and a vehicle_types array with availability per type. This endpoint resolves faster than check_availability and is suited for building pricing grids or filter UIs.
Suggestions
The get_suggestions endpoint returns an array of alternative combinations ranked by score and levenshtein_distance relative to the query combination. Each suggestion includes available, priced_from, design_price, and format_name — enough to display ranked alternatives with pricing in a search results context.
The PPQ API is a managed, monitored endpoint for ppq.com.au — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when ppq.com.au changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official ppq.com.au API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Check whether a specific Queensland plate combination is available before directing a user to purchase
- Display starting prices across all plate ranges and designs for a given combination
- Build a plate search tool that surfaces ranked alternatives when a user's first choice is unavailable
- Filter plate options by vehicle type (motor vehicle, trailer, motorcycle, small trailer) using vehicle_type_id
- Validate a combination for inappropriate content or excessive width before submission using the check_availability flags
- Show per-format pricing differences (combination_formats price_from) to help users choose a plate style
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 100 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
One credit = one API call regardless of which marketplace API you call. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.