Aller au contenu
FormFlow

Lire les Envois avec un jeton d'API de gestion à portée limitée

Un jeton d'API de gestion est haché, affiché une fois et limité par Capacité : cinq routes de lecture sur les formulaires, les Envois, les refus et un fil de tentative, avec pagination par curseur et aucune écriture.

Cet article a été traduit automatiquement. Lire l'original en anglais

A dark FormFlow plate titled 'Read only. By design.' with a scoped token chip feeding three read rows, Forms, Submissions and Refusals, and a read path that stops at a dashed write boundary before a closed door plated 'no writes'.

L'API de gestion est le côté lecture de FormFlow : cinq routes GET, un jeton porteur, et aucun moyen d'écrire quoi que ce soit. C'est la surface que les outils d'une équipe utilisent quand ils ont besoin des Envois que FormFlow détient déjà, et non d'une copie dans un tableur.

Rendez le jeton aussi étroit que la tâche

Un jeton appartient à l'équipe, pas à une personne. Le créer lui donne un nom, au moins une Capacité — submissions:read, refusals:read ou forms:read —, éventuellement une liste de formulaires et éventuellement une date d'expiration. FormFlow ne stocke qu'un hachage : l'identifiant n'est affiché qu'une fois, à la création, et jamais ensuite. La révocation est immédiate ; un jeton expiré garde sa ligne et libère sa place.

Deux de ces réglages font l'essentiel du travail. Un jeton limité à des formulaires nommés atteint ces formulaires et rien d'autre. Un jeton qui ne porte que forms:read peut lister les métadonnées des formulaires sans lire un seul Envoi. Ni l'un ni l'autre n'est une préférence : une lecture hors de la portée du jeton répond exactement comme la lecture d'un enregistrement qui n'existe pas, si bien qu'un enregistrement absent et un enregistrement interdit se ressemblent.

Les cinq lectures

Toutes les routes sont sous /api/v1/manage et s'authentifient avec un jeton porteur dans l'en-tête Authorization.

  • GET /api/v1/manage/forms — métadonnées des formulaires. Exige forms:read.

  • GET /api/v1/manage/submissions — une page d'Envois. Exige submissions:read.

  • GET /api/v1/manage/submissions/{uuid} — un Envoi, avec les signaux de spam que la liste omet.

  • GET /api/v1/manage/refused-submissions — les refus conservés par le registre du formulaire. Exige refusals:read.

  • GET /api/v1/manage/attempts/{attemptId} — la vie ordonnée d'une tentative à travers les registres qui l'ont consignée.

Le corps d'un Envoi est indexé par nom de champ et renvoyé tel qu'il a été envoyé. La plupart des champs sont des chaînes, un multiselect est une liste, un champ fichier est une référence avec son nom stocké et une grille est un objet indexé par libellé de ligne. La route d'un seul Envoi est la seule à renvoyer les signaux de spam.

Paginer avec le curseur

Les routes de liste répondent avec data et meta. limit accepte de 1 à 200 et vaut 50 par défaut, et l'ordre est created_at puis id, décroissant. meta.has_more indique s'il existe une autre page ; meta.next_cursor est la valeur opaque à renvoyer comme cursor. Il n'y a pas d'offset, parce que l'ensemble bouge pendant la lecture et qu'un offset sauterait ou répéterait des lignes en silence.

cursor=""
while :; do
  page=$(curl -sS "https://formflow.digital/api/v1/manage/submissions?limit=100&cursor=$cursor" \
    -H "Authorization: Bearer $FORMFLOW_TOKEN")
  echo "$page" | jq -r '.data[] | [.uuid, .form_id, .created_at] | @tsv'
  cursor=$(echo "$page" | jq -r '.meta.next_cursor // empty')
  [ "$(echo "$page" | jq -r '.meta.has_more')" = "true" ] || break
done

Les filtres sont étroits à dessein : form_id, since, until, is_spam, unread et q. q est une recherche par jeton exact sur un index aveugle, pas une recherche de sous-chaîne, et elle ne répond que là où l'indexation de recherche est activée.

Suivre un refus jusqu'à sa tentative

Un Envoi refusé et un Envoi sont deux enregistrements distincts. Le refus est conservé pour que le client puisse agir, il n'est jamais livré et ne devient jamais un Envoi compté. Les deux portent un identifiant de tentative, et la route des tentatives assemble le fil de cette tentative — friction Preflight, refus, acceptation, sauvetage, récupération, reçu et chaque obligation de livraison — sans valeurs envoyées ni métadonnées sur la personne.

Un identifiant de tentative n'est pas unique à lui seul : le fil est donc toujours rattaché à un formulaire. Quand l'identifiant existe sur plusieurs, la réponse est 409 et les nomme, et vous répétez la requête avec form_uuid pour choisir.

Là où la surface de lecture est abrupte

Trois autres limites valent d'être connues avant de bâtir dessus. Les corps sont déchiffrés dans FormFlow et renvoyés tels qu'ils ont été stockés : un jeton qui lit les Envois lit donc des données personnelles, et son stockage mérite le même soin que l'identifiant. Une ligne dont le corps ne peut pas être déchiffré revient avec payload_error valant payload_unavailable, le reste de la page arrivant quand même. Et les codes sont précis : 401 pour un jeton invalide, expiré ou révoqué ; 403 pour une Capacité manquante ou un forfait indisponible ; 404 quand l'enregistrement est hors de portée ; 422 pour un filtre ou un curseur invalide ; 429 avec Retry-After pour la limite de débit.

Le contrat complet — chaque paramètre, forme de réponse et erreur — se trouve dans /docs/api. Et l'entrée du journal des modifications pour v1.0.0 consigne l'API telle qu'elle est sortie : une API de gestion documentée à côté de l'unique point de terminaison qui reçoit les Envois.