> ## 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.

# Envoyer un document et suivre son extraction

> Uploader un fichier dans une opération existante, vérifier sa fiche et suivre le job IA.

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="document-extraction" label="Copier ce tutoriel et ses routes API" />

Il faut une opération déjà accessible à votre clé, les scopes `documents:write`, `documents:read`, `ai_extraction:write` et `ai_extraction:read`, et les autorisations documentaires correspondantes. L'IA doit être activée pour l'entité. Ce parcours déclenche un traitement documentaire : utiliser un fichier de test dont vous avez autorisé l'analyse.

## 1. Envoyer directement le fichier

Définir `MARKO_OPERATION_ID` avec l'UUID de l'opération et choisir une **nouvelle** référence documentaire. Ne pas créer une fiche sans fichier sous cette référence avant l'upload.

```bash theme={null}
curl --fail-with-body --request PUT \
  "$MARKO_API_BASE_URL/documents/external/crm-document-001/upload" \
  --header "Authorization: Bearer $MARKO_ACCESS_TOKEN" \
  --header "Idempotency-Key: crm-document-001-upload-01" \
  --form "file=@./exemple.pdf;type=application/pdf" \
  --form "type=autre" \
  --form "operation_id=$MARKO_OPERATION_ID" \
  --form 'partner_metadata={"source":"crm","reference":"crm-document-001"}'
```

Laisser cURL construire le formulaire multipart. `partner_metadata` est ici du texte JSON. Enregistrer le `document_id` de la réponse dans `MARKO_DOCUMENT_ID`.

Une référence existante peut être retrouvée sans ajouter de fichier. Pour une reprise du même upload, garder l'identifiant, la clé, le fichier et les métadonnées de départ.

## 2. Vérifier le document

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

Vérifier son rattachement à l'opération, ses métadonnées et la disponibilité de `download_url`. Une fiche sans fichier disponible ne prouve pas que l'upload attendu a eu lieu. Une indisponibilité liée aux droits ou au traitement doit être diagnostiquée avant l'analyse.

## 3. Demander l'extraction

Enregistrer un fichier `extraction.json` en remplaçant les deux UUID par ceux obtenus dans votre contexte :

```json theme={null}
{
  "document_id":"aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
  "operation_id":"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
}
```

```bash theme={null}
curl --fail-with-body --request POST \
  "$MARKO_API_BASE_URL/workflows/ai-extraction-jobs" \
  --header "Authorization: Bearer $MARKO_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: crm-document-001-extraction-01" \
  --data-binary @extraction.json
```

La réponse `202` contient un `id` de job. Enregistrer cet identifiant dans `MARKO_EXTRACTION_JOB_ID`. Le document doit appartenir à l'intégration et à l'environnement de la clé; un simple scope IA ne donne pas accès aux documents d'une autre intégration.

## 4. Suivre le job et lire son résultat

```bash theme={null}
curl --fail-with-body \
  "$MARKO_API_BASE_URL/workflows/ai-extraction-jobs/$MARKO_EXTRACTION_JOB_ID" \
  --header "Authorization: Bearer $MARKO_ACCESS_TOKEN"
```

Espacer les lectures et conserver l'identifiant si l'attente dépasse votre budget. Les états sont `pending`, `running`, `completed`, `failed`, `dead_letter` et `cancelled`.

Pour `failed` ou `dead_letter`, lire `error_message` et diagnostiquer le traitement avant de soumettre une nouvelle intention. Pour `completed`, lire `suggestions_created`, `auto_applied`, `operation_resolution`, `narrative` et les résultats disponibles.

Un traitement terminé ne signifie pas que toute donnée extraite est correcte ou enregistrée. Les propositions qui demandent une confirmation restent à traiter dans le parcours de validation MARKO. Vérifier les valeurs métier persistées avant de les utiliser dans votre application.


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