
# Replace the attribute mappings of a connection

`PUT /v1/connections/{connection_id}/attribute-mappings` · permission `connections:write`

Replaces the full attribute mapping list of an enterprise connection. The order of the rules does not matter; the response is ordered by target. `If-Match` is required: without it the request fails with `428`; if the list changed since you read it (or the value does not match) the request fails with `412` and nothing is written. Sending the same list writes nothing. The change applies from the next sign-in and does not invalidate the last passed connection test. The write is audited as `connection.attribute_mappings_replaced` (targets added, removed and changed); attribute values from sign-ins are never written to the audit log.

## Request

| Field | Type | Required | Description |
|---|---|---|---|
| `connection_id` | path | yes | Enterprise connection ID (`conn_…`). |
| `If-Match` | header | yes | The `ETag` from the last read. Missing: `428`; stale: `412`. |
| `attribute_mappings` | array | yes | The full new list, at most 28 rules. |
| `attribute_mappings[].target` | string | yes | `email` (required in every list), `given_name`, `family_name`, `name`, `locale`, `zoneinfo`, `phone`, `groups` or `user_metadata.<key>` (key `[a-z0-9_]{1,40}`; registered OIDC claim names and the `idp_` prefix are not allowed; at most 20 keys). One rule per target. `app_metadata` cannot be written by a mapping. |
| `attribute_mappings[].source_name` | string | yes | 1–512 bytes, no control or bidirectional characters, no leading or trailing space. |
| `attribute_mappings[].source_name_format` | string \| null | no | SAML `NameFormat` (absolute URI). One source name can be read with one format only. |
| `attribute_mappings[].overwrite` | string | yes | `always`, `if_empty` or `never`. |
| `attribute_mappings[].profile` | string | no | Preset name; `generic` when omitted. |

## Response

`200 OK` with the stored list (see [Get the attribute mappings](get.md)) and the new `ETag`.

## Errors

| HTTP | code | When |
|---|---|---|
| 404 | `not_found` | The connection does not exist in this environment or is not an enterprise connection. |
| 409 | `conflict` | The connection's protocol has no attribute mappings (LDAP). |
| 412 | `precondition_failed` | `If-Match` does not match the current version, or the list changed concurrently. |
| 422 | `validation_failed` | A rule breaks the rules above. Each broken rule is one `errors[]` item pointing at `/attribute_mappings/<index>/<field>`, or at `/attribute_mappings` for list rules (`email_required`, `too_many_metadata_keys`). |
| 428 | `precondition_required` | The `If-Match` header is missing. |
| 400 | `unknown_field` | The body has an unknown field. |
| 401 | `unauthenticated` | The credential is missing, invalid or revoked. |
| 403 | `insufficient_permissions` | The credential lacks the required permission. |
| 429 | `rate_limited` | The rate limit was exceeded; retry after the `Retry-After` period. |
| 503 | `service_unavailable` | A dependency is temporarily unavailable; retry after the `Retry-After` period. |

## Example

```bash
curl -X PUT "https://api.eu.keenauth.com/v1/connections/conn_01j9xp6q0r1s5t7v9w1x3y5z7b/attribute-mappings" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H 'If-Match: "6f1c…"' \
  -d '{"attribute_mappings":[{"target":"email","source_name":"email","overwrite":"always"},{"target":"user_metadata.department","source_name":"department","overwrite":"if_empty"}]}'
```

```bash
keenauth connections attribute-mappings put conn_01j9xp6q0r1s5t7v9w1x3y5z7b --data @mappings.json --if-match '"6f1c…"'
```
