API d’intégration v1

    Liens courts et secrets, depuis votre code.

    Une seule clé bearer, du JSON simple et les mêmes règles de forfait que le tableau de bord. Conçue pour les extensions, les CLI, les serveurs MCP et les outils internes.

    Créer une clé APISpécification OpenAPI

    Accès anticipé. Les routes v1 sont activées espace de travail par espace de travail. Si un appel renvoie 503 avec le code disabled, l’API n’est pas encore activée pour vous. Vous pouvez déjà créer des clés : elles fonctionneront dès l’activation.

    Bibliothèques clientes

    Les secrets sont chiffrés avant l’envoi, donc chaque client doit s’en charger lui-même. Ces bibliothèques l’implémentent une fois, correctement, et sont testées sur les mêmes vecteurs de test versionnés que l’application web. Toutes les trois sont sous licence MIT, publiées depuis la CI avec la provenance npm, et ne dépendent que de la bibliothèque standard.

    SDK TypeScript

    npm

    Un client typé pour chaque route. Aucune dépendance, uniquement WebCrypto : il fonctionne tel quel sous Node 20+, Deno et Bun, dans les navigateurs et sur les runtimes de périphérie.

    npm i @uselinkpilot/sdk

    Ligne de commande

    npm

    Transmettez le secret par un pipe plutôt qu’en argument : un argument finit dans l’historique du shell et reste visible dans la liste des processus.

    npm i -g linkpilot
    echo 'db password: hunter2' | linkpilot secret --ttl 1h

    Serveur MCP

    npm

    Permet à un assistant IA de transmettre des identifiants à quelqu’un sans les laisser dans un historique de conversation. Ajoutez ceci à Claude Desktop, Claude Code, Cursor ou tout autre client MCP :

    {
      "mcpServers": {
        "linkpilot": {
          "command": "npx",
          "args": ["-y", "@uselinkpilot/mcp"],
          "env": { "LINKPILOT_API_KEY": "lp_live_..." }
        }
      }
    }

    À savoir avant de l’installer : le texte en clair passe par le contexte de l’assistant, car le modèle doit voir un secret pour pouvoir le créer. Le serveur le protège pendant le transfert, au repos et en limitant son ouverture à une seule fois. Il ne peut pas le cacher au modèle lui-même.

    Code source : linkpilot-cli (SDK et CLI) et linkpilot-mcp.

    1. Authentification

    Créez une clé sur /app/api-keys (propriétaires et administrateurs de l’espace de travail). La clé brute n’est affichée qu’une fois ; LinkPilot n’en conserve que le hash SHA-256. Envoyez-la à chaque requête :

    Authorization: Bearer lp_live_...

    Chaque clé est liée à un espace de travail, il n’existe donc aucun paramètre de locataire. Les clés révoquées, expirées, inconnues ou mal formées renvoient toutes la même erreur 401 unauthorized. Les clés ne sont jamais écrites dans les journaux.

    URL de base : https://khiydxamyihofmtmgvjz.supabase.co/functions/v1/api-v1

    2. Routes

    MéthodeCheminRôle
    GET/meEspace de travail, forfait, limites, consommation et quota de requêtes de votre clé
    POST/linksCréer un lien court (url, slug facultatif, domaine, titre, tags)
    GET/linksLister les liens, du plus récent au plus ancien, avec pagination par curseur
    GET/links/{id}Récupérer un lien
    DELETE/links/{id}Supprimer un lien
    POST/secretsCréer un lien secret (ciphertext, enc_version, ttl_seconds, burn_after_read, passphrase_hash avec Pro)
    GET/secretsLister les métadonnées des secrets (jamais leur contenu)
    DELETE/secrets/{id}Révoquer un secret et détruire son contenu
    POST/public/linksCréer un lien anonyme avec un client_id, sans clé
    POST/public/secretsCréer un secret anonyme détruit après lecture, sans clé

    Les listes renvoient { data, next_cursor } ; renvoyez next_cursor dans cursor pour continuer. limit va de 1 à 100.

    3. Exemples

    Créer un lien avec curl

    curl -X POST "https://khiydxamyihofmtmgvjz.supabase.co/functions/v1/api-v1/links" \
      -H "Authorization: Bearer $LINKPILOT_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"url":"https://example.com/launch","slug":"launch","tags":["campaign"]}'

    Créer un secret en JavaScript

    const b64u = (b) => btoa(String.fromCharCode(...new Uint8Array(b)))
      .replace(/+/g, "-").replace(///g, "_").replace(/=+$/, "");
    
    // 1. Encrypt locally. This key is generated here and never sent anywhere.
    const key = crypto.getRandomValues(new Uint8Array(32));
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const k = await crypto.subtle.importKey("raw", key, "AES-GCM", false, ["encrypt"]);
    const ct = await crypto.subtle.encrypt(
      { name: "AES-GCM", iv, tagLength: 128 },
      k,
      new TextEncoder().encode("staging db password"),
    );
    const ciphertext = `v1.${b64u(iv)}.${b64u(ct)}`;
    
    // 2. Store the blob. LinkPilot cannot read it.
    const res = await fetch("https://khiydxamyihofmtmgvjz.supabase.co/functions/v1/api-v1/secrets", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.LINKPILOT_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ ciphertext, enc_version: 1, ttl_seconds: 3600 }),
    });
    if (!res.ok) throw new Error((await res.json()).error.code);
    const { secret_url } = await res.json();
    
    // 3. Attach the key. Without this the link can never be opened, by anyone.
    const shareUrl = `${secret_url}#k=${b64u(key)}`;

    Lister les liens en Python

    import os, requests
    
    r = requests.get(
        "https://khiydxamyihofmtmgvjz.supabase.co/functions/v1/api-v1/links",
        params={"limit": 20},
        headers={"Authorization": f"Bearer {os.environ['LINKPILOT_API_KEY']}"},
    )
    r.raise_for_status()
    for link in r.json()["data"]:
        print(link["short_url"], "->", link["url"])
    print("remaining this hour:", r.headers["X-RateLimit-Remaining"])

    Créer un lien anonyme sans clé

    curl -X POST "https://khiydxamyihofmtmgvjz.supabase.co/functions/v1/api-v1/public/links" \
      -H "Content-Type: application/json" \
      -d '{"url":"https://example.com","client_id":"my-cli-0123456789abcdef"}'

    Le contrat formel est publié ici, pas dans un dépôt : document OpenAPI 3.1, collection Postman et documentation de référence. Pointez directement votre générateur de code vers l’URL OpenAPI.

    4. Comment les secrets sont stockés

    Nous tenons à être précis, car un client API a ici un vrai travail à faire : l’API refuse purement et simplement un payload en clair.

    • Vous chiffrez, nous stockons un blob. Envoyez ciphertext (AES-GCM-256, v1.<iv>.<ct>) avec enc_version: 1. Un envoi de payload est refusé, et non accepté en silence.
    • Vous devez ajouter la clé vous-même. Le secret_url que nous renvoyons ne contient pas de clé, et ne peut pas en contenir, car nous n’en avons jamais vu aucune. Ajoutez #k=<base64url key>. Les navigateurs ne transmettent jamais la partie du lien située après le #, donc le destinataire déchiffre en local.
    • Aucune récupération possible. Si la clé est perdue, le secret l’est aussi, pour vous comme pour nous. C’est la preuve que la garantie fonctionne.
    • Détruit après lecture, à l’expiration ou à la révocation. Le texte chiffré stocké est écrasé côté serveur.
    • Jamais renvoyé par l’API. Aucune route ne renvoie un contenu ou un texte chiffré. Seule la page d’affichage du destinataire le lit, et elle enregistre l’ouverture.
    • Les phrases de passe (Pro et au-delà) sont un vrai second facteur : renforcées avec PBKDF2-SHA256 puis intégrées à la clé avec HKDF, si bien que ni le lien seul ni la phrase de passe seule ne permettent de déchiffrer. N’envoyez que passphrase_hash, le hash SHA-256 en hexadécimal de la phrase de passe normalisée en NFKC, jamais la phrase de passe brute, qui constitue la moitié de la clé. Dérivez la clé à partir de la même forme NFKC, pour qu’une phrase de passe saisie sur un autre clavier ouvre toujours le secret.
    • Ce que cela ne couvre pas. Quiconque détient le lien complet détient la clé, donc une expiration courte et la destruction après lecture restent importantes. Les pièces jointes sont chiffrées de la même manière, avec une clé dérivée du même lien.

    Le format d’échange est spécifié dans la référence de l’API, et @uselinkpilot/sdk effectue tout l’échange en un seul appel si vous préférez ne pas l’implémenter vous-même. Le même modèle est décrit sur la page architecture de sécurité.

    5. Limites des forfaits et limites de débit

    LimiteFreeProAgency
    Liens50IllimitésIllimités
    Secrets actifs5100100
    Secrets protégés par phrase de passeNonOuiOui
    Domaines personnalisés pour les liensDomaines déjà vérifiésJusqu’à 5Illimités
    Requêtes par clé et par heure601 0005 000

    L’API applique les mêmes règles de forfait que le tableau de bord, lues au même endroit, donc le regroupement des forfaits d’agence s’applique ici aussi. GET /me renvoie les valeurs en temps réel pour votre espace de travail. Chaque réponse authentifiée contient les en-têtes X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset.

    Comparer les forfaits

    6. Routes publiques sans clé

    POST /public/links et POST /public/secrets existent pour les clients qui ne peuvent pas afficher de vérification dans un navigateur : les CLI, les serveurs MCP, le lanceur. Elles prennent un client_id opaque (de 16 à 64 caractères, généré une fois par installation) et créent des enregistrements dans le même pool anonyme que les outils web gratuits.

    • 20 créations par client et par jour et 60 par réseau et par heure, en plus des plafonds propres au pool pour chaque réseau.
    • Le même filtrage par flux de menaces, la même vérification des URL auprès de Google et les mêmes revérifications planifiées que pour toutes les autres voies de création.
    • Pas de domaine personnalisé, pas de slug personnalisé, pas de phrase de passe ; les secrets publics sont toujours détruits après une seule lecture et expirent dans les 24 heures.
    • La réponse contient un claim_token qui permet à une personne connectée de revendiquer plus tard l’enregistrement, c’est-à-dire de le rattacher à un espace de travail. Traitez-le comme un identifiant de connexion.

    7. Erreurs

    Chaque erreur a la forme { "error": { "code", "message", "upgrade_url"? } }. Les messages nomment le champ, jamais vos données. Une ressource d’un autre espace de travail renvoie une 404, pas une 403.

    CodeHTTPCas
    unauthorized401Clé absente, mal formée, inconnue, révoquée ou expirée
    disabled503L’API n’est pas encore activée pour ce déploiement
    invalid_request400Échec de validation, destination bloquée, slug déjà pris, domaine inconnu
    not_found404Route inexistante, ou ressource hors de votre espace de travail
    plan_limit402Quota de liens ou de secrets actifs atteint (upgrade_url inclus)
    pro_required402Phrase de passe sur un forfait qui ne l’inclut pas (upgrade_url inclus)
    rate_limited429Quota horaire de la clé ou allocation publique épuisés (Retry-After inclus)

    Questions des développeurs

    Quels forfaits incluent l’API ?
    Les clés API se créent depuis le tableau de bord, sur /app/api-keys. Chaque clé suit les limites du forfait de son espace de travail : le forfait Free est limité à 50 liens et 5 secrets actifs, avec 60 requêtes par heure ; Pro offre des liens illimités, 100 secrets actifs, des secrets protégés par phrase de passe et 1 000 requêtes par heure ; les forfaits Agency ont droit à 5 000 requêtes par heure.
    Le contenu des secrets est-il chiffré de bout en bout ?
    Oui, et l’API refuse purement et simplement le texte en clair. Vous chiffrez avec AES-GCM-256 et envoyez le texte chiffré ; LinkPilot stocke un blob dont il n’a pas la clé et qu’il ne peut pas déchiffrer. Comme nous ne voyons jamais votre clé, le secret_url que nous renvoyons n’en contient pas. Vous l’ajoutez vous-même sous la forme d’un fragment #k=, que les navigateurs ne transmettent jamais. Si la clé est perdue, personne ne peut récupérer le secret, pas même nous. Les pièces jointes sont chiffrées de la même manière, avec une clé dérivée du même lien.
    Puis-je créer des liens sans clé API ?
    Oui. POST /public/links et POST /public/secrets prennent un client_id opaque au lieu d’une clé et créent des enregistrements dans le même pool anonyme que les outils web gratuits : 20 créations par client et par jour, 60 par réseau et par heure, pas de domaine personnalisé, pas de phrase de passe, et les secrets expirent dans les 24 heures.
    Que se passe-t-il quand j’atteins une limite ?
    Les limites de forfait renvoient une erreur HTTP 402 avec le code plan_limit ou pro_required et une upgrade_url. Les limites de débit renvoient une erreur 429 avec un en-tête Retry-After. Chaque réponse authentifiée contient X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset, pour que les clients puissent ralentir avant d’en arriver là.
    L’API est-elle stable ?
    Les routes v1 ne changeront pas de manière incompatible. Tout changement incompatible sera publié dans une nouvelle version. Pendant l’accès anticipé, les routes sont activées espace de travail par espace de travail ; une réponse 503 avec le code disabled signifie que l’API n’est pas encore activée pour votre espace de travail.

    Envie de commencer à développer ?

    Créez une clé dans votre espace de travail ou commencez avec le forfait Free.