Documentation de l'API
Une seule adresse de base, deux dialectes : Chat Completions pour les échanges textuels des clients compatibles OpenAI, et le dialecte natif pour les options propres à PinAI.
Démarrage
Tout part de la même adresse. Une clé se crée dans Clés API ; elle ne s'affiche qu'une fois.
https://ai.pinstudio.ch/api/v1Dans un client compatible OpenAI — éditeur, extension, bibliothèque —, renseignez cette adresse comme base URL et votre clé comme API key. Sélectionnez le mode Chat Completions textuel.
curl https://ai.pinstudio.ch/api/v1/chat/completions \
-H "Authorization: Bearer pinai_votre_cle" \
-H "Content-Type: application/json" \
-d '{
"model": "bunny-1",
"messages": [{ "role": "user", "content": "Bonjour." }]
}'import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://ai.pinstudio.ch/api/v1",
apiKey: process.env.PINAI_API_KEY,
});
const reponse = await client.chat.completions.create({
model: "bunny-1",
messages: [{ role: "user", content: "Bonjour." }],
});
console.log(reponse.choices[0].message.content);Authentification
Un en-tête Bearer, sur toutes les routes. Il n'y a pas de clé publique : une clé PinAI s'utilise depuis un serveur, jamais depuis un navigateur ni une application mobile distribuée.
Authorization: Bearer pinai_…Une clé porte les autorisations que vous lui donnez, et rien de plus. La génération exige ai.generate ; les connaissances d'équipe exigent en plus team_knowledge.read.
| Autorisation | Donne accès à | Routes |
|---|---|---|
| ai.generate | Génération | Les deux dialectes, le catalogue et l'utilisation. |
| team_knowledge.read | Connaissances | Le champ organization_id, qui joint les documents d'une équipe à la demande. |
| profile.read | Profil | Réservée aux intégrations OAuth. |
| team.read | Équipes | Réservée aux intégrations OAuth. |
Dialecte OpenAI
La route que connaissent les clients du commerce. Elle traduit vers la génération PinAI, sans rien décider d'autre.
POST/api/v1/chat/completions
| Champ | Type | Détail |
|---|---|---|
| model | chaîne, requis | Le nom d'un assistant, par exemple bunny-1. Le catalogue est donné par GET /models. |
| messages | tableau, requis | Rôles user, assistant, system, developer et tool. content accepte une chaîne ou des morceaux { type: 'text', text }. Un message tool porte tool_call_id ; un message assistant peut porter tool_calls sans contenu. 400 messages au plus ; 400 000 caractères pour l'ensemble, consignes et outils compris. |
| tools | tableau | Vos fonctions, au format OpenAI : { type: 'function', function: { name, description, parameters } }. 128 au plus. PinAI n'exécute rien : il rend les appels décidés, vous les exécutez. |
| tool_choice | chaîne ou objet | auto, none, required, ou { type: 'function', function: { name } }. Transmis au modèle tel quel. |
| parallel_tool_calls | booléen | À false, le modèle n'appelle qu'un outil à la fois. |
| temperature | nombre | Entre 0 et 2. Sans valeur, celle de l'assistant. |
| max_tokens | entier | Alias accepté de max_completion_tokens. Ramené dans l'intervalle 64–8192 plutôt que refusé. |
| max_completion_tokens | entier | Plafond de la réponse. Même intervalle. |
| stream | booléen | false par défaut. À true, la réponse arrive en événements SSE. |
Les messages system deviennent des consignes : elles se greffent sur l'identité de l'assistant au lieu de la remplacer. Un assistant sait toujours comment il s'appelle et qui l'édite, quelles que soient vos consignes.
{
"id": "chatcmpl-…",
"object": "chat.completion",
"created": 1789000000,
"model": "bunny-1",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "Bonjour." },
"finish_reason": "stop"
}
]
}{
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_…",
"type": "function",
"function": { "name": "lire_fichier", "arguments": "{\"chemin\":\"src/index.ts\"}" }
}
]
},
"finish_reason": "tool_calls"
}
]
}En flux, les événements suivent la forme habituelle : un premier morceau portant le rôle, des morceaux de texte, un dernier portant finish_reason, puis [DONE]. La raison de fin vaut length si le plafond de sortie est atteint, ou content_filter si le fournisseur filtre la réponse. En cas de panne ou de flux incomplet, un événement error remplace la fin réussie. Un appel d'outil arrive dans un seul morceau, complet, indexé comme chez OpenAI : un argument JSON n'est exécutable qu'entier, et il n'y a donc rien à accumuler.
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}], …}
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Bon"},"finish_reason":null}], …}
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"jour."},"finish_reason":null}], …}
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}], …}
data: [DONE]Dialecte PinAI
Le dialecte natif. Il donne accès à ce que le format OpenAI ne sait pas exprimer : l'effort de réflexion, les connaissances d'équipe, et ce qui reste de votre réserve.
POST/api/v1/responses
| Champ | Type | Détail |
|---|---|---|
| input | chaîne ou tableau, requis | Une question, ou une conversation de messages { role, content } avec role valant user, assistant ou tool. Un message tool porte tool_call_id ; un message assistant peut porter tool_calls sans contenu. 400 000 caractères pour l'ensemble, consignes et outils compris, et 400 messages au plus. |
| assistant_id | chaîne | Le nom d'un assistant. Omis, celui de votre forfait répond. Nommé mais introuvable, la demande est refusée plutôt que servie par un autre. |
| instructions | chaîne | Vos consignes. Elles s'ajoutent à l'identité de l'assistant, et comptent dans les 400 000 caractères du contexte. |
| organization_id | chaîne | Joint les documents de cette équipe à la demande. Exige team_knowledge.read et votre appartenance à l'équipe. |
| temperature | nombre | Entre 0 et 2. |
| max_output_tokens | entier | Entre 64 et 8192. |
| effort | chaîne | auto, minimal, low, medium ou high. Sans valeur, celui de l'assistant. |
| tools | tableau | Vos fonctions, au même format que le dialecte OpenAI. 128 au plus. |
| tool_choice | chaîne ou objet | auto, none, required, ou { type: 'function', function: { name } }. |
| parallel_tool_calls | booléen | À false, le modèle n'appelle qu'un outil à la fois. |
| stream | booléen | À true, la réponse arrive en NDJSON : une ligne de JSON par événement. |
| session_id | chaîne | Votre propre identifiant de session, repris tel quel dans le premier événement du flux. |
{
"id": "resp_…",
"object": "response",
"output": [
{
"type": "message",
"role": "assistant",
"content": [{ "type": "output_text", "text": "Bonjour." }]
}
],
"output_text": "Bonjour.",
"finish_reason": "stop",
"sources": [],
"usage": { "remaining_percent": null, "credits_remaining_micros": 9998650 }
}sources ne se remplit qu'avec organization_id : il nomme les documents d'équipe qui ont servi, et la page où ils ont été lus.
En flux, chaque ligne est un objet complet : response.created, puis des response.output_text.delta, puis response.completed. Une panne du fournisseur arrive comme une ligne error au milieu du flux, avec un statut HTTP déjà envoyé — vérifiez le type de chaque ligne plutôt que le seul code de réponse. La raison de fin se trouve dans response.finish_reason en flux, et dans finish_reason hors flux.
Avec tools, les appels décidés arrivent hors flux dans tool_calls, à côté de output_text, et en flux dans une ligne response.tool_calls puis dans response.completed. La raison de fin vaut alors tool_calls, et output_text peut être vide : le tour de parole est le vôtre.
Assistants
Le catalogue que votre accès peut appeler, et rien de plus : un assistant qui n'y figure pas sera refusé.
GET/api/v1/models
{
"object": "list",
"default_id": "bunny-1",
"data": [
{
"id": "bunny-1",
"object": "model",
"name": "Bunny 1",
"description": "Léger et économe.",
"available": true
}
]
}id est le nom à passer dans model ou assistant_id. Une liste vide signifie qu'aucun assistant n'est ouvert à votre type d'accès — pour une clé d'intégration, c'est le cas tant qu'aucun tarif n'est publié. Les tarifs figurent page Modèles.
Utilisation
Ce qui reste, dans l'unité de la bourse qui paie vos appels.
GET/api/v1/usage
| Champ | Type | Détail |
|---|---|---|
| remaining_percent | nombre ou null | Le pourcentage restant du cycle, pour un accès payé par le forfait. null pour un forfait sans limite, et pour un accès payé aux crédits. |
| credits_remaining_micros | entier | Le solde en micro-euros, pour un accès payé aux crédits. 1 000 000 micro-euros valent 1 €. |
| reset_at | date ISO ou null | La fin du cycle en cours. null pour les crédits, qui ne se renouvellent pas. |
Facturation
Deux bourses qui ne communiquent pas. Épuiser l'une ne coupe jamais l'autre.
| Accès | Ce qui paie | Ce qui se passe à l'épuisement |
|---|---|---|
| Clé d'intégration | Crédits prépayés | 402 insufficient_credits. Rechargez page Crédits. |
| Clé API dans PinCode | Crédits prépayés | 402 insufficient_credits. Rechargez page Crédits. |
| Compte PinAI connecté dans PinCode | Budget du forfait | 402 quota_error jusqu'au prochain cycle. |
| Intégration OAuth | Crédits prépayés | 402 insufficient_credits. |
Les tarifs s'expriment en euros par million de jetons, hors taxes, et diffèrent à l'entrée et à la sortie. Ils figurent page Modèles. Le prix d'un appel se calcule sur les jetons réellement consommés, pas sur le plafond demandé.
Erreurs
Toujours la même forme : un objet error avec un message lisible et un type stable.
{ "error": { "message": "Crédits insuffisants pour cette demande.", "type": "insufficient_credits" } }| Statut | Type | Ce qu'il faut faire |
|---|---|---|
| 400 | invalid_request_error | La requête ne respecte pas le format. Le message nomme le champ en cause. |
| 401 | authentication_error | Clé absente, invalide ou révoquée. |
| 402 | quota_error · insufficient_credits | La réserve ne couvre pas la demande. Attendez le cycle suivant, ou rechargez. |
| 403 | permission_error · model_unavailable | L'autorisation manque à la clé, ou l'assistant n'est pas ouvert à ce type d'accès. |
| 404 | model_unavailable | L'assistant demandé n'existe pas, ou n'est pas dans votre catalogue. |
| 429 | rate_limit_error | Trop de requêtes. L'en-tête Retry-After dit combien de temps attendre. |
| 502 | provider_error | Panne passagère côté génération. Réessayez. |
Limites
Comptées par fenêtre glissante d'une minute, et cumulatives : la plus basse s'applique.
| Portée | Génération | Catalogue et utilisation |
|---|---|---|
| Par clé | 30 / minute | 30 / minute |
| Par compte | 60 / minute | — |
| Par adresse IP | 120 / minute | — |
Le délai de génération dépend du fournisseur et du client. PinCode impose un plafond de quinze minutes. Un flux interrompu par le client reste facturé pour ce qui a déjà été produit.
