
# CRUD et upsert des enregistrements

Cette page couvre les opérations de cycle de vie d'un enregistrement unique : créer, lire, mettre à jour, supprimer et upsert. Pour lister de nombreux enregistrements, voir [Filtrage, tri et pagination](records-filtering-sorting) ; pour les écritures en masse, voir [Opérations en lot](records-batch). Tous les corps d'écriture utilisent l'enveloppe canonique `{ "fields": { ... } }` décrite dans la [vue d'ensemble des enregistrements](records-overview).

## Créer un enregistrement

```json
POST /api/tables/contacts/records
{
  "fields": {
    "email": "john@example.com",
    "first_name": "John",
    "last_name": "Doe"
  }
}
```

| Statut             | Signification                                                                                |
| ------------------ | -------------------------------------------------------------------------------------------- |
| `201 Created`      | Enregistrement créé ; le corps est l'enregistrement stocké avec `id` et auteur               |
| `400 Bad Request`  | Champ requis manquant, valeur de type de champ invalide ou violation de contrainte d'unicité |
| `401 Unauthorized` | Aucune session active                                                                        |
| `404 Not Found`    | La table n'existe pas (ou l'appelant n'y a pas accès)                                        |

La réponse porte l'`id` généré, l'écho des `fields` et les [métadonnées d'auteur](records-overview) (`createdBy`, `createdAt`, `updatedAt`).

## Lire un enregistrement

```
GET /api/tables/contacts/records/42
```

| Statut             | Signification                                                              |
| ------------------ | -------------------------------------------------------------------------- |
| `200 OK`           | Enregistrement renvoyé                                                     |
| `401 Unauthorized` | Aucune session active                                                      |
| `404 Not Found`    | Enregistrement absent **ou** non visible par l'appelant (anti-énumération) |

Les champs pour lesquels l'appelant n'a pas la permission de lecture sont omis de la réponse — le même enregistrement peut renvoyer un ensemble de champs différent selon le rôle de l'appelant et les [permissions au niveau des champs](table-permissions).

## Mettre à jour un enregistrement

`PATCH` effectue une mise à jour partielle : seuls les champs présents dans le corps sont écrits ; les champs omis sont laissés intacts.

```json
PATCH /api/tables/contacts/records/42
{
  "fields": {
    "status": "active"
  }
}
```

| Statut             | Signification                                                     |
| ------------------ | ----------------------------------------------------------------- |
| `200 OK`           | Enregistrement mis à jour ; `updatedBy`/`updatedAt` réestampillés |
| `400 Bad Request`  | Valeur de type de champ invalide ou violation de contrainte       |
| `401 Unauthorized` | Aucune session active                                             |
| `404 Not Found`    | Enregistrement absent ou non visible                              |
| `409 Conflict`     | Échec du verrouillage optimiste (écriture obsolète)               |

### Verrouillage optimiste

Incluez un jeton `updatedAt` de premier niveau à côté de `fields` pour vous prémunir contre les mises à jour perdues. Le serveur le compare à la colonne `updated_at` stockée et rejette une écriture divergente avec `409 Conflict`.

```json
PATCH /api/tables/contacts/records/42
{
  "fields": { "status": "active" },
  "updatedAt": "2025-01-15T10:30:00Z"
}
```

:::callout
**Les tables y adhèrent via le champ système `updated-at`.** Ajoutez un champ de `type: 'updated-at'` afin que la colonne soit automatiquement incrémentée à chaque écriture. Un champ `datetime` ordinaire est modifiable par l'utilisateur et **n'est pas** géré automatiquement, il ne peut donc pas étayer le contrat de verrouillage optimiste.
:::

## Supprimer un enregistrement

Par défaut, `DELETE` est une **suppression réversible** : 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
```

| Statut                      | Signification                                                                          |
| --------------------------- | -------------------------------------------------------------------------------------- |
| `200 OK` / `204 No Content` | Enregistrement supprimé de façon réversible (ou définitivement avec `?permanent=true`) |
| `401 Unauthorized`          | Aucune session active                                                                  |
| `403`/`404`                 | L'appelant n'a pas la permission de suppression, ou l'enregistrement n'est pas visible |

La suppression définitive (`?permanent=true`) requiert la permission `permanentDelete` et est irréversible. Voir [Suppression réversible et restauration](records-soft-delete) pour la corbeille, la restauration et le comportement en cascade sur les enregistrements associés.

## Upsert d'un enregistrement

L'upsert crée un enregistrement ou met à jour un enregistrement existant correspondant à un ou plusieurs champs uniques, en un seul appel. Utilisez `matchFields` (alias : `fieldsToMergeOn`) pour nommer la ou les clés de fusion.

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

La réponse indique si la ligne a été `created` (créée) ou `updated` (mise à jour) :

```json
{
  "id": 123,
  "operation": "created",
  "record": {
    "id": 123,
    "email": "john@example.com",
    "name": "John Doe",
    "status": "active"
  }
}
```

Lorsqu'une ligne existante correspond aux `matchFields`, `operation` vaut `"updated"` à la place et la ligne correspondante est mise à jour. L'upsert est idéal pour la synchronisation idempotente depuis des systèmes externes — voir [Opérations en lot](records-batch) pour la variante `upsert` multi-enregistrements.

## Mise en forme d'affichage vs brute

Le paramètre de requête `format` contrôle la manière dont les valeurs des champs sont sérialisées sur les points de terminaison de lecture.

| `format`       | Comportement                                                                                                |
| -------------- | ----------------------------------------------------------------------------------------------------------- |
| `raw` (défaut) | Valeurs stockées, inchangées — préféré pour les clients programmatiques                                     |
| `display`      | Valeurs lisibles par un humain : devises, dates, durées et noms d'affichage des pièces jointes mis en forme |

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

La mise en forme est pilotée par type de champ :

| Type de champ                         | Mise en forme d'affichage                                                             |
| ------------------------------------- | ------------------------------------------------------------------------------------- |
| `currency`                            | Symbole, nombre de décimales et séparateurs selon la locale                           |
| `datetime`                            | Format date/heure configuré ; `?timezone=` (IANA) remplace le fuseau horaire de rendu |
| `duration`                            | `h:mm`, `h:mm:ss` ou heures décimales selon la configuration du champ                 |
| `attachment` / `multiple-attachments` | URL plus métadonnées et noms d'affichage                                              |
| `json`                                | Sérialisé selon la configuration du champ et le paramètre `format`                    |

:::callout
**Le brut est la source de vérité.** `format=display` est une commodité de présentation superposée à la valeur stockée. L'aller-retour (lecture avec `display`, réécriture) n'est pas garanti — écrivez toujours la valeur brute. Les champs numériques et de date conservent leur forme brute disponible pour les calculs.
:::

## Pages associées

- [Vue d'ensemble des enregistrements](records-overview) — enveloppe, auteur, règles transversales
- [Filtrage, tri et pagination](records-filtering-sorting) — grammaire des requêtes de liste
- [Opérations en lot](records-batch) — création/mise à jour/suppression/upsert en masse
- [Suppression réversible et restauration](records-soft-delete) — corbeille et récupération
- [Permissions de table](table-permissions) — RBAC et accès au niveau des champs
