Almost every APRS packet you see on a live map began life as an AX.25 frame transmitted over a radio link. (The exception: stations that connect straight to APRS-IS over the Internet and never touch a radio. Their packets are written in the same TNC-2 text form, but no RF frame was ever keyed — and because they never have to fit in an AX.25 address field, their source identifiers can break the rules below: longer than six characters, lower-case, non-numeric SSIDs.) AX.25 is the link-layer protocol that defines how bytes on a wire — or in our case, audio tones on an FM channel — are grouped into frames, addressed, framed for error detection, and exchanged between stations. APRS is layered on top of AX.25 the same way HTTP is layered on top of TCP: AX.25 worries about delivery of a self-contained chunk of bytes between two callsigns, and APRS worries about what those bytes mean.
This guide is the technical-deep-dive companion to our What is APRS? pillar. It is aimed at operators, developers, and anyone who wants to understand exactly what is in an APRS packet at the byte level. If you are decoding raw frames with Direwolf, writing your own TNC software, debugging a stubborn iGate, or just curious — this is for you.
AX.25 is itself an adaptation of the X.25 packet-switched data protocol that dominated commercial data networks from the late 1970s through the early 1990s. X.25 was an ITU standard for connection-oriented packet switching over reliable links — the predecessor of frame relay and conceptually a cousin to TCP/IP.
In 1982, the TAPR (Tucson Amateur Packet Radio) group adapted X.25 to amateur radio's needs: half-duplex links, no guaranteed acknowledgement, hams' callsigns as addresses, and a much simpler framing strategy. The result was AX.25, with the "A" for "Amateur". The version most TNCs implement is AX.25 v2.0, published by the ARRL in 1984. The current specification is v2.2, issued in 1998 (AX.25 Link Access Protocol v2.2 specification).
For decades, AX.25 was the universal link layer for amateur packet radio — BBS systems, mailbox stores, the early KA-Node mesh routers, and TCP/IP over packet (the famous "44.0.0.0/8" Amprnet address block). Most of that ecosystem faded with the rise of consumer Internet, but APRS survived because it solved a problem (real-time situational awareness over RF) that the Internet did not address. AX.25 came along for the ride.
A complete AX.25 frame at the link layer looks like this (sizes in bytes):
+------+------------+----------+-------+--------------+-----+------+
| Flag | Address | Control | PID | Information | FCS | Flag |
| 1 | 14 to 70 | 1 or 2 | 0 or 1| 0 to 256 | 2 | 1 |
+------+------------+----------+-------+--------------+-----+------+
Each field has a precise job:
The actual on-the-wire form is bit-stuffed: every five consecutive 1-bits in the payload get a 0-bit inserted after them so that the framing flag (0x7E = 01111110, six consecutive 1-bits) cannot accidentally appear inside a frame. The TNC handles bit-stuffing transparently.
Each AX.25 address slot is exactly 7 bytes:
The left-shift is a quirk inherited from X.25 framing where the lowest bit of each address byte was reserved as an "address extension" flag — a 1 in that bit signalled "this is the last address byte in the field", letting digipeaters be appended dynamically. AX.25 inherited the convention.
Decoding an address byte back to ASCII is a single right-shift by one bit and a mask. Encoding is the opposite. This is the single biggest "gotcha" for anyone writing an AX.25 decoder for the first time; raw frames look like garbage in a hex dump because the callsigns are bit-shifted.
A worked example. The callsign "W1ABC-9" as an address field:
W -> 0x57 -> shift left -> 0xAE
1 -> 0x31 -> shift left -> 0x62
A -> 0x41 -> shift left -> 0x82
B -> 0x42 -> shift left -> 0x84
C -> 0x43 -> shift left -> 0x86
-> 0x20 -> shift left -> 0x40 (space-padded to 6 chars)
SSID byte: 0x60 | (9 << 1) = 0x72 -> 0x73 when it is the last address byte
The SSID byte's exact encoding depends on whether the callsign is the source, destination, or a digipeater, and whether it has already been used (the "H bit" — has-been-repeated). The full table is in the AX.25 spec, section 3.12.
The SSID (Secondary Station Identifier) is a 4-bit value (0-15) appended to a callsign with a dash. So W1ABC, W1ABC-1, W1ABC-9, and W1ABC-15 are all distinct AX.25 addresses for what is, in real life, the same operator's licence.
In APRS, SSIDs are used informally to identify multiple stations under one callsign. There is a long-standing community convention (not a hard rule) that goes roughly:
These are the recommendations in APRS Spec 1.2 chapter 21 and on aprs.org. They are not enforced. Some clients display the SSID with a default icon if no APRS symbol is otherwise set.
Important: a callsign without an SSID is the same address as -0. W1ABC and W1ABC-0 are the same AX.25 address. Most APRS software treats them identically.
The control byte identifies the frame type. AX.25 v2.0 defines three families:
APRS uses UI frames exclusively (control byte 0x03). The design choice is deliberate: APRS is a one-to-many broadcast protocol. Every packet is self-contained, addressed to a destination (which is usually a generic "APRS" symbol-encoding destination, not a specific station), and may be heard by many receivers. There is no notion of an APRS "session" between two stations. UI frames are the right tool because they require no setup, no teardown, no acknowledgement, and no state on either side.
This has consequences. APRS is fundamentally unreliable at the link layer — if your beacon is corrupted by interference, it is lost; there is no retry. APRS messaging adds an application-layer acknowledgement on top of UI frames (an ack packet sent in reply), but the underlying link still does not guarantee delivery. Operationally this is fine: APRS beacons every 60 seconds or so, and a lost beacon is not catastrophic.
The PID byte identifies which upper-layer protocol the information field carries. AX.25 defines several values:
APRS uses 0xF0. Every APRS packet has PID 0xF0, and decoders use this as a quick sanity check that they are looking at APRS rather than some other AX.25-encapsulated protocol.
Everything after the PID byte and before the FCS is the APRS payload. The first byte of the payload — the APRS data type identifier — tells the receiver what kind of APRS packet this is:
! or =: position without timestamp./ or @: position with timestamp.;: object report.): item report.:: message.>: status report.T: telemetry data._: positionless weather report.*: Peet Bros U-II weather station. (Mic-E position is ` in current use, or ' in its older form — a common thing to get wrong.)}: third-party traffic — a whole packet from another network wrapped inside this one. This is what a TX iGate uses when it injects APRS-IS traffic back onto RF; it is not added on the RF-to-Internet leg.The full table is in APRS Spec 1.2 chapter 5. Each identifier defines a different format for the bytes that follow. A standard position-with-timestamp packet, for example, has a fixed 19-byte position field, an APRS symbol selector, and optional comment text.
The Frame Check Sequence is a 16-bit CRC computed over the address, control, PID, and information fields using the CRC-16-CCITT polynomial (0x1021). The TNC computes and appends it on transmit and verifies it on receive; if the FCS does not match, the frame is silently dropped.
The FCS detects errors, it does not correct them. AX.25 has no forward error correction at the link layer. This is one of the practical limits of APRS — a noisy receive site sees a lot of CRC failures, all of which are lost packets. There are several proposed FEC overlays for ham packet radio (FX.25, IL2P) that wrap AX.25 frames with FEC redundancy and are transparent to standard AX.25 software; uptake is slowly growing.
For a single APRS position beacon on 2 m FM, the stack looks like this top-down:
Application: APRS — "!4011.50N/07412.50W>123/045 Mobile"
Link layer: AX.25 UI frame — dst=APRS-0, src=W1ABC-9, path=WIDE1-1,WIDE2-1
Physical: Bell 202 AFSK at 1200 baud, FM modulated on 144.390 MHz VHF
The physical layer details — Bell 202 audio frequency-shift keying, 1200 baud, 1200 Hz mark / 2200 Hz space tones — are technically separate from AX.25, but for VHF APRS they are always present. HF APRS swaps Bell 202 for PSK-31 or 300 baud AFSK; the AX.25 framing on top is identical.
For most operators, AX.25 is invisible: the radio's TNC handles framing, addressing, and CRC automatically, and the operator just sees APRS messages and positions. But understanding the layer helps in three concrete cases:
AX.25 is one of the oldest protocols still in active worldwide use — 40+ years old at the time of writing and showing no signs of going away. APRS is its most visible application, and understanding the underlying layer is a small investment with a long payoff.