# Use cases

## Identity verification (KYC-light)

The most common use case: confirm a person exists with the supplied first name, last name, and date of birth. Often used as a low-touch identity check on its own, or as a pre-screen before launching a full DVS document verification.

Request:

```json
{
  "first_name": "John",
  "middle_name": "Andrew",
  "last_name": "Smith",
  "birth_date": "1998-03-21"
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "match",
      "middle_name": "match",
      "last_name": "match",
      "dob": "match"
    }
  ]
}
```

What this tells you: there is a person named John Andrew Smith with date of birth 21 March 1998 in our universe. All four supplied fields matched cleanly. If the user had got the DOB wrong, `dob` would come back as `no_match` (or `match_year` / `match_day_month_reversal` if it was close - see the [DOB partial matches](#dob-partial-matches) example).

## Phone tied to person (signup confirmation)

During signup, you've collected a name and a phone number from the user. You want to confirm that the phone number is actually associated with that person in our universe before trusting it for SMS-based 2FA or password recovery.

Request:

```json
{
  "first_name": "John",
  "last_name": "Smith",
  "phone": "0399999999"
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "match",
      "last_name": "match",
      "phone": "match"
    }
  ]
}
```

If the phone did not belong to a John Smith on file, the best candidate would still come back, but with `phone: no_match`. That means the name was found but the phone is not associated with it - a useful signal for fraud-prevention workflows.

Phone numbers are accepted in either Australian `0NSN` form (10 digits starting with `0`) or the equivalent E.164 form (`+61` followed by 9 digits). The leading `+` on the E.164 form is optional - `61400123456` is treated the same as `+61400123456`, which avoids spurious rejections of numbers that have round-tripped through Excel / CSV imports that silently strip the `+`. All three shapes are normalised internally before comparison, so it does not matter which one your application stores.

## Email tied to person (signup confirmation)

The same pattern as the phone use case, but using an email address instead.

Request:

```json
{
  "first_name": "Mary",
  "last_name": "Jones",
  "email": "mary_jones89@example.com"
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "match",
      "last_name": "match",
      "email": "match"
    }
  ]
}
```

## Multi-contact verification

Customers often have several contact points on file (current and old phone numbers, work and personal email addresses). You can supply up to four phones (`phone`, `phone2`, `phone3`, `phone4`) and up to four emails (`email`, `email2`, `email3`, `email4`) on a single request.

Each slot is evaluated **independently** against the candidate's full set of phones / emails, so the response tells you exactly which of your supplied values matched. Slots you did not supply are omitted from the row.

Request:

```json
{
  "first_name": "John",
  "last_name": "Smith",
  "birth_date": "1998-03-21",
  "phone": "0399999999",
  "phone3": "0491222111",
  "email": "jsmith1998@example.com"
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "match",
      "last_name": "match",
      "dob": "match",
      "phone": "match",
      "phone3": "match",
      "email": "match"
    }
  ]
}
```

Notice that the request used `phone` and `phone3` (skipping `phone2`); the response mirrors that and contains a `phone` outcome and a `phone3` outcome with no `phone2` or `phone4` keys. The order does not matter for matching - each slot is checked against the candidate's complete phones list - so it is fine to use whichever slots best line up with how your application stores its data.

If a slot does not match (for example, the customer gave you an old phone number that is no longer on file), that slot will return `no_match` while the other slots can still return `match`.

## Address verification (full match)

Confirm that a person lives at a specific address. Address can be supplied in **any one** of three forms (mutually exclusive):

- **Address parts** - `street_address` + `suburb` + `state` + `postcode`, all required together.
- **Full address** - a single `full_address` string that we parse for you.
- **GNAF id** - the canonical `gnaf_id` for the property.

Whichever form you use, the address is resolved to its canonical components before evaluation, so the same `address` rollup logic applies.

### Using address parts

Request:

```json
{
  "first_name": "John",
  "last_name": "Smith",
  "street_address": "20 Hardy St",
  "suburb": "Lilydale",
  "state": "VIC",
  "postcode": "3140"
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "match",
      "last_name": "match",
      "address": "match",
      "street_address": "match",
      "suburb": "match",
      "state": "match",
      "postcode": "match"
    }
  ]
}
```

### Using full_address

Request:

```json
{
  "first_name": "John",
  "last_name": "Smith",
  "full_address": "20 Hardy St, Lilydale VIC 3140"
}
```

The response shape is identical - the `full_address` is parsed and resolved to the same canonical components, then matched against the candidate.

### Using gnaf_id

Request:

```json
{
  "first_name": "John",
  "last_name": "Smith",
  "gnaf_id": "GAVIC421647320"
}
```

Same response shape. Use this form when you already hold the canonical GNAF id (for example from a prior address validation call).

GNAF persistent identifiers come in two shapes depending on the state of issue. Some states (ACT, NSW, VIC, QLD, WA, TAS) emit them with no separator between the state prefix and the digit run (`GAVIC421647320`, `GANSW710277749`). NT and SA emit them with a single underscore between the prefix and the digits (`GANT_717247498`, `GASA_414917272`). Both forms are accepted - keep the underscore as it appears in the source id rather than pre-stripping it.

## Partial address lookup

Sometimes you only have part of an address, or the user has supplied a slightly different address than what is on file. The `address` rollup tells you at a glance how good the match is, and the four component fields tell you exactly which parts agreed.

### "Same street, different suburb" - `match_street`

Suppose your records show John Smith at `20 Hardy Street, Doncaster VIC 3108` but our universe has him at Lilydale 3140. The street number and name match, but everything else is wrong.

Request:

```json
{
  "first_name": "John",
  "last_name": "Smith",
  "street_address": "20 Hardy St",
  "suburb": "Doncaster",
  "state": "VIC",
  "postcode": "3108"
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "match",
      "last_name": "match",
      "address": "match_street",
      "street_address": "match",
      "suburb": "no_match",
      "state": "match",
      "postcode": "no_match"
    }
  ]
}
```

The rollup `match_street` says "we found someone of this name on the same street, but not at the same locality" - useful for nudging a user to double-check their address details rather than rejecting outright.

### "Is there a John Smith in postcode 3140?" - `match_locality`

If you only know the locality, supply just the suburb / state / postcode. Even with a placeholder street address, the rollup will tell you whether anyone of that name lives in that locality.

Request:

```json
{
  "first_name": "John",
  "last_name": "Smith",
  "street_address": "1 Unknown St",
  "suburb": "Lilydale",
  "state": "VIC",
  "postcode": "3140"
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "match",
      "last_name": "match",
      "address": "match_locality",
      "street_address": "no_match",
      "suburb": "match",
      "state": "match",
      "postcode": "match"
    }
  ]
}
```

`match_locality` requires `state` to match (when supplied) and at least one of `suburb` or `postcode` to also match. Use this when you want a "yes there's a John Smith in this area" confidence signal rather than an exact-address check.

## Multi-candidate review

Set `return_multiple_candidates: true` to receive up to 5 candidates ordered best-first.

### When to use it

Default single-candidate mode is the right choice for high-confidence yes/no checks: "is this person in the universe?", "does this phone belong to this person?". The single best candidate carries the per-field outcomes you need to drive an auto-approve / manual-review / reject decision, and the response stays small and easy to consume.

Switch to multi-candidate mode when one of the following applies:

- **Building a manual review queue.** Reviewers benefit from seeing several plausible candidates side-by-side so they can pick the right one rather than blindly trusting the top pick.
- **Building a "did you mean...?" disambiguation step.** When a user has typed something that could match more than one record (a common surname with a nickname, a postcode-only locality lookup), surfacing the alternates lets the user resolve the ambiguity themselves.
- **Hedging against ranker disagreement.** When the request supplies several identifiers that point in different directions (the user's phone belongs to Person A, but their name and DOB match Person B), the top candidate reflects the ranker's calibration. Multi-candidate mode lets you see both Person A and Person B and apply your own business rules to choose between them.
- **Investigating an unexpected outcome.** When a downstream workflow flagged a record and you want to understand "why didn't the right person win?", inspecting the full top-5 is the fastest way to see what else was considered.
- **Auditing or sampling.** When validating a new data feed or comparing universe coverage against an internal source, seeing all distinct candidates per query gives a clearer picture than the single best.

For straight pass/fail verification flows, leave it off - the single-candidate response is faster to handle and conveys the same yes/no signal.

### Response shape and dedupe behaviour

`match_results` always returns **one row per matched person** - it never duplicates the same person once per address on file. A person with three known addresses still appears as a single row, with the address outcome reflecting the strongest match across all of them (see [Address](#address) above for the best-address selection rules).

A subtle but important behaviour: **identical rows are collapsed.** If two underlying universe records produce the same per-field outcome map against your request (for example, two Roberts at the same address with the same DOB, both evaluated against your supplied `first_name + last_name + birth_date`), they appear in the response as a single row, not two. The number of rows in `match_results` therefore depends on how many *distinct* outcome maps your request produced, not on how many universe records were considered.

The practical implication: a request like `{ "last_name": "Brown", "return_multiple_candidates": true }` returns just a single row with `last_name: match`, because every Brown candidate evaluates identically against a request that only supplies a last name. The response cannot tell you how many Browns are in the universe; it can only tell you that at least one exists.

To get multiple distinct rows, supply additional identifiers so that the candidates differ in their outcomes. For example, supplying a `first_name` lets candidates with different first names produce different `first_name` outcomes (`match` vs `no_match`), so they appear as separate rows.

Request:

```json
{
  "first_name": "Robert",
  "last_name": "Brown",
  "first_name_matching": ["exact", "alias", "partial"],
  "return_multiple_candidates": true
}
```

Response (the sandbox has Robert Patrick Brown, a Robert Brown with no DOB, and an `R BROWN` record):

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "match",
      "last_name": "match"
    },
    {
      "first_name": "partial_match",
      "last_name": "match"
    }
  ]
}
```

The two `Robert Brown` records (with and without a DOB on file) collapse into the first row because they evaluate identically against this request; the `R BROWN` record produces a distinct second row because its first name only matches partially. Each row in `match_results` is independent and self-describing, so you can route them straight into a review UI.

## Typo tolerance with fuzzy matching

When your data may contain spelling variations of names (Mathew/Matthew, Smith/Smyth, Stephen/Steven), opt in to fuzzy matching for the affected name field. Fuzzy matching uses a phonetic (metaphone) algorithm and additionally requires the same leading letter, so it is robust against typos while still being conservative.

Request:

```json
{
  "first_name": "Mathew",
  "last_name": "Smith",
  "birth_date": "1999-10-21",
  "first_name_matching": ["exact", "fuzzy"]
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "fuzzy_match",
      "last_name": "match",
      "dob": "match"
    }
  ]
}
```

`fuzzy_match` was returned for `first_name` because Mathew and Matthew share the same metaphone code (`M0`) and start with the same letter. The DOB and last name still matched exactly, so this is a strong overall match across all three supplied identifiers.

To allow spelling variations on the last name as well, add `last_name_matching: ["exact", "fuzzy"]`. This also widens the underlying candidate search so that records with a fuzzy last name are considered, not just records with the exact supplied last name.

## Nickname / alias tolerance

Customers may identify themselves by their preferred name (Bob, Jenny, Tony) when their record is on file under a formal name (Robert, Jennifer, Antonio). Opt in to alias matching to allow the comparison.

Request:

```json
{
  "first_name": "Bob",
  "last_name": "Brown",
  "birth_date": "1971-03-18",
  "first_name_matching": ["exact", "alias"]
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "alias_match",
      "last_name": "match",
      "dob": "match"
    }
  ]
}
```

The alias group catalogue includes hundreds of common nickname pairings. You can combine `["exact", "alias", "fuzzy"]` to tolerate both nicknames and typos, or add `"partial"` to also accept single-initial matches like `"R" -> "Robert"`.

## DOB partial matches

Date of birth has its own ladder of partial outcomes for cases where the customer has the year right but not the exact date.

### Day / month reversal

Mary Jones is on file with a DOB of 12 August 1989 (`1989-08-12` in `YYYY-MM-DD`). Upstream systems frequently mix up the regional date conventions `DD/MM/YYYY` and `MM/DD/YYYY`. For example, a customer who handwrites `12/08/1989` on a form (intending 12 August) might have it entered into a US-style system as 8 December 1989 - the day and month silently swap. When that incorrect date later makes its way into a request to this API, the supplied value is `1989-12-08` instead of `1989-08-12`. The check spots that the year matches and the day and month are swapped, and reports the outcome explicitly.

> The API itself always requires `birth_date` in `YYYY-MM-DD` format - the day/month confusion described above happens upstream of the API, in the system that originally collected the date. This check just helps you recover when that has already happened.

Request:

```json
{
  "first_name": "Mary",
  "last_name": "Jones",
  "birth_date": "1989-12-08"
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "match",
      "last_name": "match",
      "dob": "match_day_month_reversal"
    }
  ]
}
```

### Year-only

If only the year matches but the day and month do not (and they are not a reversal), the response returns `match_year`:

Request:

```json
{
  "first_name": "Mary",
  "last_name": "Jones",
  "birth_date": "1989-05-15"
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "match",
      "last_name": "match",
      "dob": "match_year"
    }
  ]
}
```

### No DOB on file

Some universe records have no DOB on file at all. When that is the case, the candidate returns `dob: no_record` rather than `no_match` - a useful distinction because it tells you "we matched this person, but we have no DOB to compare against" instead of "we matched this person and their DOB is different to yours".

The sandbox includes a Robert Brown record with phone `0491222333` and no DOB on file (distinct from the Robert Patrick Brown record born 1971-03-18). Searching for him by phone, with any DOB, shows the outcome:

Request:

```json
{
  "first_name": "Robert",
  "last_name": "Brown",
  "phone": "0491222333",
  "birth_date": "1985-06-04"
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "match",
      "last_name": "match",
      "dob": "no_record",
      "phone": "match"
    }
  ]
}
```

`dob: no_record` is a signal that the candidate is on file but does not have a date of birth recorded, so the supplied DOB cannot be confirmed or refuted from our universe. It is not a positive match, but it is also not a `no_match` either - decide per workflow whether the absence of a DOB on file is acceptable, or whether you want to fall back to another verification path.

If your supplied DOB happens to match a different candidate (for example, supplying `1971-03-18` would match the Robert Patrick Brown record), that other candidate's `dob: match` will outscore this candidate's `dob: no_record` and become the top result. Set `return_multiple_candidates: true` if you want to see both rows.

The other partial DOB outcomes (`match_year`, `match_day_month_reversal`) are useful when DOB is collected from messy sources (handwritten forms, customer entry on a phone keypad).

## Dirty input with `ignore_errors`

When you are checking data collected from imperfect sources - a customer-facing signup form, a CSV import, a third-party feed - some individual fields will inevitably be malformed. By default, an invalid phone number or DOB will cause the whole call to fail with a `400`. With `ignore_errors: true`, the endpoint silently drops any individual field that fails validation and runs the match against whatever valid input remains. Dropped fields come back in the response as `not_used` so you can see exactly what was ignored.

Request:

```json
{
  "first_name": "John",
  "last_name": "Smith",
  "birth_date": "1987666",
  "phone": "1234",
  "email": "jsmith1998@example.com",
  "ignore_errors": true
}
```

Response:

```json
{
  "message": "Ok",
  "api_reference": "fe4291ca-d831-4760-96df-c9cb03b3cd95",
  "match_results": [
    {
      "first_name": "match",
      "last_name": "match",
      "dob": "not_used",
      "phone": "not_used",
      "email": "match"
    }
  ]
}
```

Use `ignore_errors: true` whenever you cannot guarantee the cleanliness of the inputs and you'd rather get a partial result than re-architect a retry-with-bad-fields-omitted loop on your side. Note that the request still has to be **structurally** well-formed (correct types, `last_name` present); only individual field-value problems are soft-ignored.

## Confidence-based routing

Each candidate row reports the per-field outcomes directly. To drive a downstream routing rule, branch on the specific outcomes that matter for your use case rather than on a single aggregate score. For example:

- exact `last_name` plus exact `dob` plus any `phone` match → auto-approve.
- exact `last_name` plus a partial DOB outcome (`match_day_month_reversal` / `match_year`) → manual review queue.
- everything else → reject or trigger a more thorough verification (such as a DVS document check or an ID Pass).

The exact thresholds depend on your risk appetite and the field mix you are sending; the [Best-candidate selection](#best-candidate-selection) table tells you how strongly each outcome weighs in the candidate ranking, which is a useful starting point when calibrating.
