Savoir-faire
Appeler l’API Vellria depuis votre code
Quatre appels suffisent pour passer de rien à un fichier terminé : créer une clé, lancer une génération, interroger l’enregistrement, télécharger le résultat. Voici cette boucle de bout en bout, avec l’erreur que chaque étape peut renvoyer et la réaction qu’un client doit y opposer.
Créer une clé et l’envoyer comme jeton Bearer
Les clés se créent dans la Console, sur la page Clés API, et cette réponse-là est le seul endroit où la clé complète s’affiche : toute lecture ultérieure de votre liste renvoie un identifiant, un nom et un préfixe, plus jamais le secret. Enregistrez-la sur-le-champ. Le préfixe existe pour qu’une ligne de journal puisse désigner une clé sans la contenir.
Envoyez-la dans l’en-tête Authorization de chaque requête, sous la forme du mot Bearer suivi de la clé. Aucune variante dans l’URL n’est prévue : la RFC 6750 range parmi ses recommandations que « Bearer tokens SHOULD NOT be passed in page URLs (for example, as query string parameters) », parce que les URL porteuses de jetons finissent dans l’historique du navigateur, les en-têtes Referer et les journaux des serveurs.
Sans clé, ou avec une clé révoquée, la réponse est un 401 avec authentication_required pour code. Toutes les erreurs partagent le même corps, un objet error qui contient message, type, param et code. Aiguillez votre logique sur code : message est rédigé pour un humain, et sa formulation changera.
Lire le catalogue au lieu de le coder en dur
GET /v1/models renvoie deux listes, image et video. Une entrée d’image énumère les tailles acceptées, le coût de chacune en crédits et une taille par défaut ; une entrée vidéo porte des résolutions avec un coût par seconde, une durée plancher et une durée plafond, ainsi que les éventuels formats d’image acceptés. Une liste de tailles appartient à un modèle et non au catalogue, tout comme une valeur par défaut : aucune valeur unique ne s’applique partout sans risque. Les pages du catalogue des modèles et des tarifs interrogent ce même endpoint.
Le champ kind indique la forme de corps qu’attend un modèle : texte en image, image en image, texte en vidéo, image en vidéo. Appuyez-vous sur lui plutôt que de déduire la même chose de la présence d’un objet d’image de référence, une déduction juste aujourd’hui et discrètement fausse pour tout ce qui sera ajouté ensuite. Un modèle inconnu, une taille invalide ou une durée hors plage reviennent en 400, avec param qui nomme le champ, et une durée hors plage est rejetée plutôt que rognée pour tenir.
Lancer le rendu, puis interroger l’enregistrement
Envoyez un POST à /v1/generate/image ou /v1/generate/video en indiquant l’identifiant du modèle, votre prompt et les réglages admis par ce modèle. La réponse est un 202 qui porte un id, le statut processing et le nombre de crédits tout juste débités : les crédits partent au début d’un rendu, pas à la fin. Interrogez ensuite GET /v1/generations/{id} à intervalles ; dès que status ne vaut plus queued ni processing, le rendu est fini. Une fois l’enregistrement à completed, son output_url pointe vers notre endpoint de fichiers ; un enregistrement en échec porte error_code, une valeur tirée d’un ensemble fermé, à côté de error, un bref résumé destiné à vos journaux.
Espacez vos interrogations et glissez du hasard dans ce délai. L’expérience de Marc Brooker sur le backoff, publiée sur l’AWS Architecture Blog, simule des clients qui se disputent une même ligne et rapporte qu’avec du jitter, « in the case with 100 contending clients, we've reduced our call count by more than half ». Interroger vous apprend l’issue, cela ne la provoque pas : les rendus sont réconciliés côté serveur, que quelqu’un observe ou non, si bien qu’une connexion coupée vous fait perdre la main sur le résultat, pas le résultat lui-même.
Toute la boucle dans un seul fichier
Enregistrez ceci dans un fichier .mjs, définissez VELLRIA_API_KEY et exécutez-le avec une version récente de Node. Aucun identifiant de modèle, aucune taille ni aucun coût n’y est codé en dur.
```js import { writeFile } from "node:fs/promises"; const BASE = "https://vellria.com"; const AUTH = { Authorization: `Bearer ${process.env.VELLRIA_API_KEY}` }; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function call(path, init) { const res = await fetch(BASE + path, { ...init, headers: { ...AUTH, ...init?.headers } }); if (res.status === 429) { await sleep(Number(res.headers.get("retry-after") ?? 5) * 1000); return call(path, init); } if (!res.ok) throw new Error((await res.json().catch(() => null))?.error?.code ?? res.status); return res.json(); } const catalog = await call("/v1/models"); const model = catalog.image.find((m) => m.kind === "text-to-image"); let rec = await call("/v1/generate/image", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: model.id, prompt: "a lighthouse in fog", size: model.default_size }), }); while (rec.status === "queued" || rec.status === "processing") { await sleep(2000 + Math.random() * 2000); rec = await call(`/v1/generations/${rec.id}`); } if (rec.status !== "completed") throw new Error(rec.error_code ?? "generation_failed"); const file = await fetch(rec.output_url, { headers: AUTH }); await writeFile(`${rec.id}.png`, Buffer.from(await file.arrayBuffer())); ```
Pour un modèle qui accepte des images, envoyez d’abord le fichier en POST à /v1/uploads avec un en-tête Content-Type conforme aux octets, puis passez l’adresse obtenue dans image_urls ; transformer une image en vidéo donne les formats acceptés et la liste des rejets. Qu’un modèle veuille des images est une propriété du modèle, et un corps de la mauvaise forme est refusé plutôt qu’ajusté en silence, ce que garder un personnage cohérent développe. Les corps vidéo portent duration et resolution au lieu de size, sujet de choisir résolution et durée. Chaque identifiant de modèle vidéo, avec son type et son prix à la seconde, figure dans un tableau unique sur la page de l’API vidéo NSFW.
Échecs, contre-pression et expiration
Quand un rendu échoue après son lancement, ses crédits retournent d’eux-mêmes sur le solde dès que l’enregistrement passe à failed ; l’historique conserve les deux écritures, l’échec et la contrepassation. Tenez failed pour un état final et soldé : lisez error_code pour connaître la raison, et ne recalculez jamais les soldes de votre côté. Le remboursement est écrit sous une condition qu’un seul rédacteur peut satisfaire : une notification du prestataire livrée deux fois ne peut ni vous créditer en double, ni vous oublier.
Franchir une limite de débit renvoie 429 avec le code rate_limit_exceeded et un en-tête Retry-After. La RFC 6585 définit ce statut : « the 429 status code indicates that the user has sent too many requests in a given amount of time ». La RFC 9110, section 10.2.3, définit l’en-tête comme celui qui « indicates how long the user agent ought to wait before making a follow-up request ». Lisez-le au lieu de deviner, et voyez storyboard et prévisualisation pour ce que cela implique quand vous lancez un lot. Un solde insuffisant donne 402, et aucun enregistrement n’est créé. Le détail complet figure dans la référence de l’API.
Questions fréquentes
Faut-il continuer d’interroger l’API jusqu’à la fin d’une génération ?
Non. L’interrogation sert à savoir, pas à faire avancer le travail. Les rendus sont réconciliés de notre côté, qu’un client soit connecté ou non : une tâche lancée par un processus qui s’est arrêté ensuite atteint quand même completed ou failed ; récupérez-la plus tard grâce à son id.
Puis-je appeler l’API depuis du code exécuté dans le navigateur ?
Pas avec votre clé. Une clé dépense des crédits et ne peut pas être restreinte : tout ce que vous livrez à un navigateur est une clé rendue publique. Placez les appels derrière votre propre serveur, gardez la clé dans son environnement, et laissez votre interface dialoguer avec lui.
Puis-je coder en dur identifiants de modèle, tailles et coûts ?
Vous le pouvez, et ils finiront par diverger. L’endpoint du catalogue alimente les pages de modèle et sert de base à la facturation du serveur : une valeur recopiée cesse de correspondre le jour où le catalogue change. Récupérez-le au démarrage et gardez-le en cache pour toute la durée de vie du processus.
