Aller au contenu principal

Invitations de mise à jour

Un IBAN qui change, un contact qui part, un siège qui déménage : les fiches de votre annuaire se périment sans que rien ne vous le signale. Une invitation de mise à jour envoie à votre client ou fournisseur un lien signé vers un formulaire public où il corrige lui-même ses coordonnées bancaires, ses identifiants légaux, ses adresses et ses contacts. À la soumission, la fiche de votre annuaire est mise à jour, sans ressaisie ni tableur échangé par e-mail. Chaque invitation porte un état, de pending à submitted, que vous suivez par l'API.

Ce que Scribee fait pour vous​

  • Génère un lien signé à usage unique, valable 14 jours. La fiche d'invitation n'en conserve qu'une empreinte, jamais le jeton en clair - celui-ci transite en revanche par la file de traitement des emails, qui est persistée en base, jusqu'à ce que le message soit envoyé.
  • Envoie l'e-mail d'invitation dans la langue du destinataire (fr ou en), avec votre message personnalisé s'il est fourni.
  • Restreint le formulaire aux données que le tiers peut légitimement corriger ; un id d'adresse ou de contact appartenant à un autre tiers est ignoré.
  • Coupe le lien dès la soumission, la révocation ou l'expiration : il répond ensuite 410 et n'expose plus rien.
  • Trace chaque soumission avec l'adresse e-mail destinataire de l'invitation. Le lien public n'authentifie personne : s'il est transféré, la trace porte toujours l'adresse invitée, pas celle de la personne qui a réellement rempli le formulaire. Ne traitez pas cette trace comme une preuve d'identité.

Le parcours d'une invitation​

ÉtatSignification
pendingInvitation créée, e-mail pas encore parti. Sur POST .../update_invitations, cet état est transitoire : la création demande l'envoi dans le même appel. Sur l'envoi en masse, il est durable : une ligne dont l'envoi a échoué reste en pending définitivement.
sentEnvoi de l'e-mail d'invitation demandé (sent_at).
openedLe destinataire a ouvert le formulaire public (opened_at).
submittedLe destinataire a soumis ses informations (submitted_at) ; la fiche est mise à jour.
expiredInvitation dépassée sans soumission. Un balayage quotidien bascule vers cet état les invitations pending, sent ou opened dont l'expires_at est passé.
revokedInvitation révoquée par vos soins (revoked_at). Possible depuis pending, sent ou opened ; une invitation déjà submitted, expired ou revoked ne peut plus être révoquée (422).

expires_at fait foi, pas state. expires_at vaut la date de création plus 14 jours, et c'est cette date qui coupe le lien : passé ce point, le formulaire répond 410, immédiatement. L'état, lui, suit avec du retard : le balayage qui bascule l'invitation vers expired ne passe qu'une fois par jour, donc une invitation dépassée peut rester en sent jusqu'à 24 heures. Pour savoir si une invitation est encore utilisable, comparez expires_at à l'heure courante plutôt que d'attendre l'état expired.

Les horodatages sont en ISO 8601 avec le décalage d'Europe/Paris (+02:00 en été, +01:00 en hiver). L'API n'émet jamais de suffixe Z.

Étape 1 : envoyer une invitation​

Cet appel envoie un e-mail réel à l'adresse recipient_email - il n'y a pas de sandbox, et un e-mail parti ne se rappelle pas. Pour une répétition sans tiers réel, créez une fiche d'essai (Clients et fournisseurs) et indiquez votre propre adresse en destinataire : vous recevez l'e-mail et parcourez le formulaire vous-même. La révocation (étape 3) désactive le lien, mais ne rappelle pas l'e-mail.

curl -X POST https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/parties/YOUR_PARTY_ID/update_invitations \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"party_update_invitation": {
"recipient_email": "comptabilite@fournisseur.example",
"custom_message": "Merci de confirmer votre IBAN avant le 15 du mois.",
"communication_language": "fr"
}
}'

Réponse 201, abrégée aux champs utiles ici :

{
"data": {
"id": 8,
"party_id": 42,
"recipient_email": "comptabilite@fournisseur.example",
"state": "sent",
"custom_message": "Merci de confirmer votre IBAN avant le 15 du mois.",
"communication_language": "fr",
"sent_at": "2026-07-31T09:15:00+02:00",
"expires_at": "2026-08-14T09:15:00+02:00"
}
}

recipient_email est obligatoire. custom_message est optionnel et apparaît dans le corps de l'e-mail. communication_language (fr ou en) est optionnel ; à défaut, Scribee utilise la langue de communication de la fiche, puis fr - la réponse renvoie toujours la langue retenue sur communication_language, sur cet appel comme sur les trois autres (GET liste, GET unitaire, DELETE). Le scope read write est requis. Chaque appel crée une invitation et un lien distincts, y compris quand une invitation ouverte existe déjà pour le même tiers.

Le 201 et l'état sent confirment que l'e-mail a été mis en file d'envoi, pas qu'il a été délivré : l'envoi est traité en arrière-plan et son résultat n'est pas exposé. Si vous omettez l'objet party_update_invitation qui enveloppe le corps de la requête, la réponse est un 400 dans l'enveloppe d'erreur habituelle, avec error à bad_request et le message "Le corps de la requête est manquant ou mal formé".

Étape 2 : suivre vos invitations​

GET .../parties/{party_id}/update_invitations retourne toutes les invitations du tiers, sans pagination : la réponse contient data sans objet meta. Le scope read suffit.

curl https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/parties/YOUR_PARTY_ID/update_invitations \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
{
"data": [
{
"id": 8,
"party_id": 42,
"state": "submitted",
"submitted_at": "2026-08-02T14:03:00+02:00",
"expires_at": "2026-08-14T09:15:00+02:00"
}
]
}

Une invitation seule se lit sur GET /api/v1/party_update_invitations/{id} - notez le chemin sans workspace_id : l'invitation est référencée directement par son id (Votre premier appel). C'est l'appel à interroger pour observer le passage à submitted : aucun webhook n'est émis pour cet événement, les webhooks couvrent les événements de facturation (voir Webhooks).

Étape 3 : révoquer une invitation​

Cet appel désactive le lien immédiatement et définitivement : le formulaire répond ensuite 410 au destinataire. Il ne supprime pas l'invitation, qui reste consultable en GET avec l'état revoked. L'appel échoue sur une invitation déjà submitted, expired ou revoked : ces trois états sont terminaux et l'appel répond 422 sans rien changer (voir plus bas).

curl -X DELETE https://app.scribee.tech/api/v1/party_update_invitations/8 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Réponse 200 avec l'invitation mise à jour :

{
"data": {
"id": 8,
"party_id": 42,
"state": "revoked",
"revoked_at": "2026-08-01T10:00:00+02:00"
}
}

Le scope read write suffit.

Étape 4 : inviter en masse​

Cet appel envoie un e-mail réel à chaque tiers éligible du lot, à l'adresse de son contact principal - jusqu'à 500 identifiants par appel. Le traitement est asynchrone : la réponse 202 confirme la prise en charge, pas la délivrance. Le scope read write est requis.

curl -X POST https://app.scribee.tech/api/v1/workspaces/YOUR_WORKSPACE_ID/parties/update_invitations/batch \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"batch": {
"party_ids": [42, 43, 57],
"custom_message": "Merci de vérifier vos informations avant le 1er septembre."
}
}'

Réponse 202 :

{
"enqueued_count": 2,
"skipped_count": 1,
"message": "Envoi d'invitations lancé pour 2 tiers."
}

Plusieurs cas d'exclusion alimentent skipped_count, notamment : le tiers n'a pas d'adresse e-mail sur son contact principal, ou une invitation ouverte (pending, sent ou opened, non expirée) existe déjà pour lui. Un identifiant qui n'existe pas dans le workspace n'apparaît dans aucun des deux compteurs : comparez la somme des deux à la taille de votre lot. La réponse ne détaille pas les tiers ignorés ; l'état par tiers se lit avec le GET de l'étape 2.

enqueued_count compte les tiers retenus pour traitement, pas les e-mails partis. Un échec sur un tiers donné est silencieux pour un appelant OAuth - aucune erreur, aucune notification - et laisse son invitation en pending. Rapprochez systématiquement le lot du GET de l'étape 2.

party_ids doit être présent et être un tableau JSON. Envoyé en scalaire, ou omis alors que batch porte un autre champ exploitable (custom_message), il est écarté avant validation et le lot est traité comme vide : l'appel répond 422 (voir plus bas). Quand batch ne contient aucun autre champ exploitable, la réponse est un 400 dans l'enveloppe d'erreur habituelle, avec error à bad_request et le message "Le corps de la requête est manquant ou mal formé". Il en va de même si vous omettez l'objet batch lui-même.

Ce que voit votre tiers​

L'e-mail contient un bouton vers le formulaire public hébergé par Scribee - une page web, distincte de l'API /api/v1/**. L'ouverture du formulaire fait passer l'invitation à opened ; la soumission met à jour la fiche et fait passer l'invitation à submitted. PUT /api/v1/p/parties/{token} documente le même mécanisme côté API partenaire : un endpoint sans authentification OAuth, où le jeton signé de l'URL est le seul facteur d'authentification. Le jeton n'est jamais communiqué par l'API - il n'existe que dans le lien envoyé au tiers - donc votre intégration ne l'appelle jamais elle-même.

Le formulaire permet au tiers de corriger : iban, bic, vat_identifier, legal_registration_id, legal_registration_scheme_id, ses adresses (party_addresses_attributes : création, modification, suppression) et ses contacts (party_contacts_attributes : name, email, phone). Le nom de la fiche et le reste de vos données ne passent pas par ce canal. À la soumission, le destinataire voit "Vos informations ont été mises à jour. Merci." et le lien devient inutilisable.

Ce qui se passe ensuite​

  • La fiche du tiers est mise à jour au moment de la soumission, dans la même transaction que le passage à submitted : quand vous lisez state: "submitted", les nouvelles données sont déjà sur la fiche (Clients et fournisseurs).
  • Rien ne part vers le PPF (Portail Public de Facturation) ni vers le réseau Peppol : une invitation ne touche que votre annuaire, et les factures déjà émises ne sont pas modifiées.
  • Passé 14 jours sans soumission, le lien répond 410 immédiatement ; l'état de l'invitation, lui, passe à expired au balayage quotidien suivant, jusqu'à 24 heures plus tard. Renvoyez alors une nouvelle invitation : chaque envoi crée un lien distinct.

Erreurs et cas limites​

422 : adresse destinataire manquante​

recipient_email vide à la création :

{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "La validation a échoué",
"details": {
"base": ["Une adresse e-mail destinataire est requise pour créer une invitation."]
}
}

Fournissez une adresse valide et rejouez l'appel.

422 : lot invalide​

Sur l'endpoint d'envoi en masse, le lot est refusé dès qu'il ne désigne aucun tiers : party_ids vide, absent ou envoyé autrement que comme un tableau JSON - ces trois cas donnent la même réponse.

{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "La validation a échoué",
"details": {
"base": ["Au moins un identifiant de tiers est requis."]
}
}

Plus de 500 identifiants retournent "Trop de tiers sélectionnés. Maximum 500 par lot.", au même format details.base. Envoyez party_ids comme un tableau JSON non vide, scindez le lot si nécessaire, et rejouez.

422 : invitation non révocable​

Sur DELETE /api/v1/party_update_invitations/{id}, une invitation déjà submitted, expired ou revoked répond 422 plutôt que de basculer vers revoked :

{
"error": "unprocessable_entity",
"code": "validation_failed",
"message": "La validation a échoué",
"details": {
"base": ["Cette invitation ne peut pas être révoquée dans son état actuel."]
}
}

Ces trois états sont terminaux : une fois atteints, l'invitation ne redevient jamais actionnable, y compris pour la révocation. Le state et le reste de la fiche restent inchangés. Une invitation dépassée mais pas encore balayée reste en sent et se révoque encore, sans rien changer pour le destinataire : son lien répond déjà 410.

404 Not Found​

Le tiers ou l'invitation n'existe pas, ou le tiers est un client dont l'entreprise n'est pas active en vente :

{
"error": "not_found",
"message": "La ressource demandée est introuvable"
}

Sur les deux routes plates - GET et DELETE /api/v1/party_update_invitations/{id} - l'invitation est cherchée uniquement dans les espaces de travail rattachés à votre application dont la liste d'IP autorise votre adresse source. Tout ce qui sort de ce périmètre est indistinctement introuvable : une adresse IP hors liste produit donc 404 ici, là où les trois routes portées par un workspace produisent 403.

Vérifiez l'identifiant et le rattachement du workspace ; si un identifiant connu répond soudain 404, vérifiez votre adresse IP source avant l'identifiant (Votre premier appel).

403 : espace de travail ou adresse IP refusés​

Sur les trois routes portées par un workspace - la liste, la création et l'envoi en masse - l'espace de travail est résolu avant l'action, et deux refus renvoient 403. Votre application n'a pas le droit d'atteindre cet espace de travail :

{
"error": "forbidden",
"message": "L'application n'a pas accès à cet espace de travail"
}

L'espace de travail a une liste d'IP autorisées qui ne couvre pas votre adresse source :

{
"error": "forbidden",
"message": "Cette adresse IP n'est pas autorisée pour cet espace de travail"
}

Un workspace_id qui ne correspond à aucun espace de travail reçoit le premier refus ci-dessus, mot pour mot : rien ne distingue un espace de travail inexistant d'un espace de travail que votre application n'a pas le droit d'atteindre. Ces deux messages désignent un problème de rattachement de workspace ou d'adresse IP source, pas de scope : GET /api/v1/workspaces liste les espaces de travail que votre application peut atteindre, et les accès workspace et IP sont configurés par Scribee - adressez-vous à votre contact. Sur GET et DELETE /api/v1/party_update_invitations/{id}, qui ne portent pas de workspace_id, aucun de ces deux cas n'est distinguable : les deux répondent 404.

403 Forbidden : scope insuffisant​

Le scope read write crée une invitation, l'envoie en masse et la révoque ; sur la révocation, read destroy fonctionne tout aussi bien. La lecture demande read :

{
"error": "forbidden",
"message": "Vous n'êtes pas autorisé à effectuer cette action"
}

Redemandez un token avec les scopes voulus (Authentification).

410 : ce que voit le tiers sur un lien mort​

Un lien révoqué, expiré ou déjà utilisé répond au destinataire :

{
"error": "invalid_or_expired_token",
"message": "Ce lien de mise à jour est invalide, expiré ou déjà utilisé."
}

invitation_not_actionable ne couvre pas toutes les invitations non soumissibles : une invitation déjà soumise, révoquée ou expirée est rejetée plus tôt, à la résolution du jeton, avec invalid_or_expired_token. invitation_not_actionable, et son message "Cette invitation ne peut pas être soumise dans son état actuel.", ne concerne que le reliquat d'états atteignant la soumission. Votre API n'est pas concernée ; gardez ces messages en tête pour le support de vos tiers.

Pages liées​