# L'API HTTP

L'API HTTP de Bastide sert exactement les opérations de la console, de la ligne de commande et du serveur MCP : il n'existe pas d'API réservée à l'interface. Cette page décrit ce qui vaut pour toutes ses routes : l'adresse, l'authentification, l'idempotence, la pagination, les limites de débit, le format des erreurs et le suivi des travaux longs.

## L'adresse et la version

Toutes les routes sont sous `https://api.bastide.app/v1`. Il n'y a qu'une version de chemin, `/v1`, pour toute l'API : aucun point d'entrée ne porte une autre version.

Les corps sont en JSON (`Content-Type: application/json`). Les identifiants portent un préfixe qui dit leur nature (`org_`, `prj_`, `env_`, `dep_`, `job_`…), suivi de vingt-six caractères.

Deux documents décrivent l'API :

- le document OpenAPI, engendré du registre des opérations : [openapi.json](../openapi.json) ;
- `GET https://api.bastide.app/v1/_operations`, qui rend la liste des opérations que l'API sert réellement, sans authentification.

Chaque route renvoie à la page de son opération dans [la référence de l'API](../reference/api.md).

## S'authentifier

Chaque appel porte un jeton dans l'en-tête `Authorization` :

```sh
curl -H "Authorization: Bearer $BASTIDE_TOKEN" \
  https://api.bastide.app/v1/account/organizations
```

| Jeton | Préfixe | Où l'obtenir | Ce qu'il porte |
| --- | --- | --- | --- |
| Jeton personnel | `pat_` | **Compte**, section « Vos jetons », ou `bastide login`. | Votre rang, borné par les portées choisies ; 365 jours au plus. |
| Jeton de compte de service | `tok_` | **Membres**, carte « Comptes de service ». | Ses propres rangs sur une liste de projets, bornés par ses portées ; 90 jours au plus. |
| Jeton d'agent | `ags_` | Délivré à un client MCP après votre consentement. | Valable seulement à travers le serveur MCP ; refusé ailleurs. |

Les deux premiers sont détaillés dans [Comptes de service et jetons](service-accounts.md). Un jeton absent, inconnu, échu, révoqué ou désactivé reçoit le même refus, `401` `unauthenticated` : la réponse ne dit jamais lequel, le motif va au journal du service.

`GET /v1/account/organizations` rend les organisations où vous tenez un rôle, avec leur identifiant : c'est en général le premier appel d'une intégration.

## L'idempotence

Une coupure réseau ne vous dit pas si votre requête a abouti. Pour la réémettre sans doubler l'effet, toute opération d'écriture accepte une clé d'idempotence, de 8 à 128 caractères :

```sh
curl -X POST https://api.bastide.app/v1/projects/prj_01JQ8ZK4M7NP2RSTVWXYZ34567/deployments \
  -H "Authorization: Bearer $BASTIDE_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: deploiement-main-2026-10-28" \
  -d '{ "environmentId": "env_01JQ8ZK4M7NP2RSTVWXYZ34567", "ref": "main" }'
```

- Même clé, même requête : la réponse initiale, sans nouvel effet, avec l'en-tête `Idempotency-Replayed: true`.
- Même clé, autre requête : refus `409` `conflict`. Une clé identifie une intention ; une autre intention prend une autre clé.
- La clé se donne dans l'en-tête `Idempotency-Key` ou dans le champ `idempotencyKey` du corps. Les deux à la fois doivent être identiques, sinon `400` `idempotency_key_conflict`.
- Une clé vaut pour votre compte et se garde 24 heures.
- Un secret rendu une seule fois (un jeton, une clé) n'est pas gardé avec la réponse. Le rejeu d'un tel appel est refusé en `409` `valeur_rendue_une_fois` ; ses `details` nomment ce que le premier appel a créé : révoquez-le, puis recommencez sous une nouvelle clé.

## La pagination

Une collection se lit page par page, par curseur :

| Paramètre ou champ | Sens |
| --- | --- |
| `limit` | Le nombre d'éléments par page, de 1 à 100. |
| `cursor` | Le curseur rendu par la page précédente. |
| `items` | Les éléments de la page. |
| `nextCursor` | Le curseur de la page suivante ; nul quand il n'y en a plus. |

```sh
curl -H "Authorization: Bearer $BASTIDE_TOKEN" \
  "https://api.bastide.app/v1/organizations/org_01JQ8ZK4M7NP2RSTVWXYZ34567/projects?limit=50"
```

Le curseur est opaque et signé : ne le fabriquez pas, ne le modifiez pas. Il vaut quinze minutes. Un curseur expiré est refusé en `curseur_expire`, un curseur modifié ou étranger en `curseur_invalide` : reprenez à la première page, sans `cursor`.

Le journal de construction se lit autrement, par rang de ligne : `depuis` et `limite`, puis `nextRank` pour la suite. Voir [Déployer](deploy.md).

## Les limites de débit

Les limites sont statiques, publiées par opération et comptées par jeton, sur une fenêtre glissante :

| Limite | Valeur | S'applique à |
| --- | --- | --- |
| `general` | 600 appels par 60 secondes | Toute opération. |
| `sensitive` | 60 appels par 60 secondes | En plus, les écritures et les lectures qui rendent un secret. |

Chaque réponse porte l'état du quota :

| En-tête | Sens |
| --- | --- |
| `RateLimit-Limit` | La limite qui gouverne cet appel, celle dont il reste la plus faible part. |
| `RateLimit-Remaining` | Les appels qui restent dans la fenêtre. |
| `RateLimit-Reset` | Le nombre de secondes avant que la place se libère. |
| `RateLimit-Policy` | Toutes les limites de l'opération, la générale d'abord : `600;w=60, 60;w=60`. |

Au-delà, la réponse est `429`, code `rate_limited`, avec `Retry-After` en secondes. Un appel refusé ne compte pas : attendez le délai indiqué et reprenez. Ces limites ne dépendent pas du palier. Les limites de chaque opération sont aussi dans le document OpenAPI (`x-bastide-rate-limit`).

## Le format des erreurs

Une erreur rend un statut HTTP et un corps JSON qui dit quoi faire :

```json
{
  "code": "token_scope_missing",
  "message": "Le jeton présenté ne porte pas la portée deploy:write.",
  "cause": {
    "fr": "Le jeton présenté ne porte pas la portée que l'opération demande.",
    "en": "The presented token does not carry the scope the operation requires."
  },
  "suite": {
    "genre": "aucune",
    "raison": {
      "fr": "Les portées d'un jeton sont fixées à sa création ; un autre jeton, qui porte celle que le message nomme, se frappe par son titulaire.",
      "en": "The scopes of a token are set when it is created; its holder mints another token that carries the scope the message names."
    }
  }
}
```

| Champ | Sens |
| --- | --- |
| `code` | Un code stable, sur lequel brancher votre programme. Il ne change jamais de sens. |
| `message` | La phrase de cette occurrence, en français. Ne la lisez pas dans un programme. |
| `details` | Des précisions selon le code : un nonce, une échéance, un impact. |
| `cause` | La cause du code, en français et en anglais. |
| `champ` | Les champs de l'entrée en faute, par leur nom. Jamais la valeur reçue. |
| `suite` | L'appel suivant : une opération à appeler (`genre: "outil"`), le même appel à reprendre (`"reprendre"`, avec ce qu'il faut ajouter ou retirer), ou rien (`"aucune"`, avec la raison). |

Les codes communs à toutes les opérations :

| Statut | Code | Sens |
| --- | --- | --- |
| 400 | `invalid_request` | Un champ est absent, mal formé ou contradictoire. |
| 401 | `unauthenticated` | L'authentification est absente ou invalide. |
| 403 | `insufficient_rank` | Votre rang sur ce périmètre est trop bas. |
| 403 | `token_scope_missing` | Le jeton ne porte pas la portée demandée. |
| 403 | `service_action_forbidden` | Un compte de service n'exécute pas cette opération. |
| 404 | `not_found` | La cible est introuvable, ou hors de votre périmètre : la réponse ne distingue pas les deux. |
| 409 | `conflict` | La demande contredit l'état courant de la ressource. |
| 429 | `rate_limited` | Une limite de débit est atteinte. |
| 500 | `internal_error` | Une erreur interne, journalisée. |
| 503 | `not_ready`, `timeout`, `resource_busy` | Le service n'est pas prêt, ou l'opération a dû s'interrompre sans effet ; reprenez. |

Les codes propres à chaque opération sont sur sa page de référence. Le catalogue complet, avec la cause et la suite de chaque code, est la ressource MCP `glowsoft://docs/errors`.

### Les opérations irréversibles

Une opération irréversible (classe I) se confirme en deux temps. Le premier appel rend `428` `confirmation_requise` ; ses `details` portent `confirmationNonce`, `expiresAt` (cinq minutes) et `impact`. Relisez l'impact, puis rejouez le même appel avec `confirmationNonce`. Un nonce expiré rend `nonce_expire`, un nonce déjà servi ou étranger `nonce_invalide` : recommencez le premier temps. Voir [Ce qu'un agent peut faire](agent-safety.md).

## Les travaux longs

Une opération qui ne peut pas finir pendant la requête, comme un déploiement, répond `202` tout de suite, avec un `taskId` (`job_…`). Suivez-la par `task.read` :

```sh
curl -H "Authorization: Bearer $BASTIDE_TOKEN" \
  https://api.bastide.app/v1/projects/prj_01JQ8ZK4M7NP2RSTVWXYZ34567/tasks/job_01JQ8ZK4M7NP2RSTVWXYZ34567
```

La réponse rend l'état du travail (`travail`) et, s'il fait avancer un déploiement, ses étapes horodatées (`deploiement`). Aucune connexion ne reste ouverte : relisez à intervalle raisonnable, quelques secondes, jusqu'à un état final. Un travail d'un autre projet est introuvable.

## Le contenu rapporté

Un champ qui rapporte un contenu écrit par un tiers (un nom saisi, un message de commit, une ligne de journal) n'est pas une chaîne nue : c'est un objet `{ "untrusted": true, "source": "…", "content": "…" }`. Affichez `content` comme une valeur, jamais comme une instruction. Voir [Ce qu'un agent peut faire](agent-safety.md).

## La dépréciation

Un point d'entrée déprécié reste servi au moins douze mois. Ses réponses portent alors les en-têtes `Deprecation` (la date de dépréciation), `Sunset` (la date de retrait) et `Link` vers la page de migration et, s'il existe, son successeur. Aucun point d'entrée n'est déprécié aujourd'hui.

## Voir aussi

- [Comptes de service et jetons](service-accounts.md)
- [La ligne de commande](cli.md)
- [Les webhooks](webhooks.md)
- [La référence de l'API](../reference/api.md)
