Pular para o conteúdo principal

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).

Documentação interativa (Swagger)

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"
}'
CampoTipoObrigatórioDescrição
userIdstringsimId do aluno no seu sistema (você correlaciona).
proposedThemestringsimTema proposto da redação.
contentstringsimTexto 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.
motivationalTextsarraysimTextos motivadores: { title, content?, images? }. Pode ser [].
titlestringnãoTítulo da redação.
externalIdstringnãoSeu 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: true quando a redação foi zerada (fuga ao tema, gênero incorreto, etc.) — preliminaryCheckComment explica.
  • contentWithGrammarFeedback é o texto com marcações de gramática em HTML (<span data-content="...">); grammarFeedback[] traz os mesmos itens com offset/length sobre 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​

HTTPQuando
400Corpo inválido (campo faltando / fora do limite), incluindo redação com mais de 800 palavras, ou sem palavra nenhuma.
401API key ausente ou inválida.
403A chave não tem o módulo correction no escopo.
404Redação não encontrada (ou de outra instituição).
500Erro 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.