API Prosperian

API HTTP publique de Prosperian. Elle permet de piloter le flux outbound de bout en bout — sourcer des prospects → créer une séquence → y attacher une liste → l'activer — depuis n'importe quel client HTTP.

  • Base URL : https://app.prosperian.co
  • Format : JSON (requêtes et réponses). Toutes les routes sont en POST.
  • Spec machine-lisible : openapi.yaml (OpenAPI 3.1, importable dans Postman / Swagger / pour générer un client).
  • MCP : la même surface est exposée comme serveur MCP — voir Utilisation via MCP. Deux façons de s'y authentifier : une clé API, ou un connecteur OAuth 2.1 branché depuis claude.ai — voir Connecteur claude.ai (OAuth 2.1).

Sommaire


Authentification

Chaque requête doit porter votre clé API dans le header X-API-Key :

X-API-Key: psk_live_xxxxxxxxxxxxxxxxxxxxxxxx

Une clé est scopée à une seule organisation. Elle ne peut accéder qu'aux ressources de cette organisation : toute tentative d'accès à une autre organisation renvoie 403.

CasRéponse
Header X-API-Key absent ou clé invalide / révoquée401 { "error": "Clé API invalide" }
Clé valide mais organisation ciblée ≠ organisation de la clé403 { "error": "Clé API non autorisée pour cette organisation" }

Le serveur MCP accepte une seconde famille de credentials. Un connecteur branché depuis claude.ai présente un jeton d'accès OAuth (pmo_at_…) au lieu d'une clé API. Ce jeton ne vaut que sur /api/mcp : les endpoints HTTP décrits ci-dessous restent en X-API-Key. Voir Connecteur claude.ai (OAuth 2.1).

Obtenir une clé API

  1. Dans l'application : Paramètres → API et développeurs.
  2. Créez une clé. Le secret complet (psk_live_…) n'est affiché qu'une seule fois à la création — copiez-le et stockez-le en lieu sûr. L'app n'en conserve qu'un hash et les 4 derniers caractères (pour l'affichage).
  3. Pour révoquer une clé, supprimez-la depuis la même page. La révocation est immédiate.

Les endpoints de gestion des clés (/api/api-keys) sont réservés à l'interface (auth par session) et ne font pas partie de l'API publique.


Endpoints

POST /api/sourcing/prospects

Lance une recherche de prospects à partir d'une description en langage naturel et de filtres optionnels. La recherche s'exécute en asynchrone ; les prospects trouvés alimentent une liste.

Paramètres (corps JSON)

ChampTypeRequisDescription
criteria.descriptionstringDescription en langage naturel des prospects recherchés
criteria.industrystringFiltre secteur
criteria.companySizestringFiltre taille d'entreprise
criteria.locationstringFiltre localisation
criteria.revenuestringFiltre chiffre d'affaires
criteria.technologiesstringFiltre technologies
criteria.keywordsstringMots-clés additionnels
listNamestringNom de la liste créée (auto-généré si absent)
userContext.organizationIdstringSi fourni, doit correspondre à l'org de la clé

Exemple

curl -X POST https://app.prosperian.co/api/sourcing/prospects \
  -H "X-API-Key: psk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "criteria": {
      "description": "Fondateurs de fintech à Londres",
      "industry": "Financial Services",
      "location": "London, United Kingdom"
    },
    "listName": "Fintech London"
  }'

Réponse 200

{
  "success": true,
  "message": "Recherche de prospects lancée avec succès pour la liste \"Fintech London\"",
  "webhookResponse": {},
  "criteria": "Fondateurs de fintech à Londres",
  "listName": "Fintech London"
}

400 si criteria.description est absent.


POST /api/copilot/create-campaign

Crée une séquence outbound (campagne) avec une suite d'étapes ordonnées. La séquence est créée au statut draft — utilisez update-status pour l'activer.

Paramètres (corps JSON)

ChampTypeRequisDescription
namestringNom de la campagne
sequencearrayÉtapes ordonnées (voir ci-dessous)
sequence[].typestringType d'étape : mail_send, linkedin_message, logic_wait, …
sequence[].subjectstringObjet (pour les étapes email)
sequence[].messagestringCorps du message
sequence[].delaynumberDélai en jours (pour les étapes d'attente)
descriptionstringDescription de la campagne
organization_idstringDoit correspondre à l'org de la clé si fourni

Exemple

curl -X POST https://app.prosperian.co/api/copilot/create-campaign \
  -H "X-API-Key: psk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Cold email fintech",
    "sequence": [
      { "type": "mail_send", "subject": "Quick question", "message": "Bonjour {{first_name}}, ..." },
      { "type": "logic_wait", "delay": 3 },
      { "type": "mail_send", "subject": "Re: Quick question", "message": "Petite relance ..." }
    ]
  }'

Réponse 200

{
  "success": true,
  "campaignId": "a1b2c3d4-0000-0000-0000-000000000000",
  "stepsCount": 3,
  "outreachSynced": true,
  "message": "Campagne créée"
}

POST /api/sequences/attach-prospects

Attache tous les prospects d'une liste à une séquence : déduplique, crée les prospects manquants, génère les tâches d'outreach et synchronise vers le système d'envoi. Les prospects présents dans la blacklist de l'organisation sont ignorés.

Opération potentiellement longue (jusqu'à 300 s pour de grandes listes).

Paramètres (corps JSON)

ChampTypeRequisDescription
sequence_idstringUUID de la séquence cible
prospect_list_idstringUUID de la liste de prospects à attacher

Exemple

curl -X POST https://app.prosperian.co/api/sequences/attach-prospects \
  -H "X-API-Key: psk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "sequence_id": "a1b2c3d4-0000-0000-0000-000000000000",
    "prospect_list_id": "e5f6a7b8-0000-0000-0000-000000000000"
  }'

Réponse 200

{
  "success": true,
  "added": 87,
  "tasksCreated": 174,
  "attachedInThisCall": 87,
  "totalInSequence": 87,
  "blacklisted": 3,
  "enrolled_skipped": 0,
  "locked_skipped": 0,
  "outreach": { "native": true, "provider": "unipile", "enrolled": 87 },
  "motif_enrolement": {
    "kind": "filtre",
    "resolus": 90,
    "enroles": 87,
    "liste_tronquee": false,
    "ventilation": { "liste_noire": 0, "deja_demarche": 2, "canal_absent": 1, "doublon": 0 }
  },
  "ai_import": { "ran": false, "keys": [], "generated": 0, "skipped_existing": 0 }
}

Champs additionnels possibles : blacklist_skips[] ({ id, label, reason }), prospect_errors[], message.

⚠️ totalInSequence est déprécié — utilisez attachedInThisCall. Les deux portent la même valeur (le volume rattaché par CET appel), mais totalInSequence porte l'ancienne sémantique « total de la séquence », qui est fausse : ce total est recalculé séparément, depuis les enrôlements réels.

⚠️ outreach n'est présent que sur le chemin natif. Ce bloc était documenté ici avec un champ pushed que le code n'émet jamais — la forme réelle est { native, provider, enrolled }, où enrolled est identique à motif_enrolement.enroles.

Lire motif_enrolement — POURQUOI ce nombre de leads, et pas un autre

Les cinq portes de sortie en 200 le portent : subset vide, liste vide ou introuvable, tout blacklisté, tout déjà en campagne, tout sous verrou 90 j, plus le chemin nominal. Le champ est une union discriminée par kind ; la liste des 11 valeurs et leur sens exact vivent dans openapi.yaml (schéma MotifEnrolement).

⚠️ Absent ≠ zéro. Un client qui ne trouve pas ce champ doit dire qu'il ne sait pas, jamais afficher une ventilation à zéro : la réponse peut venir d'un déploiement antérieur.

⚠️ enroles = INSÉRÉ, mesuré. L'écriture est synchrone : le nombre est celui que la base a réellement enregistré, pas celui qu'on a proposé. Un fait accompli, pas une intention.

⚠️ Un reliquat peut rester en vol. Sur une grosse liste, l'écriture s'arrête avant la limite de durée de l'appel et le reste est repris ensuite : non_tentes compte ces lignes, et non_tentes - non_confies seront enrôlées d'elles-mêmes en quelques minutes. non_confies est le sous-ensemble que personne ne reprendra tout seul.

⚠️ non_confies a DEUX causes, et arret_pause est le discriminant. Soit le reliquat a été REFUSÉ à la reprise — il faut alors relancer. Soit la séquence a été MISE EN PAUSE pendant l'écriture, et rien n'a alors été proposé du tout : non_confies vaut TOUT non_tentes, aucun refus n'a eu lieu, et le geste attendu est de réactiver la séquence. Lire arret_pause pour les distinguer ; la spécification OpenAPI porte le détail du champ.

⚠️ Ces nombres ne s'additionnent pas. resolus est ce que le FILTRE a vu : il n'inclut aucune des trois exclusions amont (blacklisted, enrolled_skipped, locked_skipped), retirées avant. L'égalité exacte porte trois termes, pas un : somme(ventilation) + exclus_pool + non_tentes = resolus - enroles — une ligne tombe dans un seul compartiment, et deux causes vivent HORS ventilation parce qu'elles ne sont pas des verdicts sur le prospect (aucun expéditeur sain ; écriture arrêtée avant la fin). ventilation compte désormais cinq compartiments (deja_enrole s'y est ajouté). ⚠️ Une exception : quand deja_enroles_illisible vaut true, l'égalité devient un minorant — la lecture des déjà-enrôlés ayant échoué, ces lignes sont écartées par la contrainte d'unicité sans tomber dans aucun terme. Sur ce chemin, ventilation.liste_noire vaut toujours 0 : l'exclusion est faite en amont et rapportée par blacklisted.

Réponse 502 — les leads ont bien été rattachés, mais l'ordre d'enrôlement n'a pas pu être transmis au moteur d'envoi : personne ne recevra rien. motif_enrolement.kind vaut envoi_impossible. added, count et tasksCreated restent renseignés (le rattachement, lui, a eu lieu). Rejouable tel quel : la création de tâches est dédupliquée, relancer ne double aucune ligne.

Réponse 503 — la liste noire n'a pas pu être lue. Aucun lead n'a été rattaché et aucune tâche créée : continuer sans elle risquerait de recontacter des personnes qui ont demandé à ne plus l'être. motif_enrolement.kind vaut liste_noire_illisible. Réessayez.


POST /api/sequences/update-status

Passe une séquence en active (démarre les envois) ou en paused (les arrête), et synchronise l'état vers le système d'outreach.

Pré-requis pour l'activation : un abonnement outreach actif et au moins un profil expéditeur configuré pour l'organisation. À défaut, l'endpoint renvoie une erreur.

Opération potentiellement longue (jusqu'à 300 s) : l'activation lit la liste cible en paginant (jusqu'à 20 000 lignes), charge la liste noire paginée, puis émet les ordres d'enrôlement par lots.

Paramètres (corps JSON)

ChampTypeRequisDescription
sequence_idstringUUID de la séquence
statusstringactive ou paused

Exemple

curl -X POST https://app.prosperian.co/api/sequences/update-status \
  -H "X-API-Key: psk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "sequence_id": "a1b2c3d4-0000-0000-0000-000000000000",
    "status": "active"
  }'

Réponse 200

{
  "ok": true,
  "status": "active",
  "provider": "unipile",
  "native": true,
  "enroll_items": 87,
  "motif_enrolement": {
    "kind": "filtre",
    "resolus": 90,
    "enroles": 87,
    "liste_tronquee": false,
    "ventilation": { "liste_noire": 1, "deja_demarche": 1, "canal_absent": 1, "doublon": 0 }
  }
}

Champs additionnels possibles : post_engager_ingestion.

⚠️ Cet exemple documentait getsales_synced et resynced_flow_uuid — deux champs que le code n'émet plus, les colonnes correspondantes ayant été supprimées.

enroll_items est le nombre de lignes réellement insérées en base par cet appel ; il vaut 0 sur toute sortie dont le motif_enrolement.kind n'est pas filtre. Même réserve de lecture que pour attach-prospects : absent ≠ zéro. Contrairement au chemin d'attachement, ventilation.liste_noire peut ici être non nul — l'activation filtre la liste noire elle-même.

Réponse 500 (panne d'enrôlement) — trois motifs : envoi_impossible, lecture_impossible, liste_noire_illisible. La séquence est rendue à son statut d'avant et les enrôlements réveillés par l'appel sont rendormis : rien n'a été activé, rien n'a été envoyé. L'appel est atomique du point de vue du client et se rejoue tel quel. Le corps porte ok: false, status (le statut restauré, pas active), enroll_items: 0 et motif_enrolement.

⚠️ Une activation qui n'enrôle personne pour une raison normale (brouillon sans canal, liste vide, tout filtré) reste un 200 : y rendre un 5xx déclencherait des tempêtes de reprise sur le geste le plus courant du produit. Lisez motif_enrolement, pas seulement le code HTTP.


Enrichissement

Enrichit des contacts ou des listes en email et/ou téléphone. Consomme des crédits de l'organisation (email ≈ 1, téléphone ≈ 10 par contact résolu) ; une réponse 402 est renvoyée si le solde est insuffisant. Pour une clé API, organizationId est optionnel (déduit de la clé).

Modèle asynchrone : certaines résolutions ne sont pas immédiates. Une réponse avec pending: true (ou des compteurs from…API) signifie qu'une partie est en cours côté fournisseur. Récupérez les résultats avec les endpoints de réconciliation (sync-pending-item, sync-pending-list, pull-list-item).

EndpointRôle
POST /api/enrichment/manualCréer + enrichir un nouveau contact (nom + société)
POST /api/enrichment/singleEnrichir un prospect existant (prospectId)
POST /api/enrichment/prospect-list-itemEnrichir une ligne de liste précise (itemId)
POST /api/enrichment/prontoEnrichir tous les prospects d'une liste existante
POST /api/enrichment/csvCréer une liste depuis des prospects fournis (≤ 1000) et l'enrichir
GET /api/enrichment/list-coverageCouverture LinkedIn/domaine d'une liste
POST /api/enrichment/sync-pending-itemRéconcilier une ligne en attente
POST /api/enrichment/sync-pending-listRéconcilier les lignes en attente d'une liste
POST /api/enrichment/pull-list-itemForcer le polling Pronto d'une ligne

Enrichir un contact — POST /api/enrichment/manual

ChampTypeRequisDescription
first_namestringPrénom
last_namestringNom
enrichmentTypesobject{ email?: boolean, phone?: boolean } — au moins un true
company_namestringSociété
company_domainstringDomaine (ex. stripe.com) — améliore le taux de match
linkedin_urlstringURL LinkedIn
organizationIdstringOptionnel avec une clé API
curl -X POST https://app.prosperian.co/api/enrichment/manual \
  -H "X-API-Key: psk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Jane",
    "last_name": "Doe",
    "company_domain": "stripe.com",
    "enrichmentTypes": { "email": true }
  }'
{
  "success": true,
  "source": "pronto",
  "data": { "email": "jane@stripe.com" },
  "itemId": "…",
  "listId": "…",
  "pending": false,
  "creditsDeducted": 1,
  "message": "Données enrichies immédiatement."
}

Enrichir une liste — POST /api/enrichment/pronto

ChampTypeRequisDescription
prospectListIdstringListe à enrichir
enrichmentTypesobject{ email?: boolean, phone?: boolean }
organizationIdstringOptionnel avec une clé API
curl -X POST https://app.prosperian.co/api/enrichment/pronto \
  -H "X-API-Key: psk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "prospectListId": "…", "enrichmentTypes": { "email": true, "phone": false } }'
{
  "success": true,
  "message": "Enrichissement terminé pour 120 prospects",
  "creditsDeducted": 110,
  "results": {
    "total": 120,
    "success": 110,
    "errors": 10,
    "fromDatabase": 30,
    "fromAirscaleAPI": 80
  }
}

Les autres endpoints (single, prospect-list-item, csv, list-coverage, sync-pending-*, pull-list-item) suivent les mêmes conventions d'auth et de crédits. Voir openapi.yaml pour les schémas request/response complets.


Gestion des erreurs

Les erreurs renvoient un statut HTTP 4xx/5xx et un corps JSON :

{ "error": "Message lisible", "details": "Détail technique optionnel" }
CodeSignification
400Requête invalide (champ requis manquant, statut non supporté)
401Clé API absente, invalide ou révoquée
402Crédits d'enrichissement insuffisants ({ error, required, available })
403Clé non autorisée pour l'organisation ciblée
404Ressource introuvable (prospect, item ou liste)
409Conflit. Trois émetteurs sur update-status, discriminés par code — c'est lui qui décide s'il faut réessayer, jamais le statut HTTP : activation_deja_en_cours (une autre activation tourne ; le verrou est pris avant toute écriture ⇒ réessayer est sûr), organisation_en_migration (gel temporaire ⇒ réessayer plus tard), no_healthy_sending_account (aucune boîte connectée ⇒ ne pas réessayer, un humain doit en connecter une). Sur les routes d'enrichissement : enrichissement déjà en cours pour l'organisation
500Erreur interne (le champ details précise la cause quand disponible). Sur update-status, recouvre aussi la panne d'enrôlement : la séquence est rendue à son statut d'avant, l'appel se rejoue tel quel
502attach-prospects : leads rattachés, mais l'ordre d'enrôlement n'a pas pu être transmis au moteur d'envoi. Rejouable (création de tâches dédupliquée)
503attach-prospects : la liste noire n'a pas pu être lue — rien n'a été rattaché, plutôt que de risquer de recontacter des personnes qui l'ont refusé. Réessayez

⚠️ Un 200 ne veut pas dire « des leads sont partis ». Sur les deux endpoints d'enrôlement, une sortie à zéro pour une raison normale (brouillon sans canal, liste vide, tout blacklisté, tout déjà en campagne) reste un 200 — c'est motif_enrolement qui porte le pourquoi. Les 5xx sont réservés aux pannes techniques, celles qui méritent une reprise.

Rate limiting

Restez raisonnable sur le volume et la fréquence des appels, en particulier pour attach-prospects (opération lourde). Un usage manifestement abusif peut être restreint sans préavis.

Les endpoints OAuth sont limités. Au-delà du débit autorisé, ils rendent 429 ; patientez avant de réessayer.

Utilisation via MCP

La même surface est exposée comme serveur MCP hébergé (Streamable HTTP) sur /api/mcp — implémentation dans le code source, outils dans le code source — pour piloter Prosperian depuis Claude Code (ou tout client MCP) sans écrire de requêtes HTTP :

find_leads → create_campaign → attach_prospects → start_sequence
Outil MCPEndpoint
find_leadsPOST /api/sourcing/prospects
create_campaignPOST /api/copilot/create-campaign
attach_prospectsPOST /api/sequences/attach-prospects
start_sequencePOST /api/sequences/update-status
enrich_contactPOST /api/enrichment/manual
enrich_listPOST /api/enrichment/pronto

⚠️ attach_prospects, enrich_contact, enrich_list et start_sequence (branche « activer ») ne s'exécutent PAS au premier appel. Ils passent par proposeOrExecute (le code source) : le premier appel crée une demande de validation en attente et n'exécute rien. Il faut un second appel portant un approval_action_id approuvé par un humain pour que executeApprovedAction fasse le travail — en appelant les fonctions internes, pas l'endpoint HTTP listé ci-dessus. La colonne « Endpoint » indique donc la surface équivalente, pas ce que l'outil déclenche. Seule exception : start_sequence avec status: 'paused' appelle directement POST /api/sequences/update-status.

Outils de lecture (stats) — pour que l'agent suive ses résultats :

Outil MCPEndpointRenvoie
get_campaign_statsPOST /api/copilot/stats/campaignfunnel + buckets d'une séquence (live)
list_campaignsPOST /api/copilot/stats/listcampagnes + compteurs (cache rollup) — découverte des sequence_id
get_org_statsPOST /api/copilot/stats/orgcrédits restants + totaux (prospects, listes, campagnes, RDV)
get_activity_reportPOST /api/copilot/stats/reportactivité par rep/séquence/liste (tâches, appels, RDV, issues)
get_objectionsPOST /api/copilot/stats/objectionsmatrice objections catégorie × canal
get_response_heatmapPOST /api/copilot/stats/heatmapmeilleurs créneaux de réponse par canal

Dépréciation — get_activity_report. Chaque entrée de perUser porte désormais displayName (string | null) et relation (agency | member | left | unknown). userName reste servi comme alias et vaut la chaîne vide quand l'auteur n'est pas nommable ; il sera retiré à la prochaine version majeure. relation dit comment l'auteur se rattache au compte : member = membre actuel, left = son compte d'origine est celui-ci mais il opère ailleurs, agency = opérateur de l'agence qui gère le compte, unknown = non rattachable (les compteurs restent justes, le nom est tu). Ne jamais afficher displayName nul tel quel.

Les outils de lecture sont accessibles à toute clé API valide (modèle per-org, plein accès).

Installation avec une clé API (Claude Code, ou tout autre client MCP) :

claude mcp add --transport http prosperian \
  https://app.prosperian.co/api/mcp \
  -H "x-api-key: psk_live_xxx"

⚠️ Le CLI npm @prosperian/mcp n'a jamais été publié : l'endpoint hébergé ci-dessus est la seule installation valide. L'authentification accepte Authorization: Bearer comme x-api-key, avec la clé API per-org.

Connecteur claude.ai (OAuth 2.1)

claude.ai ne sait pas coller une clé API dans un header : pour ajouter un serveur MCP, il exige un serveur d'autorisation OAuth. Prosperian en sert un. On ajoute donc Prosperian comme connecteur personnalisé (claude.ai → Settings → Connectors → Add custom connector) avec la seule URL du serveur MCP :

https://app.prosperian.co/api/mcp

Le reste est automatique. Claude suit la chaîne 401 → métadonnées de ressource protégée (RFC 9728) → métadonnées du serveur d'autorisation (RFC 8414) → enregistrement dynamique du client (RFC 7591) → écran de consentement → jetons. Un seul maillon manquant produit « Couldn't reach the MCP server », sans autre explication.

EndpointRôle
GET /.well-known/oauth-protected-resource/api/mcpRFC 9728 — décrit la ressource et pointe le serveur d'autorisation. La forme nue /.well-known/oauth-protected-resource est servie en alias
GET /.well-known/oauth-authorization-serverRFC 8414 — métadonnées du serveur d'autorisation
POST /api/oauth/registerRFC 7591 — enregistrement dynamique du client (non authentifié par nature, limité en débit)
GET /oauth/authorizeécran de consentement, en session applicative
POST /api/oauth/tokenéchange du code d'autorisation, puis rafraîchissement

Ce qu'il faut savoir avant de brancher :

  • Le consentement est réservé aux administrateurs de l'organisation. Un membre non administrateur voit l'écran refuser, avec le motif écrit.
  • Le connecteur est lié à l'organisation affichée sur l'écran de consentement, pas à l'organisation « active » au moment du clic : basculer de client dans un autre onglet pendant que l'écran est ouvert ne détourne pas le jeton.
  • PKCE S256 est obligatoire. Le client est public (aucun secret) et seuls les redirect_uri sur claude.ai / claude.com, ou en boucle locale (http://127.0.0.1, http://localhost, port ignoré — RFC 8252 §7.3), sont enregistrables. Un hôte hors de cette liste est refusé en 400 nommant l'URI, jamais en silence.
  • Durées de vie : code d'autorisation 60 s, jeton d'accès 1 h, jeton de rafraîchissement 60 j. Le rafraîchissement tourne à chaque usage : un jeton rejoué est refusé, et un rejeu caractérisé révoque toute la chaîne — c'est la détection de vol de la BCP OAuth 2.1. En cas de doute sur un connecteur, révoquez-le depuis l'application.
  • Le périmètre est celui d'une clé API. Le scope annoncé (mcp) désigne l'accès au serveur MCP, pas un sous-ensemble d'outils : un connecteur peut tout ce que peut une clé API de l'organisation. Les outils d'écriture restent derrière le flux d'approbation décrit plus haut.
  • L'accès est revérifié à chaque appel, pas seulement au consentement : désactiver la personne, ou couper son lien d'agence, lui retire l'accès sans avoir à révoquer son jeton.

Révoquer un connecteur : Paramètres → API et développeurs, carte « Connecteurs MCP ». Elle liste qui a branché quoi, quand, et la date du dernier appel. Révoquer coupe le grant et ses jetons, immédiatement. Chacun peut révoquer le sien ; un administrateur peut révoquer n'importe lequel. Les clés API ne sont pas affectées.

API HTTP — Documentation Prosperian