Extended Stats¶
Extended delegated stats contain IP and ASN allocation records with an additional opaque-id field that uniquely identifies the resource holder organization. This allows tracking resources by organization across the entire APNIC registry.
Overview¶
The extended format is identical to the standard delegated format but includes an 8th column (opaque-id) that serves as a persistent, privacy-preserving identifier for the organization holding the resource. This enables aggregation and analysis by resource holder without exposing organization names.
flowchart TB
subgraph Comparison["Delegated vs Extended"]
direction LR
subgraph Delegated["Standard Delegated"]
D1["7 fields<br/>registry|cc|type|start|value|date|status"]
end
subgraph Extended["Extended Delegated"]
E1["8+ fields<br/>registry|cc|type|start|value|date|status|opaque-id"]
end
end
Delegated -->|"Add opaque-id"| Extended
subgraph UseCase["Use Cases"]
UC1["Track resources by organization"]
UC2["Cross-reference with RDAP entities"]
UC3["Aggregate holdings per holder"]
UC4["Filter by opaque-id"]
end
Extended --> UC1 --> UC2 --> UC3 --> UC4 Methods¶
| Method | Description |
|---|---|
FetchExtendedEntries(ctx) | Fetch latest extended stats |
GetExtendedEntries(ctx) | Cached extended stats (30min TTL) |
FetchExtendedEntriesByDate(ctx, date) | Fetch by date (YYYYMMDD format) |
FetchExtendedResult(ctx, date) | Full result with header/summary/entries |
FetchExtendedByYear(ctx, year) | Fetch by year (last day of year) |
FetchExtendedResultByYear(ctx, year) | Full result by year |
Method Signatures¶
// Fetch latest extended stats, returns full result
func (c *Client) FetchExtendedEntries(ctx context.Context) (*ExtendedResult, error)
// Cached variant - fetches fresh data if cache expired
func (c *Client) GetExtendedEntries(ctx context.Context) ([]DelegatedExtendedEntry, error)
// Fetch by specific date (YYYYMMDD format)
func (c *Client) FetchExtendedEntriesByDate(ctx context.Context, date string) (*ExtendedResult, error)
// Full result including header and summaries
func (c *Client) FetchExtendedResult(ctx context.Context, date string) (*ExtendedResult, error)
// Fetch by year (returns data from December 31 of that year)
func (c *Client) FetchExtendedResultByYear(ctx context.Context, year int) (*ExtendedResult, error)
Data Structures¶
DelegatedExtendedEntry¶
type DelegatedExtendedEntry struct {
Registry string // "apnic"
Country string // ISO 3166-1 alpha-2 country code
Type string // "ipv4", "ipv6", or "asn"
Start string // Starting address or AS number
Value int64 // Count (IPv4: IPs, IPv6: prefix length, ASN: count)
Date time.Time // Allocation/assignment date
Status string // "allocated", "assigned", "available", "reserved"
OpaqueID string // Unique identifier for the organization (e.g., "A92E1062")
Extensions []string // Additional extension fields
}
ExtendedResult¶
type ExtendedResult struct {
Header StatsFileHeader // File metadata
Summaries []StatsSummary // Per-type summaries
Entries []DelegatedExtendedEntry // Data records with opaque-IDs
}
Comparison: Standard vs Extended¶
flowchart LR
subgraph Source["Source Data"]
Standard["delegated-apnic-latest<br/>7 columns"]
ExtFile["delegated-apnic-extended-latest<br/>8+ columns"]
end
subgraph Models["Result Types"]
DE["DelegatedEntry<br/>No opaque-id"]
EE["DelegatedExtendedEntry<br/>Includes opaque-id"]
end
Standard --> DE
ExtFile --> EE
subgraph Capability["Additional Capability"]
FilterID["Filter by opaque-id"]
GroupID["Group by organization"]
CrossRef["Cross-reference<br/>RDAP entities"]
end
EE --> FilterID --> GroupID --> CrossRef | Feature | Standard Delegated | Extended Delegated |
|---|---|---|
| Fields | 7 | 8+ |
| Organization ID | No | Yes (opaque-id) |
| Filtering by holder | No | Yes |
| Cross-RIR tracking | No | Yes (via REx) |
| File size | Smaller | Slightly larger |
Examples¶
Basic Usage¶
package main
import (
"context"
"fmt"
"log"
apnic "github.com/cyberspacesec/apnic-skills"
)
func main() {
client := apnic.NewClient()
ctx := context.Background()
// Fetch latest extended stats
result, err := client.FetchExtendedEntries(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Total entries: %d\n", len(result.Entries))
// Print first 5 entries with opaque-ids
for i, entry := range result.Entries {
if i >= 5 {
break
}
fmt.Printf("%s: %s/%d (%s) - OpaqueID: %s\n",
entry.Country, entry.Start, entry.Value, entry.Status, entry.OpaqueID)
}
}
Filtering by Opaque-ID¶
// Get extended entries
entries, err := client.GetExtendedEntries(ctx)
if err != nil {
log.Fatal(err)
}
// Filter by opaque-id
filtered := apnic.NewExtendedFilter(entries).
ByOpaqueID("A92E1062").
Result()
fmt.Printf("Resources held by A92E1062: %d\n", len(filtered))
// Show all resources for this organization
for _, entry := range filtered {
fmt.Printf(" %s: %s/%d (%s)\n",
entry.Type, entry.Start, entry.Value, entry.Status)
}
Grouping by Organization¶
entries, _ := client.GetExtendedEntries(ctx)
// Group entries by opaque-id
groups := apnic.GroupExtendedByOpaqueID(entries)
// Print top 10 organizations by resource count
type orgCount struct {
opaqueID string
count int
}
var counts []orgCount
for id, entries := range groups {
counts = append(counts, orgCount{id, len(entries)})
}
// Sort by count descending
sort.Slice(counts, func(i, j int) bool {
return counts[i].count > counts[j].count
})
fmt.Println("Top 10 organizations by resource count:")
for i, c := range counts {
if i >= 10 {
break
}
fmt.Printf(" %s: %d resources\n", c.opaqueID, c.count)
}
Filtering by Country and Type¶
entries, _ := client.GetExtendedEntries(ctx)
// Filter Japanese IPv6 allocations
jpIPv6 := apnic.NewExtendedFilter(entries).
ByCountry("JP").
ByType("ipv6").
Result()
fmt.Printf("Japan IPv6 allocations: %d\n", len(jpIPv6))
// Filter Australian ASN assignments
auASN := apnic.NewExtendedFilter(entries).
ByCountry("AU").
ByType("asn").
ByStatus("assigned").
Result()
fmt.Printf("Australia assigned ASNs: %d\n", len(auASN))
Historical Comparison¶
// Compare extended stats from two dates
current, _ := client.FetchExtendedResult(ctx, "")
past, _ := client.FetchExtendedResult(ctx, "20230101")
// Count entries by opaque-id
currentCounts := make(map[string]int)
for _, e := range current.Entries {
currentCounts[e.OpaqueID]++
}
pastCounts := make(map[string]int)
for _, e := range past.Entries {
pastCounts[e.OpaqueID]++
}
// Find organizations that gained resources
fmt.Println("Organizations that gained IPv4 resources:")
for id, count := range currentCounts {
if pastCount := pastCounts[id]; count > pastCount {
fmt.Printf(" %s: %d -> %d\n", id, pastCount, count)
}
}
Cross-referencing with RDAP¶
// Get extended entry and look up entity in RDAP
entries, _ := client.GetExtendedEntries(ctx)
// Find a specific organization's resources
for _, entry := range entries {
if entry.OpaqueID == "A92E1062" && entry.Type == "ipv4" {
// Look up the network in RDAP
network, err := client.RDAPLookupIP(ctx, entry.Start)
if err != nil {
continue
}
fmt.Printf("Network: %s\n", network.Handle)
fmt.Printf("Name: %s\n", network.Name)
fmt.Printf("Country: %s\n", network.Country)
fmt.Printf("Type: %s\n", network.Type)
// Print associated entities
for _, entity := range network.Entities {
fmt.Printf(" Entity: %s (roles: %v)\n", entity.Handle, entity.Roles)
}
break
}
}
File Format¶
The extended stats file uses pipe-delimited format with 8+ columns:
# Header
2|apnic|1737030783|45837|20240101|20240116|0
# Summary lines
apnic|*|ipv4|12345
apnic|*|ipv6|2345
apnic|*|asn|567
# Data lines (with opaque-id)
apnic|AU|ipv4|1.0.0.0|256|20110811|allocated|A92E1062
apnic|CN|ipv6|2001:200::|32|19990714|allocated|B12F3456
apnic|JP|asn|173|1|19930101|allocated|C78D9012
Opaque-ID Format¶
The opaque-id is a unique, persistent identifier for resource holders:
- Format: Alphanumeric string (e.g.,
A92E1062,ORG-ARAD1-AP) - Purpose: Privacy-preserving organization identification
- Persistence: Remains constant across resource changes
- Cross-RIR: Can be used with REx API to find resources in other RIRs
Data Sources¶
- Latest:
ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-extended-latest - Archived:
ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-extended-YYYYMMDD - By Year:
ftp://ftp.apnic.net/pub/stats/apnic/YYYY/delegated-apnic-extended-YYYY1231.gz
See Also¶
- Delegated Stats - Standard delegated stats without opaque-IDs
- RDAP - Structured registration data queries
- REx Cross-RIR - Cross-RIR resource lookup by opaque-id