# ABN Verification Spec — RateRight Contractor Signup

> **Status:** Ready for implementation  
> **Priority:** CRITICAL BLOCKER — blocks launch  
> **Date:** 2026-02-06  
> **Problem:** `mockCompanyResearch()` returned fabricated ABN data; AI profile builder claimed "built from public records" but data was fake  
> **Resolution:** Already partially implemented — `rateright-v2` has a working ABR API route. This spec documents the full architecture and remaining work.

---

## Table of Contents

1. [Current State Assessment](#1-current-state-assessment)
2. [ABR API Integration (Primary)](#2-abr-api-integration-primary)
3. [ABN Client-Side Validation](#3-abn-client-side-validation)
4. [ASIC Integration (Director Verification)](#4-asic-integration-director-verification)
5. [User Flow](#5-user-flow)
6. [Edge Cases](#6-edge-cases)
7. [Fraud Prevention](#7-fraud-prevention)
8. [Fallback Strategy](#8-fallback-strategy)
9. [Implementation Plan](#9-implementation-plan)
10. [Cost Analysis](#10-cost-analysis)
11. [Data Schema](#11-data-schema)
12. [Code Reference](#12-code-reference)

---

## 1. Current State Assessment

### What Already Exists (rateright-v2)

The codebase already has a solid foundation:

| File | Status | Notes |
|------|--------|-------|
| `src/lib/abn-utils.ts` | ✅ Complete | Checksum validation, formatting, cleaning |
| `src/app/api/abn-lookup/route.ts` | ✅ ~90% done | ABR JSON integration, JSONP parsing, mock fallback |
| `src/app/(authenticated)/contractor/signup/page.tsx` | ✅ Working | Calls `/api/abn-lookup`, maps response |

### What's Missing

1. **ABR GUID registration** — env var `ABR_GUID` is not set (using mock data in dev)
2. **ASIC director verification** — stub exists, needs real implementation or manual-review flow
3. **Production readiness** — rate limiting, caching, monitoring
4. **the-50-dollar-app** — needs to be synced with rateright-v2's implementation

---

## 2. ABR API Integration (Primary)

### Overview

| Property | Value |
|----------|-------|
| **Provider** | Australian Business Register (ABR) |
| **URL** | https://abr.business.gov.au |
| **Cost** | **FREE** |
| **Auth** | GUID (Globally Unique Identifier) issued on registration |
| **Registration** | https://abr.business.gov.au/Tools/WebServices |
| **Protocols** | SOAP (XML), HTTP GET/POST, **JSON (JSONP)** |
| **Rate Limits** | Not officially documented; no known hard limits for reasonable use |
| **SLA** | No formal SLA; government service with planned maintenance windows |

### Registration Steps

1. Go to https://abr.business.gov.au/Tools/WebServicesAgreement
2. Accept the web services agreement
3. Fill in the registration form (business details, contact info)
4. Receive GUID via email (typically within 1-2 business days)
5. Set `ABR_GUID` in `.env`

### API Endpoints

#### JSON Endpoint (What we use — simplest for Next.js)

```
GET https://abr.business.gov.au/json/AbnDetails.aspx?abn={ABN}&callback={CALLBACK}&guid={GUID}
```

Returns JSONP wrapped response. Our API route strips the callback wrapper server-side.

#### Available JSON Endpoints

| Endpoint | URL | Purpose |
|----------|-----|---------|
| ABN Lookup | `/json/AbnDetails.aspx?abn={abn}&callback={cb}&guid={guid}` | Look up by ABN |
| ACN Lookup | `/json/AcnDetails.aspx?acn={acn}&callback={cb}&guid={guid}` | Look up by ACN |
| Name Search | `/json/MatchingNames.aspx?name={name}&maxResults={n}&callback={cb}&guid={guid}` | Search by name |

#### SOAP/XML Endpoints (More data, more complex)

| Method | Version | Notes |
|--------|---------|-------|
| `SearchByABNv202001` | Latest | Full data including AWEF |
| `SearchByABNv201408` | Recommended fallback | DGR + ACNC data |
| `SearchByABNv201205` | Includes business names | |
| `SearchByASICv201408` | Search by ACN | |
| `ABRSearchByNameAdvanced2017` | Name search with active filter | |

WSDL: `https://abr.business.gov.au/abrxmlsearch/abrxmlsearch.asmx`

### JSON Response Shape

```typescript
interface ABRJsonResponse {
  Abn: string;                    // "51824753556"
  AbnStatus: string;              // "Active" or "Cancelled"
  AbnStatusEffectiveFrom: string; // "01 Jul 2000"
  Acn: string;                    // "123456789" or ""
  AddressDate: string | null;     // "01 Jan 2020"
  AddressPostcode: string;        // "2000"
  AddressState: string;           // "NSW"
  BusinessName: Array<string>;    // ["TRADING NAME 1", "TRADING NAME 2"]
  EntityName: string;             // "SMITH CONSTRUCTIONS PTY LTD"
  EntityTypeCode: string;         // "PRV", "IND", "PUB", etc.
  EntityTypeName: string;         // "Australian Private Company"
  Gst: string | null;             // "01 Jul 2000" (date GST registered) or null
  Message: string;                // "" on success, error message otherwise
}
```

### Data Available from ABR

| Field | Available | Notes |
|-------|-----------|-------|
| Entity name | ✅ | Legal name or main name |
| ABN | ✅ | 11 digits |
| ABN status | ✅ | Active / Cancelled |
| ACN | ✅ | If company (not sole trader) |
| Entity type | ✅ | Code + description (IND, PRV, PUB, PTR, etc.) |
| GST registration | ✅ | Date registered or null |
| Business location (state + postcode) | ✅ | State code + postcode only |
| Business names | ✅ | Trading/registered names |
| Full street address | ❌ | **Not available** — only state + postcode |
| Phone/email | ❌ | Not available |
| Directors/owners | ❌ | Not available (need ASIC) |
| Revenue/employees | ❌ | Not available |
| Industry/trade category | ❌ | Entity type only, not trade classification |

### Key Entity Types for Contractors

| Code | Description | Common For |
|------|-------------|------------|
| `IND` | Individual/Sole Trader | Solo tradies |
| `PRV` | Australian Private Company | Most contractor companies |
| `PUB` | Australian Public Company | Large builders |
| `PTR` | Other Partnership | Business partnerships |
| `FPT` | Family Partnership | Family businesses |
| `TRT` | Other Trust | Trading trusts |
| `DTT` | Discretionary Trading Trust | Common structure |

---

## 3. ABN Client-Side Validation

### Checksum Algorithm (Already Implemented)

The ABN uses a modulus-89 weighted checksum:

```typescript
const ABN_WEIGHTS = [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19];

function isValidABN(abn: string): boolean {
  const digits = abn.replace(/\D/g, '');
  if (digits.length !== 11) return false;

  const d = digits.split('').map(Number);
  d[0] -= 1; // Subtract 1 from first digit

  let sum = 0;
  for (let i = 0; i < 11; i++) {
    sum += d[i] * ABN_WEIGHTS[i];
  }

  return sum % 89 === 0;
}
```

**Important caveat:** Any 9-digit number can be made into a valid ABN. The checksum only proves format validity, not that the ABN is actually registered. **Always verify with ABR API.**

### Validation Flow

```
User types ABN → Strip non-digits → Check length (11) → Checksum mod 89
                                                              ↓
                                                    Pass → Call ABR API
                                                    Fail → Show error immediately
```

---

## 4. ASIC Integration (Director Verification)

### The Problem

ABR tells us the **company exists** but not **who owns/runs it**. Anyone could enter a valid ABN. We need to verify that the person signing up is actually associated with the business.

### ASIC API Options

#### Option A: ASIC EDGE API (Official, Complex)

| Property | Value |
|----------|-------|
| **Provider** | ASIC (Australian Securities and Investments Commission) |
| **Cost** | **FREE** (no API fees) |
| **Registration** | Email webservices@asic.gov.au, apply as digital service provider |
| **Approval** | Manual review, testing required, can take weeks |
| **Data** | Full company details including officeholders |
| **Complexity** | High — need to pass ASIC's testing requirements |

**Process:**
1. Contact webservices@asic.gov.au requesting EDGE API access
2. Review ASIC's [Digital Services T&Cs](https://download.asic.gov.au/media/5393724/digital-services-access-terms-and-conditions-v2.pdf)
3. Receive test environment credentials
4. Build integration, pass ASIC testing
5. Receive production credentials

**Free data available from ASIC registers:**
- Company name, type, ACN, ABN
- Registration date, next review date
- Registered office (suburb, state, postcode only)
- List of lodged documents

**Paid data ($9.40 per extract as of 2026):**
- Full company extract with officeholder details (directors, secretaries)
- Note: Officeholder *addresses* are no longer available (privacy change)

#### Option B: Vigil.sh API (Third-party, Simple)

| Property | Value |
|----------|-------|
| **Provider** | vigil.sh (private company) |
| **Cost** | Paid subscription (pricing not public) |
| **Endpoint** | `GET https://api.vigil.sh/asic/company/find/?query={query}` |
| **Auth** | API key via `x-api-key` header |
| **Data** | Company name, ACN, ABN, type, status, registration date |
| **Directors** | ❌ Not available in their API |

**Verdict:** Not useful — doesn't include director data and costs money.

#### Option C: Manual Review (MVP Approach) ✅ RECOMMENDED FOR LAUNCH

For MVP/launch, skip automated ASIC integration entirely:

1. **ABR lookup** verifies the company exists and is active
2. **Flag all signups** for manual review
3. **Admin dashboard** shows pending contractor verifications
4. **Manual check** — admin searches ASIC Connect Online (free) to verify
5. **Approve/reject** from admin panel

**Why this is the right call:**
- ASIC API registration takes weeks
- Paid extracts at $9.40/each add up
- Director info not available for sole traders (IND type) — which is most tradies
- Manual review catches fraud that APIs can't
- We can automate later once volume justifies it

### Director Verification Roadmap

| Phase | Approach | Timeline |
|-------|----------|----------|
| **Phase 1 (Launch)** | ABR lookup + manual admin review | Now |
| **Phase 2 (Month 2-3)** | Add self-declaration: "I confirm I am an authorized representative" | Soon |
| **Phase 3 (Month 4+)** | ASIC EDGE API integration for auto-verification of Pty Ltd companies | Later |
| **Phase 4 (Scale)** | Full KYC with document verification (driver's license + ABN ownership) | When needed |

---

## 5. User Flow

### Happy Path

```
┌─────────────────────────────────────────────────────────┐
│ Step 1: Auth                                             │
│ User signs up / logs in with email                       │
└──────────────────────┬──────────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────────┐
│ Step 2: Enter ABN                                        │
│ ┌─────────────────────────────────────────────┐         │
│ │ ABN: [51 824 753 556        ] [Verify]      │         │
│ │                                              │         │
│ │ • Real-time checksum validation as they type │         │
│ │ • Format hint: "XX XXX XXX XXX"             │         │
│ │ • "Don't have an ABN?" link → ABR apply page│         │
│ └─────────────────────────────────────────────┘         │
└──────────────────────┬──────────────────────────────────┘
                       ↓ Click "Verify"
┌─────────────────────────────────────────────────────────┐
│ Step 3: Loading (1-3 seconds)                            │
│                                                          │
│ "Verifying your ABN with the Australian Business         │
│  Register..."                                            │
│                                                          │
│ [spinner animation]                                      │
└──────────────────────┬──────────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────────┐
│ Step 4: Review Company Details                           │
│                                                          │
│ ✅ ABN Verified                                          │
│                                                          │
│ Company:      Smith Constructions Pty Ltd                │
│ ABN:          51 824 753 556                             │
│ Status:       Active ✅                                  │
│ Entity Type:  Australian Private Company                 │
│ GST:          Registered since 01 Jul 2020               │
│ Location:     NSW 2000                                   │
│                                                          │
│ ☑ I confirm I am an authorized representative            │
│   of this business                                       │
│                                                          │
│ [Continue →]                                             │
└──────────────────────┬──────────────────────────────────┘
                       ↓
┌─────────────────────────────────────────────────────────┐
│ Step 5: Complete Profile                                 │
│                                                          │
│ Additional details (trade, services, etc.)               │
│ Pre-filled with ABR data where possible                  │
└─────────────────────────────────────────────────────────┘
```

### Error States

| Scenario | User Sees | Action |
|----------|-----------|--------|
| Invalid checksum | "Invalid ABN. Check the number and try again." | Immediate (no API call) |
| ABN not found on ABR | "This ABN is not registered. Please check the number." | After API call |
| ABN cancelled/inactive | "This ABN is no longer active (cancelled DD/MM/YYYY)." | Show details, block signup |
| ABN suppressed | "Limited information available for this ABN." | Show what's available |
| ABR API down | "Verification service temporarily unavailable. Please try again shortly." | Retry with exponential backoff |
| Network error | "Unable to connect to verification service." | Offer manual entry fallback |

---

## 6. Edge Cases

### 6.1 Sole Traders (Entity Type: IND)

- **ABR returns:** Individual's legal name (given name + family name), not a company name
- **No ACN** — sole traders don't have one
- **No ASIC record** — they're not registered with ASIC
- **Privacy concern:** Full legal name is public on ABR, but we should be careful about displaying it
- **Action:** Accept sole traders, but display differently:
  ```
  Entity: John Smith (Individual/Sole Trader)
  Trading as: Smith Plumbing (if business name exists)
  ```

### 6.2 Companies (PRV, PUB)

- Have both ABN and ACN
- Registered with both ABR and ASIC
- Director information available via ASIC (paid)
- **Action:** Standard flow, flag for director verification

### 6.3 Partnerships (PTR, FPT)

- Have ABN but may not have ACN
- Multiple partners
- **Action:** Accept, note partnership structure

### 6.4 Trusts (TRT, DTT, etc.)

- Common structure for builder/contractor businesses
- ABN registered to the trust, not the individual
- **Action:** Accept, note trust structure

### 6.5 Cancelled/Replaced ABNs

- ABN status is "Cancelled"
- May have a replacement ABN (replacedIdentifierValue in SOAP response)
- **Action:** Block signup, show message: "This ABN was cancelled on {date}. If you have a new ABN, please enter it instead."

### 6.6 Suppressed ABNs

- Only ABN, status, and GST info visible
- All other details hidden
- **Action:** Allow with manual review flag and warning

### 6.7 GST Not Registered

- Not all contractors are GST registered (under $75k threshold)
- **Action:** Allow signup. Show informational note:
  ```
  ℹ️ This ABN is not registered for GST. Contractors earning over $75,000/year must register.
  ```

### 6.8 Duplicate ABN Signup

- Another user already registered with this ABN
- **Action:** Block or allow with admin review flag. Message: "This ABN is already registered on RateRight. Contact support if this is an error."

---

## 7. Fraud Prevention

### Threat Model

| Threat | Risk | Mitigation |
|--------|------|------------|
| Using someone else's ABN | High | Self-declaration + manual review + future ASIC check |
| Using a cancelled ABN | Medium | ABR status check (already implemented) |
| Fabricated ABN (passes checksum) | Low | ABR API verification (already implemented) |
| Multiple accounts, same ABN | Medium | Unique constraint on ABN in database |
| ABN of unrelated business | Medium | Entity type check (should be construction-related) |

### Verification Layers

```
Layer 1: Checksum validation (client-side)
    ↓
Layer 2: ABR API verification (server-side)
    ↓
Layer 3: Status check (Active required)
    ↓
Layer 4: Self-declaration checkbox
    ↓
Layer 5: ABN uniqueness check (database)
    ↓
Layer 6: Manual admin review (dashboard)
    ↓
Layer 7 (future): ASIC director cross-reference
    ↓
Layer 8 (future): Document verification (ID + ABN ownership proof)
```

### Implementation Checklist

- [x] ABN checksum validation
- [x] ABR API lookup
- [x] ABN status verification (Active/Cancelled)
- [ ] Self-declaration checkbox ("I am authorized...")
- [ ] Unique ABN constraint in Supabase
- [ ] Admin review queue/dashboard
- [ ] Rate limiting on `/api/abn-lookup` (prevent scraping)
- [ ] Logging of all verification attempts
- [ ] Duplicate ABN detection

---

## 8. Fallback Strategy

### If ABR API is Down

```typescript
// Fallback chain in /api/abn-lookup/route.ts

async function lookupABN(abn: string): Promise<ABNLookupResult> {
  // 1. Try ABR JSON endpoint (primary)
  try {
    return await callABR(abn);
  } catch (e) {
    console.error('[ABN] ABR JSON failed:', e);
  }

  // 2. Try ABR SOAP endpoint (backup, different infrastructure)
  try {
    return await callABRSoap(abn);
  } catch (e) {
    console.error('[ABN] ABR SOAP failed:', e);
  }

  // 3. Check local cache (if previously looked up)
  const cached = await getCachedABN(abn);
  if (cached) {
    return { ...cached, stale: true };
  }

  // 4. Allow manual entry with enhanced review flag
  return {
    success: false,
    error: 'Verification service temporarily unavailable',
    allowManualEntry: true,
  };
}
```

### Caching Strategy

```typescript
// Cache ABR responses in Supabase or Redis

interface CachedABNLookup {
  abn: string;
  data: CompanyData;
  fetchedAt: string;      // ISO timestamp
  expiresAt: string;      // 24 hours after fetch
  source: 'abr_json' | 'abr_soap' | 'manual';
}

// Cache TTL: 24 hours (ABN data rarely changes)
// Stale-while-revalidate: Serve cached data if ABR is down, flag as stale
```

### If ABR GUID Not Yet Registered

Current implementation already handles this:
- In development: Returns mock data with clear `(Dev Mode)` labels
- In production without GUID: Returns 503 with "ABR service not configured"

---

## 9. Implementation Plan

### Phase 1: Launch-Ready (1-2 days)

**Priority:** Get real ABR data flowing. Everything else is polish.

#### Task 1: Register for ABR GUID
- Go to https://abr.business.gov.au/Tools/WebServicesAgreement
- Register with RateRight business details
- Wait for GUID email (usually 1-2 business days)
- Add to `.env` as `ABR_GUID`

#### Task 2: Add Self-Declaration Checkbox
```tsx
// In contractor signup page, Step 4 (Review)
<div className="flex items-start gap-2 mt-4 p-3 border rounded-md">
  <input
    type="checkbox"
    id="confirm-authorized"
    checked={isAuthorized}
    onChange={(e) => setIsAuthorized(e.target.checked)}
    className="mt-1"
  />
  <label htmlFor="confirm-authorized" className="text-sm">
    I confirm that I am an authorized representative of{' '}
    <strong>{companyData.name}</strong> (ABN: {companyData.abn}) and
    am authorized to create an account on behalf of this business.
  </label>
</div>
```

#### Task 3: Add ABN Uniqueness Check
```sql
-- Supabase migration
ALTER TABLE contractor_profiles
  ADD CONSTRAINT unique_abn UNIQUE (abn);

-- Or add check in API
CREATE INDEX idx_contractor_abn ON contractor_profiles(abn);
```

```typescript
// In signup flow, before creating profile
const { data: existing } = await supabase
  .from('contractor_profiles')
  .select('id')
  .eq('abn', cleanABN(abn))
  .single();

if (existing) {
  throw new Error('This ABN is already registered on RateRight');
}
```

#### Task 4: Rate Limit the API Route
```typescript
// Simple in-memory rate limiter for /api/abn-lookup
const rateLimiter = new Map<string, { count: number; resetAt: number }>();

function checkRateLimit(ip: string): boolean {
  const now = Date.now();
  const limit = rateLimiter.get(ip);
  
  if (!limit || now > limit.resetAt) {
    rateLimiter.set(ip, { count: 1, resetAt: now + 60000 }); // 1 min window
    return true;
  }
  
  if (limit.count >= 10) { // Max 10 lookups per minute per IP
    return false;
  }
  
  limit.count++;
  return true;
}
```

### Phase 2: Polish (Week 2)

- [ ] Add ABR response caching (Supabase table or Redis)
- [ ] Admin dashboard for manual contractor verification
- [ ] Email notification to admin on new signups
- [ ] Handle edge cases (suppressed ABNs, replaced ABNs)
- [ ] Add ABR SOAP fallback endpoint
- [ ] Sync `the-50-dollar-app` with same implementation

### Phase 3: Enhanced Verification (Month 2-3)

- [ ] Apply for ASIC EDGE API access
- [ ] Auto-verify Pty Ltd directors via ASIC
- [ ] Insurance verification integration (future)
- [ ] Licence verification (state-based builder's licence)

---

## 10. Cost Analysis

| Service | Cost | Volume | Monthly Est. |
|---------|------|--------|--------------|
| ABR Web Services | **FREE** | Unlimited (reasonable use) | $0 |
| ABR GUID Registration | **FREE** | One-time | $0 |
| ASIC EDGE API | **FREE** | After approval | $0 |
| ASIC Company Extracts | **$9.40/extract** | Only if auto-verifying directors | $0 (Phase 1) |
| Vigil.sh | Paid subscription | Not recommended | N/A |
| InfoTrack ASIC Searches | ~$5-15/search | Alternative to direct ASIC | N/A |

**Phase 1 total cost: $0/month**

The only cost is ASIC extracts if we want automated director verification in Phase 3, at $9.40 per company lookup. At 100 signups/month, that's ~$940/month — only worthwhile when we have revenue to justify it.

---

## 11. Data Schema

### Supabase Table: `contractor_profiles`

```sql
CREATE TABLE contractor_profiles (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES auth.users(id) NOT NULL,
  
  -- ABN data (from ABR)
  abn VARCHAR(11) NOT NULL UNIQUE,
  abn_status VARCHAR(20) NOT NULL DEFAULT 'Active',
  abn_status_date DATE,
  entity_name VARCHAR(200) NOT NULL,
  entity_type_code VARCHAR(4),       -- 'PRV', 'IND', 'PUB', etc.
  entity_type_name VARCHAR(100),     -- 'Australian Private Company'
  acn VARCHAR(9),                    -- NULL for sole traders
  gst_registered BOOLEAN DEFAULT false,
  gst_registered_from DATE,
  business_state VARCHAR(3),         -- 'NSW', 'VIC', etc.
  business_postcode VARCHAR(12),
  business_names TEXT[],             -- Array of trading names
  
  -- Verification status
  abr_verified BOOLEAN DEFAULT false,
  abr_verified_at TIMESTAMPTZ,
  abr_raw_response JSONB,           -- Store full ABR response
  director_verified BOOLEAN DEFAULT false,
  director_verified_at TIMESTAMPTZ,
  verification_status VARCHAR(20) DEFAULT 'pending',
    -- pending | abr_verified | fully_verified | rejected
  verification_notes TEXT,
  
  -- Self-declaration
  authorized_declaration BOOLEAN DEFAULT false,
  authorized_declared_at TIMESTAMPTZ,
  
  -- Profile data (user-provided)
  display_name VARCHAR(200),
  trade_category VARCHAR(100),       -- Plumbing, Electrical, etc.
  website VARCHAR(500),
  phone VARCHAR(20),
  logo_url TEXT,
  
  -- Metadata
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

-- Indexes
CREATE INDEX idx_contractor_abn ON contractor_profiles(abn);
CREATE INDEX idx_contractor_user ON contractor_profiles(user_id);
CREATE INDEX idx_contractor_status ON contractor_profiles(verification_status);
```

### ABR Response Cache Table

```sql
CREATE TABLE abr_cache (
  abn VARCHAR(11) PRIMARY KEY,
  response JSONB NOT NULL,
  entity_name VARCHAR(200),
  abn_status VARCHAR(20),
  fetched_at TIMESTAMPTZ DEFAULT now(),
  expires_at TIMESTAMPTZ DEFAULT (now() + INTERVAL '24 hours')
);

CREATE INDEX idx_abr_cache_expires ON abr_cache(expires_at);
```

---

## 12. Code Reference

### Existing Implementation Files

```
rateright-v2/
├── src/
│   ├── lib/
│   │   └── abn-utils.ts              ✅ Complete — validation, formatting
│   └── app/
│       ├── api/
│       │   └── abn-lookup/
│       │       └── route.ts           ✅ ~90% — ABR JSON, JSONP, mock fallback
│       └── (authenticated)/
│           └── contractor/
│               └── signup/
│                   └── page.tsx        ✅ Working — calls API, shows results
```

### Key Functions Already Implemented

```typescript
// abn-utils.ts
cleanABN(abn: string): string          // Strip non-digits
validateABN(abn: string): boolean       // Checksum validation
isValidABN(abn: string): boolean        // Alias
formatABN(abn: string): string          // "XX XXX XXX XXX" format

// route.ts
buildABRUrl(abn: string): string        // Construct ABR JSON URL
stripJSONP(jsonp: string): object       // Parse JSONP response
normaliseABRResponse(abr): CompanyData  // Normalize to our schema
verifyDirector(abn, acn, email)         // Stub — returns pending
generateMockData(abn): CompanyData      // Dev mode fallback
```

### What Needs to Be Added

```typescript
// TODO: Add to route.ts
async function checkABRCache(abn: string): Promise<CompanyData | null> { ... }
async function cacheABRResponse(abn: string, data: CompanyData): Promise<void> { ... }
function rateLimit(ip: string): boolean { ... }

// TODO: Add to contractor signup page
function DuplicateABNCheck({ abn }: { abn: string }) { ... }
function SelfDeclarationCheckbox({ ... }) { ... }

// TODO: New file - admin verification queue
// src/app/admin/verification/page.tsx
```

---

## Appendix A: ABR Test ABNs

From official ABR documentation:

| Description | ABN |
|-------------|-----|
| Suppressed ABN | 34 241 177 887 |
| Replaced ABN | 30 613 501 612 |
| Re-issued ABN | 40 348 075 953 |
| Multiple addresses | 33 531 321 789 |
| Multiple GST status | 76 093 555 992 |
| Multiple ABN status | 53 772 093 958 |
| Many name types | 97 047 258 128 |

## Appendix B: ABN Checksum Worked Example

```
ABN: 51 824 753 556

Step 1: Subtract 1 from first digit
        41 824 753 556

Step 2: Multiply by weights [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
        4×10 = 40
        1×1  = 1
        8×3  = 24
        2×5  = 10
        4×7  = 28
        7×9  = 63
        5×11 = 55
        3×13 = 39
        5×15 = 75
        5×17 = 85
        6×19 = 114

Step 3: Sum = 534

Step 4: 534 % 89 = 0 ✓ (534 = 6 × 89)
```

## Appendix C: Immediate Action Items

1. **TODAY:** Register for ABR GUID at https://abr.business.gov.au/Tools/WebServicesAgreement
2. **TODAY:** Add `ABR_GUID` placeholder to `.env.example` with registration link
3. **WHEN GUID ARRIVES:** Set in production `.env`, test with real ABNs
4. **THIS WEEK:** Add self-declaration checkbox and ABN uniqueness constraint
5. **NEXT WEEK:** Build admin verification queue
