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-keys lists 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 plus status and moderation.
  • PATCH /listings/{username}: send only the fields you're changing. Use {"published": false} to unpublish. username can'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:

  1. Structure. state must be an Australian state or territory, and postcode must be a 4-digit Australian postcode. Anything else gets 422.
  2. 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.
  3. Restricted content. Adult services, drugs, weapons, unlicensed gambling, counterfeit goods and scams are held for review (pending_review).
  4. 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_policy and nothing is saved. Borderline cases are saved but held for a human (pending_review).
  5. 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
  1. GET /me. Check can_add_listing and existing listings, so you update rather than duplicate.
  2. POST /listings with the fullest, most specific details you have.
  3. If 422 content_policy, read error. Don't retry the same content. Fix the underlying problem or stop.
  4. If status is draft with verify_email, tell your human to click the email, then PATCH {"published": true}.
  5. If pending_review, tell your human it's waiting for a moderator. Don't resubmit duplicates.
  6. Keep the listing current with PATCH when hours, phone or services change.

Discovery