Authentication
The API has no API key. The caller signs in as a person (email and password) and receives two tokens:
| Token | Lifetime | Purpose |
|---|---|---|
accessToken | short (expiresIn seconds; currently 15 minutes) | Goes on every call: Authorization: Bearer <accessToken>. |
refreshToken | long (up to 30 days; the institution can shorten it) | Only to get a new pair at POST /auth/refresh. |
Always use the expiresIn value from the response instead of hard-coding 15 minutes.
Account for the integration
Ask the institution's curator to create an account just for the integration (for example integracao@exemplo.org), with the minimum role needed (see Permissions). The person receives an invitation by email and sets the password in the app. Avoid using a staff member's account: if they leave, the integration stops.
Sign in
POST /auth/login with email and senha (password). The response is the session:
curl -X POST "$API/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"integracao@exemplo.org","senha":"********"}'const r = await fetch(`${API}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "integracao@exemplo.org", senha: process.env.SENHA }),
})
const session = await r.json()import os, requests
r = requests.post(f"{API}/auth/login", json={"email": "integracao@exemplo.org", "senha": os.environ["SENHA"]})
session = r.json(){
"accessToken": "eyJhbGciOi...",
"refreshToken": "q3v8...",
"expiresIn": 900,
"usuario": {
"id": "0198a000-0000-7000-8000-000000000001",
"email": "integracao@exemplo.org",
"nome": "Example Integration",
"locale": "en-US",
"nationality": null,
"vinculos": [
{ "papel": "CURADOR", "instituicaoId": "0198a000-0000-7000-8000-0000000000aa", "disciplinaId": null, "turmaId": null, "podeAvaliar": false, "podeCadastrar": false }
]
}
}usuario.vinculos shows the account's roles and where they apply (institution, discipline or class). GET /auth/me returns the same information at any time.
Calling the API
curl "$API/auth/me" -H "Authorization: Bearer $TOKEN"Renewing
Before the accessToken expires (or on 401 UNAUTHENTICATED), exchange the refreshToken:
curl -X POST "$API/auth/refresh" -H "Content-Type: application/json" \
-d '{"refreshToken":"q3v8..."}'The response is a new session, with a new refreshToken. Rules that matter when you implement this:
- Each
refreshTokenworks only once. Always keep the latest pair. - Presenting a
refreshTokenthat was already used is treated as possible theft: all of the account's sessions are ended and you must sign in again with the password. So, in an integration with several processes, renew in one place only and share the resulting token; two simultaneous renewals with the samerefreshTokenmake one of them fail with401. - If renewal fails with
401, sign in again withPOST /auth/login.
Signing out
POST /auth/logout with the refreshToken ends that session. POST /auth/logout-todas (authenticated) ends all of the account's sessions.
Lockout after failed attempts
Repeated wrong passwords for the same email block new attempts for a while (default: 5 failures, 15-minute block). In that case, and when too many attempts come from the same origin, the response is 429 RATE_LIMITED with retryAfterSeg and the Retry-After header. Do not retry in a loop: see Pagination and limits. A wrong password, an unknown email and a locked account get the same 401 response, on purpose, so that it does not reveal which accounts exist.
Keep the secret safe
The password and the refreshToken give the same access the person would have. Keep them in a secrets vault or in a server environment variable, never in code, in the repository or in logs.