agentleFS
pedroiff0pedroiff0/awesome-skills/skills/data-science/suap-iff-api/SKILL.md

suap-iff-api

Skill1 starsChanged 45 days agoMITView on GitHub

Authenticate to and consume the SUAP IFF (Instituto Federal Fluminense) API v2 from the CLI — obtain the JWT access/refresh token via matricula+senha, then fetch student data (periodos letivos, dados do aluno, boletim). Use when the user has a suap.iff.edu.br matricula+senha and wants to extract academic data, integrate with the SUAP IFF API, or reference suap.iff.edu.br/api endpoints.

  • Sends data out

The file

We split this skill into the rules it gives an agent

1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    If Bearer isn't enough, get a sessionid cookie by logging into the web login page and reuse it as Cookie: sessionid=....

    line 56

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

SUAP IFF API (v2 / django-ninja)

When to use

  • User has a SUAP IFF matricula + senha and wants data programmatically (boletim, períodos, dados do aluno, calendário).
  • User references suap.iff.edu.br/api/....

Key facts / pitfalls (learned the hard way)

  • The SUAP instance is https://suap.iff.edu.br — NOT suap.ifrn.edu.br. They are different instances with different data. The IFRN docs/swagger describe the SAME django-ninja API shape, but the user's data lives on the IFF instance.
  • Real API v2 endpoints are under /api/ensino/... (django-ninja), NOT /api/v2/edu/.... The /api/v2/... edu paths mostly 404.
  • The WAF blocks requests with no User-Agent → returns 403 Forbidden (nginx). ALWAYS send -H "User-Agent: Mozilla/5.0".
  • The auth endpoint does NOT use Basic Auth. Send username + password in the JSON body. (Sending Basic Auth or a user_token body first yields 422 "Field required".)

Step 1 — Get the token

curl -s -X POST "https://suap.iff.edu.br/api/v2/autenticacao/token/" \
  -H "User-Agent: Mozilla/5.0" \
  -H "Content-Type: application/json" \
  -d '{"username": "<MATRICULA>", "password": "<SENHA>"}'

Returns HTTP 200 with:

{"username": "<MAT>", "refresh": "<JWT>", "access": "<JWT>"}

Save it (e.g. /tmp/suap_token.json). The access JWT is what you send as Bearer.

Step 2 — Use the token

Authorization: Bearer <access>

Endpoints that WORK for a regular student profile (tested, real data)

MethodPathResult
GET/api/ensino/meus-periodos-letivos/200, {"results":[{"ano_letivo":2026,"periodo_letivo":1}, ...], "count":N}
GET/api/ensino/aluno-matriculado/?matricula=<MAT>200, nome, matricula, curso, campus, periodo_atual, nascimento, cpf, foto_base64
GET/api/ensino/meu-boletim/{ano_letivo}/{periodo_letivo}/200 but count:0 when no grades are posted (matches the empty UI)

Endpoints that do NOT return data for a regular student (tested)

PathResultNote
/api/ensino/meu-calendario-academico/{ano}/{periodo}/404 on EVERY periodNot exposed for this profile — NOT a param error (schema wants integer ano + integer periodo_letivo)
/api/ensino/meus-diarios/{ano}/{periodo}/404Not exposed
/api/ensino/meus-diarios-ead/401 UnauthorizedNeeds a different scope (OAuth2TokenAuth/TrustedAppAuth)
/api/ensino/meus-dados-aluno/500Server bug on the IFF instance (not auth)
/api/ensino/meu-boletim/...200 count:0Empty when UI is empty (confirmed by user)

The 404 on calendário across ALL 7 valid periods (with valid Bearer OR valid sessionid) means the IFF instance simply does not expose that endpoint for this profile. It is a server-side per-profile limitation, not a token/param mistake.

Alternative auth: SessionAuth (web sessionid cookie)

If Bearer isn't enough, get a sessionid cookie by logging into the web login page and reuse it as Cookie: sessionid=.... Same endpoint limitations apply (calendário/diarios still 404/401) — the limit is server-side, not auth-method dependent.

import urllib.request, urllib.parse, http.cookiejar, re
MAT, PW = "<MATRICULA>", "<SENHA>"
cj = http.cookiejar.CookieJar()
op = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
html = op.open(urllib.request.Request('https://suap.iff.edu.br/accounts/login/',
           headers={'User-Agent':'Mozilla/5.0'})).read().decode('utf-8','ignore')
csrf = re.search(r'name="csrfmiddlewaretoken" value="([^"]+)"', html).group(1)
data = urllib.parse.urlencode({'csrfmiddlewaretoken':csrf,'username':MAT,
           'password':PW,'next':'/'}).encode()
op.open(urllib.request.Request('https://suap.iff.edu.br/accounts/login/', data=data,
      headers={'User-Agent':'Mozilla/5.0','Referer':'https://suap.iff.edu.br/accounts/login/',
               'Content-Type':'application/x-www-form-urlencoded'}))
sid = next(c.value for c in cj if c.name=='sessionid')

Extracting boletim/calendário when the API is empty

The API returns empty/404 for boletim grades and calendário for this profile. That data DOES exist in the web UI (/edu/... area). Use authenticated scraping with the sessionid cookie (build the opener above, then GET the calendar/boletim page and parse the HTML). There is NO historico endpoint in the IFF API v2.

Discovering exact paths/params

The IFRN instance serves its OpenAPI schema at /api/openapi.json (django-ninja). The IFF instance blocks it (403), but the API shape is identical. Download it from IFRN to learn exact paths/schemas, then filter paths containing: boletim, periodo, aluno, histor, nota, disciplina, calendario, diario, ensino.

Minimal Python helper (token + periods + aluno)

import json, urllib.request, urllib.error
BASE="https://suap.iff.edu.br"
def auth(mat,pw):
    req=urllib.request.Request(BASE+"/api/v2/autenticacao/token/",
        data=json.dumps({"username":mat,"password":pw}).encode(),
        headers={"User-Agent":"Mozilla/5.0","Content-Type":"application/json"})
    return json.loads(urllib.request.urlopen(req).read())
def get(path, token):
    H={"User-Agent":"Mozilla/5.0","Accept":"application/json",
       "Authorization":f"Bearer {token['access']}"}
    return json.loads(urllib.request.urlopen(
        urllib.request.Request(BASE+path, headers=H)).read())
# t=auth("<MAT>","<PW>")
# print(get("/api/ensino/meus-periodos-letivos/", t))
# print(get(f"/api/ensino/aluno-matriculado/?matricula=<MAT>", t))

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.