Referência
O que você consulta enquanto integra. O caminho, com exemplos, está nos guias: Montar a página de inscrição, Consultar a situação e Portal de prova.
Endpoints
Base API_BASE/public/v1/admissions, com a chave em Authorization: Bearer wf_.... A chave
fixa a instituição: nenhum endpoint recebe organização ou instituição. Todo endpoint pode
responder 401 e 403 (Erros); abaixo estão só os erros próprios de cada um.
GET /institution
A instituição da chave: name, slug, website, logoUrl e primaryColor. Os três
últimos podem vir null.
GET /courses
O catálogo. Só entra curso ativo com matriz vigente. Cada curso traz id, name, area,
description, degree, modalities[], curriculums[] (id, modality, workloadHours,
durationTerms, termUnit) e hasOpenNotice.
GET /courses/{courseId}
O curso (id, name, area, description, degree) com blocks[], um por matriz
vigente (em geral, uma por modalidade): curriculumId, modality, workloadHours,
durationTerms, termUnit e o admissionNoticeId do edital aberto, ou null.
Erros: 404 ADMISSION_NOT_FOUND.
GET /curriculums/{curriculumId}
A matriz: id, version, modality, courseId, courseName e terms[], cada período com
term, termLabel e disciplines[] (id, name, code, module, workloadHours).
Responde mesmo sem edital aberto.
Erros: 404 ADMISSION_NOT_FOUND.
GET /links/{slug}
O que ?origem= aponta: destination, refusal e origin (Origem e links).
Nunca responde erro: link inexistente, inativo ou vencido volta 200, com o motivo em
refusal.
GET /notices/{noticeId}/showcase
O que o edital oferece: applicationFeeAmount, offers[] (id, courseId, campusId,
courseName, courseDegree, courseModality, campusName, campusCity, campusState) e
journeys[], as formas de ingresso ativas no edital (id, entryFormName, evaluationType,
examRequired, chargesFee, examPeriodEnded).
Erros: 404 ADMISSION_NOT_FOUND (edital inexistente, em rascunho ou de outra instituição) ·
422 CANDIDACY_NOTICE_NOT_ACCEPTING (fora da janela de inscrição).
GET /notices/{noticeId}/application-fields
Query journey, obrigatória: o journeys[].id da vitrine. Devolve fields[] e
enrollmentDocuments[] (Os campos do formulário).
Erros: 404 ADMISSION_NOT_FOUND · 422 CANDIDACY_NOTICE_NOT_ACCEPTING.
GET /postal-codes/{cep}
O endereço de um CEP de 8 dígitos, com ou sem hífen: street, district, city e state.
Erros: 404 ADMISSION_NOT_FOUND (CEP inexistente, ou a consulta fora do ar).
POST /applications/draft
Grava a inscrição como rascunho. Corpo: o corpo do envio, com a forma de
ingresso, o aceite e o par CPF + nascimento opcionais. Devolve a
inscrição curta, mais rejoined (true quando a chamada gravou num
rascunho que já existia, o do token ou o do CPF; false quando criou um) e draftToken
(O rascunho sem CPF).
Erros: 404 CANDIDACY_NOTICE_NOT_FOUND · 409 CANDIDACY_NOT_EDITABLE,
CANDIDACY_DRAFT_GONE · 422 CANDIDACY_NOTICE_NOT_ACCEPTING,
CANDIDACY_ENTRY_FORM_NOT_AVAILABLE, CANDIDACY_IDENTITY_MISMATCH.
POST /applications
Conclui a inscrição em uma chamada. Mesmo corpo, com a forma de ingresso, o CPF, o nascimento
e o dataConsentAt obrigatórios. Devolve a inscrição curta. Nos dois
POST, edital ainda em rascunho responde 422 CANDIDACY_NOTICE_NOT_ACCEPTING, com as datas
nulas.
Erros: 404 CANDIDACY_NOTICE_NOT_FOUND · 409 CANDIDACY_NOT_EDITABLE,
EXAM_PERIOD_ENDED, CANDIDACY_CUTOFF_MISSING · 422 CANDIDACY_NOTICE_NOT_ACCEPTING,
CANDIDACY_CONSENT_REQUIRED, CANDIDACY_ENTRY_FORM_NOT_AVAILABLE,
CANDIDACY_IDENTITY_MISMATCH, CANDIDACY_MISSING_ANSWERS, ENEM_SCORE_INVALID.
GET /applications/{id}
A inscrição inteira, pelo número que o envio devolveu.
Erros: 404 CANDIDACY_NOT_FOUND, também para o rascunho que ainda não tem CPF.
GET /applications
As inscrições de uma pessoa na instituição, em { "data": [...] }, da alterada mais
recentemente para a mais antiga. Cada linha é a inscrição com journey e
timeline vazias.
| Query | |
|---|---|
candidateCpf | 11 dígitos, com os verificadores corretos |
candidateEmail | comparado inteiro, sem diferenciar maiúsculas. Pode trazer pessoas diferentes que usam o mesmo e-mail |
admissionNoticeId | opcional; estreita para um edital |
Informe candidateCpf ou candidateEmail, nunca os dois. Sem inscrição, { "data": [] }.
O rascunho sem CPF não aparece.
Erros: 400 (nenhum dos dois, os dois juntos, ou um deles inválido).
GET /exam-candidates
Quem pode fazer a prova: inscrições em avaliação, no vestibular com prova exigida e com
tentativa restante, de editais abertos ou encerrados. Query admissionNoticeId, opcional.
Cada linha traz applicationId, candidateName, candidateCpf, candidateEmail,
admissionNotice (id, name), entryFormName, courseName, attemptsUsed e
attemptLimit. Nunca traz a nota.
POST /applications/{id}/exam-attempts
A nota de uma tentativa. Corpo: attemptNumber (inteiro de 1 a 2147483647) e score (0 a 10,
até duas casas). Devolve applicationId, outcome (APPLIED · DUPLICATE · DISCARDED),
status, attemptsUsed e attemptLimit. A ordem das respostas está em
Portal de prova.
Erros: 404 CANDIDACY_NOT_FOUND · 409 CANDIDACY_NOT_UNDER_REVIEW, EXAM_NOT_REQUIRED ·
422 SCORE_OUT_OF_SCALE, SCORE_PRECISION_EXCEEDED.
Corpo do envio
Vale para POST /applications e para POST /applications/draft.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
admissionNoticeId | string | sim | o edital |
admissionOfferId | string | sim | a oferta escolhida, que carrega curso e polo |
admissionNoticeEntryFormId | string | no envio | a forma de ingresso (journeys[].id da vitrine) |
candidateName | string | sim | nome completo, até 200 caracteres |
candidateEmail | string | sim | e-mail, até 254 caracteres |
candidatePhone | string | sim | telefone, até 32 caracteres. Não normalizamos: com ou sem máscara, com ou sem +55, ele volta como foi enviado |
candidateCpf | string | no envio | só dígitos, 11 posições, com os verificadores corretos. No rascunho pode faltar, junto com candidateBirthDate |
candidateBirthDate | data | no envio | AAAA-MM-DD, ou data e hora ISO com fuso (Z ou ±hh:mm), e vale o dia escrito. Não futura, não antes de 1900, de quem tem 14 anos ou mais. Vem junto com o CPF: um sem o outro responde 400 |
dataConsentAt | data | no envio | quando o candidato aceitou o tratamento de dados. Sem ele, o envio responde 422 |
secondChoiceOfferId | string | não | a 2ª opção de curso, quando o edital a oferece |
formAnswers | objeto | não | as respostas, com a fieldKey como chave (Os campos do formulário) |
captureLinkId | string | não | o link de captação por onde a pessoa chegou |
captureOriginId | string | não | a origem, quando o endereço nomeou uma sem link salvo |
draftToken | string | não | a chave do rascunho ainda sem CPF, que o POST /applications/draft devolveu |
O rascunho sem CPF e o draftToken
O primeiro passo da página da Workfuse pede só nome, celular, e-mail e curso, e já grava o
rascunho: é o que deixa a instituição falar com quem começou e não terminou. Sem CPF não há
chave natural, então a resposta traz um draftToken.
| Regra | O que significa para você |
|---|---|
guarde o draftToken na sessão do seu site e mande-o em toda chamada seguinte | sem ele, cada chamada abre um rascunho novo |
| ele vale até o CPF chegar | a chamada que traz CPF e nascimento devolve draftToken: null; daí em diante a chave é edital + CPF. Não reenvie o token velho numa chamada sem CPF: ela abre um rascunho novo |
| o CPF já tem inscrição neste edital | com o nascimento conferindo, a chamada cai nela, e o rascunho do token deixa de existir. Com o nascimento errado, 422 CANDIDACY_IDENTITY_MISMATCH, e o rascunho continua |
| o rascunho sem CPF não é consultável | GET /applications/{id} responde 404 e a busca por CPF ou e-mail não o acha. A secretaria o vê no painel |
| o primeiro rascunho sem CPF não é idempotente | repetir a chamada sem o token (um retry por tempo esgotado, por exemplo) abre um segundo rascunho. Repita só com o draftToken em mãos |
o token não é o id | são 32 bytes aleatórios que só quem criou o rascunho recebe. Não o mostre a outra pessoa |
Os campos do formulário
GET /notices/{noticeId}/application-fields diz quais campos o edital pede e com que
exigência. O que cada um é, e para onde vai no corpo, é fixo:
fieldKey | Rótulo | Vai em | Valores |
|---|---|---|---|
FULL_NAME | Nome completo | candidateName | texto |
MOBILE_PHONE | Celular | candidatePhone | texto |
EMAIL | candidateEmail | texto | |
COURSE · CAMPUS | Curso · Polo | admissionOfferId | a oferta carrega os dois |
ENTRY_FORM | Forma de ingresso | admissionNoticeEntryFormId | journeys[].id da vitrine |
CPF | CPF | candidateCpf | 11 dígitos |
BIRTH_DATE | Data de nascimento | candidateBirthDate | AAAA-MM-DD |
SECOND_COURSE_OPTION | 2ª opção de curso | secondChoiceOfferId | outra oferta da vitrine |
RG | RG | formAnswers.RG | texto, como está no documento, com 5 a 13 dígitos, não todos iguais |
GENDER | Sexo | formAnswers.GENDER | male · female · other |
MARITAL_STATUS | Estado civil | formAnswers.MARITAL_STATUS | SINGLE · MARRIED · DIVORCED · WIDOWED · OTHER |
SPECIAL_NEEDS | Possui necessidade especial? | formAnswers | "true" · "false" |
SPECIAL_NEEDS_DESCRIPTION | Descrição da necessidade especial | formAnswers | texto; obrigatório quando SPECIAL_NEEDS é "true" |
POSTAL_CODE · NUMBER · COMPLEMENT | CEP · Número · Complemento | formAnswers | o resto do endereço vem de GET /postal-codes/{cep} |
ENEM_SCORE · ENEM_YEAR | Nota do ENEM · Ano do ENEM | formAnswers | número. A nota vai de 0 a 1000, com até duas casas, e é obrigatória na forma Nota do ENEM |
PREVIOUS_INSTITUTION · PREVIOUS_COURSE | Instituição anterior · Curso anterior | formAnswers | texto |
CLASS_SECTION · REFERRAL_CODE · PHONE · SOCIAL_NAME · HOW_DID_YOU_HEAR | Turma · Código de indicação · Telefone · Nome social · Como você conheceu a instituição? | formAnswers | texto |
GUARDIAN_NAME · GUARDIAN_CPF · GUARDIAN_PHONE | Nome · CPF · Telefone do responsável | formAnswers | texto |
O que a nossa página confere antes de chamar a API
A API recusa com 400 o que fere o formato ou a regra do próprio campo, e com 422 o que fere a
regra do edital. A página da Workfuse confere mais cedo, no campo, para o erro de digitação não
custar uma chamada. Vale copiar:
| Campo | Regra da nossa página | A API cobra? |
|---|---|---|
| Nome completo | ao menos duas palavras com 2 letras ou mais ("Maria e Silva" passa) | só não vazio, até 200 |
| a mesma expressão da API | sim | |
| Celular | 11 dígitos com DDD de 11 a 99, e o 3º dígito é 9 | só não vazio, até 32 |
| CPF | os dois verificadores fecham | sim |
| Data de nascimento | data real, não futura, não antes de 1900, 14 anos ou mais | sim, com folga de fuso: a API aceita quem está a até dois dias do 14º aniversário. Corte os 14 anos na sua página, pelo calendário da pessoa |
| RG | de 5 a 13 dígitos, não todos iguais; letras do órgão emissor livres | sim |
| CEP | 8 dígitos | não |
Origem e links
?origem= no endereço do seu site resolve para um link salvo ou para uma origem do
cadastro. GET /links/{slug} diz qual:
{
"destination": "https://faculdade-horizonte.example",
"refusal": null,
"origin": {
"captureLinkId": "cmu4sh98y00dqp0mbio5wjxgf",
"admissionNoticeId": "cmu4sh97w00chp0mb551lq2jq",
"captureOriginId": "cmu4sh97s00ccp0mbcuul1pi8",
"campusId": null,
"campaignCode": "PANFLETO-CENTRO"
}
}
Repasse captureLinkId (ou captureOriginId, quando não há link salvo) na primeira
chamada, a que cria a inscrição: o primeiro POST /applications/draft, ou o envio quando não
há rascunho. A origem é gravada quando a inscrição nasce, e as chamadas seguintes não a mudam.
refusal vale NOT_FOUND, INACTIVE ou EXPIRED quando o link não leva a uma inscrição:
mande a pessoa para destination (o site da instituição, ou o catálogo de cursos) em vez de
seguir. A origem que fica na inscrição segue esta ordem:
origem do link salvo → origem do endereço → origem padrão do edital → desconhecida
A inscrição
O que GET /applications/{id} devolve: a inscrição inteira, como o candidato a vê na área
dele, mais o CPF em claro e os ids que o seu sistema guarda.
| Campo | O que traz |
|---|---|
id · status | o número e uma das treze situações |
admissionNoticeId · noticeName · admissionOfferId · admissionNoticeEntryFormId | o edital (id e nome), a oferta e a forma de ingresso. A forma é null no lead |
courseName · campusName · campusCity · journeyName | os nomes do curso, do polo e da forma |
secondChoiceCourseName | a 2ª opção, quando houve |
examRequired | se a forma tem prova neste edital |
journey[] | as etapas, futuras incluídas: kind, position, state (DONE · CURRENT · FUTURE · HALTED), owner (CANDIDATE · INSTITUTION), exam, evaluation e document |
journey[].exam | só na etapa EVALUATION com prova: { url, opensAt, closesAt }. A url é o Endereço da prova com applicationId=<id> acrescentado, e só vem dentro da janela, com tentativa sobrando; as datas vêm null quando o edital não as define |
journey[].evaluation | só na etapa EVALUATION do vestibular e do ENEM: score e source (INTEGRATION · MANUAL · ENEM), cutoffScore, scale, shortfall, result (PENDING · BELOW_CUTOFF · APPROVED · REJECTED · EXPIRED), attemptsUsed, attemptLimit, attemptsRemaining, decidedBy, decidedAt, manualScoredAt e examPeriodEnded |
journey[].document | só na etapa EVALUATION da Transferência Externa e da 2ª Graduação: kind (UNDERGRADUATE_TRANSCRIPT · UNDERGRADUATE_DIPLOMA), situation (IN_REVIEW · APPROVED · RESUBMISSION_REQUESTED · REJECTED, ou null sem envio), fileName, uploadedAt, note, illegible, resubmissionRequestedAt, uploadOpen, noticeDueOn e dueOn (dias AAAA-MM-DD). O arquivo nunca vem, e o envio é só do candidato, na área dele: por isso, nesta porta, uploadOpen vem sempre false. Para saber se o candidato ainda deve enviar, leia situation (null ou RESUBMISSION_REQUESTED) com dueOn |
feePolicy · feeAmount | CHARGED ou EXEMPT, e o valor congelado na inscrição |
candidateName · candidateEmail · candidatePhone · candidateBirthDate | o que a pessoa declarou |
candidateCpf | os 11 dígitos, em claro |
createdAt · updatedAt | quando foi criada e a última gravação. O updatedAt muda com a situação e os dados; o link da prova e examPeriodEnded são calculados na leitura e não o movem |
timeline[] | fromStatus, toStatus, actorKind (CANDIDATE · PARTNER · STAFF · SYSTEM), reason e at. reason é o motivo: o texto que a secretaria escreveu, ou o motivo automático (como a aprovação pela nota); actorKind diz de que lado veio a mudança, sem dizer quem |
Na lista de GET /applications, journey e timeline vêm vazias.
A inscrição curta
O que os dois POST devolvem: id, status, admissionNoticeId, admissionOfferId,
admissionNoticeEntryFormId, feePolicy, feeAmount, createdAt e updatedAt. O rascunho
acrescenta rejoined e draftToken.
As treze situações
status | Na tela | Final |
|---|---|---|
DRAFT | Rascunho | |
SUBMITTED | Concluída | |
FEE_PENDING | Taxa pendente | |
UNDER_REVIEW | Em avaliação. Na Transferência Externa e na 2ª Graduação, com o documento: Avaliação · Aguardando envio, Em análise ou Reenvio de documento solicitado | |
DOCUMENTS_PENDING | Documentos pendentes | |
APPROVED | Aprovada | |
CALLED | Convocada | |
SENT_TO_ENROLLMENT | Encaminhada à matrícula | |
ENROLLED | Matriculada | sim |
REJECTED | Reprovada | sim |
ABSENT | Ausente na prova | sim |
CANCELLED | Cancelada | sim |
EXPIRED | Expirada | sim |
As cinco finais não mudam mais: desfazer é uma inscrição nova.
As seis etapas da jornada
journey[].kind usa o mesmo vocabulário em toda a plataforma, e a ordem é do edital:
REGISTRATION · DOCUMENTS · EVALUATION · PAYMENT · CALL · ENROLLMENT
Cadastro, documentos, avaliação, pagamento, convocação e matrícula. As etapas que a inscrição
percorre dependem da forma de ingresso, e duas regras do edital ajustam: edital sem taxa
pula PAYMENT, e "liberar a avaliação só após o pagamento" põe PAYMENT antes de
EVALUATION.
Erros
O corpo do erro
A forma depende de onde a recusa nasce:
| Quando | HTTP | Corpo |
|---|---|---|
| regra de negócio | 404 409 422 | { "errorCode": "...", "message": "...", "details": {...} }. details só vem em alguns códigos |
| validação do formato | 400 | { "success": false, "error": { "name": "ZodError", "message": "..." } }. message é uma string JSON com a lista de problemas: faça JSON.parse e leia o path de cada item |
| chave ausente ou inválida | 401 | { "error": "...", "code": "API_KEY_MISSING" } ou "API_KEY_INVALID" |
| chave sem o módulo no escopo | 403 | { "error": "...", "code": "MODULE_NOT_IN_KEY_SCOPE" } |
| módulo não contratado pela organização | 403 | { "error": "CAPABILITY_NOT_ENABLED", "code": "admission.selection", "message": "..." } |
Reconheça o erro pelo código, nunca pelo texto de message.
Por status
| HTTP | Quando |
|---|---|
400 | corpo ou query fora do formato: CPF, e-mail, campo faltando, CPF sem nascimento (ou o contrário), draftToken fora do formato, e a data de nascimento ou o RG fora da regra da tabela |
401 | chave ausente, inválida, desabilitada ou revogada |
403 | a chave não tem o módulo de Processo seletivo no escopo, ou a organização não contratou o módulo |
404 | curso, matriz, edital ou inscrição não encontrados na instituição da chave, e também o edital em rascunho (na vitrine e no formulário), a forma inativa no edital e o rascunho que ainda não tem CPF |
409 | a inscrição já foi concluída, outra chamada deu CPF ao rascunho do token no mesmo instante, o período de prova terminou, a forma ficou sem nota de corte, ou a nota chegou a uma inscrição fora de avaliação. O duplo clique simultâneo não cai aqui: os dois recebem 200 |
422 | fora da janela do edital (ou edital ainda em rascunho, no envio), consentimento faltando, dados que não conferem, campo obrigatório vazio, forma desabilitada na instituição, nota fora da escala |
O 404 também é recusa de visibilidade, de propósito. Uma inscrição de outra
instituição, um edital em rascunho na vitrine e uma forma inativa respondem igual a "não existe":
distinguir os casos contaria ao chamador o que a instituição ainda não publicou, ou o que não é
dela.
Os códigos
errorCode | HTTP | O que fazer |
|---|---|---|
ADMISSION_NOT_FOUND | 404 | curso, matriz, edital ou CEP não encontrados. Confira o id |
CANDIDACY_NOTICE_NOT_FOUND | 404 | algum id do corpo não vale para este edital: o edital, a oferta (admissionOfferId, secondChoiceOfferId), a forma de ingresso (inexistente ou inativa no edital), o link ou a origem |
CANDIDACY_NOT_FOUND | 404 | a inscrição não existe na instituição da chave. Na leitura pelo número, também o rascunho que ainda não tem CPF |
CANDIDACY_NOT_EDITABLE | 409 | a inscrição já foi concluída (details.status). Mostre a situação dela em vez de reenviar |
CANDIDACY_DRAFT_GONE | 409 | outra chamada deu CPF ao rascunho do draftToken no mesmo instante (outra aba, por exemplo). Siga para o passo do CPF: a chamada com CPF e nascimento acha a inscrição |
EXAM_PERIOD_ENDED | 409 | o período de prova do vestibular terminou, e a forma não recebe inscrição nova. A vitrine já avisa com journeys[].examPeriodEnded |
CANDIDACY_CUTOFF_MISSING | 409 | a forma por nota ficou sem nota de corte. Use outra forma, ou tente de novo depois que a instituição definir o corte |
CANDIDACY_NOT_UNDER_REVIEW · EXAM_NOT_REQUIRED | 409 | a nota chegou a uma inscrição fora de avaliação (details.status) · a forma não aplica prova neste edital |
CANDIDACY_NOTICE_NOT_ACCEPTING | 422 | o edital não recebe inscrições hoje, ou ainda está em rascunho. details traz opensAt e closesAt da próxima janela, ou null nos dois quando não há próxima |
CANDIDACY_CONSENT_REQUIRED | 422 | mande dataConsentAt: sem aceite não há como concluir |
CANDIDACY_ENTRY_FORM_NOT_AVAILABLE | 422 | a forma de ingresso foi desabilitada na instituição. Ofereça outra |
CANDIDACY_IDENTITY_MISMATCH | 422 | o CPF já tem inscrição neste edital com outra data de nascimento. Peça para a pessoa conferir os dois, sem dizer qual não conferiu |
CANDIDACY_MISSING_ANSWERS | 422 | falta campo obrigatório; details.missing lista as fieldKey. Na forma Nota do ENEM, inclui o ENEM_SCORE |
ENEM_SCORE_INVALID | 422 | a nota do ENEM não é um número de 0 a 1000 com até duas casas |
SCORE_OUT_OF_SCALE · SCORE_PRECISION_EXCEEDED | 422 | a nota da tentativa está fora de 0 a 10 (details traz min e max) · tem mais de duas casas |
Limites
Quantas vezes
Não há teto de chamadas por CPF nesta API. O que limita a integração é a própria chave:
desabilitada ou revogada no painel, toda chamada responde 401 na hora.
Quanto cabe
Recusa com 400 antes de qualquer regra:
| Campo | Teto |
|---|---|
candidateName | 200 caracteres |
candidateEmail | 254 caracteres |
candidatePhone | 32 caracteres |
formAnswers | 29 chaves (o catálogo inteiro), 32 KB serializados e 32 níveis de aninhamento |
Nenhum dos três tetos de formAnswers é alcançado por um envio real.
Swagger
| Recurso | URL |
|---|---|
| Swagger interativo | API_BASE/public/v1/doc |
| Contrato OpenAPI (JSON) | API_BASE/public/v1/openapi |
Clique em Authorize e cole só a chave (wf_...): o Swagger acrescenta o Bearer, e
Bearer wf_... no campo responde 401. Mais em Referência da API.