adkixDéveloppeurs
API v1 · OpenAPI 3.1 · MCP

Ton IA fait la vidéo,
tu valides.

adkix transforme un site en vidéo courte de marque, étape par étape. Avec l'API, c'est ton IA qui conduit : Claude, ChatGPT, Cursor ou ton propre code. adkix propose, la personne valide chaque étape et dit oui à chaque prix.

Base URLhttps://adkix.ai/api/v1
Créer une vidéo
curl https://adkix.ai/api/v1/contents \
  -H "Authorization: Bearer $ADKIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "source": { "url": "https://maisonlumen.fr" } }'
Démarrer

#Démarrage en 3 minutes

  1. Crée une clé

    Sur adkix.ai/compte, section API, crée une clé. Elle commence par adk_live_ et n'est montrée qu'une fois : garde-la dans une variable d'environnement, jamais dans ton code.

    Terminal
    export ADKIX_API_KEY="adk_live_..."   # la clé, montrée une seule fois
  2. Vérifie le compte

    GET /me répond avec ton solde de crédits et l'état de la vidéo de bienvenue. Un 401 veut dire que la clé est fausse ou révoquée.

    GET /me
    curl https://adkix.ai/api/v1/me \
      -H "Authorization: Bearer $ADKIX_API_KEY"
  3. Crée un contenu

    Un site, un @compte Instagram ou une idée. adkix lit la marque tout de suite ; rien de payant ne part. Garde le contentId.

    POST /contents
    curl https://adkix.ai/api/v1/contents \
      -H "Authorization: Bearer $ADKIX_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "source": { "url": "https://maisonlumen.fr" } }'
  4. Suis l'état

    GET /contents/{id}?waitSec=25 attend qu'il se passe quelque chose. Lis step, montre les previews, et propose suggested : le prochain clic qu'adkix ferait.

    GET /contents/{id}
    curl "https://adkix.ai/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19?waitSec=25" \
      -H "Authorization: Bearer $ADKIX_API_KEY"
  5. Agis, reçois un devis

    Une fois la personne d'accord, lance l'action. Si elle est payante, la réponse est un devis : { quote: { proposalId, price, text } }. Rien n'est dépensé.

    POST /contents/{id}/actions
    curl https://adkix.ai/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19/actions \
      -H "Authorization: Bearer $ADKIX_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "action": "approve_script", "args": {} }'
  6. Confirme après le oui, récupère le film

    Montre quote.text, et seulement après un oui explicite, confirme. Le tournage prend 15 à 20 minutes ; à la fin, video.shareUrl ouvre le film dans n'importe quel navigateur.

    POST /contents/{id}/confirm
    curl https://adkix.ai/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19/confirm \
      -H "Authorization: Bearer $ADKIX_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "proposalId": "p-7c41e9a02b6d" }'
Démarrer

#Le principe

Un contenu, c'est un projet vidéo. Il avance sur un rail d'étapes, de la marque au film monté. Ton IA conduit, adkix propose, et la personne garde la main sur chaque décision et chaque crédit.

01Marquemarque

adkix lit le site : nom, promesse, couleurs, typos.

02Élémentselements

Produits ou lieux à montrer, phrase imposée.

03Formatformat

Plateformes, langue, durée.

04Angleangle

Trois idées d'histoire, la personne en choisit une.

devis
05Stylemedium

Tournage, UGC, motion design, dessin animé, pâte à modeler…

06Narrationnarration

Voix off, histoire, fondateur, dialogue…

07Optionsoptions

Titres, logo, appel à l'action.

08Lookstyle

Quatre ambiances dessinées, une gardée.

09Castingcasting

Personnages, lieux, objets dessinés.

10Scriptscript

Les phrases scène par scène.

11Voix offvoix

Choix de la voix, lecture de chaque phrase.

12Qualitéqualite

Le modèle vidéo, qui fixe le prix du tournage.

13Tournagetournage

Chaque scène filmée par l'IA, 15 à 20 minutes.

devis
14Sonson

Musique, bruitages.

15Reelreel

Montage, exports, sous-titres.

1GETTon IA lit l'état

step, previews, actions

2suggestedadkix propose

jamais exécuté par le serveur

3humainLa personne valide

chaque étape, chaque choix

4POSTTon IA agit

gratuit : fait · payant : devis

5ouiConfirm

le seul appel qui dépense

on recommence, jusqu'au film

#Les devis

Toute action payante répond d'abord par un devis : proposalId, action, price en crédits, text à lire à la personne, free (vidéo de bienvenue, rien de débité) et expiresInSec: 600.

Seul POST /contents/{id}/confirm avec ce proposalId (ou l'action confirm) dépense. Un devis non confirmé expire au bout de 10 minutes ; il apparaît aussi dans le fil du site et dans state.pendingQuote.

Certaines actions gratuites qui défont du travail (changer la narration après le script, retirer un personnage…) répondent elles aussi par un devis à 0 crédit, avec l'avertissement dans text.

#Le tournage

Après la confirmation de approve_script, chaque scène est filmée par l'IA : comptez 15 à 20 minutes pour 30 secondes. Ne bloque jamais la conversation : suis l'avancement avec waitSec et donne des nouvelles.

Réponse d'une action payante
{
  "quote": {
    "proposalId": "p-7c41e9a02b6d",
    "action": "approve_script",
    "price": 1180,
    "text": "Tourner coûte environ 1180 crédits, débités au coût réel au fil des étapes.",
    "free": false,
    "expiresInSec": 600
  }
}
La règle d'or
Ton IA ne confirme jamais un devis sans un oui explicite de la personne sur ce prix exact. Le serveur n'exécute jamais suggested tout seul.
Démarrer

#Authentification

Chaque requête porte la clé dans l'en-tête Authorization: Bearer adk_live_…. C'est la seule méthode : pas de cookie, pas de paramètre d'URL.

  • 5 clés actives au plus par compte, créées et révoquées sur /compte. Révoquer prend effet tout de suite.
  • Changer de mot de passe invalide toutes les clés (et leurs liens signés).
  • La clé agit avec les droits du compte, équipes comprises ; jamais avec des droits d'administration.
  • L'API ne répond à aucune requête CORS : appelle-la depuis un serveur, un terminal ou un agent, jamais depuis une page web.
  • Créer une clé demande une adresse email confirmée.
Garde la clé secrète
Elle n'est montrée qu'une fois. Range-la dans ADKIX_API_KEY ou ~/.config/adkix/key (chmod 600). Si elle fuit, révoque-la et crée-en une autre.
En-tête
Authorization: Bearer adk_live_ab12cd34_••••••••••••
401 Unauthorized
{
  "error": "clé invalide ou révoquée",
  "code": "auth"
}
Référence

#Endpoints

Toutes les routes vivent sous https://adkix.ai/api/v1, répondent en JSON (sauf les fichiers) avec Cache-Control: no-store, et une erreur a toujours la forme { error, code }.

#Inscrire depuis l'IA

POST/api/v1/auth/start
startSignInPublic, sans clé

Pas encore de clé : ton IA demande l'email de la personne et l'envoie ici. Un code à 6 chiffres part par mail. Même chemin pour un nouveau compte et un compte existant ; rien n'est créé avant le code.

  • Le code vit 15 minutes et ne sert qu'une fois. 3 mails par adresse et par heure (429 au-delà).
Paramètres
  • emailstringbodyrequis

    L'adresse de la personne.

  • namestringbody

    Son prénom, pour un nouveau compte.

  • langfr | en | esbody

    Langue du mail et du compte.

Requête
curl -X POST "https://adkix.ai/api/v1/auth/start" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "name": "Camille",
    "lang": "fr"
  }'
200 OK
{
  "requestId": "Qm9uam91ci1hZGtpeC0yMDI2",
  "email": "[email protected]",
  "expiresInSec": 900
}

#Échanger le code contre une clé

POST/api/v1/auth/verify
verifySignInPublic, sans clé

La personne donne le code reçu à ton IA. Le compte est créé s'il n'existe pas (email vérifié, crédits de bienvenue), puis une clé API est rendue, une seule fois.

  • 5 mauvais codes tuent la demande (401 expired : recommence /auth/start). 5 clés actives au plus (409 too_many_keys).
  • Range la clé dans un fichier privé, jamais dans la conversation.
Paramètres
  • requestIdstringbodyrequis

    Rendu par /auth/start.

  • codestringbodyrequis

    Les 6 chiffres du mail.

  • keyNamestringbody

    Nom de la clé sur /compte (Claude Code par défaut).

Requête
curl -X POST "https://adkix.ai/api/v1/auth/verify" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "Qm9uam91ci1hZGtpeC0yMDI2",
    "code": "482913"
  }'
201 Created
{
  "key": "adk_live_…",
  "created": true,
  "email": "[email protected]",
  "balance": 1000
}

#Lire le compte

GET/api/v1/me
getAccountBearer requislecture · 120 / min

Le compte de la clé : nom, solde de crédits, offre, et si la vidéo de bienvenue est encore disponible. Appelle-le en premier pour vérifier que la clé fonctionne.

  • freeVideo vaut available, verify_email (adresse à confirmer d'abord), used ou unavailable. available = createContent accepte offer: "free".
Paramètres

Aucun paramètre.

Requête
curl "https://adkix.ai/api/v1/me" \
  -H "Authorization: Bearer $ADKIX_API_KEY"
200 OK
{
  "id": "u_4d9a61f0",
  "name": "Camille Rey",
  "balance": 2400,
  "plan": "Studio",
  "freeVideo": "used"
}

#Lister les contenus

GET/api/v1/contents
listContentsBearer requislecture · 120 / min

Les 50 contenus les plus récents du compte et des projets d'équipe partagés avec lui, du plus récent au plus ancien.

  • thumbUrl est relatif et demande le Bearer. step est l'étape du rail, le même mot que dans l'état.
Paramètres

Aucun paramètre.

Requête
curl "https://adkix.ai/api/v1/contents" \
  -H "Authorization: Bearer $ADKIX_API_KEY"
200 OK
{
  "contents": [
    {
      "id": "3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19",
      "projectId": "b71e04c2-9a3d-4f65-8c20-6e1f5d7a3b48",
      "title": "Maison Lumen",
      "status": "script_review",
      "step": "script",
      "createdAt": "2026-10-20T08:12:44.120Z",
      "thumbUrl": "/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19/files/frames/1",
      "videoReady": false
    }
  ]
}

#Créer un contenu

POST/api/v1/contents
createContentBearer requiscréation · 10 / jour

Démarre un projet vidéo à partir d'un site, d'un compte @instagram ou d'une idée, et lit la marque sur le site. Rien de coûteux ne part ici : chaque étape payante répondra plus tard par un devis.

  • Le brief ne lance rien : il pré-remplit state.suggested (format, langue, durée, note pour les angles).
  • offer: "free" utilise la vidéo de bienvenue du compte et demande source.url ; un refus répond free_refused avec reason.
  • Limite : 10 créations par jour et par clé, 20 par compte.
Paramètres
  • source.urlstring ≤ 500body

    Un site (https://…) ou un compte @instagram. source.url ou source.idea est requis.

  • source.ideastring ≤ 1000body

    Une idée en texte libre, sans site. Elle part dans la note du brief.

  • offer"paid" | "free"body

    paid par défaut. free = la vidéo de bienvenue (source.url requis).

  • igConsentbooleanbody

    true quand la personne accepte qu'on lise ses posts Instagram publics.

  • brief.duration15 | 30 | 60body

    Durée en secondes (6, 12 ou 24 scènes).

  • brief.languagestringbody

    Langue de la voix off : fr, en, es, de, it, pt…

  • brief.platformsstring[]body

    instagram, tiktok, shorts, youtube… Les inconnues sont ignorées.

  • brief.notestring ≤ 2000body

    Brief libre : offre, public, ton, ce qui doit être dit.

Requête
curl -X POST "https://adkix.ai/api/v1/contents" \
  -H "Authorization: Bearer $ADKIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": {
      "url": "https://maisonlumen.fr"
    },
    "brief": {
      "duration": 30,
      "language": "fr",
      "platforms": [
        "instagram",
        "tiktok"
      ],
      "note": "Lancement de la collection d'hiver, ton chaleureux."
    }
  }'
201 Created
{
  "contentId": "3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19",
  "projectId": "b71e04c2-9a3d-4f65-8c20-6e1f5d7a3b48",
  "webUrl": "https://adkix.ai/projets/b71e04c2-9a3d-4f65-8c20-6e1f5d7a3b48?job=3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19",
  "state": {
    "id": "3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19",
    "status": "ingest",
    "step": "marque",
    "busy": {
      "label": "Lecture du site"
    },
    "suggested": null,
    "pollAfterSec": 10,
    "…": "…"
  }
}

#Lire l'état d'un contenu

GET/api/v1/contents/{id}
getContentBearer requislecture · 120 / min

La source de vérité : étape, progression, actions permises, suggestion d'adkix, aperçus, film et devis en attente. Avec waitSec, la réponse attend qu'un changement arrive.

  • waitSec (0 à 25) tient la requête ouverte et répond dès que l'étape, la tâche en cours, une image, un clip, une voix, le film ou une erreur bouge.
  • L'en-tête x-adkix-waited vaut changed, timeout ou none (pas d'attente demandée).
Paramètres
  • idstringpathrequis

    Identifiant du contenu (contentId de createContent, ou id de listContents).

  • waitSecinteger 0–25query

    Attente longue (long poll). Remplace les sleep entre deux appels.

Requête
curl "https://adkix.ai/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19?waitSec=25" \
  -H "Authorization: Bearer $ADKIX_API_KEY"
200 OK · x-adkix-waited: changed
{
  "id": "3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19",
  "projectId": "b71e04c2-9a3d-4f65-8c20-6e1f5d7a3b48",
  "webUrl": "https://adkix.ai/projets/b71e04c2-9a3d-4f65-8c20-6e1f5d7a3b48?job=3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19",
  "title": "Maison Lumen",
  "status": "script_review",
  "step": "script",
  "rail": {
    "marque": "done",
    "elements": "done",
    "format": "done",
    "angle": "done",
    "medium": "done",
    "narration": "done",
    "options": "done",
    "style": "done",
    "casting": "done",
    "script": "active",
    "voix": "todo",
    "qualite": "todo",
    "tournage": "todo",
    "son": "todo",
    "reel": "todo"
  },
  "busy": null,
  "progress": {
    "pct": 48,
    "label": "Script à valider",
    "waiting": true
  },
  "error": null,
  "free": false,
  "balance": 2400,
  "context": {
    "brand": {
      "name": "Maison Lumen",
      "promise": "Des bougies coulées à la main à Lyon"
    },
    "format": {
      "platforms": [
        "instagram",
        "tiktok"
      ],
      "language": "fr",
      "duration": 30
    },
    "angle": "La bougie qu'on rallume chaque soir",
    "style": "Tournage, lumière chaude",
    "script": {
      "phrases": 9,
      "words": 78
    }
  },
  "actions": [
    {
      "name": "open_voice",
      "hint": "{} (the script is validated: on to picking and hearing the voice-over, free)"
    },
    {
      "name": "edit_phrase",
      "hint": "{ \"phraseId\": number, \"text\"?: string, \"highlight\"?: string }"
    },
    {
      "name": "rewrite_script",
      "hint": "{ \"note\": string }"
    }
  ],
  "suggested": {
    "action": "open_voice",
    "args": {},
    "price": 0
  },
  "previews": [
    {
      "kind": "frame",
      "label": "Plan 1",
      "url": "/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19/files/frames/1",
      "shareUrl": "https://adkix.ai/api/v1/links/eyJ1IjoiNGQ5YSIsImsiOiJrXzhmMmEiLCJqIjoiM2Y5YyJ9.Xk2vQ7tLwz…"
    }
  ],
  "video": null,
  "pendingQuote": null,
  "notice": null,
  "pollAfterSec": 30
}

#Lancer une action

POST/api/v1/contents/{id}/actions
runActionBearer requisaction · 30 / min

Exécute une action de l'étape en cours (les noms sont dans state.actions). Une action gratuite part tout de suite ; une action payante ne dépense rien et répond par un devis.

  • Réponse : soit { ok, note } (fait), soit { quote } à montrer à la personne. Ne confirme qu'après un oui explicite.
  • action: "cancel" + proposalId retire un devis. action: "confirm" + proposalId équivaut à confirmQuote.
  • Un nom inconnu répond 400 bad_action avec validActions ; set_mode, edit_scene, revert_scene_edit, set_cast_photo et derive_reel répondent 400 not_in_api.
Paramètres
  • idstringpathrequis

    Identifiant du contenu (contentId de createContent, ou id de listContents).

  • actionstringbodyrequis

    Un nom de state.actions, ou cancel.

  • argsobjectbody

    Arguments, de la forme que donne le hint de l'action.

  • proposalIdstringbody

    Pour cancel (ou confirm) : le devis visé.

Requête
curl -X POST "https://adkix.ai/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19/actions" \
  -H "Authorization: Bearer $ADKIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "approve_script",
    "args": {}
  }'
200 OK
{
  "quote": {
    "proposalId": "p-7c41e9a02b6d",
    "action": "approve_script",
    "price": 1180,
    "text": "Tourner coûte environ 1180 crédits, débités au coût réel au fil des étapes.",
    "free": false,
    "expiresInSec": 600
  }
}

Une action payante répond par un devis. Une action gratuite répond { "ok": true, "note": "…" }.

#Accepter un devis

POST/api/v1/contents/{id}/confirm
confirmQuoteBearer requisaction · 30 / min

Accepte un devis et dépense les crédits. À n'appeler qu'après le oui de la personne sur ce prix exact. C'est le seul appel qui débite.

  • Un devis vit 10 minutes. Expiré ou remplacé : 409 proposalExpired / proposalSuperseded, redemande un devis.
  • Solde insuffisant : 402 notEnoughCredits avec credits.need et credits.balance.
  • Route dédiée pour qu'un GPT la marque « conséquente » : ChatGPT demande toujours avant de l'appeler.
Paramètres
  • idstringpathrequis

    Identifiant du contenu (contentId de createContent, ou id de listContents).

  • proposalIdstringbodyrequis

    quote.proposalId de runAction, ou state.pendingQuote.proposalId.

Requête
curl -X POST "https://adkix.ai/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19/confirm" \
  -H "Authorization: Bearer $ADKIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "proposalId": "p-7c41e9a02b6d"
  }'
200 OK
{
  "ok": true,
  "note": "Tournage lancé : 12 scènes, environ 15 à 20 minutes."
}

#Télécharger un aperçu

GET/api/v1/contents/{id}/files/{kind}/{name}
getContentFileBearer requislecture · 120 / min

Une image, un clip ou un son listé dans state.previews (son champ url). Demande le Bearer ; pour un humain, donne plutôt le shareUrl.

Paramètres
  • idstringpathrequis

    Identifiant du contenu (contentId de createContent, ou id de listContents).

  • kindmoodboard | frames | videos | audiopathrequis

    Famille de l'aperçu.

  • namestringpathrequis

    Dernier segment de l'url de l'aperçu : 1, option-2, cast-a, phrase-3…

Requête
curl "https://adkix.ai/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19/files/frames/1" \
  -H "Authorization: Bearer $ADKIX_API_KEY" \
  -o plan-1.png

Le fichier : image/png, image/jpeg, video/mp4 ou audio/mpeg.

#Télécharger le film

GET/api/v1/contents/{id}/video
getContentVideoBearer requislecture · 120 / min

Le film fini en MP4, en téléchargement ou en lecture continue. Les requêtes Range sont acceptées (réponse 206).

Paramètres
  • idstringpathrequis

    Identifiant du contenu (contentId de createContent, ou id de listContents).

  • aspect"16:9"query

    La version horizontale, quand state.video.aspects la contient.

  • download"1"query

    Ajoute Content-Disposition: attachment.

  • Rangebytes=…header

    Lecture partielle, réponse 206.

Requête
curl "https://adkix.ai/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19/video?download=1" \
  -H "Authorization: Bearer $ADKIX_API_KEY" \
  -o maison-lumen.mp4

video/mp4 (200, ou 206 avec Range).

#Télécharger un export

GET/api/v1/contents/{id}/export/{kind}
getContentExportBearer requislecture · 120 / min

Les exports du film fini : sous-titres, couverture, bande son, ou version sans titres incrustés.

Paramètres
  • idstringpathrequis

    Identifiant du contenu (contentId de createContent, ou id de listContents).

  • kindsrt | cover | audio | cleanpathrequis

    srt = sous-titres, cover = image de couverture, audio = bande son, clean = film sans titres.

  • aspect"16:9"query

    L'export de la version horizontale.

Requête
curl "https://adkix.ai/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19/export/srt" \
  -H "Authorization: Bearer $ADKIX_API_KEY" \
  -o maison-lumen.srt

Le fichier de l'export.

GET/api/v1/links/{token}
openSignedLinkPublic, sans clé

Les shareUrl et downloadUrl de l'état. Ils s'ouvrent dans n'importe quel navigateur, sans Bearer, pendant 24 h : c'est ce que ton IA donne à la personne.

  • Lien expiré, clé révoquée ou mot de passe changé : 404 link_invalid. Relis l'état pour un lien neuf.
Paramètres
  • tokenstringpathrequis

    Le jeton d'un shareUrl / downloadUrl.

Requête
curl "https://adkix.ai/api/v1/links/eyJ1IjoiNGQ5YSIsImsiOiJrXzhmMmEiLCJqIjoiM2Y5YyJ9.Xk2vQ7tLwz…" \
  -o film.mp4

Le fichier, avec Range pour le film.

#Spécification OpenAPI

GET/api/v1/openapi.json
getOpenApiSpecPublic, sans clé

Cette API décrite en OpenAPI 3.1, publique et lisible depuis n'importe quelle origine : Postman, Swagger, générateurs de SDK, ou l'import d'un GPT.

  • ?profile=gpt donne le profil Actions d'un GPT : les opérations JSON seulement, confirmQuote marquée conséquente.
Paramètres
  • profilefull | gptquery

    full par défaut.

Requête
curl "https://adkix.ai/api/v1/openapi.json?profile=gpt"
200 OK
{
  "openapi": "3.1.0",
  "info": {
    "title": "adkix API",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://adkix.ai/api/v1"
    }
  ],
  "paths": {
    "/me": {},
    "/contents": {},
    "…": {}
  }
}
Référence

#L'objet état

GET /contents/{id} renvoie tout ce qu'il faut pour décider de la suite. Lis step et busy d'abord, puis actions et suggested.

ChampTypeDescription
idstringIdentifiant du contenu.
projectIdstring | nullLe projet (la marque) qui le range.
webUrlstringLe contenu dans l'atelier adkix : la personne peut tout reprendre là.
titlestring | nullTitre (nom du site au début).
statusJobStatusbrief, ingest, moodboard, moodboard_review, script, script_review, frames, render, done, error.
stepStepL'étape du rail où en est le contenu. C'est elle qui compte, pas status.
railRecord<Step, State>Chaque étape : todo, active, done ou error. L'ordre peut varier selon le parcours Style : fais confiance à step.
busy{ label } | nullUne tâche tourne (« Lecture du site », « Tournage »…) : attends avec waitSec.
progress{ pct, label, waiting }0–100, ne recule jamais ; waiting = la personne doit valider.
error{ step, message } | nullUne étape en échec. relaunch ou retry_task la relancent.
freebooleanVidéo de bienvenue : les devis affichent un prix mais rien n'est débité.
balancenumberCrédits restants du compte (ou du siège d'équipe).
contextobjectCe qui est décidé : marque, format, angle, style, casting, script, voix… Jamais un chemin disque ni une URL de fournisseur.
actions{ name, hint }[]Les actions permises maintenant, avec la forme de leurs arguments.
suggested{ action, args, price } | nullLe prochain clic qu'adkix ferait. Jamais exécuté par le serveur : ton IA le propose, la personne décide.
previewsPreview[]Images, clips, voix, musiques à montrer : kind, label, url (Bearer) et shareUrl (24 h, sans Bearer).
videoobject | nullLe film fini : ready, url, aspects, shareUrl, downloadUrl, linkExpiresInSec.
pendingQuoteQuote | nullUn devis qui attend un oui (moins de 10 minutes), y compris créé depuis le site.
notice{ kind, text, at } | nullLe dernier message d'erreur du fil (un site qui ne répond pas…), quand error reste vide.
pollAfterSecnumber10 pendant une tâche, 30 sinon : le rythme conseillé sans waitSec.
Un contenu à l'étape script
{
  "id": "3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19",
  "projectId": "b71e04c2-9a3d-4f65-8c20-6e1f5d7a3b48",
  "webUrl": "https://adkix.ai/projets/b71e04c2-9a3d-4f65-8c20-6e1f5d7a3b48?job=3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19",
  "title": "Maison Lumen",
  "status": "script_review",
  "step": "script",
  "rail": {
    "marque": "done",
    "elements": "done",
    "format": "done",
    "angle": "done",
    "medium": "done",
    "narration": "done",
    "options": "done",
    "style": "done",
    "casting": "done",
    "script": "active",
    "voix": "todo",
    "qualite": "todo",
    "tournage": "todo",
    "son": "todo",
    "reel": "todo"
  },
  "busy": null,
  "progress": {
    "pct": 48,
    "label": "Script à valider",
    "waiting": true
  },
  "error": null,
  "free": false,
  "balance": 2400,
  "context": {
    "brand": {
      "name": "Maison Lumen",
      "promise": "Des bougies coulées à la main à Lyon"
    },
    "format": {
      "platforms": [
        "instagram",
        "tiktok"
      ],
      "language": "fr",
      "duration": 30
    },
    "angle": "La bougie qu'on rallume chaque soir",
    "style": "Tournage, lumière chaude",
    "script": {
      "phrases": 9,
      "words": 78
    }
  },
  "actions": [
    {
      "name": "open_voice",
      "hint": "{} (the script is validated: on to picking and hearing the voice-over, free)"
    },
    {
      "name": "edit_phrase",
      "hint": "{ \"phraseId\": number, \"text\"?: string, \"highlight\"?: string }"
    },
    {
      "name": "rewrite_script",
      "hint": "{ \"note\": string }"
    }
  ],
  "suggested": {
    "action": "open_voice",
    "args": {},
    "price": 0
  },
  "previews": [
    {
      "kind": "frame",
      "label": "Plan 1",
      "url": "/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19/files/frames/1",
      "shareUrl": "https://adkix.ai/api/v1/links/eyJ1IjoiNGQ5YSIsImsiOiJrXzhmMmEiLCJqIjoiM2Y5YyJ9.Xk2vQ7tLwz…"
    }
  ],
  "video": null,
  "pendingQuote": null,
  "notice": null,
  "pollAfterSec": 30
}
state.video, une fois le film prêt
{
  "video": {
    "ready": true,
    "url": "/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19/video",
    "aspects": [
      "9:16",
      "16:9"
    ],
    "shareUrl": "https://adkix.ai/api/v1/links/eyJ1IjoiNGQ5YSIsImsiOiJrXzhmMmEiLCJqIjoiM2Y5YyJ9.Xk2vQ7tLwz…",
    "downloadUrl": "https://adkix.ai/api/v1/links/eyJ1IjoiNGQ5YSIsImsiOiJrXzhmMmEiLCJqIjoiM2Y5YyJ9.Xk2vQ7tLwz…dl",
    "linkExpiresInSec": 86400
  }
}
Référence

#Actions

Les actions sont les mêmes que celles de l'assistant du site. Celles permises à l'instant sont dans state.actions, avec leur hint : la forme exacte de args. Les actions du chemin principal sont en tête de chaque étape.

chemin chemin principalestimation le devis est une estimation, les crédits sont débités au coût réel au fil des étapesconfirmation peut défaire du travail : répond par un devis, même gratuit

Marque

marque7
  • brand_from_sitechemin
    { "url": string }
  • keep_brandchemin
    {}
  • pick_brand
    { "brandId": string }
  • manual_brand
    { "name": string, "description"?: string, "promise"?: string, "audience"?: string } — fill what the user already said
  • set_brand_field
    { "field": "name" | "promise" | "description" | "audience" | "features" | "cta" | "url" | "language" | "colors" | "fonts" | "pronunciations", "value": unknown } — free, changes the brand itself (this reel's next pictures and every later reel); features is a string[]
  • sync_brand
    {}
  • suggest_brand_copy
    {}

Éléments

elements3
  • finish_elementschemin
    { "skip"?: boolean }
  • set_reel_elementsconfirmation
    { "elementIds": string[], "mustInclude"?: string } (elements of the brand, at most 3; find them by name in the brand elements; mustInclude also sets the phrase in the same call)
  • set_must_includeconfirmation
    { "text": string } (what the reel must say word for word, ≤ 200 characters; "" removes it)

Format

format1
  • set_formatscheminconfirmation
    { "platforms"?: string[] (all vertical — tiktok/instagram/shorts — or only youtube, never both; left out = the format on screen), "language"?: "fr" | "en" | "es" | "de" | "it" | "pt", "duration"?: 15 | 30 | 60 (seconds: 6, 12 or 24 scenes) }

Angle

angle5
  • propose_angleschemin
    { "note"?: string }
  • pick_anglechemin
    { "proposalId": string }
  • start_reelcheminestimation
    {}
  • retry_element_angles
    {}
  • custom_angle
    { "text": string }

Style

medium2
  • choose_mediumchemin
    { "pack": number } (0-based, when packs are offered: medium + narration in one pick) or { "medium": string } — a catalogue id (tournage, ugc, motion-design, dessin-anime, anime, 3d-clay…), its French or English label, or any free wording (« en pâte à modeler ») taken as a custom medium. Free while the grid is shown; the next draw after « Changer de médium » costs the 4 styles.
  • change_medium
    {} — back to the medium grid while the 4 styles wait; the styles already drawn stay until a new medium is chosen.

Narration

narration2
  • choose_narrationchemin
    { "mode": string, "settings"?: object } — a catalogue id (voix-off, histoire, ugc, voix-interieure, fondateur, dialogue, interview) or its French / English label. "settings" (optional): { "titles": boolean (words painted on the pictures), "cta": boolean (the film ends on a call to action), "ctaText": string (its exact words, 60 characters at most; empty = written from the site) }. Free. The casting, the script and every plan are then made for that narration.
  • back_style_flow
    { "to": "medium" | "narration" } — Style flow: back to the Style (texture) or Narration screen before the ambiances are drawn. Free; the choices stay pre-checked.

Options

options1
  • set_optionschemin
    { "titles": boolean (words painted on the pictures), "logo": "never" | "end" | "corner", "cta": boolean (the film ends on a call to action), "ctaText": string (its exact words, 60 characters at most; empty = written from the site) } — Style flow screen 3 « Générer »: saves the options and draws the 4 ambiances (one in turbo). Prefer letting the person use the Options screen.

Look

style4
  • approve_stylechemin
    { "index"?: number } (index = the style picked among the proposed ones, 0-based)
  • regenerate_moodboard
    { "note"?: string }
  • redo_typecard
    { "note"?: string }
  • redo_board
    { "index": 1 | 2 | 3 | 4 }

Casting

casting6
  • approve_castingchemin
    {} — keeps the drawn casting and writes the script with it
  • do_casting
    {} — does the casting the server skipped on this content: the script is rewritten with it, the shots redrawn
  • set_cast_fieldconfirmation
    { "id": "<casting element id>", "fields": { … } } — free edit of the fiche, no drawing; keys: "name", character ["age","hair","outfit","accessories","signature"], place ["materials","light","never"]. e.g. « Joe s'appelle Gino » → { "id": "joe", "fields": { "name": "Gino" } }; "redraw": true also draws the element again from the edited fiche (one image, in the same run)
  • add_cast
    { "name": string, "kind": "character" | "place" | "object", "description": "the person's words" } — one new element, drawn once
  • remove_castconfirmation
    { "id": "<casting element id>" } — the element leaves the casting (free before the validation; after it, the scenes that showed it are redrawn)
  • redraw_cast_unitconfirmation
    { "unit": "character-<id>" | "place-<id>" | "objects", "note"?: "the person's instruction word for word (« brune, mate de peau »): the look is rewritten from it before drawing; without note the same look is drawn again" } — then offer recast_plans: the scenes keep the old look until their plans are redrawn — WITH A CASTING (the Casting card): { "id": "<casting element id>", "note"?: "the person's instruction word for word" } — e.g. « Refais Joe plus jeune » → { "id": "joe", "note": "plus jeune" }; without note the element is drawn again from its fiche. After the validation the scenes showing it are redrawn too.

Script

script16
  • approve_scriptcheminestimation
    {} (the SHOOT, paid: films every scene of the reel — only when the person asks to film / tourner / lancer le reel; validating the script or going to the voice is open_voice)
  • open_voicechemin
    {} (the script is validated: on to picking and hearing the voice-over, free)
  • change_narrationconfirmation
    { "mode": string, "settings"?: object } — same ids as choose_narration (the current id to change only its settings). "settings" (optional): { "titles": boolean (words painted on the pictures), "cta": boolean (the film ends on a call to action), "ctaText": string (its exact words, 60 characters at most; empty = written from the site) }. Only while the script is read (before the shoot): the script is written again for that narration and those settings, its voice and plans redone; the casting stays.
  • change_optionsconfirmation
    { "titles": boolean (words painted on the pictures), "logo": "never" | "end" | "corner", "cta": boolean (the film ends on a call to action), "ctaText": string (its exact words, 60 characters at most; empty = written from the site) } — Style flow, while the script is read (before the shoot): the logo end/corner is saved as it is (free); titles, the call to action or the logo to/from "never" rewrite the script (its voice and plans redone).
  • edit_phrase
    { "phraseId": number, "text"?: string, "highlight"?: string }
  • rewrite_script
    { "note": string }
  • set_layout
    { "shotId": number, "layout": "kinetic"|"title"|"clean" }
  • set_scene_text
    { "shotId": number, "textMode"?: "phrase"|"keywords"|"none", "keywords"?: string }
  • set_transition
    { "shotId": number, "kind": "whip"|"through"|"match-action"|"match-shape"|"pass-lens"|"focus"|"crane", "prompt"?: "one English sentence naming the concrete element (the object the camera dives into, the gesture that carries on)" } (free: how the camera passes from this scene to the next, filmed in the clip; « fais un filé entre la 3 et la 4 » = shotId 3, kind whip; then offer redo_clip when that clip is already filmed)
  • sketch_scenes
    { "shotIds"?: number[] } (the scenes whose plan is missing or out of date; all of them when absent)
  • set_pronunciations
    { "list": [{ "word": string (one word, as written), "say": string (how the voice must say it) }] } (saved on the brand; the phrases it touches are read again, free)
  • set_scene_pronunciation
    { "phraseId": number, "word": string (one word of that phrase, as written), "say": string (how to say it; "" removes it) } (that scene only, re-read free; for every scene use set_pronunciations)
  • toggle_sfx
    { "on": boolean }
  • redo_sfx
    { "note"?: string }
  • remove_sfx_cue
    { "cueId": string ("sfx-N") } (before the shoot; after it, the montage removes a cue)
  • recast_plans
    {}

Voix off

voix3
  • open_qualitychemin
    {} (the voice is ready: on to choosing the video quality, free; only for a content that has the quality step)
  • pick_voicechemin
    { "voiceId": string, "ownerId"?: string (library voice), "name"?: string, "speaker"?: "a" | "b" (a two-voice script: whose voice, default a), "read"?: boolean (false = only select it, make_voice reads it later; default true) } (then the voice-over is read with it, free; on a finished reel it is re-cut on the new voice, free)
  • make_voice
    {} (reads the missing phrases again with the current voice, e.g. after a phrase edit; free)

Qualité

qualite1
  • choose_qualitychemin
    { "id": string } — the video quality, free: h3-768, h3-2k, sd2-720, sd2-1080 or sd25-1080 when nobody speaks on camera; omni-1080 when someone does. The price of the shoot follows it.

Tournage

tournage7
  • redo_scene
    { "shotId": number (1 to the scene count), "note"?: string, "refs"?: ["c_…" ids from context.media, 3 at most — the pictures the scene must be drawn from; [] takes them off; leave it out to keep the ones the scene has] }
  • redo_scenes
    { "scenes": [{ "shotId": number, "note"?: string, "refs"?: ["c_…" ids from context.media, 3 at most — the pictures the scene must be drawn from; [] takes them off; leave it out to keep the ones the scene has] }] } — several scenes redone at once, one price
  • redo_clip
    { "shotId": number (1 to the scene count) }
  • retry_task
    { "taskId": string } — a failed task from context.tasks, tried again
  • redo_clips
    {}
  • add_landscape
    {}
  • refilm_missing
    {}

Son

son5
  • pick_musicchemin
    { "candidateId": string }
  • set_voice
    { "voiceId": string }
  • new_music_candidates
    {}
  • rewrite_music_briefs
    { "note"?: string }
  • set_caption_style
    { "style": "karaoke"|"pop"|"clean"|"none", "layout"?: { "font": "inter"|"inter-heavy"|"inter-light"|null, "scale": 0.6-1.6, "y": 15-95 (bas du texte, % depuis le haut)|null } }

Reel

reel6
  • open_exportchemin
    {} (the montage is fine: on to the export step — downloads, captions, other language or format; free)
  • edit_montage
    { "phrasePadSec"?: { [phraseId]: seconds -0.3..1.5 | null }, "musicGainOffsetDb"?: -12..6 | null, "effectsGainOffsetDb"?: -20..6 | null (every sound effect and the sound of the clips at once), "captions"?: { [phraseId]: { "text"?: same word count, "highlight"?: word } | null }, "sfx"?: { [cueId]: { "anchor"?: "in"|"mid"|"out", "offsetSec"?: -0.5..0.5, "gainDb"?: -14..0, "removed"?: boolean } | null }, "cuts"?: { [shotId]: { "splits"?: seconds from the scene start[], "removed"?: [from, to][] (pieces taken out of the reel) } | null }, "wholeClips"?: true | false | null (true: every clip whole at its own speed, nothing cut, only the voice laid on the titles, the reel may pass 30 s; false: clips fitted to the voice; null: the reel default, context.wholeClipsDefault) }
  • export_clean
    {}
  • write_post
    { "note"?: string }
  • open_montage
    {} (back from the export to the montage step, to adjust scene lengths, music, captions or effects; free)
  • rerender
    {}

À toutes les étapes

3
  • forget_constraint
    { "id": string } — a constraint id from context.constraints
  • relaunch
    {}
  • goto_step
    { "step": string }

#Ce qui reste sur le site

Cinq actions répondent 400 not_in_api : elles demandent un envoi de fichier, ou dépenseraient sans devis. Envoie la personne sur webUrl.

ActionPourquoi
set_modeLe mode turbo enchaîne les étapes payantes sans devis.
edit_sceneRetouche d'une scène avec images envoyées.
revert_scene_editAnnule une retouche faite sur le site.
set_cast_photoEnvoi d'une photo pour un personnage.
derive_reelDéclinaison d'un contenu, prévue en v2.
400 bad_action
{
  "error": "Action inconnue : les noms valides sont dans « validActions » ; celles de l'étape en cours sont dans GET /api/v1/contents/:id (« actions »).",
  "code": "bad_action",
  "validActions": [
    "add_cast",
    "add_landscape",
    "approve_casting",
    "…"
  ]
}
Référence

Une IA sans terminal ne peut pas donner à la personne une URL qui demande un en-tête Bearer. Chaque aperçu porte donc un shareUrl, et le film un shareUrl et un downloadUrl.

  • Ils s'ouvrent dans n'importe quel navigateur, sans clé, pendant 24 heures (linkExpiresInSec: 86400).
  • Chacun ouvre un seul fichier d'un seul contenu, et ne contient ni la clé ni une session.
  • Ils sont signés par la clé qui a lu l'état : révoquer la clé ou changer de mot de passe les coupe aussitôt.
  • Expiré : 404 link_invalid. Relis l'état pour en obtenir des neufs.
  • Le film accepte les requêtes Range : on peut le lire et avancer dedans directement.
Un élément de state.previews
{
  "kind": "frame",
  "label": "Plan 3",
  "url": "/api/v1/contents/3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19/files/frames/3",
  "shareUrl": "https://adkix.ai/api/v1/links/eyJ1IjoiNGQ5YSIsImsiOiJrXzhmMmEiLCJqIjoiM2Y5YyJ9.Xk2vQ7tLwz…"
}
Référence

#Attendre sans bloquer

Lire une marque prend quelques secondes, dessiner des styles une à deux minutes, tourner 15 à 20 minutes. Plutôt que d'appeler en boucle, demande à l'API d'attendre pour toi.

  • ?waitSec=25 tient la requête jusqu'à 25 secondes et répond dès que quelque chose change : étape, tâche, image, clip, voix, film, erreur.
  • L'en-tête x-adkix-waited dit changed ou timeout. Dans les deux cas, enchaîne simplement l'appel suivant.
  • Sans waitSec, respecte pollAfterSec : 10 secondes quand busy est rempli, 30 sinon.
  • Une requête de 25 s tient dans les 45 s d'une Action ChatGPT et dans le délai d'un outil MCP.
  • Pendant le tournage, donne des nouvelles à la personne (progress.label, progress.pct) au lieu de la faire attendre en silence.
Suivre un tournage
while true; do
  STATE=$(curl -s "https://adkix.ai/api/v1/contents/$ID?waitSec=25" \
    -H "Authorization: Bearer $ADKIX_API_KEY")
  echo "$STATE" | jq -r '.progress.label, (.suggested.action // "—")'
  [ "$(echo "$STATE" | jq -r '.video.ready // false')" = "true" ] && break
done

#La boucle recommandée

Un contenu de bout en bout
const API = "https://adkix.ai/api/v1";
const headers = {
  Authorization: `Bearer ${process.env.ADKIX_API_KEY}`,
  "Content-Type": "application/json",
};

async function call(method, path, body) {
  const res = await fetch(API + path, { method, headers, body: body && JSON.stringify(body) });
  if (res.status === 429) {
    await new Promise((r) => setTimeout(r, Number(res.headers.get("retry-after") ?? 10) * 1000));
    return call(method, path, body);
  }
  const data = await res.json();
  if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
  return data;
}

// 1. Un contenu à partir d'un site
const { contentId } = await call("POST", "/contents", {
  source: { url: "https://maisonlumen.fr" },
  brief: { duration: 30, language: "fr" },
});

// 2. Suivre, étape par étape
while (true) {
  const s = await call("GET", `/contents/${contentId}?waitSec=25`);
  if (s.video?.ready) {
    console.log("Film prêt :", s.video.shareUrl);
    break;
  }
  if (s.busy || !s.suggested) continue; // adkix travaille : on attend encore

  // 3. Montrer s.previews et s.suggested à la personne, prendre sa décision
  const choice = await askPerson(s); // { action, args } ou null
  if (!choice) continue;
  const r = await call("POST", `/contents/${contentId}/actions`, choice);

  // 4. Un prix ? Rien n'est dépensé sans son oui
  if (r.quote && (await askPersonYes(r.quote.text))) {
    await call("POST", `/contents/${contentId}/confirm`, { proposalId: r.quote.proposalId });
  }
}
Référence

#Erreurs

Une erreur a toujours un corps JSON { error, code } : error est une phrase en français qu'on peut montrer, code est stable et sert au code. Certains codes ajoutent un champ.

402 Payment Required
{
  "error": "Crédits insuffisants.",
  "code": "notEnoughCredits",
  "credits": {
    "need": 1180,
    "balance": 400
  }
}
CodeHTTPSignificationQue faire
auth401Clé absente, mal formée, inconnue ou révoquée.Vérifie l'en-tête Authorization: Bearer adk_live_… ; recrée une clé sur /compte.
rate429Trop de requêtes.Attends Retry-After secondes, puis reprends.
not_found404Contenu inconnu ou pas à toi, ou route inexistante.Vérifie l'id avec listContents.
method_not_allowed405Méthode non acceptée par la route (en-tête Allow).Utilise la méthode documentée.
bad_action400Nom d'action inconnu ou corps mal formé. validActions liste tous les noms.Prends un nom de state.actions.
not_in_api400Action réservée au site (set_mode, edit_scene, revert_scene_edit, set_cast_photo, derive_reel).Envoie la personne sur webUrl.
bad_source400Ni source.url ni source.idea, ou offre gratuite sans URL.Donne une URL ou une idée.
bad_brief400Brief invalide : durée hors 15/30/60, langue inconnue, plateformes inconnues.Corrige le champ cité dans error.
free_refused403Vidéo de bienvenue refusée ; reason dit pourquoi (déjà utilisée, adresse à vérifier…).Crée le contenu en offer: "paid".
notEnoughCredits402Solde insuffisant ; credits.need et credits.balance.La personne recharge sur adkix.ai, puis redemande un devis.
proposalExpired409Le devis a plus de 10 minutes.Relance l'action pour un devis neuf.
proposalSuperseded409Un devis plus récent l'a remplacé.Utilise state.pendingQuote.
service_unavailable503Un fournisseur (image, vidéo, voix) est en panne.Réessaie plus tard ; rien n'a été débité.
site_down / site_blocked400Le site ne répond pas, ou refuse les robots (vidéo de bienvenue).Une autre adresse, un @compte ou une idée.
verify_email403Adresse email pas encore confirmée.La personne clique le lien reçu par mail.
bad_webhook400Webhook refusé : adresse pas en https, pas publique, ou événement inconnu.Une URL https publique et des événements de la liste.
too_many_webhooks409Déjà 5 webhooks sur le compte.Supprime-en un avec DELETE /webhooks/{id}.
link_invalid404Lien signé expiré, clé révoquée ou mot de passe changé.Relis l'état pour un lien neuf.
Référence

#Limites

20 / 10 minÉchecs d'authentificationpar adresse IP, puis 429
120 / minLectures (GET)par clé
30 / minActions et confirmationspar clé
10 / jourCréations de contenupar clé, et 20 / jour par compte
10 minDurée d'un devisexpiresInSec = 600
24 hLiens signéslinkExpiresInSec = 86400
≤ 25 sAttente longuewaitSec
5Clés activespar compte
50listContentsles plus récents

Une limite dépassée répond 429 avec l'en-tête Retry-After, en secondes.

Intégrations

#Utiliser adkix depuis Claude ou ChatGPT

Pas besoin d'écrire de code : donne la clé à ton assistant, il conduit la vidéo avec toi.

#Claude : le skill adkix

Un Agent Skill pour Claude Code, Claude Desktop et claude.ai. Avec lui, Claude :

  • comprend ta marque à partir de ton site, ton @compte ou une phrase ;
  • lance le contenu, te montre chaque étape (images, voix, clips) et attend ta validation ;
  • ne dépense jamais un crédit sans ton oui sur un prix précis ;
  • suit le tournage de 20 minutes sans bloquer la conversation, puis te donne le lien du film.

Téléchargements : adkix-skill.zip (le dossier complet) ou adkix.md (un seul fichier).

  1. Claude Code : colle la commande d'installation, ou décompresse le dossier dans ~/.claude/skills.
  2. claude.ai et Claude Desktop : Paramètres, Capacités, Skills, puis envoie le fichier zip.
  3. La clé : dans ~/.config/adkix/key (chmod 600) ou la variable ADKIX_API_KEY. Le skill ne l'affiche jamais.
Installer dans Claude Code
claude "Installe ce skill dans ~/.claude/skills/adkix/SKILL.md : https://adkix.ai/connect/adkix.md"
Ranger la clé (dans ton terminal)
mkdir -p ~/.config/adkix && (umask 077; printf 'Colle ta clé adkix puis Entrée : '; \
  read -rs K; printf %s "$K" > ~/.config/adkix/key; chmod 600 ~/.config/adkix/key; echo; echo OK)
adkix-skill.zip
adkix/
├── SKILL.md            # quand l'utiliser, la boucle, les règles de prix
├── reference/*.md      # étapes, actions, erreurs, détails de l'API
└── scripts/adkix.py    # petit client : clé lue sans jamais l'afficher

#ChatGPT : un GPT adkix

  1. Dans ChatGPT, Explorer les GPT, Créer, onglet Configurer.
  2. Colle les instructions de /connect/chatgpt/instructions.md.
  3. Actions, Créer une action, Importer depuis une URL : https://adkix.ai/api/v1/openapi.json?profile=gpt.
  4. Authentification : Clé API, type Bearer, colle ta clé.
  5. Enregistre le GPT en mode « Moi uniquement » : il agit avec ta clé.

Le profil gpt ne garde que les opérations JSON. confirmQuote y est marquée conséquente : ChatGPT te demande toujours avant de dépenser. Les images et le film s'ouvrent par leurs liens signés. Le pas à pas complet est dans /connect/chatgpt/README.md.

Importer depuis une URL
https://adkix.ai/api/v1/openapi.json?profile=gpt
Connecteurs web
Les connecteurs de claude.ai et de ChatGPT sur le web demandent OAuth, pas encore disponible. Utilise le skill ou le GPT en attendant.

#Codex et Cursor

Codex découvre les skills rangés dans ~/.codex/skills/<nom>/SKILL.md : la même commande d'installation fonctionne. Cursor, Codex et VS Code peuvent aussi parler à adkix par le serveur MCP.

Installer dans Codex
codex "Installe ce skill dans ~/.codex/skills/adkix/SKILL.md : https://adkix.ai/connect/adkix.md"
Intégrations

#Serveur MCP

Un serveur Model Context Protocol en Streamable HTTP, sans état, à l'adresse https://adkix.ai/api/mcp. Deux façons d'entrer : la connexion OAuth (ton assistant ouvre une page adkix, tu acceptes, rien à copier) ou ta clé dans l'en-tête Authorization.

  • Versions du protocole : 2025-11-25, 2025-06-18 et 2025-03-26. POST seulement (GET répond 405).
  • Le serveur envoie ses consignes (instructions) à l'initialisation : ton IA connaît les règles de prix d'emblée.
  • Une page web d'une autre origine qu'adkix.ai reçoit 403.
  • Mêmes limites et mêmes devis que l'API REST.
OutilRôle
adkix_get_accountCompte, solde, vidéo de bienvenue (getAccount).
adkix_list_contentsLes 50 derniers contenus (listContents).
adkix_create_contentNouveau contenu depuis un site, un @compte ou une idée (createContent).
adkix_get_contentL'état, avec waitSec pour attendre un changement (getContent).
adkix_view_previewRegarde un aperçu (file: frames/3, moodboard/option-2) : renvoyé en image MCP (JPEG, 1024 px max), ton IA peut le décrire.
adkix_run_actionUne action de l'étape ; les payantes renvoient un devis (runAction).
adkix_confirm_quoteAccepte un devis après le oui de la personne (confirmQuote). Annoté destructiveHint : le client demande la permission.
adkix_cancel_quoteRetire un devis (runAction cancel).
Installer le serveur
claude mcp add --transport http adkix https://adkix.ai/api/mcp \
  --header "Authorization: Bearer adk_live_..."

Cursor : ~/.cursor/mcp.json. VS Code : .vscode/mcp.json. Codex : ~/.codex/config.toml, avec la clé dans ADKIX_API_KEY.

Tester à la main
curl https://adkix.ai/api/mcp \
  -H "Authorization: Bearer $ADKIX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }'
Intégrations

#Webhooksbêta, sur activation

Plutôt que de surveiller l'état, reçois un POST quand un contenu a besoin de la personne ou quand le film est prêt.

POST/api/v1/webhooks
GET/api/v1/webhooks
DELETE/api/v1/webhooks/{id}
  • Créer : { url, events? } → 201 { id, url, events, secret, createdAt }. Le secret (whsec_…) n'est montré qu'une fois.
  • Lister : { webhooks: [{ id, url, events, createdAt, lastDelivery }] }. Supprimer : { ok: true }.
  • La dernière livraison : lastDelivery { at, event, http, ok, error? }.
  • 5 adresses au plus par compte (sinon 409 too_many_webhooks), https publique seulement (sinon 400 bad_webhook). Sans events, tous les événements.
  • Un webhook appartient à la clé qui l'a créé : la révoquer arrête ses livraisons.
  • Un événement part environ 3 secondes après que le contenu s'est posé, une fois par changement ; il repart si la même étape revient avec un contenu neuf.
ÉvénementQuand
content.step_readyUne étape attend la personne (choisir un angle, valider le script…).
content.quote_pendingUn prix attend un oui ; data.quote porte proposalId, action et price.
content.video_readyLe film est prêt ; data.video.shareUrl l'ouvre.
content.failedUne étape a échoué.
POST /webhooks
curl https://adkix.ai/api/v1/webhooks \
  -H "Authorization: Bearer $ADKIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/adkix",
    "events": ["content.step_ready", "content.quote_pending", "content.video_ready", "content.failed"]
  }'
201 Created
{
  "id": "wh_5e2c19a07b",
  "url": "https://example.com/hooks/adkix",
  "events": [
    "content.step_ready",
    "content.quote_pending",
    "content.video_ready",
    "content.failed"
  ],
  "secret": "whsec_9Qm2…",
  "createdAt": "2026-10-20T09:30:12.004Z"
}
Lister, supprimer
curl https://adkix.ai/api/v1/webhooks \
  -H "Authorization: Bearer $ADKIX_API_KEY"

curl -X DELETE https://adkix.ai/api/v1/webhooks/wh_5e2c19a07b \
  -H "Authorization: Bearer $ADKIX_API_KEY"

#Vérifier la signature

Chaque livraison (User-Agent adkix-webhooks/1.0) porte Adkix-Event-Id, Adkix-Event-Type et Adkix-Signature: t=<secondes unix>,v1=<hex>, où v1 est le HMAC-SHA256 de "<t>.<corps brut>" avec ton secret.

  1. Lis le corps brut, avant tout parsing JSON.
  2. Recalcule le HMAC et compare-le en temps constant.
  3. Rejette un t de plus de 5 minutes (rejeu).
  4. Ignore un Adkix-Event-Id déjà traité.

#Relivraisons

3 tentatives : tout de suite, après 1 minute, après 10 minutes. Délai de 10 secondes par tentative ; toute réponse 2xx vaut livraison, et les redirections ne sont pas suivies. Réponds vite et traite ensuite.

Une livraison
POST /hooks/adkix HTTP/1.1
Content-Type: application/json
User-Agent: adkix-webhooks/1.0
Adkix-Event-Id: evt_01a9f3c47d2e
Adkix-Event-Type: content.video_ready
Adkix-Signature: t=1792488612,v1=5b0c7e1f9a…

{
  "id": "evt_01a9f3c47d2e",
  "type": "content.video_ready",
  "createdAt": "2026-10-20T09:50:12.000Z",
  "data": {
    "contentId": "3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19",
    "projectId": "b71e04c2-9a3d-4f65-8c20-6e1f5d7a3b48",
    "title": "Maison Lumen",
    "status": "done",
    "step": "reel",
    "webUrl": "https://adkix.ai/projets/b71e04c2-9a3d-4f65-8c20-6e1f5d7a3b48?job=3f9c2a7e-5b14-4d8a-9e61-0c7b2d4f8a19",
    "video": {
      "shareUrl": "https://adkix.ai/api/v1/links/eyJ1Ijoi…"
    }
  }
}
Recevoir et vérifier
import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";

function verifyAdkix(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(
    (header ?? "").split(",").map((p) => p.trim().split("=", 2)),
  );
  const t = Number(parts.t);
  if (!t || Date.now() / 1000 - t > toleranceSec) return false; // plus de 5 min : rejeté
  const want = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const got = Buffer.from(parts.v1 ?? "", "hex");
  return got.length === want.length && timingSafeEqual(got, want);
}

const app = express();
// le corps brut, tel qu'il a été signé
app.post("/hooks/adkix", express.raw({ type: "application/json" }), (req, res) => {
  const raw = req.body.toString("utf8");
  if (!verifyAdkix(raw, req.get("Adkix-Signature"), process.env.ADKIX_WEBHOOK_SECRET)) {
    return res.status(400).send("signature invalide");
  }
  const event = JSON.parse(raw);
  // Adkix-Event-Id : ignore un événement déjà traité (une relivraison est possible)
  res.sendStatus(200); // tout 2xx = livré ; réponds vite, traite ensuite
  handle(event);
});
Suivi

#Changelog

Connexion OAuth, registre MCP
  • Le serveur MCP accepte OAuth 2.1 (PKCE S256, inscription dynamique) : Claude, ChatGPT ou Codex se connectent avec la seule adresse https://adkix.ai/api/mcp, sans copier de clé.
  • adkix est inscrit au registre MCP officiel sous le nom ai.adkix/adkix.
Liens signés, attente longue, OpenAPI
  • Liens signés 24 h (shareUrl, downloadUrl) sur chaque aperçu et sur le film.
  • waitSec sur getContent (jusqu'à 25 s) et en-tête x-adkix-waited.
  • Route POST /contents/{id}/confirm pour accepter un devis.
  • Spécification OpenAPI 3.1 publique, profil gpt pour les Actions.
  • Serveur MCP et webhooks en bêta, sur activation.
  • Skill Claude v3 (dossier complet) et kit GPT pour ChatGPT.
API v1
  • Clés personnelles adk_live_…
  • Contenus : créer, lister, lire l'état.
  • Actions de chaque étape, mêmes noms que l'assistant du site.
  • Devis : rien n'est dépensé sans confirmation.
Suivi

#Support

Une question, un bug, une intégration qui coince : écris à [email protected]. On répond en français, en anglais ou en espagnol, en général sous 24 h ouvrées.

  • Donne l'id du contenu (contentId) et l'heure de l'appel : on retrouve tout de suite le journal.
  • Les connexions de tes assistants (Claude, ChatGPT…) se voient et se coupent dans « Applications connectées », sur ton compte.
  • Données personnelles : politique de confidentialité. Conditions : CGU.