← Guides pour développeurs

Intégration directe de l'API

Appelez l'API en HTTP brut — sans SDK, n'importe quel langage. Les formes exactes des requêtes/réponses anonymes et authentifiées, avec des exemples curl.

Sans SDK, n’importe quel langage — voici le contrat HTTP exact pour appeler l’API directement. Si vous utilisez JavaScript, le SDK enveloppe tout cela (soumission, polling, gestion des erreurs) pour vous — voir Intégration directe d’une page web simple ou Choisissez votre parcours d’intégration si vous n’avez pas encore choisi de guide.

Chaque exemple ci-dessous affiche l’URL de base réelle de l’API pour cet environnement — copiez-la telle quelle, sans segment de chemin supplémentaire ajouté après.

Deux endpoints différents, pas une seule URL avec deux modes d’authentification

Ce sont deux endpoints HTTP distincts. Ils retournent aussi des formes de réponse différentes pour le même outil — sachez lequel vous construisez.

Anonyme — POST /tools/:slug/run + GET /tools/runs/:ticketId

Aucun identifiant requis. Utilise le slug de l’outil. Les champs de réponse sont normalisés pour chaque outil vers result.text (plus des métadonnées de tokens/latence), afin que le Tool Portal public puisse afficher n’importe quel outil avec un seul rendu générique. Limité en débit par IP ; voir Erreurs plus bas pour le tableau d’erreurs du chemin anonyme (en particulier, ce chemin nécessite un véritable navigateur — voir la note sur 403).

curl -X POST "https://api.quravin.com/tools/translate/run" \
  -H "Content-Type: application/json" \
  -d '{
    "inputs": { "text": "Hello", "target_language": "de" }
  }'

Réponse (202) :

{ "ticket_id": "01J...ULID", "status": "QUEUED" }

Polling :

curl "https://api.quravin.com/tools/runs/01J...ULID"

Réponse une fois terminé :

{
  "ticket_id": "01J...ULID",
  "status": "DONE",
  "mode": "pipeline",
  "pipeline_id": "translate-string",
  "result": { "text": "Hallo", "model": "gpt-4o-mini", "latency_ms": 812 },
  "created_at": "2026-07-10T12:00:00.000Z",
  "updated_at": "2026-07-10T12:00:02.000Z"
}

Notez result.text, pas result.translation — le chemin anonyme normalise toujours vers text.

Authentifié — POST /tickets + GET /tickets/:id

Nécessite x-api-key: <key> ou Authorization: Bearer <jwt> (voir Choisissez votre mode d’authentification pour savoir comment obtenir l’un ou l’autre). Utilise le pipeline_id, et retourne la forme de sortie brute et non normalisée de chaque pipeline — pour translate-string, c’est translation, pas text. Voir Référence des outils pour le pipeline_id de chaque outil et ses vrais champs de sortie.

Avec une clé API statique :

curl -X POST "https://api.quravin.com/tickets" \
  -H "Content-Type: application/json" \
  -H "x-api-key: <your-api-key>" \
  -d '{
    "mode": "pipeline",
    "pipeline_id": "translate-string",
    "inputs": { "text": "Hello", "target_language": "de" }
  }'

Avec un session JWT (créé via POST /auth/token — voir Choisissez votre mode d’authentification) — corps identique, seul l’en-tête change :

curl -X POST "https://api.quravin.com/tickets" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <your-jwt>" \
  -d '{
    "mode": "pipeline",
    "pipeline_id": "translate-string",
    "inputs": { "text": "Hello", "target_language": "de" }
  }'

Les deux retournent la même réponse (202) :

{ "ticket_id": "01J...ULID", "status": "QUEUED" }

Polling — avec une clé API :

curl "https://api.quravin.com/tickets/01J...ULID" \
  -H "x-api-key: <your-api-key>"

Polling — avec un session JWT :

curl "https://api.quravin.com/tickets/01J...ULID" \
  -H "Authorization: Bearer <your-jwt>"

Réponse une fois terminé :

{
  "ticket_id": "01J...ULID",
  "status": "DONE",
  "mode": "pipeline",
  "pipeline_id": "translate-string",
  "pipeline_version": "v3",
  "result": { "translation": "Hallo", "applied_terms": [] },
  "created_at": "2026-07-10T12:00:00.000Z",
  "updated_at": "2026-07-10T12:00:02.000Z"
}

En cas d’échec :

{
  "ticket_id": "01J...ULID",
  "status": "FAILED",
  "mode": "pipeline",
  "pipeline_id": "translate-string",
  "error": "Ticket failed: <reason>",
  "created_at": "2026-07-10T12:00:00.000Z",
  "updated_at": "2026-07-10T12:00:02.000Z"
}

Annuler un ticket — DELETE /tickets/:id

Demande l’annulation (en écrivant un cancel.flag). Vous ne pouvez annuler qu’un ticket créé avec vos propres identifiants — une clé API avec portée opérateur peut annuler n’importe quel ticket, un appelant JWT/webtoken uniquement le sien (sinon 403).

curl -X DELETE "https://api.quravin.com/tickets/01J...ULID" \
  -H "x-api-key: <your-api-key>"

# or with a session JWT:
curl -X DELETE "https://api.quravin.com/tickets/01J...ULID" \
  -H "Authorization: Bearer <your-jwt>"

Réponse :

{ "ticket_id": "01J...ULID", "status": "CANCELED" }

Un GET /tickets/:id sur un ticket annulé, une fois interrogé, retourne la même forme que DONE/FAILED ci-dessus, avec simplement status: "CANCELED".

Lister vos tickets — GET /tickets

Retourne les tickets rattachés à votre propre identité — un appelant JWT/webtoken récupère toujours ses propres tickets ; seule une clé API à portée opérateur peut passer ?user= / ?app= pour consulter les tickets d’un utilisateur/application arbitraire.

curl "https://api.quravin.com/tickets?limit=20" \
  -H "Authorization: Bearer <your-jwt>"

Paramètres de requête :

ParamètreDéfautNotes
limit50Maximum 1000.
hydratetruefalse retourne uniquement [{ ticket_id }] — économique, évite de récupérer l’état complet de chaque ticket.
userid de l’appelantSeule une clé API à portée opérateur peut passer l’id d’un autre utilisateur.
appapplication de l’appelantSeule une clé API à portée opérateur peut passer l’id d’une autre application.

Réponse :

{ "tickets": [ { "ticket_id": "01J...ULID", "status": "DONE", "...": "..." } ] }

Le point important si vous passez du widget public « essayer » à une véritable intégration : le nom du champ de sortie change, passant de result.text (anonyme) au champ propre au pipeline (authentifié) — pour translate, c’est result.translation.

Erreurs

Les erreurs que vous pouvez obtenir en retour dépendent de la façon dont vous appelez — les appels anonymes et authentifiés sont validés différemment. Ne supposez pas que le comportement d’erreur d’un chemin s’applique à l’autre.

Appel anonyme (sans clé API)

Tous les outils publiés, avec limitation de débit par IP.

StatutCode d’erreurSignification
404Slug d’outil inconnu.
403Échec de la vérification anti-bot. Ce chemin nécessite un véritable contexte navigateur capable d’exécuter un challenge Cloudflare Turnstile — il retourne toujours 403 depuis un serveur, curl, ou un script headless sans token Turnstile. Utilisez un chemin authentifié (clé API ou session JWT) pour les appels serveur à serveur.
429guest_quota_exceededVous avez atteint la limite quotidienne de requêtes anonymes, par IP ou globale.
429guest_cost_exceededVous avez atteint la limite quotidienne de coût anonyme, par IP ou globale (les outils coûteux épuisent ce budget plus vite).
202Succès — { ticket_id, status: "QUEUED" }. Effectuez le polling du résultat comme pour tout autre appel.

Important : sur ce chemin, vos inputs ne sont pas vérifiées par rapport au schéma de chaque outil avant que l’appel soit accepté — un champ obligatoire manquant ou une valeur invalide ne vous vaut pas une erreur 400. C’est l’exécution elle-même qui échouera à la place, ce que vous verrez en faisant le polling du résultat. Une fois que vous avez une clé API, passez au chemin authentifié ci-dessous, qui valide en amont.

Appel avec une clé API ou un token de session

L’un quelconque des outils auxquels vous avez accès.

StatutCode d’erreurSignification
401Identifiants manquants, invalides, ou expirés.
403app_disabledVotre application a été désactivée.
403org_suspendedVotre organisation a été suspendue.
403org_pending_deletionVotre organisation est programmée pour suppression.
403Cet outil ne fait pas partie de la liste des pipelines autorisés de vos identifiants.
403origin_not_allowedVotre Application a un allowed_origins configuré et l’en-tête Origin de la requête ne correspond exactement à aucun d’eux. S’applique uniquement si vous (ou l’administrateur de votre organisation) avez choisi cette option dans la Console — les applications qui ne la configurent jamais ne sont pas concernées.
400Vos inputs ne correspondent pas aux exigences de l’outil (champ obligatoire manquant, valeur invalide, ou entrée totale trop volumineuse). Ce chemin valide bien en amont, contrairement au chemin anonyme.
429rate_limitedVous envoyez des requêtes plus vite que votre limite par minute — une fenêtre fixe d’une minute par organisation, pas une fenêtre glissante. Comme elle est fixe plutôt que glissante, une rafale peut brièvement dépasser le plafond nominal jusqu’à environ 2x juste à la frontière d’une minute (par exemple une rafale à 12:00:59 suivie d’une autre à 12:01:00) ; patientez et réessayez plutôt que de supposer un plafond exact.
429quota_exceededVous avez atteint votre quota mensuel de requêtes.
429daily_cost_exceededVous avez atteint un plafond de coût quotidien — une limite de vélocité, distincte du fait de manquer de solde ci-dessous.
402insufficient_creditsLe solde de crédits de votre organisation est trop faible pour couvrir cet appel. Rechargez et réessayez. Remarque : une x-api-key nue n’a aucun portefeuille d’organisation, donc un appel facturable en facturation par portefeuille retourne cette erreur même avec des fonds disponibles — passez à un session JWT (voir Choisissez votre mode d’authentification).
202Succès — { ticket_id, status: "QUEUED" }. Effectuez le polling du résultat.

Pour en savoir plus sur le fonctionnement des identifiants et des décisions d’authentification 401/403/429, voir Choisissez votre mode d’authentification. Pour les entrées, valeurs valides et forme de sortie de chaque outil, voir Référence des outils.