Download OpenAPI specification:Download
Lecture des résultats agrégés Survey et engagomètre pour les outils BI.
API publique en lecture seule des résultats Listen d'une corporation : campagnes Survey et engagomètre (EM), itérations, catalogue de questions, filtres, détails, diagnostic et résultat d'une question. Elle est destinée aux outils de BI : sept opérations GET, des résultats agrégés, aucune donnée individuelle.
GET /v1/surveys → campagnes lancées de la corporation.GET /v1/surveys/{surveyId}/iterations →
itérations et snapshots.GET .../questions → identifiants et ordre des
questions ; GET .../filters → valeurs utilisables
dans filters (voir Filtres).GET .../results → résultats par question, paginés
(voir Pagination et curseurs) ;
GET .../diagnostic → diagnostic d'une population ;
GET .../questions/{questionId}/results → une
question.Exemple, avec la clé dans une variable d'environnement (jamais dans l'URL) :
curl -H "Authorization: Bearer $ZEST_API_KEY" \
"https://<hôte>/v1/surveys?limit=10"
Authorization: Bearer <clé> sur chaque appel. Ne placez jamais la clé dans une URL
(chemin ou query) : les URL sont journalisées par les proxys et les navigateurs.
zpk_v1_…. La clé est un secret ; elle n'est affichée qu'à sa création.survey:read) des résultats Survey et EM d'une seule
corporation, celle de la clé. Aucune écriture, aucune autre corporation.token_expired) ; une clé inconnue ou mal formée l'est aussi (401
token_invalid, token_missing sans en-tête).token_revoked).203.0.113.7 ou 203.0.113.0/24),
dans l'écran API Keys ; sans liste, la clé s'utilise depuis n'importe quelle adresse.
Une requête qui vient d'une autre adresse est refusée (403 ip_not_allowed) avant tout
calcul et ne compte dans aucun quota. L'adresse prise en compte est celle que voit Zest
en entrée de la plateforme : pour un outil BI hébergé, c'est l'adresse de sortie
(publique) de son hébergeur, qui doit être stable.Les routes de résultats acceptent un paramètre filters : un objet JSON encodé en URL.
Absent, il désigne l'ensemble de la corporation.
{"type": "overall"} (défaut) : une cohorte pour toute la corporation.{"type": "custom", "segments": [...]} : une cohorte (segment) par sélection de
segments. Le diagnostic n'accepte qu'un seul segment.restrictions : sélections appliquées à toutes les cohortes.department, location, team, smart_group, role, attribute
et question. Les identifiants et valeurs utilisables proviennent de
listResultFilters, pour la même itération et la même
vue (viewMode dynamic ou static avec snapshotId). Un identifiant inconnu est
refusé (400 invalid_filters).filters une fois encodé. Une
combinaison hors budget est refusée (400 filter_budget_exceeded), jamais tronquée.Listes : limit (défaut 50, maximum 100 ; 50 pour les résultats) et cursor renvoyé
dans meta.nextCursor (null en fin de liste). Tri stable par identifiant croissant ; aucune garantie
d'instantané entre deux pages d'une liste. Résultats : seule la collection questions
est paginée ; participation et scores globaux sont calculés sur le périmètre complet et
répétés sur chaque page.
Le curseur est opaque : ne le décodez pas, ne le fabriquez pas, ne le stockez pas
au-delà de la lecture en cours. Il est lié à la corporation, à la route et à tous les
paramètres de la requête. Rejouer un curseur sur une autre route, avec d'autres
paramètres ou pour une autre corporation renvoie 400 invalid_cursor : modifiez un
paramètre, repartez sans curseur.
Chaque mesure est un objet {status, value}. status vaut :
available : valeur présente ;suppressed : masquée par les règles d'anonymat de l'itération ;no_data : aucune réponse ;not_applicable : sans objet pour ce type de question ou cette comparaison ;not_available : non calculable, par exemple benchmark indisponible.Hors available, la valeur est toujours null. Zéro est une valeur, jamais un
substitut à une absence ou à un masquage.
Seuls des agrégats sont exposés : aucune identité, aucun libellé de membre, aucune
réponse individuelle ni texte libre. Une mesure dérivée (évolution, écart au benchmark)
n'est calculée que si tous ses opérandes sont available, sinon suppressed. Les règles
de masquage et leurs limites sont détaillées dans Anonymat.
L'API applique exactement la règle de la plateforme web : les chiffres de l'API sont identiques à ceux affichés à l'écran, sans écart à expliquer. Elle ne protège ni plus ni moins que le web.
Seuil. Chaque campagne a un seuil d'anonymat (anonymousThreshold de
listSurveys), celui configuré pour la campagne, 3 par
défaut. Il est le même pour toutes les itérations de la campagne.
Quand une cohorte est masquée. Pour chaque question et chaque cohorte, on additionne
les répondants (answered) et les personnes qui ont passé la question (skipped). Si
ce total est strictement inférieur au seuil, la cohorte est masquée pour cette
question, à condition que la campagne soit anonyme (private) ou mixte (open) avec au
moins une réponse anonyme dans la cohorte. Les campagnes nominatives (public), ou
mixtes dont toutes les réponses de la cohorte sont nominatives, ne sont jamais masquées.
Exemple avec un seuil de 3 : 2 répondants et 1 personne ayant passé la question font 3,
la cohorte est visible ; 1 répondant et 1 personne ayant passé la question font 2, elle
est masquée.
Sous le seuil.
suppressed) : score, écart type, polarité et distribution, ainsi que toute
mesure dérivée qui en dépend (évolution, écart au benchmark, score de thème, écarts du
diagnostic).answered, skipped et commentCount, et la participation
(invited, submitted, rate). Le nombre de personnes ayant répondu reste donc
visible, et révèle qu'un groupe est petit. Le rythme quotidien de participation
(daily) est vide pour une cohorte masquée et pour un segment dont le nombre de
soumissions est sous le seuil.question) révèle ces réponses par ses effectifs ; lorsqu'elle est masquée, ses
effectifs et sa participation le sont aussi.Pas de masquage par modalité. La règle s'applique à la cohorte entière, jamais à une modalité de réponse : dans une cohorte visible, une modalité choisie par une seule personne apparaît dans la distribution.
Aucune protection entre cohortes. Le masquage est évalué cohorte par cohorte, dans une même requête, et ne protège pas contre le recoupement de plusieurs cohortes ou de plusieurs requêtes. En comparant deux cohortes proches, ou en soustrayant des cohortes visibles d'une cohorte plus large, on peut retrouver par différence les effectifs et les réponses d'un petit groupe masqué. Des cohortes imbriquées d'une même requête (un service avec et sans ses sous-groupes, l'ensemble de la corporation et la même population restreinte) donnent même les valeurs exactes. Zest ne l'empêche pas : les règles sont les mêmes que sur le web, où le super admin consulte ses propres données.
Responsabilité du client et bonnes pratiques. L'usage des résultats relève de la responsabilité du client, conformément à sa politique interne de confidentialité.
Les résultats sont à jour à 30 secondes près : Zest peut servir une réponse depuis son
cache de calcul interne de 30 secondes (les mêmes requêtes répétées dans ce délai donnent le
même résultat, et les pages d'une même requête sont calculées sur le même instantané). Ce
cache est interne à Zest : la réponse HTTP porte Cache-Control: no-store, et les clients
et les proxys ne doivent pas la mettre en cache. La liste des
destinataires d'une campagne, base des effectifs d'invités et de la participation en vue
dynamique, peut avoir jusqu'à 10 minutes de retard. Les valeurs de listResultFilters
(départements, sites, équipes, smart groups, rôles, attributs et questions suivies) sont
mises en cache et peuvent avoir jusqu'à 5 minutes de retard (les résultats restent à
30 secondes). Le benchmark Zest est indisponible pendant le pilote (benchmark=zest :
400 invalid_parameter).
Au plus 4 itérations comparées pour un engagomètre, 2 pour un sondage (1 pour le
diagnostic), les budgets de filtres ci-dessus (5 cohortes) et filters ≤ 6 Kio une fois
encodé en pourcentage, tel qu'il figure dans l'URL (au-delà : 400 filter_budget_exceeded).
L'ingress refuse une ligne de requête de plus de 8 Kio par un 414 hors de l'enveloppe
d'erreur ci-dessous : ces bornes l'évitent, y compris avec tous les autres paramètres à
leur maximum. Les résultats paginent par 50 questions au plus. Le
benchmark zest est refusé pendant le pilote (400 invalid_parameter). Une combinaison
hors budget est refusée avant calcul, jamais tronquée.
Toute erreur a la forme {ok: false, error: {code, message}, requestId}. Les codes sont
stables et la liste est extensible : traitez un code inconnu selon son statut HTTP. Le
message est informatif ; citez requestId au support.
| Statut | Codes principaux | Conduite |
|---|---|---|
| 400 | invalid_request, invalid_parameter, unknown_parameter, invalid_filters, filter_budget_exceeded, invalid_cursor, snapshot_required, snapshot_mismatch, unsupported_lang |
Corriger la requête, ne pas rejouer. |
| 401 | token_missing, token_invalid, token_expired, token_revoked |
Vérifier ou renouveler la clé. |
| 403 | insufficient_scope, module_disabled, corporation_disabled (corporation désactivée ou pas encore ouverte à l'API publique), ip_not_allowed (adresse IP hors de la liste autorisée de la clé) |
ip_not_allowed : appeler depuis une adresse autorisée ou faire modifier la liste par un super admin (écran API Keys). Autres codes : contacter Zest. |
| 404 | resource_not_found |
Identifiant inconnu ou d'une autre corporation. |
| 405 | method_not_allowed |
Seul GET est exposé (Allow: GET). |
| 409 | results_not_available |
Itération non démarrée : réessayer plus tard. |
| 429 | rate_limited |
Réessayer après Retry-After. |
| 503 | service_unavailable |
Réessayer après Retry-After s'il est présent, sinon avec backoff exponentiel. |
| 504 | computation_timeout |
Réduire filtres ou comparaisons, puis réessayer avec backoff. |
Le code client_closed_request (408) ne figure pas au contrat : il ne sert qu'à terminer en
interne une requête que le client a déjà abandonnée, et aucun client ne le reçoit.
Pour 429, 503 et 504, réessayez avec un backoff exponentiel plafonné (par exemple 1, 2,
4, 8 secondes, avec une part d'aléa) et respectez Retry-After (en secondes) lorsqu'il
est présent. Ne réessayez pas une erreur 4xx autre que 409 et 429 sans changer la
requête.
Valeurs initiales, à calibrer avant la généralisation : 120 requêtes par minute pour
l'ensemble des clés d'une corporation, 60 par minute pour une clé, et 20 échecs
d'authentification par minute et par adresse IP. Au-delà : 429 rate_limited avec
Retry-After. Ces valeurs peuvent évoluer sans changement de version ; fiez-vous à
Retry-After plutôt qu'à un chiffre. Répartissez les appels (par exemple une
itération à la fois) et ne parallélisez pas les calculs lourds (results,
diagnostic, résultat d'une question).
Le chemin (/v1) ne change que pour une rupture. info.version suit le semver du
contrat : patch pour une correction de description ou d'exemple, mineure pour un ajout
compatible, majeure pour une rupture publiée sous une nouvelle version d'URL. Les
clients ignorent les champs inconnus de réponse et les codes d'erreur inconnus. Les
changements sont consignés dans contract/CHANGELOG.md du dépôt Philophrosyne.
Cette page est publique et statique : elle ne demande aucune clé, n'en stocke aucune et
ne propose pas de console d'essai. Le contrat OpenAPI est téléchargeable sans
authentification (lien « Download » ci-dessus ; fichiers openapi.yaml et
openapi.json à côté de cette page). Les exemples utilisent des identifiants et des
clés factices.
Contact technique : dev@zestmeup.com.
Campagnes Survey et EM lancées de la corporation (les brouillons ne sont jamais listés), des plus récentes aux plus anciennes, y compris celles sans résultat (statut et disponibilité explicites). Une liste vide renvoie 200 et un tableau vide.
| type | string (SurveyType) Enum: "survey" "engageometer" Restreint la liste à un type de campagne. |
| lang | string^[a-z]{2,3}(_[A-Z]{2})?$ Langue des libellés (code produit, ex. |
| limit | integer [ 1 .. 100 ] Default: 50 Taille de page des listes du catalogue. |
| cursor | string [ 1 .. 1024 ] characters Curseur opaque issu de |
{- "ok": true,
- "data": [
- {
- "id": "1201",
- "type": "survey",
- "title": "Baromètre qualité de vie 2026",
- "status": "ongoing",
- "frequency": "biannual",
- "privacy": "private",
- "anonymousThreshold": 5,
- "availableViewModes": [
- "dynamic",
- "static"
], - "resultsAvailability": "available",
- "iterationCount": 2,
- "latestIterationId": "8801",
- "createdAt": "2025-08-28T09:12:00Z",
- "updatedAt": "2026-03-02T07:00:00Z"
}, - {
- "id": "1260",
- "type": "survey",
- "title": "Onboarding nouveaux arrivants",
- "status": "ongoing",
- "frequency": "oneshot",
- "privacy": "open",
- "anonymousThreshold": 3,
- "availableViewModes": [
- "dynamic"
], - "resultsAvailability": "no_iteration",
- "iterationCount": 0,
- "latestIterationId": null,
- "createdAt": "2026-09-15T14:30:00Z",
- "updatedAt": "2026-09-15T14:30:00Z"
}
], - "meta": {
- "requestId": "req_01J9Z3K7Q3",
- "nextCursor": "eyJsYXN0SWQiOiIxMjYwIn0"
}
}Itérations de la campagne, de la fin la plus récente à la plus ancienne (la première est
l'itération par défaut des résultats et latestIterationId de la campagne), avec dates,
état, mode de confidentialité, snapshot disponible pour la vue statique et
disponibilité des benchmarks. Les itérations de test ne sont jamais exposées.
| surveyId required | string (Id) ^[1-9][0-9]{0,18}$ Identifiant de campagne (Survey ou EM). |
| limit | integer [ 1 .. 100 ] Default: 50 Taille de page des listes du catalogue. |
| cursor | string [ 1 .. 1024 ] characters Curseur opaque issu de |
{- "ok": true,
- "data": [
- {
- "id": "8801",
- "surveyId": "1201",
- "startAt": "2026-03-02T07:00:00Z",
- "endAt": "2026-03-23T21:59:59Z",
- "state": "ended",
- "privacy": "private",
- "resultsAvailability": "available",
- "snapshotId": "65f1c0a2b3d4e5f6a7b8c9d0",
- "benchmarks": {
- "overall": "available",
- "zest": "not_available"
}
}, - {
- "id": "8650",
- "surveyId": "1201",
- "startAt": "2025-09-08T07:00:00Z",
- "endAt": "2025-09-29T21:59:59Z",
- "state": "ended",
- "privacy": "private",
- "resultsAvailability": "available",
- "snapshotId": "66de1f0a9b3c4d5e6f708192",
- "benchmarks": {
- "overall": "available",
- "zest": "not_available"
}
}
], - "meta": {
- "requestId": "req_01J9Z3K7Q5",
- "nextCursor": null
}
}Questions de l'itération (hors sections, textes et images) dans l'ordre d'affichage,
chaque matrice juste avant ses sous-questions, avec type, libellé, échelle, options et
thème. Aucune réponse. position est le rang dans cet ordre, à partir de 0.
templateId identifie la même question d'une itération à l'autre.
| surveyId required | string (Id) ^[1-9][0-9]{0,18}$ Identifiant de campagne (Survey ou EM). |
| iterationId required | string (Id) ^[1-9][0-9]{0,18}$ Identifiant d'itération ; doit appartenir à la campagne. |
| lang | string^[a-z]{2,3}(_[A-Z]{2})?$ Langue des libellés (code produit, ex. |
| limit | integer [ 1 .. 100 ] Default: 50 Taille de page des listes du catalogue. |
| cursor | string [ 1 .. 1024 ] characters Curseur opaque issu de |
{- "ok": true,
- "data": [
- {
- "id": "77001",
- "templateId": "5101",
- "iterationId": "8801",
- "position": 0,
- "type": "scale",
- "label": "Je recommanderais mon entreprise à un proche.",
- "description": null,
- "isOptional": false,
- "isScored": true,
- "contributesToOverallScore": true,
- "isStarred": false,
- "starLabel": null,
- "theme": {
- "id": "41",
- "kind": "custom",
- "label": "Bien-être au travail"
}, - "matrixParentId": null,
- "responseScale": {
- "min": 0,
- "max": 10
}, - "options": [ ]
}, - {
- "id": "77002",
- "templateId": "5102",
- "iterationId": "8801",
- "position": 1,
- "type": "single_choice",
- "label": "Quel est votre mode de travail principal ?",
- "description": null,
- "isOptional": true,
- "isScored": false,
- "contributesToOverallScore": false,
- "isStarred": true,
- "starLabel": "Mode de travail",
- "theme": null,
- "matrixParentId": null,
- "responseScale": null,
- "options": [
- {
- "id": "310021",
- "position": 0,
- "label": "Sur site",
- "score": null
}, - {
- "id": "310022",
- "position": 1,
- "label": "Hybride",
- "score": null
}, - {
- "id": "310023",
- "position": 2,
- "label": "Télétravail",
- "score": null
}
]
}
], - "meta": {
- "requestId": "req_01J9Z3K7Q7",
- "nextCursor": null
}
}Valeurs de filtres utilisables dans le paramètre filters des résultats de cette
itération : départements, sites, équipes, smart groups, rôles, attributs et
questions étoilées. Aucune liste de membres ni réponse individuelle. En vue
dynamique, les départements, sites et équipes sont ceux de la population de
l'itération (avec leurs ancêtres) et les options d'attribut celles portées par cette
population. En vue statique, l'organisation, les smart groups et les attributs
proviennent du snapshot demandé. Les smart groups sont tous ceux de la corporation
(vue dynamique) ou du snapshot (vue statique). Les valeurs sont ordonnées par dimension
dans l'ordre de FilterDimension, puis par identifiant, les rôles suivant l'ordre de
RoleId. Une question filtrable est identifiée par son templateId.
| surveyId required | string (Id) ^[1-9][0-9]{0,18}$ Identifiant de campagne (Survey ou EM). |
| iterationId required | string (Id) ^[1-9][0-9]{0,18}$ Identifiant d'itération ; doit appartenir à la campagne. |
| dimension | string (FilterDimension) Enum: "department" "location" "team" "smart_group" "role" "attribute" "question" Restreint la liste à une dimension de filtre. |
| viewMode | string (ViewMode) Default: "dynamic" Enum: "dynamic" "static"
|
| snapshotId | string (SnapshotId) ^[0-9a-f]{24}$ Snapshot de l'itération de référence, obligatoire en vue statique et interdit en vue
dynamique. Doit appartenir à l'itération (400 |
| lang | string^[a-z]{2,3}(_[A-Z]{2})?$ Langue des libellés (code produit, ex. |
| limit | integer [ 1 .. 100 ] Default: 50 Taille de page des listes du catalogue. |
| cursor | string [ 1 .. 1024 ] characters Curseur opaque issu de |
{- "ok": true,
- "data": [
- {
- "dimension": "department",
- "id": "501",
- "label": "Direction commerciale",
- "parentId": null
}, - {
- "dimension": "department",
- "id": "502",
- "label": "Ventes Europe",
- "parentId": "501"
}, - {
- "dimension": "team",
- "id": "20431",
- "label": "Camille Martin",
- "parentId": null
}, - {
- "dimension": "role",
- "id": "manager",
- "label": "Managers"
}, - {
- "dimension": "attribute",
- "id": "930",
- "label": "Contrat",
- "valueType": "text",
- "options": [
- {
- "value": "cdi",
- "label": "CDI"
}, - {
- "value": "cdd",
- "label": "CDD"
}
]
}, - {
- "dimension": "question",
- "id": "5102",
- "label": "Mode de travail",
- "questionType": "single_choice",
- "responseScale": null,
- "options": [
- {
- "value": "310021",
- "label": "Sur site"
}, - {
- "value": "310022",
- "label": "Hybride"
}, - {
- "value": "310023",
- "label": "Télétravail"
}
]
}
], - "meta": {
- "requestId": "req_01J9Z3K7Q9",
- "nextCursor": "eyJkaW1lbnNpb24iOiJxdWVzdGlvbiJ9"
}
}Résultats agrégés par question, cohorte et itération : scores, distributions, polarité, participation et score global (Survey) ou expérience collaborateur (EM).
Pagination. Seule la collection questions est paginée : limit compte des
questions distinctes de l'itération de référence, dans l'ordre de
listIterationQuestions. Participation, scores globaux, cohortes et itérations sont
calculés sur le périmètre complet et répétés sur chaque page ; themes ne liste que
les thèmes de la page. Les pages d'une même requête sont calculées sur le même
instantané pendant 30 secondes (résultats à jour à 30 secondes près).
Une ligne par question, itération et cohorte. Chaque question de la page produit
exactement une ligne par itération (référence puis comparées, dans l'ordre demandé) et
par cohorte. Une question dont le templateId n'existe pas dans une itération
comparée y reçoit une ligne dont toutes les mesures valent no_data (questionId et
templateId de la référence). Une question qui n'existe que dans une itération
comparée n'est pas listée. Le benchmark zest n'a de lignes que pour l'itération de
référence.
Cohortes. Libellés neutres et non localisés : Overall, Segment N (rang dans
filters.segments, à partir de 1), Benchmark overall, Benchmark Zest. Une cohorte
se reconnaît à kind, segmentIndex et benchmark, jamais à son libellé.
Masquage. Une ligne sous le seuil d'anonymat de l'itération garde ses effectifs
(answered, skipped, commentCount) ; score, écart type, polarité et distribution
valent suppressed. Une cohorte définie par des réponses (condition ou restriction de
type question) révèle ces réponses par ses effectifs : lorsqu'elle est masquée, ses
effectifs le sont aussi (answered, skipped, commentCount, et en participation
invited, submitted, rate). La participation d'une autre cohorte masquée garde
invited, submitted et rate ; le rythme daily est vide pour une cohorte masquée,
pour un benchmark et pour un segment sous le seuil d'anonymat.
Benchmark. Un benchmark demandé mais indisponible renvoie 200 avec
benchmark.status à not_available et son motif, sans cohorte benchmark. Pendant le
pilote, benchmark=zest est refusé (400 invalid_parameter).
Bornes. limit au plus 50 questions par page, au plus 5 cohortes et, selon le type
de campagne, 4 itérations comparées (engagomètre, 5 itérations lues) ou 2 (sondage,
3 itérations lues). Une requête hors bornes est refusée avant calcul.
Erreurs. 400 invalid_filters (JSON invalide, identifiant ou valeur de filtre
absent de listResultFilters), filter_budget_exceeded (bornes de filtres, dont les
5 cohortes, ou filters au-delà de 6 Kio encodés), invalid_parameter (trop d'itérations
comparées, comparaison incohérente : doublon, référence répétée, listes d'itérations et
de snapshots désalignées ; benchmark=zest pendant le pilote),
snapshot_required, snapshot_mismatch, invalid_cursor, unsupported_lang ;
404 resource_not_found ; 409 results_not_available.
| surveyId required | string (Id) ^[1-9][0-9]{0,18}$ Identifiant de campagne (Survey ou EM). |
| iterationId required | string (Id) ^[1-9][0-9]{0,18}$ Identifiant d'itération ; doit appartenir à la campagne. |
| compareIterationIds | Array of strings (Id) <= 4 items unique [ items^[1-9][0-9]{0,18}$ ] Itérations de la même campagne à comparer à la référence, séparées par des
virgules. Sans doublon ni répétition de la référence. Jamais de sélection
implicite de toutes les itérations. Au plus 4 pour un engagomètre (5 itérations lues)
et 2 pour un sondage (3 itérations lues) : au-delà, 400 |
| viewMode | string (ViewMode) Default: "dynamic" Enum: "dynamic" "static"
|
| snapshotId | string (SnapshotId) ^[0-9a-f]{24}$ Snapshot de l'itération de référence, obligatoire en vue statique et interdit en vue
dynamique. Doit appartenir à l'itération (400 |
| compareSnapshotIds | Array of strings (SnapshotId) <= 4 items unique [ items^[0-9a-f]{24}$ ] Vue statique uniquement : snapshots des itérations comparées, dans le même ordre et
en même nombre que |
ResultFiltersOverall (object) or ResultFiltersCustom (object) (ResultFilters) Objet JSON encodé en URL définissant les cohortes et restrictions. Absent :
ensemble de la corporation. Les identifiants proviennent de | |
| benchmark | string (BenchmarkType) Enum: "overall" "zest" Benchmark optionnel, absent par défaut. |
| lang | string^[a-z]{2,3}(_[A-Z]{2})?$ Langue des libellés (code produit, ex. |
| limit | integer [ 1 .. 50 ] Default: 50 Nombre de questions distinctes par page de résultats (au plus 50). |
| cursor | string [ 1 .. 1024 ] characters Curseur opaque issu de |
{- "ok": true,
- "data": {
- "surveyId": "1201",
- "surveyType": "survey",
- "viewMode": "dynamic",
- "iterations": [
- {
- "iterationId": "8801",
- "role": "reference",
- "snapshotId": null
}, - {
- "iterationId": "8650",
- "role": "comparison",
- "snapshotId": null
}
], - "benchmark": {
- "requested": null,
- "status": "not_requested",
- "reason": null,
- "period": null
}, - "cohorts": [
- {
- "id": "cohort_1",
- "kind": "segment",
- "label": "Segment 1",
- "segmentIndex": 0,
- "benchmark": null
}
], - "participation": [
- {
- "cohortId": "cohort_1",
- "iterationId": "8801",
- "invited": {
- "status": "available",
- "value": 120
}, - "submitted": {
- "status": "available",
- "value": 84
}, - "rate": {
- "status": "available",
- "value": 70
}, - "daily": [
- {
- "date": "2026-03-02",
- "submitted": {
- "status": "available",
- "value": 31
}, - "cumulativeRate": {
- "status": "available",
- "value": 25.8
}
}
]
}, - {
- "cohortId": "cohort_1",
- "iterationId": "8650",
- "invited": {
- "status": "available",
- "value": 115
}, - "submitted": {
- "status": "available",
- "value": 69
}, - "rate": {
- "status": "available",
- "value": 60
}, - "daily": [ ]
}
], - "overallScores": [
- {
- "cohortId": "cohort_1",
- "iterationId": "8801",
- "kind": "global_score",
- "score": {
- "status": "available",
- "normalized": 7.2
}
}, - {
- "cohortId": "cohort_1",
- "iterationId": "8650",
- "kind": "global_score",
- "score": {
- "status": "available",
- "normalized": 6.8
}
}
], - "themes": [
- {
- "id": "41",
- "kind": "custom",
- "label": "Bien-être au travail"
}
], - "questions": [
- {
- "questionId": "77001",
- "templateId": "5101",
- "iterationId": "8801",
- "cohortId": "cohort_1",
- "questionType": "scale",
- "themeId": "41",
- "answered": {
- "status": "available",
- "value": 82
}, - "skipped": {
- "status": "available",
- "value": 2
}, - "commentCount": {
- "status": "available",
- "value": 9
}, - "score": {
- "status": "available",
- "value": 7.4,
- "normalized": 7.4
}, - "scoreScale": {
- "min": 0,
- "max": 10
}, - "standardDeviation": {
- "status": "available",
- "value": 1.9
}, - "polarity": {
- "status": "available",
- "positive": 62,
- "neutral": 26,
- "negative": 12
}, - "distribution": {
- "status": "available",
- "items": [
- {
- "optionId": null,
- "value": 6,
- "count": 21,
- "percent": 26
}, - {
- "optionId": null,
- "value": 8,
- "count": 51,
- "percent": 62
}, - {
- "optionId": null,
- "value": 3,
- "count": 10,
- "percent": 12
}
]
}
}, - {
- "questionId": "76001",
- "templateId": "5101",
- "iterationId": "8650",
- "cohortId": "cohort_1",
- "questionType": "scale",
- "themeId": "41",
- "answered": {
- "status": "available",
- "value": 65
}, - "skipped": {
- "status": "available",
- "value": 4
}, - "commentCount": {
- "status": "available",
- "value": 5
}, - "score": {
- "status": "available",
- "value": 6.8,
- "normalized": 6.8
}, - "scoreScale": {
- "min": 0,
- "max": 10
}, - "standardDeviation": {
- "status": "available",
- "value": 2.1
}, - "polarity": {
- "status": "available",
- "positive": 55,
- "neutral": 28,
- "negative": 17
}, - "distribution": {
- "status": "available",
- "items": [
- {
- "optionId": null,
- "value": 6,
- "count": 18,
- "percent": 28
}, - {
- "optionId": null,
- "value": 8,
- "count": 36,
- "percent": 55
}, - {
- "optionId": null,
- "value": 3,
- "count": 11,
- "percent": 17
}
]
}
}, - {
- "questionId": "77002",
- "templateId": "5102",
- "iterationId": "8801",
- "cohortId": "cohort_1",
- "questionType": "single_choice",
- "themeId": null,
- "answered": {
- "status": "available",
- "value": 80
}, - "skipped": {
- "status": "available",
- "value": 4
}, - "commentCount": {
- "status": "available",
- "value": 0
}, - "score": {
- "status": "not_applicable",
- "value": null,
- "normalized": null
}, - "scoreScale": null,
- "standardDeviation": {
- "status": "not_applicable",
- "value": null
}, - "polarity": {
- "status": "not_applicable",
- "positive": null,
- "neutral": null,
- "negative": null
}, - "distribution": {
- "status": "available",
- "items": [
- {
- "optionId": "310021",
- "value": null,
- "count": 22,
- "percent": 28
}, - {
- "optionId": "310022",
- "value": null,
- "count": 50,
- "percent": 62
}, - {
- "optionId": "310023",
- "value": null,
- "count": 8,
- "percent": 10
}
]
}
}, - {
- "questionId": "77002",
- "templateId": "5102",
- "iterationId": "8650",
- "cohortId": "cohort_1",
- "questionType": "single_choice",
- "themeId": null,
- "answered": {
- "status": "no_data",
- "value": null
}, - "skipped": {
- "status": "no_data",
- "value": null
}, - "commentCount": {
- "status": "no_data",
- "value": null
}, - "score": {
- "status": "no_data",
- "value": null,
- "normalized": null
}, - "scoreScale": null,
- "standardDeviation": {
- "status": "no_data",
- "value": null
}, - "polarity": {
- "status": "no_data",
- "positive": null,
- "neutral": null,
- "negative": null
}, - "distribution": {
- "status": "no_data",
- "items": [ ]
}
}
]
}, - "meta": {
- "requestId": "req_01J9Z3K7QB",
- "nextCursor": "eyJsYXN0UXVlc3Rpb25JZCI6Ijc3MDAyIn0"
}
}Diagnostic d'une population (ensemble de la corporation ou un segment) : participation, thèmes, questions étoilées, écarts top/flop et score global (Survey) ou expérience collaborateur (EM), avec au plus une itération comparée. Réponse non paginée : sa taille est bornée par le catalogue de l'itération, dont chaque question notée figure dans son thème (sous-questions de matrice comprises, sans leur matrice).
Bornes. Une seule population : filters vaut overall (restrictions éventuelles)
ou custom avec EXACTEMENT un segment (population : cohort_1, Overall ou
Segment 1). Au plus une itération comparée (compareIterationIds) et, en vue
statique, un snapshot comparé (compareSnapshotIds) ; limit et cursor n'existent
pas (400 unknown_parameter). Le benchmark (overall ou zest) ne concerne que
l'itération de référence ; il n'est jamais une cohorte de la réponse. Pendant le
pilote, benchmark=zest est refusé (400 invalid_parameter).
Masquage. Une mesure dérivée (évolution, écart au benchmark) n'est calculée que si
tous ses opérandes sont available : un opérande masqué la rend suppressed, jamais
une différence avec un côté caché. Sans comparaison, évolution not_applicable ;
benchmark non demandé, not_applicable ; benchmark demandé mais indisponible,
not_available. Le score d'un thème est la moyenne de ses questions visibles, et sa
completeness vaut complete (toutes visibles), partial (au moins une masquée ou
sans réponse) ou suppressed (toutes masquées). L'évolution d'un thème est la moyenne
des évolutions de ses questions visibles dans les deux itérations ; une question qui
a changé de thème, absente de l'itération comparée ou sous-question de matrice en est
exclue. theme vaut
null pour les questions notées sans thème. Les écarts (gaps) ne portent que sur les
questions visibles, sous-questions de matrice exclues : moyenne et écart type des
scores normalisés, top (au-dessus de
la moyenne) et flop (en dessous), trois au plus chacun ; l'écart type vaut no_data
sous deux questions. Les effectifs de participation (invited, submitted, rate)
sont conservés pour une population sous le seuil d'anonymat, sauf si elle est définie
par des réponses (condition ou restriction de type question) : ils sont alors
suppressed, comme rateEvolution (avec comparaison) et rateBenchmarkGap (avec
benchmark). Pour l'engagomètre, les
questions fixes d'engagement et d'entreprise ne sont plus étoilées (starredQuestions
peut être vide) et n'entrent pas dans les écarts.
Arrondis. Scores et évolutions sur une décimale ; écarts du bloc gaps (moyenne,
écart type, zScore) sur deux décimales ; points de pourcentage de participation sur
une décimale.
Erreurs. 400 invalid_filters (JSON invalide, identifiant ou valeur de filtre
absent de listResultFilters), filter_budget_exceeded (plus d'un segment, bornes de
filtres ou filters au-delà de 6 Kio encodés), invalid_parameter (plus d'une itération
comparée, doublon, référence répétée, listes d'itérations et de snapshots
désalignées ; benchmark=zest pendant le pilote), snapshot_required,
snapshot_mismatch, unsupported_lang ; 404 resource_not_found ;
409 results_not_available.
| surveyId required | string (Id) ^[1-9][0-9]{0,18}$ Identifiant de campagne (Survey ou EM). |
| iterationId required | string (Id) ^[1-9][0-9]{0,18}$ Identifiant d'itération ; doit appartenir à la campagne. |
| compareIterationIds | Array of strings (Id) <= 1 items [ items^[1-9][0-9]{0,18}$ ] Itération de la même campagne comparée à la référence (au plus une). |
| viewMode | string (ViewMode) Default: "dynamic" Enum: "dynamic" "static"
|
| snapshotId | string (SnapshotId) ^[0-9a-f]{24}$ Snapshot de l'itération de référence, obligatoire en vue statique et interdit en vue
dynamique. Doit appartenir à l'itération (400 |
| compareSnapshotIds | Array of strings (SnapshotId) <= 1 items [ items^[0-9a-f]{24}$ ] Vue statique uniquement, snapshot de l'itération comparée. |
DiagnosticFiltersOverall (object) or DiagnosticFiltersCustom (object) (DiagnosticFilters) Objet JSON encodé en URL définissant la population du diagnostic : ensemble de la corporation ou un seul segment, avec restrictions éventuelles. Au plus 6 Kio une fois encodé. | |
| benchmark | string (BenchmarkType) Enum: "overall" "zest" Benchmark optionnel, absent par défaut. |
| lang | string^[a-z]{2,3}(_[A-Z]{2})?$ Langue des libellés (code produit, ex. |
{- "ok": true,
- "data": {
- "surveyId": "1201",
- "surveyType": "survey",
- "viewMode": "dynamic",
- "iterations": [
- {
- "iterationId": "8801",
- "role": "reference",
- "snapshotId": null
}, - {
- "iterationId": "8650",
- "role": "comparison",
- "snapshotId": null
}
], - "benchmark": {
- "requested": null,
- "status": "not_requested",
- "reason": null,
- "period": null
}, - "population": {
- "id": "cohort_1",
- "kind": "overall",
- "label": "Overall",
- "segmentIndex": null,
- "benchmark": null
}, - "participation": {
- "invited": {
- "status": "available",
- "value": 412
}, - "submitted": {
- "status": "available",
- "value": 296
}, - "rate": {
- "status": "available",
- "value": 71.8
}, - "rateEvolution": {
- "status": "available",
- "value": 6
}, - "rateBenchmarkGap": {
- "status": "not_applicable",
- "value": null
}
}, - "overallScore": {
- "kind": "global_score",
- "score": {
- "status": "available",
- "normalized": 6.4
}, - "evolution": {
- "status": "available",
- "normalized": 0.4
}, - "benchmarkGap": {
- "status": "not_applicable",
- "normalized": null
}
}, - "themes": [
- {
- "theme": {
- "id": "41",
- "kind": "custom",
- "label": "Bien-être au travail"
}, - "completeness": "complete",
- "score": {
- "status": "available",
- "normalized": 6.4
}, - "evolution": {
- "status": "available",
- "normalized": 0.4
}, - "benchmarkGap": {
- "status": "not_applicable",
- "normalized": null
}, - "questions": [
- {
- "questionId": "77001",
- "templateId": "5101",
- "questionType": "scale",
- "label": "Je recommanderais mon entreprise à un proche.",
- "isStarred": true,
- "starLabel": "Recommandation",
- "scoreScale": {
- "min": 0,
- "max": 10
}, - "score": {
- "status": "available",
- "value": 7.4,
- "normalized": 7.4
}, - "evolution": {
- "status": "available",
- "value": 0.5,
- "normalized": 0.5
}, - "benchmarkGap": {
- "status": "not_applicable",
- "value": null,
- "normalized": null
}
}, - {
- "questionId": "77002",
- "templateId": "5102",
- "questionType": "scale",
- "label": "Je dispose des moyens nécessaires pour bien travailler.",
- "isStarred": false,
- "starLabel": null,
- "scoreScale": {
- "min": 0,
- "max": 10
}, - "score": {
- "status": "available",
- "value": 5.4,
- "normalized": 5.4
}, - "evolution": {
- "status": "available",
- "value": 0.3,
- "normalized": 0.3
}, - "benchmarkGap": {
- "status": "not_applicable",
- "value": null,
- "normalized": null
}
}
]
}
], - "starredQuestions": [
- {
- "questionId": "77001",
- "templateId": "5101",
- "questionType": "scale",
- "label": "Je recommanderais mon entreprise à un proche.",
- "isStarred": true,
- "starLabel": "Recommandation",
- "scoreScale": {
- "min": 0,
- "max": 10
}, - "score": {
- "status": "available",
- "value": 7.4,
- "normalized": 7.4
}, - "evolution": {
- "status": "available",
- "value": 0.5,
- "normalized": 0.5
}, - "benchmarkGap": {
- "status": "not_applicable",
- "value": null,
- "normalized": null
}
}
], - "gaps": {
- "mean": {
- "status": "available",
- "value": 6.4
}, - "standardDeviation": {
- "status": "available",
- "value": 1.41
}, - "top": [
- {
- "questionId": "77001",
- "zScore": 0.71
}
], - "flop": [
- {
- "questionId": "77002",
- "zScore": -0.71
}
]
}
}, - "meta": {
- "requestId": "req_01J9Z3K7QF"
}
}Une question de l'itération de référence : sa définition (question, comme dans
listIterationQuestions), puis ses résultats agrégés par cohorte et par itération
(results), l'évolution de son score par rapport aux itérations comparées
(evolutions) et son écart au benchmark (benchmarkGaps). Aucune identité ni réponse
individuelle. results reprend à l'identique les lignes de cette question dans
getSurveyResults pour les mêmes paramètres : une ligne par itération et par cohorte
(cohortes benchmark comprises ; le benchmark zest n'a de lignes que pour l'itération de
référence), avec des mesures no_data pour une itération comparée où la question
n'existe pas. Pour une matrice, la réponse porte sa propre
ligne ; chacune de ses sous-questions est une requête distincte (matrixParentId).
Bornes. Réponse non paginée : au plus 6 cohortes (5 segments et le benchmark)
× 5 itérations (la référence et 4 comparées ; 3 et 2 pour un sondage). Mêmes paramètres
et mêmes bornes de comparaison et de filtres que getSurveyResults, sans limit ni
cursor (400 unknown_parameter).
Évolutions et écarts. evolutions compte une entrée par cohorte régulière
(overall ou segment, jamais le benchmark) et par itération comparée ;
comparedQuestionId est la question de l'itération comparée appariée par templateId,
null si elle n'en a pas (l'évolution vaut alors not_applicable). Comme dans
getSurveyDiagnostic, l'évolution vaut aussi not_applicable lorsque la question a
changé de thème entre les deux itérations. benchmarkGaps compte une entrée par cohorte
régulière, par rapport à la cohorte benchmark de l'itération de référence ; il est
vide sans benchmark. Une mesure dérivée n'est calculée que si tous ses opérandes sont
available : un opérande masqué la rend suppressed, jamais une différence avec un côté
caché ; sans comparaison ni benchmark il n'y a aucune entrée.
Masquage. Mêmes règles que getSurveyResults : une ligne sous le seuil d'anonymat
garde ses effectifs (answered, skipped, commentCount) et masque score, écart type,
polarité et distribution ; les effectifs ne sont masqués que pour une cohorte définie
par des réponses (condition ou restriction de type question). Les distributions n'ont
pas de masquage par option : seule la ligne est protégée par le seuil.
Échelles. Pour une question nps, score.value est sur l'échelle de scoreScale
(-100 à 100) alors que les value de distribution sont sur l'échelle native des
réponses (0 à 10, comme question.responseScale).
Texte. Aucun texte libre n'est jamais exposé : ni réponse, ni commentaire, ni
valeur « autre ». Une question open_text n'a que ses effectifs (answered,
skipped, commentCount) ; score, écart type, polarité et distribution valent
no_data.
Erreurs. 400 invalid_filters (JSON invalide, identifiant ou valeur de filtre
absent de listResultFilters), filter_budget_exceeded (bornes de filtres ou
filters au-delà de 6 Kio encodés), invalid_parameter (trop d'itérations comparées,
comparaison incohérente : doublon, référence répétée, listes d'itérations et de
snapshots désalignées ; benchmark=zest pendant le pilote),
snapshot_required, snapshot_mismatch, unsupported_lang ; 404
resource_not_found (campagne, itération ou snapshot inconnus, question d'une autre
itération ou non exposée : section, texte, matrice sans sous-question) ; 409
results_not_available.
| surveyId required | string (Id) ^[1-9][0-9]{0,18}$ Identifiant de campagne (Survey ou EM). |
| iterationId required | string (Id) ^[1-9][0-9]{0,18}$ Identifiant d'itération ; doit appartenir à la campagne. |
| questionId required | string (Id) ^[1-9][0-9]{0,18}$ Identifiant de question ; doit appartenir à l'itération. |
| compareIterationIds | Array of strings (Id) <= 4 items unique [ items^[1-9][0-9]{0,18}$ ] Itérations de la même campagne à comparer à la référence, séparées par des
virgules. Sans doublon ni répétition de la référence. Jamais de sélection
implicite de toutes les itérations. Au plus 4 pour un engagomètre (5 itérations lues)
et 2 pour un sondage (3 itérations lues) : au-delà, 400 |
| viewMode | string (ViewMode) Default: "dynamic" Enum: "dynamic" "static"
|
| snapshotId | string (SnapshotId) ^[0-9a-f]{24}$ Snapshot de l'itération de référence, obligatoire en vue statique et interdit en vue
dynamique. Doit appartenir à l'itération (400 |
| compareSnapshotIds | Array of strings (SnapshotId) <= 4 items unique [ items^[0-9a-f]{24}$ ] Vue statique uniquement : snapshots des itérations comparées, dans le même ordre et
en même nombre que |
ResultFiltersOverall (object) or ResultFiltersCustom (object) (ResultFilters) Objet JSON encodé en URL définissant les cohortes et restrictions. Absent :
ensemble de la corporation. Les identifiants proviennent de | |
| benchmark | string (BenchmarkType) Enum: "overall" "zest" Benchmark optionnel, absent par défaut. |
| lang | string^[a-z]{2,3}(_[A-Z]{2})?$ Langue des libellés (code produit, ex. |
{- "ok": true,
- "data": {
- "surveyId": "1201",
- "surveyType": "survey",
- "viewMode": "dynamic",
- "question": {
- "id": "77001",
- "templateId": "5101",
- "iterationId": "8801",
- "position": 0,
- "type": "scale",
- "label": "Je recommanderais mon entreprise à un proche.",
- "description": null,
- "isOptional": false,
- "isScored": true,
- "contributesToOverallScore": true,
- "isStarred": false,
- "starLabel": null,
- "theme": {
- "id": "41",
- "kind": "custom",
- "label": "Bien-être au travail"
}, - "matrixParentId": null,
- "responseScale": {
- "min": 0,
- "max": 10
}, - "options": [ ]
}, - "iterations": [
- {
- "iterationId": "8801",
- "role": "reference",
- "snapshotId": null
}, - {
- "iterationId": "8650",
- "role": "comparison",
- "snapshotId": null
}
], - "benchmark": {
- "requested": null,
- "status": "not_requested",
- "reason": null,
- "period": null
}, - "cohorts": [
- {
- "id": "cohort_1",
- "kind": "segment",
- "label": "Segment 1",
- "segmentIndex": 0,
- "benchmark": null
}
], - "results": [
- {
- "questionId": "77001",
- "templateId": "5101",
- "iterationId": "8801",
- "cohortId": "cohort_1",
- "questionType": "scale",
- "themeId": "41",
- "answered": {
- "status": "available",
- "value": 82
}, - "skipped": {
- "status": "available",
- "value": 2
}, - "commentCount": {
- "status": "available",
- "value": 9
}, - "score": {
- "status": "available",
- "value": 7.4,
- "normalized": 7.4
}, - "scoreScale": {
- "min": 0,
- "max": 10
}, - "standardDeviation": {
- "status": "available",
- "value": 1.9
}, - "polarity": {
- "status": "available",
- "positive": 62,
- "neutral": 26,
- "negative": 12
}, - "distribution": {
- "status": "available",
- "items": [
- {
- "optionId": null,
- "value": 8,
- "count": 51,
- "percent": 62
}
]
}
}, - {
- "questionId": "76420",
- "templateId": "5101",
- "iterationId": "8650",
- "cohortId": "cohort_1",
- "questionType": "scale",
- "themeId": "41",
- "answered": {
- "status": "available",
- "value": 76
}, - "skipped": {
- "status": "available",
- "value": 4
}, - "commentCount": {
- "status": "available",
- "value": 6
}, - "score": {
- "status": "available",
- "value": 6.9,
- "normalized": 6.9
}, - "scoreScale": {
- "min": 0,
- "max": 10
}, - "standardDeviation": {
- "status": "available",
- "value": 2.1
}, - "polarity": {
- "status": "available",
- "positive": 55,
- "neutral": 29,
- "negative": 16
}, - "distribution": {
- "status": "available",
- "items": [
- {
- "optionId": null,
- "value": 8,
- "count": 41,
- "percent": 54
}
]
}
}
], - "evolutions": [
- {
- "cohortId": "cohort_1",
- "comparedIterationId": "8650",
- "comparedQuestionId": "76420",
- "score": {
- "status": "available",
- "value": 0.5,
- "normalized": 0.5
}
}
], - "benchmarkGaps": [ ]
}, - "meta": {
- "requestId": "req_01J9Z3K7QJ"
}
}