> ## Documentation Index
> Fetch the complete documentation index at: https://docs.retriever.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Enrichir une liste de votre CRM

> Importer des entreprises de votre CRM dans Retriever, trouver leur CEO et LinkedIn, puis exporter le résultat en CSV.

Les exemples Node utilisent [le client Node commun](/recipes/client) (`retriever.mjs`).

**Problème** : vous avez des entreprises dans votre CRM. Vous voulez leur CEO et son LinkedIn.

**Solution** : importez la liste dans une table, lancez un run qui la cible avec une sortie au format imposé, exportez en CSV.

```js theme={null}
import { writeFile } from "node:fs/promises";
import { call, download, waitRun } from "./retriever.mjs";

const batchId = "crm-enrich-2026-09-15"; // identifiant stable, choisi par vous
const crmAccounts = [
  { name: "Acme", domain: "acme.example" },
  { name: "Beta", domain: "beta.example" },
];

// 1. Workspace et import (à ne faire qu'une fois par lot : stockez les ids)
const ws = await call("/v1/workspaces", { method: "POST", body: { name: `Enrichissement ${batchId}` } });
const table = await call(`/v1/workspaces/${ws.id}/tables`, {
  method: "POST",
  body: {
    name: "Comptes CRM",
    columns: [
      { key: "name", name: "Entreprise" },
      { key: "domain", name: "Domaine" },
    ],
    // n'envoyer que les champs déclarés en colonnes
    rows: crmAccounts.map(({ name, domain }) => ({ name, domain })),
  },
});

// 2. Run ciblé. La clé d'idempotence dépend du lot, pas d'un id généré à chaque exécution.
const run = await call("/v1/runs", {
  method: "POST",
  headers: { "Idempotency-Key": `${batchId}:run` },
  body: {
    workspace_id: ws.id,
    prompt: `Pour chaque entreprise de la table ${table.id}, trouve le nom du CEO et l'URL de son profil LinkedIn. Publie une ligne par entreprise.`,
    output: {
      type: "tables",
      tables: [{
        key: "contacts",
        row_schema: {
          type: "object",
          required: ["domain", "ceo_name", "ceo_linkedin_url"],
          properties: {
            domain: { type: "string" },
            ceo_name: { type: ["string", "null"] },
            ceo_linkedin_url: { type: ["string", "null"] },
          },
        },
      }],
    },
  },
});

const done = await waitRun(run.id);
if (done.status !== "completed") throw new Error(done.error?.message ?? done.status);

// 3. Export CSV de la table figée
const exp = await call(`/v1/runs/${run.id}/exports`, { method: "POST", body: { table_key: "contacts", format: "csv" } });
await writeFile("contacts.csv", await download(exp.links.self));
```

**À savoir**

* Chaque clé de ligne doit être une colonne déclarée, sinon `400` et rien n'est importé.
* Si le script plante après l'import, ne recréez pas le workspace : stockez `ws.id` et `table.id` avec `batchId`, et reprenez à l'étape 2.
* Une table ne reçoit pas de lignes après sa création. Pour un nouveau lot, créez une nouvelle table.
* Pour de gros volumes, découpez en lots de quelques centaines de lignes. La durée dépend du nombre de lignes et des recherches nécessaires : suivez `progress.phase`.
* Réimportez les résultats dans le CRM en matchant sur `domain`, jamais sur l'ordre des lignes.
* Les champs `["string", "null"]` permettent à l'agent de publier une ligne même quand il ne trouve pas l'information.
