Skip to content

Synchronize students ​

Goal: keep the students of a Presente class equal to the ones in your academic system. The recommended way is the CSV import, which is idempotent and validates before writing.

You need: an account with CADASTRAR on the class (for example, COORDENADOR of the discipline; see Permissions) and the class identifier (id).

Overview ​

  1. Sign in and find the class (GET /turmas).
  2. Build the CSV with the students from your system.
  3. Simulate (modo: "simular"): nothing is written; the report says what would happen.
  4. Check the report and apply (modo: "aplicar").
  5. Deactivate whoever left the class in your system (the import never removes anyone).

1. Find the class ​

GET /turmas lists the classes the account can see (each item has id, nome, disciplinaId, instituicaoId). For one specific discipline: GET /disciplinas/{disciplinaId}/turmas. Keep the class id next to its identifier in your system.

2. The CSV ​

csv
nome,email
Ana Example,ana@exemplo.org
Bruno Example,bruno@exemplo.org
  • Required columns: nome (name) and email. Column names accept aliases in Portuguese, Spanish and English (for example name, nombre, correo), ignoring case and accents.
  • Separator: comma, semicolon or tab (detected from the header). Double quotes for values containing the separator; "" for a quote inside a value. Accepts CRLF or LF and the Excel BOM.
  • Limits: 500 lines and 90,000 characters per file. Above that, split into several files and send one at a time.
  • The same email twice in the file: the second occurrence is an error (duplicate).

3. Simulate ​

POST /turmas/{turmaId}/importacoes/alunos, with the CSV as text in the JSON body:

bash
curl -X POST "$API/turmas/$TURMA/importacoes/alunos" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile csv alunos.csv '{csv: $csv, modo: "simular"}')"
js
import { readFileSync } from "node:fs"

const csv = readFileSync("alunos.csv", "utf8")
const r = await fetch(`${API}/turmas/${TURMA}/importacoes/alunos`, {
  method: "POST",
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
  body: JSON.stringify({ csv, modo: "simular" }),
})
const report = await r.json()
python
csv = open("alunos.csv", encoding="utf-8").read()
r = requests.post(f"{API}/turmas/{TURMA}/importacoes/alunos",
                  headers={"Authorization": f"Bearer {token}"},
                  json={"csv": csv, "modo": "simular"})
report = r.json()

The response is 200 (even when some lines have errors: a line error is part of the report, not of the call):

json
{
  "tipo": "alunos",
  "modo": "simular",
  "aplicado": false,
  "erroArquivo": null,
  "colunaAusente": null,
  "resumo": { "VALIDA": 1, "JA_EXISTE": 1 },
  "linhas": [
    { "linha": 2, "situacao": "VALIDA", "rotulo": "ana@exemplo.org" },
    { "linha": 3, "situacao": "JA_EXISTE", "rotulo": "bruno@exemplo.org" }
  ],
  "convites": null
}

Situation of each line:

SituationMeaning
VALIDA(simulate only) would be created or linked.
CRIADANew account created and enrolled; the person receives an invitation by email.
VINCULADAThe email already had an account on the platform; the person was enrolled in this class.
REATIVADAThe enrollment was inactive and came back.
JA_EXISTEAlready enrolled: nothing to do. Repeating the import duplicates nothing and sends no new invitation.
ERROThe line blocks the import; campo and codigo give the reason.

codigo can be required, invalid, too_long, out_of_range, duplicate or not_found. erroArquivo indicates a problem with the whole file: empty, too_many_lines, unterminated_quote, too_large or missing_column (with colunaAusente).

Simulating does not reveal whether an email already has an account on the platform (for privacy); that is why convites is null and new lines show as VALIDA.

4. Apply ​

Repeat the call with "modo": "aplicar". By default it is all or nothing: if any line has ERRO, aplicado is false and nothing is written. Fix the file and send it again. On success, aplicado is true and convites says how many invitation emails went out.

Warning: aplicar with real emails sends real invitations. Test your code on the demo, with fictional data, before pointing it at production.

Partial mode ​

To import only the valid lines and leave the others for later, use "parcial": true with a content lock:

  1. simular with parcial: true: the response carries hashConteudo (SHA-256 of the content) and contagens (importadas, ignoradas).
  2. aplicar with parcial: true and the same hashConteudo, with the same file. If the file changed, the response is 409 CONFLICT.

The partial aplicar response carries ignoradas: the lines that were left out. They exist only in that response (they are not stored), so record them on your side.

5. Whoever left the class ​

The import only adds or reactivates. To reflect departures, read the enrollments and deactivate anyone who is no longer in your system:

bash
# all enrollments, including inactive (paginate: see "Pagination and limits")
curl "$API/turmas/$TURMA/matriculas?limite=200&inativos=true" -H "Authorization: Bearer $TOKEN"

# deactivate an enrollment
curl -X PATCH "$API/turmas/$TURMA/matriculas/$MATRICULA" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"ativo": false}'

Each item in the enrollment list has id (the enrollment), usuarioId, nome, email, ativo and the account status (CONVIDADO, ATIVO or SUSPENSO). Compare by email with the list in your system. Deactivating an enrollment keeps the student's attendance history; to bring them back, import again (REATIVADA) or send {"ativo": true}.

Complete example (JavaScript) ​

js
const auth = { Authorization: `Bearer ${token}` }
const json = { ...auth, "Content-Type": "application/json" }

async function runImport(csv, modo, extra = {}) {
  const r = await fetch(`${API}/turmas/${TURMA}/importacoes/alunos`, { method: "POST", headers: json, body: JSON.stringify({ csv, modo, ...extra }) })
  if (!r.ok) throw new Error(`HTTP ${r.status} ${(await r.json()).code}`)
  return r.json()
}

const simulation = await runImport(csv, "simular")
const errors = simulation.linhas.filter((l) => l.situacao === "ERRO")
if (simulation.erroArquivo || errors.length) {
  console.error("Fix the file:", simulation.erroArquivo ?? errors)
} else {
  const applied = await runImport(csv, "aplicar")
  console.info(applied.resumo, `invitations: ${applied.convites}`)
}

People with a role (staff) ​

To create teachers, coordinators, preceptors and assistants in bulk, and also students of several classes at once, use POST /instituicoes/{instituicaoId}/importacoes/pessoas. It requires ADMINISTRAR on the institution (CURADOR). It works the same way; the columns are:

ColumnRequiredValues
nome, emailyesAs above.
papelyesALUNO, PROFESSOR, COORDENADOR, AUXILIAR, PRECEPTOR, CURADOR.
disciplinadepending on the roleThe discipline code (sigla).
turmadepending on the roleThe class name (within the discipline).
avalia, cadastranosim/não (accepts s, n, yes, true, 1...): the podeAvaliar and podeCadastrar flags.

Scope required per role: ALUNO and PROFESSOR need discipline and class; COORDENADOR needs a discipline; AUXILIAR needs a discipline (and may have a class); CURADOR and PRECEPTOR take neither discipline nor class. A discipline or class that does not exist becomes ERRO with not_found.

contato@presente.app.br