{
  "openapi": "3.1.0",
  "info": {
    "title": "API Netaix",
    "version": "1.0.0",
    "description": "Passerelle Netaix, compatible avec l'API d'OpenAI et, pour /v1/messages et /v1/messages/count_tokens, avec celle d'Anthropic. Toute erreur rendue sous /v1 prend une enveloppe unique : {\"error\": {\"message\", \"type\", \"code\", \"param\"}}, avec en plus \"type\": \"error\" au premier niveau sur les routes au format Anthropic. Cette description est ecrite a la main et ne couvre que les sept routes publiques. Documentation redigee : https://netaix.fr/docs/",
    "contact": { "name": "Netaix", "url": "https://netaix.fr/contact.html" }
  },
  "servers": [{ "url": "https://app.netaix.fr/v1" }],
  "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
  "tags": [
    { "name": "modeles" },
    { "name": "conversation" },
    { "name": "embeddings" },
    { "name": "moderation" },
    { "name": "consommation" }
  ],
  "paths": {
    "/models": {
      "get": {
        "tags": ["modeles"],
        "operationId": "listerLesModeles",
        "summary": "Les modeles et les niveaux que la cle peut appeler",
        "responses": {
          "200": {
            "description": "Liste des niveaux Netaix puis des modeles autorises",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ListeDeModeles" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/NonAuthentifie" },
          "429": { "$ref": "#/components/responses/TropDeRequetes" }
        }
      }
    },
    "/chat/completions": {
      "post": {
        "tags": ["conversation"],
        "operationId": "creerUneReponseDeConversation",
        "summary": "Conversation au format OpenAI, en bloc ou en flux",
        "description": "Avec stream=true, la reponse est un flux text/event-stream dont chaque ligne utile commence par 'data: ' et porte un objet chat.completion.chunk ; le dernier fragment porte finish_reason et usage, puis la ligne 'data: [DONE]'.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/RequeteDeConversation" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reponse complete, ou flux d'evenements serveur si stream=true",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ReponseDeConversation" }
              },
              "text/event-stream": {
                "schema": { "type": "string" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/RequeteInvalide" },
          "401": { "$ref": "#/components/responses/NonAuthentifie" },
          "402": { "$ref": "#/components/responses/RechargeRequise" },
          "403": { "$ref": "#/components/responses/ModeleNonAutorise" },
          "422": { "$ref": "#/components/responses/ImageRefusee" },
          "429": { "$ref": "#/components/responses/TropDeRequetes" },
          "451": { "$ref": "#/components/responses/DonneeSensibleBloquee" },
          "502": { "$ref": "#/components/responses/EchecDuFournisseur" }
        }
      }
    },
    "/messages": {
      "post": {
        "tags": ["conversation"],
        "operationId": "creerUnMessage",
        "summary": "Conversation au format Anthropic, flux, outils et images",
        "description": "Format de l'API Anthropic : consigne systeme en chaine ou en blocs, definitions d'outils, blocs tool_use et tool_result, images, temperature, max_tokens, top_p et stop_sequences. Les clients ecrits pour ce format, Claude Code compris, fonctionnent sans adaptation, usage agent inclus. Avec stream=true, la reponse suit le protocole de flux d'Anthropic : chaque evenement porte sa ligne event: puis sa ligne data:, dans l'ordre message_start, ping, content_block_start, content_block_delta (text_delta ou input_json_delta), content_block_stop, message_delta, message_stop ; une erreur en cours de flux devient un evenement error. Les stop_sequences sont appliquees par la passerelle, qui coupe le texte au premier motif et referme l'appel au modele. Avec un SDK Anthropic, l'adresse de base est https://app.netaix.fr sans /v1. Les erreurs de cette route portent en plus \"type\": \"error\" au premier niveau.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/RequeteDeMessages" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message complet, ou flux d'evenements serveur si stream=true",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ReponseDeMessages" }
              },
              "text/event-stream": { "schema": { "type": "string" } }
            }
          },
          "400": { "$ref": "#/components/responses/RequeteInvalide" },
          "401": { "$ref": "#/components/responses/NonAuthentifieFormatAnthropic" },
          "402": { "$ref": "#/components/responses/RechargeRequise" },
          "403": { "$ref": "#/components/responses/ModeleNonAutorise" },
          "422": { "$ref": "#/components/responses/ImageRefusee" },
          "429": { "$ref": "#/components/responses/TropDeRequetes" },
          "451": { "$ref": "#/components/responses/DonneeSensibleBloquee" },
          "502": { "$ref": "#/components/responses/EchecDuFournisseur" }
        }
      }
    },
    "/messages/count_tokens": {
      "post": {
        "tags": ["conversation"],
        "operationId": "compterLesJetonsDEntree",
        "summary": "Estimation du nombre de jetons d'entree d'une requete /messages",
        "description": "Le corps est celui de /messages ; max_tokens n'est pas necessaire. Le compte porte sur la consigne systeme, les messages, les arguments des appels d'outils rejoues et les definitions d'outils. C'est une ESTIMATION calculee par la passerelle, pas le compte exact d'un fournisseur : aucun modele n'est appele, l'appel n'est pas decompte en credits. Le compte reel d'un appel est dans le bloc usage de sa reponse.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/RequeteDeMessages" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Estimation du nombre de jetons d'entree",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "input_tokens": { "type": "integer" } },
                  "required": ["input_tokens"]
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/RequeteInvalide" },
          "401": { "$ref": "#/components/responses/NonAuthentifieFormatAnthropic" },
          "429": { "$ref": "#/components/responses/TropDeRequetes" }
        }
      }
    },
    "/netaix/usage": {
      "get": {
        "tags": ["consommation"],
        "operationId": "lireLaConsommationDeLaCle",
        "summary": "Consommation et plafonds de la cle qui appelle",
        "description": "Aucun parametre : l'identite vient de la cle, jamais de la requete. Une cle ne lit que sa propre consommation, jamais celle d'une autre cle ni celle de l'organisation. Les chiffres sortent de la meme fonction que le refus au depassement et que le releve de l'application. Limite a 30 requetes par minute, et non 60. Non decompte en credits. Repond pour une cle d'API uniquement.",
        "responses": {
          "200": {
            "description": "Consommation du jour et du cycle, avec les plafonds de la cle",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConsommationDeLaCle" }
              }
            }
          },
          "400": {
            "description": "L'identifiant utilise n'est pas une cle d'API et n'a pas de releve propre. code : usage_not_available_for_this_credential",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Erreur" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/NonAuthentifie" },
          "429": { "$ref": "#/components/responses/TropDeRequetes" }
        }
      }
    },
    "/embeddings": {
      "post": {
        "tags": ["embeddings"],
        "operationId": "creerDesVecteurs",
        "summary": "Vecteurs a partir de texte, au format OpenAI",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/RequeteDEmbeddings" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Un vecteur par entree, dans l'ordre recu",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ReponseDEmbeddings" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/RequeteInvalide" },
          "401": { "$ref": "#/components/responses/NonAuthentifie" },
          "402": { "$ref": "#/components/responses/RechargeRequise" },
          "404": { "$ref": "#/components/responses/ModeleIntrouvable" },
          "429": { "$ref": "#/components/responses/TropDeRequetes" },
          "451": { "$ref": "#/components/responses/DonneeSensibleBloquee" },
          "503": { "$ref": "#/components/responses/ServiceIndisponible" }
        }
      }
    },
    "/moderations": {
      "post": {
        "tags": ["moderation"],
        "operationId": "modererUnTexte",
        "summary": "Verdict de moderation sur un texte, au format OpenAI",
        "description": "Un 503 signifie qu'aucun verdict n'a pu etre produit. Ce n'est pas un verdict favorable et ne doit pas etre lu comme tel.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/RequeteDeModeration" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verdict",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ReponseDeModeration" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/RequeteInvalide" },
          "401": { "$ref": "#/components/responses/NonAuthentifie" },
          "422": { "$ref": "#/components/responses/ImageRefusee" },
          "429": { "$ref": "#/components/responses/TropDeRequetes" },
          "451": { "$ref": "#/components/responses/DonneeSensibleBloquee" },
          "503": { "$ref": "#/components/responses/ServiceIndisponible" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer" },
      "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }
    },
    "schemas": {
      "ListeDeModeles": {
        "type": "object",
        "properties": {
          "object": { "type": "string", "const": "list" },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": { "type": "string" },
                "object": { "type": "string", "const": "model" },
                "created": { "type": "integer" },
                "owned_by": { "type": "string" },
                "description": {
                  "type": "string",
                  "description": "Ajout Netaix, present sur les cinq niveaux seulement"
                }
              },
              "required": ["id", "object", "created", "owned_by"]
            }
          }
        },
        "required": ["object", "data"]
      },
      "Message": {
        "type": "object",
        "properties": {
          "role": {
            "type": "string",
            "enum": ["system", "user", "assistant", "tool"]
          },
          "content": {
            "description": "Une chaine, ou une liste de blocs de texte et d'images",
            "oneOf": [
              { "type": "string" },
              { "type": "array", "items": { "type": "object" } }
            ]
          }
        },
        "required": ["role"]
      },
      "RequeteDeConversation": {
        "type": "object",
        "description": "Les champs inconnus sont acceptes sans erreur. response_format, n, stop, seed, logprobs, presence_penalty et frequency_penalty sont acceptes mais sans effet.",
        "properties": {
          "model": {
            "type": "string",
            "maxLength": 200,
            "description": "Un niveau (netaix-auto, netaix-fast, netaix-balanced, netaix-powerful, netaix-reasoning) ou un identifiant rendu par GET /models"
          },
          "messages": {
            "type": "array",
            "maxItems": 200,
            "items": { "$ref": "#/components/schemas/Message" }
          },
          "stream": { "type": "boolean", "default": false },
          "temperature": { "type": "number", "minimum": 0, "maximum": 2 },
          "max_tokens": { "type": "integer", "minimum": 1, "maximum": 128000 },
          "tools": {
            "type": "array",
            "items": { "type": "object" },
            "description": "Un tour d'inference. La passerelle rend les tool_calls, elle n'execute pas les outils."
          },
          "tool_choice": {
            "oneOf": [{ "type": "string" }, { "type": "object" }]
          },
          "top_p": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Transmis au modele"
          }
        },
        "required": ["model", "messages"]
      },
      "Usage": {
        "type": "object",
        "properties": {
          "prompt_tokens": {
            "type": "integer",
            "description": "Jetons d'entree hors part relue depuis le cache du fournisseur"
          },
          "completion_tokens": { "type": "integer" },
          "total_tokens": { "type": "integer" },
          "prompt_tokens_details": {
            "type": "object",
            "properties": { "cached_tokens": { "type": "integer" } }
          },
          "cache_write_tokens": {
            "type": "integer",
            "description": "Ajout Netaix, hors norme OpenAI"
          }
        }
      },
      "ReponseDeConversation": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "object": { "type": "string", "const": "chat.completion" },
          "created": { "type": "integer" },
          "model": {
            "type": "string",
            "description": "Le modele reellement servi, meme quand un niveau a ete demande"
          },
          "choices": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": { "type": "integer" },
                "message": {
                  "type": "object",
                  "properties": {
                    "role": { "type": "string", "const": "assistant" },
                    "content": { "type": ["string", "null"] },
                    "tool_calls": {
                      "type": "array",
                      "items": { "type": "object" }
                    }
                  }
                },
                "finish_reason": {
                  "type": "string",
                  "enum": ["stop", "tool_calls", "length"]
                }
              }
            }
          },
          "usage": { "$ref": "#/components/schemas/Usage" }
        }
      },
      "MessageAnthropic": {
        "type": "object",
        "description": "Un message au format Anthropic. Le contenu est une chaine, ou une liste de blocs text, image, tool_use et tool_result, transmis sans aplatissement.",
        "properties": {
          "role": { "type": "string", "enum": ["user", "assistant"] },
          "content": {
            "oneOf": [
              { "type": "string" },
              { "type": "array", "items": { "type": "object" } }
            ]
          }
        },
        "required": ["role", "content"]
      },
      "RequeteDeMessages": {
        "type": "object",
        "description": "Les champs inconnus sont acceptes sans erreur.",
        "properties": {
          "model": {
            "type": "string",
            "maxLength": 200,
            "description": "Un niveau (netaix-auto, netaix-fast, netaix-balanced, netaix-powerful, netaix-reasoning) ou un identifiant rendu par GET /models"
          },
          "messages": {
            "type": "array",
            "maxItems": 2000,
            "items": { "$ref": "#/components/schemas/MessageAnthropic" }
          },
          "system": {
            "description": "Une chaine, ou une liste de blocs {\"type\": \"text\"}. Les deux formes sont prises en charge, et examinees par le filtre des donnees sensibles comme les messages.",
            "oneOf": [
              { "type": "string" },
              { "type": "array", "items": { "type": "object" } }
            ]
          },
          "stream": { "type": "boolean", "default": false },
          "max_tokens": {
            "type": "integer",
            "minimum": 1,
            "maximum": 128000,
            "default": 4096,
            "description": "Transmis au modele"
          },
          "temperature": {
            "type": "number",
            "minimum": 0,
            "maximum": 2,
            "description": "Transmis au modele"
          },
          "top_p": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Transmis au modele"
          },
          "stop_sequences": {
            "type": "array",
            "maxItems": 16,
            "items": { "type": "string" },
            "description": "Appliquees par la passerelle : le texte est coupe au premier motif rencontre, stop_reason vaut alors stop_sequence, et l'appel au modele est referme"
          },
          "tools": {
            "type": "array",
            "maxItems": 256,
            "items": { "type": "object" },
            "description": "Un tour d'inference. La passerelle rend les blocs tool_use, elle n'execute pas les outils."
          },
          "tool_choice": { "type": "object" }
        },
        "required": ["model", "messages"]
      },
      "ReponseDeMessages": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "type": { "type": "string", "const": "message" },
          "role": { "type": "string", "const": "assistant" },
          "content": {
            "type": "array",
            "description": "Blocs text et tool_use, dans leur ordre d'apparition",
            "items": {
              "oneOf": [
                {
                  "type": "object",
                  "properties": {
                    "type": { "type": "string", "const": "text" },
                    "text": { "type": "string" }
                  }
                },
                {
                  "type": "object",
                  "properties": {
                    "type": { "type": "string", "const": "tool_use" },
                    "id": { "type": "string" },
                    "name": { "type": "string" },
                    "input": { "type": "object" }
                  }
                }
              ]
            }
          },
          "model": {
            "type": "string",
            "description": "Le modele reellement servi, meme quand un niveau a ete demande"
          },
          "stop_reason": {
            "type": ["string", "null"],
            "enum": ["end_turn", "tool_use", "max_tokens", "stop_sequence", null]
          },
          "stop_sequence": {
            "type": ["string", "null"],
            "description": "Le motif rencontre, quand stop_reason vaut stop_sequence"
          },
          "usage": {
            "type": "object",
            "properties": {
              "input_tokens": { "type": "integer" },
              "output_tokens": { "type": "integer" },
              "cache_read_input_tokens": { "type": "integer" },
              "cache_creation_input_tokens": { "type": "integer" }
            }
          }
        }
      },
      "FenetreDeConsommation": {
        "type": "object",
        "properties": {
          "debut": {
            "type": "string",
            "description": "Debut de la fenetre, en heure universelle. Minuit UTC pour le jour, debut du cycle de facturation pour le cycle."
          },
          "consomme": {
            "type": "integer",
            "description": "Credits consommes par cette cle dans la fenetre"
          },
          "plafond": {
            "type": ["integer", "null"],
            "description": "Plafond reglé sur la cle pour cette fenetre, null quand elle n'en a pas"
          },
          "restant": {
            "type": ["integer", "null"],
            "description": "null quand il n'y a pas de plafond : un plafond absent n'est pas un plafond a zero"
          },
          "part": {
            "type": ["number", "null"],
            "description": "Part consommee, entre 0 et 1. null sans plafond."
          },
          "alerte": {
            "type": "boolean",
            "description": "true a partir de 80 % du plafond, tant qu'il n'est pas depasse"
          },
          "depasse": {
            "type": "boolean",
            "description": "true quand la fenetre est au plafond : les appels de cette cle sont refuses par un 429 jusqu'a sa reouverture"
          }
        }
      },
      "ConsommationDeLaCle": {
        "type": "object",
        "properties": {
          "object": { "type": "string", "const": "netaix.usage" },
          "unite": {
            "type": "string",
            "const": "credits",
            "description": "Toujours des credits. Jamais des euros, jamais des jetons."
          },
          "cle": {
            "type": "object",
            "properties": {
              "prefixe": {
                "type": "string",
                "description": "Le debut de la cle, celui que l'application affiche pour la reconnaitre. Jamais la cle entiere."
              },
              "type": {
                "type": "string",
                "enum": ["personal_token", "workspace_key"]
              }
            }
          },
          "jour": { "$ref": "#/components/schemas/FenetreDeConsommation" },
          "cycle": { "$ref": "#/components/schemas/FenetreDeConsommation" },
          "verifie_a": {
            "type": "string",
            "description": "Instant de la lecture, en heure universelle. Aucune valeur n'est mise en cache."
          }
        },
        "required": ["object", "unite", "cle", "jour", "cycle", "verifie_a"]
      },
      "RequeteDEmbeddings": {
        "type": "object",
        "properties": {
          "input": {
            "description": "Un texte, ou jusqu'a 96 textes. 32 000 caracteres par texte, 400 000 au total. Les jetons deja decoupes ne sont pas acceptes.",
            "oneOf": [
              { "type": "string" },
              { "type": "array", "maxItems": 96, "items": { "type": "string" } }
            ]
          },
          "model": {
            "type": "string",
            "maxLength": 100,
            "description": "Un modele d'embeddings du catalogue. Par defaut, celui de la passerelle."
          },
          "encoding_format": {
            "type": "string",
            "enum": ["float", "base64"],
            "default": "float"
          },
          "dimensions": {
            "type": "integer",
            "description": "Refuse par un 400 : les modeles disponibles rendent des vecteurs de taille fixe"
          }
        },
        "required": ["input"]
      },
      "ReponseDEmbeddings": {
        "type": "object",
        "properties": {
          "object": { "type": "string", "const": "list" },
          "model": { "type": "string" },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "object": { "type": "string", "const": "embedding" },
                "index": { "type": "integer" },
                "embedding": {
                  "oneOf": [
                    { "type": "array", "items": { "type": "number" } },
                    {
                      "type": "string",
                      "description": "float32 petit-boutiste encode en base64, quand encoding_format vaut base64"
                    }
                  ]
                }
              }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "prompt_tokens": { "type": "integer" },
              "total_tokens": { "type": "integer" }
            }
          }
        }
      },
      "RequeteDeModeration": {
        "type": "object",
        "properties": {
          "input": {
            "oneOf": [
              { "type": "string" },
              { "type": "array", "items": { "type": "object" } }
            ]
          }
        },
        "required": ["input"]
      },
      "ReponseDeModeration": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "model": { "type": "string" },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "flagged": { "type": "boolean" },
                "categories": {
                  "type": "object",
                  "additionalProperties": { "type": "boolean" }
                },
                "category_scores": {
                  "type": "object",
                  "additionalProperties": { "type": "number" }
                }
              }
            }
          }
        }
      },
      "ObjetDErreur": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "description": "Cause decrite en termes metier. Jamais le message brut d'un fournisseur, jamais la valeur envoyee par l'appelant."
          },
          "type": {
            "type": "string",
            "description": "Famille d'erreur dans le vocabulaire d'OpenAI : invalid_request_error, authentication_error, permission_error, rate_limit_error, insufficient_quota, api_error..."
          },
          "code": {
            "type": ["string", "null"],
            "description": "Code machine stable, a tester dans le code appelant. null sur les statuts qui n'en ont pas : replier alors sur le statut HTTP."
          },
          "param": {
            "type": ["string", "null"],
            "description": "Champ fautif sur une erreur de validation, null sinon"
          }
        },
        "required": ["message", "type"]
      },
      "Erreur": {
        "type": "object",
        "description": "L'enveloppe d'erreur UNIQUE de toutes les routes sous /v1.",
        "properties": { "error": { "$ref": "#/components/schemas/ObjetDErreur" } },
        "required": ["error"]
      },
      "ErreurFormatAnthropic": {
        "type": "object",
        "description": "La meme enveloppe sur /messages et /messages/count_tokens : elle porte en plus un champ type valant error au premier niveau, forme attendue par un SDK Anthropic.",
        "properties": {
          "type": { "type": "string", "const": "error" },
          "error": { "$ref": "#/components/schemas/ObjetDErreur" }
        },
        "required": ["type", "error"]
      }
    },
    "responses": {
      "NonAuthentifie": {
        "description": "La cle n'a pas ete acceptee. UNE SEULE REPONSE pour toutes les causes (cle absente, inconnue, revoquee, expiree, mal formee, ou depourvue des droits necessaires) : meme statut, meme message, meme code invalid_api_key, de type authentication_error. Un refus qui distinguerait ces cas renseignerait qui essaie des cles au hasard. Ne pas reessayer.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erreur" }
          }
        }
      },
      "NonAuthentifieFormatAnthropic": {
        "description": "Meme refus unique que NonAuthentifie (code invalid_api_key, toutes causes confondues), dans la forme des routes au format Anthropic : l'enveloppe porte en plus un champ type valant error au premier niveau. Toutes les autres erreurs de ces routes prennent la meme forme.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErreurFormatAnthropic" }
          }
        }
      },
      "RequeteInvalide": {
        "description": "Requete invalide : champ hors bornes (param le nomme), corps qui n'est pas du JSON, borne de volume depassee, ou aucune cle de fournisseur configuree pour le modele demande. code : invalid_request, ou le code propre a la borne depassee sur /embeddings.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erreur" }
          }
        }
      },
      "RechargeRequise": {
        "description": "Deux cas, tous deux de type insufficient_quota. 1) topup_required : compte Developpeur dont la premiere recharge n'est pas payee. 2) insufficient_balance : compte Developpeur dont le solde prepaye est epuise. Le controle a lieu AVANT l'appel et le decompte apres : un appel entame avec un solde positif va au bout, meme s'il fait passer le solde sous zero, et la recharge suivante eponge cette dette. Ne pas reessayer : rechargez.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erreur" }
          }
        }
      },
      "ModeleNonAutorise": {
        "description": "code : model_not_allowed. Voir GET /models",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erreur" }
          }
        }
      },
      "ModeleIntrouvable": {
        "description": "codes : model_not_found (sur /embeddings), not_found (adresse inconnue, presque toujours un /v1 en trop ou en moins)",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erreur" }
          }
        }
      },
      "ImageRefusee": {
        "description": "codes : invalid_image, unsupported_image_format, image_too_large, too_many_images, invalid_input, invalid_role, invalid_system. Memes bornes sur /chat/completions et sur /messages : 4 images par message, 8 Mo, png, jpeg, webp.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erreur" }
          }
        }
      },
      "TropDeRequetes": {
        "description": "Trois cas distincts. 1) Debit depasse : code rate_limit_exceeded, de type rate_limit_error, avec un en-tete Retry-After en secondes. La limite est de 60 requetes par minute, sauf GET /netaix/usage limitee a 30. 2) Echecs d'authentification repetes depuis la meme origine : code too_many_auth_failures, avec un en-tete Retry-After. 3) Plafond de credits atteint : codes quota_exceeded, key_daily_quota_exceeded, key_quota_exceeded, de type insufficient_quota, refus definitif a ne pas reessayer.",
        "headers": {
          "Retry-After": {
            "description": "Secondes a attendre. Present sur le 429 de debit et sur celui des echecs d'authentification repetes ; absent sur un plafond de credits, qui ne se resout pas en attendant.",
            "required": false,
            "schema": { "type": "string" }
          }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erreur" }
          }
        }
      },
      "DonneeSensibleBloquee": {
        "description": "Le filtre des donnees sensibles a arrete l'envoi avant qu'il atteigne un fournisseur. code : netaix_dlp_blocked. L'objet error porte un bloc netaix (regle, mode, retenu, incident_id, message_key) hors norme OpenAI. Sur les routes au format Anthropic, cette reponse est la seule a ne pas porter de champ type au premier niveau. Ne pas reessayer tant qu'un administrateur n'a pas autorise l'envoi.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erreur" }
          }
        }
      },
      "EchecDuFournisseur": {
        "description": "code : upstream_error, de type api_error. En flux, le meme echec arrive dans un fragment d'erreur portant le code inference_error. Pour les comptes chez qui la bascule est ouverte, un appel sur un niveau netaix-* peut etre rejoue UNE fois sur un modele de niveau equivalent quand le fournisseur est en panne (delai depasse, connexion refusee, 5xx ou 429 du fournisseur) et avant tout fragment de reponse ; jamais sur un refus de contenu ni une requete invalide, et jamais pour un modele demande par son nom. La facturation est unique, au nom du modele qui a repondu, que porte le champ model de la reponse. Sans bascule ou sans remplacant disponible, c'est a l'appelant de reessayer.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erreur" }
          }
        }
      },
      "ServiceIndisponible": {
        "description": "codes : moderation_unavailable, embeddings_unavailable, service_unavailable",
        "headers": {
          "Retry-After": { "schema": { "type": "string" } }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Erreur" }
          }
        }
      }
    }
  }
}
