V1Updated December 2025

Public API Reference

Programmatic access to Australia's Register of Radiocommunications Licences. Query sites, licences, devices, and clients with powerful filtering, pagination, and GeoJSON export.

The ACMA RRL Public API provides programmatic access to Australia's Register of Radiocommunications Licences data. This guide covers authentication, available endpoints, filtering, pagination, and rate limits.

#Table of Contents


#Authentication

All API requests require authentication via API key.

Header Format:

Authorization: Bearer YOUR_API_KEY

Or alternatively:

X-API-Key: YOUR_API_KEY

API keys are managed in your account settings. Each key is scoped to your subscription tier.


#Base URL

https://app.spectaura.com.au/api/v1/

All endpoints support CORS for browser-based requests.


#Endpoints

Data API

GET /api/v1

Query ACMA database tables with filtering, sorting, and pagination.

Query Parameters:

ParameterTypeDefaultDescription
tablestringrequiredTable to query
columnsstringallComma-separated column names
sortstring-Column to sort by
orderstringascSort direction: asc or desc
pageinteger1Page number (1-indexed)
limitinteger100Results per page (max: 1000)
filter[column]various-Filter values (see Filtering)

Response Format:

json
{
  "success": true,
  "data": [...],
  "pagination": {
    "total": 1234,
    "page": 1,
    "limit": 100,
    "totalPages": 13,
    "hasNext": true,
    "hasPrev": false
  },
  "meta": {
    "table": "site",
    "columns": ["site_id", "name", "latitude", "longitude"],
    "filters": {},
    "queryTimeMs": 45
  },
  "rateLimit": {
    "limit": 1000,
    "remaining": 999,
    "resetAt": "2025-01-01T00:01:00.000Z"
  }
}

Response Headers:

HeaderDescription
X-Total-CountTotal matching records
X-PageCurrent page number
X-Page-SizeResults per page
X-Total-PagesTotal pages available
X-Query-Time-MsQuery execution time
X-RateLimit-LimitRate limit ceiling
X-RateLimit-RemainingRemaining requests
X-RateLimit-ResetReset timestamp (Unix ms)

Schema API

GET /api/v1/schema

Get schema information for available tables. Requires API key authentication.

Query Parameters:

ParameterTypeRequiredDescription
tablestringNoSpecific table to get schema for. If omitted, returns list of all tables.

List All Tables Response:

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.example.com/api/v1/schema"
json
{
  "success": true,
  "tables": [
    { "name": "site", "description": "Transmitter site locations", "estimatedRows": "~127,000" },
    { "name": "licence", "description": "Radio communication licences", "estimatedRows": "~163,000" },
    ...
  ]
}

Get Table Schema Response:

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.example.com/api/v1/schema?table=site"
json
{
  "success": true,
  "schema": {
    "table": "site",
    "description": "Transmitter site locations",
    "estimatedRows": "~127,000",
    "primaryKey": "site_id",
    "columns": [
      { "name": "site_id", "type": "string", "nullable": false, "filterable": true, "sortable": true, "description": "Unique site identifier" },
      { "name": "latitude", "type": "number", "nullable": true, "filterable": true, "sortable": true, "description": "Latitude in decimal degrees (WGS84)" },
      ...
    ]
  }
}

Intermod API

POST /api/v1/intermod

Calculate third-order intermodulation (IM3) products for transmitters at a site, identifying potential interference with a reference frequency.

Tier Access:

TierAccessDaily Limit
FreeDisabled0
ProfessionalEnabled50/day
BusinessEnabledUnlimited

Request Body:

json
{
  "siteId": "string (required)",
  "reference": {
    "licenceNo": "string"
  }
}

Or with manual frequency:

json
{
  "siteId": "string (required)",
  "reference": {
    "frequency": 450000000,
    "bandwidth": 25000
  }
}
FieldTypeRequiredDescription
siteIdstringYesSite ID to analyse
reference.licenceNostringConditionalLicence number at the site to use as reference frequency
reference.frequencynumberConditionalManual reference frequency in Hz
reference.bandwidthnumberConditionalManual reference bandwidth in Hz

Note: Provide either licenceNo OR both frequency and bandwidth, not both.

Response Format:

json
{
  "success": true,
  "data": {
    "reference": {
      "frequency": 450000000,
      "bandwidth": 25000,
      "licenceNo": "1234567/1",
      "source": "licence"
    },
    "siteId": "10001",
    "siteName": "MOUNT DANDENONG",
    "deviceCount": 45,
    "products": [
      {
        "productType": "2f1-f2",
        "frequency": 449750000,
        "bandwidth": 75000,
        "device1": {
          "sddId": "123456",
          "licenceNo": "1234567/1",
          "frequency": 450000000
        },
        "device2": {
          "sddId": "123457",
          "licenceNo": "1234568/1",
          "frequency": 450250000
        },
        "overlapStatus": "partial",
        "overlapPercentage": 35,
        "separationHz": 0
      }
    ],
    "summary": {
      "totalProducts": 124,
      "fullOverlaps": 2,
      "partialOverlaps": 5,
      "adjacentProducts": 12,
      "riskLevel": "medium"
    },
    "calculatedAt": "2025-01-15T10:30:00.000Z"
  },
  "meta": {
    "queryTimeMs": 156,
    "tier": "professional"
  },
  "rateLimit": {
    "limit": 50,
    "remaining": 49,
    "resetAt": "2025-01-16T00:00:00.000Z"
  }
}

Overlap Status Values:

StatusDescription
fullIM3 product completely overlaps reference band
partialIM3 product partially overlaps reference band
adjacentIM3 product is within 2× bandwidth of reference band edge
noneNo overlap or adjacency

Risk Level:

LevelCriteria
highAny full overlaps
mediumPartial overlaps but no full overlaps
lowAdjacent products only
noneNo overlapping or adjacent products

Limits:

  • Maximum 500 transmitters per site
  • Sites exceeding this limit return TOO_MANY_DEVICES error

Error Codes (specific to Intermod API):

CodeStatusDescription
SITE_NOT_FOUND404Site ID does not exist
REFERENCE_NOT_FOUND404Licence not found at specified site
TOO_MANY_DEVICES400Site exceeds 500 transmitter limit
TIER_ACCESS_DENIED403Subscription tier does not allow intermod access

Example Request:

bash
curl -X POST \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"siteId": "10001", "reference": {"licenceNo": "1234567/1"}}' \
  "https://app.spectaura.com.au/api/v1/intermod"

#Available Tables

Table access is controlled by subscription tier. All ACMA database tables are available to Professional subscribers, with additional views and materialized views available to Business subscribers.

Professional Tier Tables

All core ACMA RRL tables:

TableDescriptionEst. Rows
siteTransmitter site locations~127,000
licenceRadio communication licences~163,000
device_detailsTransmitter/receiver equipment~2,400,000
clientLicence holder records~14,000
antennaAntenna specifications~8,500
bslBroadcasting Service Licences~3,600
auth_spectrum_areaAWL spectrum authorization areas~3,400
auth_spectrum_freqSpectrum frequency allocations~3,400

Plus reference/lookup tables: access_area, antenna_pattern, antenna_polarity, applic_text_block, bsl_area, class_of_station, client_type, fee_status, industry_cat, licence_service, licence_status, licence_subservice, licensing_area, nature_of_service, reports_text_block, satellite.

Business Tier Additional Tables

Business subscribers have access to all Professional tables plus:

TableTypeDescription
base_infoMaterialized ViewPre-aggregated site/licence data with geometry
point_to_point_linksMaterialized ViewP2P microwave link pairs with geometry
awl_infoMaterialized ViewAWL licence spectrum areas with polygon geometry
client_infoViewEnriched client data with industry info
bsl_infoViewBroadcasting licence details
sites_without_licenceViewOrphaned site records

#Table Schemas

Schema details for commonly used tables. Use the /api/v1/schema?table={name} endpoint to get full schema information for any table.

site (Professional+)

ColumnTypeFilterableSortableDescription
site_idstringYesYesUnique site identifier
namestringYesYesSite name
latitudenumberYesYesLatitude (WGS84)
longitudenumberYesYesLongitude (WGS84)
statestringYesYesState/territory code
postcodestringYesYesPostal code
elevationnumberYesYesElevation (metres ASL)
site_precisionstringYesNoLocation precision indicator
hcis_l2stringYesNoHCIS Level 2 classification

licence (Professional+)

ColumnTypeFilterableSortableDescription
licence_nostringYesYesUnique licence number
client_nobigintYesYesClient/licensee number
sv_idbigintYesYesService type ID
ss_idbigintYesYesSubservice type ID
licence_type_namestringYesYesLicence type
licence_category_namestringYesYesLicence category
date_issueddateYesYesIssue date
date_of_effectdateYesYesEffective date
date_of_expirydateYesYesExpiry date
statusstringYesYesStatus code
status_textstringYesYesStatus description
bsl_nostringYesYesBSL number (if applicable)
awl_typestringYesYesAWL type

client_info (Business)

ColumnTypeFilterableSortableDescription
client_nobigintYesYesUnique client number
licenceestringYesYesLicensee name*
trading_namestringYesYesTrading name
postal_statestringYesYesPostal state
postal_postcodestringYesYesPostal postcode
industrystringYesYesIndustry category
client_typestringYesYesType (Company, Person, etc.)
fee_statusstringYesYesFee status

*Personal details are redacted for individual licensees (client_type: "Person")

Professional tier: Use the client table for basic client data.

device_details (Professional+)

ColumnTypeFilterableSortableDescription
sdd_idbigintYesYesUnique device ID
licence_nostringYesYesAssociated licence
site_idstringYesYesSite location
device_typestringYesYesDevice type
frequencynumberYesYesOperating frequency (Hz)
bandwidthnumberYesYesBandwidth (Hz)
polarisationstringYesYesSignal polarisation
transmitter_powernumberYesYesTransmitter power
transmitter_power_unitstringYesNoPower unit
eirpnumberYesYesEIRP value
eirp_unitstringYesNoEIRP unit
heightnumberYesYesAntenna height (m)
azimuthnumberYesYesAzimuth (degrees)
antenna_idstringYesYesAssociated antenna
sv_idbigintYesYesService type ID
ss_idbigintYesYesSubservice type ID
efl_idbigintYesYesEquipment Freq Licence ID
call_signstringYesYesStation call sign

base_info (Business)

Pre-aggregated site and licence data optimized for map display and filtering.

ColumnTypeFilterableSortableDescription
licence_nostringYesYesLicence number
site_idstringYesYesSite identifier
sv_idbigintYesYesService type ID
ss_idbigintYesYesSubservice type ID
licence_type_namestringYesYesLicence type
licence_category_namestringYesYesLicence category
date_issueddateYesYesDate issued
date_of_effectdateYesYesDate of effect
date_of_expirydateYesYesDate of expiry
licence_statusstringYesYesLicence status
client_nobigintYesYesClient number
bsl_nostringYesYesBSL number
licenceestringYesYesLicensee name*
industrystringYesYesIndustry
client_typestringYesYesClient type
site_namestringYesYesSite name
latitudenumberYesYesLatitude
longitudenumberYesYesLongitude
statestringYesYesState/territory
postcodestringYesYesPostcode
site_licence_countbigintYesYesLicences at site
has_awlbooleanYesYesHas AWL
has_bslbooleanYesYesHas BSL

#Filtering

Filter syntax: filter[column][operator]=value

Operators:

OperatorDescriptionExample
eqEqual tofilter[state][eq]=NSW
neNot equal tofilter[state][ne]=NSW
inIn arrayfilter[state][in]=NSW,VIC,QLD
gteGreater than or equalfilter[frequency][gte]=450000000
lteLess than or equalfilter[frequency][lte]=470000000
gtGreater thanfilter[date_issued][gt]=2024-01-01
ltLess thanfilter[date_of_expiry][lt]=2025-12-31
betweenBetween range (inclusive)filter[frequency][between]=400000000,500000000
likeSQL LIKE patternfilter[name][like]=Mount%
ilikeCase-insensitive LIKEfilter[licencee][ilike]=%some_licencee%
isNULL checkfilter[bsl_no][is]=null

Shorthand:

filter[column]=value is equivalent to filter[column][eq]=value

Type-Specific Operators:

Column TypeAllowed Operators
stringeq, ne, in, like, ilike, is, between
number/biginteq, ne, in, gte, lte, gt, lt, between, is
dateeq, ne, gte, lte, gt, lt, between, is
booleaneq

Query Complexity Limits:

To protect database performance, the following limits apply:

LimitValueDescription
Max filters10Maximum number of filter conditions per request
Max IN values100Maximum values in a single in clause

#GeoJSON Format

The API supports GeoJSON output for tables with geometry columns. Add format=geojson to get RFC 7946 compliant GeoJSON.

GeoJSON Request

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.example.com/api/v1?table=site&format=geojson&filter[state]=NSW"

GeoJSON Parameters

ParameterTypeDefaultDescription
formatstringjsonSet to geojson for GeoJSON output
precisioninteger6Coordinate decimal places (1-10)
bboxstring-Bounding box filter: minLon,minLat,maxLon,maxLat

GeoJSON Response

json
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "id": "10001",
      "geometry": {
        "type": "Point",
        "coordinates": [151.209900, -33.865143]
      },
      "properties": {
        "site_id": "10001",
        "name": "Sydney Tower",
        "state": "NSW"
      }
    }
  ],
  "bbox": [150.5, -34.2, 152.0, -33.0],
  "metadata": {
    "success": true,
    "table": "site",
    "geometryColumn": "geom_4326",
    "featureCount": 150,
    "results": {
      "total": 150,
      "returned": 150,
      "truncated": false,
      "limit": -1
    },
    "queryTimeMs": 234,
    "rateLimit": { ... }
  }
}

Precision and File Size

The precision parameter controls coordinate decimal places, affecting both accuracy and response size:

PrecisionAccuracyExampleUse Case
1~11 km151.2Regional overview
2~1.1 km151.21City-level
3~110 m151.210Suburb-level
4~11 m151.2099Street-level
5~1.1 m151.20990Building-level
6~0.11 m151.209900Default - Survey grade
7+<0.01 m151.2099000Unnecessary for most uses

File size impact: Each additional decimal place adds ~2 bytes per coordinate. For a 100,000 feature response:

PrecisionApprox. Size
3~15 MB
6~20 MB
10~28 MB

Recommendation: Use precision 5-6 for most applications. Lower precision for large exports or visualization-only use cases.

Tables with Geometry Support

TableGeometry TypeDescription
sitePointTransmitter site locations
base_infoPointPre-aggregated site data
point_to_point_linksLineStringP2P microwave links
awl_infoPolygonArea Wide spectrum Licences

GeoJSON Pagination

Important: GeoJSON format does NOT support pagination. The page and limit parameters are rejected for GeoJSON requests. All matching features (up to your tier's GeoJSON row limit) are returned in a single response.

Use filters and bbox to reduce result size instead of pagination.

NDJSON Streaming (Large Responses)

For large GeoJSON responses (10,000+ features), the API supports Newline Delimited JSON (NDJSON) streaming. This reduces memory usage and allows progressive processing.

Request streaming:

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Accept: application/x-ndjson" \
  "https://api.example.com/api/v1?table=site&format=geojson"

NDJSON Response Format:

Each line is a separate JSON object:

{"type":"header","format":"ndjson-geojson","version":"1.0","table":"site",...} {"type":"Feature","id":"10001","geometry":{...},"properties":{...}} {"type":"Feature","id":"10002","geometry":{...},"properties":{...}} ... {"type":"footer","results":{"total":50000,"returned":50000},"queryTimeMs":1234}

Response Headers for Streaming:

HeaderValue
Content-Typeapplication/x-ndjson
Transfer-Encodingchunked
X-Streamingtrue
X-Streaming-Threshold10000

#Pagination & Sorting

Pagination Parameters

ParameterTypeDefaultMaxDescription
pageinteger1-Page number (1-indexed)
limitinteger1001000Results per page

Tier-Based Limits:

TierMax Rows Per Request
FreeAPI access disabled
Professional1,000
Business10,000

Sorting

?sort=column&order=asc|desc

Only columns marked as "sortable" can be used for sorting.


#Rate Limits

Authenticated API Requests

Rate limits are determined by your subscription tier. View your current limits and usage in your Account API Settings.

Note: API access requires a Professional or Business subscription. Free tier users do not have API access.

Tile Requests

Tiles are rate limited separately at 300 requests per minute per IP.

Rate Limit Headers

All responses include:

X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 999 X-RateLimit-Reset: 1735689600000

When rate limited, responses return 429 with:

Retry-After: 60

#Error Handling

Error Response Format

json
{
  "success": false,
  "error": {
    "code": "INVALID_FILTER",
    "message": "Column 'foo' is not filterable",
    "details": {
      "field": "filter[foo]"
    }
  }
}

Error Codes

Authentication Errors (401)

CodeDescription
AUTHENTICATION_REQUIREDAPI key missing from request
INVALID_API_KEYAPI key not found in database
INVALID_KEY_FORMATMalformed API key (must start with sk_live_)
EXPIRED_API_KEYAPI key has passed its expiration date
REVOKED_API_KEYAPI key has been revoked by the user

Authorization Errors (403)

CodeDescription
TIER_ACCESS_DENIEDSubscription tier does not allow API access
SCOPE_ACCESS_DENIEDAPI key scope does not permit this operation
TABLE_ACCESS_DENIEDTable not available for your subscription tier

Rate Limit Errors (429)

CodeDescription
RATE_LIMIT_EXCEEDEDRequest rate limit exceeded. See Retry-After header.

Validation Errors (400)

CodeDescription
INVALID_TABLEUnknown or unavailable table name
INVALID_COLUMNUnknown column name for the table
INVALID_FILTERInvalid filter syntax
INVALID_FILTER_OPERATORUnknown filter operator
INVALID_FILTER_VALUEValue type doesn't match column type
INVALID_SORT_COLUMNColumn is not sortable
INVALID_PAGINATIONInvalid page or limit value
ROW_LIMIT_EXCEEDEDRequested limit exceeds tier maximum
TOO_MANY_FILTERSMore than 10 filter conditions
TOO_MANY_IN_VALUESMore than 100 values in IN clause
INVALID_BETWEEN_VALUEInvalid between range format

GeoJSON Errors (400)

CodeDescription
TABLE_NO_GEOMETRYTable does not support GeoJSON output
INVALID_BBOXInvalid bounding box format
INVALID_PRECISIONPrecision must be 1-10
GEOJSON_ROW_LIMIT_EXCEEDEDGeoJSON result exceeds tier limit

Server Errors (500+)

CodeStatusDescription
QUERY_ERROR500Database query failed
QUERY_TIMEOUT504Query exceeded time limit (add filters to reduce scope)
INTERNAL_ERROR500Unexpected server error

#Examples

Basic Site Query

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.example.com/api/v1?table=site&filter[state][in]=NSW,VIC&limit=10"

Frequency Range Search (using between)

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.example.com/api/v1?table=device_details&filter[frequency][between]=450000000,470000000&sort=frequency&order=asc"

Licences Expiring Soon (using between)

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.example.com/api/v1?table=licence&filter[date_of_expiry][between]=2025-01-01,2025-06-30&sort=date_of_expiry"

Exclude Specific State (using ne)

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.example.com/api/v1?table=site&filter[state][ne]=NSW&limit=50"

Client Search (Case-Insensitive)

bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.example.com/api/v1?table=client_info&filter[licencee][ilike]=%some_licencee%"
bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.example.com/api/v1?table=point_to_point_links&filter[client_no][eq]=12345&columns=a_site_id,b_site_id,distance_km,a_frequency"

#Privacy Notice

Personal information of individual licensees (where client_type is "Person") is automatically redacted in API responses. This includes:

  • licencee - replaced with "[REDACTED]"
  • trading_name - set to null
  • postal_state - set to null
  • postal_postcode - set to null

This redaction is applied to all endpoints and export functions.


#Example Integrations

Python

python
import requests

API_KEY = "sk_live_your_api_key_here"
BASE_URL = "https://api.example.com/api/v1"

headers = {"Authorization": f"Bearer {API_KEY}"}

# Query sites in NSW
response = requests.get(
    BASE_URL,
    headers=headers,
    params={
        "table": "site",
        "filter[state][eq]": "NSW",
        "limit": 100
    }
)

data = response.json()
if data["success"]:
    for site in data["data"]:
        print(f"{site['site_id']}: {site['name']}")

    # Check rate limit
    remaining = data["rateLimit"]["remaining"]
    print(f"Requests remaining: {remaining}")

JavaScript/Node.js

javascript
const API_KEY = "sk_live_your_api_key_here";
const BASE_URL = "https://api.example.com/api/v1";

async function querySites(state) {
  const params = new URLSearchParams({
    table: "site",
    "filter[state][eq]": state,
    limit: "100"
  });

  const response = await fetch(`${BASE_URL}?${params}`, {
    headers: { Authorization: `Bearer ${API_KEY}` }
  });

  const data = await response.json();

  if (!data.success) {
    throw new Error(`API Error: ${data.error.code} - ${data.error.message}`);
  }

  return data;
}

// Usage
const result = await querySites("VIC");
console.log(`Found ${result.pagination.total} sites`);

GeoJSON with Python (GeoPandas)

python
import requests
import geopandas as gpd
from io import StringIO

API_KEY = "sk_live_your_api_key_here"

# Fetch GeoJSON
response = requests.get(
    "https://api.example.com/api/v1",
    headers={"Authorization": f"Bearer {API_KEY}"},
    params={
        "table": "site",
        "format": "geojson",
        "filter[state][eq]": "NSW",
        "precision": 6
    }
)

# Load into GeoPandas
gdf = gpd.GeoDataFrame.from_features(response.json()["features"])
print(f"Loaded {len(gdf)} features")

# Save to file
gdf.to_file("nsw_sites.geojson", driver="GeoJSON")

NDJSON Streaming (Python)

python
import requests
import json

API_KEY = "sk_live_your_api_key_here"

# Stream large GeoJSON response
response = requests.get(
    "https://api.example.com/api/v1",
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Accept": "application/x-ndjson"
    },
    params={"table": "site", "format": "geojson"},
    stream=True
)

features = []
for line in response.iter_lines():
    if line:
        obj = json.loads(line)
        if obj.get("type") == "Feature":
            features.append(obj)
        elif obj.get("type") == "footer":
            print(f"Query completed in {obj['queryTimeMs']}ms")

print(f"Processed {len(features)} features")

Pagination Loop (All Results)

python
import requests

API_KEY = "sk_live_your_api_key_here"
BASE_URL = "https://api.example.com/api/v1"

def fetch_all(table, filters=None):
    """Fetch all records using pagination."""
    all_data = []
    page = 1
    limit = 1000

    while True:
        params = {"table": table, "page": page, "limit": limit}
        if filters:
            params.update(filters)

        response = requests.get(
            BASE_URL,
            headers={"Authorization": f"Bearer {API_KEY}"},
            params=params
        )

        data = response.json()
        if not data["success"]:
            raise Exception(f"Error: {data['error']['message']}")

        all_data.extend(data["data"])

        if not data["pagination"]["hasNext"]:
            break

        page += 1

    return all_data

# Fetch all Victorian licences
licences = fetch_all("licence", {"filter[state][eq]": "VIC"})
print(f"Total licences: {len(licences)}")

#Support

For API issues or questions:

  • Check the status page
  • Contact support via the website
  • Review your API usage in account settings