Superfasttt

Quickstart

Créer une app minimale, importer son manifest, l'installer, échanger un launch_token et écrire une première donnée.

Ce guide crée une app "Audit Hello" qui stocke et relit des inspections depuis un frontend externe.

Prérequis. Vous avez un espace client Superfasttt actif, un jeton utilisateur disposant des droits d'administration nécessaires, et une URL de frontend. En développement, http://localhost:3000 est accepté.

1. Créer le manifest

Créez manifest.json :

{
  "manifest_version": "2.0",
  "id": "audit-hello",
  "name": "Audit Hello",
  "version": "1.0.0",
  "description": "Application de démonstration pour gérer des inspections.",
  "runtime": {
    "type": "external",
    "entry_url": "http://localhost:3000",
    "allowed_origins": ["http://localhost:3000"]
  },
  "permissions": [
    "app_data:read",
    "app_data:write"
  ],
  "collections": {
    "inspections": {
      "schema": {
        "type": "object",
        "properties": {
          "title": { "type": "string" },
          "status": { "type": "string", "enum": ["draft", "done"] }
        },
        "required": ["title", "status"],
        "additionalProperties": false
      },
      "indexes": ["status"]
    }
  }
}

entry_url est l'URL vers laquelle l'utilisateur sera envoyé au lancement. allowed_origins doit contenir l'origin exacte de votre frontend (scheme + host + port).

2. Valider le manifest

Les endpoints de manifest attendent une enveloppe JSON :

{
  "manifest": {
    "...": "contenu de votre manifest"
  }
}

Créez manifest-request.json avec cette enveloppe, puis validez :

curl -X POST https://api.superfasttt.ai/api/v1/app-platform/manifests/validate \
  -H "Authorization: Bearer <user_access_token>" \
  -H "X-Tenant-ID: <espace_client>" \
  -H "Content-Type: application/json" \
  -d @manifest-request.json

Réponse attendue :

{
  "valid": true,
  "errors": [],
  "warnings": []
}

La validation ne modifie rien. Utilisez-la avant chaque import ou dans votre CI pour détecter les erreurs de manifest.

3. Importer puis installer

L'import enregistre la version du manifest pour l'espace client.

curl -X POST https://api.superfasttt.ai/api/v1/app-platform/manifests/import \
  -H "Authorization: Bearer <user_access_token>" \
  -H "X-Tenant-ID: <espace_client>" \
  -H "Content-Type: application/json" \
  -d @manifest-request.json

Exemple de réponse :

{
  "app_id": "audit-hello",
  "version": "1.0.0",
  "lifecycle_status": "draft",
  "created": true,
  "validation": {
    "valid": true,
    "errors": [],
    "warnings": []
  }
}

Installez ensuite l'app :

curl -X POST https://api.superfasttt.ai/api/v1/app-platform/apps/audit-hello/install \
  -H "Authorization: Bearer <user_access_token>" \
  -H "X-Tenant-ID: <espace_client>"

Exemple de réponse :

{
  "id": "<id>",
  "app_id": "audit-hello",
  "state": "active",
  "enabled": true,
  "app_version_id": "<version_id>",
  "granted_permissions": ["app_data:read", "app_data:write"]
}

N'exposez jamais un jeton utilisateur d'administration dans votre frontend externe. Les actions d'import et d'installation doivent être effectuées depuis un contexte autorisé.

4. Générer une URL de lancement

Un utilisateur autorisé peut demander un launch_token pour ouvrir l'app :

curl -X POST https://api.superfasttt.ai/api/v1/app-platform/apps/audit-hello/launch-token \
  -H "Authorization: Bearer <user_access_token>" \
  -H "X-Tenant-ID: <espace_client>"

Réponse :

{
  "launch_token": "<launch_token>",
  "entry_url": "http://localhost:3000?launch_token=<launch_token>",
  "expires_at": "2026-05-28T10:17:37Z"
}

Redirigez l'utilisateur vers entry_url. Le launch_token est court et à usage unique : votre frontend doit l'échanger immédiatement.

5. Échanger le launch_token dans le frontend

Dans votre app, lisez launch_token, appelez l'endpoint d'échange, puis retirez le paramètre de l'URL.

const url = new URL(window.location.href);
const launchToken = url.searchParams.get("launch_token");

if (!launchToken) {
  throw new Error("launch_token manquant");
}

const response = await fetch(
  "https://api.superfasttt.ai/api/v1/app-platform/runtime/exchange",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ launch_token: launchToken }),
  }
);

if (!response.ok) {
  throw new Error(`Exchange failed: ${response.status}`);
}

const { access_token, token_type, expires_in, scopes } = await response.json();

url.searchParams.delete("launch_token");
window.history.replaceState({}, document.title, url.toString());
curl -X POST https://api.superfasttt.ai/api/v1/app-platform/runtime/exchange \
  -H "Content-Type: application/json" \
  -H "Origin: http://localhost:3000" \
  -d '{"launch_token":"<launch_token>"}'
{
  "access_token": "<app_access_token>",
  "token_type": "Bearer",
  "expires_in": 900,
  "scopes": ["app_data:read", "app_data:write"]
}

Gardez l'access_token uniquement le temps de la session app. Pour les appels suivants, envoyez-le dans le header Authorization.

Échange navigateur vs serveur — le header Origin est obligatoire. Un fetch lancé depuis le navigateur ajoute automatiquement l'Origin, donc l'exemple ci-dessus fonctionne tel quel. Si vous échangez le launch_token depuis votre backend (recommandé : l'access_token ne touche jamais le navigateur), aucun Origin n'est posé automatiquement — vous devez l'ajouter vous-même avec une valeur présente dans allowed_origins, sinon l'échange renvoie 403 origin_required. La même règle s'applique aux appels app-data si vous y joignez un Origin : il doit matcher allowed_origins.

6. Créer et lister une inspection

const headers = {
  Authorization: `Bearer ${access_token}`,
  "Content-Type": "application/json",
};

const created = await fetch(
  "https://api.superfasttt.ai/api/v1/app-data/inspections",
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      data: { title: "Inspection ligne A", status: "draft" },
    }),
  }
).then((r) => r.json());

const list = await fetch(
  "https://api.superfasttt.ai/api/v1/app-data/inspections/query",
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      filter: { status: "draft" },
      sort: "-updated_at",
      page: 1,
      page_size: 50
    }),
  }
).then((r) => r.json());

Réponse de liste :

{
  "items": [
    {
      "id": "<record_id>",
      "collection": "inspections",
      "data": { "title": "Inspection ligne A", "status": "draft" },
      "version": 1,
      "created_at": "2026-05-28T10:20:00Z",
      "updated_at": "2026-05-28T10:20:00Z"
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 50
}

Savoir quel utilisateur agit. L'access_token est émis pour l'utilisateur qui a ouvert l'app : son identifiant est porté par le claim sub du jeton (avec tenant_id, app_id et installation_id). C'est ainsi qu'une app externe sait qui l'utilise, même quand l'espace client compte des milliers d'utilisateurs. En revanche, les records app-data n'exposent pas de champ created_by_id dans leurs réponses : si vous voulez afficher une attribution par utilisateur côté app, recopiez l'identifiant (sub) dans le data du record au moment de la création.

7. Aller plus loin : documents riches et fichiers

Si votre app gère des rapports éditables ou des pièces jointes, vous appelez deux API supplémentaires avec le même access_token. Pensez d'abord à ajouter les permissions et déclarations dans votre manifest, puis réimportez et réinstallez :

"permissions": [
  "app_data:read", "app_data:write",
  "rich_documents:read", "rich_documents:write",
  "assets:read", "assets:write"
],
"rich_documents": {
  "audit_report": {
    "editor": "prosemirror",
    "schema_version": "audit-report-v1"
  }
},
"assets": {
  "allowed_mime_types": ["application/pdf", "image/png"],
  "max_file_size_mb": 25,
  "max_total_size_mb": 1024
}

Créer un document riche

La clé collection correspond à celle déclarée dans manifest.rich_documents. editor et schema_version doivent matcher la déclaration du manifest installé.

const doc = await fetch(
  "https://api.superfasttt.ai/api/v1/rich-documents",
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      collection: "audit_report",
      title: "Rapport site A",
      editor: "prosemirror",
      schema_version: "audit-report-v1",
      content: {
        type: "doc",
        content: [
          {
            type: "paragraph",
            content: [{ type: "text", text: "Contenu initial." }],
          },
        ],
      },
    }),
  }
).then((r) => r.json());

Le serveur extrait automatiquement plain_text depuis content pour la recherche. Les mises à jour suivantes via PATCH /api/v1/rich-documents/{id} incrémentent version. L'historique est listable via GET /api/v1/rich-documents/{id}/versions.

Uploader un fichier

Le flow assets est en 3 appels — l'upload binaire va directement au stockage, votre serveur ne le voit jamais transiter.

// 1. URL signée
const { asset_id, upload_url, required_headers } = await fetch(
  "https://api.superfasttt.ai/api/v1/app-assets/upload-url",
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      filename: "rapport.pdf",
      content_type: "application/pdf",
      size_bytes: 12345,
    }),
  }
).then((r) => r.json());

// 2. PUT direct au stockage S3-compatible
await fetch(upload_url, {
  method: "PUT",
  headers: required_headers,
  body: fileBlob,
});

// 3. Confirmer — le serveur revérifie taille + content-type via HEAD
await fetch(
  "https://api.superfasttt.ai/api/v1/app-assets/complete",
  {
    method: "POST",
    headers,
    body: JSON.stringify({ asset_id }),
  }
);

Pour télécharger ensuite, demandez une URL signée à durée limitée :

const { download_url } = await fetch(
  `https://api.superfasttt.ai/api/v1/app-assets/${asset_id}/download-url`,
  { method: "POST", headers }
).then((r) => r.json());

size_bytes doit être strictement positif et inférieur ou égal à assets.max_file_size_mb de votre manifest. La somme des assets d'une installation ne peut pas dépasser assets.max_total_size_mb. Le serveur refuse les valeurs négatives avant d'enregistrer le fichier.

Erreurs fréquentes

ErreurCause probableAction
422 à l'importLe body n'est pas enveloppé dans { "manifest": ... } ou le manifest est invalideValider avec /manifests/validate et corriger les champs indiqués
403 origin_requiredL'échange est appelé sans header OriginTester depuis le navigateur ou ajouter Origin en curl
403 origin_not_allowedL'origin réelle n'est pas dans runtime.allowed_originsCorriger le manifest, réimporter et réinstaller
401 token_invalid_or_used_or_expiredlaunch_token déjà utilisé ou expiréGénérer un nouveau launch_token
403 missing_scopeL'app n'a pas la permission requiseAjouter la permission au manifest, réimporter et réinstaller
404 collection_not_foundLa collection n'est pas déclarée dans le manifest installéAjouter la collection, réimporter et réinstaller

Et après

Consultez la référence du manifest pour ajouter des documents, des fichiers ou plusieurs collections. Consultez Authentification & sécurité pour les règles de session et d'origin.

On this page