## TL;DR

The external id looks right but matches nothing because of invisible differences: whitespace, case, or leading zeros. Normalize both sides identically (trim, consistent case, strip or pad zeros per the key's format) before matching. Keys must be compared canonically, not literally.

## Error

```text
Key error: upsert with external id " ACME-001 " matched zero records; the stored key is "ACME-001".
```

## Steps

1. Compare the sent key and the stored key character by character (length, whitespace, case). Expected: you spot the invisible difference.
2. Define the canonical form: trim whitespace, fix case per the key format, normalize leading zeros. Expected: one true form.
3. Apply the canonicalization in the agent before every upsert. Expected: keys match.
4. Clean the stored keys too, or at least the ones that fail. Expected: both sides canonical.
5. Add a key-shape check that warns on suspicious keys (spaces, mixed case) before the API call. Expected: early warning.

## When to use

- Upserts match zero rows on keys that look correct.
- An agent's keys come from another system with different formatting.
- After data imports, matching degrades.

## When not to use

- DUPLICATE_VALUE (key collision, different fix).
- Truly missing records (then insert is correct).

## Tool compatibility

- Salesforce upsert by external id.
- Key normalization in the agent.

## Variant phrasings

### external id matched nothing

Normalize whitespace, case, and zeros.

### key error on upsert

Compare canonically, not literally.

## Why it happens

Keys cross system boundaries, and each system formats them slightly differently. Literal comparison treats " ACME-001" and "ACME-001" as different keys, and the upsert inserts a duplicate instead of updating.

## Edge cases

- Leading zeros are significant in some key formats and noise in others; know your format.
- Case-insensitive unique fields need case normalization on the agent side too.
- Do not "fix" keys by changing their meaning; canonicalize format, not content.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_Bn25kco-7o--7yB9F58Ikw
