Estrutura base da coleta (Harvest)
Uma coleta eh o conjunto de respostas de um respondente a um questionario. Ela chega na plataforma como um arquivo JSON gravado no bucket S3.
O arquivo pode conter um objeto (uma coleta) ou um array de objetos (varias coletas no mesmo arquivo). Os dois formatos sao aceitos.
{
"uuid": "<UUID da coleta. OBRIGATORIO. Eh a chave de deduplicacao: reenviar um JSON com o mesmo uuid ATUALIZA a coleta existente em vez de criar uma nova>",
"questionnaire": <id do questionario. OBRIGATORIO. Sem esta chave a coleta eh rejeitada. O questionario precisa existir e nao estar deletado>,
"release": <numero do release do questionario usado na aplicacao. Define de qual versao do questionario sao os ids de questao e de opcao usados em `values`. Veja a secao "Relacao com o release">,
"created": "<data/hora de INICIO da entrevista, no formato ISO 8601. OBRIGATORIO>",
"updated": "<data/hora de FIM da entrevista, no formato ISO 8601. OBRIGATORIO>",
"pin": "<pin do dispositivo que fez a coleta. OBRIGATORIO (a chave precisa sempre existir no JSON, mesmo que o valor seja null). null em coletas online (feitas pelo navegador, sem app). Se informado, o dispositivo precisa existir, senao a coleta eh rejeitada>",
"user": <id do entrevistador que aplicou o questionario. null em coletas online>,
"user_id": <id do usuario da plataforma que respondeu. Usado em coletas online. null em coletas de campo>,
"interviewer": <id do entrevistador. Alternativa a chave `user`, com o mesmo efeito. Veja a secao "user, user_id e interviewer">,
"finished": <true se a entrevista foi concluida ate o fim, false se foi interrompida>,
"valid": <true se a coleta eh considerada valida>,
"denied": <true se o respondente recusou a entrevista, false caso contrario>,
"lat": <latitude decimal capturada no momento da coleta. 0 quando nao houve captura de GPS>,
"lng": <longitude decimal capturada no momento da coleta. 0 quando nao houve captura de GPS>,
"accuracy": <precisao do GPS em metros. Apenas informativo, NAO eh gravado no banco>,
"id": <id da coleta no banco local do dispositivo. IGNORADO pelo servidor>,
"cati_contact_uuid": "<uuid do contato no modulo CATI, quando a coleta veio de uma ligacao telefonica. null nos demais casos>",
"cati_phone": "<telefone usado na ligacao CATI. null nos demais casos>",
"pre_fill_data_item_id": <id do item de pre-preenchimento que originou a coleta. null quando a coleta nao veio de uma base pre-carregada>,
"whatsapp": { "recipient": "<telefone do respondente no WhatsApp, somente digitos>" },
"url_variables": [<array de variaveis de URL capturadas em coletas online, descrito abaixo>],
"values": [<array com as respostas da coleta, descrito abaixo>]
}
Regras:
uuid,questionnaire,createdeupdatedsao obrigatorios. A ausencia de qualquer um deles faz a coleta inteira ser rejeitada.pintambem eh obrigatorio, mas de um jeito diferente dos quatro acima: o processamento acessa essa chave diretamente (sem tratar ausencia), entao omiti-la nao gera uma rejeicao limpa da coleta — o processamento do arquivo inteiro quebra com erro (KeyError: 'pin') e o arquivo eh marcado comoFAILEDno bucket. Em coletas sem dispositivo (online), envie"pin": null— nunca omita a chave.createdeh a data de inicio eupdateda data de fim da entrevista.updatednunca deve ser anterior acreated.- Toda chave cujo nome coincida com uma coluna da coleta eh gravada automaticamente. Chaves desconhecidas sao ignoradas silenciosamente — nao geram erro, mas tambem nao sao armazenadas em lugar nenhum.
- Ao gerar o JSON para atualizar uma coleta existente, preserve todos os campos do JSON original, mesmo os desconhecidos, e altere apenas o que precisa mudar.
- Nao existe campo
surplusno JSON. Ele eh calculado pelo servidor (marca coletas excedentes de questionarios em modo teste). - Nao existe campo de duracao. A duracao eh derivada de
updated - created.
user, user_id e interviewer
Sao tres chaves distintas e frequentemente confundidas:
| Chave | O que identifica | Quando usar |
|---|---|---|
user |
o entrevistador que aplicou o questionario | coletas de campo. No processamento esta chave eh renomeada para interviewer |
interviewer |
o entrevistador | equivalente a user. Pode ser usada diretamente |
user_id |
o usuario da plataforma que respondeu | coletas online, em que o proprio respondente esta logado |
Regras que decorrem disso:
- Em coleta de campo: preencha
user(ouinterviewer) epin; deixeuser_idcomonull. - Em coleta online: preencha
user_id; deixepineuser/interviewercomonull. - Nunca envie
usereinterviewerao mesmo tempo com valores diferentes.
Estrutura de resposta (item de values)
Cada item do array values eh a resposta de uma questao.
{
"question": <id da questao. OBRIGATORIO. A questao precisa existir, senao a coleta inteira eh rejeitada>,
"datadoc": {<dicionario com a resposta em si, descrito abaixo>},
"harvest": "<id ou uuid da coleta no banco local do dispositivo. IGNORADO pelo servidor>",
"id": <id do value no banco local do dispositivo. IGNORADO pelo servidor>
}
Regras:
- Existe no maximo um item por questao. Nunca envie dois objetos com o mesmo
questionno mesmo arrayvalues— o segundo sobrescreve o primeiro. - Questoes nao respondidas simplesmente nao aparecem no array
values. Nao envie um item comdatadocvazio para representar ausencia de resposta; use__blank__(veja abaixo) quando quiser registrar explicitamente que a questao foi deixada em branco. - Uma questao matriz nao gera um item proprio em
values. Cada linha da matriz eh uma questao filha e aparece como um item independente, no formato de questao de escolha unica. - Respostas dentro de loops nao sao importadas por este formato.
Estrutura do datadoc
O datadoc eh um dicionario onde:
- as chaves sao ids de opcao (
idda opcao no JSON do questionario), sempre como string; - os valores sao o que foi respondido naquela opcao.
Alem das chaves numericas, existem chaves especiais com duplo underline, descritas mais abaixo.
{
"<id da opcao>": "<valor respondido>",
"<id de outra opcao>": "<valor respondido>"
}
O que vai no valor depende do tipo da opcao:
class_name da opcao |
O que vai no valor |
|---|---|
Option |
o campo value da opcao (o codigo numerico dela, como string). Ex.: "3" |
OtherOption |
o texto digitado pelo respondente no campo "Outro". Ex.: "Vila Rica" |
InputOption |
o texto digitado naquele campo da questao aberta. Ex.: "Sofia Reguel" |
Chaves especiais
| Chave | Significado | Valor |
|---|---|---|
ordered |
ordem de selecao das opcoes, usada em questoes de escolha multipla com widget_ordered_selection = true |
array com os ids das opcoes, na ordem em que foram selecionadas |
__blank__ |
a questao foi deixada em branco | o blank_value configurado no questionario. Ex.: "#", "NSNR", "99" |
__dkda__ |
resposta "Nao sabe / Nao respondeu" (NSNR), disponivel em questoes com option_dkda = true |
o literal "__dkda__" |
__none__ |
opcao de exclusao ("Nenhuma das anteriores"), disponivel em questoes com option_none = true |
o option_none_value da questao, ou o id da opcao. Ex.: "472", "__none__" |
__picture__ |
resposta de questao de foto | data URI em base64 no envio; vira URL do S3 depois de processada |
__sign__ |
resposta de questao de assinatura | data URI em base64 no envio; vira URL do S3 depois de processada |
__audio__ |
resposta de questao de audio | data URI em base64 no envio; vira URL do S3 depois de processada |
value |
formato generico, legado | evite usar. Se o valor for vazio, null ou 0, a chave eh descartada no processamento |
Regras:
- As chaves numericas sao ids de opcao, nunca ids de questao. O id da questao aparece so no campo
questiondo item devalues. - As chaves sao sempre strings, mesmo sendo numericas:
"12109244", nao12109244. - Os valores tambem sao strings, inclusive os codigos numericos das opcoes:
"3", nao3. - Os ids de opcao usados precisam existir no release informado na coleta. Veja a secao "Relacao com o release".
__blank__,__dkda__e__none__sao mutuamente exclusivas entre si e com as chaves de opcao: quando uma delas esta presente, ela eh a resposta inteira e odatadocnao tem mais nada (a excecao ehordered).- Envie no maximo uma chave de midia (
__sign__,__picture__ou__audio__) pordatadoc. Se houver mais de uma, apenas a primeira na ordem__sign__->__picture__->__audio__eh processada; as demais sao gravadas como base64 cru.
Formato por tipo de questao
Escolha unica (ChoiceQuestion)
Uma unica chave: o id da opcao escolhida, apontando para o value dessa opcao.
{ "12109244": "3" }
Quando a opcao escolhida eh uma OtherOption ("Outro, qual?"), o valor eh o texto digitado em vez do codigo:
{ "137495": "Vila Rica" }
Escolha multipla (MultipleChoiceQuestion)
Uma chave por opcao marcada. As opcoes nao marcadas simplesmente nao aparecem.
{
"12127421": "3",
"12127422": "4"
}
Se a questao usa selecao ordenada (widget_ordered_selection = true), acrescente a chave ordered com os ids na ordem em que foram escolhidos:
{
"12127421": "3",
"12127422": "4",
"ordered": ["12127421", "12127422"]
}
A posicao de cada id dentro de ordered eh o que define a prioridade da selecao (primeiro elemento = primeira escolha).
Questao aberta (OpenQuestion)
Uma questao aberta pode ter varios campos, cada um sendo uma InputOption. Cada chave eh o id do campo e o valor eh o texto digitado nele:
{
"103006": "Sofia Reguel",
"103007": "Rua Herminio Dagnone 196",
"103008": "Vl Nova",
"103009": "47 996791313"
}
Se a questao aberta tem um unico campo, o datadoc tem uma unica chave:
{ "260383": "Lidiane Souza" }
Escala (ScaleQuestion)
Mesmo formato da escolha unica: id da opcao da escala apontando para o valor selecionado.
{ "1332159": "6" }
Matriz (MatrixQuestion)
A questao matriz nao tem datadoc proprio. Cada linha da matriz eh uma questao filha (ChoiceQuestion com matrix_question_id preenchido) e aparece como um item separado em values, no formato de escolha unica. Os ids de opcao usados sao os da questao matriz, que sao herdados pelas filhas.
Foto, assinatura e audio (PictureQuestion, SignatureQuestion, AudioQuestion)
No JSON enviado ao bucket, o conteudo vai como data URI em base64:
{ "__picture__": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..." }
Extensoes reconhecidas: png, jpg, jpeg, mp3. O prefixo do data URI (os 22 primeiros caracteres) eh usado para identificar a extensao, entao ele precisa estar presente e bem formado.
Ao analisar uma coleta ja processada, espere sempre uma URL. Ao gerar uma coleta nova, envie o base64.
Variaveis de URL
Coletas online podem carregar parametros de query string capturados na URL de acesso ao formulario:
"url_variables": [
{ "name": "utm_source", "value": "Instagram_Feed" },
{ "name": "utm_medium", "value": "paid" },
{ "name": "sname", "value": "6TLO0enjtz_6OPXQslWfp56eQl4" }
]
Regras:
nameevaluesao ambos strings e ambos obrigatorios.- As variaveis aceitas sao as declaradas no array
url_variablesdo JSON do questionario. - O array pode ser omitido ou vir vazio em coletas de campo.
Relacao com o release
O JSON de coleta eh puramente numerico: ele so carrega ids de questao e ids de opcao. Quem da significado a esses ids eh o release, que eh o snapshot da estrutura do questionario no momento em que ele foi publicado.
O vinculo eh feito pelo par questionnaire + release da coleta. Ele aponta para um snapshot que contem a lista completa de questoes e opcoes vigentes na epoca, e eh esse snapshot que traduz cada id de opcao em rotulo e em coluna de exportacao.
Isso existe para que uma coleta antiga continue sendo lida com a estrutura da epoca em que foi respondida, mesmo que o questionario tenha sido alterado depois.
Regras:
- O
releaseinformado precisa existir para aquele questionario. - Todos os ids de questao e de opcao usados em
valuesdevem pertencer ao release informado. Usar um id de opcao que so existe em um release mais novo produz uma coleta que nao consegue ser exportada corretamente. - Ao gerar um JSON de coleta, sempre parta do JSON do release correspondente para obter os ids de opcao e os
valuede cada opcao. Nao invente ids. - Ao analisar um JSON de coleta, o
releaseeh a primeira coisa a olhar: sem ele nao ha como saber a que questao/opcao cada id se refere.
Exemplos completos
Coleta online, com uma questao aberta
{
"uuid": "4c6ff122-5d5f-47f6-93ab-a5ce4124e766",
"questionnaire": 237687,
"release": 1,
"created": "2026-08-11T14:20:20.751Z",
"updated": "2026-08-11T14:28:07.699Z",
"pin": null,
"interviewer": null,
"user_id": 2762,
"finished": true,
"valid": true,
"denied": false,
"lat": -19.7521501,
"lng": -43.9341315,
"accuracy": 5100,
"values": [
{
"question": 237689,
"datadoc": { "74346": "jonathan" }
}
]
}
Coleta de campo, com varios tipos de questao
{
"uuid": "b51110b1-7e89-4fb7-8457-44c0b68f69a6",
"questionnaire": 1325786,
"release": 1,
"created": "2026-08-11T13:55:13.676Z",
"updated": "2026-08-11T13:58:43.724Z",
"pin": "M0FMWK",
"user": 23506,
"user_id": null,
"finished": true,
"valid": true,
"denied": false,
"lat": -30.023284,
"lng": -51.217722,
"values": [
{
"question": 12109240,
"datadoc": { "12109244": "3" }
},
{
"question": 12127420,
"datadoc": {
"12127421": "3",
"12127422": "4",
"ordered": ["12127421", "12127422"]
}
},
{
"question": 12109290,
"datadoc": { "137495": "Vila Rica" }
},
{
"question": 12109300,
"datadoc": { "__blank__": "#" }
},
{
"question": 12109310,
"datadoc": { "__dkda__": "__dkda__" }
},
{
"question": 12109320,
"datadoc": { "__none__": "472" }
}
]
}
Nome do arquivo no bucket
O nome do arquivo nao identifica a coleta (isso vem do conteudo), mas segue um padrao fixo e precisa terminar em harvest.json ou harvest.zip para ser processado:
<data>-<uuid da coleta>-<pin do dispositivo>-harvest.json
Exemplo: 2026-08-11T11.16.46-b51110b1-7e89-4fb7-8457-44c0b68f69a6-M0FMWK-harvest.json
Regras:
- Quando nao ha dispositivo (coleta online), o literal
null(minusculo) ocupa a posicao do pin:...-4c6ff122-5d5f-47f6-93ab-a5ce4124e766-null-harvest.json. - No caso do
.zip, o JSON dentro do arquivo compactado deve ter **o mesmo nome da key, trocando.zippor.json**.