Listar respostas do Flow
Os formulários que os clientes enviaram para UM Flow, os mais novos primeiro. O Flow é resolvido primeiro dentro da WABA da própria chave, e essa resolução é a única coisa que escopa o resto — um Flow de outra WABA (inclusive outra WABA do mesmo tenant) responde 404 e nenhuma resposta chega a ser lida.
Respostas fora da janela de retenção são excluídas AQUI, não só pela tarefa de poda, para que ninguém leia um envio que a janela publicada diz ter sumido. contactPhone vem null quando o telefone já não é nosso para publicar. externalMessageId é o wamid da Meta e é NOMEADO como id de provedor de propósito — todo outro id desta superfície é local.
notice: "FLOW_RESPONSES_WINDOW_TRUNCATED" aparece na página quando o Flow cunhou mais envios do que uma requisição correlaciona (teto de 10.000 sessões). Uma resposta curta que não diz que é curta é o que faz o cliente concluir que perdeu envios. Permissão flows:read.
Exige chave com escopo de número. Uma chave de tenant precisa nomear o número no header x-whatsapp-number-id, senão recebe 403 TENANT_SCOPE_NOT_ALLOWED.
media[] leva os ficheiros que a pessoa ANEXOU dentro do formulário (PhotoPicker / DocumentPicker). Eles NÃO estão em response: um picker exige data_exchange, portanto as referências chegam no pedido que o nosso endpoint decifra — uma troca HTTP diferente do nfm_reply que produziu a linha — e este array é a cópia re-hospedada, ligada pelo flowToken. É o único sítio onde um chamador alcança os bytes: a Meta apaga o original ao fim de 20 dias. É SEMPRE um array, [] quando nada foi anexado. url é um link estável nosso e está presente assim que a linha existe, INCLUSIVE enquanto status for "PENDING". Buscá-lo antes de a re-hospedagem terminar não dá 404: a rota segura o pedido enquanto espera pelo ficheiro e então serve-o, toda busca posterior é um redirect, e uma espera que estoura responde 404 com o cabeçalho Retry-After. O valor não muda depois de os bytes chegarem, portanto é seguro guardá-lo logo na chegada, e o status (PENDING/STORED/FAILED) continua a ser como o chamador sabe se eles chegaram. É a ordem que justifica esse link: o evento é emitido assim que o envio é guardado e a re-hospedagem corre depois dele, portanto o anúncio precede normalmente os bytes. url só é nulo quando a instalação não tem segredo de assinatura configurado (aí volta ao comportamento antigo, a URL direta do objeto assim que ficar STORED), quando a Meta não enviou media_id para assinar uma identidade em cima, ou num número RELAY_ONLY onde nada foi guardado — nunca apenas por a transferência estar por terminar. Trate-o como opaco: busque, nunca construa um. mimeType é farejado dos bytes decifrados e nunca do nome do ficheiro, e ele e sizeBytes são null até o ficheiro ficar guardado. Nem cdn_url nem material de cifra são alguma vez publicados.
submitted[] é o que a pessoa de fato ESCREVEU, tela a tela. ⛔ Num Flow data_exchange é o ÚNICO sítio onde essas respostas existem: ali o response é o nfm_reply da Meta, um envelope que leva o flow_token e mais nada — quem abre o response num Flow desses encontra uma sala vazia e conclui que o envio se perdeu. Num Flow SIMPLES, sem endpoint, as respostas CHEGAM no response e o submitted vem [].
Cada entrada é um pedido data_exchange que a META NOS ENVIOU: screen (a tela em que a pessoa estava, ou null), at (ISO 8601), data (o objeto já parseado) e omitted. Nunca é o que o SEU endpoint respondeu — o extension_message_response não é publicado em sítio nenhum do Pilot Status. E só as trocas data_exchange são capturadas: o INIT devolve o estado que NÓS semeamos quando o Flow foi enviado, e o BACK é navegação, portanto esta lista é legitimamente MAIS CURTA do que o tráfego que o seu próprio endpoint viu. Essa diferença não é perda.
NESTE endpoint o submitted é sempre um array e nunca null — [] tanto quando não há nada como quando o número não guarda conteúdo de cliente, porque “nada foi capturado” e “não conseguimos olhar” seriam senão o mesmo valor. (No webhook flow.response_received é o contrário, null e nunca [], porque lá o redator só apaga um campo que tenha algo dentro.) Nada é truncado em silêncio: ou a entrada leva data com omitted: null, ou leva data: null e diz o PORQUÊ. DATA_TOO_LARGE é uma troca grande demais, que mantém a linha e o lugar dela na sequência — o teto que ela passou conta-se em BYTES, não em caracteres, portanto texto com acentos chega lá antes do que o comprimento sugere. LIMIT_REACHED é o teto de 50 trocas por formulário: uma entrada final de marcação, e nada depois dela ficou guardado. UNREADABLE é uma troca que ficou guardada mas cujo conteúdo não se conseguiu reler. Não há terceira forma.
A lista só cresce e nunca é de-duplicada: quem volta atrás numa tela e responde de novo produz uma segunda entrada GENUÍNA, e é o at que distingue duas passagens debaixo do mesmo flowToken. As trocas ficam 30 dias + 1 — um dia a MAIS do que a linha da resposta — porque a troca é capturada enquanto a pessoa ainda está preenchendo, minutos ANTES do envio que cria a linha; com uma janela igual elas expirariam primeiro, e uma resposta ainda dentro da retenção dela publicaria submitted: [] nos seus últimos minutos. Num número com piiMode: "RELAY_ONLY" não fica aqui nada: nem uma entrada ocultada, nenhuma entrada.
curl --request GET \
--url https://pilotstatus.com.br/v1/flows/{id}/responses \
--header 'x-api-key: <api-key>'import requests
url = "https://pilotstatus.com.br/v1/flows/{id}/responses"
headers = {"x-api-key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'x-api-key': '<api-key>'}};
fetch('https://pilotstatus.com.br/v1/flows/{id}/responses', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://pilotstatus.com.br/v1/flows/{id}/responses",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://pilotstatus.com.br/v1/flows/{id}/responses"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://pilotstatus.com.br/v1/flows/{id}/responses")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://pilotstatus.com.br/v1/flows/{id}/responses")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"responses": [
{
"id": "cmf9z8y7x6w5v4u3t2s1r0q9",
"flowId": "cmf1a2b3c4d5e6f7g8h9i0j1",
"contactPhone": "5564999978874",
"flowToken": "b7c1e4f2a9d84c6e8f0a1b2c3d4e5f60",
"externalMessageId": "wamid.HBgNNTU2NDk5OTc4ODc0FQIAERgSN0E5...",
"response": {
"flow_token": "b7c1e4f2a9d84c6e8f0a1b2c3d4e5f60"
},
"submitted": [
{
"screen": "WELCOME_SCREEN",
"at": "2026-09-01T18:21:44.318Z",
"data": {
"screen_0_nome": "Amanda",
"screen_0_nota": "9"
},
"omitted": null
},
{
"screen": "COMENTARIO_SCREEN",
"at": "2026-09-01T18:22:03.907Z",
"data": null,
"omitted": "DATA_TOO_LARGE"
}
],
"media": [
{
"field": "photo_picker",
"fileName": "documento.jpg",
"mimeType": "image/jpeg",
"sizeBytes": 184320,
"status": "STORED",
"url": "https://media.pilotstatus.com.br/public/tenants/tenant_01HZX.../flow-media/8f2c1a4b9d.jpg"
},
{
"field": "document_picker",
"fileName": "comprovante.pdf",
"mimeType": null,
"sizeBytes": null,
"status": "PENDING",
"url": "https://pilotstatus.com.br/api/flows/media/eyJ0IjoiY21xc2N4MXczIiwiayI6ImZvdG8ifQ.9f2c1a4b8e0d6c73"
}
],
"receivedAt": "2026-09-01T18:22:11.000Z",
"expiresAt": "2026-10-01T18:22:11.000Z"
}
],
"total": 1,
"page": 1,
"pageSize": 30,
"totalPages": 1
}{
"error": "Paginação profunda demais: page × pageSize não pode passar de 10000.",
"errorEN": "Pagination too deep: page × pageSize must not exceed 10000.",
"code": "INVALID_PAGINATION"
}{
"error": "Unauthorized"
}{
"error": "Tenant-scoped keys cannot call number endpoints",
"code": "TENANT_SCOPE_NOT_ALLOWED"
}{
"error": "Flow não encontrado para o número desta chave.",
"errorEN": "Flow not found for this key's number.",
"code": "FLOW_NOT_FOUND"
}{
"error": "Flows existem apenas em números Meta (API Oficial). O número desta chave não é Meta ou não tem uma WABA associada — use uma chave de um número Meta.",
"errorEN": "Flows only exist on Meta (Cloud API) numbers. This key's number is not a Meta number, or has no WABA behind it — use a key bound to a Meta number.",
"code": "FLOW_REQUIRES_META_NUMBER"
}{
"error": "Too many requests"
}{
"error": "Erro interno do servidor.",
"errorEN": "Internal server error.",
"code": "INTERNAL_ERROR"
}Autorizações
Sua chave de API ps_
Parâmetros de caminho
O id local do Flow — o campo id que GET /v1/flows devolve, nunca o metaFlowId.
Parâmetros de consulta
Número da página, começando em 1.
x >= 1Linhas por página. Padrão 30, máximo 100.
1 <= x <= 100Resposta
Listar respostas do Flow
Os envios desta página, os mais novos primeiro.
Show child attributes
Show child attributes
Envios que casam com a leitura em todas as páginas, não o tamanho desta página.
Presente APENAS quando o Flow cunhou mais envios do que uma requisição correlaciona (teto de 10.000 sessões); ausente caso contrário. A página fica então CURTA — leia isso como "pode haver mais", nunca como "estes são todos os envios".
FLOW_RESPONSES_WINDOW_TRUNCATED Esta página foi útil?
curl --request GET \
--url https://pilotstatus.com.br/v1/flows/{id}/responses \
--header 'x-api-key: <api-key>'import requests
url = "https://pilotstatus.com.br/v1/flows/{id}/responses"
headers = {"x-api-key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'x-api-key': '<api-key>'}};
fetch('https://pilotstatus.com.br/v1/flows/{id}/responses', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://pilotstatus.com.br/v1/flows/{id}/responses",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://pilotstatus.com.br/v1/flows/{id}/responses"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://pilotstatus.com.br/v1/flows/{id}/responses")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://pilotstatus.com.br/v1/flows/{id}/responses")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"responses": [
{
"id": "cmf9z8y7x6w5v4u3t2s1r0q9",
"flowId": "cmf1a2b3c4d5e6f7g8h9i0j1",
"contactPhone": "5564999978874",
"flowToken": "b7c1e4f2a9d84c6e8f0a1b2c3d4e5f60",
"externalMessageId": "wamid.HBgNNTU2NDk5OTc4ODc0FQIAERgSN0E5...",
"response": {
"flow_token": "b7c1e4f2a9d84c6e8f0a1b2c3d4e5f60"
},
"submitted": [
{
"screen": "WELCOME_SCREEN",
"at": "2026-09-01T18:21:44.318Z",
"data": {
"screen_0_nome": "Amanda",
"screen_0_nota": "9"
},
"omitted": null
},
{
"screen": "COMENTARIO_SCREEN",
"at": "2026-09-01T18:22:03.907Z",
"data": null,
"omitted": "DATA_TOO_LARGE"
}
],
"media": [
{
"field": "photo_picker",
"fileName": "documento.jpg",
"mimeType": "image/jpeg",
"sizeBytes": 184320,
"status": "STORED",
"url": "https://media.pilotstatus.com.br/public/tenants/tenant_01HZX.../flow-media/8f2c1a4b9d.jpg"
},
{
"field": "document_picker",
"fileName": "comprovante.pdf",
"mimeType": null,
"sizeBytes": null,
"status": "PENDING",
"url": "https://pilotstatus.com.br/api/flows/media/eyJ0IjoiY21xc2N4MXczIiwiayI6ImZvdG8ifQ.9f2c1a4b8e0d6c73"
}
],
"receivedAt": "2026-09-01T18:22:11.000Z",
"expiresAt": "2026-10-01T18:22:11.000Z"
}
],
"total": 1,
"page": 1,
"pageSize": 30,
"totalPages": 1
}{
"error": "Paginação profunda demais: page × pageSize não pode passar de 10000.",
"errorEN": "Pagination too deep: page × pageSize must not exceed 10000.",
"code": "INVALID_PAGINATION"
}{
"error": "Unauthorized"
}{
"error": "Tenant-scoped keys cannot call number endpoints",
"code": "TENANT_SCOPE_NOT_ALLOWED"
}{
"error": "Flow não encontrado para o número desta chave.",
"errorEN": "Flow not found for this key's number.",
"code": "FLOW_NOT_FOUND"
}{
"error": "Flows existem apenas em números Meta (API Oficial). O número desta chave não é Meta ou não tem uma WABA associada — use uma chave de um número Meta.",
"errorEN": "Flows only exist on Meta (Cloud API) numbers. This key's number is not a Meta number, or has no WABA behind it — use a key bound to a Meta number.",
"code": "FLOW_REQUIRES_META_NUMBER"
}{
"error": "Too many requests"
}{
"error": "Erro interno do servidor.",
"errorEN": "Internal server error.",
"code": "INTERNAL_ERROR"
}