> ## Documentation Index
> Fetch the complete documentation index at: https://react-native-nfc-kit.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Sending APDUs (ISO 7816)

> DESFire, JavaCard applets and transit cards, and the AID declaration iOS will not deliver a tag without.

For DESFire, JavaCard applets, transit cards, EMV-adjacent work — anything where
you select an application by AID and exchange APDUs.

```ts theme={null}
import { nfc } from 'react-native-nfc-kit';
import { fromHex } from 'react-native-nfc-kit/ndef';
import { selectByName, sendApdu, describeStatusWord } from 'react-native-nfc-kit/protocols';

await nfc.withTag({ tech: ['isoDep'] }, async (tag) => {
  if (!tag.is('isoDep')) throw new Error('Not an ISO-DEP tag');

  const transport = (apdu: Uint8Array) => tag.transceive(apdu);

  const selected = await sendApdu(transport, selectByName(fromHex('A0000002471001')));
  if (!selected.ok) throw new Error(describeStatusWord(selected.status));

  const response = await sendApdu(transport, { cla: 0x00, ins: 0xb0, p1: 0x00, p2: 0x00, le: 256 });
  if (!response.ok) throw new Error(response.statusHex);
});
```

The builders return a command; `sendApdu` sends one. The full API, including
chaining and the `61xx`/`6Cxx` follow-ups it handles for you, is in
[Tag protocols](/protocols).

## The iOS rule that catches everyone

<Warning>
  **iOS does not deliver an ISO 7816 tag to your app unless the AID you are selecting
  is declared in Info.plist.** Not an error, not an empty result — the tag simply
  never arrives, exactly as if it were not there.

  This is the single most common reason a card that works on Android appears
  unreadable on iOS.
</Warning>

Android has no such restriction, so the same code works there with no declaration
at all. That asymmetry is why this is easy to ship and hard to diagnose.

## Expo

```json app.json theme={null}
{
  "expo": {
    "plugins": [
      [
        "react-native-nfc-kit",
        {
          "readerUsageDescription": "Hold your card near the top of the phone",
          "ios": { "selectIdentifiers": ["A0000002471001"] }
        }
      ]
    ]
  }
}
```

Write the AID as plain uppercase hex: no `0x`, no spaces, no separators. The
plugin rejects anything else, and anything outside the 5-to-16-byte range ISO
7816-4 allows, because Apple rejects those when the profile is built — a long way
from where the mistake was made.

### A trap worth knowing about

<Note>
  Declaring `D2760000850101`, the NDEF application AID, changes how iOS presents cards
  that support it: a DESFire card then arrives as an ISO 7816 tag rather than a MIFARE
  one, so `tag.is('mifareUltralight')` and the MIFARE-specific commands stop matching.

  That is a legitimate thing to want and a baffling thing to trip over, so the plugin
  warns when it sees it.
</Note>

## Bare React Native

### `ios/<App>/<App>.entitlements`

```xml theme={null}
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
  <string>TAG</string>
</array>
```

### `ios/<App>/Info.plist`

```xml theme={null}
<key>NFCReaderUsageDescription</key>
<string>Hold your device near an NFC tag to read it.</string>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
  <string>A0000002471001</string>
</array>
```

Add one `<string>` per AID your app selects. A card whose AID is not listed is
never handed over.

### `android/app/src/main/AndroidManifest.xml`

```xml theme={null}
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="false" />
<activity android:name=".MainActivity" android:launchMode="singleTop">
  <!-- your existing intent filters stay here -->
</activity>
```

Nothing ISO 7816-specific: Android decides nothing at the radio.

## Timeouts

Crypto takes longer than the platform's default patience. DESFire authentication
and GlobalPlatform key derivation both routinely exceed Android's 125 ms presence
check, and the OS then declares the tag lost part-way through an exchange that was
going fine:

```ts theme={null}
await nfc.withTag({ tech: ['isoDep'], android: { presenceCheckDelayMs: 500 } }, work);
```

<Info>
  On iOS the equivalent limits are the session's 60 seconds and roughly 20 seconds of connected tag.
  Neither can be extended, so a long personalisation sequence has to be split across sessions.
</Info>

## Next

<Columns cols={2}>
  <Card title="Tag protocols" icon="microchip" href="/protocols">
    Command builders, chaining, and status words.
  </Card>

  <Card title="Card emulation" icon="credit-card" href="/setup/hce">
    The other side: answering a terminal's SELECT.
  </Card>
</Columns>
