How does a computer talk to a USB NFC reader?
Through PC/SC, the interface computers use for card readers: the program opens a context, lists readers, connects to what is on one of them and transmits APDUs. Windows exposes it as winscard, macOS as CryptoTokenKit. A tag with no card application answers through commands the reader implements.
Where the code sits
A program never speaks to the USB device. It speaks to the resource manager the operating system provides, which speaks to the reader, which speaks to the card or the tag. The PC/SC specifications define that chain part by part and are published in the open; part 3 is the one about the reader itself, and it is where a developer ends up when a tag behaves unlike a card.
What travels between the program and the card is an APDU, the unit the APDU page describes. A contactless reader carries it to a Type 4 tag or a card without changing anything; everything else in this page exists because the other tag types do not speak that language.
The same exchange, two systems
On Windows the calls belong to winscard: a context, the list of readers, then SCardConnect, which takes a share mode, shared, exclusive or direct, and the protocols the program accepts, T=0 or T=1. SCardTransmit sends the command with the matching protocol control structure and returns the answer.
On macOS and iOS the same exchange goes through CryptoTokenKit. TKSmartCardSlotManager lists the slots and requires the com.apple.security.smartcard entitlement; TKSmartCard opens a session, transmits and ends it, and exposes send(ins:p1:p2:data:le:), which builds the APDU for you, with cla, useExtendedLength and useCommandChaining beside it.
From iOS 26, the same framework can open a slot on the phone's own radio: createNFCSlot(message:completion:) creates an NFC smart card slot and presents a system interface, which puts a tag behind the API a reader would have used. Core NFC remains the framework for tags that are not cards, as the session page describes.
Tags with no card application
A Type 2 or Type 5 tag has no application to select and no APDU to answer. Part 3 of the specification handles them by making the reader do the work: a compliant reader must support Read Binary, Update Binary, Load Keys, General Authenticate, Verify for cards with a security code, and Get Data. The specification is explicit that these commands come from the industry rather than from ISO, and that reader vendors gather the details from the chip makers.
Get Data is the one every project uses: class FF, instruction CA, parameters 00 00, and an expected length of 00 for the whole identifier. The specification fixes what comes back: 4, 7 or 10 bytes for an ISO 14443 A tag, the 4 byte PUPI for a 14443 B card, 8 bytes for ISO 15693, in the order received during anticollision, padded rather than cast, and 6A 81 when the card has no identifier at all.
So a reader that reads a tag your code cannot is usually a reader that implements a command yours does not send, not a better radio.
What the reader says a tag is
A contactless tag has no answer to reset, and programs written for cards expect one. The reader therefore builds a synthetic one, whose fields the supplement to part 3 defines: the registered identifier of the PC/SC Workgroup, A0 00 00 03 06, a byte naming the standard, ISO 14443 A part 3, ISO 15693 part 1, FeliCa and so on, then two bytes naming the chip from a registry the workgroup maintains on request.
Read it before sending anything. It tells your code which family it is holding, and an unknown chip appears as no information given rather than as a failure, which is the difference between a program that adapts and one that reports a broken reader.
Frequently asked questions
Can I send an APDU to a tag that is not a card?
Not to the tag. You send the commands the reader implements for storage cards, in class FF, and the reader turns them into what the tag understands.
How do I read the identifier of a tag?
With Get Data, FF CA 00 00, and an expected length of 00. It returns 4, 7 or 10 bytes on ISO 14443 A, the 4 byte PUPI on 14443 B, and 8 bytes on ISO 15693.
Why does a reader report an ATR for a tag that has none?
Because programs expect one. The reader builds it from the PC/SC registry, naming the standard and the chip family, so code can tell what it is holding.
Is the API the same on macOS?
The calls differ, CryptoTokenKit rather than winscard, and it takes the smart card entitlement. The APDUs travelling over it are the same.