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

# Importer un lot et traiter ses résultats

> Envoyer un lot, suivre le job, paginer ses résultats et choisir la bonne reprise.

export const CopyDocumentation = ({topic = "marko-documentation", label, chooseFamily = false}) => {
  const [state, setState] = useState("idle");
  const [message, setMessage] = useState("");
  const [selected, setSelected] = useState(topic);
  const [topics, setTopics] = useState([]);
  const base = "https://raw.githubusercontent.com/mathieuworoniecki/marko-developer-docs/main/downloads/";
  useEffect(() => {
    if (!chooseFamily) return;
    let active = true;
    fetch(base + "index.json").then(async response => {
      if (!response.ok) throw new Error("Index indisponible");
      const index = await response.json();
      if (active && index.format === "marko-documentation-index/v1") setTopics(index.topics);
    }).catch(() => {
      if (active) setMessage("La liste des périmètres est indisponible. La copie complète reste accessible.");
    });
    return () => {
      active = false;
    };
  }, [chooseFamily]);
  const id = chooseFamily ? selected : topic;
  const downloadUrl = base + encodeURIComponent(id) + ".json";
  const copy = async () => {
    setState("loading");
    setMessage("");
    try {
      const document = fetch(downloadUrl).then(async response => {
        if (!response.ok) throw new Error("Documentation indisponible");
        const payload = await response.json();
        const text = payload.markdown;
        if (payload.format !== "marko-documentation/v1" || typeof text !== "string" || !text.startsWith("# MARKO — ")) {
          throw new Error("Format de documentation inattendu");
        }
        return text;
      });
      if (navigator.clipboard.write && typeof ClipboardItem !== "undefined") {
        await navigator.clipboard.write([new ClipboardItem({
          "text/plain": document.then(text => new Blob([text], {
            type: "text/plain"
          }))
        })]);
      } else {
        await navigator.clipboard.writeText(await document);
      }
      setState("copied");
      setMessage("Documentation copiée. Vous pouvez la coller dans votre assistant.");
    } catch {
      setState("error");
      setMessage("La copie n'a pas abouti. Utilisez le lien de téléchargement ci-dessous.");
    }
  };
  return <div className="not-prose my-4">
      {chooseFamily && <label className="mb-3 block text-sm">
          <span className="mb-1 block">Documentation à copier</span>
          <select aria-label="Documentation à copier" value={selected} disabled={state === "loading"} onChange={event => {
    setSelected(event.target.value);
    setState("idle");
    setMessage("");
  }} className="w-full rounded-lg border px-3 py-2" style={{
    color: "inherit",
    backgroundColor: "transparent",
    borderColor: "#087E8B"
  }}>
            {!topics.length && <option value="marko-documentation">Toute la documentation</option>}
            {topics.map(item => <option key={item.id} value={item.id}>{item.kind === "tutorial" ? "Tutoriel : " : item.kind === "family" ? "API : " : ""}{item.title}</option>)}
          </select>
        </label>}
      <button type="button" onClick={copy} disabled={state === "loading"} className="rounded-lg border px-4 py-2 text-sm font-semibold disabled:opacity-60 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2" style={{
    backgroundColor: "#76E7F4",
    color: "#092E33",
    borderColor: "#087E8B"
  }}>
        {state === "loading" ? "Copie en cours…" : state === "copied" ? "Documentation copiée ✓" : label || (chooseFamily ? "Copier la sélection pour une IA" : "Copier toute la documentation pour une IA")}
      </button>
      <p className="mt-2 text-sm" role="status" aria-live="polite">{message}</p>
      <a className="text-sm underline" href={downloadUrl}>{chooseFamily || topic !== "marko-documentation" ? "Télécharger cette documentation" : "Télécharger la documentation complète"}</a>
    </div>;
};

<CopyDocumentation topic="batch-import" label="Copier ce tutoriel et ses routes API" />

Ce parcours utilise `operations:write` pour l'envoi et les actions sur le job, et `operations:read` pour son suivi. Obtenir un bearer avant les appels.

## 1. Préparer le lot

Créer un fichier `lot.json` avec les références de votre système. Ce petit exemple ne choisit aucune taxonomie propre à votre entité :

```json theme={null}
{
  "operations": [
    {"external_id":"crm-lot-001", "name":"Première opération"},
    {"external_id":"crm-lot-002", "name":"Seconde opération"}
  ]
}
```

Un lot accepte jusqu'à 500 opérations, 10 000 notes et 20 Mio d'enveloppe JSON normalisée. Les identifiants externes doivent rester stables entre les lots et les reprises.

## 2. Envoyer et conserver le job

```bash theme={null}
curl --fail-with-body --request POST \
  "$MARKO_API_BASE_URL/operations/batch" \
  --header "Authorization: Bearer $MARKO_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: crm-lot-20261001-01" \
  --data-binary @lot.json
```

La réponse `202` signifie que le lot a été accepté. Enregistrer `job_id` durablement avant d'afficher l'import comme démarré. Garder aussi la clé d'idempotence et le fichier exact envoyé.

## 3. Suivre l'état

Avec le `job_id` reçu dans `MARKO_JOB_ID` :

```bash theme={null}
curl --fail-with-body \
  "$MARKO_API_BASE_URL/import-jobs/$MARKO_JOB_ID" \
  --header "Authorization: Bearer $MARKO_ACCESS_TOKEN"
```

Espacer les lectures, par exemple de deux secondes, avec une durée d'attente maximale dans votre application. Si cette durée est dépassée, conserver le job pour le reprendre plus tard; ne pas renvoyer le lot comme s'il avait disparu.

Les états terminaux du job sont `completed`, `partial`, `failed` et `cancelled`. À ce stade, lire `completed_items`, `noop_items`, `failed_items` et `conflict_items`.

## 4. Lire tous les résultats

```bash theme={null}
curl --fail-with-body \
  "$MARKO_API_BASE_URL/import-jobs/$MARKO_JOB_ID/items?limit=100&offset=0" \
  --header "Authorization: Bearer $MARKO_ACCESS_TOKEN"
```

Continuer la pagination jusqu'à la fin de la liste. Les éléments exposent leur `external_id`, `item_kind`, `status` et, si disponible, `target_id`, `error_code`, `error_message` et `conflict_payload`.

| Résultat d'un élément | Suite à donner |
| - | - |
| `completed` | Conserver l'identifiant cible et relire la ressource utile. |
| `noop` | Conserver l'identifiant cible : aucun changement n'était nécessaire. |
| `failed` | Corriger ou diagnostiquer l'échec avant de choisir une reprise. |
| `conflict` | Comparer les données source à la cible; ne pas écraser une correction humaine sans décision. |

## 5. Reprendre ou annuler

Pour un job `failed` ou `partial`, `POST /import-jobs/{job_id}/retry` remet les éléments éligibles en file. Transmettre une clé d'idempotence propre à cette action. Cette route ne remplace pas le contenu du lot et ne réécrit pas ses éléments déjà terminés.

Pour un contenu source corrigé, préparer une nouvelle révision avec ses références stables et une nouvelle clé d'envoi. Vérifier d'abord les résultats du lot précédent.

`POST /import-jobs/{job_id}/cancel` demande l'arrêt du traitement restant. Les mutations déjà enregistrées sont conservées. Une annulation ne restaure pas les anciennes valeurs.

Pour les lots avec notes historiques, consulter [Imports et notes](/imports-and-notes) avant d'ajouter les dates et les versions source.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.