For AI agents & automations
Agent Playbook
Register, get an API key and list Australian businesses on OZFINDA over JSON, with no CAPTCHA and no browser. Written for agents to read; humans welcome.
How an AI agent, script or automation creates and maintains listings on OZFINDA, Australia's business directory. This page is meant to be read by agents: every rule is explicit, every error is machine-readable, and you don't need a browser, CAPTCHA or human in the loop for anything except one email confirmation.
One rule shapes everything else: OZFINDA lists Australian businesses only. Every listing needs an Australian state or territory, and every submission is moderated, including by an AI model that checks the business is real and operates in Australia. Submissions that fail are refused or held for a human. They are never published silently.
Quick start
# 1. Register an account and get an API key (shown once)
curl -X POST https://ozfinda.com.au/api/v1/agents/register \
-H "Content-Type: application/json" \
-d '{"name":"Sam Citizen","email":"[email protected]","password":"a-long-password"}'
# 2. Create a listing (published once the email is confirmed)
curl -X POST https://ozfinda.com.au/api/v1/listings \
-H "Authorization: Bearer ozf_..." \
-H "Content-Type: application/json" \
-d '{
"business_name": "Bondi Plumbing Co",
"categories": ["trades"],
"tagline": "Licensed plumbers for the Eastern Suburbs",
"description": "Family-run plumbing business servicing Bondi, Randwick and Coogee since 2004.",
"phone": "0412 345 678",
"website": "https://bondiplumbing.com.au",
"suburb": "Bondi",
"state": "NSW",
"postcode": "2026",
"service_area": "Eastern Suburbs, Sydney"
}'
# 3. After the human clicks the confirmation email, publish it
curl -X PATCH https://ozfinda.com.au/api/v1/listings/bondi-plumbing-co \
-H "Authorization: Bearer ozf_..." \
-H "Content-Type: application/json" \
-d '{"published": true}'
Who should use this
- Business owners' assistants: an agent listing the business it works for.
- Agencies and automations: maintaining listings for Australian clients. Each account's plan caps how many listings it can own. The Free plan includes 1, and paid plans include more (see https://ozfinda.com.au/#pricing).
- Search and recommendation agents: read-only search needs no key.
Don't use it to bulk-import scraped business data, to list businesses you don't represent, or to list anything outside Australia. Those listings are refused, and repeated attempts get the account removed.
Basics
| Base URL | https://ozfinda.com.au/api/v1 |
| Format | JSON in, JSON out. Send Content-Type: application/json. Bodies max 64 KB. |
| Field names | snake_case |
| Auth | Authorization: Bearer ozf_... (API key). Cookies are never used, and CORS is open. |
| Machine spec | https://ozfinda.com.au/openapi.json |
| User-Agent | Send a descriptive one, e.g. MyAgent/1.0 (+https://example.com) |
1. Get an API key
New account: POST /agents/register
{ "name": "Sam Citizen", "email": "[email protected]", "password": "a-long-password", "key_name": "Claude agent" }
Returns 201 with account, api_key.key (shown once, so store it) and next_actions. The account is a normal OZFINDA account. A human can log in at https://ozfinda.com.au/login with the same email and password and see everything the agent did.
We email a confirmation link to the address. Nothing can be published until a human clicks it. This is our main defence against throwaway-account spam. Use a real inbox the business controls.
Existing account: POST /api-keys
{ "email": "[email protected]", "password": "a-long-password", "name": "Zapier" }
Or, with an existing key in the Authorization header, just { "name": "..." }. Owners can also mint and revoke keys in the dashboard under Account → API keys.
GET /api-keyslists live keys (prefix only).DELETE /api-keys/{id}revokes one.- An account can hold up to 10 live keys.
2. Create a listing: POST /listings
| Field | Type | Required | Notes |
|---|---|---|---|
business_name |
string 2-120 | yes | Trading name |
categories |
string[] | yes | Category slugs from GET /categories. The first is primary. The Free plan allows 1. (category, a single slug, also works.) |
state |
string | yes | Australian state/territory: NSW, VIC, QLD, WA, SA, TAS, ACT, NT, or the full name |
username |
string 3-32 | no | Public link https://ozfinda.com.au/<username>: lowercase letters, numbers, hyphens. Derived from the name if omitted. |
tagline |
string ≤160 | no | One line |
description |
string ≤2000 | no | Plain text about the business. More than 2 links is refused. |
phone |
string ≤32 | no | Australian number (04xx, 02 xxxx xxxx, +61..., 1300...) |
email |
string | no | Public contact email |
website |
URL | no | One listing per website across the directory |
address |
string ≤200 | no | Street address |
suburb |
string ≤80 | no | |
postcode |
string | no | 4-digit Australian postcode |
service_area |
string ≤200 | no | e.g. "Greater Brisbane" |
highlights |
string[] ≤8 | no | Short "why choose us" lines, ≤120 chars each |
published |
boolean | no | Default true: go live as soon as every check passes |
Unknown fields are rejected with 400, so typos surface instead of being silently dropped.
Response 201:
{
"listing": {
"username": "bondi-plumbing-co",
"url": "https://ozfinda.com.au/bondi-plumbing-co",
"status": "live",
"moderation": { "status": "clear", "reason": null },
"...": "every field above, plus id, dashboard_url, created_at, updated_at"
},
"next_actions": []
}
Always read status and next_actions. next_actions tells you exactly what's blocking a listing from going live.
Listing status
status |
Meaning | What to do |
|---|---|---|
live |
Public at url, in search and the sitemap |
Nothing |
draft |
Saved, not public | Follow next_actions: usually verify_email, then PATCH {"published": true} |
pending_review |
Held for a human moderator. moderation.reason says why. |
Wait, or fix the flagged content and PATCH again (re-runs the checks) |
rejected |
A moderator rejected it | Can't be published. Contact https://ozfinda.com.au/contact if it's a mistake. |
3. Read, update, delete
GET /listings/{username}: anyone can read a live listing. With your key you also see your drafts plusstatusandmoderation.PATCH /listings/{username}: send only the fields you're changing. Use{"published": false}to unpublish.usernamecan't be changed over the API.DELETE /listings/{username}: permanent. Removes the listing and everything attached to it.GET /me: your account, plan, listing quota (listings_used/listings_limit) and all your listings with status.
Content edits re-run moderation. A live listing that's edited into something that fails is unpublished and held for review.
4. Search (no key needed)
GET /listings?q=emergency+plumber&state=NSW&category=trades&limit=20&offset=0
Returns results[] (username, url, business_name, category, tagline, suburb, state, service_area, phone, website, featured) and next_offset. Search combines full-text and semantic matching over live listings. Featured (paid) listings rank first when there's no q.
GET /categories returns every category slug and label.
Moderation and spam rules
Every create and content edit goes through, in order:
- Structure.
statemust be an Australian state or territory, andpostcodemust be a 4-digit Australian postcode. Anything else gets422. - One listing per website. A website already on OZFINDA gets
409 conflict. If it's an unclaimed listing for your business, the response includes a claim link. If it's yours, it tells you which username to PATCH. - Restricted content. Adult services, drugs, weapons, unlicensed gambling, counterfeit goods and scams are held for review (
pending_review). - AI review. A decision model checks that the listing is a genuine business operating in Australia. Confident failures (overseas business, SEO/link spam, gibberish, not a business) are refused with
422 content_policyand nothing is saved. Borderline cases are saved but held for a human (pending_review). - Email confirmation. Nothing goes live until the account's email is confirmed.
What passes easily: a real Australian business described in plain, specific language, with a real suburb, state, postcode, Australian phone number and its own website.
What fails: keyword lists, city-name stuffing, links in the description, businesses based overseas, placeholder text, and listings for businesses you don't represent.
Rate limits
| Scope | Limit |
|---|---|
| Registration | 5 per hour per IP |
| Key minting | 10 per hour per IP |
| Reads (search, GET) | 60 per minute per IP |
| Writes (POST/PATCH/DELETE) | 12 per minute per account, 30 per minute per IP |
| New listings | 20 per day per account (and your plan's listing cap) |
Over the limit you get 429 rate_limited with a Retry-After header in seconds. Back off and retry. Don't hammer.
Errors
Every error has the same shape:
{ "error": "human-readable message", "code": "content_policy", "field": "state", "docs_url": "https://ozfinda.com.au/docs/agents" }
| HTTP | code |
Meaning |
|---|---|---|
| 400 | invalid_request |
Bad JSON, missing or unknown field, invalid value (field names it) |
| 401 | unauthorized |
Missing, invalid or revoked API key |
| 403 | quota_exceeded |
Plan's listing allowance is used up (upgrade at https://ozfinda.com.au/dashboard/billing) |
| 403 | forbidden |
e.g. publishing a moderator-rejected listing |
| 404 | not_found |
No such listing, or it's not yours |
| 409 | conflict |
Username or website already listed, or email already registered |
| 413 | payload_too_large |
Body over 64 KB |
| 422 | content_policy |
Refused by moderation (not Australian, spam, prohibited). Not saved. |
| 422 | invalid_request |
Non-Australian state or postcode |
| 429 | rate_limited |
Slow down, honour Retry-After |
Recommended agent loop
GET /me. Checkcan_add_listingand existing listings, so you update rather than duplicate.POST /listingswith the fullest, most specific details you have.- If
422 content_policy, readerror. Don't retry the same content. Fix the underlying problem or stop. - If
statusisdraftwithverify_email, tell your human to click the email, thenPATCH {"published": true}. - If
pending_review, tell your human it's waiting for a moderator. Don't resubmit duplicates. - Keep the listing current with
PATCHwhen hours, phone or services change.
Discovery
- https://ozfinda.com.au/llms.txt: site summary for language models
- https://ozfinda.com.au/llms-full.txt: everything in one file
- https://ozfinda.com.au/openapi.json: OpenAPI 3.1 spec
- https://ozfinda.com.au/.well-known/api-catalog: RFC 9727 API catalog
- https://ozfinda.com.au/docs/agents.md: this playbook as markdown