
# Upsert et suppression

Les deux opérations d'écriture qui ne sont ni une création ni une mise à jour simple — fusionner sur une clé et retirer une ligne — ainsi que le paramètre `format` qui décide de la forme des valeurs renvoyées en lecture.

## Upsert d'un enregistrement

L'upsert crée des enregistrements ou met à jour ceux qui existent déjà, appariés sur un ou plusieurs champs uniques, en un seul appel. C'est le point de terminaison de la synchronisation idempotente depuis un système externe : rejouer la même charge utile converge au lieu de dupliquer.

Il existe **un seul point de terminaison d'upsert, et il est intrinsèquement multi-enregistrements** — le corps porte toujours un tableau `records`, même pour une seule ligne.

```json
POST /api/tables/contacts/records/upsert
{
  "records": [
    { "fields": { "email": "john@example.com", "name": "John Doe", "status": "active" } }
  ],
  "fieldsToMergeOn": ["email"],
  "returnRecords": true
}
```

| Propriété         | Description                                                                                   |
| ----------------- | --------------------------------------------------------------------------------------------- |
| `records`         | **Obligatoire.** Tableau d'enveloppes `{ fields }`, de 1 à 100 entrées.                       |
| `fieldsToMergeOn` | **Obligatoire.** Noms de champs formant la clé de fusion, au moins un. Alias : `matchFields`. |
| `returnRecords`   | Renvoyer les lignes affectées dans la réponse. Par défaut `false`.                            |

La réponse rapporte des compteurs, pas un verdict par ligne :

```json
{
  "records": [],
  "created": 1,
  "updated": 0
}
```

`records` n'est peuplé que lorsque `returnRecords` vaut `true`. Un corps composé d'un seul `{ "fields": … }` sans tableau `records` échoue à la validation avec `400 VALIDATION_ERROR`.

## Supprimer un enregistrement

Par défaut, `DELETE` est une **suppression douce** : il renseigne `deletedAt`/`deletedBy` et laisse la ligne récupérable.

```
DELETE /api/tables/contacts/records/42
DELETE /api/tables/contacts/records/42?permanent=true
DELETE /api/tables/contacts/records/42?purge=true
```

| Mode               | Comportement                                                              | Succès            |
| ------------------ | ------------------------------------------------------------------------- | ----------------- |
| Par défaut (douce) | Met la ligne à la corbeille ; récupérable par restauration.               | `204`, sans corps |
| `?permanent=true`  | Supprime définitivement la ligne. **Administrateur uniquement.**          | `200`             |
| `?purge=true`      | Supprime les fichiers de stockage attachés, puis la ligne définitivement. | `204`             |

`?permanent=true` est conditionné au fait que l'appelant soit administrateur, et un non-administrateur reçoit **404** plutôt que 403 — la même règle anti-énumération que pour tout autre refus sur le chemin des enregistrements. `?purge=true` n'est pas réservé aux administrateurs : il ne demande que la permission de suppression normale, et c'est le mode à employer lorsque la ligne possède des fichiers téléversés qu'il ne faut pas laisser orphelins.

:::callout
**Chaque refus d'autorisation répond ici 404.** Enregistrement absent, enregistrement invisible et « visible mais vous ne pouvez pas le supprimer » sont indiscernables par conception. `403` n'est pas renvoyé sur ce point de terminaison.
:::

Voir [Suppression douce et restauration](/fr/docs/records-soft-delete) pour la corbeille, la restauration et le comportement en cascade sur les enregistrements liés.

## Mise en forme d'affichage vs brute

Le paramètre de requête `format` contrôle la sérialisation des valeurs de champ sur les points de terminaison de lecture.

| `format`  | Comportement                                                                             |
| --------- | ---------------------------------------------------------------------------------------- |
| _(omis)_  | Valeurs stockées, inchangées — le défaut, et ce que veulent les clients programmatiques. |
| `display` | Les champs mis en forme deviennent un objet portant la valeur brute et la valeur rendue. |

```
GET /api/tables/orders/records?format=display&timezone=Europe/Paris
```

Sous `format=display`, un champ mis en forme est **enveloppé** plutôt que remplacé : la valeur devient `{ value, displayValue, … }`, si bien que la forme brute reste disponible pour les calculs. Seuls ces types de champs sont mis en forme ; tout le reste est renvoyé inchangé.

| Type de champ                                | Mise en forme d'affichage                                              |
| -------------------------------------------- | ---------------------------------------------------------------------- |
| `currency`                                   | Symbole, décimales et séparateurs selon la locale.                     |
| `date` / `datetime` / `time`                 | Format configuré ; `?timezone=` (IANA) surcharge le fuseau de rendu.   |
| `duration`                                   | `h:mm`, `h:mm:ss` ou heures décimales selon la configuration du champ. |
| `single-attachment` / `multiple-attachments` | Contraintes de téléversement déclarées, lorsque le champ en déclare.   |

:::callout
**`?format=raw` est rejeté sur la lecture d'un enregistrement unique.** `GET /api/tables/:t/records/:id` n'accepte que `display` ; toute autre valeur, `raw` compris, renvoie `400 VALIDATION_ERROR`. Omettez le paramètre pour obtenir les valeurs brutes. Sur le point de terminaison de liste, `raw` est accepté et sans effet.
:::

L'aller-retour n'est pas pris en charge : lisez avec `display`, et réécrivez toujours la valeur brute, jamais la valeur rendue. Les URL signées des pièces jointes sont ajoutées par une étape d'enrichissement distincte et apparaissent quel que soit `format`.

## Pages associées

- [Créer, lire et mettre à jour](/fr/docs/records-crud) — écritures unitaires et verrouillage optimiste.
- [Suppression douce et restauration](/fr/docs/records-soft-delete) — corbeille, restauration et cascades.
- [Opérations par lot](/fr/docs/records-batch) — création, mise à jour, suppression et restauration en masse.
- [Présentation des enregistrements](/fr/docs/records-overview) — enveloppe, paternité, règles transversales.
- [Cycle de vie des fichiers](/fr/docs/file-lifecycle) — ce que `?purge=true` nettoie.
