API Ask

La validation humaine en un seul appel HTTP. Votre script ou job CI POSTe une question avec quelques actions possibles. Zenhook notifie un humain (push, email, desktop) et l'appel HTTP retourne ce qu'il a choisi. Pas de SDK, pas de session, pas de framework de polling.

Poser une question

Même token que l'URL webhook de votre canal : un ask est simplement un événement en forme de question sur le canal. Par défaut, l'appel bloque jusqu'à la réponse d'un humain (ou ~80 secondes, voirmodes d'attente).

POSThttps://zenhook.dev/api/ask/{token}
Exemple de requête
curl -X POST https://zenhook.dev/api/ask/abc123def456 \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Déployer api-server en production ?",
    "message": "3 commits depuis le dernier déploiement. Tests verts.",
    "actions": ["Déployer", "Annuler"],
    "default": "Annuler",
    "timeout": 600
  }'
Réponse une fois répondu
{
  "id": "ask_cmr5o5l6",
  "status": "answered",
  "answer": "Déployer",
  "answeredVia": "link",
  "answeredAt": "2026-07-04T09:41:12.000Z",
  "actions": ["Déployer", "Annuler"],
  "defaultAction": "Annuler",
  "askUrl": "https://zenhook.dev/ask/…",
  "pollUrl": "https://zenhook.dev/api/ask/…/ask_cmr5o5l6"
}

Tout le canal reçoit la question comme une alerte normale, avec un lien Répondre. Le lien ouvre une page de réponse publique. Un tap sur le téléphone et l'appel bloqué ci-dessus retourne. Quiconque détient le lien peut répondre ; aucun compte Zenhook requis.

Champs de la requête

titlestringRequis
La question. Formulez-la pour qu'un tap suffise à y répondre.
actionsstring[]
1 à 6 choix courts. Par défaut ["Approve", "Deny"]. La première action devient le bouton principal.
messagestring
Contexte sous le titre (ce qui a changé, pourquoi vous demandez).
fieldsarray
Mêmes paires label/valeur que l'API webhook, affichées sur la page de réponse.
timeoutnumber
Secondes avant expiration de l'ask. De 30 à 86400. Défaut 600.
defaultstring
Repli suggéré si personne ne répond. Retourné comme defaultAction ; jamais appliqué à votre place, jamais dans answer.
waitbool | number
true (défaut) retient l'appel jusqu'à la réponse ou ~80 s. Un nombre = autant de secondes. false retourne 201 immédiatement avec pollUrl.
callbackUrlstring
Reçoit le résultat en POST à la réponse ou l'expiration : { type: "ask.answered" | "ask.expired", ask: {…} }.
emoji / level / sourcestring
Mêmes sémantiques que l'API webhook. Level par défaut warning, emoji ❓.

Modes d'attente

Une requête HTTP retenue est plafonnée autour de 80 secondes (limites proxy). Pour les décisions plus longues, bouclez surpollUrl : chaque poll peut lui-même attendre avec ?wait=60, la boucle est donc économe :

Barrière d'approbation (bash)
RES=$(curl -s -X POST "https://zenhook.dev/api/ask/$TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"title":"Déployer en prod ?","actions":["Déployer","Annuler"],"timeout":600}')

while [ "$(echo "$RES" | jq -r .status)" = "pending" ]; do
  RES=$(curl -s "$(echo "$RES" | jq -r .pollUrl)?wait=60")
done

if [ "$(echo "$RES" | jq -r .answer)" = "Déployer" ]; then
  ./deploy.sh
else
  echo "Bloqué par un humain." && exit 1
fi

Ou n'attendez pas du tout et recevez le résultat en push :

Mode callback
{
  "title": "Rembourser 420 $ au client #1847 ?",
  "actions": ["Rembourser", "Escalader", "Ignorer"],
  "timeout": 3600,
  "wait": false,
  "callbackUrl": "https://votre-app.com/hooks/zenhook-answer"
}

Sémantique des réponses

  • La première réponse gagne.Deux personnes qui tapent en même temps ne peuvent pas gagner toutes les deux ; le perdant voit qui a répondu en premier.
  • L'expiration n'invente jamais de réponse.Un ask expiré a status: "expired" et answer: null. Votre default revient comme defaultAction ; l'appliquer reste votre décision.
  • Les réponses sont auditées.Qui a répondu (si connecté), par quelle surface, et quand.

Limites par plan

Free inclut 25 asks par mois par workspace. Pro monte à 1 000 asks par mois. Les asks comptent aussi dans votre quota mensuel d'événements.