# UDS negative response codes explained for testers

What a UDS negative response (0x7F) tells you on the bench: the structure, the codes that matter during enumeration and SecurityAccess, and how to read them.

Source: https://auto-st.com/insights/uds-negative-response-codes-explained · Updated: 2026-10-07

A UDS negative response is the ECU telling you it will not do what you asked, and the reason why. It is always three bytes: `7F`, the service identifier (SID) of the request that was rejected, and a negative response code (NRC). Read correctly, those codes map the ECU for you, because an ECU that rejects a request precisely is also describing its own structure one answer at a time.

## The structure, byte by byte

Send a request and you get one of three outcomes. A positive response starts with the SID plus `0x40`: a request `22 F1 90` (ReadDataByIdentifier, DID 0xF190) answered with `62 F1 90 ...` is a success. A negative response looks like this:

```
7F 22 31
│  │  └─ NRC: requestOutOfRange
│  └──── SID that was rejected: 0x22 ReadDataByIdentifier
└─────── negative response service identifier
```

The third outcome is silence, which on CAN usually means the ISO-TP (ISO 15765-2) frame was never accepted, the arbitration ID is wrong, or the ECU is busy. Silence is not an NRC, and you should treat it differently from an explicit rejection.

## The codes that carry the most information

ISO 14229-1 Annex A defines the full table. During a scan, a handful of codes do most of the work:

| Code | ISO name | What it tells the tester |
| --- | --- | --- |
| `0x10` | generalReject | The ECU refuses without a reason. Some implementations answer this to everything they do not understand, which hides structure. |
| `0x11` | serviceNotSupported | The service does not exist on this ECU at all. |
| `0x12` | subFunctionNotSupported | The service exists, the sub-function does not. |
| `0x13` | incorrectMessageLengthOrInvalidFormat | Length or format wrong. Useful signal while fuzzing. |
| `0x22` | conditionsNotCorrect | The service exists but preconditions are unmet (wrong state, speed, voltage). |
| `0x31` | requestOutOfRange | The parameter, usually a DID or RID, is unknown or out of range. |
| `0x33` | securityAccessDenied | The service is gated behind SecurityAccess (0x27). |
| `0x35` | invalidKey | Wrong key sent to SecurityAccess. |
| `0x36` | exceedNumberOfAttempts | Too many wrong keys; the lockout has tripped. |
| `0x37` | requiredTimeDelayNotExpired | You must wait before trying again. |
| `0x7E` | subFunctionNotSupportedInActiveSession | Sub-function exists, not in this session. |
| `0x7F` | serviceNotSupportedInActiveSession | Service exists, not in this session. |
| `0x78` | requestCorrectlyReceived-ResponsePending | Not an error: wait for the real answer. |

## Telling "not here" from "not now"

The single most useful distinction during enumeration is between a service that does not exist and one that exists but is locked behind a session or SecurityAccess. The codes make it explicit.

- `0x11` serviceNotSupported: the SID is absent. Stop probing it.
- `0x7F` serviceNotSupportedInActiveSession: the SID is present, but you are in the wrong session. Re-send after `10 03` (extended) or, carefully, `10 02` (programming).
- `0x33` securityAccessDenied: the SID is present and reachable in this session, but needs an unlocked SecurityAccess level first.

So a RoutineControl probe that returns `7F 31 33` is not a dead end. It is confirmation that `0x31` exists here and that something worth protecting sits behind `0x27`. That one byte redirects the whole test toward the seed/key gate.

## Response pending, and the ECUs that abuse it

`0x78` is the ECU asking for more time. The correct tester behaviour is to reset its P2* timer and keep waiting for either a positive response or a different NRC. Byte-for-byte:

```
tx 31 01 FF 00    RoutineControl startRoutine 0xFF00
rx 7F 31 78       response pending, keep waiting
rx 7F 31 78       still working
rx 71 01 FF 00    final positive response
```

Two patterns are findings in their own right. An ECU that sends `0x78` and never follows up hangs any tester that honours the protocol. An ECU that sends an unbounded stream of `0x78` can be used to keep a session, or even a programming session, alive far longer than intended.

## Using NRCs during SecurityAccess

The SecurityAccess codes form a small state machine you can watch. A healthy gate moves through them:

```
tx 27 01          requestSeed level 1
rx 67 01 A1 B2    seed returned
tx 27 02 00 00    sendKey (wrong)
rx 7F 27 35       invalidKey
...repeat...
rx 7F 27 36       exceedNumberOfAttempts (lockout tripped)
tx 27 02 00 00    sendKey
rx 7F 27 37       requiredTimeDelayNotExpired
```

An ECU that answers `0x35` forever, never escalating to `0x36` and `0x37`, has no attempt counter and no delay. That is a weak gate, and the NRC trail is the evidence.

## Where the codes mislead

Not every ECU uses the table cleanly. Some answer `0x10` generalReject to anything unfamiliar, erasing the difference between "not supported" and "not in this session". Some return `0x22` conditionsNotCorrect where `0x31` would be correct. A single response is therefore weak evidence. The reliable method is statistical: probe each SID across every discovered session and compare the response codes, so a mislabelled ECU still reveals its real map. AutoST's enumeration does exactly this, classifying every service from its response pattern rather than trusting one answer, and our free [UDS NRC decoder](/tools/uds-nrc-decoder) resolves a single code when you just need the name.

## FAQ

**What is the structure of a UDS negative response?**

Three bytes: 0x7F, the service identifier of the rejected request, and the negative response code. A tester that sends 22 F1 90 and receives 7F 22 31 learns the service exists but the data identifier is out of range.

**Is response pending (0x78) a failure?**

No. requestCorrectlyReceived-ResponsePending tells the tester the ECU needs more time, so the tester extends its P2* timeout and waits. An ECU that sends 0x78 and then never answers is itself a finding, because it hangs the tester.

**Why do ECUs answer 0x31 for an unknown DID instead of 0x7F?**

requestOutOfRange (0x31) is the correct answer to a data identifier the ECU does not support. serviceNotSupportedInActiveSession (0x7F) means the whole service is unavailable in the current session. Confusing the two is common and makes enumeration harder.

## Sources

- [ISO 14229-1:2020 Road vehicles, Unified diagnostic services (UDS), Part 1: Application layer, Annex A](https://www.iso.org/standard/72439.html)
- [ISO 15765-2:2016 Road vehicles, Diagnostic communication over CAN (DoCAN), Part 2: Transport protocol and network layer services](https://www.iso.org/standard/84211.html)

## Related

- [NRC (Negative Response Code)](https://auto-st.com/glossary/nrc)
- [UDS (Unified Diagnostic Services)](https://auto-st.com/glossary/uds)
- [UDS Negative Response Code Decoder](https://auto-st.com/tools/uds-nrc-decoder)
- [Know every door into the ECU.](https://auto-st.com/uds-enumeration)
- [Does your seed/key actually hold?](https://auto-st.com/security-access-testing)
- [UDS enumeration explained: how to map an ECU safely](https://auto-st.com/insights/uds-enumeration-explained)

---
AutoST by Zyberum. Canonical page: https://auto-st.com/insights/uds-negative-response-codes-explained
