API de Correção de Redação (ENEM)
API REST do Workfuse para enviar redações e receber a correção no padrão ENEM
(5 competências, nota 0–1000, comentários e marcações de gramática). A correção é
assíncrona: você envia a redação, recebe um id, e consulta o resultado (ou
recebe um webhook quando ficar pronta).
Explore e teste os endpoints direto no navegador: API_BASE/public/v1/doc.
O contrato OpenAPI cru está em API_BASE/public/v1/openapi.
API_BASE é a URL da API Workfuse do seu ambiente (ex.: https://api.workfuse.com).
Todos os caminhos abaixo são relativos a API_BASE/public/v1/correction.
Autenticação
Toda requisição usa uma API key no header Authorization, no formato Bearer:
Authorization: Bearer wf_sua_chave_aqui
A chave é gerada no painel (Configurações → API Keys), tem escopo por módulo
(precisa do módulo correction) e identifica a sua instituição — você só acessa
as redações enviadas com a sua chave. Detalhes em Autenticação.
Enviar uma redação
POST /essays
curl -X POST "API_BASE/public/v1/correction/essays" \
-H "Authorization: Bearer wf_sua_chave_aqui" \
-H "Content-Type: application/json" \
-d '{
"userId": "aluno-123",
"proposedTheme": "Os desafios da mobilidade urbana no Brasil",
"content": "Texto completo da redação do aluno...",
"motivationalTexts": [
{ "title": "Texto motivador 1", "content": "..." }
],
"externalId": "redacao-42"
}'
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
userId | string | sim | Id do aluno no seu sistema (você correlaciona). |
proposedTheme | string | sim | Tema proposto da redação. |
content | string | sim | Texto da redação. De uma a 800 palavras (limite da correção — ver abaixo), até 50.000 caracteres. Texto sem palavra nenhuma (só espaços ou pontuação) é recusado com 400. |
motivationalTexts | array | sim | Textos motivadores: { title, content?, images? }. Pode ser []. |
title | string | não | Título da redação. |
externalId | string | não | Seu id de referência (eco na consulta). |
Resposta 201 — a correção foi enfileirada:
{ "id": "clz...", "processStatus": "PENDING" }
Guarde o id para consultar o resultado.
Código executável (Node, sem dependências): submit-essay — envia a redação e faz polling até concluir.
Consultar uma redação
GET /essays/:id
curl "API_BASE/public/v1/correction/essays/clz..." \
-H "Authorization: Bearer wf_sua_chave_aqui"
Enquanto processa, enemFeedback vem null. Quando conclui (processStatus: SUCCESS),
vem a correção completa:
{
"id": "clz...",
"userId": "aluno-123",
"externalId": "redacao-42",
"proposedTheme": "Os desafios da mobilidade urbana no Brasil",
"content": "Texto completo...",
"contentWithGrammarFeedback": "Texto com <span data-content=\"...\">marcações</span>...",
"status": "DONE",
"processStatus": "SUCCESS",
"processErrorMessage": null,
"enemFeedback": {
"competence1Score": 160, "competence1Comment": "...",
"competence2Score": 120, "competence2Comment": "...",
"competence3Score": 160, "competence3Comment": "...",
"competence4Score": 160, "competence4Comment": "...",
"competence5Score": 120, "competence5Comment": "...",
"overallScore": 720,
"overallComment": "...",
"isScoreZeroed": false,
"preliminaryCheckComment": null,
"grammarFeedback": [
{
"message": "Esta conjunção deve ser separada por vírgula.",
"shortMessage": "Separe com vírgulas",
"replacements": ["presentes, mas"],
"offset": 43,
"length": 13
}
]
},
"createdAt": "2026-06-10T20:24:05.734Z",
"updatedAt": "2026-06-10T20:25:12.132Z"
}
Notas do enemFeedback:
- Cada competência (C1–C5) tem nota (0/40/80/120/160/200) + comentário;
overallScoreé a soma (0–1000). isScoreZeroed: truequando a redação foi zerada (fuga ao tema, gênero incorreto, etc.) —preliminaryCheckCommentexplica.contentWithGrammarFeedbacké o texto com marcações de gramática em HTML (<span data-content="...">);grammarFeedback[]traz os mesmos itens comoffset/lengthsobre o texto cru.
Listar redações
GET /essays — paginado, escopado à instituição da sua chave.
curl "API_BASE/public/v1/correction/essays?page=1&pageSize=20" \
-H "Authorization: Bearer wf_sua_chave_aqui"
{ "data": [ { "id": "clz...", "processStatus": "SUCCESS", "...": "..." } ],
"pagination": { "page": 1, "pageSize": 20, "pages": 3, "items": 47 } }
Ciclo de vida (processStatus)
PENDING → PROCESSING → SUCCESS
↘ ERROR (processErrorMessage explica)
status é o estado de alto nível para o aluno: SENT (enviada) → DONE (corrigida).
Receber o resultado por webhook
Em vez de fazer polling no GET, registre um webhook (no painel) para receber um
POST assinado quando a correção concluir (essay.corrected) ou falhar
(essay.failed). Veja Webhooks — exemplo de receiver:
nodejs-webhook.
Erros
| HTTP | Quando |
|---|---|
400 | Corpo inválido (campo faltando / fora do limite), incluindo redação com mais de 800 palavras, ou sem palavra nenhuma. |
401 | API key ausente ou inválida. |
403 | A chave não tem o módulo correction no escopo. |
404 | Redação não encontrada (ou de outra instituição). |
500 | Erro interno. |
Redação acima do limite de palavras, ou sem palavra nenhuma
A correção aceita até 800 palavras — 801 já é recusado. As palavras são contadas
como o corretor conta: sequências de letras e números, com hífen, apóstrofo e pontuação
separando (guarda-chuva conta 2). Tema e textos motivadores não entram na conta.
Acima do limite a redação é recusada no envio, com 400: antes ela era aceita e
falhava depois, deixando o aluno sem correção e sem motivo claro. Texto sem
palavra nenhuma (só espaços, quebras de linha ou pontuação) é recusado do mesmo jeito, com a
mensagem A redação está vazia — não há texto para corrigir.
A recusa acontece na validação do corpo, então ela usa o mesmo formato dos demais erros
de validação deste endpoint — error.message é uma string JSON com a lista de
problemas, não um texto solto:
{
"success": false,
"error": {
"name": "ZodError",
"message": "[{ \"code\": \"custom\", \"message\": \"A redação tem 801 palavras e o limite da correção é de até 800 palavras.\", \"path\": [\"content\"] }]"
}
}
Para exibir o motivo ao usuário, faça JSON.parse(error.message) e leia o message do
item com path: ["content"] — não compare a string inteira: ela vem indentada e a
lista pode trazer mais de um problema.
Conte as palavras antes de enviar e encurte o texto — reenviar o mesmo texto dá o mesmo
400. A contagem é por sequência de letras e números: espaço, hífen, apóstrofo e
pontuação separam palavras (guarda-chuva e d'água contam 2 cada; pontuação sozinha não
conta), e o tema e os textos motivadores não entram na conta. Em código, é a regex
/[\p{L}\p{N}_]+/gu (o \b\w+\b do Python). É a mesma régua que o corretor aplica hoje, então um
texto aceito aqui não é recusado depois por tamanho. O limite é do corretor e pode mudar.