---
title: "Référence de l’API REST et de MCP de Kodama"
description: "Appelez Kodama depuis du code : authentification, enveloppe de requête et de résultat, codes d’erreur, clés d’idempotence, versions, routes REST et MCP."
canonical_url: https://optimi.com/fr/docs/kodama/api
md_url: https://optimi.com/fr/docs/kodama/api.md
language: fr
alternates:
  - lang: en
    url: https://optimi.com/en/docs/kodama/api
    md_url: https://optimi.com/en/docs/kodama/api.md
last_updated: 2026-11-14
---

# API et MCP

Kodama a **un seul routeur d’opérations derrière deux interfaces** : REST sous
`/api/v1` et MCP sur `/mcp`. Les deux exposent les mêmes 58 opérations avec la
même autorisation. `kodactl` est un client REST, tout comme un conteneur de
pipeline. Les ordinateurs portables, les applications externes, la CI et les
charges de travail utilisent tous cette API.

## S’authentifier

Envoyez un jeton bearer en HTTPS :

```text
Authorization: Bearer <token>
```

| Appelant | Jeton |
| --- | --- |
| Une personne | Un jeton d’accès OAuth émis pour l’audience de Kodama ; `kodactl login` en obtient un et le renouvelle |
| Un compte de service | Un jeton `kodsvc.` qu’un administrateur émet, avec une expiration explicite |
| Un conteneur de pipeline | Le jeton d’exécution `kodex.` monté dans `/kodama/secrets/KODAMA_TOKEN` |

`GET /.well-known/kodama-configuration` décrit la configuration OAuth de
l’installation ; `kodactl login` la lit. `/healthz`, `/readyz` et `/metrics`
ne nécessitent aucun jeton.

Gardez les jetons hors des lignes de commande et des URL. Avec `curl`, écrivez
un fichier d’en-têtes privé :

```sh
source ~/.kodama/instances/default/env.sh
header="$(mktemp)" && chmod 600 "$header"
printf 'Authorization: Bearer %s\nContent-Type: application/json\n' \
  "$(cat "$KODAMA_CREDENTIAL_FILE")" > "$header"

curl --silent --show-error --cacert "$KODAMA_CA_FILE" --header @"$header" \
  "$KODAMA_URL/api/v1/whoami"
```

## L’enveloppe

Chaque opération prend le même objet de requête et renvoie le même objet de
résultat. Le seul type de contenu est `application/json`.

```json
{
  "input": { "title": "Fix the login bug", "pipeline_id": "…" },
  "expected_version": 3,
  "idempotency_key": "ticket-1843-start"
}
```

| Champ | Signification |
| --- | --- |
| `input` | Les champs propres à l’opération. Les identifiants présents dans le chemin, comme le projet ou l’exécution, peuvent être omis |
| `expected_version` | La version que vous avez lue en dernier, pour une opération qui modifie une ressource versionnée |
| `idempotency_key` | Votre clé de nouvelle tentative pour une mutation : jusqu’à 128 caractères parmi `A-Z a-z 0-9 . _ : -` |

Un appel réussi répond toujours **200** avec :

```json
{
  "operation": "task.create_and_start",
  "body": { "task_id": "…", "execution_id": "…", "lifecycle_version": 2 }
}
```

`replayed: true` est ajouté quand l’appel était une nouvelle tentative qui a
renvoyé un résultat enregistré.

Règles à connaître :

- **Les lectures sont des requêtes `GET`** dont l’entrée est un corps JSON. Un
  corps vide est accepté, donc `curl "$KODAMA_URL/api/v1/executions/<id>"`
  fonctionne.
- **Les champs inconnus sont refusés**, dans l’enveloppe comme dans l’entrée.
- **Le YAML est en base64.** Le champ `yaml` de `pipeline.apply` et
  `pipeline.start` porte le document en base64, 1 Mio au maximum. Les fragments
  de journaux sont aussi en base64.
- **Les listes sont paginées.** Passez `"page": {"limit": 50, "cursor": "…"}`
  dans l’entrée et continuez avec le `next_cursor` du résultat. La limite par
  défaut est 50 et le maximum 200.
- Un corps de requête fait au plus 1 Mio, ou 2 Mio quand il porte du YAML.

## Erreurs

Une erreur répond avec un code stable. Le statut HTTP correspond au code, un
pour un :

```json
{ "code": "version_conflict", "message": "…", "request_id": "…" }
```

| Code | Statut | Signification, et quoi faire |
| --- | --- | --- |
| `invalid_input` | 400 | La forme de la requête ou une valeur est refusée. Corrigez la requête |
| `unauthenticated` | 401 | Le jeton est absent, expiré ou non vérifiable |
| `forbidden` | 403 | Le principal n’a pas l’autorité nécessaire pour cette opération |
| `not_found` | 404 | Aucune entité correspondante dans votre périmètre |
| `deadline_expired` | 408 | Le travail a atteint ou dépassé son échéance, par exemple une réponse à une demande dont le délai a expiré |
| `version_conflict` | 409 | La version attendue est périmée. Relisez, puis décidez |
| `changed_idempotency_content` | 422 | La clé a déjà été utilisée avec une requête différente |
| `internal_failure` | 500 | Échec inattendu ; conservez le `request_id` |
| `unsupported_capability` | 501 | Le runtime de l’installation ne peut pas exécuter ce qui a été demandé |
| `uncertain_external_effect` | 502 | Le résultat d’un effet est inconnu. Observez avant de le répéter |
| `dependency_unavailable` | 503 | Une dépendance est indisponible. Réessayez plus tard avec la même clé |

## Idempotence et versions

Chaque mutation exige une `idempotency_key`. Kodama enregistre ensemble l’effet
et un reçu, délimités par le tenant, le projet, le principal, l’opération et
votre clé.

- **Réessayer la requête identique renvoie le résultat enregistré.** Utilisez
  cette propriété quand une réponse est perdue : répétez l’appel avec la même
  clé.
- **Réutiliser une clé avec un contenu différent est refusé** avec
  `changed_idempotency_content`.
- Les reçus sont conservés jusqu’à la suppression du projet, donc une clé reste
  réservée.

Dérivez les clés d’un élément stable de votre système, comme
`erp:evt-1843:start`. Ne générez jamais une nouvelle clé pour réessayer après
un échec ambigu : c’est ainsi qu’un travail est fait deux fois.

Les versions protègent les transitions. Le contenu des tâches, le cycle de vie
des tâches, les exécutions, les actions, les pipelines, les appartenances et
les liaisons portent chacun une version. Envoyez celle que vous avez lue ; si
quelqu’un d’autre a modifié la ressource avant vous, vous obtenez
`version_conflict` et rien n’est enregistré.

L’autorisation n’est jamais mise en cache : chaque appel, y compris un appel
rejoué, est vérifié par rapport aux rôles et à l’état actuels de l’appelant.

## Exemple détaillé

Démarrez un fichier de pipeline sans l’enregistrer, puis suivez-le :

```sh
project="$KODAMA_PROJECT"
jq -n --arg yaml "$(base64 -w0 hello.yaml)" \
  '{input: {yaml: $yaml}, idempotency_key: "hello-1"}' > start.json

curl --silent --show-error --cacert "$KODAMA_CA_FILE" --header @"$header" \
  --data-binary @start.json \
  "$KODAMA_URL/api/v1/projects/$project/executions" | tee started.json
execution="$(jq -r .body.execution_id started.json)"

curl --silent --show-error --cacert "$KODAMA_CA_FILE" --header @"$header" \
  "$KODAMA_URL/api/v1/executions/$execution" | jq '.body | {state, actions: [.actions[] | {name, state}]}'
```

Le résultat de l’inspection contient le `state` et la `version` de l’exécution,
ses `actions` (chacune avec `action_id`, `name`, `state`, `version`, et ses
sorties, son sujet ou sa décision), un `failure` quand il y en a un, et l’état
`cleanup` de sa charge de travail et de son espace de travail.

Répondez à une demande en attente avec l’ID et la version de l’action tirés de
ce résultat :

```sh
jq -n --argjson version "$action_version" \
  '{input: {expected_action_version: $version, decision: "pass"}, idempotency_key: "review-1"}' |
  curl --silent --show-error --cacert "$KODAMA_CA_FILE" --header @"$header" \
    --data-binary @- "$KODAMA_URL/api/v1/executions/$execution/actions/$action_id/resolve"
```

## Routes

Tous les chemins sont sous `/api/v1`. Le nom de l’opération est aussi
l’argument de `kodactl call`.

**Exécutions et pipelines**

| Opération | Méthode et chemin |
| --- | --- |
| `pipeline.start` | `POST /projects/{project}/pipelines/{pipeline}/start`, ou `POST /projects/{project}/executions` avec le YAML fourni dans la requête |
| `execution.inspect` | `GET /executions/{execution}` |
| `execution.list` | `GET /projects/{project}/executions` |
| `execution.cancel` | `POST /executions/{execution}/cancel` |
| `execution.logs.read` | `GET /executions/{execution}/logs` |
| `execution.action.outputs` | `PUT /executions/{execution}/actions/{action}/outputs` |
| `execution.request.resolve` | `POST /executions/{execution}/actions/{action}/resolve` |
| `pipeline.apply` | `PUT /projects/{project}/pipelines/{pipeline}` |
| `pipeline.read`, `pipeline.update`, `pipeline.delete` | `GET`, `PATCH`, `DELETE /projects/{project}/pipelines/{pipeline}` |
| `pipeline.list` | `GET /projects/{project}/pipelines` |

**Tâches**

| Opération | Méthode et chemin |
| --- | --- |
| `task.create` | `POST /projects/{project}/tasks` |
| `task.create_and_start` | `POST /projects/{project}/tasks/create-and-start` |
| `task.list` | `GET /projects/{project}/tasks` |
| `task.inbox` | `GET /projects/{project}/inbox` |
| `task.read`, `task.edit` | `GET`, `PATCH /tasks/{task}` |
| `task.comment` | `POST /tasks/{task}/comments` |
| `task.select_pipeline` | `PUT /tasks/{task}/pipeline` |
| `task.start` | `POST /tasks/{task}/start` |
| `task.ask` | `POST /tasks/{task}/ask` |
| `task.reply` | `POST /tasks/{task}/reply` |
| `task.cancel` | `POST /tasks/{task}/cancel` |
| `task.history` | `GET /tasks/{task}/history` |

**Administration**

| Opération | Méthode et chemin |
| --- | --- |
| `principal.whoami` | `GET /whoami` |
| `tenant.create` | `POST /tenants` |
| `tenant.rotate_key` | `POST /tenants/{tenant}/rotate-key` |
| `project.create`, `project.list` | `POST`, `GET /tenants/{tenant}/projects` |
| `project.update` | `PATCH /projects/{project}` |
| `membership.grant`, `membership.revoke` | `PUT`, `DELETE /projects/{project}/memberships/{principal}` |
| `membership.list` | `GET /projects/{project}/memberships` |
| `secret.write`, `secret.delete`, `secret.metadata.read` | `PUT`, `DELETE`, `GET /projects/{project}/secrets/{secret}` |
| `secret.metadata.list` | `GET /projects/{project}/secrets` |
| `service.create`, `service.list` | `POST`, `GET /projects/{project}/services` |
| `service.update` | `PATCH /services/{service}` |
| `service.enable`, `service.disable` | `POST /services/{service}/enable`, `POST /services/{service}/disable` |
| `service.credential.issue`, `service.credential.revoke` | `POST`, `DELETE /services/{service}/credentials` |
| `service.credential.rotate` | `POST /services/{service}/credentials/rotate` |

**Ressources, événements, modèles et outils**

| Opération | Méthode et chemin |
| --- | --- |
| `resource.binding.register`, `resource.binding.list` | `POST`, `GET /projects/{project}/bindings` |
| `resource.binding.read`, `resource.binding.update`, `resource.binding.delete` | `GET`, `PATCH`, `DELETE /bindings/{binding}` |
| `actor.verify` | `POST /verify` |
| `resource.report` | `POST /reports` |
| `event.publish`, `event.read` | `POST`, `GET /projects/{project}/events` |
| `event.subscribe` | `POST /projects/{project}/events/subscribe` |
| `model.invoke` | `POST /projects/{project}/models/{model}/invoke` |
| `tool.invoke` | `POST /projects/{project}/tools/{tool}/invoke` |

Les types Go de chaque entrée et de chaque résultat sont dans le
[paquet `api`](https://github.com/optimiware/kodama/tree/master/api), et un
client REST en Go est dans le
[paquet `client`](https://github.com/optimiware/kodama/tree/master/client).

## MCP

L’interface MCP est à `<server URL>/mcp`, en Streamable HTTP. Elle n’accepte
que `POST` et utilise les mêmes jetons bearer que REST.

- **Les outils sont les opérations.** Chaque opération est un outil dont le nom
  remplace les points par des tirets bas, comme `task_create_and_start` ;
  `tools/call` accepte aussi le nom avec des points. Un outil prend la même
  enveloppe que REST et renvoie le résultat sous forme de `content` et de
  `structuredContent`.
- **Clients.** `kodactl mcp install claude`, `cursor` ou `codex` écrit la
  configuration du client. Il enregistre `kodactl mcp stdio`, un pont qui
  ajoute votre identifiant et l’AC de l’installation, de sorte que le client
  n’a besoin d’aucun jeton propre.
- **HTTP direct.** Un client qui parle Streamable HTTP peut se connecter à
  `/mcp` avec un jeton bearer. Un appel d’outil non authentifié répond 401 avec
  les métadonnées de ressource qu’attendent les clients compatibles OAuth.
- **Vérification de l’origine.** Une requête dont l’en-tête `Origin` n’est pas
  l’origine de l’installation elle-même est refusée. Les clients en ligne de
  commande n’envoient pas d’`Origin` et ne sont pas concernés.

Dans la version 0.2.1, un `tools/list` non authentifié répond 200 ; seuls les
appels d’outils répondent 401.

Ce point d’accès expose les opérations de Kodama à un client. Le **proxy MCP**,
distinct et optionnel, fait l’inverse : il permet à un pipeline d’appeler des
outils MCP externes qu’un opérateur a enregistrés, via `tool.invoke`.

## Événements et journaux

Chaque changement du domaine est un événement avec un ID stable, un type, une
source, un horodatage, son tenant et son projet, et des données bornées.

- **Lisez les événements** avec `event.read`, filtrés par `types` et paginés.
  C’est la voie à suivre pour les personnes et pour la plupart des
  intégrations.
- **Publiez des observations personnalisées** avec `event.publish`. Un
  événement personnalisé ne clôt jamais une action et ne compte jamais comme
  une décision.
- **Lisez les journaux** avec `execution.logs.read`, par action et par flux.
  Les fragments sont disponibles quand la charge de travail de l’action est
  terminée.
- **Abonnez-vous via NATS** en tant que compte de service, pour les services
  pilotés par les événements. JetStream livre au moins une fois : dédupliquez
  par ID d’événement et tolérez le réordonnancement. Dans la version 0.2.1, les
  abonnements des personnes et des navigateurs sont refusés ; lisez plutôt via
  HTTP ou MCP.

NATS ne transporte que des événements. Il n’existe aucun moyen de soumettre une
opération via NATS.

## Construire une intégration

Un adaptateur Slack, e-mail, webhook ou minuteur est un client d’API ordinaire
avec un compte de service :

1. Un administrateur crée le compte de service (`service.create`), lui donne
   des rôles et émet un identifiant (`service.credential.issue`).
2. L’adaptateur gère son protocole externe et appelle Kodama avec des clés
   d’idempotence stables : `task.create_and_start` pour un nouveau travail,
   `task.reply` ou `execution.request.resolve` pour les questions et les
   demandes attribuées à `service/<its id>`.
3. Pour réagir à Kodama, il lit la boîte de réception et les événements.
   Parcourez l’état courant en plus des nouveaux événements, afin qu’un
   événement manqué plus tôt ne puisse pas laisser une demande sans réponse.

Un service qui protège sa propre ressource, comme le fait le service Git,
utilise aussi `actor.verify` avant chaque effet et `resource.report` après.
Voir **[Architecture](/fr/docs/kodama/architecture)**.
